Commit 5d4a1199b5

5d4a1199b51602f64985982c82de9b7c537583c6

parent: e86c55dbfa

Unsigned

cmc <hello@cleberg.net> · 2024-07-29 00:46 UTC

conversion from Zola to Weblorg

Layout: unified · split

.gitignore +1
@@ -1,3 +1,4 @@
1.build
1.DS_Store 2.DS_Store
2.vscode 3.vscode
3public 4public
README.md deleted −32
@@ -1,32 +0,0 @@
1# cleberg.net
2
3[cleberg.net](https://cleberg.net) is my personal webpage and blog.
4
5## Overview
6
7This website & blog uses Markdown for content, HTML for templates, built and
8published with [Zola](https://getzola.org).
9
10## Configuration
11
12All configuration options are available within the =config.toml= file. Refer to
13the Zola documentation for further configuration options.
14
15## Building
16
17To get running:
18
19```sh
20git clone https://git.sr.ht/~cyborg/cleberg.net && \
21cd cleberg.net && \
22zola build
23```
24
25## Publishing
26
27Use `zola build` and deploy to whichever deployment target you prefer.
28
29## Tasks
30
31Refer to the [todo page](https://todo.sr.ht/~cyborg/cleberg.net) for open
32tickets or to submit a new ticket.
README.org added +44
@@ -0,0 +1,44 @@
1* cleberg.net
2
3[[https://cleberg.net][cleberg.net]] is my personal webpage and blog.
4
5** Overview
6
7This website & blog uses [[https://orgmode.org/][Org-Mode]], published with [[https://github.com/emacs-love/weblorg][weblorg]].
8
9** Configuration
10
11All configuration options are available within the =publish.el= file. Refer to
12the weblorg documentation for further configuration options.
13
14** Building & Publishing
15
16Local testing is available via [[https://www.gnu.org/software/emacs/][Emacs]] or through the command line.
17
18#+begin_src sh
19git clone https://git.cleberg.net/cleberg.net.git && \
20cd cleberg.net && \
21emacs -nw
22#+end_src
23
24Within Emacs, open any of the repository files. In Doom, I do this with =Spc f
25f= and selecting =README.org=. Make any changes necessary to customize the
26project.
27
28To publish, you can use the =build.sh= script (change the deployment target!) or
29you can run the following commands.
30
31Use the =ENV= environment variable to determine which base URL weblorg will use.
32If ENV is ommitted, it will default to =localhost:8000=. If =ENV=prod=, weblorg
33will look in the =publish.el= file for the production base URL.
34
35#+begin_src sh
36ENV=prod emacs --script publish.el
37#+end_src
38
39The files will be published to the =.build= directory. You can deploy these
40files to the target through any number of methods, such as =scp= or SFTP.
41
42** Tasks
43
44Refer to the [[https://todo.sr.ht/~cyborg/cleberg.net][todo page]] for open tickets or to submit a new ticket.
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_feeds = true
9feed_filenames = ["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/#2022-03-24-server-hardening.org# added +293
@@ -0,0 +1,293 @@
1#+date: <2022-03-24>
2#+title: Hardening a Public-Facing Home Server
3#+description:
4
5
6* Post Updates
7
8#+begin_quote
9After reviewing this post today (2022-10-04), I noticed quite a few gaps in my
10write-up and wanted to add a few things, even though this blog is really just a
11retrospective and knowledge dump for myself. I left things intact and simply
12crossed them out (+like this+) for posterity.
13#+end_quote
14
15* Planning Data Flows & Security
16
17** My Personal Data Flow
18
19#+begin_src
20 ┌───────┐ ┌─────────────────┐
21 ┌──► VLAN1 ├───► Private Devices │
22 │ └───────┘ └─────────────────┘
23┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
24│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
25└──────────┘ └────────┘ └──────────┘ └────────┘ │
26 │ ┌───────┐ ┌───────────────┐
27 └──► VLAN2 ├───► Public Server │
28 └───────┘ └───────────────┘
29#+end_src
30
31** Thought Process
32
33To serve content from your home server and harden your security posture, you
34have to think about the transport of data from =server= to =client=.
35
36Let's start with the actual server itself. Think about the following:
37
38- Do I have a firewall enabled? Do I need to update this to allow new ports or
39 IPs?
40- Do I have an IPS/IDS that may prevent outside traffic?
41- Do I have any other security software installed?
42- Are the services hosted inside Docker containers, behind a reverse proxy, or
43 virtualized? If so, are they configured to allow outside traffic?
44
45Once the data leaves the server, where does it go? In my case, it goes to a
46managed switch. In this case, I asked the following:
47
48- What configurations is the switch using?
49- Am I using VLANs?
50 - Yes, I am using 802.1Q VLANs.
51- Are the VLANs configured properly?
52 - Yes, as shown in the Switch section below, I have a separate VLAN to allow
53 outside traffic to and from the server alone. No other devices, except for a
54 service port, and in that VLAN.
55
56At this point, the data has been processed through the switch. Where does it go
57next? In my case, it's pretty simple: it goes to the router/modem device.
58
59- Does my ISP block any ports that I need?
60 - This is an important step that a lot of people run into when self-hosting at
61 home. Use an online port-checker tool for your IP or call your ISP if you
62 think ports are blocked.
63- Is there a router firewall?
64 - Yes, I checked that it's configured to allow the ports I need to run my
65 services publicly. Common web servers and reverse proxies require ports 80
66 and 443, but other services like media servers or games can require unique
67 ports, so be sure to check the documentation for your 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 require
75 the use of ports 80 and 443)
76
77Once the data leaves my router, it goes to the upstream ISP and can be accessed
78publicly.
79
80*** Server
81
82The services I run on my server are installed straight into the OS, without any
83use of Docker or VMs, so I don't need any extra application configuration to
84make them accessible to the outside world.+
85
86As of 2022-10-04, the paragraph above is no longer true as I now run a reverse
87proxy with Nginx and host many services inside Docker. However, it doesn't
88change anything regarding this post as I still just need to open ports 80 & 443
89and create the necessary website configuration files.
90
91When creating new services - either installed directly on bare metal or within
92something like Docker - I ensure that I read through the documentation
93thoroughly to understand a few key things: - What network activities should this
94app perform (if any)? Using which ports and protocols? - Does this app require
95any commands/services to be run as =root=? - Does this app log errors,
96authentication failures/successes, or anything else that would be useful for an
97investigation?
98
99For extra security, I use limit all incoming connections to SSH connections
100through my server firewall (=ufw=) and disable common SSH settings. After all of
101that, I use =fail2ban= as a preventative measure against brute-force login
102attempts.
103
104As another piece of security, you can randomize your SSH port to ensure that
105random scanners or attackers can't easily try to force their way into your
106network. For example, you can edit the port rules in your server to block all
107connection requests to port =22= but forward all remote connections from port
108=12345= to your server's port =22=. Then you just need to SSH to your network
109via your randomized port.
110
111** =ufw=
112
113To see how to configure =ufw=, see my other post: [[/blog/ufw.html][Secure Your
114Network with the Uncomplicated Firewall]].
115
116The general notion with an on-device firewall is that you want to deny all
117incoming connections by default and then selectively open certain ports for
118services or users that you know need access.
119
120If you know that you will only be logging into this server from a certain set or
121list of IPs, you can always set the firewall to only allow connections to port
12222 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** =ssh=
134
1351. Using SSH Keys
136
137 First, make sure you have an SSH keypair generated on the device(s) that
138 you'll be using to log in to the server. If you don't have an SSH key, run
139 this command:
140
141 #+begin_src sh
142 ssh-keygen
143 #+end_src
144
145 Now that we have an SSH key, copy it to the server with the following
146 command, which will ask for the user's password before accepting the key:
147
148 #+begin_src sh
149 ssh-copy-id my_user@my_server
150 #+end_src
151
152 If you have multiple keys, you'll need to specify which to use. After it's
153 complete, =ssh= back into the server as that user and make sure it doesn't
154 ask for a password.
155
1562. Disable Password & Root Authentication
157
158 Now that we can access the server without a password, we will disable
159 password authentication and disable anyone from using =ssh= to login as
160 =root=.
161
162 To do this, open the =sshd_config= file:
163
164 #+begin_src sh
165 sudo nano /etc/ssh/sshd_config
166 #+end_src
167
168 You'll need to update the parameters to the values below. If one of these
169 rules is commented-out or doesn't exist, create the rule at the bottom of the
170 file.
171
172 #+begin_src config
173 PermitRootLogin no
174 PasswordAuthentication no
175 PubkeyAuthentication yes
176 #+end_src
177
178 Finally, restart the =ssh= service:
179
180 #+begin_src sh
181 sudo systemctl restart sshd.service
182 #+end_src
183
184 To test that everything's working so far, open ANOTHER terminal and try
185 logging in as =root= over SSH. It is very important that you keep your
186 current SSH session open and test with an additional session, or you will
187 lock yourself out at some point and will need to use a recovery method (e.g.,
188 hooking monitor up to home server) to get yourself back in.
189
1903. Enable MFA for =ssh=
191
192 This part is optional, but I highly recommend it. So far, we've ensured that
193 no one can log into our user on the server without using our secret key, and
194 we've ensured that no one can log in remotely as =root=. Next, you can enable
195 MFA authentication for =ssh= connections.
196
197 This process involves editing a couple files and installing an MFA package,
198 so I will not include all the details in this post. To see how to configure
199 MFA for =ssh=, see my other post: [[/blog/ssh-mfa.html][Enabling MFA for
200 SSH]].
201
202** =fail2ban=
203
204I haven't written a post on how I use =fail2ban=, but it's quite simple. I use
205the default =sshd= jail, but you can always create new jails for respective
206applications or ports. For example, if you use Nginx as your web server, you can
207use the =nginx-http-auth= jail.
208
209In order to get it up and running, use the following commands:
210
211#+begin_src sh
212sudo apt install fail2ban
213sudo fail2ban-client start sshd
214sudo fail2ban-client status sshd
215#+end_src
216
217This should be used as a last-resort defense and shouldn't be a replacement for
218the security measures mentioned above.
219
220* Switch
221
222Between the router and any local devices is my managed switch, which is used to
223create VLANs. The example below shows how I would isolate the VLANs if I were
224starting to host a single service at home.
225
226** 802.1Q VLAN Configuration
227
228In this configuration, port 8 is the public server that needs to be accessed
229from the outside. Port 23 is my 'dedicated service port' for this server. In
230order to SSH to this server, I need to plug my laptop into port 23 or else I
231cannot SSH. Otherwise, I'd need to hook up a monitor and keyboard directly to
232the server to manage it.
233
234#+begin_verse
235#+end_verse
236
237| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
238|---------+-----------+--------------+--------------+----------------|
239| 1 | Default | 1-24 | 1-24 | |
240| 2 | Server | 1,8,23 | 1,8,23 | |
241
242** 802.1Q VLAN PVID Setting
243
244Once the VLAN is created, I simply add the =VLAN ID= of =2= as the =PVID= for
245any related ports (in this case, see that ports =8= and =23= have a PVID of
246=2=).
247
248| Port | PVID |
249|------+------|
250| 1 | 1 |
251| 2 | 1 |
252| 3 | 1 |
253| 4 | 1 |
254| 5 | 1 |
255| 6 | 1 |
256| 7 | 1 |
257| 8 | 2 |
258| 9 | 1 |
259| 10 | 1 |
260| 11 | 1 |
261| 12 | 1 |
262| 13 | 1 |
263| 14 | 1 |
264| 15 | 1 |
265| 16 | 1 |
266| 17 | 1 |
267| 18 | 1 |
268| 19 | 1 |
269| 20 | 1 |
270| 21 | 1 |
271| 22 | 1 |
272| 23 | 2 |
273| 24 | 1 |
274
275* Router
276
277On my router, the configuration was as easy as opening the firewall settings and
278unblocking the ports I needed for my services (e.g., HTTP/S, Plex, SSH, MySQL,
279etc.).
280
281Since I'm relying on an ISP-provided modem/router combo for now (not by
282choice), I do not use any other advanced settings on my router that would
283inhibit any valid traffic to these services.
284
285The paragraph above regarding the ISP-owned router is no longer accurate as I
286now use the Ubiquiti Unifi Dream Machine Pro as my router. Within this router, I
287enabled port forwarding/firewall rules, segregate the network based on the
288device, and enable traffic restrictions (e.g., silently drop traffic from
289certain countries and threat categories).
290
291If you have the option with your ISP, I recommend using a personal router with
292software that you are familiar with so that you can explore all the options
293available to you.
content/blog/2018-11-28-aes-encryption.md deleted −118
@@ -1,118 +0,0 @@
1+++
2date = 2018-11-28
3title = "AES Encryption"
4description = ""
5draft = false
6+++
7
8# Basic AES
9
10If you're not familiar with encryption techniques,
11[AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) is the
12**Advanced Encryption Standard**. This specification was established by the
13National Institute of Standards and Technology, sub-selected from the Rijndael
14family of ciphers (128, 192, and 256 bits) in 2001. Furthering its popularity
15and status, the US government chose AES as their default encryption method for
16top-secret data, removing the previous standard which had been in place since 1977.
17
18AES has proven to be an extremely safe encryption method, with 7-round and
198-round attacks making no material improvements since the release of this
20encryption standard almost two decades ago.
21
22> Though many papers have been published on the cryptanalysis of AES, the
23> fastest single-key attacks on round-reduced AES variants [20, 33] so far are
24> only slightly more powerful than those proposed 10 years ago [23,24].
25>
26> - [Bogdonav, et
27> al.](http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf)
28
29# How Secure is AES?
30
31In theory, AES-256 is non-crackable due to the massive number of combinations
32that can be produced. However, AES-128 is no longer recommended as a viable
33implementation to protect important data.
34
35A semi-short [comic
36strip](http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html)
37from Moserware quickly explains AES for the public to understand. Basically AES
38encrypts the data by obscuring the relationship between the data and the
39encrypted data. Additionally, this method spreads the message out. Lastly, the
40key produced by AES is the secret to decrypting it. Someone may know the method
41of AES, but without the key, they are powerless.
42
43To obscure and spread the data out, AES creates a substitution-permutation
44network. Wikipedia has a wonderful [example of an SP
45network](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png)
46available. This network sends the data through a set of S boxes (using the
47unique key) to substitute the bits with another block of bits. Then, a P box
48will permutate, or rearrange, the bits. This is done over and over, with the key
49being derived from the last round. For AES, the key size specifies the number of
50transformation rounds: 10, 12, and 14 rounds for 128-bit, 192-bit, and 256-bit
51keys, respectively.
52
53# The Process
54
551. **KeyExpansion**: Using [Rijndael's key
56 schedule](https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard), the
57 keys are dynamically generated.
582. **AddRoundKey**: Each byte of the data is combined with this key using
59 bitwise xor.
603. **SubBytes**: This is followed by the substitution of each byte of data.
614. **ShiftRows**: Then, the final three rows are shifted a certain number of
62 steps, dictated by the cipher.
635. **MixColumns**: After the rows have been shifted, the columns are mixed and
64 combined.
65
66This process does not necessarily stop after one full round. Steps 2 through 5
67will repeat for the number of rounds specified by the key. However, the final
68round excludes the MixColumns step. As you can see, this is a fairly complex
69process. One must have a solid understanding of general mathematic principles to
70fully understand how the sequence works (and to even attempt to find a
71weakness).
72
73According to research done by Bogdanov et al., it would take billions of years
74to brute force a 126-bit key with current hardware. Additionally, this brute
75force attack would require storing 2^88^ bits of data! However, there are a few
76different attacks that have been used to show vulnerabilities with the use of
77this technology. Side-channel attacks use inadvertent leaks of data from the
78hardware or software, which can allow attackers to obtain the key or run
79programs on a user's hardware.
80
81Please note that this is not something you should run out and try to implement
82in your `Hello, World!` app after only a few hours of research. While AES
83(basically all encryption methods) is extremely efficient in what it does, it
84takes a lot of time and patience to understand. If you're looking for something
85which currently implements AES, check out the [Legion of the Bouncy
86Castle](https://www.bouncycastle.org/documentation.html) for Java
87implementations of cryptographic algorithms.
88
89# Why Does Encryption Matter?
90
91There are limitless reasons to enable encryption at-rest or in-transit for
92various aspects of your digital life. You can research specific examples, such
93as [Australia passes new law to thwart strong
94encryption](https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/).
95However, I will simply list a few basic reasons to always enable encryption,
96where feasible:
97
981. Privacy is a human right and is recognized as a national right in some
99 countries (e.g., [US Fourth
100 Amendment](https://www.law.cornell.edu/wex/fourth_amendment)).
1012. "Why not?" Encryption rarely affects performance or speed, so there's usually
102 not a reason to avoid it in the first place.
1033. Your digital identity and activity (texts, emails, phone calls, online
104 accounts, etc.) are extremely valuable and can result in terrible
105 consequences, such as identity theft, if leaked to other parties. Encrypting
106 this data prevents such leaks from ruining lives.
1074. Wiping or factory-resetting does not actually wipe all data from the storage
108 device. There are methods to read data from the physical disks/boards inside
109 devices.
1105. Corporations, governments, and other nefarious groups/individuals are
111 actively looking for ways to collect personal information about anyone they
112 can. If someone's data is unencrypted, that person may become a target due to
113 the ease of data collection.
114
115​**Read More:**
116
117- [Federal Information Processing Standards Publication
118 197](http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf)
content/blog/2018-11-28-aes-encryption.org added +124
@@ -0,0 +1,124 @@
1#+title: AES Encryption
2#+date: <2018-11-28>
3#+description: A beginner's guide to AES encryption.
4
5* Basic AES
6
7If you're not familiar with encryption techniques,
8[[https://en.wikipedia.org/wiki/Advanced_Encryption_Standard][AES]] is
9the *Advanced Encryption Standard*. This specification was established
10by the National Institute of Standards and Technology, sub-selected from
11the Rijndael family of ciphers (128, 192, and 256 bits) in 2001.
12Furthering its popularity and status, the US government chose AES as
13their default encryption method for top-secret data, removing the
14previous standard which had been in place since 1977.
15
16AES has proven to be an extremely safe encryption method, with 7-round
17and 8-round attacks making no material improvements since the release of
18this encryption standard almost two decades ago.
19
20#+begin_quote
21Though many papers have been published on the cryptanalysis of AES, the
22fastest single-key attacks on round-reduced AES variants [20, 33] so far
23are only slightly more powerful than those proposed 10 years ago
24[23,24].
25
26- [[http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf][Bogdonav,
27 et al.]]
28#+end_quote
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
37[[http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html][comic
38strip]] from Moserware quickly explains AES for the public to
39understand. Basically AES encrypts the data by obscuring the
40relationship between the data and the encrypted data. Additionally, this
41method spreads the message out. Lastly, the key produced by AES is the
42secret to decrypting it. Someone may know the method of AES, but without
43the key, they are powerless.
44
45To obscure and spread the data out, AES creates a
46substitution-permutation network. Wikipedia has a wonderful
47[[https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png][example
48of an SP network]] available. This network sends the data through a set
49of S boxes (using the unique key) to substitute the bits with another
50block of bits. Then, a P box will permutate, or rearrange, the bits.
51This is done over and over, with the key being derived from the last
52round. For AES, the key size specifies the number of transformation
53rounds: 10, 12, and 14 rounds for 128-bit, 192-bit, and 256-bit keys,
54respectively.
55
56* The Process
57
581. *KeyExpansion*: Using
59 [[https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard][Rijndael's
60 key schedule]], the keys are dynamically generated.
612. *AddRoundKey*: Each byte of the data is combined with this key using
62 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 number
66 of steps, dictated by the cipher.
675. *MixColumns*: After the rows have been shifted, the columns are mixed
68 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
91[[https://www.bouncycastle.org/documentation.html][Legion of the Bouncy
92Castle]] for Java implementations 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
99[[https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/][Australia
100passes new law to thwart strong encryption]]. However, I will simply
101list a few basic reasons to always enable encryption, where feasible:
102
1031. Privacy is a human right and is recognized as a national right in
104 some countries (e.g.,
105 [[https://www.law.cornell.edu/wex/fourth_amendment][US Fourth
106 Amendment]]).
1072. "Why not?" Encryption rarely affects performance or speed, so there's
108 usually not a reason to avoid it in the first place.
1093. Your digital identity and activity (texts, emails, phone calls,
110 online accounts, etc.) are extremely valuable and can result in
111 terrible consequences, such as identity theft, if leaked to other
112 parties. Encrypting this data prevents such leaks from ruining 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 are
117 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- [[http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf][Federal
124 Information Processing Standards Publication 197]]
content/blog/2018-11-28-cpp-compiler.md deleted −136
@@ -1,136 +0,0 @@
1+++
2date = 2018-11-28
3title = "The C++ Compiler"
4description = ""
5draft = false
6+++
7
8# A Brief Introduction
9
10[C++](https://en.wikipedia.org/wiki/C%2B%2B) is a general-purpose programming
11language with object-oriented, generic, and functional features in addition to
12facilities for low-level memory manipulation.
13
14The source code, shown in the snippet below, must be compiled before it can be
15executed. There are many steps and intricacies to the compilation process, and
16this post was a personal exercise to learn and remember as much information as I
17can.
18
19```cpp
20#include <iostream>
21
22int main()
23{
24 std::cout << "Hello, world!\n";
25}
26```
27
28## Compilation Process
29
30### An Overview
31
32Compiling C++ projects is a frustrating task most days. Seemingly nonexistent
33errors keeping your program from successfully compiling can be annoying
34(especially since you know you wrote it perfectly the first time, right?).
35
36I'm learning more and more about C++ these days and decided to write this
37concept down so that I can cement it even further in my own head. However, C++
38is not the only compiled language. Check out [the Wikipedia entry for compiled
39languages](https://en.wikipedia.org/wiki/Compiled_language) for more examples of
40compiled languages.
41
42I'll start with a wonderful, graphical way to conceptualize the C++ compiler.
43View [The C++ Compilation
44Process](https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html)
45by Kurt MacMahon, an NIU professor, to see the graphic and an explanation. The
46goal of the compilation process is to take the C++ code and produce a shared
47library, dynamic library, or an executable file.
48
49## Compilation Phases
50
51Let's break down the compilation process. There are four major steps to
52compiling C++ code.
53
54### Step 1
55
56The first step is to expand the source code file to meet all dependencies. The
57C++ preprocessor includes the code from all the header files, such as `#include
58<iostream>`. Now, what does that mean? The previous example includes the
59`iostream` header. This tells the computer that you want to use the `iostream`
60standard library, which contains classes and functions written in the core
61language. This specific header allows you to manipulate input/output streams.
62After all this, you'll end up which a temporary file that contains the expanded
63source code.
64
65In the example of the C++ code above, the `iostream` class would be included in
66the expanded code.
67
68### Step 2
69
70After the code is expanded, the compiler comes into play. The compiler takes the
71C++ code and converts this code into the assembly language, understood by the
72platform. You can see this in action if you head over to the [GodBolt Compiler
73Explorer](https://godbolt.org), which shows C++ being converted into assembly
74dynamically.
75
76For example, the `Hello, world!` code snippet above compiles into the following
77assembly code:
78
79```asm
80.LC0:
81 .string "Hello, world!\n"
82main:
83 push rbp
84 mov rbp, rsp
85 mov esi, OFFSET FLAT:.LC0
86 mov edi, OFFSET FLAT:_ZSt4cout
87 call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
88 mov eax, 0
89 pop rbp
90 ret
91__static_initialization_and_destruction_0(int, int):
92 push rbp
93 mov rbp, rsp
94 sub rsp, 16
95 mov DWORD PTR [rbp-4], edi
96 mov DWORD PTR [rbp-8], esi
97 cmp DWORD PTR [rbp-4], 1
98 jne .L5
99 cmp DWORD PTR [rbp-8], 65535
100 jne .L5
101 mov edi, OFFSET FLAT:_ZStL8__ioinit
102 call std::ios_base::Init::Init() [complete object constructor]
103 mov edx, OFFSET FLAT:__dso_handle
104 mov esi, OFFSET FLAT:_ZStL8__ioinit
105 mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
106 call __cxa_atexit
107.L5:
108 nop
109 leave
110 ret
111_GLOBAL__sub_I_main:
112 push rbp
113 mov rbp, rsp
114 mov esi, 65535
115 mov edi, 1
116 call __static_initialization_and_destruction_0(int, int)
117 pop rbp
118 ret
119```
120
121### Step 3
122
123Third, the assembly code generated by the compiler is assembled into the object
124code for the platform. Essentially, this is when the compiler takes the assembly
125code and assembles it into machine code in a binary format. After researching
126this online, I figured out that a lot of compilers will allow you to stop
127compilation at this step. This would be useful for compiling each source code
128file separately. This saves time later if a single file changes; only that file
129needs to be recompiled.
130
131### Step 4
132
133Finally, the object code file generated by the assembler is linked together with
134the object code files for any library functions used to produce a shared
135library, dynamic library, or an executable file. It replaces all references to
136undefined symbols with the correct addresses.
content/blog/2018-11-28-cpp-compiler.org added +136
@@ -0,0 +1,136 @@
1#+date: <2018-11-28>
2#+title: The C++ Compiler
3#+description:
4
5
6* A Brief Introduction
7
8[[https://en.wikipedia.org/wiki/C%2B%2B][C++]] is a general-purpose
9programming language with object-oriented, generic, and functional
10features in addition to facilities for low-level memory manipulation.
11
12The source code, shown in the snippet below, must be compiled before it
13can be executed. There are many steps and intricacies to the compilation
14process, and this post was a personal exercise to learn and remember as
15much information as I can.
16
17#+begin_src cpp
18#include <iostream>
19
20int main()
21{
22 std::cout << "Hello, world!\n";
23}
24#+end_src
25
26** Compilation Process
27
28*** An Overview
29
30Compiling C++ projects is a frustrating task most days. Seemingly
31nonexistent errors keeping your program from successfully compiling can
32be annoying (especially since you know you wrote it perfectly the first
33time, right?).
34
35I'm learning more and more about C++ these days and decided to write
36this concept down so that I can cement it even further in my own head.
37However, C++ is not the only compiled language. Check out
38[[https://en.wikipedia.org/wiki/Compiled_language][the Wikipedia entry
39for compiled languages]] for more examples of compiled languages.
40
41I'll start with a wonderful, graphical way to conceptualize the C++
42compiler. View
43[[https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html][The
44C++ Compilation Process]] by Kurt MacMahon, an NIU professor, to see the
45graphic and an explanation. The goal of the compilation process is to
46take the C++ code and produce a shared library, dynamic library, or an
47executable file.
48
49** Compilation Phases
50
51Let's break down the compilation process. There are four major steps to
52compiling C++ code.
53
54*** Step 1
55
56The first step is to expand the source code file to meet all
57dependencies. The C++ preprocessor includes the code from all the header
58files, such as =#include <iostream>=. Now, what does that mean? The
59previous example includes the =iostream= header. This tells the computer
60that you want to use the =iostream= standard library, which contains
61classes and functions written in the core language. This specific header
62allows you to manipulate input/output streams. After all this, you'll
63end up which a temporary file that contains the expanded source code.
64
65In the example of the C++ code above, the =iostream= class would be
66included in the expanded code.
67
68*** Step 2
69
70After the code is expanded, the compiler comes into play. The compiler
71takes the C++ code and converts this code into the assembly language,
72understood by the platform. You can see this in action if you head over
73to the [[https://godbolt.org][GodBolt Compiler Explorer]], which shows
74C++ being converted into assembly dynamically.
75
76For example, the =Hello, world!= code snippet above compiles into the
77following assembly code:
78
79#+begin_src asm
80.LC0:
81 .string "Hello, world!\n"
82main:
83 push rbp
84 mov rbp, rsp
85 mov esi, OFFSET FLAT:.LC0
86 mov edi, OFFSET FLAT:_ZSt4cout
87 call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
88 mov eax, 0
89 pop rbp
90 ret
91__static_initialization_and_destruction_0(int, int):
92 push rbp
93 mov rbp, rsp
94 sub rsp, 16
95 mov DWORD PTR [rbp-4], edi
96 mov DWORD PTR [rbp-8], esi
97 cmp DWORD PTR [rbp-4], 1
98 jne .L5
99 cmp DWORD PTR [rbp-8], 65535
100 jne .L5
101 mov edi, OFFSET FLAT:_ZStL8__ioinit
102 call std::ios_base::Init::Init() [complete object constructor]
103 mov edx, OFFSET FLAT:__dso_handle
104 mov esi, OFFSET FLAT:_ZStL8__ioinit
105 mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
106 call __cxa_atexit
107.L5:
108 nop
109 leave
110 ret
111_GLOBAL__sub_I_main:
112 push rbp
113 mov rbp, rsp
114 mov esi, 65535
115 mov edi, 1
116 call __static_initialization_and_destruction_0(int, int)
117 pop rbp
118 ret
119#+end_src
120
121*** Step 3
122
123Third, the assembly code generated by the compiler is assembled into the
124object code for the platform. Essentially, this is when the compiler
125takes the assembly code and assembles it into machine code in a binary
126format. After researching this online, I figured out that a lot of
127compilers will allow you to stop compilation at this step. This would be
128useful for compiling each source code file separately. This saves time
129later if a single file changes; only that file needs to be recompiled.
130
131*** Step 4
132
133Finally, the object code file generated by the assembler is linked
134together with the object code files for any library functions used to
135produce a shared library, dynamic library, or an executable file. It
136replaces all references to undefined symbols with the correct addresses.
content/blog/2019-01-07-useful-css.md deleted −190
@@ -1,190 +0,0 @@
1+++
2date = 2019-01-07
3title = "Useful CSS Snippets"
4description = ""
5draft = false
6+++
7
8# Introduction to CSS
9
10[CSS](https://en.wikipedia.org/wiki/CSS), the language used to markup HTML code
11and make it "pretty", is one of the most effective ways to increase the
12attractiveness of a website. It can also lead to increased user engagement,
13retention, and satisfaction. In fact, there are whole career fields are
14dedicated to the improvement of user experiences, known as UI design and UX
15design.
16
17Some web developers are used to the common CSS properties, such as element
18sizing, fonts, colors, etc., but are not as well versed in less-used properties
19and values such as `flexbox`, `clip-path`, and `transform`. This article will
20provide some insight into the less-used and unique CSS properties.
21
22# CSS Variables
23
24The first topic today is CSS variables. Variables are not often used by smaller
25developers. CSS variables allow you to give your website a well-defined
26structure, where you can easily reuse CSS properties throughout the project.
27
28You can use variables to define things, such as color palettes. Then, you can
29use these colors for backgrounds anywhere else in the HTML. This could be
30extended, where extra variables could be defined for `primary-text`,
31`quoted-text`, etc. Variables can also be used to define spacing (e.g. `32px` or
32`2rem`), which can then be applied to margins, padding, font sizes, and more.
33
34For example, here are some variables defined at the root of the website, which
35allows for any subsequent CSS rules to use those variables:
36
37```css
38:root {
39 --primary-color: black;
40 --secondary-color: white;
41}
42
43body {
44 background-color: var(--primary-color);
45 color: var(--secondary-color);
46}
47```
48
49# CSS Box Shadows
50
51Box shadows were once my mortal enemy. No matter how hard I tried, I just
52couldn't get them to work how I wanted. Because of this, my favorite discovery
53has been CSSMatic's [box shadow generator](https://www.cssmatic.com/box-shadow).
54It provides an excellent tool to generate box shadows using their simple
55sliders. Surprisingly, this is the reason I learned how box shadows work! You
56can use the sliders and watch how the CSS code changes in the image that is
57displayed. Through this, you should understand that the basic structure for box
58shadows is:
59
60```css
61box-shadow: inset horizontal vertical blur spread color;
62```
63
64Now, let's look at some basic examples! You can copy and paste the following
65code into a site like CodePen or your own HTML files. Feel free to play around
66with the code, experiment, and learn.
67
68**Box Shadow #1**
69
70```html
71<div class="shadow-examples">
72 <div class="box effect1">
73 <h3>Effect 1</h3>
74 </div>
75</div>
76```
77
78```css
79.box h3 {
80 text-align: center;
81 position: relative;
82 top: 80px;
83}
84.box {
85 width: 70%;
86 height: 200px;
87 background: #fff;
88 margin: 40px auto;
89}
90.effect1 {
91 box-shadow: 0 10px 6px -6px #777;
92}
93```
94
95**Box Shadow #2**
96
97```html
98<div class="shadow-examples">
99 <div class="box effect2">
100 <h3>Effect 2</h3>
101 </div>
102</div>
103```
104
105```css
106.box h3 {
107 text-align: center;
108 position: relative;
109 top: 80px;
110}
111.box {
112 width: 70%;
113 height: 200px;
114 background: #fff;
115 margin: 40px auto;
116}
117.effect2 {
118 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
119}
120```
121
122Try these box shadows out on your own and see how changing each shadow value
123works.
124
125# CSS Flexbox
126
127Now, let's move on to the best part of this article: flexbox. The flexbox is by
128far my favorite new toy. I originally stumbled across this solution after
129looking for more efficient ways of centering content horizontally AND
130vertically. I had used a few hack-ish methods before, but flexbox throws those
131out the window. The best part of it all is that flexbox is _dead simple_.
132
133Flexbox pertains to the parent div of any element. You want the parent to be the
134flexbox in which items are arranged to use the flex methods. It's easier to see
135this in action that explained, so let's see an example.
136
137**Flexbox**
138
139```html
140<div class="flex-examples">
141 <div class="sm-box">
142 <h3>1</h3>
143 </div>
144 <div class="sm-box">
145 <h3>2</h3>
146 </div>
147</div>
148```
149
150```css
151.flex-examples {
152 display: flex;
153 flex-wrap: wrap;
154 justify-content: flex-start;
155 align-items: center;
156 padding: 10px;
157 background-color: #f2f2f2;
158}
159.sm-box {
160 display: flex;
161 justify-content: center;
162 align-items: center;
163 width: 20%;
164 height: 100px;
165 background: #fff;
166 margin: 40px 10px;
167}
168```
169
170You may notice that we no longer need to use the `top` property for the `h3`
171elements in our code. This is because we set the display box to be a flex
172container for the small boxes, AND we made the small boxes flex containers for
173their elements (the h3 tags). Flex boxes can be nested like this to center
174content that is inside centered content.
175
176For the example above, we designated the `justify-content` property to be
177`flex-start` so that the boxes stack from the left side of the screen. This
178property can be changed to `center` to make the boxes appear in the center of
179the screen.
180
181For an interactive example, [check out this
182CodePen](https://codepen.io/LandonSchropp/pen/KpzzGo) from
183[LandonScropp](https://codepen.io/LandonSchropp/). Resize the window with dice
184to see how they collapse and re-align.
185
186# Even More CSS
187
188For more inspiration, you can visit [CodePen](https://www.codepen.io),
189[Dribbble](https://dribbble.com), or [UI Movement](https://uimovement.com) to
190browse the collections of many amazing web designers.
content/blog/2019-01-07-useful-css.org added +194
@@ -0,0 +1,194 @@
1#+date: <2019-01-07>
2#+title: Useful CSS Snippets
3#+description:
4
5
6* Introduction to CSS
7
8[[https://en.wikipedia.org/wiki/CSS][CSS]], the language used to markup
9HTML code and make it "pretty", is one of the most effective ways to
10increase the attractiveness of a website. It can also lead to increased
11user engagement, retention, and satisfaction. In fact, there are whole
12career fields are dedicated to the improvement of user experiences,
13known as UI design and UX design.
14
15Some web developers are used to the common CSS properties, such as
16element sizing, fonts, colors, etc., but are not as well versed in
17less-used properties and values such as =flexbox=, =clip-path=, and
18=transform=. This article will provide some insight into the less-used
19and unique CSS properties.
20
21* CSS Variables
22
23The first topic today is CSS variables. Variables are not often used by
24smaller developers. CSS variables allow you to give your website a
25well-defined structure, where you can easily reuse CSS properties
26throughout the project.
27
28You can use variables to define things, such as color palettes. Then,
29you can use these colors for backgrounds anywhere else in the HTML. This
30could be extended, where extra variables could be defined for
31=primary-text=, =quoted-text=, etc. Variables can also be used to define
32spacing (e.g. =32px= or =2rem=), which can then be applied to margins,
33padding, font sizes, and more.
34
35For example, here are some variables defined at the root of the website,
36which allows for any subsequent CSS rules to use those variables:
37
38#+begin_src css
39:root {
40 --primary-color: black;
41 --secondary-color: white;
42}
43
44body {
45 background-color: var(--primary-color);
46 color: var(--secondary-color);
47}
48#+end_src
49
50* CSS Box Shadows
51
52Box shadows were once my mortal enemy. No matter how hard I tried, I
53just couldn't get them to work how I wanted. Because of this, my
54favorite discovery has been CSSMatic's
55[[https://www.cssmatic.com/box-shadow][box shadow generator]]. It
56provides an excellent tool to generate box shadows using their simple
57sliders. Surprisingly, this is the reason I learned how box shadows
58work! You can use the sliders and watch how the CSS code changes in the
59image that is displayed. Through this, you should understand that the
60basic structure for box shadows is:
61
62#+begin_src css
63box-shadow: inset horizontal vertical blur spread color;
64#+end_src
65
66Now, let's look at some basic examples! You can copy and paste the
67following code into a site like CodePen or your own HTML files. Feel
68free to play around with the code, experiment, and learn.
69
70*Box Shadow #1*
71
72#+begin_src html
73<div class="shadow-examples">
74 <div class="box effect1">
75 <h3>Effect 1</h3>
76 </div>
77</div>
78#+end_src
79
80#+begin_src css
81.box h3 {
82 text-align: center;
83 position: relative;
84 top: 80px;
85}
86.box {
87 width: 70%;
88 height: 200px;
89 background: #fff;
90 margin: 40px auto;
91}
92.effect1 {
93 box-shadow: 0 10px 6px -6px #777;
94}
95#+end_src
96
97*Box Shadow #2*
98
99#+begin_src html
100<div class="shadow-examples">
101 <div class="box effect2">
102 <h3>Effect 2</h3>
103 </div>
104</div>
105#+end_src
106
107#+begin_src css
108.box h3 {
109 text-align: center;
110 position: relative;
111 top: 80px;
112}
113.box {
114 width: 70%;
115 height: 200px;
116 background: #fff;
117 margin: 40px auto;
118}
119.effect2 {
120 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
121}
122#+end_src
123
124Try these box shadows out on your own and see how changing each shadow
125value works.
126
127* CSS Flexbox
128
129Now, let's move on to the best part of this article: flexbox. The
130flexbox is by far my favorite new toy. I originally stumbled across this
131solution after looking for more efficient ways of centering content
132horizontally AND vertically. I had used a few hack-ish methods before,
133but flexbox throws those out the window. The best part of it all is that
134flexbox is /dead simple/.
135
136Flexbox pertains to the parent div of any element. You want the parent
137to be the flexbox in which items are arranged to use the flex methods.
138It's easier to see this in action that explained, so let's see an
139example.
140
141*Flexbox*
142
143#+begin_src html
144<div class="flex-examples">
145 <div class="sm-box">
146 <h3>1</h3>
147 </div>
148 <div class="sm-box">
149 <h3>2</h3>
150 </div>
151</div>
152#+end_src
153
154#+begin_src css
155.flex-examples {
156 display: flex;
157 flex-wrap: wrap;
158 justify-content: flex-start;
159 align-items: center;
160 padding: 10px;
161 background-color: #f2f2f2;
162}
163.sm-box {
164 display: flex;
165 justify-content: center;
166 align-items: center;
167 width: 20%;
168 height: 100px;
169 background: #fff;
170 margin: 40px 10px;
171}
172#+end_src
173
174You may notice that we no longer need to use the =top= property for the
175=h3= elements in our code. This is because we set the display box to be
176a flex container for the small boxes, AND we made the small boxes flex
177containers for their elements (the h3 tags). Flex boxes can be nested
178like this to center content that is inside centered content.
179
180For the example above, we designated the =justify-content= property to
181be =flex-start= so that the boxes stack from the left side of the
182screen. This property can be changed to =center= to make the boxes
183appear in the center of the screen.
184
185For an interactive example,
186[[https://codepen.io/LandonSchropp/pen/KpzzGo][check out this CodePen]]
187from [[https://codepen.io/LandonSchropp/][LandonScropp]]. Resize the
188window with dice to see how they collapse and re-align.
189
190* Even More CSS
191
192For more inspiration, you can visit [[https://www.codepen.io][CodePen]],
193[[https://dribbble.com][Dribbble]], or [[https://uimovement.com][UI
194Movement]] to browse the collections of many amazing web designers.
content/blog/2019-09-09-audit-analytics.md deleted −201
@@ -1,201 +0,0 @@
1+++
2date = 2019-09-09
3title = "Audit Analytics"
4description = ""
5draft = false
6+++
7
8# What Are Data Analytics?
9
10A quick aside before I dive into this post: `data analytics` is a vague term
11that has become popular in recent years. Think of a `data analytic` as the
12output of any data analysis you perform. For example, a pivot table or a pie
13chart could be a data analytic.
14
15[Data analysis](https://en.wikipedia.org/wiki/Data_analysis) is a process that
16utilizes statistics and other mathematical methods to discover useful
17information within datasets. This involves examining, cleaning, transforming,
18and modeling data so that you can use the data to support an opinion, create
19more useful viewpoints, and gain knowledge to implement into audit planning or
20risk assessments.
21
22One of the common mistakes that managers (and anyone new to the process) make is
23assuming that everything involved with this process is "data analytics". In
24fact, data analytics are only a small part of the process.
25
26See **Figure 1** for a more accurate representation of where data analysis sits
27within the full process. This means that data analysis does not include querying
28or extracting data, selecting samples, or performing audit tests. These steps
29can be necessary for an audit (and may even be performed by the same
30associates), but they are not data analytics.
31
32# Current Use of Analytics in Auditing
33
34While data analysis has been an integral part of most businesses and departments
35for the better part of the last century, only recently have internal audit
36functions been adopting this practice. The internal audit function works
37exclusively to provide assurance and consulting services to the business areas
38within the firm (except for internal auditing firms who are hired by different
39companies to perform their roles).
40
41> Internal Auditing helps an organization accomplish its objectives by bringing
42> a systematic, disciplined approach to evaluate and improve the effectiveness
43> of risk management, control and governance processes.
44>
45> - The IIA's Definition of Internal Audit
46
47Part of the blame for the slow adoption of data analysis can be attributed to
48the fact that internal auditing is strongly based on tradition and following the
49precedents set by previous auditors. However, there can be no progress without
50auditors who are willing to break the mold and test new audit techniques. In
51fact, as of 2018, [only 63% of internal audit departments currently utilize data
52analytics](https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics)
53in North America. This number should be as close as possible to 100%. I have
54never been part of an audit that would not have benefited from data analytics.
55
56So, how do internal audit functions remedy this situation? It's definitely not
57as easy as walking into work on Monday and telling your Chief Audit Executive
58that you're going to start implementing analytics in the next audit. You need a
59plan and a system to make the analysis process as effective as possible.
60
61# The DELTA Model
62
63One of the easiest ways to experiment with data analytics and gain an
64understanding of the processes is to implement them within your own department.
65But how do we do this if we've never worked with analysis before? One of the
66most common places to start is to research some data analysis models currently
67available. For this post, we'll take a look at the DELTA model. You can take a
68look at **Figure 2** for a quick overview of the model.
69
70The DELTA model sets a few guidelines for areas wanting to implement data
71analytics so that the results can be as comprehensive as possible:
72
73- **Data**: Must be clean, accessible, and (usually) unique.
74- **Enterprise-Wide Focus**: Key data systems and analytical resources must be
75 available for use (by the Internal Audit Function).
76- **Leaders**: Must promote a data analytics approach and show the value of
77 analytical results.
78- **Targets**: Must be set for key areas and risks that the analytics can be
79 compared against (KPIs).
80- **Analysts**: There must be auditors willing and able to perform data
81 analytics or else the system cannot be sustained.
82
83# Finding the Proper KPIs
84
85Once the Internal Audit Function has decided that they want to start using data
86analytics internally and have ensured they're properly set up to do so, they
87need to figure out what they will be testing against. Key Performance Indicators
88(KPIs) are qualitative or quantitative factors that can be evaluated and
89assessed to determine if the department is performing well, usually compared to
90historical or industry benchmarks. Once KPIs have been agreed upon and set,
91auditors can use data analytics to assess and report on these KPIs. This allows
92the person performing the analytics the freedom to express opinions on the
93results, whereas the results are ambiguous if no KPIs exist.
94
95It should be noted that tracking KPIs in the department can help ensure you have
96a rigorous Quality Assurance and Improvement Program (QAIP) in accordance with
97some applicable standards, such as IPPF Standard 1300.
98
99> The chief audit executive must develop and maintain a quality assurance and
100> improvement program that covers all aspects of the internal audit activity.
101>
102> - IPPF Standard 1300
103
104Additionally, IPPF Standard 2060 discusses reporting:
105
106> The chief audit executive must report periodically to senior management and
107> the board on the internal audit activity's purpose, authority, responsibility,
108> and performance relative to its plan and on its conformance with the Code of
109> Ethics and the Standards. Reporting must also include significant risk and
110> control issues, including fraud risks, governance issues, and other matters
111> that require the attention of senior management and/or the board.
112>
113> - IPPF Standard 2060
114
115The hardest part of finding KPIs is to determine which KPIs are appropriate for
116your department. Since every department is different and has different goals,
117KPIs will vary drastically between companies. To give you an idea of where to
118look, here are some ideas I came up with when discussing the topic with a few
119colleagues.
120
121- Efficiency/Budgeting:
122 - Audit hours to staff utilization ratio (annual hours divided by total
123 annual work hours).
124 - Audit hours compared to the number of audits completed.
125 - Time between audit steps or to complete the whole audit. E.g., time from
126 fieldwork completion to audit report issuance.
127- Reputation:
128 - The frequency that management has requested the services of the IAF.
129 - Management, audit committee, or external audit satisfaction survey
130 results.
131 - Education, experience, certifications, tenure, and training of the
132 auditors on staff.
133- Quality:
134 - Number and frequency of audit findings. Assign monetary or numerical
135 values, if possible.
136 - Percentage of recommendations issued and implemented.
137- Planning:
138 - Percentage or number of key risks audited per year or per audit.
139 - Proportion of audit universe audited per year.
140
141# Data Analysis Tools
142
143Finally, to be able to analyze and report on the data analysis, auditors need to
144evaluate the tools at their disposal. There are many options available, but a
145few of the most common ones can easily get the job done. For example, almost
146every auditor already has access to Microsoft Excel. Excel is more powerful than
147most people give it credit for and can accomplish a lot of basic statistics
148without much work. If you don't know a lot about statistics but still want to
149see some of the more basic results, Excel is a great option.
150
151To perform more in-depth statistical analysis or to explore large datasets that
152Excel cannot handle, auditors will need to explore other options. The big three
153that have had a lot of success in recent years are Python, R, and ACL. ACL can
154be used as either a graphical tool (point and click) or as a scripting tool,
155where the auditor must write the scripts manually. Python and the R-language are
156solely scripting languages.
157
158The general trend in the data analytics environment is that if the tool allows
159you to do everything by clicking buttons or dragging elements, you won't be able
160to fully utilize the analytics you need. The most robust solutions are created
161by those who understand how to write the scripts manually. It should be noted
162that as the utility of a tool increases, it usually means that the learning
163curve for that tool will also be higher. It will take auditors longer to learn
164how to utilize Python, R, or ACL versus learning how to utilize Excel.
165
166# Visualization
167
168Once an auditor has finally found the right data, KPIs, and tools, they must
169report these results so that actions can be taken. Performing in-depth data
170analysis is only useful if the results are understood by the audiences of the
171data. The best way to create this understanding is to visualize the results of
172the data. Let's take a look at some of the best options to visualize and report
173the results you've found.
174
175Some of the most popular commercial tools for visualization are Microsoft
176PowerBI and Tableau Desktop. However, other tools exist such as JMP, Plotly,
177Qlikview, Alteryx, or D3. Some require commercial licenses while others are
178simply free to use. For corporate data, you may want to make sure that the tool
179does not communicate any of the data outside the company (such as cloud
180storage). I won't be going into depth on any of these tools since visualization
181is largely a subjective and creative experience, but remember to constantly
182explore new options as you repeat the process.
183
184Lastly, let's take a look at an example of data visualization. This example
185comes from a [blog post written by Kushal
186Chakrabarti](https://talent.works/2018/03/28/the-science-of-the-job-search-part-iii-61-of-entry-level-jobs-require-3-years-of-experience/)
187in 2018 about the percent of entry-level US jobs that require experience.
188**Figure 3** shows us an easy-to-digest picture of the data. We can quickly tell
189that only about 12.5% of entry-level jobs don't require experience.
190
191This is the kind of result that easily describes the data for you. However, make
192sure to include an explanation of what the results mean. Don't let the reader
193assume what the data means, especially if it relates to a complex subject. _Tell
194a story_ about the data and why the results matter.
195
196# Wrap-Up
197
198While this is not an all-encompassing program that you can just adopt into your
199department, it should be enough to get anyone started on the process of
200understanding and implementing data analytics. Always remember to continue
201learning and exploring new options as your processes grow and evolve.
content/blog/2019-09-09-audit-analytics.org added +220
@@ -0,0 +1,220 @@
1#+date: <2019-09-09>
2#+title: Audit Analytics
3#+description:
4
5
6* What Are Data Analytics?
7
8A quick aside before I dive into this post: =data analytics= is a vague
9term that has become popular in recent years. Think of a =data analytic=
10as the output of any data analysis you perform. For example, a pivot
11table or a pie chart could be a data analytic.
12
13[[https://en.wikipedia.org/wiki/Data_analysis][Data analysis]] is a
14process that utilizes statistics and other mathematical methods to
15discover useful information within datasets. This involves examining,
16cleaning, transforming, and modeling data so that you can use the data
17to support an opinion, create more useful viewpoints, and gain knowledge
18to implement into audit planning or risk assessments.
19
20One of the common mistakes that managers (and anyone new to the process)
21make is assuming that everything involved with this process is "data
22analytics". In fact, data analytics are only a small part of the
23process.
24
25See *Figure 1* for a more accurate representation of where data analysis
26sits within the full process. This means that data analysis does not
27include querying or extracting data, selecting samples, or performing
28audit tests. These steps can be necessary for an audit (and may even be
29performed by the same associates), but they are not data analytics.
30
31* Current Use of Analytics in Auditing
32
33While data analysis has been an integral part of most businesses and
34departments for the better part of the last century, only recently have
35internal audit functions been adopting this practice. The internal audit
36function works exclusively to provide assurance and consulting services
37to the business areas within the firm (except for internal auditing
38firms who are hired by different companies to perform their roles).
39
40#+begin_quote
41Internal Auditing helps an organization accomplish its objectives by
42bringing a systematic, disciplined approach to evaluate and improve the
43effectiveness of risk management, control and governance processes.
44
45- The IIA's Definition of Internal Audit
46#+end_quote
47
48Part of the blame for the slow adoption of data analysis can be
49attributed to the fact that internal auditing is strongly based on
50tradition and following the precedents set by previous auditors.
51However, there can be no progress without auditors who are willing to
52break the mold and test new audit techniques. In fact, as of 2018,
53[[https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics][only
5463% of internal audit departments currently utilize data analytics]] in
55North America. This number should be as close as possible to 100%. I
56have never been part of an audit that would not have benefited from data
57analytics.
58
59So, how do internal audit functions remedy this situation? It's
60definitely not as easy as walking into work on Monday and telling your
61Chief Audit Executive that you're going to start implementing analytics
62in the next audit. You need a plan and a system to make the analysis
63process as effective as possible.
64
65* The DELTA Model
66
67One of the easiest ways to experiment with data analytics and gain an
68understanding of the processes is to implement them within your own
69department. But how do we do this if we've never worked with analysis
70before? One of the most common places to start is to research some data
71analysis models currently available. For this post, we'll take a look at
72the DELTA model. You can take a look at *Figure 2* for a quick overview
73of the model.
74
75The DELTA model sets a few guidelines for areas wanting to implement
76data analytics so that the results can be as comprehensive as possible:
77
78- *Data*: Must be clean, accessible, and (usually) unique.
79- *Enterprise-Wide Focus*: Key data systems and analytical resources
80 must be available for use (by the Internal Audit Function).
81- *Leaders*: Must promote a data analytics approach and show the value
82 of analytical results.
83- *Targets*: Must be set for key areas and risks that the analytics can
84 be compared against (KPIs).
85- *Analysts*: There must be auditors willing and able to perform data
86 analytics or else the system cannot be sustained.
87
88* Finding the Proper KPIs
89
90Once the Internal Audit Function has decided that they want to start
91using data analytics internally and have ensured they're properly set up
92to do so, they need to figure out what they will be testing against. Key
93Performance Indicators (KPIs) are qualitative or quantitative factors
94that can be evaluated and assessed to determine if the department is
95performing well, usually compared to historical or industry benchmarks.
96Once KPIs have been agreed upon and set, auditors can use data analytics
97to assess and report on these KPIs. This allows the person performing
98the analytics the freedom to express opinions on the results, whereas
99the results are ambiguous if no KPIs exist.
100
101It should be noted that tracking KPIs in the department can help ensure
102you have a rigorous Quality Assurance and Improvement Program (QAIP) in
103accordance with some applicable standards, such as IPPF Standard 1300.
104
105#+begin_quote
106The chief audit executive must develop and maintain a quality assurance
107and improvement program that covers all aspects of the internal audit
108activity.
109
110- IPPF Standard 1300
111#+end_quote
112
113Additionally, IPPF Standard 2060 discusses reporting:
114
115#+begin_quote
116The chief audit executive must report periodically to senior management
117and the board on the internal audit activity's purpose, authority,
118responsibility, and performance relative to its plan and on its
119conformance with the Code of Ethics and the Standards. Reporting must
120also include significant risk and control issues, including fraud risks,
121governance issues, and other matters that require the attention of
122senior management and/or the board.
123
124- IPPF Standard 2060
125#+end_quote
126
127The hardest part of finding KPIs is to determine which KPIs are
128appropriate for your department. Since every department is different and
129has different goals, KPIs will vary drastically between companies. To
130give you an idea of where to look, here are some ideas I came up with
131when discussing the topic with a few colleagues.
132
133- Efficiency/Budgeting:
134 - Audit hours to staff utilization ratio (annual hours divided by
135 total annual work hours).
136 - Audit hours compared to the number of audits completed.
137 - Time between audit steps or to complete the whole audit. E.g., time
138 from fieldwork completion to audit report issuance.
139- Reputation:
140 - The frequency that management has requested the services of the IAF.
141 - Management, audit committee, or external audit satisfaction survey
142 results.
143 - Education, experience, certifications, tenure, and training of the
144 auditors on staff.
145- Quality:
146 - Number and frequency of audit findings. Assign monetary or numerical
147 values, if possible.
148 - Percentage of recommendations issued and implemented.
149- Planning:
150 - Percentage or number of key risks audited per year or per audit.
151 - Proportion of audit universe audited per year.
152
153* Data Analysis Tools
154
155Finally, to be able to analyze and report on the data analysis, auditors
156need to evaluate the tools at their disposal. There are many options
157available, but a few of the most common ones can easily get the job
158done. For example, almost every auditor already has access to Microsoft
159Excel. Excel is more powerful than most people give it credit for and
160can accomplish a lot of basic statistics without much work. If you don't
161know a lot about statistics but still want to see some of the more basic
162results, Excel is a great option.
163
164To perform more in-depth statistical analysis or to explore large
165datasets that Excel cannot handle, auditors will need to explore other
166options. The big three that have had a lot of success in recent years
167are Python, R, and ACL. ACL can be used as either a graphical tool
168(point and click) or as a scripting tool, where the auditor must write
169the scripts manually. Python and the R-language are solely scripting
170languages.
171
172The general trend in the data analytics environment is that if the tool
173allows you to do everything by clicking buttons or dragging elements,
174you won't be able to fully utilize the analytics you need. The most
175robust solutions are created by those who understand how to write the
176scripts manually. It should be noted that as the utility of a tool
177increases, it usually means that the learning curve for that tool will
178also be higher. It will take auditors longer to learn how to utilize
179Python, R, or ACL versus learning how to utilize Excel.
180
181* Visualization
182
183Once an auditor has finally found the right data, KPIs, and tools, they
184must report these results so that actions can be taken. Performing
185in-depth data analysis is only useful if the results are understood by
186the audiences of the data. The best way to create this understanding is
187to visualize the results of the data. Let's take a look at some of the
188best options to visualize and report the results you've found.
189
190Some of the most popular commercial tools for visualization are
191Microsoft PowerBI and Tableau Desktop. However, other tools exist such
192as JMP, Plotly, Qlikview, Alteryx, or D3. Some require commercial
193licenses while others are simply free to use. For corporate data, you
194may want to make sure that the tool does not communicate any of the data
195outside the company (such as cloud storage). I won't be going into depth
196on any of these tools since visualization is largely a subjective and
197creative experience, but remember to constantly explore new options as
198you repeat the process.
199
200Lastly, let's take a look at an example of data visualization. This
201example comes from a
202[[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
203post written by Kushal Chakrabarti]] in 2018 about the percent of
204entry-level US jobs that require experience. *Figure 3* shows us an
205easy-to-digest picture of the data. We can quickly tell that only about
20612.5% of entry-level jobs don't require experience.
207
208This is the kind of result that easily describes the data for you.
209However, make sure to include an explanation of what the results mean.
210Don't let the reader assume what the data means, especially if it
211relates to a complex subject. /Tell a story/ about the data and why the
212results matter.
213
214* Wrap-Up
215
216While this is not an all-encompassing program that you can just adopt
217into your department, it should be enough to get anyone started on the
218process of understanding and implementing data analytics. Always
219remember to continue learning and exploring new options as your
220processes grow and evolve.
content/blog/2019-12-03-the-ansoff-matrix.md deleted −116
@@ -1,116 +0,0 @@
1+++
2date = 2019-12-03
3title = "The Ansoff Matrix"
4description = ""
5draft = false
6+++
7
8# Overview
9
10As the world of business evolves, managers must approach business planning and
11strategy with a contemporary mindset. According to Dess, McNamara, Eisner, and
12Lee, managers must be willing to adapt to the modern business environment by
13going beyond "'incremental management', whereby they view their job as making a
14series of small, minor changes to improve the efficiency of the firm's
15operations"(2019).
16
17One reason that strategic management is crucial is because most businesses that
18fail in the United States each year fail due to a lack of strategic focus or
19direction(2019). The rate of failure for businesses with poor strategies shows
20that strategic planning and management are crucial to a business's strength and
21longevity, injecting the critical factors of growth and direction into a
22company's business plan.
23
24One of the most significant strategic planning and management frameworks that
25companies can use is the [Ansoff
26Matrix](https://en.wikipedia.org/wiki/Ansoff_matrix). While this framework has
27unique purposes and use-cases, it can effectively help an organization grow and
28compete. Specifically, the Ansoff matrix is one of the most effective frameworks
29for companies who want to focus on increasing sales revenue or
30profitability(2019).
31
32This framework uses a two-by-two figure to show the four strategic options for
33companies to use in this framework: market penetration, market development,
34product development, and diversification (see **Figure 1**). The x-axis of the
35matrix focuses on the firm's markets and also determines if the firm is looking
36to enter new markets or innovate in its current markets. The y-axis of the
37matrix focuses on the firm's products and determines if the firm wants to pursue
38strategies around their existing products or explore new products.
39
40# Strategic Options
41
42## Market Penetration
43
44The most straightforward strategy in the Ansoff matrix is to focus on existing
45products in existing markets, also known as market penetration(2019). Companies
46such as Coca-Cola have used market penetration successfully by investing a lot
47of money to get further value out of their current markets. Coca-Cola does this
48by introducing new features such as Christmas-themed bottles, personal names on
49the bottles, and other marketing schemes.
50
51## Market Development
52
53Market development extends existing products into new markets in an attempt to
54increase the number of buyers. One interesting way that Coca-Cola used this
55strategy comes from the stigma that Diet Coke is a woman's drink(2019).
56Coca-Cola introduced Coca-Cola Zero, which contained the same nutritional
57content as Diet Coke, but was packaged in a dark black can to appear more
58"manly"(2019).
59
60## Product Development
61
62Product development uses existing markets to introduce new products so that the
63firm can better meet customer needs(2019). The extreme end of diversification is
64home to companies such as Johnson & Johnson, a healthcare company that has
65developed a business portfolio of more than 60,000 different products(2019).
66Johnson & Johnson's dedication to continuous diversification has led them to a
67balance sheet rating of "AAA", industry recognition for diversification, and
68increases in their investor dividends for 57 consecutive years(2019).
69
70## Related Diversification
71
72Diversification, the final strategy of the Ansoff Matrix, is more difficult than
73the others since it involves exploring both new markets and new products.
74Related diversification is a diversification strategy that closely relates to
75the firm's core business. Coca-Cola's best example of related diversification is
76its acquisition of Glaceau and Vitamin Water, which expanded their drinking
77lines of business(2019).
78
79## Unrelated Diversification
80
81Unrelated diversification is a diversification strategy that does not really
82relate to the firm's core business but still diversifies their business
83portfolio. A good example of this would be a coffee company who has decided to
84enter the market for bicycle sales. The main purpose of this strategy is to an
85extremely diverse company that will not go bankrupt if one market goes through
86difficult times. However, this requires a lot of independent skills and heavy
87investments since the company most likely cannot easily transfer knowledge
88between the markets they compete in.
89
90# Requirements for Success
91
92To use the Ansoff Matrix framework, managers need to formulate corporate goals
93and objectives. Without goals and direction, management frameworks do not
94present much practical utility. Further, the Ansoff Matrix requires the managers
95involved to make tactical decisions and create a path for the company to take
96toward their goals. Lastly, both the Ansoff Matrix needs to consider both
97internal and external perspectives throughout the strategy formulation process.
98
99One interesting probability is that companies will be using multiple strategic
100planning and management frameworks at the same time. While this may sound like
101it could crowd the management process, there are numerous reasons to do so. For
102example, the Ansoff Matrix and the Balanced Scorecard are relatively popular,
103and they cover entirely different parts of a company's strategy. Using the
104results from the Balanced Scorecard could inform a company of the potential
105product and market demands, such as from customer or supplier survey results, to
106help the company determine which Ansoff Matrix strategy to pursue. However, a
107combined approach at this level would require mature frameworks and focused
108managers who are able to strategize at a high level.
109
110Lastly, it should be noted that the author of the Ansoff matrix, Igor Ansoff,
111often used the term [paralysis by
112analysis](https://en.wikipedia.org/wiki/Analysis_paralysis) to explain the
113mistake of companies who overuse analysis and spend too much time planning.
114Companies need to understand the utility of a strategic management framework
115while ensuring that the company is poised to execute as efficiently as they have
116planned.
content/blog/2019-12-03-the-ansoff-matrix.org added +122
@@ -0,0 +1,122 @@
1#+date: <2019-12-03>
2#+title: The Ansoff Matrix
3#+description:
4
5
6* Overview
7
8As the world of business evolves, managers must approach business
9planning and strategy with a contemporary mindset. According to Dess,
10McNamara, Eisner, and Lee, managers must be willing to adapt to the
11modern business environment by going beyond "'incremental management',
12whereby they view their job as making a series of small, minor changes
13to improve the efficiency of the firm's operations"(2019).
14
15One reason that strategic management is crucial is because most
16businesses that fail in the United States each year fail due to a lack
17of strategic focus or direction(2019). The rate of failure for
18businesses with poor strategies shows that strategic planning and
19management are crucial to a business's strength and longevity, injecting
20the critical factors of growth and direction into a company's business
21plan.
22
23One of the most significant strategic planning and management frameworks
24that companies can use is the
25[[https://en.wikipedia.org/wiki/Ansoff_matrix][Ansoff Matrix]]. While
26this framework has unique purposes and use-cases, it can effectively
27help an organization grow and compete. Specifically, the Ansoff matrix
28is one of the most effective frameworks for companies who want to focus
29on increasing sales revenue or profitability(2019).
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 and
35also determines if the firm is looking to enter new markets or innovate
36in its current markets. The y-axis of the matrix focuses on the firm's
37products and determines if the firm wants to pursue strategies around
38their existing products or explore new products.
39
40* Strategic Options
41
42** Market Penetration
43
44The most straightforward strategy in the Ansoff matrix is to focus on
45existing products in existing markets, also known as market
46penetration(2019). 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
53
54Market development extends existing products into new markets in an
55attempt to increase the number of buyers. One interesting way that
56Coca-Cola used this strategy comes from the stigma that Diet Coke is a
57woman's drink(2019). Coca-Cola introduced Coca-Cola Zero, which
58contained the same nutritional content as Diet Coke, but was packaged in
59a dark black can to appear more "manly"(2019).
60
61** Product Development
62
63Product development uses existing markets to introduce new products so
64that the firm can better meet customer needs(2019). The extreme end of
65diversification is home to companies such as Johnson & Johnson, a
66healthcare company that has developed a business portfolio of more than
6760,000 different products(2019). Johnson & Johnson's dedication to
68continuous diversification has led them to a balance sheet rating of
69"AAA", industry recognition for diversification, and increases in their
70investor dividends for 57 consecutive years(2019).
71
72** Related Diversification
73
74Diversification, the final strategy of the Ansoff Matrix, is more
75difficult than the others since it involves exploring both new markets
76and new products. Related diversification is a diversification strategy
77that closely relates to the firm's core business. Coca-Cola's best
78example of related diversification is its acquisition of Glaceau and
79Vitamin Water, which expanded their drinking lines of business(2019).
80
81** Unrelated Diversification
82
83Unrelated diversification is a diversification strategy that does not
84really relate to the firm's core business but still diversifies their
85business portfolio. A good example of this would be a coffee company who
86has decided to enter the market for bicycle sales. The main purpose of
87this strategy is to an extremely diverse company that will not go
88bankrupt if one market goes through difficult times. However, this
89requires a lot of independent skills and heavy investments since the
90company most likely cannot easily transfer knowledge between the markets
91they compete in.
92
93* Requirements for Success
94
95To use the Ansoff Matrix framework, managers need to formulate corporate
96goals and objectives. Without goals and direction, management frameworks
97do not present much practical utility. Further, the Ansoff Matrix
98requires the managers involved to make tactical decisions and create a
99path for the company to take toward their goals. Lastly, both the Ansoff
100Matrix needs to consider both internal and external perspectives
101throughout the strategy formulation process.
102
103One interesting probability is that companies will be using multiple
104strategic planning and management frameworks at the same time. While
105this may sound like it could crowd the management process, there are
106numerous reasons to do so. For example, the Ansoff Matrix and the
107Balanced Scorecard are relatively popular, and they cover entirely
108different parts of a company's strategy. Using the results from the
109Balanced Scorecard could inform a company of the potential product and
110market demands, such as from customer or supplier survey results, to
111help the company determine which Ansoff Matrix strategy to pursue.
112However, a combined approach at this level would require mature
113frameworks and focused managers who are able to strategize at a high
114level.
115
116Lastly, it should be noted that the author of the Ansoff matrix, Igor
117Ansoff, often used the term
118[[https://en.wikipedia.org/wiki/Analysis_paralysis][paralysis by
119analysis]] to explain the mistake of companies who overuse analysis and
120spend too much time planning. Companies need to understand the utility
121of a strategic management framework while ensuring that the company is
122poised to execute as efficiently as they have planned.
content/blog/2019-12-16-password-security.md deleted −117
@@ -1,117 +0,0 @@
1+++
2date = 2019-12-16
3title = "Password Security"
4description = ""
5draft = false
6+++
7
8# Users
9
10## Why Does It Matter?
11
12Information security, including passwords and identities, has become one of the
13most important digital highlights of the last decade. With [billions of people
14affected by data breaches each
15year](https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/),
16there's a greater need to introduce strong information security systems. If you
17think you've been part of a breach, or you want to check and see, you can use
18[Have I Been Pwned](https://haveibeenpwned.com/) to see if your email has been
19involved in any public breaches. Remember that there's a possibility that a
20company 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 gather a list
25of all the different websites, apps, or programs that require you to have login
26credentials. Optionally, once you know where your information is being stored,
27you can sort the list from the most-important items such as banks or government
28logins to less important items such as your favorite meme site. You will want to
29ensure that your critical logins are secure before getting to the others.
30
31Once you think you have a good idea of all your different authentication
32methods, I recommend using a password manager such as
33[Bitwarden](https://bitwarden.com/). Using a password manager allows you to
34automatically save your logins, create randomized passwords, and transfer
35passwords across devices. However, you'll need to memorize your "vault password"
36that allows you to open the password manager. It's important to make this
37something hard to guess since it would allow anyone who has it to access every
38password you've stored in there.
39
40Personally, I recommend using a
41[passphrase](https://en.wikipedia.org/wiki/Passphrase) instead of a
42[password](https://en.wikipedia.org/wiki/Password) for your vault password.
43Instead of using a string of characters (whether random or simple), use a phrase
44and add in symbols and a number. For example, your vault password could be
45`Racing-Alphabet-Gourd-Parrot3`. Swap the symbols out for whichever symbol you
46want, move the number around, and fine-tune the passphrase until you are
47confident that you can remember it whenever necessary.
48
49Once you've stored your passwords, make sure you continually check up on your
50account and make sure you aren't following bad password practices. Krebs on
51Security has a great [blog post on password
52recommendations](https://krebsonsecurity.com/password-dos-and-donts/). Any time
53that a data breach happens, make sure you check to see if you were included, and
54if you need to reset any account passwords.
55
56# Developers
57
58## What Are the Basic Requirements?
59
60When developing any password-protected application, there are a few basic rules
61that anyone should follow even if they do not follow any official guidelines
62such as NIST. The foremost practice is to require users to use passwords that
63are at least 8 characters and cannot easily be guessed. This sounds extremely
64simple, but it requires quite a few different strategies. First, the application
65should check the potential passwords against a dictionary of insecure passwords
66such `password`, `1234abc`, or `application_name`.
67
68Next, the application should offer guidance on the strength of passwords being
69entered during enrollment. Further, NIST officially recommends **not**
70implementing any composition rules that make passwords hard to remember (e.g.
71passwords with letters, numbers, and special characters) and instead encouraging
72the use of long pass phrases which can include spaces. It should be noted that
73to be able to keep spaces within passwords, all unicode characters should be
74supported, and passwords should not be truncated.
75
76## What Does NIST Recommend?
77
78The National Institute of Standards and Technology
79([NIST](https://www.nist.gov)) in the US Department of Commerce regularly
80publishes information around information security and digital identity
81guidelines. Recently, NIST published [Special Publication
82800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html): Digital Identity
83Guidelines and Authentication and Lifecycle Management.
84
85> A Memorized Secret authenticator - commonly referred to as a password or, if
86> numeric, a PIN - is a secret value intended to be chosen and memorized by the
87> user. Memorized secrets need to be of sufficient complexity and secrecy that
88> it would be impractical for an attacker to guess or otherwise discover the
89> correct secret value. A memorized secret is something you know.
90>
91> - NIST Special Publication 800-63B
92
93NIST offers a lot of guidance on passwords, but I'm going to highlight just a
94few of the important factors:
95
96- Require passwords to be a minimum of 8 characters (6 characters if randomly
97 generated and be generated using an approved random bit generator).
98- Compare potential passwords against a list that contains values known to be
99 commonly-used, expected, or compromised.
100- Offer guidance on password strength, such as a strength meter.
101- Implement a rate-limiting mechanism to limit the number of failed
102 authentication attempts for each user account.
103- Do not require composition rules for passwords and do not require passwords
104 to be changed periodically (unless compromised).
105- Allow pasting of user identification and passwords to facilitate the use of
106 password managers.
107- Allow users to view the password as it is being entered.
108- Use secure forms of communication and storage, including salting and hashing
109 passwords using a one-way key derivation function.
110
111NIST offers further guidance on other devices that require specific security
112policies, querying for passwords, and more. All the information discussed so far
113comes from [NIST SP800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html) but
114NIST offers a lot of information on digital identities, enrollment, identity
115proofing, authentication, lifecycle management, federation, and assertions in
116the total [NIST SP800-63 Digital Identity
117Guidelines](https://pages.nist.gov/800-63-3/).
content/blog/2019-12-16-password-security.org added +127
@@ -0,0 +1,127 @@
1#+date: <2019-12-16>
2#+title: Password Security
3#+description:
4
5
6* Users
7
8** Why Does It Matter?
9
10Information security, including passwords and identities, has become one
11of the most important digital highlights of the last decade. With
12[[https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/][billions
13of people affected by data breaches each year]], there's a greater need
14to introduce strong information security systems. If you think you've
15been part of a breach, or you want to check and see, you can use
16[[https://haveibeenpwned.com/][Have I Been Pwned]] to see if your email
17has been involved in any public breaches. Remember that there's a
18possibility that a company experienced a breach and did not report it to
19anyone.
20
21** How Do I Protect Myself?
22
23The first place to start with any personal security check-up is to
24gather a list of all the different websites, apps, or programs that
25require you to have login credentials. Optionally, once you know where
26your information is being stored, you can sort the list from the
27most-important items such as banks or government logins to less
28important items such as your favorite meme site. You will want to ensure
29that your critical logins are secure before getting to the others.
30
31Once you think you have a good idea of all your different authentication
32methods, I recommend using a password manager such as
33[[https://bitwarden.com/][Bitwarden]]. Using a password manager allows
34you to automatically save your logins, create randomized passwords, and
35transfer passwords across devices. However, you'll need to memorize your
36"vault password" that allows you to open the password manager. It's
37important to make this something hard to guess since it would allow
38anyone who has it to access every password you've stored in there.
39
40Personally, I recommend using a
41[[https://en.wikipedia.org/wiki/Passphrase][passphrase]] instead of a
42[[https://en.wikipedia.org/wiki/Password][password]] for your vault
43password. Instead of using a string of characters (whether random or
44simple), use a phrase and add in symbols and a number. For example, your
45vault password could be =Racing-Alphabet-Gourd-Parrot3=. Swap the
46symbols out for whichever symbol you want, move the number around, and
47fine-tune the passphrase until you are confident that you can remember
48it whenever necessary.
49
50Once you've stored your passwords, make sure you continually check up on
51your account and make sure you aren't following bad password practices.
52Krebs on Security has a great
53[[https://krebsonsecurity.com/password-dos-and-donts/][blog post on
54password recommendations]]. Any time that a data breach happens, make
55sure you check to see if you were included, and if you need to reset any
56account passwords.
57
58* Developers
59
60** What Are the Basic Requirements?
61
62When developing any password-protected application, there are a few
63basic rules that anyone should follow even if they do not follow any
64official guidelines such as NIST. The foremost practice is to require
65users to use passwords that are at least 8 characters and cannot easily
66be guessed. This sounds extremely simple, but it requires quite a few
67different strategies. First, the application should check the potential
68passwords against a dictionary of insecure passwords such =password=,
69=1234abc=, or =application_name=.
70
71Next, the application should offer guidance on the strength of passwords
72being entered during enrollment. Further, NIST officially recommends
73*not* implementing any composition rules that make passwords hard to
74remember (e.g. passwords with letters, numbers, and special characters)
75and instead encouraging the use of long pass phrases which can include
76spaces. It should be noted that to be able to keep spaces within
77passwords, all unicode characters should be supported, and passwords
78should not be truncated.
79
80** What Does NIST Recommend?
81
82The National Institute of Standards and Technology
83([[https://www.nist.gov][NIST]]) in the US Department of Commerce
84regularly publishes information around information security and digital
85identity guidelines. Recently, NIST published
86[[https://pages.nist.gov/800-63-3/sp800-63b.html][Special Publication
87800-63b]]: Digital Identity Guidelines and Authentication and Lifecycle
88Management.
89
90#+begin_quote
91A Memorized Secret authenticator - commonly referred to as a password
92or, if numeric, a PIN - is a secret value intended to be chosen and
93memorized by the user. Memorized secrets need to be of sufficient
94complexity and secrecy that it would be impractical for an attacker to
95guess or otherwise discover the correct secret value. A memorized secret
96is something you know.
97
98- NIST Special Publication 800-63B
99#+end_quote
100
101NIST offers a lot of guidance on passwords, but I'm going to highlight
102just a few of the important factors:
103
104- Require passwords to be a minimum of 8 characters (6 characters if
105 randomly generated and be generated using an approved random bit
106 generator).
107- Compare potential passwords against a list that contains values known
108 to be commonly-used, expected, or compromised.
109- Offer guidance on password strength, such as a strength meter.
110- Implement a rate-limiting mechanism to limit the number of failed
111 authentication attempts for each user account.
112- Do not require composition rules for passwords and do not require
113 passwords to be changed periodically (unless compromised).
114- Allow pasting of user identification and passwords to facilitate the
115 use of password managers.
116- Allow users to view the password as it is being entered.
117- Use secure forms of communication and storage, including salting and
118 hashing passwords using a one-way key derivation function.
119
120NIST offers further guidance on other devices that require specific
121security policies, querying for passwords, and more. All the information
122discussed so far comes from
123[[https://pages.nist.gov/800-63-3/sp800-63b.html][NIST SP800-63b]] but
124NIST offers a lot of information on digital identities, enrollment,
125identity proofing, authentication, lifecycle management, federation, and
126assertions in the total [[https://pages.nist.gov/800-63-3/][NIST
127SP800-63 Digital Identity Guidelines]].
content/blog/2020-01-25-linux-software.md deleted −246
@@ -1,246 +0,0 @@
1+++
2date = 2020-01-25
3title = "My Favorite Linux Software"
4description = ""
5draft = false
6+++
7
8# GUI Applications
9
10## Etcher
11
12[Etcher](https://www.balena.io/etcher/) is a quick and easy way to burn ISO
13images to CDs and USB devices. There are two different ways you can install this
14program. First, you can navigate to the [official
15website](https://www.balena.io/etcher/) and download the AppImage file, which
16can run without installation.
17
18However, AppImage files are not executable by default, so you'll either need to
19right-click to open the properties of the file and click the "Allow executing
20file as program" box in the Permissions tab or use the following command:
21
22```sh
23chmod u+x FILE_NAME
24```
25
26If you don't like AppImage files or just prefer repositories, you can use the
27following commands to add the author's repository and install it through the
28command-line only.
29
30First, you'll have to echo the repo and write it to a list file:
31
32```sh
33echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
34```
35
36Next, add the application keys to Ubuntu's keyring:
37
38```sh
39sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
40```
41
42Finally, update the repositories and install the app.
43
44```sh
45sudo apt update && sudo apt install balena-etcher-electron
46```
47
48Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
49
50```sh
51sudo pacman -S etcher
52```
53
54## Atom
55
56[Atom](https://atom.io) is the self-proclaimed "hackable text editor for the
5721st century". This text editor is made by GitHub, [now owned by
58Microsoft](https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/),
59and has some of the best add-ons available to customize the layout and abilities
60of the app.
61
62First, add the Atom repository to your sources.
63
64```sh
65sudo add-apt-repository ppa:webupd8team/atom
66```
67
68Next, update your package listings and install atom.
69
70```sh
71sudo apt update && sudo apt install atom
72```
73
74If you have issues updating your packages with the Atom repository, you'll need
75to use the snap package described below instead of the repository. To remove the
76repository we just added, use this command:
77
78```sh
79sudo add-apt-repository -r ppa:webupd8team/atom
80```
81
82You can also install Atom as a snap package, but it must be installed with the
83`--classic` flag. A [full explanation is
84available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement)
85if you'd like to read more about why you need the classic flag.
86
87```sh
88snap install atom --classic
89```
90
91Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
92
93```sh
94sudo pacman -S atom
95```
96
97## Visual Studio Code
98
99[Visual Studio Code](https://code.visualstudio.com) is yet another fantastic
100choice for programming on Linux, especially if you need those extra add-ons to
101spice up your late-night coding sessions. The theme used in the screenshot is
102[Mars](https://marketplace.visualstudio.com/items?itemName=EliverLara.mars) by
103theme creator [Eliver Lara](https://github.com/EliverLara), who makes a ton of
104great themes for VS Code, Atom, and various Linux desktop environments.
105
106To install VS Code, you'll need to download the `.deb` file from the official
107website. Once you've downloaded the file, either double-click it to install
108through the Software Center or run the following command:
109
110```sh
111sudo dpkg -i FILE_NAME.deb
112```
113
114You can also install VS Code as a snap package, but it must be installed with
115the `--classic` flag. A [full explanation is
116available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement)
117if you'd like to read more about why you need the classic flag.
118
119```sh
120snap install code --classic
121```
122
123Using Arch, Manjaro, or another distro using the AUR? Use these commands
124instead:
125
126```sh
127sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
128```
129
130## GNOME Tweaks
131
132[Gnome Tweaks](https://gitlab.gnome.org/GNOME/gnome-tweaks) is the ultimate tool
133to use if you want to customize your GNOME desktop environment. This is how you
134can switch application themes (GTK), shell themes, icons, fonts, and more. To
135install GNOME Tweaks on Ubuntu, you just need to install the official package.
136
137```sh
138sudo apt install gnome-tweaks
139```
140
141If you've installed Manjaro or Arch with Gnome, you should have the tweak tool
142pre-installed. If you're on Fedora, this tool is available as an official
143package:
144
145```sh
146sudo dnf install gnome-tweaks
147```
148
149## Steam
150
151[Steam](https://steampowered.com) is one of the most popular gaming libraries
152for computers and is one of the main reasons that many people have been able to
153switch to Linux in recent years, thanks to Steam Proton, which makes it easier
154to play games not officially created for Linux platforms.
155
156To install Steam on Ubuntu, you just need to install the official package.
157
158```sh
159sudo apt install steam-installer
160```
161
162For Arch-based systems, you'll simply need to install the `steam` package.
163However, this requires that you enable the `multilib` source. To do so, use the
164following command:
165
166```sh
167sudo nano /etc/pacman.conf
168```
169
170Now, scroll down and uncomment the `multilib` section.
171
172```config
173# Before:
174#[multilib]
175#Include = /etc/pacman.d/mirrorlist
176
177# After:
178[multilib]
179Include = /etc/pacman.d/mirrorlist
180```
181
182Finally, install the program:
183
184```sh
185sudo pacman -S steam
186```
187
188[Problem Launching Steam Games? Click
189Here.](./2020-01-26-steam-on-ntfs-drives.html)
190
191# Command-Line Packages
192
193## neofetch
194
195[Neofetch](https://github.com/dylanaraps/neofetch) is a customizable tool used
196in the command-line to show system information. This is exceptionally useful if
197you want to see your system's information quickly without the clutter of some
198resource-heavy GUI apps.
199
200This is an official package if you're running Ubuntu 17.04 or later, so simply
201use the following command:
202
203```sh
204sudo apt install neofetch
205```
206
207If you're running Ubuntu 16.10 or earlier, you'll have to use a series of
208commands:
209
210```sh
211sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
212```
213
214Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
215
216```sh
217sudo pacman -S neofetch
218```
219
220## yt-dlp
221
222[yt-dlp](https://github.com/yt-dlp/yt-dlp) is an extremely handy command-line
223tool that allows you to download video or audio files from various websites,
224such as YouTube. There are a ton of different options when running this package,
225so be sure to run `yt-dlp --help` first to look through everything you can do
226(or give up and search for the best config online).
227
228While this shouldn't be a problem for most users, yt-dlp requires Python 2.6,
2292.7, or 3.2+ to work correctly, so install Python if you don't have it already.
230You can check to see if you have Python installed by running:
231
232```sh
233python -V
234```
235
236To get the youtube-dl package, simply curl the URL and output the results.
237
238```sh
239sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
240```
241
242Finally, make the file executable so that it can be run from the command-line.
243
244```sh
245sudo chmod a+rx /usr/local/bin/yt-dlp
246```
content/blog/2020-01-25-linux-software.org added +259
@@ -0,0 +1,259 @@
1#+date: <2020-01-25>
2#+title: My Favorite Linux Software
3#+description:
4
5
6* GUI Applications
7
8** Etcher
9
10[[https://www.balena.io/etcher/][Etcher]] is a quick and easy way to
11burn ISO images to CDs and USB devices. There are two different ways you
12can install this program. First, you can navigate to the
13[[https://www.balena.io/etcher/][official website]] and download the
14AppImage file, which can run without installation.
15
16However, AppImage files are not executable by default, so you'll either
17need to right-click to open the properties of the file and click the
18"Allow executing file as program" box in the Permissions tab or use the
19following command:
20
21#+begin_src sh
22chmod u+x FILE_NAME
23#+end_src
24
25If you don't like AppImage files or just prefer repositories, you can
26use the following commands to add the author's repository and install it
27through the command-line only.
28
29First, you'll have to echo the repo and write it to a list file:
30
31#+begin_src sh
32echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
33#+end_src
34
35Next, add the application keys to Ubuntu's keyring:
36
37#+begin_src sh
38sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
39#+end_src
40
41Finally, update the repositories and install the app.
42
43#+begin_src sh
44sudo apt update && sudo apt install balena-etcher-electron
45#+end_src
46
47Using Arch, Manjaro, or another distro using the AUR? Use this command
48instead:
49
50#+begin_src sh
51sudo pacman -S etcher
52#+end_src
53
54** Atom
55
56[[https://atom.io][Atom]] is the self-proclaimed "hackable text editor
57for the 21st century". This text editor is made by GitHub,
58[[https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/][now
59owned by Microsoft]], and has some of the best add-ons available to
60customize the layout and abilities of the app.
61
62First, add the Atom repository to your sources.
63
64#+begin_src sh
65sudo add-apt-repository ppa:webupd8team/atom
66#+end_src
67
68Next, update your package listings and install atom.
69
70#+begin_src sh
71sudo apt update && sudo apt install atom
72#+end_src
73
74If you have issues updating your packages with the Atom repository,
75you'll need to use the snap package described below instead of the
76repository. To remove the repository we just added, use this command:
77
78#+begin_src sh
79sudo add-apt-repository -r ppa:webupd8team/atom
80#+end_src
81
82You can also install Atom as a snap package, but it must be installed
83with the =--classic= flag. A
84[[https://language-bash.com/blog/how-to-snap-introducing-classic-confinement][full
85explanation is available]] if you'd like to read more about why you need
86the classic flag.
87
88#+begin_src sh
89snap install atom --classic
90#+end_src
91
92Using Arch, Manjaro, or another distro using the AUR? Use this command
93instead:
94
95#+begin_src sh
96sudo pacman -S atom
97#+end_src
98
99** Visual Studio Code
100
101[[https://code.visualstudio.com][Visual Studio Code]] is yet another
102fantastic choice for programming on Linux, especially if you need those
103extra add-ons to spice up your late-night coding sessions. The theme
104used in the screenshot is
105[[https://marketplace.visualstudio.com/items?itemName=EliverLara.mars][Mars]]
106by theme creator [[https://github.com/EliverLara][Eliver Lara]], who
107makes a ton of great themes for VS Code, Atom, and various Linux desktop
108environments.
109
110To install VS Code, you'll need to download the =.deb= file from the
111official website. Once you've downloaded the file, either double-click
112it to install through the Software Center or run the following command:
113
114#+begin_src sh
115sudo dpkg -i FILE_NAME.deb
116#+end_src
117
118You can also install VS Code as a snap package, but it must be installed
119with the =--classic= flag. A
120[[https://language-bash.com/blog/how-to-snap-introducing-classic-confinement][full
121explanation is available]] if you'd like to read more about why you need
122the classic flag.
123
124#+begin_src sh
125snap install code --classic
126#+end_src
127
128Using Arch, Manjaro, or another distro using the AUR? Use these commands
129instead:
130
131#+begin_src sh
132sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
133#+end_src
134
135** GNOME Tweaks
136
137[[https://gitlab.gnome.org/GNOME/gnome-tweaks][Gnome Tweaks]] is the
138ultimate tool to use if you want to customize your GNOME desktop
139environment. This is how you can switch application themes (GTK), shell
140themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you
141just need to install the official package.
142
143#+begin_src sh
144sudo apt install gnome-tweaks
145#+end_src
146
147If you've installed Manjaro or Arch with Gnome, you should have the
148tweak tool pre-installed. If you're on Fedora, this tool is available as
149an official package:
150
151#+begin_src sh
152sudo dnf install gnome-tweaks
153#+end_src
154
155** Steam
156
157[[https://steampowered.com][Steam]] is one of the most popular gaming
158libraries for computers and is one of the main reasons that many people
159have been able to switch to Linux in recent years, thanks to Steam
160Proton, which makes it easier to play games not officially created for
161Linux platforms.
162
163To install Steam on Ubuntu, you just need to install the official
164package.
165
166#+begin_src sh
167sudo apt install steam-installer
168#+end_src
169
170For Arch-based systems, you'll simply need to install the =steam=
171package. However, this requires that you enable the =multilib= source.
172To do so, use the following command:
173
174#+begin_src sh
175sudo nano /etc/pacman.conf
176#+end_src
177
178Now, scroll down and uncomment the =multilib= section.
179
180#+begin_src config
181# Before:
182#[multilib]
183#Include = /etc/pacman.d/mirrorlist
184
185# After:
186[multilib]
187Include = /etc/pacman.d/mirrorlist
188#+end_src
189
190Finally, install the program:
191
192#+begin_src sh
193sudo pacman -S steam
194#+end_src
195
196[[./2020-01-26-steam-on-ntfs-drives.html][Problem Launching Steam Games?
197Click Here.]]
198
199* Command-Line Packages
200
201** neofetch
202
203[[https://github.com/dylanaraps/neofetch][Neofetch]] is a customizable
204tool used in the command-line to show system information. This is
205exceptionally useful if you want to see your system's information
206quickly without the clutter of some resource-heavy GUI apps.
207
208This is an official package if you're running Ubuntu 17.04 or later, so
209simply use the following command:
210
211#+begin_src sh
212sudo apt install neofetch
213#+end_src
214
215If you're running Ubuntu 16.10 or earlier, you'll have to use a series
216of commands:
217
218#+begin_src sh
219sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
220#+end_src
221
222Using Arch, Manjaro, or another distro using the AUR? Use this command
223instead:
224
225#+begin_src sh
226sudo pacman -S neofetch
227#+end_src
228
229** yt-dlp
230
231[[https://github.com/yt-dlp/yt-dlp][yt-dlp]] is an extremely handy
232command-line tool that allows you to download video or audio files from
233various websites, such as YouTube. There are a ton of different options
234when running this package, so be sure to run =yt-dlp --help= first to
235look through everything you can do (or give up and search for the best
236config online).
237
238While this shouldn't be a problem for most users, yt-dlp requires Python
2392.6, 2.7, or 3.2+ to work correctly, so install Python if you don't have
240it already. You can check to see if you have Python installed by
241running:
242
243#+begin_src sh
244python -V
245#+end_src
246
247To get the youtube-dl package, simply curl the URL and output the
248results.
249
250#+begin_src sh
251sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
252#+end_src
253
254Finally, make the file executable so that it can be run from the
255command-line.
256
257#+begin_src sh
258sudo chmod a+rx /usr/local/bin/yt-dlp
259#+end_src
content/blog/2020-01-26-steam-on-ntfs.md deleted −91
@@ -1,91 +0,0 @@
1+++
2date = 2020-01-26
3title = "Linux Gaming Tweak: Steam on NTFS Drives"
4description = ""
5draft = false
6+++
7
8# Auto-Mount Steam Drives
9
10If you want to see how to install Steam on Linux, see my other post: [Linux
11Software](../linux-software/).
12
13Are you having trouble launching games, even though they've installed correctly?
14This may happen if you're storing your games on an NTFS-formatted drive. This
15shouldn't be an issue if you're storing your games on the same drive that Steam
16is on, but some gamers prefer to put Steam on their main drive and game files on
17another SSD or HDD.
18
19To fix this problem, you'll need to try a few things. First, you'll need to
20install the `ntfs-3g` package, which is meant for better interoperability with
21Linux.
22
23```sh
24sudo apt install ntfs-3g
25```
26
27Next, you should set up the `/etc/fstab` file to automatically mount your drives
28on boot. To automatically mount your drives when the computer boots up, you'll
29have to create the folders you want to mount your drive to first. I store mine
30in the `/mnt` folder using names that I'll recognize, but you can create your
31folders wherever you want.
32
33```sh
34mkdir /path/to/folder
35```
36
37For example:
38
39```sh
40mkdir /mnt/steam_library
41```
42
43To automatically mount drives upon system boot, you will need to collect a few
44items. The UUID is the identification number connected to whichever drive you're
45using to store Steam games.
46
47Drives are usually labeled similar to `/dev/nvme0n1p1` or `/dev/sda1`, so you'll
48need to find the line in the output of the command below that correlates to your
49drive and copy the UUID over to the `/etc/fstab` file.
50
51```sh
52sudo blkid | grep UUID=
53```
54
55Next, you'll need your `uid` and `gid`. To find these, run the following
56command:
57
58```sh
59id -u && id -g
60```
61
62Now that you have collected the necessary information, open the `/etc/fstab`
63file:
64
65```sh
66sudo nano /etc/fstab
67```
68
69Each drive you want to mount on boot should have its own line in the
70`/etc/fstab` file that looks similar to this:
71
72```config
73UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
74```
75
76Now all you need to do is unmount your drive and re-mount it. You can unmount
77the drive by doing this (be sure to use the correct drive name here):
78
79```sh
80sudo umount /dev/sdxX
81```
82
83You can re-mount all your drives by executing the following:
84
85```sh
86sudo mount -a
87```
88
89If you don't know what your drive name is, or you're nervous about unmounting
90and re-mounting, simply reboot your computer, and it will be done for you
91automatically.
content/blog/2020-01-26-steam-on-ntfs.org added +91
@@ -0,0 +1,91 @@
1#+date: <2020-01-26>
2#+title: Linux Gaming Tweak: Steam on NTFS Drives
3#+description:
4
5
6* Auto-Mount Steam Drives
7
8If you want to see how to install Steam on Linux, see my other post:
9[[../linux-software/][Linux Software]].
10
11Are you having trouble launching games, even though they've installed
12correctly? This may happen if you're storing your games on an
13NTFS-formatted drive. This shouldn't be an issue if you're storing your
14games on the same drive that Steam is on, but some gamers prefer to put
15Steam on their main drive and game files on another SSD or HDD.
16
17To fix this problem, you'll need to try a few things. First, you'll need
18to install the =ntfs-3g= package, which is meant for better
19interoperability with Linux.
20
21#+begin_src sh
22sudo apt install ntfs-3g
23#+end_src
24
25Next, you should set up the =/etc/fstab= file to automatically mount
26your drives on boot. To automatically mount your drives when the
27computer boots up, you'll have to create the folders you want to mount
28your drive to first. I store mine in the =/mnt= folder using names that
29I'll recognize, but you can create your folders wherever you want.
30
31#+begin_src sh
32mkdir /path/to/folder
33#+end_src
34
35For example:
36
37#+begin_src sh
38mkdir /mnt/steam_library
39#+end_src
40
41To automatically mount drives upon system boot, you will need to collect
42a few items. The UUID is the identification number connected to
43whichever drive you're using to store Steam games.
44
45Drives are usually labeled similar to =/dev/nvme0n1p1= or =/dev/sda1=,
46so you'll need to find the line in the output of the command below that
47correlates to your drive and copy the UUID over to the =/etc/fstab=
48file.
49
50#+begin_src sh
51sudo blkid | grep UUID=
52#+end_src
53
54Next, you'll need your =uid= and =gid=. To find these, run the following
55command:
56
57#+begin_src sh
58id -u && id -g
59#+end_src
60
61Now that you have collected the necessary information, open the
62=/etc/fstab= file:
63
64#+begin_src sh
65sudo nano /etc/fstab
66#+end_src
67
68Each drive you want to mount on boot should have its own line in the
69=/etc/fstab= file that looks similar to this:
70
71#+begin_src config
72UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
73#+end_src
74
75Now all you need to do is unmount your drive and re-mount it. You can
76unmount the drive by doing this (be sure to use the correct drive name
77here):
78
79#+begin_src sh
80sudo umount /dev/sdxX
81#+end_src
82
83You can re-mount all your drives by executing the following:
84
85#+begin_src sh
86sudo mount -a
87#+end_src
88
89If you don't know what your drive name is, or you're nervous about
90unmounting and re-mounting, simply reboot your computer, and it will be
91done for you automatically.
content/blog/2020-02-09-cryptography-basics.md deleted −167
@@ -1,167 +0,0 @@
1+++
2date = 2020-02-09
3title = "Cryptography Basics"
4description = ""
5draft = false
6+++
7
8# Similar Article Available
9
10If you haven't already, feel free to read my post on [AES
11Encryption](../aes-encryption/).
12
13# What is Cryptography?
14
15In layman's terms, cryptography is a process that can change data from a
16readable format into an unreadable format (and vice-versa) through a series of
17processes and secrets. More technically, this is the Internet Security
18Glossary's definition:
19
20> [Cryptography is] the mathematical science that deals with transforming data
21> to render its meaning unintelligible (i.e., to hide its semantic content),
22> prevent its undetected alteration, or prevent its unauthorized use. If the
23> transformation is reversible, cryptography also deals with restoring encrypted
24> data to an intelligible form.
25>
26> - [Internet Security Glossary (2000)](https://tools.ietf.org/html/rfc2828)
27
28Cryptography cannot offer protection against the loss of data; it simply offers
29encryption methods to protect data at-rest and data in-traffic. At a high-level,
30encrypted is when plaintext data is encrypted to ciphertext (a secure form of
31text that cannot be understood unless decrypted back to plaintext). The
32encryption process is completed through the use of a mathematical function that
33utilizes one or more values called keys to encrypt or decrypt the data.
34
35# Key Elements of Cryptographic Systems
36
37To create or evaluate a cryptographic system, you need to know the essential
38pieces to the system:
39
40- **Encryption Algorithm (Primitive):** A mathematical process that encrypts
41 and decrypts data.
42- **Encryption Key:** A string of bits used within the encryption algorithm as
43 the secret that allows successful encryption or decryption of data.
44- **Key Length (Size):** The maximum number of bits within the encryption key.
45 It's important to remember that key size is regulated in many countries.
46- **Message Digest:** A smaller, fixed-size bit string version of the original
47 message. This is practically infeasible to reverse, which is why it's
48 commonly used to verify integrity.
49
50# Symmetric Systems (Secret Key Cryptography)
51
52Symmetric cryptography utilizes a secret, bidirectional key to perform both
53encryption and decryption of the data. The most common implementation of
54symmetric cryptography is the Advanced Encryption Standard, which uses keys that
55are 128 bits to 256 bits in size. This standard came after the National
56Institute of Standards and Technology (NIST) decided to retire the Data
57Encryption Standard (DES) in 2001.
58
59Since brute force attacks strongly correlate with key length, the 56-bit key
60length of DES was considered insecure after it was publicly broken in under 24
61hours. However, there is a modern implementation of DES called Triple DES where
62the DES method is applied three times to each data block.
63
64The main advantages to symmetric systems are the ease of use, since only one key
65is required for both encryption and decryption, and the simplicity of the
66algorithms. This helps with bulk data encryption that may unnecessarily waste
67time and power using asymmetric systems.
68
69However, symmetric systems have disadvantages to keep in mind. Since the key is
70private, it can be difficult to safely distribute keys to communication
71partners. Additionally, the key cannot be used to sign messages since it's
72necessary to keep the key private.
73
74# Asymmetric Systems (Public Key Cryptography)
75
76Asymmetric cryptography utilizes two keys within the system: a secret key that
77is privately-held and a public key that can be distributed freely. The
78interesting aspect of asymmetric cryptography is that either key can be used to
79encrypt the data, there's no rule that dictates which key must be used for
80encryption. Once one key is used to encrypt the data, only the other key can be
81used to decrypt the data. This means that if the private key encrypts the data,
82only the public key can decrypt the data.
83
84An advantage of this system is that if you successfully decrypt data using one
85of the keys, you can be sure of the sender since only the other key could have
86encrypted the data.
87
88One of the major implementations of an asymmetric system is a digital signature.
89A digital signature can be generated using the sender's private key, or a
90one-way hash function and is used to provide assurance for the integrity and
91authenticity of the message. A couple common message digest algorithms are
92SHA-256 and SHA-512, which securely compress data and produce a 128-bit message
93digest.
94
95It should be noted that man-in-the-middle attacks are one of the risks with
96digital signatures and public keys. To combat this, applications often use a
97public key infrastructure (PKI) to independently authenticate the validity of
98signatures and keys.
99
100Due to the large key size and [inefficient mathematical
101functions](https://crypto.stackexchange.com/a/591) of asymmetric encryption,
102elliptical curve cryptography (ECC) is often used to increase security while
103using fewer resources.
104
105# Applications of Cryptographic Systems
106
107There are quite a few implementations of cryptographic systems around the world.
108Here are a few popular examples:
109
110**Transport Layer Security (TLS):** One of the most famous cryptographic
111solutions created is TLS, a session-layered or connection-layered internet
112protocol that allows for secure communications between browsers and servers.
113Using handshakes, peer negotiation, and authentication allows TLS to prevent
114eavesdropping and malicious transformation of data. The major reason for TLS
115popularity is that a major vulnerability was found in the SSL protocol in 2014.
116Instead of SSL, TLS can be used with HTTP to form HTTPS and is the preferred
117method for modern web development due to its increased security.
118
119**Secure Hypertext Transfer Protocol (HTTPS):** An application layer protocol
120that allows for secure transport of data between servers and web clients. One of
121the unique parts of HTTPS is that it uses a secured port number instead of the
122default web port address.
123
124**Virtual Private Network (VPN):** VPNs are made to securely extend a private
125network across public networks by utilizing an encrypted layered tunneling
126protocol paired with an authentication method, such as usernames and passwords.
127This technology originally allowed remote employees to access their company's
128data but have evolved into one of the top choices for anyone who wishes to mask
129their sensitive personal data.
130
131**Internet Protocol Security (IPSec):** This protocol suite facilitates
132communication between two or more hosts or subnets by authenticating and
133encrypting the data packets. IPSec is used in a lot of VPNs to establish the VPN
134connection through the transport and tunnel mode encryption methods. IPSec
135encrypts just the data portion of packets in the transport methods, but it
136encrypts both the data and headers in the tunnel method (introducing an
137additional header for authentication).
138
139**Secure Shell (SSH):** SSH is another network protocol used to protect network
140services by authenticating users through a secure channel. This protocol is
141often used for command-line (shell) functions such as remote shell commands,
142logins, and file transfers.
143
144**Kerberos:** Developed by MIT, Kerberos is a computer-network authentication
145protocol that works on the basis of tickets to allow nodes communicating over a
146non-secure network to prove their identity to one another securely. This is most
147commonly used in business environments when used as the authentication and
148encryption method for Windows Active Directory (AD).
149
150# Cybersecurity Controls
151
152If you're someone who needs solutions on how to control risks associated with
153utilizing a crytograhpic system, start with a few basic controls:
154
155- **Policies:** A policy on the use of cryptographic controls for protection
156 of information is implemented and is in accordance with organizational
157 objectives.
158- **Key management:** A policy on the use, protection and lifetime of
159 cryptographic keys is implemented through the entire application lifecycle.
160- **Key size:** The organization has researched the optimal key size for their
161 purposes, considering national laws, required processing power, and
162 longevity of the solution.
163- **Algorithm selection:** Implemented algorithms are sufficiently appropriate
164 for the business of the organization, robust, and align with recommended
165 guidelines.
166- **Protocol configuration:** Protocols have been reviewed and configured
167 suitable to the purpose of the business.
content/blog/2020-02-09-cryptography-basics.org added +177
@@ -0,0 +1,177 @@
1#+date: <2020-02-09>
2#+title: Cryptography Basics
3#+description:
4
5
6* Similar Article Available
7
8If you haven't already, feel free to read my post on
9[[../aes-encryption/][AES Encryption]].
10
11* What is Cryptography?
12
13In layman's terms, cryptography is a process that can change data from a
14readable format into an unreadable format (and vice-versa) through a
15series of processes and secrets. More technically, this is the Internet
16Security Glossary's definition:
17
18#+begin_quote
19[Cryptography is] the mathematical science that deals with transforming
20data to render its meaning unintelligible (i.e., to hide its semantic
21content), prevent its undetected alteration, or prevent its unauthorized
22use. If the transformation is reversible, cryptography also deals with
23restoring encrypted data to an intelligible form.
24
25- [[https://tools.ietf.org/html/rfc2828][Internet Security Glossary
26 (2000)]]
27#+end_quote
28
29Cryptography cannot offer protection against the loss of data; it simply
30offers encryption methods to protect data at-rest and data in-traffic.
31At a high-level, encrypted is when plaintext data is encrypted to
32ciphertext (a secure form of text that cannot be understood unless
33decrypted back to plaintext). The encryption process is completed
34through the use of a mathematical function that utilizes one or more
35values called keys to encrypt or decrypt the data.
36
37* Key Elements of Cryptographic Systems
38
39To create or evaluate a cryptographic system, you need to know the
40essential pieces to the system:
41
42- *Encryption Algorithm (Primitive):* A mathematical process that
43 encrypts and decrypts data.
44- *Encryption Key:* A string of bits used within the encryption
45 algorithm as the secret that allows successful encryption or
46 decryption of data.
47- *Key Length (Size):* The maximum number of bits within the encryption
48 key. It's important to remember that key size is regulated in many
49 countries.
50- *Message Digest:* A smaller, fixed-size bit string version of the
51 original message. This is practically infeasible to reverse, which is
52 why it's commonly used to verify integrity.
53
54* Symmetric Systems (Secret Key Cryptography)
55
56Symmetric cryptography utilizes a secret, bidirectional key to perform
57both encryption and decryption of the data. The most common
58implementation of symmetric cryptography is the Advanced Encryption
59Standard, which uses keys that are 128 bits to 256 bits in size. This
60standard came after the National Institute of Standards and Technology
61(NIST) decided to retire the Data Encryption Standard (DES) in 2001.
62
63Since brute force attacks strongly correlate with key length, the 56-bit
64key length of DES was considered insecure after it was publicly broken
65in under 24 hours. However, there is a modern implementation of DES
66called Triple DES where the DES method is applied three times to each
67data block.
68
69The main advantages to symmetric systems are the ease of use, since only
70one key is required for both encryption and decryption, and the
71simplicity of the algorithms. This helps with bulk data encryption that
72may unnecessarily waste time and power using asymmetric systems.
73
74However, symmetric systems have disadvantages to keep in mind. Since the
75key is private, it can be difficult to safely distribute keys to
76communication partners. Additionally, the key cannot be used to sign
77messages since it's necessary to keep the key private.
78
79* Asymmetric Systems (Public Key Cryptography)
80
81Asymmetric cryptography utilizes two keys within the system: a secret
82key that is privately-held and a public key that can be distributed
83freely. The interesting aspect of asymmetric cryptography is that either
84key can be used to encrypt the data, there's no rule that dictates which
85key must be used for encryption. Once one key is used to encrypt the
86data, 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
88decrypt the data.
89
90An advantage of this system is that if you successfully decrypt data
91using one of the keys, you can be sure of the sender since only the
92other key could have encrypted the data.
93
94One of the major implementations of an asymmetric system is a digital
95signature. A digital signature can be generated using the sender's
96private key, or a one-way hash function and is used to provide assurance
97for the integrity and authenticity of the message. A couple common
98message digest algorithms are SHA-256 and SHA-512, which securely
99compress data and produce a 128-bit message digest.
100
101It should be noted that man-in-the-middle attacks are one of the risks
102with digital signatures and public keys. To combat this, applications
103often use a public key infrastructure (PKI) to independently
104authenticate the validity of signatures and keys.
105
106Due to the large key size and
107[[https://crypto.stackexchange.com/a/591][inefficient mathematical
108functions]] of asymmetric encryption, elliptical curve cryptography
109(ECC) is often used to increase security while using fewer resources.
110
111* Applications of Cryptographic Systems
112
113There are quite a few implementations of cryptographic systems around
114the world. Here are a few popular examples:
115
116*Transport Layer Security (TLS):* One of the most famous cryptographic
117solutions created is TLS, a session-layered or connection-layered
118internet protocol that allows for secure communications between browsers
119and servers. Using handshakes, peer negotiation, and authentication
120allows TLS to prevent eavesdropping and malicious transformation of
121data. The major reason for TLS popularity is that a major vulnerability
122was 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
124development due to its increased security.
125
126*Secure Hypertext Transfer Protocol (HTTPS):* An application layer
127protocol 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
129port number instead of the default web port address.
130
131*Virtual Private Network (VPN):* VPNs are made to securely extend a
132private network across public networks by utilizing an encrypted layered
133tunneling protocol paired with an authentication method, such as
134usernames and passwords. This technology originally allowed remote
135employees to access their company's data but have evolved into one of
136the top choices for anyone who wishes to mask their sensitive personal
137data.
138
139*Internet Protocol Security (IPSec):* This protocol suite facilitates
140communication between two or more hosts or subnets by authenticating and
141encrypting the data packets. IPSec is used in a lot of VPNs to establish
142the VPN connection through the transport and tunnel mode encryption
143methods. IPSec encrypts just the data portion of packets in the
144transport methods, but it encrypts both the data and headers in the
145tunnel method (introducing an additional header for authentication).
146
147*Secure Shell (SSH):* SSH is another network protocol used to protect
148network services by authenticating users through a secure channel. This
149protocol is often used for command-line (shell) functions such as remote
150shell commands, logins, and file transfers.
151
152*Kerberos:* Developed by MIT, Kerberos is a computer-network
153authentication protocol that works on the basis of tickets to allow
154nodes communicating over a non-secure network to prove their identity to
155one another securely. This is most commonly used in business
156environments when used as the authentication and encryption method for
157Windows Active Directory (AD).
158
159* Cybersecurity Controls
160
161If you're someone who needs solutions on how to control risks associated
162with utilizing a crytograhpic system, start with a few basic controls:
163
164- *Policies:* A policy on the use of cryptographic controls for
165 protection of information is implemented and is in accordance with
166 organizational objectives.
167- *Key management:* A policy on the use, protection and lifetime of
168 cryptographic keys is implemented through the entire application
169 lifecycle.
170- *Key size:* The organization has researched the optimal key size for
171 their purposes, considering national laws, required processing power,
172 and longevity of the solution.
173- *Algorithm selection:* Implemented algorithms are sufficiently
174 appropriate for the business of the organization, robust, and align
175 with recommended guidelines.
176- *Protocol configuration:* Protocols have been reviewed and configured
177 suitable to the purpose of the business.
content/blog/2020-03-25-session-messenger.md deleted −111
@@ -1,111 +0,0 @@
1+++
2date = 2020-03-25
3title = "Session Private Messenger"
4description = ""
5draft = false
6+++
7
8# Privacy Warning
9
10The company behind Session (Loki Foundation) is from Australia. If you didn't
11know, Australia has introduced
12[legislation](https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf)
13mandating companies comply with government requests to build backdoor access
14into applications. For more information, read my article on [AES
15Encryption](./2020-01-25-aes-encryption.html).
16
17# About Session
18
19[Session](https://getsession.org) is a private, cross-platform messaging app
20from the [Loki Foundation](https://loki.foundation). As someone who has spent
21years looking for quality alternatives to major messaging apps, I was excited
22when I first heard about Session. Reading through [Session's white
23paper](https://arxiv.org/pdf/2002.04609.pdf), you can learn the technologies
24behind the Session app. Part of the security of Session comes from the Signal
25protocol, which was forked as the origin of Session.
26
27> Session is an end-to-end encrypted messenger that removes sensitive metadata
28> collection, and is designed for people who want privacy and freedom from any
29> forms of surveillance.
30
31In general, this app promises security through end-to-end encryption,
32decentralized onion routing, and private identities. The biggest change that the
33Loki Foundation has made to the Signal protocol is removing the need for a phone
34number. Instead, a random identification string is generated for any session you
35create. This means you can create a new session for each device if you want to,
36or link new devices with your ID.
37
38Since Session's website and white paper describe the details of Session's
39security, I'm going to focus on using the app in this post.
40
41# Features
42
43Since most people are looking for an alternative to a popular chat app, I am
44going to list out the features that Session has so that you are able to
45determine if the app would suit your needs:
46
47- Multiple device linking (via QR code or ID)
48- App locking via device screen lock, password, or fingerprint
49- Screenshot blocking
50- Incognito keyboard
51- Read receipts and typing indicators
52- Mobile notification customization
53- Old message deletion and conversation limit
54- Backups
55- Recovery phrase
56- Account deletion, including ID, messages, sessions, and contacts
57
58# Downloads
59
60I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and iOS 15.
61All apps have worked well without many issues.
62
63Below is a brief overview of the Session app on Linux. To get this app, you'll
64need to go to the [Downloads](https://getsession.org/download/) page and click
65to link to the operating system you're using.
66
67For Linux, it will download an AppImage that you'll need to enable with the
68following command:
69
70```sh
71sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
72```
73
74# Creating an Account
75
76Once you've installed the app, simply run the app and create your unique Session
77ID. It will look something like this:
78`05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608`.
79
80You'll need to set a display name and, optionally, a password. If you set a
81password, you will need to enter it every time you open the app.
82
83# Start Messaging
84
85Once you've created your account and set up your profile details, the next step
86is to start messaging other people. To do so, you'll need to share your Session
87ID with other people. From this point, it's fairly straightforward and acts like
88any other messaging app, so I won't dive into much detail here.
89
90## macOS
91
92One key feature to note is that the desktop application now provides a helpful
93pop-up box explaining the process that Session uses to hide your IP address:
94
95## iOS
96
97The mobile app is quite simple and effective, giving you all the standard mobile
98messaging options you'd expect.
99
100# Potential Issues
101
102I've discovered one annoying issue that would prevent from using this app
103regularly. On a mobile device, there have been issues with receiving messages on
104time. Even with battery optimization disabled and no network restrictions,
105Session notifications sometimes do not display until I open the app or the
106conversation itself and wait a few moments. This is actually one of the reasons
107I stopped using Signal (this seems fixed as of my updates in 2021/2022, so I
108wouldn't worry about this issue anymore).
109
110Looking for another messenger instead of Session? I recommend Signal, Matrix,
111and IRC.
content/blog/2020-03-25-session-messenger.org added +116
@@ -0,0 +1,116 @@
1#+date: <2020-03-25>
2#+title: Session Private Messenger
3#+description:
4
5
6* Privacy Warning
7
8The company behind Session (Loki Foundation) is from Australia. If you
9didn't know, Australia has introduced
10[[https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf][legislation]]
11mandating companies comply with government requests to build backdoor
12access into applications. For more information, read my article on
13[[./2020-01-25-aes-encryption.html][AES Encryption]].
14
15* About Session
16
17[[https://getsession.org][Session]] is a private, cross-platform
18messaging app from the [[https://loki.foundation][Loki Foundation]]. As
19someone who has spent years looking for quality alternatives to major
20messaging apps, I was excited when I first heard about Session. Reading
21through [[https://arxiv.org/pdf/2002.04609.pdf][Session's white paper]],
22you can learn the technologies behind the Session app. Part of the
23security of Session comes from the Signal protocol, which was forked as
24the origin of Session.
25
26#+begin_quote
27Session is an end-to-end encrypted messenger that removes sensitive
28metadata collection, and is designed for people who want privacy and
29freedom from any forms of surveillance.
30#+end_quote
31
32In general, this app promises security through end-to-end encryption,
33decentralized onion routing, and private identities. The biggest change
34that the Loki Foundation has made to the Signal protocol is removing the
35need for a phone number. Instead, a random identification string is
36generated for any session you create. This means you can create a new
37session for each device if you want to, or link new devices with your
38ID.
39
40Since Session's website and white paper describe the details of
41Session's security, I'm going to focus on using the app in this post.
42
43* Features
44
45Since most people are looking for an alternative to a popular chat app,
46I am going to list out the features that Session has so that you are
47able to determine if the app would suit your needs:
48
49- Multiple device linking (via QR code or ID)
50- App locking via device screen lock, password, or fingerprint
51- Screenshot blocking
52- Incognito keyboard
53- Read receipts and typing indicators
54- Mobile notification customization
55- Old message deletion and conversation limit
56- Backups
57- Recovery phrase
58- Account deletion, including ID, messages, sessions, and contacts
59
60* Downloads
61
62I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and
63iOS 15. All apps have worked well without many issues.
64
65Below is a brief overview of the Session app on Linux. To get this app,
66you'll need to go to the [[https://getsession.org/download/][Downloads]]
67page and click to link to the operating system you're using.
68
69For Linux, it will download an AppImage that you'll need to enable with
70the following command:
71
72#+begin_src sh
73sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
74#+end_src
75
76* Creating an Account
77
78Once you've installed the app, simply run the app and create your unique
79Session ID. It will look something like this:
80=05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608=.
81
82You'll need to set a display name and, optionally, a password. If you
83set a password, you will need to enter it every time you open the app.
84
85* Start Messaging
86
87Once you've created your account and set up your profile details, the
88next step is to start messaging other people. To do so, you'll need to
89share your Session ID with other people. From this point, it's fairly
90straightforward and acts like any other messaging app, so I won't dive
91into much detail here.
92
93** macOS
94
95One key feature to note is that the desktop application now provides a
96helpful pop-up box explaining the process that Session uses to hide your
97IP address:
98
99** iOS
100
101The mobile app is quite simple and effective, giving you all the
102standard mobile messaging options you'd expect.
103
104* Potential Issues
105
106I've discovered one annoying issue that would prevent from using this
107app regularly. On a mobile device, there have been issues with receiving
108messages on time. Even with battery optimization disabled and no network
109restrictions, Session notifications sometimes do not display until I
110open the app or the conversation itself and wait a few moments. This is
111actually one of the reasons I stopped using Signal (this seems fixed as
112of my updates in 2021/2022, so I wouldn't worry about this issue
113anymore).
114
115Looking for another messenger instead of Session? I recommend Signal,
116Matrix, and IRC.
content/blog/2020-05-03-homelab.md deleted −152
@@ -1,152 +0,0 @@
1+++
2date = 2020-05-03
3title = "An Inside Look at My Home Lab"
4description = ""
5draft = false
6+++
7
8# What is a Homelab?
9
10Starting as a developer, I have largely stayed away from hardware-based hobbies
11(other than building a gaming desktop). However, as the quarantine for COVID-19
12stretches out further and further, I found myself bored and in search of new
13hobbies. After spending the last few months browsing the
14[r/homelab](https://www.reddit.com/r/homelab/) subreddit, I decided it was time
15to jump in and try things out for myself.
16
17Since I am a beginner and just recently graduated from college, everything I've
18done so far in my homelab is fairly low-budget.
19
20# Hardware
21
22## Raspberry Pi 4
23
24Luckily, I had actually purchased a [Raspberry Pi
254](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) before the
26quarantine started so that I could try to keep Plex Media Center running, even
27while my desktop computer was turned off. I started here, using the Pi to hold
28Plex and Pi-hole until I grew tired with the slow performance.
29
30Here are the specifications for the Pi 4:
31
32- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
33- 4GB LPDDR4-3200 SDRAM
34- Gigabit Ethernet
35- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
36- 64 GB MicroSD Card
37
38## Dell Optiplex 5040
39
40Since I wasn't happy with the Pi as my main server, I turned to Craigslist. I
41know a lot of other homelabbers use Ebay, but I can't seem to ever trust it
42enough to purchase items on there. So I used Craigslist and found a Dell
43Optiplex 5040 desktop computer on sale for $90. While this computer might be
44underpowered, it was one of the few computers under $100 that was available
45during quarantine.
46
47Here are the specifications for the Dell Optiplex 5040:
48
49- Intel Core i3 6100
50- 8GB RAM DDR3
51- Intel HD Graphics
52- Gigabit Ethernet
53- 500GB Hard Drive
54
55While this hardware would be awful for a work computer or a gaming rig, it
56turned out to be wonderful for my server purposes. The only limitation I have
57found so far is the CPU. The i3-6100 only has enough power for a single 4k video
58transcode at a time. I haven't tested more than three 1080p streams at a time,
59but the maximum amount of streams I've ever actually used is two.
60
61## WD easystore 10TB & 8TB
62
63Application storage and temporary files are stored on the internal hard drive of
64the server, but all media files (movies, tv, games, books, etc) are stored
65externally on my WD easystore hard drive. Creating auto-boot configurations in
66the `/etc/fstab` file on my server allows the hard drives to automatically mount
67whenever I need to restart my server.
68
69> Update: In March 2022, I shucked the hard drives out of their external cases,
70> put some Kapton tape on the third power pin to prevent power shutdowns, and
71> stuck them inside my server tower using internal SATA cables.
72
73## Netgear Unmanaged Switch
74
75To manage all the ethernet cords used by my homelab, my desktop, and my living
76room media center, I purchased an 8-port gigabit ethernet switch for $50 at my
77local computer store. This is probably much more than I should have spent on an
78unmanaged switch, but I am comfortable with the choice.
79
80## TP-Link Managed Switch
81
82Since I use the unmanaged switch to group all living room devices together, I
83use the managed switch to configure VLANs and secure my network.
84
85## Arris TM1602A Modem & Sagecom Fast 5280 Router
86
87My default modem and router, provided by my ISP, are fairly standard. The Arris
88modem supports DOCSIS 3.0, which is something that I definitely wanted as a
89minimum. The Sagecom router is also standard, no fancy bells or whistles.
90However, it does support DHCP and DHCPv6, which is something you can use to
91route all household traffic through a pi-hole or firewall.
92
93## TP-Link EAP
94
95In order to gain better control over the network, I use my own wireless access
96point instead of the one included in the Sagecom router above. Now I can control
97and organize all of my ethernet connections through the VLANs on the managed
98switch and wireless connections through the VLANS on the EAP.
99
100## Generic Printer
101
102The last piece to my homelab is a standard wireless printer. Nothing special
103here.
104
105# Software
106
107## Ubuntu Server 20.04
108
109While the 20.04 version of Ubuntu was just released, I always like to experiment
110with new features (and I don't mind breaking my system - it just gives me more
111experience learning how to fix things). So, I have Ubuntu Server 20.04 installed
112on the Dell Optiplex server and Ubuntu Server 19.10 installed on the Raspberry
113Pi. Once I find an acceptable use for the Pi, I will most likely switch the
114operating system.
115
116## Docker
117
118I am _very_ new to Docker, but I have had a lot of fun playing with it so far.
119Docker is used to create containers that can hold all the contents of a system
120without interfering with other software on the same system. So far, I have
121successfully installed pi-hole, GitLab, Gogs, and Nextcloud in containers.
122However, I opted to delete all of those so that I can reconfigure them more
123professionally at a later time.
124
125## Plex Media Server
126
127Plex is a media center software that allows you to organize your movies, TV
128shows, music, photos, and videos automatically. It will even download metadata
129for you so that you can easily browse these collections.
130
131## Pi-hole
132
133Pi-hole is an alternative ad-blocker that runs at the DNS level, allowing you to
134block traffic when it hits your network, so that you can reject any traffic you
135deem to be bad. Pi-hole uses blacklists and whitelists to decide which traffic
136block and, luckily, there are a lot of pre-made lists out there on Reddit,
137GitHub, etc.
138
139## Nextcloud
140
141While I had trouble with the Docker version of Nextcloud, I was very successful
142when setting up the snap version. Using this, I was able to map Nextcloud to a
143subdomain of a domain I own in Namecheap. Additionally, Nextcloud has an
144integration with Let's Encrypt that allows me to issue certificates
145automatically to any new domain I authorize.
146
147## Webmin
148
149To monitor my servers, and the processes running on them, I use the Webmin
150dashboard. This was fairly painless to set up, and I currently access it
151straight through the server's IP address. In the future, I will be looking to
152configure Webmin to use a custom domain just like Nextcloud.
content/blog/2020-05-03-homelab.org added +162
@@ -0,0 +1,162 @@
1#+date: <2020-05-03>
2#+title: An Inside Look at My Home Lab
3#+description:
4
5
6* What is a Homelab?
7
8Starting as a developer, I have largely stayed away from hardware-based
9hobbies (other than building a gaming desktop). However, as the
10quarantine for COVID-19 stretches out further and further, I found
11myself bored and in search of new hobbies. After spending the last few
12months browsing the [[https://www.reddit.com/r/homelab/][r/homelab]]
13subreddit, I decided it was time to jump in and try things out for
14myself.
15
16Since I am a beginner and just recently graduated from college,
17everything I've done so far in my homelab is fairly low-budget.
18
19* Hardware
20
21** Raspberry Pi 4
22
23Luckily, I had actually purchased a
24[[https://www.raspberrypi.org/products/raspberry-pi-4-model-b/][Raspberry
25Pi 4]] before the quarantine started so that I could try to keep Plex
26Media Center running, even while my desktop computer was turned off. I
27started here, using the Pi to hold Plex and Pi-hole until I grew tired
28with the slow performance.
29
30Here are the specifications for the Pi 4:
31
32- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
33- 4GB LPDDR4-3200 SDRAM
34- Gigabit Ethernet
35- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
36- 64 GB MicroSD Card
37
38** Dell Optiplex 5040
39
40Since I wasn't happy with the Pi as my main server, I turned to
41Craigslist. I know a lot of other homelabbers use Ebay, but I can't seem
42to ever trust it enough to purchase items on there. So I used Craigslist
43and found a Dell Optiplex 5040 desktop computer on sale for $90. While
44this computer might be underpowered, it was one of the few computers
45under $100 that was available during quarantine.
46
47Here are the specifications for the Dell Optiplex 5040:
48
49- Intel Core i3 6100
50- 8GB RAM DDR3
51- Intel HD Graphics
52- Gigabit Ethernet
53- 500GB Hard Drive
54
55While this hardware would be awful for a work computer or a gaming rig,
56it turned out to be wonderful for my server purposes. The only
57limitation I have found so far is the CPU. The i3-6100 only has enough
58power for a single 4k video transcode at a time. I haven't tested more
59than three 1080p streams at a time, but the maximum amount of streams
60I've ever actually used is two.
61
62** WD easystore 10TB & 8TB
63
64Application storage and temporary files are stored on the internal hard
65drive of the server, but all media files (movies, tv, games, books, etc)
66are stored externally on my WD easystore hard drive. Creating auto-boot
67configurations in the =/etc/fstab= file on my server allows the hard
68drives to automatically mount whenever I need to restart my server.
69
70#+begin_quote
71Update: In March 2022, I shucked the hard drives out of their external
72cases, put some Kapton tape on the third power pin to prevent power
73shutdowns, and stuck them inside my server tower using internal SATA
74cables.
75#+end_quote
76
77** Netgear Unmanaged Switch
78
79To manage all the ethernet cords used by my homelab, my desktop, and my
80living room media center, I purchased an 8-port gigabit ethernet switch
81for $50 at my local computer store. This is probably much more than I
82should have spent on an unmanaged switch, but I am comfortable with the
83choice.
84
85** TP-Link Managed Switch
86
87Since I use the unmanaged switch to group all living room devices
88together, I use the managed switch to configure VLANs and secure my
89network.
90
91** Arris TM1602A Modem & Sagecom Fast 5280 Router
92
93My default modem and router, provided by my ISP, are fairly standard.
94The Arris modem supports DOCSIS 3.0, which is something that I
95definitely wanted as a minimum. The Sagecom router is also standard, no
96fancy bells or whistles. However, it does support DHCP and DHCPv6, which
97is something you can use to route all household traffic through a
98pi-hole or firewall.
99
100** TP-Link EAP
101
102In order to gain better control over the network, I use my own wireless
103access point instead of the one included in the Sagecom router above.
104Now I can control and organize all of my ethernet connections through
105the VLANs on the managed switch and wireless connections through the
106VLANS on the EAP.
107
108** Generic Printer
109
110The last piece to my homelab is a standard wireless printer. Nothing
111special here.
112
113* Software
114
115** Ubuntu Server 20.04
116
117While the 20.04 version of Ubuntu was just released, I always like to
118experiment with new features (and I don't mind breaking my system - it
119just gives me more experience learning how to fix things). So, I have
120Ubuntu Server 20.04 installed on the Dell Optiplex server and Ubuntu
121Server 19.10 installed on the Raspberry Pi. Once I find an acceptable
122use for the Pi, I will most likely switch the operating system.
123
124** Docker
125
126I am /very/ new to Docker, but I have had a lot of fun playing with it
127so far. Docker is used to create containers that can hold all the
128contents of a system without interfering with other software on the same
129system. So far, I have successfully installed pi-hole, GitLab, Gogs, and
130Nextcloud in containers. However, I opted to delete all of those so that
131I can reconfigure them more professionally at a later time.
132
133** Plex Media Server
134
135Plex is a media center software that allows you to organize your movies,
136TV shows, music, photos, and videos automatically. It will even download
137metadata for you so that you can easily browse these collections.
138
139** Pi-hole
140
141Pi-hole is an alternative ad-blocker that runs at the DNS level,
142allowing you to block traffic when it hits your network, so that you can
143reject any traffic you deem to be bad. Pi-hole uses blacklists and
144whitelists to decide which traffic block and, luckily, there are a lot
145of pre-made lists out there on Reddit, GitHub, etc.
146
147** Nextcloud
148
149While I had trouble with the Docker version of Nextcloud, I was very
150successful when setting up the snap version. Using this, I was able to
151map Nextcloud to a subdomain of a domain I own in Namecheap.
152Additionally, Nextcloud has an integration with Let's Encrypt that
153allows me to issue certificates automatically to any new domain I
154authorize.
155
156** Webmin
157
158To monitor my servers, and the processes running on them, I use the
159Webmin dashboard. This was fairly painless to set up, and I currently
160access it straight through the server's IP address. In the future, I
161will be looking to configure Webmin to use a custom domain just like
162Nextcloud.
content/blog/2020-05-19-customizing-ubuntu.md deleted −193
@@ -1,193 +0,0 @@
1+++
2date = 2020-05-19
3title = "A Beginner's Guide: Customizing Ubuntu"
4description = ""
5draft = false
6+++
7
8# More Information
9
10For inspiration on designing your \*nix computer, check out the
11[r/unixporn](https://libredd.it/r/unixporn) subreddit!
12
13# Customizing Ubuntu
14
15New to Linux and want to add a personal touch to your machine? One of the best
16perks of Linux is that it is **extremely** customizable. You can change the
17styles of the windows, shell (status bars/docks), icons, fonts, terminals, and
18more.
19
20In this post, I'm going to go through customization on Ubuntu 20.04 (GNOME)
21since most new users tend to choose Ubuntu-based distros. If you've found a way
22to install Arch with i3-gaps, I'm assuming you know how to find more advanced
23tutorials out there on customizations.
24
25## Required Tools
26
27Ubuntu 20.04 ships with the default desktop environment
28[Gnome](https://www.gnome.org/), which includes the handy `gnome-tweaks` tool to
29quickly change designs. To install this, just open your terminal and enter the
30following command:
31
32```sh
33sudo apt install gnome-tweaks
34```
35
36After you've finished installing the tool, simply launch the Tweaks application,
37and you'll be able to access the various customization options available by
38default on Ubuntu. You might even like some of the pre-installed options.
39
40## GNOME Application Themes
41
42To change the themes applied to applications in GNOME, you will need to change
43the Applications dropdown in the Appearance section of Tweaks. To add more
44themes, you will have to find your preferred theme online and follow the steps
45below to have it show up in the Tweaks tool. While you may find themes anywhere,
46one of the most popular sites for GNOME themes is
47[gnome-look.org](https://www.gnome-look.org/). This website contains themes for
48applications, shells, icons, and cursors.
49
50Steps to import themes into Tweaks:
51
521. Download the theme.
532. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you will need
54 to extract the contents. This is easiest when opening the file explorer,
55 right-clicking the compressed file, and choosing "Extract here."
563. Move the theme folder to `/usr/share/themes/`. You can do so with the
57 following command: `sudo mv theme-folder/ /usr/share/themes/`.
58 - Icons and cursors will be moved to the `/usr/share/icons/` folder.
59 - Fonts will be moved to the `/usr/share/fonts/` folder Alternatively, you
60 can move them to the `/usr/share/fonts/opentype/` or
61 `/usr/share/fonts/opentype/` 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 Tweaks.
64
65If the theme is not showing up after you've moved it into the themes folder, you
66may have uncompressed the folder into a sub-folder. You can check this by
67entering the theme folder and listing the contents:
68
69```sh
70cd /usr/share/themes/Mojave-Dark && ls -la
71```
72
73This is an example of what the contents of your theme folder should look like.
74If you just see another folder there, you should move that folder up into the
75`/usr/share/themes/` folder.
76
77```sh
78cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
79```
80
81## GNOME Shell Themes
82
83To change the appearance of the title bar, default dock, app menu, and other
84parts of the GNOME shell, you'll need to install the [user
85themes](https://extensions.gnome.org/extension/19/user-themes/) extension on
86[Gnome Extensions](https://extensions.gnome.org/). To be able to install
87extensions, you will first need to install the browser extension that the
88website instructs you to. See this screenshot for the blue box with a link to
89the extension.
90
91After the browser extension is installed, you will need to install the native
92host connector:
93
94```sh
95sudo apt install chrome-gnome-shell
96```
97
98Finally, you can go the [user
99themes](https://extensions.gnome.org/extension/19/user-themes/) extension page
100and click the installation button. This will enable the Shell option in Tweaks.
101Now you can move shell themes to the `/usr/share/themes` directory, using the
102same steps mentioned in the previous section, and enable the new theme in
103Tweaks.
104
105## Icons & Cursors
106
107Icons and cursors are installed exactly the same way, so I'm grouping these
108together in this post. Both of these items will need to follow the same process
109as installing themes, except you will want to move your font folders to the
110`/usr/share/icons/` directory instead.
111
112## Fonts
113
114Fonts are one of the overlooked parts of customization, but a good font can make
115the whole screen look different. For example, I have installed the [IBM
116Plex](https://github.com/IBM/plex/releases) fonts on my system. This follows the
117same process as installing themes, except you will want to move your font
118folders to the `/usr/share/fonts/` directory instead.
119
120## Terminal
121
122If you spend a lot of time typing commands, you know how important the style and
123functionality of the terminal is. After spending a lot of time using the default
124GNOME terminal with [unix
125shell](<https://en.wikipedia.org/wiki/Bash_(Unix_shell)>), I decided to try some
126different options. I ended up choosing
127[Terminator](https://terminator-gtk3.readthedocs.io/en/latest/) with
128[zsh](https://en.wikipedia.org/wiki/Z_shell).
129
130Terminator is great if you need to open multiple terminals at one time by simply
131right-clicking and splitting the screen into as many terminals as you want.
132While this project hasn't been updated in a while, [it is coming under new
133development](https://github.com/gnome-terminator/terminator/issues/1). However,
134this terminal is great and I haven't experienced any errors yet.
135
136For the shell choice, I decided to choose zsh after trying it out on a fresh
137Manjaro installation. Zsh is great if you like to change the themes of your
138terminal, include icons, or add plugins.
139
140The desktop uses the
141[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions) to
142suggest past commands as you type. In addition, it suggests corrections if you
143misspell a command. Lastly, it uses the `af-magic` theme, which adds dashed
144lines between commands, moving the user@host tag to the right side of the
145terminal, and changes the colors. There are plenty of plugins and themes to
146choose from. Just figure out what you like and add it to your `~/.zshrc` file!
147
148### Steps to Replicate My Terminal
149
150To install zsh on Ubuntu, enter the following command into a terminal:
151
152```sh
153sudo apt install zsh
154```
155
156Then, enter the next command to activate zsh:
157
158```sh
159sudo chsh -s $(which zsh) $(whoami)
160```
161
162To install Terminator on Ubuntu:
163
164```sh
165sudo apt install terminator
166```
167
168To install Oh My Zsh on Ubuntu:
169
170```sh
171sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
172```
173
174To install zsh-autosuggestions via Oh My Zsh:
175
176```sh
177git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
178```
179
180Then, add the following plugin wording to your `~/.zshrc` file (the default
181config usually has the `git` plugin activated, so just add any other plugins to
182the parentheses separated by a space):
183
184```sh
185nano ~/.zshrc
186```
187
188```sh
189plugins=(git zsh-autosuggestions)
190```
191
192Finally, you need to log out of your computer and log back in so your user shell
193can refresh.
content/blog/2020-05-19-customizing-ubuntu.org added +198
@@ -0,0 +1,198 @@
1#+date: <2020-05-19>
2#+title: A Beginner's Guide: Customizing Ubuntu
3#+description:
4
5
6* More Information
7
8For inspiration on designing your *nix computer, check out the
9[[https://libredd.it/r/unixporn][r/unixporn]] subreddit!
10
11* Customizing Ubuntu
12
13New to Linux and want to add a personal touch to your machine? One of
14the best perks of Linux is that it is *extremely* customizable. You can
15change the styles of the windows, shell (status bars/docks), icons,
16fonts, terminals, and more.
17
18In this post, I'm going to go through customization on Ubuntu 20.04
19(GNOME) since most new users tend to choose Ubuntu-based distros. If
20you've found a way to install Arch with i3-gaps, I'm assuming you know
21how to find more advanced tutorials out there on customizations.
22
23** Required Tools
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
40
41To change the themes applied to applications in GNOME, you will need to
42change the Applications dropdown in the Appearance section of Tweaks. To
43add more themes, you will have to find your preferred theme online and
44follow the steps below to have it show up in the Tweaks tool. While you
45may find themes anywhere, one of the most popular sites for GNOME themes
46is [[https://www.gnome-look.org/][gnome-look.org]]. This website
47contains themes for applications, shells, icons, and cursors.
48
49Steps to import themes into Tweaks:
50
511. Download the theme.
522. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you
53 will need to extract the contents. This is easiest when opening the
54 file explorer, right-clicking the compressed file, and choosing
55 "Extract here."
563. Move the theme folder to =/usr/share/themes/=. You can do so with the
57 following command: =sudo mv theme-folder/ /usr/share/themes/=.
58 - Icons and cursors will be moved to the =/usr/share/icons/= folder.
59 - Fonts will be moved to the =/usr/share/fonts/= folder
60 Alternatively, you can move them to the
61 =/usr/share/fonts/opentype/= or =/usr/share/fonts/opentype/=
62 folders, if you have a specific font type.
634. Close tweaks if it is open. Re-open Tweaks and your new theme will be
64 available in the Applications dropdown in the Appearance section of
65 Tweaks.
66
67If the theme is not showing up after you've moved it into the themes
68folder, you may have uncompressed the folder into a sub-folder. You can
69check this by entering the theme folder and listing the contents:
70
71#+begin_src sh
72cd /usr/share/themes/Mojave-Dark && ls -la
73#+end_src
74
75This is an example of what the contents of your theme folder should look
76like. If you just see another folder there, you should move that folder
77up into the =/usr/share/themes/= folder.
78
79#+begin_src sh
80cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
81#+end_src
82
83** GNOME Shell Themes
84
85To change the appearance of the title bar, default dock, app menu, and
86other parts of the GNOME shell, you'll need to install the
87[[https://extensions.gnome.org/extension/19/user-themes/][user themes]]
88extension on [[https://extensions.gnome.org/][Gnome Extensions]]. To be
89able to install extensions, you will first need to install the browser
90extension that the website instructs you to. See this screenshot for the
91blue box with a link to the extension.
92
93After the browser extension is installed, you will need to install the
94native host connector:
95
96#+begin_src sh
97sudo apt install chrome-gnome-shell
98#+end_src
99
100Finally, you can go the
101[[https://extensions.gnome.org/extension/19/user-themes/][user themes]]
102extension page and click the installation button. This will enable the
103Shell option in Tweaks. Now you can move shell themes to the
104=/usr/share/themes= directory, using the same steps mentioned in the
105previous section, and enable the new theme in Tweaks.
106
107** Icons & Cursors
108
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
115
116Fonts are one of the overlooked parts of customization, but a good font
117can make the whole screen look different. For example, I have installed
118the [[https://github.com/IBM/plex/releases][IBM Plex]] fonts on my
119system. This follows the same process as installing themes, except you
120will want to move your font folders to the =/usr/share/fonts/= directory
121instead.
122
123** Terminal
124
125If you spend a lot of time typing commands, you know how important the
126style and functionality of the terminal is. After spending a lot of time
127using the default GNOME terminal with
128[[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][unix shell]], I
129decided to try some different options. I ended up choosing
130[[https://terminator-gtk3.readthedocs.io/en/latest/][Terminator]] with
131[[https://en.wikipedia.org/wiki/Z_shell][zsh]].
132
133Terminator is great if you need to open multiple terminals at one time
134by simply right-clicking and splitting the screen into as many terminals
135as you want. While this project hasn't been updated in a while,
136[[https://github.com/gnome-terminator/terminator/issues/1][it is coming
137under new development]]. However, this terminal is great and I haven't
138experienced any errors yet.
139
140For the shell choice, I decided to choose zsh after trying it out on a
141fresh Manjaro installation. Zsh is great if you like to change the
142themes of your terminal, include icons, or add plugins.
143
144The desktop uses the
145[[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]]
146to suggest past commands as you type. In addition, it suggests
147corrections if you misspell a command. Lastly, it uses the =af-magic=
148theme, which adds dashed lines between commands, moving the user@host
149tag to the right side of the terminal, and changes the colors. There are
150plenty of plugins and themes to choose from. Just figure out what you
151like and add it to your =~/.zshrc= file!
152
153*** Steps to Replicate My Terminal
154
155To install zsh on Ubuntu, enter the following command into a terminal:
156
157#+begin_src sh
158sudo apt install zsh
159#+end_src
160
161Then, enter the next command to activate zsh:
162
163#+begin_src sh
164sudo chsh -s $(which zsh) $(whoami)
165#+end_src
166
167To install Terminator on Ubuntu:
168
169#+begin_src sh
170sudo apt install terminator
171#+end_src
172
173To install Oh My Zsh on Ubuntu:
174
175#+begin_src sh
176sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
177#+end_src
178
179To install zsh-autosuggestions via Oh My Zsh:
180
181#+begin_src sh
182git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
183#+end_src
184
185Then, add the following plugin wording to your =~/.zshrc= file (the
186default config usually has the =git= plugin activated, so just add any
187other plugins to the parentheses separated by a space):
188
189#+begin_src sh
190nano ~/.zshrc
191#+end_src
192
193#+begin_src sh
194plugins=(git zsh-autosuggestions)
195#+end_src
196
197Finally, you need to log out of your computer and log back in so your
198user shell can refresh.
content/blog/2020-07-20-video-game-sales.md → content/blog/2020-07-20-video-game-sales.org renamed +59 −60
@@ -1,36 +1,35 @@
1+++ 1#+date: <2020-07-20>
2date = 2020-07-20 2#+title: Data Exploration: Video Games Sales
3title = "Data Exploration: Video Games Sales" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Background Information 5
6* Background Information
9 7
10This dataset (obtained from 8This dataset (obtained from
11[Kaggle](https://www.kaggle.com/gregorut/videogamesales/data)) contains a list 9[[https://www.kaggle.com/gregorut/videogamesales/data][Kaggle]])
12of video games with sales greater than 100,000 copies. It was generated by a 10contains a list of video games with sales greater than 100,000 copies.
13scrape of vgchartz.com. 11It was generated by a scrape of vgchartz.com.
14 12
15Fields include: 13Fields include:
16 14
17- Rank: Ranking of overall sales 15- Rank: Ranking of overall sales
18- Name: The game name 16- Name: The game name
19- Platform: Platform of the game release (i.e. PC,PS4, etc.) 17- Platform: Platform of the game release (i.e. PC,PS4, etc.)
20- Year: Year of the game's release 18- Year: Year of the game's release
21- Genre: Genre of the game 19- Genre: Genre of the game
22- Publisher: Publisher of the game 20- Publisher: Publisher of the game
23- NA~Sales~: Sales in North America (in millions) 21- NA_{Sales}: Sales in North America (in millions)
24- EU~Sales~: Sales in Europe (in millions) 22- EU_{Sales}: Sales in Europe (in millions)
25- JP~Sales~: Sales in Japan (in millions) 23- JP_{Sales}: Sales in Japan (in millions)
26- Other~Sales~: Sales in the rest of the world (in millions) 24- Other_{Sales}: Sales in the rest of the world (in millions)
27- Global~Sales~: Total worldwide sales. 25- Global_{Sales}: Total worldwide sales.
28 26
29There are 16,598 records. 2 records were dropped due to incomplete information. 27There are 16,598 records. 2 records were dropped due to incomplete
28information.
30 29
31# Import the Data 30* Import the Data
32 31
33```python 32#+begin_src python
34# Import the Python libraries we will be using 33# Import the Python libraries we will be using
35import pandas as pd 34import pandas as pd
36import numpy as np 35import numpy as np
@@ -41,42 +40,42 @@ import matplotlib.pyplot as plt
41file = r'video_game_sales.csv' 40file = r'video_game_sales.csv'
42df = pd.read_csv(file) 41df = pd.read_csv(file)
43df 42df
44``` 43#+end_src
45 44
46# Explore the Data 45* Explore the Data
47 46
48```python 47#+begin_src python
49# With the description function, we can see the basic stats. For example, we can 48# With the description function, we can see the basic stats. For example, we can
50# also see that the 'Year' column has some incomplete values. 49# also see that the 'Year' column has some incomplete values.
51df.describe() 50df.describe()
52``` 51#+end_src
53 52
54```python 53#+begin_src python
55# This function shows the rows and columns of NaN values. For example, df[179,3] = nan 54# This function shows the rows and columns of NaN values. For example, df[179,3] = nan
56np.where(pd.isnull(df)) 55np.where(pd.isnull(df))
57 56
58(array([179, ..., 16553], dtype=int64), 57(array([179, ..., 16553], dtype=int64),
59 array([3, ..., 5], dtype=int64)) 58 array([3, ..., 5], dtype=int64))
60``` 59#+end_src
61 60
62# Visualize the Data 61* Visualize the Data
63 62
64```python 63#+begin_src python
65# This function plots the global sales by platform 64# This function plots the global sales by platform
66sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90) 65sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
67``` 66#+end_src
68 67
69```python 68#+begin_src python
70# This function plots the global sales by genre 69# This function plots the global sales by genre
71sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45) 70sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
72``` 71#+end_src
73 72
74```python 73#+begin_src python
75# This function plots the global sales by year 74# This function plots the global sales by year
76sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45) 75sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
77``` 76#+end_src
78 77
79```python 78#+begin_src python
80# This function plots four different lines to show sales from different regions. 79# This function plots four different lines to show sales from different regions.
81# The global sales plot line is commented-out, but can be included for comparison 80# The global sales plot line is commented-out, but can be included for comparison
82df2 = df.groupby('Year').sum() 81df2 = df.groupby('Year').sum()
@@ -100,11 +99,11 @@ ax.plot(years, d, label='Other_Sales')
100 99
101ax.legend() 100ax.legend()
102plt.show() 101plt.show()
103``` 102#+end_src
104 103
105## Investigate Outliers 104** Investigate Outliers
106 105
107```python 106#+begin_src python
108# Find the game with the highest sales in North America 107# Find the game with the highest sales in North America
109df.loc[df['NA_Sales'].idxmax()] 108df.loc[df['NA_Sales'].idxmax()]
110 109
@@ -124,33 +123,33 @@ Name: 0, dtype: object
124# Explore statistics in the year 2006 (highest selling year) 123# Explore statistics in the year 2006 (highest selling year)
125df3 = df[(df['Year'] == 2006)] 124df3 = df[(df['Year'] == 2006)]
126df3.describe() 125df3.describe()
127``` 126#+end_src
128 127
129```python 128#+begin_src python
130# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports 129# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
131sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45) 130sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
132``` 131#+end_src
133 132
134```python 133#+begin_src python
135# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe 134# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
136# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS) 135# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
137df3.sort_values(by=['Global_Sales'], ascending=False).head(5) 136df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
138``` 137#+end_src
139 138
140# Discussion 139* Discussion
141 140
142The purpose of exploring datasets is to ask questions, answer questions, and 141The purpose of exploring datasets is to ask questions, answer questions,
143discover intelligence that can be used to inform decision-making. So, what have 142and discover intelligence that can be used to inform decision-making.
144we found in this dataset? 143So, what have we found in this dataset?
145 144
146Today we simply explored a publicly-available dataset to see what kind of 145Today we simply explored a publicly-available dataset to see what kind
147information it contained. During that exploration, we found that video game 146of information it contained. During that exploration, we found that
148sales peaked in 2006. That peak was largely due to Nintendo, who sold the top 5 147video game sales peaked in 2006. That peak was largely due to Nintendo,
149games in 2006 and has a number of games in the top-10 list for the years 148who sold the top 5 games in 2006 and has a number of games in the top-10
1501980-2020. Additionally, the top four platforms by global sales (Wii, NES, GB, 149list for the years 1980-2020. Additionally, the top four platforms by
151DS) are owned by Nintendo. 150global sales (Wii, NES, GB, DS) are owned by Nintendo.
152 151
153We didn't explore everything this dataset has to offer, but we can tell from a 152We didn't explore everything this dataset has to offer, but we can tell
154brief analysis that Nintendo seems to rule sales in the video gaming world. 153from a brief analysis that Nintendo seems to rule sales in the video
155Further analysis could provide insight into which genres, regions, publishers, 154gaming world. Further analysis could provide insight into which genres,
156or world events are correlated with sales. 155regions, publishers, or world events are correlated with sales.
content/blog/2020-07-26-business-analysis.md → content/blog/2020-07-26-business-analysis.org renamed +102 −98
@@ -1,32 +1,30 @@
1+++ 1#+date: <2020-07-26>
2date = 2020-07-26 2#+title: Algorithmically Analyzing Local Businesses
3title = "Algorithmically Analyzing Local Businesses " 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Background Information
9 5
10This project aims to help investors learn more about a random city in order to 6* Background Information
11determine optimal locations for business investments. The data used in this 7
12project was obtained using Foursquare's developer API. 8This project aims to help investors learn more about a random city in
9order to determine optimal locations for business investments. The data
10used in this project was obtained using Foursquare's developer API.
13 11
14Fields include: 12Fields include:
15 13
16- Venue Name 14- Venue Name
17- Venue Category 15- Venue Category
18- Venue Latitude 16- Venue Latitude
19- Venue Longitude 17- Venue Longitude
20 18
21There are 232 records found using the center of Lincoln as the area of interest 19There are 232 records found using the center of Lincoln as the area of
22with a radius of 10,000. 20interest with a radius of 10,000.
23 21
24# Import the Data 22* Import the Data
25 23
26The first step is the simplest: import the applicable libraries. We will be 24The first step is the simplest: import the applicable libraries. We will
27using the libraries below for this project. 25be using the libraries below for this project.
28 26
29```python 27#+begin_src python
30# Import the Python libraries we will be using 28# Import the Python libraries we will be using
31import pandas as pd 29import pandas as pd
32import requests 30import requests
@@ -35,29 +33,29 @@ import math
35import json 33import json
36from pandas.io.json import json_normalize 34from pandas.io.json import json_normalize
37from sklearn.cluster import KMeans 35from sklearn.cluster import KMeans
38``` 36#+end_src
39 37
40To begin our analysis, we need to import the data for this project. The data we 38To begin our analysis, we need to import the data for this project. The
41are using in this project comes directly from the Foursquare API. The first step 39data we are using in this project comes directly from the Foursquare
42is to get the latitude and longitude of the city being studied (Lincoln, NE) and 40API. The first step is to get the latitude and longitude of the city
43setting up the folium map. 41being studied (Lincoln, NE) and setting up the folium map.
44 42
45```python 43#+begin_src python
46# Define the latitude and longitude, then map the results 44# Define the latitude and longitude, then map the results
47latitude = 40.806862 45latitude = 40.806862
48longitude = -96.681679 46longitude = -96.681679
49map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12) 47map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12)
50 48
51map_LNK 49map_LNK
52``` 50#+end_src
53 51
54Now that we have defined our city and created the map, we need to go get the 52Now that we have defined our city and created the map, we need to go get
55business data. The Foursquare API will limit the results to 100 per API call, so 53the business data. The Foursquare API will limit the results to 100 per
56we use our first API call below to determine the total results that Foursquare 54API call, so we use our first API call below to determine the total
57has found. Since the total results are 232, we perform the API fetching process 55results that Foursquare has found. Since the total results are 232, we
58three times (100 + 100 + 32 = 232). 56perform the API fetching process three times (100 + 100 + 32 = 232).
59 57
60```python 58#+begin_src python
61# Foursquare API credentials 59# Foursquare API credentials
62CLIENT_ID = 'your-client-id' 60CLIENT_ID = 'your-client-id'
63CLIENT_SECRET = 'your-client-secret' 61CLIENT_SECRET = 'your-client-secret'
@@ -115,18 +113,19 @@ url3 = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret
115 113
116# Fetch the final results (201 - 232) 114# Fetch the final results (201 - 232)
117results3 = requests.get(url3).json() 115results3 = requests.get(url3).json()
118``` 116#+end_src
119 117
120# Clean the Data 118* Clean the Data
121 119
122Now that we have our data in three separate dataframes, we need to combine them 120Now that we have our data in three separate dataframes, we need to
123into a single dataframe and make sure to reset the index so that we have a 121combine them into a single dataframe and make sure to reset the index so
124unique ID for each business. The `get~categorytype~` function below will pull 122that we have a unique ID for each business. The =get~categorytype~=
125the categories and name from each business's entry in the Foursquare data 123function below will pull the categories and name from each business's
126automatically. Once all the data has been labeled and combined, the results are 124entry in the Foursquare data automatically. Once all the data has been
127stored in the `nearby_venues` dataframe. 125labeled and combined, the results are stored in the =nearby_venues=
126dataframe.
128 127
129```python 128#+begin_src python
130# 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
131def get_category_type(row): 130def get_category_type(row):
132 try: 131 try:
@@ -189,15 +188,15 @@ nearby_venues3.columns = [col.split(".")[-1] for col in nearby_venues3.columns]
189nearby_venues = nearby_venues.append(nearby_venues3) 188nearby_venues = nearby_venues.append(nearby_venues3)
190nearby_venues = nearby_venues.reset_index(drop=True) 189nearby_venues = nearby_venues.reset_index(drop=True)
191nearby_venues 190nearby_venues
192``` 191#+end_src
193 192
194# Visualize the Data 193* Visualize the Data
195 194
196We now have a complete, clean data set. The next step is to visualize this data 195We now have a complete, clean data set. The next step is to visualize
197onto the map we created earlier. We will be using folium's `CircleMarker()` 196this data onto the map we created earlier. We will be using folium's
198function to do this. 197=CircleMarker()= function to do this.
199 198
200```python 199#+begin_src python
201# add markers to map 200# add markers to map
202for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']): 201for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']):
203 label = '{} ({})'.format(name, categories) 202 label = '{} ({})'.format(name, categories)
@@ -213,20 +212,21 @@ for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng']
213 ).add_to(map_LNK) 212 ).add_to(map_LNK)
214 213
215map_LNK 214map_LNK
216``` 215#+end_src
217 216
218# Clustering: _k-means_ 217* Clustering: /k-means/
219 218
220To cluster the data, we will be using the _k-means_ algorithm. This algorithm is 219To cluster the data, we will be using the /k-means/ algorithm. This
221iterative and will automatically make sure that data points in each cluster are 220algorithm is iterative and will automatically make sure that data points
222as close as possible to each other, while being as far as possible away from 221in each cluster are as close as possible to each other, while being as
223other clusters. 222far as possible away from other clusters.
224 223
225However, we first have to figure out how many clusters to use (defined as the 224However, we first have to figure out how many clusters to use (defined
226variable _'k'_). To do so, we will use the next two functions to calculate the 225as the variable /'k'/). To do so, we will use the next two functions to
227sum of squares within clusters and then return the optimal number of clusters. 226calculate the sum of squares within clusters and then return the optimal
227number of clusters.
228 228
229```python 229#+begin_src python
230# This function will return the sum of squares found in the data 230# This function will return the sum of squares found in the data
231def calculate_wcss(data): 231def calculate_wcss(data):
232 wcss = [] 232 wcss = []
@@ -260,13 +260,13 @@ def optimal_number_of_clusters(wcss):
260 260
261# calculating the optimal number of clusters 261# calculating the optimal number of clusters
262n = optimal_number_of_clusters(sum_of_squares) 262n = optimal_number_of_clusters(sum_of_squares)
263``` 263#+end_src
264 264
265Now that we have found that our optimal number of clusters is six, we need to 265Now that we have found that our optimal number of clusters is six, we
266perform k-means clustering. When this clustering occurs, each business is 266need to perform k-means clustering. When this clustering occurs, each
267assigned a cluster number from 0 to 5 in the dataframe. 267business is assigned a cluster number from 0 to 5 in the dataframe.
268 268
269```python 269#+begin_src python
270# set number of clusters equal to the optimal number 270# set number of clusters equal to the optimal number
271kclusters = n 271kclusters = n
272 272
@@ -275,12 +275,13 @@ kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(cluster_df)
275 275
276# add clustering labels to dataframe 276# add clustering labels to dataframe
277nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_) 277nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_)
278``` 278#+end_src
279 279
280Success! We now have a dataframe with clean business data, along with a cluster 280Success! We now have a dataframe with clean business data, along with a
281number for each business. Now let's map the data using six different colors. 281cluster number for each business. Now let's map the data using six
282different colors.
282 283
283```python 284#+begin_src python
284# create map with clusters 285# create map with clusters
285map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12) 286map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12)
286colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717'] 287colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717']
@@ -299,30 +300,31 @@ for lat, lng, name, categories, cluster in zip(nearby_venues['lat'], nearby_venu
299 fill_opacity=0.7).add_to(map_clusters) 300 fill_opacity=0.7).add_to(map_clusters)
300 301
301map_clusters 302map_clusters
302``` 303#+end_src
303 304
304# Investigate Clusters 305* Investigate Clusters
305 306
306Now that we have figured out our clusters, let's do a little more analysis to 307Now that we have figured out our clusters, let's do a little more
307provide more insight into the clusters. With the information below, we can see 308analysis to provide more insight into the clusters. With the information
308which clusters are more popular for businesses and which are less popular. The 309below, we can see which clusters are more popular for businesses and
309results below show us that clusters 0 through 3 are popular, while clusters 4 310which are less popular. The results below show us that clusters 0
310and 5 are not very popular at all. 311through 3 are popular, while clusters 4 and 5 are not very popular at
312all.
311 313
312```python 314#+begin_src python
313# Show how many venues are in each cluster 315# Show how many venues are in each cluster
314color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black'] 316color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black']
315for x in range(0,6): 317for x in range(0,6):
316 print("Color of Cluster", x, ":", color_names[x]) 318 print("Color of Cluster", x, ":", color_names[x])
317 print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0]) 319 print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0])
318 print("---") 320 print("---")
319``` 321#+end_src
320 322
321Our last piece of analysis is to summarize the categories of businesses within 323Our last piece of analysis is to summarize the categories of businesses
322each cluster. With these results, we can clearly see that restaurants, coffee 324within each cluster. With these results, we can clearly see that
323shops, and grocery stores are the most popular. 325restaurants, coffee shops, and grocery stores are the most popular.
324 326
325```python 327#+begin_src python
326# Calculate how many venues there are in each category 328# Calculate how many venues there are in each category
327# Sort from largest to smallest 329# Sort from largest to smallest
328temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng']) 330temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng'])
@@ -342,21 +344,23 @@ with pd.option_context('display.max_rows', None, 'display.max_columns', None):
342 print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1]) 344 print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1])
343 print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1]) 345 print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1])
344 print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1]) 346 print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1])
345``` 347#+end_src
346 348
347# Discussion 349* Discussion
348 350
349In this project, we gathered location data for Lincoln, Nebraska, USA and 351In this project, we gathered location data for Lincoln, Nebraska, USA
350clustered the data using the k-means algorithm in order to identify the unique 352and clustered the data using the k-means algorithm in order to identify
351clusters of businesses in Lincoln. Through these actions, we found that there 353the unique clusters of businesses in Lincoln. Through these actions, we
352are six unique business clusters in Lincoln and that two of the clusters are 354found that there are six unique business clusters in Lincoln and that
353likely unsuitable for investors. The remaining four clusters have a variety of 355two of the clusters are likely unsuitable for investors. The remaining
354businesses, but are largely dominated by restaurants and grocery stores. 356four clusters have a variety of businesses, but are largely dominated by
355 357restaurants and grocery stores.
356Using this project, investors can now make more informed decisions when deciding 358
357the location and category of business in which to invest. 359Using this project, investors can now make more informed decisions when
358 360deciding the location and category of business in which to invest.
359Further studies may involve other attributes for business locations, such as 361
360population density, average wealth across the city, or crime rates. In addition, 362Further studies may involve other attributes for business locations,
361further studies may include additional location data and businesses by utilizing 363such as population density, average wealth across the city, or crime
362multiple sources, such as Google Maps and OpenStreetMap. 364rates. In addition, further studies may include additional location data
365and businesses by utilizing multiple sources, such as Google Maps and
366OpenStreetMap.
content/blog/2020-08-22-redirect-github-pages.md deleted −124
@@ -1,124 +0,0 @@
1+++
2date = 2020-08-22
3title = "Redirect GitHub Pages from Subdomain to the Top-Level Domain"
4description = ""
5draft = false
6+++
7
8# Short answer
9
10## Step 1
11
12Add a new file CNAME to your GitHub Pages repository containing only one line:
13your top-level domain name. E.g.: `example.com`
14
15## Step 2
16
17[Optional] but highly recommended
18
192.1: Remove all other top-level records (prefixed with @) of type A from your
20DNS configuration.
21
222.2: Remove a CNAME record for the second-level domain www if it is present.
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 username.
37
38## Step 4
39
40Wait for your DNS changes to propagate. DNS changes aren't effective
41immediately. They can take up to a full day to propagate.
42
43# Long answer
44
45This issue has two sides. One is the DNS configuration itself. Another one is
46the way GitHub Pages will forward HTTP requests.
47
48We need to know a few things to understand what GitHub is trying to say in their
49documentation.
50
51## DNS Entry Types
52
53There are two types of DNS records which interest us: CNAME and A.
54
55`A` is also known as `Apex` or sometimes as `root entry`. It forwards requests
56to a specified fixed IP address. `CNAME` entry forwards requests to a specified
57URL (actual valid plain text URL, not an IP address).
58
59## DNS Load balancing
60
61GitHub has one central URL address which accepts all DNS requests for GitHub
62Pages: `http://username.github.io`. That URL is resolved to different IP
63addresses based on your geographical location. Website hosted on GitHub Pages is
64a simple collection of `HTML`, `CSS` and `JS` files. GitHub distributes these
65files to different servers across the globe. So that when your browser sends a
66request from Europe, it receives data from a server in Europe. The same is valid
67for the requests from Asia and the USA.
68
69## What GitHub is trying to say
70
71Since `A` records in DNS must contain IP addresses, and they must be either
72`185.199.108.153` or `185.199.109.153` or `185.199.110.153` or
73`185.199.111.153`, there is no way to forward requests to a server located
74somewhere in Europe or Asia. Your website hosted at GitHub Pages will be
75downloaded from a central GitHub Pages server. There is a minor risk that if
76GitHub Pages DNS servers (`x.x.x.153`) are down for some reason, all custom
77domains which use fixed GitHub Pages IP addresses will not be accessible (their
78DNS requests will not be resolvable).
79
80That is why GitHub strongly suggests to either use a second-level domain for
81your GitHub Pages (e.g. `blog.example.com`) or use a DNS service provider that
82supports a record type `ALIAS` that acts as `A` record but forwards request to a
83URL address (e.g. `username.github.io`) instead of a fixed IP address.
84
85## How GitHub Pages treats HTTP requests
86
87After a DNS request for `your_github_username.github.io` is resolved into an IP
88address, e.g. `185.199.108.153` your browser sends an HTTP request to that
89server with an HTTP header `Host`. Below are `curl` examples that load the same
90website (these examples might not work if you are behind a proxy server):
91
92```sh
93curl --header "Host: your_github_username.github.io" http://185.199.108.153/
94curl --header "Host: www.example.com" http://185.199.108.153/
95curl --header "Host: example.com" http://185.199.108.153/
96```
97
98This way GitHub Pages servers know which user website to serve.
99
100> GitHub Pages server will automatically redirect HTTP requests to the top-level
101> domain if your `CNAME` file contains `example.com` but `www.example.com` is
102> requested.
103>
104> The same is valid if your `CNAME` file contains `www.example.com` but the
105> header `Host` in the `HTTP` request contains `example.com`.
106
107## Why can't I add a `CNAME` record entry that accepts a top-level request (`@`) to my DNS configuration?
108
109Quote from the GitHub Pages documentation:
110
111> Warning: Do not create a CNAME record for your custom apex domain! Doing so
112> may cause issues with other services, such as email, on that domain.
113
114## References:
115
1161. [Setting up a custom domain with GitHub
117 Pages](https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site)
1182. [My custom domain isn't
119 working](https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages)
1203. [Cannot access my GitHub Pages website by IP
121 Address](https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address)
1224. [How do I set up GitHub Pages to redirect DNS requests from a subdomain (e.g.
123 www) to the top-level domain (TLD, Apex
124 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-08-22-redirect-github-pages.org added +131
@@ -0,0 +1,131 @@
1#+date: <2020-08-22>
2#+title: Redirect GitHub Pages from Subdomain to the Top-Level Domain
3#+description:
4
5
6* Short answer
7
8** Step 1
9
10Add a new file CNAME to your GitHub Pages repository containing only one
11line: your top-level domain name. E.g.: =example.com=
12
13** Step 2
14
15[Optional] but highly recommended
16
172.1: Remove all other top-level records (prefixed with @) of type A from
18your DNS configuration.
19
202.2: Remove a CNAME record for the second-level domain www if it is
21present.
22
23** Step 3
24
25Add these 5 entries to the very top of your DNS configuration:
26
27#+begin_src txt
28@ A 185.199.108.153
29@ A 185.199.109.153
30@ A 185.199.110.153
31@ A 185.199.111.153
32www CNAME your_github_username.github.io.
33#+end_src
34
35Replace =your_github_username= with your actual GitHub username.
36
37** Step 4
38
39Wait for your DNS changes to propagate. DNS changes aren't effective
40immediately. They can take up to a full day to propagate.
41
42* Long answer
43
44This issue has two sides. One is the DNS configuration itself. Another
45one is the way GitHub Pages will forward HTTP requests.
46
47We need to know a few things to understand what GitHub is trying to say
48in their documentation.
49
50** DNS Entry Types
51
52There are two types of DNS records which interest us: CNAME and A.
53
54=A= is also known as =Apex= or sometimes as =root entry=. It forwards
55requests to a specified fixed IP address. =CNAME= entry forwards
56requests to a specified URL (actual valid plain text URL, not an IP
57address).
58
59** DNS Load balancing
60
61GitHub has one central URL address which accepts all DNS requests for
62GitHub Pages: =http://username.github.io=. That URL is resolved to
63different IP addresses based on your geographical location. Website
64hosted on GitHub Pages is a simple collection of =HTML=, =CSS= and =JS=
65files. GitHub distributes these files to different servers across the
66globe. So that when your browser sends a request from Europe, it
67receives data from a server in Europe. The same is valid for the
68requests from Asia and the USA.
69
70** What GitHub is trying to say
71
72Since =A= records in DNS must contain IP addresses, and they must be
73either =185.199.108.153= or =185.199.109.153= or =185.199.110.153= or
74=185.199.111.153=, there is no way to forward requests to a server
75located somewhere in Europe or Asia. Your website hosted at GitHub Pages
76will be downloaded from a central GitHub Pages server. There is a minor
77risk that if GitHub Pages DNS servers (=x.x.x.153=) are down for some
78reason, all custom domains which use fixed GitHub Pages IP addresses
79will not be accessible (their DNS requests will not be resolvable).
80
81That is why GitHub strongly suggests to either use a second-level domain
82for your GitHub Pages (e.g. =blog.example.com=) or use a DNS service
83provider that supports a record type =ALIAS= that acts as =A= record but
84forwards request to a URL address (e.g. =username.github.io=) instead of
85a fixed IP address.
86
87** How GitHub Pages treats HTTP requests
88
89After a DNS request for =your_github_username.github.io= is resolved
90into an IP address, e.g. =185.199.108.153= your browser sends an HTTP
91request to that server with an HTTP header =Host=. Below are =curl=
92examples that load the same website (these examples might not work if
93you are behind a proxy server):
94
95#+begin_src sh
96curl --header "Host: your_github_username.github.io" http://185.199.108.153/
97curl --header "Host: www.example.com" http://185.199.108.153/
98curl --header "Host: example.com" http://185.199.108.153/
99#+end_src
100
101This way GitHub Pages servers know which user website to serve.
102
103#+begin_quote
104GitHub Pages server will automatically redirect HTTP requests to the
105top-level domain if your =CNAME= file contains =example.com= but
106=www.example.com= is requested.
107
108The same is valid if your =CNAME= file contains =www.example.com= but
109the header =Host= in the =HTTP= request contains =example.com=.
110#+end_quote
111
112** Why can't I add a =CNAME= record entry that accepts a top-level request (=@=) to my DNS configuration?
113
114Quote from the GitHub Pages documentation:
115
116#+begin_quote
117Warning: Do not create a CNAME record for your custom apex domain! Doing
118so may cause issues with other services, such as email, on that domain.
119#+end_quote
120
121** References:
122
1231. [[https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site][Setting
124 up a custom domain with GitHub Pages]]
1252. [[https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages][My
126 custom domain isn't working]]
1273. [[https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address][Cannot
128 access my GitHub Pages website by IP Address]]
1294. [[https://stackoverflow.com/questions/23375422/how-do-i-set-up-github-pages-to-redirect-dns-requests-from-a-subdomain-e-g-www][How
130 do I set up GitHub Pages to redirect DNS requests from a subdomain
131 (e.g. www) to the top-level domain (TLD, Apex record)?]]
content/blog/2020-08-29-php-auth-flow.md → content/blog/2020-08-29-php-auth-flow.org renamed +74 −72
@@ -1,23 +1,22 @@
1+++ 1#+date: <2020-08-29>
2date = 2020-08-29 2#+title: PHP Authentication Flow
3title = "PHP Authentication Flow" 3#+description:
4description = "" 4
5draft = false 5
6+++ 6* Introduction
7 7
8# Introduction 8When creating websites that will allow users to create accounts, the
9 9developer always needs to consider the proper authentication flow for
10When creating websites that will allow users to create accounts, the developer 10their app. For example, some developers will utilize an API for
11always needs to consider the proper authentication flow for their app. For 11authentication, some will use OAuth, and some may just use their own
12example, some developers will utilize an API for authentication, some will use 12simple database.
13OAuth, and some may just use their own simple database. 13
14 14For those using pre-built libraries, authentication may simply be a
15For those using pre-built libraries, authentication may simply be a problem of 15problem of copying and pasting the code from their library's
16copying and pasting the code from their library's documentation. For example, 16documentation. For example, here's the code I use to authenticate users
17here's the code I use to authenticate users with the Tumblr OAuth API for my 17with the Tumblr OAuth API for my Tumblr client, Vox Populi:
18Tumblr client, Vox Populi: 18
19 19#+begin_src php
20```php
21// Start the session 20// Start the session
22session_start(); 21session_start();
23 22
@@ -38,27 +37,28 @@ $client = new Tumblr\API\Client(
38 $token, 37 $token,
39 $token_secret 38 $token_secret
40); 39);
41``` 40#+end_src
42 41
43However, developers creating authentication flows from scratch will need to 42However, developers creating authentication flows from scratch will need
44think carefully about when to make sure a web page will check the user's 43to think carefully about when to make sure a web page will check the
45authenticity. 44user's authenticity.
46 45
47In this article, we're going to look at a simple authentication flow using a 46In this article, we're going to look at a simple authentication flow
48MySQL database and PHP. 47using a MySQL database and PHP.
49 48
50# Creating User Accounts 49* Creating User Accounts
51 50
52The beginning to any type of user authentication is to create a user account. 51The beginning to any type of user authentication is to create a user
53This process can take many formats, but the simplest is to accept user input 52account. This process can take many formats, but the simplest is to
54from a form (e.g., username and password) and send it over to your database. For 53accept user input from a form (e.g., username and password) and send it
55example, here's a snippet that shows how to get username and password parameters 54over to your database. For example, here's a snippet that shows how to
56that would come when a user submits a form to your PHP script. 55get username and password parameters that would come when a user submits
56a form to your PHP script.
57 57
58**Note**: Ensure that your password column is large enough to hold the hashed 58*Note*: Ensure that your password column is large enough to hold the
59value (at least 60 characters or longer). 59hashed value (at least 60 characters or longer).
60 60
61```php 61#+begin_src php
62// Get the values from the URL 62// Get the values from the URL
63$username = $_POST['username']; 63$username = $_POST['username'];
64$raw_password = $_POST['password']; 64$raw_password = $_POST['password'];
@@ -91,15 +91,15 @@ if ($conn->query($sql) === TRUE) {
91} 91}
92 92
93$conn->close(); 93$conn->close();
94``` 94#+end_src
95 95
96## Validate Returning Users 96** Validate Returning Users
97 97
98To be able to verify that a returning user has a valid username and password in 98To be able to verify that a returning user has a valid username and
99your database is as simple as having users fill out a form and comparing their 99password in your database is as simple as having users fill out a form
100inputs to your database. 100and comparing their inputs to your database.
101 101
102```php 102#+begin_src php
103// Query the database for username and password 103// Query the database for username and password
104// ... 104// ...
105 105
@@ -110,41 +110,42 @@ if(password_verify($password_input, $hashed_password)) {
110 110
111// Else, Redirect them back to the login page. 111// Else, Redirect them back to the login page.
112... 112...
113``` 113#+end_src
114 114
115# Storing Authentication State 115* Storing Authentication State
116 116
117Once you've created the user's account, now you're ready to initialize the 117Once you've created the user's account, now you're ready to initialize
118user's session. **You will need to do this on every page you load while the user 118the user's session. *You will need to do this on every page you load
119is logged in.** To do so, simply enter the following code snippet: 119while the user is logged in.* To do so, simply enter the following code
120snippet:
120 121
121```php 122#+begin_src php
122session_start(); 123session_start();
123``` 124#+end_src
124 125
125Once you've initialized the session, the next step is to store the session in a 126Once you've initialized the session, the next step is to store the
126cookie so that you can access it later. 127session in a cookie so that you can access it later.
127 128
128```php 129#+begin_src php
129setcookie(session_name()); 130setcookie(session_name());
130``` 131#+end_src
131 132
132Now that the session name has been stored, you'll be able to check if there's an 133Now that the session name has been stored, you'll be able to check if
133active session whenever you load a page. 134there's an active session whenever you load a page.
134 135
135```php 136#+begin_src php
136if(isset(session_name())) { 137if(isset(session_name())) {
137 // The session is active 138 // The session is active
138} 139}
139``` 140#+end_src
140 141
141## Removing User Authentication 142** Removing User Authentication
142 143
143The next logical step is to give your users the option to log out once they are 144The next logical step is to give your users the option to log out once
144done using your application. This can be tricky in PHP since a few of the 145they are done using your application. This can be tricky in PHP since a
145standard ways do not always work. 146few of the standard ways do not always work.
146 147
147```php 148#+begin_src php
148// Initialize the session. 149// Initialize the session.
149// If you are using session_name("something"), don't forget it now! 150// If you are using session_name("something"), don't forget it now!
150session_start(); 151session_start();
@@ -176,17 +177,18 @@ session_write_close();
176// Go back to sign-in page 177// Go back to sign-in page
177header('Location: https://example.com/logged-out/'); 178header('Location: https://example.com/logged-out/');
178die(); 179die();
179``` 180#+end_src
180 181
181# Wrapping Up 182* Wrapping Up
182 183
183Now you should be ready to begin your authentication programming with PHP. You 184Now you should be ready to begin your authentication programming with
184can create user accounts, create sessions for users across different pages of 185PHP. You can create user accounts, create sessions for users across
185your site, and then destroy the user data when they're ready to leave. 186different pages of your site, and then destroy the user data when
187they're ready to leave.
186 188
187For more information on this subject, I recommend reading the [PHP 189For more information on this subject, I recommend reading the
188Documentation](https://www.php.net/). Specifically, you may want to look at 190[[https://www.php.net/][PHP Documentation]]. Specifically, you may want
189[HTTP Authentication with 191to look at [[https://www.php.net/manual/en/features.http-auth.php][HTTP
190PHP](https://www.php.net/manual/en/features.http-auth.php), [session 192Authentication with PHP]],
191handling](https://www.php.net/manual/en/book.session.php), and 193[[https://www.php.net/manual/en/book.session.php][session handling]],
192[hash](https://www.php.net/manual/en/function.hash.php). 194and [[https://www.php.net/manual/en/function.hash.php][hash]].
content/blog/2020-09-01-visual-recognition.md deleted −199
@@ -1,199 +0,0 @@
1+++
2date = 2020-09-01
3title = "IBM Watson Visual Recognition"
4description = ""
5draft = false
6+++
7
8# What is IBM Watson?
9
10If you've never heard of [Watson](https://www.ibm.com/watson), this service is a
11suite of enterprise-ready AI services, applications, and tooling provided by
12IBM. Watson contains quite a few useful tools for data scientists and students,
13including the subject of this post today: visual recognition.
14
15If you'd like to view the official documentation for the Visual Recognition API,
16visit the [API
17Docs](https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python).
18
19# Prerequisites
20
21To be able to use Watson Visual Recognition, you'll need the following:
22
231. Create a free account on [IBM Watson
24 Studio](https://www.ibm.com/cloud/watson-studio).
252. Add the [Watson Visual
26 Recognition](https://www.ibm.com/cloud/watson-visual-recognition) service to
27 your IBM Watson account.
283. Get your API key and URL. To do this, first go to the [profile
29 dashboard](https://dataplatform.cloud.ibm.com/home2?context=cpdaas) for your
30 IBM account and click on the Watson Visual Recognition service you created.
31 This will be listed in the section titled **Your services**. Then click the
32 **Credentials** tab and open the **Auto-generated credentials** dropdown.
33 Copy your API key and URL so that you can use them in the Python script
34 later.
354. **[Optional]** While not required, you can also create the Jupyter Notebook
36 for this project right inside [Watson
37 Studio](https://www.ibm.com/cloud/watson-studio). Watson Studio will save
38 your notebooks inside an organized project and allow you to use their other
39 integrated products, such as storage containers, AI models, documentation,
40 external sharing, etc.
41
42# Calling the IBM Watson Visual Recognition API
43
44Okay, now let's get started.
45
46To begin, we need to install the proper Python package for IBM Watson.
47
48```sh
49pip install --upgrade --user "ibm-watson>=4.5.0"
50```
51
52Next, we need to specify the API key, version, and URL given to us when we
53created the Watson Visual Recognition service.
54
55```python
56apikey = "<your-apikey>"
57version = "2018-03-19"
58url = "<your-url>"
59```
60
61Now, let's import the necessary libraries and authenticate our service.
62
63```python
64import json
65from ibm_watson import VisualRecognitionV3
66from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
67
68authenticator = IAMAuthenticator(apikey)
69visual_recognition = VisualRecognitionV3(
70 version=version,
71 authenticator=authenticator
72)
73
74visual_recognition.set_service_url(url)
75```
76
77**[Optional]** If you'd like to tell the API not to use any data to improve
78their products, set the following header.
79
80```python
81visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
82```
83
84Now we have our API all set and ready to go. For this example, I'm going to
85include a `dict` of photos to load as we test out the API.
86
87```python
88data = [
89 {
90 "title": "Grizzly Bear",
91 "url": "https://example.com/photos/image1.jpg"
92 },
93 {
94 "title": "Nature Lake",
95 "url": "https://example.com/photos/image2.jpg"
96 },
97 {
98 "title": "Welcome Sign",
99 "url": "https://example.com/photos/image3.jpg"
100 },
101 {
102 "title": "Honey Badger",
103 "url": "https://example.com/photos/image4.jpg"
104 },
105 {
106 "title": "Grand Canyon Lizard",
107 "url": "https://example.com/photos/image5.jpg"
108 },
109 {
110 "title": "Castle",
111 "url": "https://example.com/photos/image6.jpg"
112 }
113]
114```
115
116Now that we've set up our libraries and have the photos ready, let's create a
117loop to call the API for each image. The code below shows a loop that calls the
118URL of each image and sends it to the API, requesting results with at least 60%
119confidence. The results are output to the console with dotted lines separating
120each section.
121
122In the case of an API error, the codes and explanations are output to the
123console.
124
125```python
126from ibm_watson import ApiException
127
128for x in range(len(data)):
129try:
130 url = data[x]["url"]
131 images_filename = data[x]["title"]
132 classes = visual_recognition.classify(
133 url=url,
134 images_filename=images_filename,
135 threshold='0.6',
136 owners=["IBM"]).get_result()
137 print("-----------------------------------------------")
138 print("Image Title: ", data[x]["title"], "\n")
139 print("Image URL: ", data[x]["url"], "\n")
140 classification_results = classes["images"][0]["classifiers"][0]["classes"]
141 for result in classification_results:
142 print(result["class"], "(", result["score"], ")")
143 print("-----------------------------------------------")
144except ApiException as ex:
145 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
146```
147
148# The Results
149
150Here we can see the full result set of our function above. If you view each of
151the URLs that we sent to the API, you'll be able to see that it was remarkably
152accurate. To be fair, these are clear high-resolution, clear photos shot with a
153professional camera. In reality, you will most likely be processing images that
154are lower quality and may have a lot of noise in the photo.
155
156However, we can clearly see the benefit of being able to call this API instead
157of attempting to write our own image recognition function. Each of the
158classifications returned was a fair description of the image.
159
160If you wanted to restrict the results to those that are at least 90% confident
161or greater, you would simply adjust the `threshold` in the
162`visual_recognition.classify()` function.
163
164When your program runs, it should show the output below for each photo you
165provide.
166
167```txt
168----------------------------------------------------------------
169Image Title: Grizzly Bear
170Image URL: https://example.com/photos/image1.jpg
171
172brown bear ( 0.944 )
173bear ( 1 )
174carnivore ( 1 )
175mammal ( 1 )
176animal ( 1 )
177Alaskan brown bear ( 0.759 )
178greenishness color ( 0.975 )
179----------------------------------------------------------------
180```
181
182# Discussion
183
184Now, this was a very minimal implementation of the API. We simply supplied some
185images and looked to see how accurate the results were. However, you could
186implement this type of API into many machine learning (ML) models.
187
188For example, you could be working for a company that scans their warehouses or
189inventory using drones. Would you want to pay employees to sit there and watch
190drone footage all day in order to identify or count things in the video?
191Probably not. Instead, you could use a classification system similar to this one
192in order to train your machine learning model to correctly identify items that
193the drones show through video. More specifically, you could have your machine
194learning model watch a drone fly over a field of sheep in order to count how
195many sheep are living in that field.
196
197There are many ways to implement machine learning functionality, but hopefully
198this post helped inspire some deeper thought about the tools that can help
199propel us further into the future of machine learning and AI.
content/blog/2020-09-01-visual-recognition.org added +202
@@ -0,0 +1,202 @@
1#+date: <2020-09-01>
2#+title: IBM Watson Visual Recognition
3#+description:
4
5
6* What is IBM Watson?
7
8If you've never heard of [[https://www.ibm.com/watson][Watson]], this
9service is a suite of enterprise-ready AI services, applications, and
10tooling provided by IBM. Watson contains quite a few useful tools for
11data scientists and students, including the subject of this post today:
12visual recognition.
13
14If you'd like to view the official documentation for the Visual
15Recognition API, visit the
16[[https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python][API
17Docs]].
18
19* Prerequisites
20
21To be able to use Watson Visual Recognition, you'll need the following:
22
231. Create a free account on
24 [[https://www.ibm.com/cloud/watson-studio][IBM Watson Studio]].
252. Add the [[https://www.ibm.com/cloud/watson-visual-recognition][Watson
26 Visual Recognition]] service to your IBM Watson account.
273. Get your API key and URL. To do this, first go to the
28 [[https://dataplatform.cloud.ibm.com/home2?context=cpdaas][profile
29 dashboard]] for your IBM account and click on the Watson Visual
30 Recognition service you created. This will be listed in the section
31 titled *Your services*. Then click the *Credentials* tab and open the
32 *Auto-generated credentials* dropdown. Copy your API key and URL so
33 that you can use them in the Python script later.
344. *[Optional]* While not required, you can also create the Jupyter
35 Notebook for this project right inside
36 [[https://www.ibm.com/cloud/watson-studio][Watson Studio]]. Watson
37 Studio will save your notebooks inside an organized project and allow
38 you to use their other integrated products, such as storage
39 containers, AI models, documentation, external sharing, etc.
40
41* Calling the IBM Watson Visual Recognition API
42
43Okay, now let's get started.
44
45To begin, we need to install the proper Python package for IBM Watson.
46
47#+begin_src sh
48pip install --upgrade --user "ibm-watson>=4.5.0"
49#+end_src
50
51Next, we need to specify the API key, version, and URL given to us when
52we created the Watson Visual Recognition service.
53
54#+begin_src python
55apikey = "<your-apikey>"
56version = "2018-03-19"
57url = "<your-url>"
58#+end_src
59
60Now, let's import the necessary libraries and authenticate our service.
61
62#+begin_src python
63import json
64from ibm_watson import VisualRecognitionV3
65from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
66
67authenticator = IAMAuthenticator(apikey)
68visual_recognition = VisualRecognitionV3(
69 version=version,
70 authenticator=authenticator
71)
72
73visual_recognition.set_service_url(url)
74#+end_src
75
76*[Optional]* If you'd like to tell the API not to use any data to
77improve their products, set the following header.
78
79#+begin_src python
80visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
81#+end_src
82
83Now we have our API all set and ready to go. For this example, I'm going
84to include a =dict= of photos to load as we test out the API.
85
86#+begin_src python
87data = [
88 {
89 "title": "Grizzly Bear",
90 "url": "https://example.com/photos/image1.jpg"
91 },
92 {
93 "title": "Nature Lake",
94 "url": "https://example.com/photos/image2.jpg"
95 },
96 {
97 "title": "Welcome Sign",
98 "url": "https://example.com/photos/image3.jpg"
99 },
100 {
101 "title": "Honey Badger",
102 "url": "https://example.com/photos/image4.jpg"
103 },
104 {
105 "title": "Grand Canyon Lizard",
106 "url": "https://example.com/photos/image5.jpg"
107 },
108 {
109 "title": "Castle",
110 "url": "https://example.com/photos/image6.jpg"
111 }
112]
113#+end_src
114
115Now that we've set up our libraries and have the photos ready, let's
116create a loop to call the API for each image. The code below shows a
117loop that calls the URL of each image and sends it to the API,
118requesting results with at least 60% confidence. The results are output
119to the console with dotted lines separating each section.
120
121In the case of an API error, the codes and explanations are output to
122the console.
123
124#+begin_src python
125from ibm_watson import ApiException
126
127for x in range(len(data)):
128try:
129 url = data[x]["url"]
130 images_filename = data[x]["title"]
131 classes = visual_recognition.classify(
132 url=url,
133 images_filename=images_filename,
134 threshold='0.6',
135 owners=["IBM"]).get_result()
136 print("-----------------------------------------------")
137 print("Image Title: ", data[x]["title"], "\n")
138 print("Image URL: ", data[x]["url"], "\n")
139 classification_results = classes["images"][0]["classifiers"][0]["classes"]
140 for result in classification_results:
141 print(result["class"], "(", result["score"], ")")
142 print("-----------------------------------------------")
143except ApiException as ex:
144 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
145#+end_src
146
147* The Results
148
149Here we can see the full result set of our function above. If you view
150each of the URLs that we sent to the API, you'll be able to see that it
151was remarkably accurate. To be fair, these are clear high-resolution,
152clear photos shot with a professional camera. In reality, you will most
153likely be processing images that are lower quality and may have a lot of
154noise in the photo.
155
156However, we can clearly see the benefit of being able to call this API
157instead of attempting to write our own image recognition function. Each
158of the classifications returned was a fair description of the image.
159
160If you wanted to restrict the results to those that are at least 90%
161confident or greater, you would simply adjust the =threshold= in the
162=visual_recognition.classify()= function.
163
164When your program runs, it should show the output below for each photo
165you provide.
166
167#+begin_src txt
168----------------------------------------------------------------
169Image Title: Grizzly Bear
170Image URL: https://example.com/photos/image1.jpg
171
172brown bear ( 0.944 )
173bear ( 1 )
174carnivore ( 1 )
175mammal ( 1 )
176animal ( 1 )
177Alaskan brown bear ( 0.759 )
178greenishness color ( 0.975 )
179----------------------------------------------------------------
180#+end_src
181
182* Discussion
183
184Now, this was a very minimal implementation of the API. We simply
185supplied some images and looked to see how accurate the results were.
186However, you could implement this type of API into many machine learning
187(ML) models.
188
189For example, you could be working for a company that scans their
190warehouses or inventory using drones. Would you want to pay employees to
191sit there and watch drone footage all day in order to identify or count
192things in the video? Probably not. Instead, you could use a
193classification system similar to this one in order to train your machine
194learning model to correctly identify items that the drones show through
195video. More specifically, you could have your machine learning model
196watch a drone fly over a field of sheep in order to count how many sheep
197are living in that field.
198
199There are many ways to implement machine learning functionality, but
200hopefully this post helped inspire some deeper thought about the tools
201that can help propel us further into the future of machine learning and
202AI.
content/blog/2020-09-22-internal-audit.md deleted −233
@@ -1,233 +0,0 @@
1+++
2date = 2020-09-22
3title = "Who is Internal Audit?"
4description = ""
5draft = false
6+++
7
8# Definitions
9
10One of the many reasons that Internal Audit needs such thorough explaining to
11non-auditors is that Internal Audit can serve many purposes, depending on the
12organization's size and needs. However, the Institute of Internal Auditors (IIA)
13defines Internal Auditing as:
14
15> Internal auditing is an independent, objective assurance and consulting
16> activity designed to add value and improve an organization's operations. It
17> helps an organization accomplish its objectives by bringing a systematic,
18> disciplined approach to evaluate and improve the effectiveness of risk
19> management, control, and governance processes.
20
21However, this definition uses quite a few terms that aren't clear unless the
22reader already has a solid understanding of the auditing profession. To further
23explain, the following is a list of definitions that can help supplement
24understanding of internal auditing.
25
26## Independent
27
28Independence is the freedom from conditions that threaten the ability of the
29internal audit activity to carry out internal audit responsibilities in an
30unbiased manner. To achieve the degree of independence necessary to effectively
31carry out the responsibilities of the internal audit activity, the chief audit
32executive has direct and unrestricted access to senior management and the board.
33This can be achieved through a dual-reporting relationship. Threats to
34independence must be managed at the individual auditor, engagement, functional,
35and organizational levels.
36
37## Objective
38
39Objectivity is an unbiased mental attitude that allows internal auditors to
40perform engagements in such a manner that they believe in their work product and
41that no quality compromises are made. Objectivity requires that internal
42auditors do not subordinate their judgment on audit matters to others. Threats
43to objectivity must be managed at the individual auditor, engagement,
44functional, and organizational levels.
45
46## Assurance
47
48Assurance services involve the internal auditor's objective assessment of
49evidence to provide opinions or conclusions regarding an entity, operation,
50function, process, system, or other subject matters. The internal auditor
51determines the nature and scope of an assurance engagement. Generally, three
52parties are participants in assurance services: (1) the person or group directly
53involved with the entity, operation, function, process, system, or other subject
54
55- (the process owner), (2) the person or group making the assessment - (the
56 internal auditor), and (3) the person or group using the assessment - (the
57 user).
58
59## Consulting
60
61Consulting services are advisory in nature and are generally performed at the
62specific request of an engagement client. The nature and scope of the consulting
63engagement are subject to agreement with the engagement client. Consulting
64services generally involve two parties: (1) the person or group offering the
65advice (the internal auditor), and (2) the person or group seeking and receiving
66the advice (the engagement client). When performing consulting services, the
67internal auditor should maintain objectivity and not assume management
68responsibility.
69
70## Governance, Risk Management, & Compliance (GRC)
71
72The integrated collection of capabilities that enable an organization to
73reliably achieve objectives, address uncertainty and act with integrity.
74
75# Audit Charter & Standards
76
77First, it's important to note that not every organization needs internal
78auditors. In fact, it's unwise for an organization to hire internal auditors
79unless they have regulatory requirements for auditing and have the capital to
80support the department. Internal audit is a cost center that can only affect
81revenue indirectly.
82
83Once an organization determines the need for internal assurance services, they
84will hire a Chief Audit Executive and create the audit charter. This charter is
85a document, approved by the company's governing body, that will define internal
86audit's purpose, authority, responsibility, and position within the
87organization. Fortunately, the IIA has model charters available to IIA members
88for those developing or improving their charter.
89
90Beyond the charter and organizational documents, internal auditors follow a few
91different standards in order to perform their job. First is the International
92Professional Practices Framework (IPPF) by the IIA, which is the model of
93standards for internal auditing. In addition, ISACA's Information Technology
94Assurance Framework (ITAF) helps guide auditors in reference to information
95technology (IT) compliance and assurance. Finally, additional standards such as
96FASB, GAAP, and industry-specific standards are used when performing internal
97audit work.
98
99# Three Lines of Defense
100
101[The IIA](https://theiia.org) released the original Three Lines of Defense model
102in 2013, but have released an updated version in 2020.
103
104I won't go into depth about the changes made to the model in this article.
105Instead, let's take a look at the most current model.
106
107The updated model forgets the strict idea of areas performing their own
108functions or line of defense. Instead of talking about management, risk, and
109internal audit as 1-2-3, the new model creates a more fluid and cooperative
110model.
111
112Looking at this model from an auditing perspective shows us that auditors will
113need to align, communicate, and collaborate with management, including business
114area managers and chief officers, as well as reporting to the governing body.
115The governing body will instruct internal audit _functionally_ on their goals
116and track their progress periodically.
117
118However, the internal audit department will report _administratively_ to a chief
119officer in the company for the purposes of collaboration, direction, and
120assistance with the business. Note that in most situations, the governing body
121is the audit committee on the company's board of directors.
122
123The result of this structure is that internal audit is an independent and
124objective function that can provide assurance over the topics they audit.
125
126# Audit Process
127
128A normal audit will generally follow the same process, regardless of the topic.
129However, certain special projects or abnormal business areas may call for
130changes to the audit process. The audit process is not set in stone, it's simply
131a set of best practices so that audits can be performed consistently.
132
133While different organizations may tweak the process, it will generally follow
134this flow:
135
136## 1. Risk Assessment
137
138The risk assessment part of the process has historically been performed
139annually, but many organizations have moved to performing this process much more
140frequently. In fact, some organizations are moving to an agile approach that can
141take new risks into the risk assessment and re-prioritize risk areas on-the-go.
142To perform a risk assessment, leaders in internal audit will research industry
143risks, consult with business leaders around the company, and perform analyses on
144company data.
145
146Once a risk assessment has been documented, the audit department has a
147prioritized list of risks that can be audited. This is usually in the form of
148auditable entities, such as business areas or departments.
149
150## 2. Planning
151
152During the planning phase of an audit, auditors will meet with the business area
153to discuss the various processes, controls, and risks applicable to the
154business. This helps the auditors determine the scope limits for the audit, as
155well as timing and subject-matter experts. Certain documents will be created in
156this phase that will be used to keep the audit on-track an in-scope as it goes
157forward.
158
159## 3. Testing
160
161The testing phase, also known as fieldwork or execution, is where internal
162auditors will take the information they've discovered and test it against
163regulations, industry standards, company rules, best practices, as well as
164validating that any processes are complete and accurate. For example, an audit
165of HR would most likely examine processes such as employee on-boarding, employee
166termination, security of personally identifiable information (PII), or the IT
167systems involved in these processes. Company standards would be examined and
168compared against how the processes are actually being performed day-to-day, as
169well as compared against regulations such as the Equal Employment Opportunity
170(EEO), American with Disabilities Act, and National Labor Relations Act.
171
172## 4. Reporting
173
174Once all the tests have been completed, the audit will enter the reporting
175phase. This is when the audit team will conclude on the evidence they've
176collected, interviews they've held, and any opinions they've formed on the
177controls in place. A summary of the audit findings, conclusions, and specific
178recommendations are officially communicated to the client through a draft
179report. Clients have the opportunity to respond to the report and submit an
180action plan and time frame. These responses become part of the final report
181which is distributed to the appropriate level of administration.
182
183## 5. Follow-Up
184
185After audits have been completed and management has formed action plans and time
186frames for audit issues, internal audit will follow up once that due date has
187arrived. In most cases, the follow-up will simply consist of a meeting to
188discuss how the action plan has been completed and to request documentation to
189prove it.
190
191# Audit Department Structure
192
193While an internal audit department is most often thought of as a team of
194full-time employees, there are actually many different ways in which a
195department can be structured. As the world becomes more digital and fast-paced,
196outsourcing has become a more attractive option for some organizations. Internal
197audit can be fully outsourced or partially outsourced, allowing for flexibility
198in cases where turnover is high.
199
200In addition, departments can implement a rotational model. This allows for
201interested employees around the organization to rotate into the internal audit
202department for a period of time, allowing them to obtain knowledge of risks and
203controls and allowing the internal audit team to obtain more business area
204knowledge. This program is popular in very large organizations, but
205organizations tend to rotate lower-level audit staff instead of managers. This
206helps prevent any significant knowledge loss as auditors rotate out to business
207areas.
208
209# Consulting
210
211Consulting is not an easy task at any organization, especially for a department
212that can have negative perceptions within the organization as the "compliance
213police." However, once an internal audit department has delivered value to
214organization, adding consulting to their suite of services is a smart move. In
215most cases, Internal Audit can insert themselves into a consulting role without
216affecting the process of project management at the company. This means that
217internal audit can add objective assurance and opinions to business areas as
218they develop new processes, instead of coming in periodically to audit an area
219and file issues that could have been fixed at the beginning.
220
221# Data Science & Data Analytics
222
223One major piece of the internal audit function in the modern world is data
224science. While the process is data science, most auditors will refer to anything
225in this realm as data analytics. Hot topics such as robotic process automation
226(RPA), machine learning (ML), and data mining have taken over the auditing world
227in recent years. These technologies have been immensely helpful with increasing
228the effectiveness and efficiency of auditors.
229
230For example, mundane and repetitive tasks can be automated in order for auditors
231to make more room in their schedules for labor-intensive work. Further, auditors
232will need to adapt technologies like machine learning in order to extract more
233value from the data they're using to form conclusions.
content/blog/2020-09-22-internal-audit.org added +248
@@ -0,0 +1,248 @@
1#+date: <2020-09-22>
2#+title: Who is Internal Audit?
3#+description:
4
5
6* Definitions
7
8One of the many reasons that Internal Audit needs such thorough
9explaining to non-auditors is that Internal Audit can serve many
10purposes, depending on the organization's size and needs. However, the
11Institute of Internal Auditors (IIA) defines Internal Auditing as:
12
13#+begin_quote
14Internal auditing is an independent, objective assurance and consulting
15activity designed to add value and improve an organization's operations.
16It helps an organization accomplish its objectives by bringing a
17systematic, disciplined approach to evaluate and improve the
18effectiveness of risk management, control, and governance processes.
19#+end_quote
20
21However, this definition uses quite a few terms that aren't clear unless
22the reader already has a solid understanding of the auditing profession.
23To further explain, the following is a list of definitions that can help
24supplement understanding of internal auditing.
25
26** Independent
27
28Independence is the freedom from conditions that threaten the ability of
29the internal audit activity to carry out internal audit responsibilities
30in an unbiased manner. To achieve the degree of independence necessary
31to effectively carry out the responsibilities of the internal audit
32activity, the chief audit executive has direct and unrestricted access
33to senior management and the board. This can be achieved through a
34dual-reporting relationship. Threats to independence must be managed at
35the individual auditor, engagement, functional, and organizational
36levels.
37
38** Objective
39
40Objectivity is an unbiased mental attitude that allows internal auditors
41to perform engagements in such a manner that they believe in their work
42product and that no quality compromises are made. Objectivity requires
43that internal auditors do not subordinate their judgment on audit
44matters to others. Threats to objectivity must be managed at the
45individual auditor, engagement, functional, and organizational levels.
46
47** Assurance
48
49Assurance services involve the internal auditor's objective assessment
50of evidence to provide opinions or conclusions regarding an entity,
51operation, function, process, system, or other subject matters. The
52internal auditor determines the nature and scope of an assurance
53engagement. Generally, three parties are participants in assurance
54services: (1) the person or group directly involved with the entity,
55operation, function, process, system, or other subject
56
57- (the process owner), (2) the person or group making the assessment -
58 (the internal auditor), and (3) the person or group using the
59 assessment - (the user).
60
61** Consulting
62
63Consulting services are advisory in nature and are generally performed
64at the specific request of an engagement client. The nature and scope of
65the consulting engagement are subject to agreement with the engagement
66client. Consulting services generally involve two parties: (1) the
67person or group offering the advice (the internal auditor), and (2) the
68person or group seeking and receiving the advice (the engagement
69client). When performing consulting services, the internal auditor
70should maintain objectivity and not assume management responsibility.
71
72** Governance, Risk Management, & Compliance (GRC)
73
74The integrated collection of capabilities that enable an organization to
75reliably achieve objectives, address uncertainty and act with integrity.
76
77* Audit Charter & Standards
78
79First, it's important to note that not every organization needs internal
80auditors. In fact, it's unwise for an organization to hire internal
81auditors unless they have regulatory requirements for auditing and have
82the capital to support the department. Internal audit is a cost center
83that can only affect revenue indirectly.
84
85Once an organization determines the need for internal assurance
86services, they will hire a Chief Audit Executive and create the audit
87charter. This charter is a document, approved by the company's governing
88body, that will define internal audit's purpose, authority,
89responsibility, and position within the organization. Fortunately, the
90IIA has model charters available to IIA members for those developing or
91improving their charter.
92
93Beyond the charter and organizational documents, internal auditors
94follow a few different standards in order to perform their job. First is
95the International Professional Practices Framework (IPPF) by the IIA,
96which is the model of standards for internal auditing. In addition,
97ISACA's Information Technology Assurance Framework (ITAF) helps guide
98auditors in reference to information technology (IT) compliance and
99assurance. Finally, additional standards such as FASB, GAAP, and
100industry-specific standards are used when performing internal audit
101work.
102
103* Three Lines of Defense
104
105[[https://theiia.org][The IIA]] released the original Three Lines of
106Defense model in 2013, but have released an updated version in 2020.
107
108I won't go into depth about the changes made to the model in this
109article. Instead, let's take a look at the most current model.
110
111The updated model forgets the strict idea of areas performing their own
112functions or line of defense. Instead of talking about management, risk,
113and internal audit as 1-2-3, the new model creates a more fluid and
114cooperative model.
115
116Looking at this model from an auditing perspective shows us that
117auditors will need to align, communicate, and collaborate with
118management, including business area managers and chief officers, as well
119as reporting to the governing body. The governing body will instruct
120internal audit /functionally/ on their goals and track their progress
121periodically.
122
123However, the internal audit department will report /administratively/ to
124a chief officer in the company for the purposes of collaboration,
125direction, and assistance with the business. Note that in most
126situations, the governing body is the audit committee on the company's
127board of directors.
128
129The result of this structure is that internal audit is an independent
130and objective function that can provide assurance over the topics they
131audit.
132
133* Audit Process
134
135A normal audit will generally follow the same process, regardless of the
136topic. However, certain special projects or abnormal business areas may
137call for changes to the audit process. The audit process is not set in
138stone, it's simply a set of best practices so that audits can be
139performed consistently.
140
141While different organizations may tweak the process, it will generally
142follow this flow:
143
144** 1. Risk Assessment
145
146The risk assessment part of the process has historically been performed
147annually, but many organizations have moved to performing this process
148much more frequently. In fact, some organizations are moving to an agile
149approach that can take new risks into the risk assessment and
150re-prioritize risk areas on-the-go. To perform a risk assessment,
151leaders in internal audit will research industry risks, consult with
152business leaders around the company, and perform analyses on company
153data.
154
155Once a risk assessment has been documented, the audit department has a
156prioritized list of risks that can be audited. This is usually in the
157form of auditable entities, such as business areas or departments.
158
159** 2. Planning
160
161During the planning phase of an audit, auditors will meet with the
162business area to discuss the various processes, controls, and risks
163applicable to the business. This helps the auditors determine the scope
164limits for the audit, as well as timing and subject-matter experts.
165Certain documents will be created in this phase that will be used to
166keep the audit on-track an in-scope as it goes forward.
167
168** 3. Testing
169
170The testing phase, also known as fieldwork or execution, is where
171internal auditors will take the information they've discovered and test
172it against regulations, industry standards, company rules, best
173practices, as well as validating that any processes are complete and
174accurate. For example, an audit of HR would most likely examine
175processes such as employee on-boarding, employee termination, security
176of personally identifiable information (PII), or the IT systems involved
177in these processes. Company standards would be examined and compared
178against how the processes are actually being performed day-to-day, as
179well as compared against regulations such as the Equal Employment
180Opportunity (EEO), American with Disabilities Act, and National Labor
181Relations Act.
182
183** 4. Reporting
184
185Once all the tests have been completed, the audit will enter the
186reporting phase. This is when the audit team will conclude on the
187evidence they've collected, interviews they've held, and any opinions
188they've formed on the controls in place. A summary of the audit
189findings, conclusions, and specific recommendations are officially
190communicated to the client through a draft report. Clients have the
191opportunity to respond to the report and submit an action plan and time
192frame. These responses become part of the final report which is
193distributed to the appropriate level of administration.
194
195** 5. Follow-Up
196
197After audits have been completed and management has formed action plans
198and time frames for audit issues, internal audit will follow up once
199that due date has arrived. In most cases, the follow-up will simply
200consist of a meeting to discuss how the action plan has been completed
201and to request documentation to prove it.
202
203* Audit Department Structure
204
205While an internal audit department is most often thought of as a team of
206full-time employees, there are actually many different ways in which a
207department can be structured. As the world becomes more digital and
208fast-paced, outsourcing has become a more attractive option for some
209organizations. Internal audit can be fully outsourced or partially
210outsourced, allowing for flexibility in cases where turnover is high.
211
212In addition, departments can implement a rotational model. This allows
213for interested employees around the organization to rotate into the
214internal audit department for a period of time, allowing them to obtain
215knowledge of risks and controls and allowing the internal audit team to
216obtain more business area knowledge. This program is popular in very
217large organizations, but organizations tend to rotate lower-level audit
218staff instead of managers. This helps prevent any significant knowledge
219loss as auditors rotate out to business areas.
220
221* Consulting
222
223Consulting is not an easy task at any organization, especially for a
224department that can have negative perceptions within the organization as
225the "compliance police." However, once an internal audit department has
226delivered value to organization, adding consulting to their suite of
227services is a smart move. In most cases, Internal Audit can insert
228themselves into a consulting role without affecting the process of
229project management at the company. This means that internal audit can
230add objective assurance and opinions to business areas as they develop
231new processes, instead of coming in periodically to audit an area and
232file issues that could have been fixed at the beginning.
233
234* Data Science & Data Analytics
235
236One major piece of the internal audit function in the modern world is
237data science. While the process is data science, most auditors will
238refer to anything in this realm as data analytics. Hot topics such as
239robotic process automation (RPA), machine learning (ML), and data mining
240have taken over the auditing world in recent years. These technologies
241have been immensely helpful with increasing the effectiveness and
242efficiency of auditors.
243
244For example, mundane and repetitive tasks can be automated in order for
245auditors to make more room in their schedules for labor-intensive work.
246Further, auditors will need to adapt technologies like machine learning
247in order to extract more value from the data they're using to form
248conclusions.
content/blog/2020-09-25-happiness-map.md → content/blog/2020-09-25-happiness-map.org renamed +72 −71
@@ -1,88 +1,87 @@
1+++ 1#+date: <2020-09-25>
2date = 2020-09-25 2#+title: Data Visualization: World Choropleth Map of Happiness
3title = "Data Visualization: World Choropleth Map of Happiness" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Background Information 5
6* Background Information
9 7
10The dataset (obtained from 8The dataset (obtained from
11[Kaggle](https://www.kaggle.com/unsdsn/world-happiness)) used in this article 9[[https://www.kaggle.com/unsdsn/world-happiness][Kaggle]]) used in this
12contains a list of countries around the world, their happiness rankings and 10article contains a list of countries around the world, their happiness
13scores, as well as other national scoring measures. 11rankings and scores, as well as other national scoring measures.
14 12
15Fields include: 13Fields include:
16 14
17- Overall rank 15- Overall rank
18- Country or region 16- Country or region
19- GDP per capita 17- GDP per capita
20- Social support 18- Social support
21- Healthy life expectancy 19- Healthy life expectancy
22- Freedom to make life choices 20- Freedom to make life choices
23- Generosity 21- Generosity
24- Perceptions of corruption 22- Perceptions of corruption
25 23
26There are 156 records. Since there are ~195 countries in the world, we can see 24There are 156 records. Since there are ~195 countries in the world, we
27that around 40 countries will be missing from this dataset. 25can see that around 40 countries will be missing from this dataset.
28 26
29# Install Packages 27* Install Packages
30 28
31As always, run the `install` command for all packages needed to perform 29As always, run the =install= command for all packages needed to perform
32analysis. 30analysis.
33 31
34```python 32#+begin_src python
35!pip install folium geopandas matplotlib numpy pandas 33!pip install folium geopandas matplotlib numpy pandas
36``` 34#+end_src
37 35
38# Import the Data 36* Import the Data
39 37
40We only need a couple packages to create a choropleth map. We will use 38We only need a couple packages to create a choropleth map. We will use
41[Folium](https://python-visualization.github.io/folium/), which provides map 39[[https://python-visualization.github.io/folium/][Folium]], which
42visualizations in Python. We will also use geopandas and pandas to wrangle our 40provides map visualizations in Python. We will also use geopandas and
43data before we put it on a map. 41pandas to wrangle our data before we put it on a map.
44 42
45```python 43#+begin_src python
46# Import the necessary Python packages 44# Import the necessary Python packages
47import folium 45import folium
48import geopandas as gpd 46import geopandas as gpd
49import pandas as pd 47import pandas as pd
50``` 48#+end_src
51 49
52To get anything to show up on a map, we need a file that will specify the 50To get anything to show up on a map, we need a file that will specify
53boundaries of each country. Luckily, GeoJSON files exist (for free!) on the 51the boundaries of each country. Luckily, GeoJSON files exist (for free!)
54internet. To get the boundaries of every country in the world, we will use the 52on the internet. To get the boundaries of every country in the world, we
55GeoJSON link shown below. 53will use the GeoJSON link shown below.
56 54
57GeoPandas will take this data and load it into a dataframe so that we can easily 55GeoPandas will take this data and load it into a dataframe so that we
58match it to the data we're trying to analyze. Let's look at the GeoJSON 56can easily match it to the data we're trying to analyze. Let's look at
59dataframe: 57the GeoJSON dataframe:
60 58
61```python 59#+begin_src python
62# Load the GeoJSON data with geopandas 60# Load the GeoJSON data with geopandas
63geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson') 61geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson')
64geo_data.head() 62geo_data.head()
65``` 63#+end_src
66 64
67Next, let's load the data from the Kaggle dataset. I've downloaded this file, so 65Next, let's load the data from the Kaggle dataset. I've downloaded this
68update the file path if you have it somewhere else. After loading, let's take a 66file, so update the file path if you have it somewhere else. After
69look at this dataframe: 67loading, let's take a look at this dataframe:
70 68
71```python 69#+begin_src python
72# Load the world happiness data with pandas 70# Load the world happiness data with pandas
73happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv') 71happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv')
74happy_data.head() 72happy_data.head()
75``` 73#+end_src
76 74
77# Clean the Data 75* Clean the Data
78 76
79Some countries need to be renamed, or they will be lost when you merge the 77Some countries need to be renamed, or they will be lost when you merge
80happiness and GeoJSON dataframes. This is something I discovered when the map 78the happiness and GeoJSON dataframes. This is something I discovered
81below showed empty countries. I searched both data frames for the missing 79when the map below showed empty countries. I searched both data frames
82countries to see the naming differences. Any countries that do not have records 80for the missing countries to see the naming differences. Any countries
83in the `happy_data` df will not show up on the map. 81that do not have records in the =happy_data= df will not show up on the
82map.
84 83
85```python 84#+begin_src python
86# Rename some countries to match our GeoJSON data 85# Rename some countries to match our GeoJSON data
87 86
88# Rename USA 87# Rename USA
@@ -100,34 +99,35 @@ happy_data.at[republic_congo_index, 'Country or region'] = 'Republic of Congo'
100# Rename the DRC 99# Rename the DRC
101democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)'] 100democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)']
102happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo' 101happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo'
103``` 102#+end_src
104 103
105# Merge the Data 104* Merge the Data
106 105
107Now that we have clean data, we need to merge the GeoJSON data with the 106Now that we have clean data, we need to merge the GeoJSON data with the
108happiness data. Since we've stored them both in dataframes, we just need to call 107happiness data. Since we've stored them both in dataframes, we just need
109the `.merge()` function. 108to call the =.merge()= function.
110 109
111We will also rename a couple columns, just so that they're a little easier to 110We will also rename a couple columns, just so that they're a little
112use when we create the map. 111easier to use when we create the map.
113 112
114```python 113#+begin_src python
115# Merge the two previous dataframes into a single geopandas dataframe 114# Merge the two previous dataframes into a single geopandas dataframe
116merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region') 115merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region')
117 116
118# Rename columns for ease of use 117# Rename columns for ease of use
119merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'}) 118merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'})
120merged_df = merged_df.rename(columns = {'Country or region':'Country'}) 119merged_df = merged_df.rename(columns = {'Country or region':'Country'})
121``` 120#+end_src
122 121
123# Create the Map 122* Create the Map
124 123
125The data is finally ready to be added to a map. The code below shows the 124The data is finally ready to be added to a map. The code below shows the
126simplest way to find the center of the map and create a Folium map object. The 125simplest way to find the center of the map and create a Folium map
127important part is to remember to reference the merged dataframe for our GeoJSON 126object. The important part is to remember to reference the merged
128data and value data. The columns specify which geo data and value data to use. 127dataframe for our GeoJSON data and value data. The columns specify which
128geo data and value data to use.
129 129
130```python 130#+begin_src python
131# Assign centroids to map 131# Assign centroids to map
132x_map = merged_df.centroid.x.mean() 132x_map = merged_df.centroid.x.mean()
133y_map = merged_df.centroid.y.mean() 133y_map = merged_df.centroid.y.mean()
@@ -151,17 +151,18 @@ folium.Choropleth(
151 smooth_factor=0, 151 smooth_factor=0,
152 highlight=True 152 highlight=True
153).add_to(world_map) 153).add_to(world_map)
154``` 154#+end_src
155 155
156Let's look at the resulting map. 156Let's look at the resulting map.
157 157
158# Create a Tooltip on Hover 158* Create a Tooltip on Hover
159 159
160Now that we have a map set up, we could stop. However, I want to add a tooltip 160Now that we have a map set up, we could stop. However, I want to add a
161so that I can see more information about each country. The `tooltip_data` code 161tooltip so that I can see more information about each country. The
162below will show a popup on hover with all the data fields shown. 162=tooltip_data= code below will show a popup on hover with all the data
163fields shown.
163 164
164```python 165#+begin_src python
165 # Adding labels to map 166 # Adding labels to map
166 style_function = lambda x: {'fillColor': '#ffffff', 167 style_function = lambda x: {'fillColor': '#ffffff',
167 'color':'#000000', 168 'color':'#000000',
@@ -202,6 +203,6 @@ folium.LayerControl().add_to(world_map)
202 203
203# Display the map 204# Display the map
204world_map 205world_map
205``` 206#+end_src
206 207
207The tooltip will now appear whenever you hover over a country. 208The tooltip will now appear whenever you hover over a country.
content/blog/2020-10-12-mediocrity.md deleted −116
@@ -1,116 +0,0 @@
1+++
2date = 2020-10-12
3title = "On the Pursuit of Mediocrity"
4description = ""
5draft = false
6+++
7
8# Perfect is the Enemy of Good
9
10As the saying goes, "the best is the enemy of the good." As we strive for
11perfection, we often fail to realize the implications of such an undertaking.
12Attempting to reach perfection is often unrealistic. Even worse, it can get in
13the way of achieving a good outcome. In certain situations, we try so hard to
14achieve the ideal solution that we have burned the bridges that would have
15allowed us to reach a lesser yet still superb solution.
16
17Philosophers throughout history have inspected this plight from many viewpoints.
18Greek mythology speaks of the [golden
19mean](<https://en.wikipedia.org/wiki/Golden_mean_(philosophy)>), which uses the
20story of Icarus to illustrate that sometimes "the middle course" is the best
21solution. In this story, Daedalus, a famous artist of his time, built feathered
22wings for himself and his son so that they might escape the clutches of King
23Minos. Daedalus warns his beloved son whom he loved so much to "fly the middle
24course", between the sea spray and the sun's heat. Icarus did not heed his
25father; he flew up and up until the sun melted the wax off his wings. For not
26heeding the middle course, he fell into the sea and drowned.
27
28More recently, management scholars have explored the [Pareto
29principle](https://en.wikipedia.org/wiki/Pareto_principle) and found that as we
30increase the frequency of something, or strive to perform actions to achieve
31some form of perfection, we run into [diminishing
32returns](https://en.wikipedia.org/wiki/Diminishing_returns).
33
34Even further, Harold Demsetz is noted as coining the term [the Nirvana
35fallacy](https://en.wikipedia.org/wiki/Nirvana_fallacy) in 1969, which shows the
36fallacy of comparing actual things with unrealistic, idealized alternatives.
37This is another trap that we may fall into, where we are constantly thinking of
38the ultimate solutions to problems, when something more realistic needs to be
39considered.
40
41Over and over throughout history, we've found that perfection is often
42unrealistic and unachievable. However, we push ourselves and our peers to "give
43100%" or "go the extra mile," while it may be that the better course is to give
44a valuable level of effort while considering the effects of further effort on
45the outcome. Working harder does not always help us achieve loftier goals.
46
47This has presented itself to me most recently during my time studying at my
48university. I was anxious and feeling the stresses of my courses, career, and
49personal life for quite a while, which was greatly affecting how well I was
50doing at school and my level of effort at work. One day, I happened to be
51talking to my father when he said something simple that hit home:
52
53> All you can do is show up and do your best. Worrying about the outcomes won't
54> affect the outcome itself.
55
56The thought was extremely straightforward and uncomplicated, yet it was
57something that I had lost sight of during my stress-filled years at school. Ever
58since then, I've found myself pausing and remembering that quote every time I
59get anxious or stressed. It helps to stop and think "Can I do anything to affect
60the outcome, or am I simply worrying over something I can't change?"
61
62# When Mediocrity Isn't Enough
63
64One problem with the philosophies presented in this post is that they are
65implemented far too often in situations where mediocrity simply isn't adequate.
66For example, let's take a look at digital user data, specifically
67personally-identifiable information (PII). As a cybersecurity auditor in the
68United States, I have found that most companies are concerned more with
69compliance than any actual safeguards over the privacy or protection of user
70data. Other than companies who have built their reputation on privacy and
71security, most companies will use
72[satisficing](https://en.wikipedia.org/wiki/Satisficing) as their primary
73decision-making strategy around user data.
74
75> Satisficing is a decision-making strategy or cognitive heuristic that entails
76> searching through the available alternatives until an acceptability threshold
77> is met.
78
79This means that each decision will be met with certain possible solutions until
80one of the solutions meets their minimum acceptable standards. For companies
81that deal with user data, the minimum-acceptable standards come from three
82areas:
83
841. Laws and regulations
852. Competitive pressure
863. Risk of monetary or reputation loss
87
88Working with project management or auditing, the primary concern here is the
89risk of legal ramifications. Since the primary risk comes from laws and
90regulations, companies will require that any project that involves user data
91must follow all the rules of those laws so that the company can protect itself
92from fines or other penalties.
93
94Following this, companies will consider best practices in order to place itself
95in a competitive position (e.g. Google vs. Apple) and review any recent or
96ongoing litigation against companies regarding user data. In a perfect company,
97management would then consider the ethical responsibilities of their
98organization and discuss their responsibilities over things like
99personally-identifiable information.
100
101However, as we mentioned above, most companies follow the idea of satisficing,
102which states that they have met the minimum acceptable standards and can now
103move on to other decisions. Modern business culture in the United States
104dictates that profits are the golden measure of how well a company or manager is
105performing, so we often don't think about our responsibilities beyond these
106basic standards.
107
108Not all situations demand excellence, but I believe that applying any philosophy
109as a broad stroke across one's life can be a mistake. We must be able to think
110critically about what we are doing as we do it and ask ourselves a few
111questions. Have I done everything I can in this situation? Is mediocrity an
112acceptable outcome, or should we strive for perfection, even if we can't attain
113it?
114
115Taking a few moments to think critically throughout our day, as we make
116decisions, can have a tremendous effect on the outcomes we create.
content/blog/2020-10-12-mediocrity.org added +124
@@ -0,0 +1,124 @@
1#+date: <2020-10-12>
2#+title: On the Pursuit of Mediocrity
3#+description:
4
5
6* Perfect is the Enemy of Good
7
8As the saying goes, "the best is the enemy of the good." As we strive
9for perfection, we often fail to realize the implications of such an
10undertaking. Attempting to reach perfection is often unrealistic. Even
11worse, it can get in the way of achieving a good outcome. In certain
12situations, we try so hard to achieve the ideal solution that we have
13burned the bridges that would have allowed us to reach a lesser yet
14still superb solution.
15
16Philosophers throughout history have inspected this plight from many
17viewpoints. Greek mythology speaks of the
18[[https://en.wikipedia.org/wiki/Golden_mean_(philosophy)][golden mean]],
19which uses the story of Icarus to illustrate that sometimes "the middle
20course" is the best solution. In this story, Daedalus, a famous artist
21of his time, built feathered wings for himself and his son so that they
22might escape the clutches of King Minos. Daedalus warns his beloved son
23whom he loved so much to "fly the middle course", between the sea spray
24and the sun's heat. Icarus did not heed his father; he flew up and up
25until the sun melted the wax off his wings. For not heeding the middle
26course, he fell into the sea and drowned.
27
28More recently, management scholars have explored the
29[[https://en.wikipedia.org/wiki/Pareto_principle][Pareto principle]] and
30found that as we increase the frequency of something, or strive to
31perform actions to achieve some form of perfection, we run into
32[[https://en.wikipedia.org/wiki/Diminishing_returns][diminishing
33returns]].
34
35Even further, Harold Demsetz is noted as coining the term
36[[https://en.wikipedia.org/wiki/Nirvana_fallacy][the Nirvana fallacy]]
37in 1969, which shows the fallacy of comparing actual things with
38unrealistic, idealized alternatives. This is another trap that we may
39fall into, where we are constantly thinking of the ultimate solutions to
40problems, when something more realistic needs to be considered.
41
42Over and over throughout history, we've found that perfection is often
43unrealistic and unachievable. However, we push ourselves and our peers
44to "give 100%" or "go the extra mile," while it may be that the better
45course is to give a valuable level of effort while considering the
46effects of further effort on the outcome. Working harder does not always
47help us achieve loftier goals.
48
49This has presented itself to me most recently during my time studying at
50my university. I was anxious and feeling the stresses of my courses,
51career, and personal life for quite a while, which was greatly affecting
52how 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
54hit home:
55
56#+begin_quote
57All you can do is show up and do your best. Worrying about the outcomes
58won't affect the outcome itself.
59#+end_quote
60
61The thought was extremely straightforward and uncomplicated, yet it was
62something that I had lost sight of during my stress-filled years at
63school. Ever since then, I've found myself pausing and remembering that
64quote every time I get anxious or stressed. It helps to stop and think
65"Can I do anything to affect the outcome, or am I simply worrying over
66something I can't change?"
67
68* When Mediocrity Isn't Enough
69
70One problem with the philosophies presented in this post is that they
71are implemented far too often in situations where mediocrity simply
72isn't adequate. For example, let's take a look at digital user data,
73specifically personally-identifiable information (PII). As a
74cybersecurity auditor in the United States, I have found that most
75companies are concerned more with compliance than any actual safeguards
76over the privacy or protection of user data. Other than companies who
77have built their reputation on privacy and security, most companies will
78use [[https://en.wikipedia.org/wiki/Satisficing][satisficing]] as their
79primary decision-making strategy around user data.
80
81#+begin_quote
82Satisficing is a decision-making strategy or cognitive heuristic that
83entails searching through the available alternatives until an
84acceptability threshold is met.
85#+end_quote
86
87This means that each decision will be met with certain possible
88solutions until one of the solutions meets their minimum acceptable
89standards. For companies that deal with user data, the
90minimum-acceptable standards come from three areas:
91
921. Laws and regulations
932. Competitive pressure
943. Risk of monetary or reputation loss
95
96Working with project management or auditing, the primary concern here is
97the risk of legal ramifications. Since the primary risk comes from laws
98and regulations, companies will require that any project that involves
99user data must follow all the rules of those laws so that the company
100can protect itself from fines or other penalties.
101
102Following this, companies will consider best practices in order to place
103itself in a competitive position (e.g. Google vs. Apple) and review any
104recent or ongoing litigation against companies regarding user data. In a
105perfect company, management would then consider the ethical
106responsibilities of their organization and discuss their
107responsibilities over things like personally-identifiable information.
108
109However, as we mentioned above, most companies follow the idea of
110satisficing, which states that they have met the minimum acceptable
111standards and can now move on to other decisions. Modern business
112culture in the United States dictates that profits are the golden
113measure of how well a company or manager is performing, so we often
114don't think about our responsibilities beyond these basic standards.
115
116Not all situations demand excellence, but I believe that applying any
117philosophy as a broad stroke across one's life can be a mistake. We must
118be able to think critically about what we are doing as we do it and ask
119ourselves a few questions. Have I done everything I can in this
120situation? Is mediocrity an acceptable outcome, or should we strive for
121perfection, even if we can't attain it?
122
123Taking a few moments to think critically throughout our day, as we make
124decisions, can have a tremendous effect on the outcomes we create.
content/blog/2020-12-27-website-redesign.md deleted −94
@@ -1,94 +0,0 @@
1+++
2date = 2020-12-27
3title = "Redesigning My Website: The 5 KB Result"
4description = ""
5draft = false
6+++
7
8# A Brief History
9
10As a form of continuous learning and entertainment, I've been running a handful
11of websites since 2016 when I took my first programming courses in college. I
12maintain one main website, the place I consider the official website to
13represent me. Under this site, I have a handful of subdirectories and
14subdomains.
15
16One of the parts I've enjoyed the most about web development is the aspect of
17designing an identity for a web page and working to find exciting ways to
18display the site's content. Inevitably, this means I've changed the designs for
19my websites more times than I could possibly count. Since I don't really host
20anything on my main webpage that's vital, it allows me the freedom to change
21things as inspiration strikes.
22
23Historically, I've relied on core utilities for spacing, components, and layouts
24from [Bootstrap](https://getbootstrap.com) and added custom CSS for fonts,
25accents, colors, and other items. I also tend to create sites with no border
26radius on items, visible borders, and content that takes up the entire screen
27(using whitespace inside components instead of whitespace around my components).
28
29# The Redesign Process
30
31About a week ago, I found myself wishing for a new design yet again. The prior
32design was largely inspired by IBM's [Carbon Design
33System](https://www.carbondesignsystem.com) and relied on jQuery, Bootstrap,
34along with some compressed [.webp](https://en.wikipedia.org/wiki/WebP) images.
35
36To anyone who knows my preferences toward web design - and even in my personal
37life - it should be no surprise that I immediately started looking for
38inspiration on minimalism. While there are some decent minimalistic designs on
39sites like
40[Dribbble](https://dribbble.com/search/shots/popular/web-design?q=minimalism),
41people seem to mostly discuss [brutalist web
42design](https://brutalist-web.design) when you ask about minimalism. While
43brutalist web design doesn't have to be minimal, it often is.
44
45I suppose, in a way, I did create a brutalist website since my HTML is semantic
46and accessible, hyperlinks are colored and underlined, and all native browser
47functions like scrolling and the back button work as expected. However, I didn't
48think about brutalism while designing these sites.
49
50The new design followed a simple design process. I walked through the screens on
51my blog and asked myself: "Is this element necessary for a user?" This allowed
52me to first start by removing all javascript, which had the sole purpose of
53allowing users to open a collapsed navbar on mobile. Replacing the collapsible
54navbar allowed me to remove both jQuery and Bootstrap's javascript.
55
56Next, I removed things like author names (since I'm literally the only person
57who will ever write on this site), multiple ways to click on a blog post card,
58blog post descriptions, and the scroll-to-top button. It also helped to move all
59categories to a single page, rather than have each category on its own page.
60
61The final big piece to finish the
62"[KonMari](https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method)"-like part
63of my process was to remove Bootstrap CSS in its entirety. However, this meant
64pulling out a few very useful classes, such as `.img-fluid` and the default font
65stacks to keep in my custom CSS.
66
67After removing all the unnecessary pieces, I was finally able to reorganize my
68content and add a very small amount of custom CSS to make everything pretty.
69This took a brief amount of time, effectively just consisting of me converting
70`<div>` tags into things like `<ul>` lists and choosing accent colors.
71
72# The Results
73
74## Reflection
75
76So, what did all of this reorganizing do to my webpages? Well, first, my
77websites are now **ridiculously fast**. Since the prior designs were also
78minimal and didn't have many images, they measured up in Firefox's Network
79Monitor around 300 KB - 600KB. After making the changes, my main site is at 5 KB
80transferred (22 KB total), and my blog is at 6.5 KB transferred (13 KB total).
81**That means the redesigned pages are less than 2% the size of the old
82designs.**
83
84Google Lighthouse ranks the new webpage as 100 in performance, accessibility,
85and best practices, with SEO at 92 since they think tap targets are not sized
86appropriately for mobile users. First contextual paints of the pages are under
870.8 seconds with 0 ms of blocking time. However, the blog subdomain ranks at 100
88for all four categories! First contextual paints of the blog homepage are under
891.0 seconds with 0 ms of blocking time, due to the fact that the CSS for my blog
90is within a separate CSS file, and the CSS for my main website is simply
91embedded in the HTML file.
92
93Now that everything is complete, I can confidently say I'm happy with the result
94and proud to look at the fastest set of websites I've created so far.
content/blog/2020-12-27-website-redesign.org added +100
@@ -0,0 +1,100 @@
1#+date: <2020-12-27>
2#+title: Redesigning My Website: The 5 KB Result
3#+description:
4
5
6* A Brief History
7
8As a form of continuous learning and entertainment, I've been running a
9handful of websites since 2016 when I took my first programming courses
10in college. I maintain one main website, the place I consider the
11official website to represent me. Under this site, I have a handful of
12subdirectories and subdomains.
13
14One of the parts I've enjoyed the most about web development is the
15aspect of designing an identity for a web page and working to find
16exciting ways to display the site's content. Inevitably, this means I've
17changed the designs for my websites more times than I could possibly
18count. Since I don't really host anything on my main webpage that's
19vital, it allows me the freedom to change things as inspiration strikes.
20
21Historically, I've relied on core utilities for spacing, components, and
22layouts from [[https://getbootstrap.com][Bootstrap]] and added custom
23CSS for fonts, accents, colors, and other items. I also tend to create
24sites with no border radius on items, visible borders, and content that
25takes up the entire screen (using whitespace inside components instead
26of whitespace around my components).
27
28* The Redesign Process
29
30About a week ago, I found myself wishing for a new design yet again. The
31prior design was largely inspired by IBM's
32[[https://www.carbondesignsystem.com][Carbon Design System]] and relied
33on jQuery, Bootstrap, along with some compressed
34[[https://en.wikipedia.org/wiki/WebP][.webp]] images.
35
36To anyone who knows my preferences toward web design - and even in my
37personal life - it should be no surprise that I immediately started
38looking for inspiration on minimalism. While there are some decent
39minimalistic designs on sites like
40[[https://dribbble.com/search/shots/popular/web-design?q=minimalism][Dribbble]],
41people seem to mostly discuss [[https://brutalist-web.design][brutalist
42web design]] when you ask about minimalism. While brutalist web design
43doesn't have to be minimal, it often is.
44
45I suppose, in a way, I did create a brutalist website since my HTML is
46semantic and accessible, hyperlinks are colored and underlined, and all
47native browser functions like scrolling and the back button work as
48expected. However, I didn't think about brutalism while designing these
49sites.
50
51The new design followed a simple design process. I walked through the
52screens on my blog and asked myself: "Is this element necessary for a
53user?" This allowed me to first start by removing all javascript, which
54had the sole purpose of allowing users to open a collapsed navbar on
55mobile. Replacing the collapsible navbar allowed me to remove both
56jQuery and Bootstrap's javascript.
57
58Next, I removed things like author names (since I'm literally the only
59person who will ever write on this site), multiple ways to click on a
60blog post card, blog post descriptions, and the scroll-to-top button. It
61also helped to move all categories to a single page, rather than have
62each category on its own page.
63
64The final big piece to finish the
65"[[https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method][KonMari]]"-like
66part of my process was to remove Bootstrap CSS in its entirety. However,
67this meant pulling out a few very useful classes, such as =.img-fluid=
68and the default font stacks to keep in my custom CSS.
69
70After removing all the unnecessary pieces, I was finally able to
71reorganize my content and add a very small amount of custom CSS to make
72everything pretty. This took a brief amount of time, effectively just
73consisting of me converting =<div>= tags into things like =<ul>= lists
74and choosing accent colors.
75
76* The Results
77
78** Reflection
79
80So, what did all of this reorganizing do to my webpages? Well, first, my
81websites are now *ridiculously fast*. Since the prior designs were also
82minimal and didn't have many images, they measured up in Firefox's
83Network Monitor around 300 KB - 600KB. After making the changes, my main
84site is at 5 KB transferred (22 KB total), and my blog is at 6.5 KB
85transferred (13 KB total). *That means the redesigned pages are less
86than 2% the size of the old designs.*
87
88Google Lighthouse ranks the new webpage as 100 in performance,
89accessibility, and best practices, with SEO at 92 since they think tap
90targets are not sized appropriately for mobile users. First contextual
91paints of the pages are under 0.8 seconds with 0 ms of blocking time.
92However, the blog subdomain ranks at 100 for all four categories! First
93contextual paints of the blog homepage are under 1.0 seconds with 0 ms
94of blocking time, due to the fact that the CSS for my blog is within a
95separate CSS file, and the CSS for my main website is simply embedded in
96the HTML file.
97
98Now that everything is complete, I can confidently say I'm happy with
99the result and proud to look at the fastest set of websites I've created
100so far.
content/blog/2020-12-28-neon-drive.md deleted −81
@@ -1,81 +0,0 @@
1+++
2date = 2020-12-28
3title = "Neon Drive: A Nostalgic 80s Arcade Racing Game"
4description = ""
5draft = false
6+++
7
8# Game Description
9
10[Neon Drive](https://store.steampowered.com/app/433910/Neon_Drive/) presents
11itself as a simple arcade-style game inspired by the arcade race games of the
121980s, yet it has managed to take up hours of my life without much effort. The
13game description, directly from the Steam page, is intriguing enough to entice
14anyone who's been looking for a good arcade racing game:
15
16> Neon Drive is a slick retro-futuristic arcade game that will make your brain
17> melt. You've been warned. From beautiful cityscapes and ocean roads to
18> 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, including
24chrome 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 arcade
28machine lets you play on Normal, Hard, Insane, Practice, and Free Run. To beat
29each arcade, you must reach the end of the level without crashing your car into
30the various obstacles on the course. Basic levels let you move left or right to
31avoid blocks in the road. Later levels put you through other tests, such as
32dodging traffic or blasting asteroids.
33
34The game uses synthwave music to keep you on track to make the correct moves by
35timing the beats of the songs to the correct moves on the screen. It reminds me
36of the early Guitar Hero games, as well as mobile apps like VOEZ - repetition
37and 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 every
42stereotype you can think of around retro-futuristic, synthwave arcades (in a
43good way).
44
45Once you get into the first level, we see that the choice of car fits right in
46with the stereotypical cars of the 80s, like the
47[DeLorean](https://en.wikipedia.org/wiki/DMC_DeLorean) or the [Ferrari
48F40](https://en.wikipedia.org/wiki/Ferrari_F40). Each new level comes with new
49color schemes and cars, so you should never get tired of the aesthetic.
50
51Personally, I love the orange and blue colors used in level 2:
52
53If you're the competitive type and getting 100% on all arcade machines isn't
54enough, there are leaderboards for the regular part of the game, and the
55endurance game mode.
56
57# Other Suggestions
58
59Neon Drive sits nicely within the well-founded cult genre of Outrun. Other games
60that I've enjoyed in this same spectrum are:
61
62- [Far Cry 3: Blood
63 Dragon](https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/)
64- [Retrowave](https://store.steampowered.com/app/1239690/Retrowave/)
65- [Slipstream](https://store.steampowered.com/app/732810/Slipstream/)
66
67Although these games aren't necessarily in the same genre, they do have aspects
68that place them close enough to interest gamers that enjoyed Neon Drive:
69
70- [Black Ice](https://store.steampowered.com/app/311800/Black_Ice/)
71- [Cloudpunk](https://store.steampowered.com/app/746850/Cloudpunk/)
72- [Need for Speed:
73 Heat](https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/)
74- [VirtuaVerse](https://store.steampowered.com/app/1019310/VirtuaVerse/)
75
76Of course, if all you really care about is the arcade aspect of these games, you
77can check out the [Atari
78Vault](https://store.steampowered.com/app/400020/Atari_Vault/) or any of the
79other classic games sold on Steam by companies like Namco, Atari. For something
80like Nintendo, you'd have to settle for buying used classic consoles or delve
81into the world of emulation.
content/blog/2020-12-28-neon-drive.org added +84
@@ -0,0 +1,84 @@
1#+date: <2020-12-28>
2#+title: Neon Drive: A Nostalgic 80s Arcade Racing Game
3#+description:
4
5
6* Game Description
7
8[[https://store.steampowered.com/app/433910/Neon_Drive/][Neon Drive]]
9presents itself as a simple arcade-style game inspired by the arcade
10race games of the 1980s, yet it has managed to take up hours of my life
11without much effort. The game description, directly from the Steam page,
12is intriguing enough to entice anyone who's been looking for a good
13arcade racing game:
14
15#+begin_quote
16Neon Drive is a slick retro-futuristic arcade game that will make your
17brain melt. You've been warned. From beautiful cityscapes and ocean
18roads to exploding enemy spaceships, Neon Drive has it all.
19#+end_quote
20
21* Gameplay
22
23The game holds true to the
24[[https://en.wikipedia.org/wiki/Retrofuturism][retro-futurism]] style,
25including chrome female robots, pixelated arcade machines, and
26[[https://teddit.net/r/outrun/][outrun]] aesthetics.
27
28Each level of the game is shown as a separate arcade machine. Each
29arcade machine lets you play on Normal, Hard, Insane, Practice, and Free
30Run. To beat each arcade, you must reach the end of the level without
31crashing your car into the various obstacles on the course. Basic levels
32let you move left or right to avoid blocks in the road. Later levels put
33you through other tests, such as dodging traffic or blasting asteroids.
34
35The game uses synthwave music to keep you on track to make the correct
36moves by timing the beats of the songs to the correct moves on the
37screen. It reminds me of the early Guitar Hero games, as well as mobile
38apps like VOEZ - repetition and staying on-beat is the only way to win.
39
40* In-Game Screenshots
41
42Taking a look at the main menu, you can see that Neon Drive plays into
43every stereotype you can think of around retro-futuristic, synthwave
44arcades (in a good way).
45
46Once you get into the first level, we see that the choice of car fits
47right in with the stereotypical cars of the 80s, like the
48[[https://en.wikipedia.org/wiki/DMC_DeLorean][DeLorean]] or the
49[[https://en.wikipedia.org/wiki/Ferrari_F40][Ferrari F40]]. Each new
50level comes with new color schemes and cars, so you should never get
51tired of the aesthetic.
52
53Personally, I love the orange and blue colors used in level 2:
54
55If you're the competitive type and getting 100% on all arcade machines
56isn't enough, there are leaderboards for the regular part of the game,
57and the endurance game mode.
58
59* Other Suggestions
60
61Neon Drive sits nicely within the well-founded cult genre of Outrun.
62Other games that I've enjoyed in this same spectrum are:
63
64- [[https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/][Far
65 Cry 3: Blood Dragon]]
66- [[https://store.steampowered.com/app/1239690/Retrowave/][Retrowave]]
67- [[https://store.steampowered.com/app/732810/Slipstream/][Slipstream]]
68
69Although these games aren't necessarily in the same genre, they do have
70aspects that place them close enough to interest gamers that enjoyed
71Neon Drive:
72
73- [[https://store.steampowered.com/app/311800/Black_Ice/][Black Ice]]
74- [[https://store.steampowered.com/app/746850/Cloudpunk/][Cloudpunk]]
75- [[https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/][Need
76 for Speed: Heat]]
77- [[https://store.steampowered.com/app/1019310/VirtuaVerse/][VirtuaVerse]]
78
79Of course, if all you really care about is the arcade aspect of these
80games, you can check out the
81[[https://store.steampowered.com/app/400020/Atari_Vault/][Atari Vault]]
82or any of the other classic games sold on Steam by companies like Namco,
83Atari. For something like Nintendo, you'd have to settle for buying used
84classic consoles or delve into the world of emulation.
content/blog/2020-12-29-zork.md deleted −80
@@ -1,80 +0,0 @@
1+++
2date = 2020-12-29
3title = "Zork: Let's Explore a Classic"
4description = ""
5draft = false
6+++
7
8# Download (Free)
9
10Before we get into the game itself, you should know that you can download Zork
11for free from Infocom's [download
12page](http://infocom-if.org/downloads/downloads.html). So feel free to boot it
13up and take a ride back to the 1980s with this masterpiece.
14
15# Game Description
16
17Zork is an interactive, text-based computer game originally released in 1980.
18This series, split into three separate games, introduced a robust and
19sophisticated text parser to gamers. People were largely used to the simple
20commands used in the popular game [Colossal Cave
21Adventure](https://en.wikipedia.org/wiki/Colossal_Cave_Adventure), but Zork
22allowed users to send more complex commands that included prepositions and
23conjunctions.
24
25Zork tracks your score as you explore the map, find tools, and collect trophy
26items (e.g., a jewel-encrusted egg). When you place your trophy items in the
27trophy case found in the Living Room area, you gain score points. Collecting the
28Twenty Treasures of Zork and placing them within the trophy case wins the game.
29However, you must explore the map, solve puzzles, and avoid being eaten by a
30grue to collect these treasures.
31
32# The Map
33
34Since Zork is a vast and complex game, it helps to have a map as you explore and
35collect your trophies. However, if you want to play the game as it was truly
36intended, you should try to play it without using the map.
37
38![Zork Map](https://img.cleberg.net/blog/20201229-zork/zork_map.jpg)
39
40_[Map Source](https://www.filfre.net/2012/01/exploring-zork-part-1/)_
41
42# In-Game Screenshots
43
44After playing the game (for the first time ever) for several weeks around 2014,
45I was finally able to beat the game with some online help to find the last
46couple items. As I was writing this post, I installed the game again to grab
47some screenshots to show off the true glory of this game. As noted in [Jimmy
48Maher's playthrough](https://www.filfre.net/2012/01/exploring-zork-part-1/), the
49original Zork games looked quite a bit different due to the older hardware of
50computers like the Apple II and multiple bug fixes that Infocom pushed out after
51the game's initial release. My play-through uses the [Zork
52Anthology](https://store.steampowered.com/app/570580/Zork_Anthology/) version,
53which utilizes DOSBox on Windows.
54
55The first screenshot here shows the introductory information, which doesn't
56include instructions of any kind for the player. If you haven't played text
57adventures before, try to use simple commands like "go west," "look around," or
58"hit troll with elvish sword."
59
60In this second screenshot, we see the player has entered the house and found the
61trophy case in the living room. The lantern and sword in this room allow the
62player to explore dark areas and attack enemies. If you don't use the lantern,
63you won't be able to see anything in dark areas, and you may be eaten by a grue.
64
65Finally, we see that the player has found the first treasure: a jewel-encrusted
66egg. These treasures can be taken back to the house and placed in the trophy
67case or carried until you feel like you want to put things away.
68
69# Conclusion
70
71It's been quite a few years since I first played Zork, but I clearly remember
72the late nights and bloodshot eyes that helped me find all the treasures. This
73game is well worth the time and effort, even though the text-based aspect may be
74off-putting to gamers who didn't have to grow up playing games without graphics.
75However, I believe that the strategy and skills learned in early video games
76like Zork can actually help you, even when playing newer games.
77
78If you do decide to play Zork, you can download Zork I, II, and III from
79Infocom's [download page](http://infocom-if.org/downloads/downloads.html) for
80free or search the internet for an online version.
content/blog/2020-12-29-zork.org added +86
@@ -0,0 +1,86 @@
1#+date: <2020-12-29>
2#+title: Zork: Let's Explore a Classic
3#+description:
4
5
6* Download (Free)
7
8Before we get into the game itself, you should know that you can
9download Zork for free from Infocom's
10[[http://infocom-if.org/downloads/downloads.html][download page]]. So
11feel free to boot it up and take a ride back to the 1980s with this
12masterpiece.
13
14* Game Description
15
16Zork is an interactive, text-based computer game originally released
17in 1980. This series, split into three separate games, introduced a
18robust and sophisticated text parser to gamers. People were largely used
19to the simple commands used in the popular game
20[[https://en.wikipedia.org/wiki/Colossal_Cave_Adventure][Colossal Cave
21Adventure]], but Zork allowed users to send more complex commands that
22included prepositions and conjunctions.
23
24Zork 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
26items in the trophy case found in the Living Room area, you gain score
27points. Collecting the Twenty Treasures of Zork and placing them within
28the trophy case wins the game. However, you must explore the map, solve
29puzzles, and avoid being eaten by a grue to collect these treasures.
30
31* The Map
32
33Since 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
35as it was truly intended, you should try to play it without using the
36map.
37
38#+caption: Zork Map
39[[https://img.cleberg.net/blog/20201229-zork/zork_map.jpg]]
40
41/[[https://www.filfre.net/2012/01/exploring-zork-part-1/][Map Source]]/
42
43* In-Game Screenshots
44
45After playing the game (for the first time ever) for several weeks
46around 2014, I was finally able to beat the game with some online help
47to find the last couple items. As I was writing this post, I installed
48the game again to grab some screenshots to show off the true glory of
49this game. As noted in
50[[https://www.filfre.net/2012/01/exploring-zork-part-1/][Jimmy Maher's
51playthrough]], the original Zork games looked quite a bit different due
52to the older hardware of computers like the Apple II and multiple bug
53fixes that Infocom pushed out after the game's initial release. My
54play-through uses the
55[[https://store.steampowered.com/app/570580/Zork_Anthology/][Zork
56Anthology]] version, which utilizes DOSBox on Windows.
57
58The first screenshot here shows the introductory information, which
59doesn't include instructions of any kind for the player. If you haven't
60played text adventures before, try to use simple commands like "go
61west," "look around," or "hit troll with elvish sword."
62
63In this second screenshot, we see the player has entered the house and
64found the trophy case in the living room. The lantern and sword in this
65room allow the player to explore dark areas and attack enemies. If you
66don't use the lantern, you won't be able to see anything in dark areas,
67and you may be eaten by a grue.
68
69Finally, we see that the player has found the first treasure: a
70jewel-encrusted egg. These treasures can be taken back to the house and
71placed in the trophy case or carried until you feel like you want to put
72things away.
73
74* Conclusion
75
76It's been quite a few years since I first played Zork, but I clearly
77remember the late nights and bloodshot eyes that helped me find all the
78treasures. This game is well worth the time and effort, even though the
79text-based aspect may be off-putting to gamers who didn't have to grow
80up playing games without graphics. However, I believe that the strategy
81and skills learned in early video games like Zork can actually help you,
82even when playing newer games.
83
84If you do decide to play Zork, you can download Zork I, II, and III from
85Infocom's [[http://infocom-if.org/downloads/downloads.html][download
86page]] for free or search the internet for an online version.
content/blog/2021-01-01-seum.md deleted −71
@@ -1,71 +0,0 @@
1+++
2date = 2021-01-01
3title = "SEUM: Spreedrunners from Hell"
4description = ""
5draft = false
6+++
7
8# Game Description
9
10[SEUM: Speedrunners from
11Hell](https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/) is
12an incredibly fast-paced mashup of a puzzle game and a free-running game.
13Created by [Pine Studio](https://pinestudio.co) in early 2016 and first released
14as a [demo on
15GameJolt](https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868),
16this game was successfully green-lit on Steam and has amassed a cult following
17on multiple consoles.
18
19Here's the game description straight from the developers:
20
21> Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners from
22> Hell is truly hardcore and focuses on speed and fast reaction.
23
24# Story
25
26SEUM does a great job setting the stage when you open the game for the first
27time, playing an animated video in the form of comic book images. You see Marty,
28the protagonist, sitting around drinking his beer as Satan busts down the door,
29cuts his hand off, and steals all his beer and bacon. As Satan is leaving, Marty
30whips a vinyl record across the room and cuts off one of Satan's hands. This
31hand is what allows you to use all the powers in the game.
32
33# Gameplay
34
35To accomplish each level, you will need to get to the final blue portal under a
36certain time limit. You can beat a level by getting to the portal before the
37time meter runs out or "Dominate" a level by beating it within a shorter time
38limit (noted by a bright red/orange color in the time meter).
39
40The portal is usually set behind locked gates, protected by dangerous defenses,
41or in hard-to-reach places. To reach the portal, you'll need to go through any
42existing orange portals, light all yellow beacons, avoid things like fireballs
43and blades, or use any satanic power orbs lying around. These special abilities
44include:
45
46- Gravity
47- Teleport
48- Rewind
49- Spawn platform
50- Roar (DLC)
51- Rocket (DLC)
52- Shadow world (DLC)
53
54For the main storyline, there are nine floors to beat. Each floor contains nine
55regular levels, one boss level, and one bonus level; although you don't
56technically need to beat all levels to advance to the next floor.
57# In-Game Screenshots
58
59The main menu gives you plenty of options for tuning your system, playing main
60levels, playing the DLC, or exploring the other game modes.
61
62Once you enter a level, you can look around and strategize before starting.
63Clicking any button will start the menu, and you'll have to restart if you die.
64One of the great things about SEUM is that it has great keyboard shortcuts. You
65can quickly smash `R` to restart the level or `M` to return to the level menu.
66
67Once you're able to win a level, you'll see the high scores and can watch
68replays of the best scores.
69
70Each regular level also contains a beer in a disguised location that may take
71some out-of-the-box thinking.
content/blog/2021-01-01-seum.org added +73
@@ -0,0 +1,73 @@
1#+date: <2021-01-01>
2#+title: SEUM: Spreedrunners from Hell
3#+description:
4
5
6* Game Description
7
8[[https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/][SEUM:
9Speedrunners from Hell]] is an incredibly fast-paced mashup of a puzzle
10game and a free-running game. Created by [[https://pinestudio.co][Pine
11Studio]] in early 2016 and first released as a
12[[https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868][demo
13on GameJolt]], this game was successfully green-lit on Steam and has
14amassed a cult following on multiple consoles.
15
16Here's the game description straight from the developers:
17
18#+begin_quote
19Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners
20from Hell is truly hardcore and focuses on speed and fast reaction.
21#+end_quote
22
23* Story
24
25SEUM 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.
27You see Marty, the protagonist, sitting around drinking his beer as
28Satan busts down the door, cuts his hand off, and steals all his beer
29and bacon. As Satan is leaving, Marty whips a vinyl record across the
30room and cuts off one of Satan's hands. This hand is what allows you to
31use all the powers in the game.
32
33* Gameplay
34
35To accomplish each level, you will need to get to the final blue portal
36under a certain time limit. You can beat a level by getting to the
37portal before the time meter runs out or "Dominate" a level by beating
38it within a shorter time limit (noted by a bright red/orange color in
39the time meter).
40
41The portal is usually set behind locked gates, protected by dangerous
42defenses, or in hard-to-reach places. To reach the portal, you'll need
43to go through any existing orange portals, light all yellow beacons,
44avoid things like fireballs and blades, or use any satanic power orbs
45lying around. These special abilities include:
46
47- Gravity
48- Teleport
49- Rewind
50- Spawn platform
51- Roar (DLC)
52- Rocket (DLC)
53- Shadow world (DLC)
54
55For the main storyline, there are nine floors to beat. Each floor
56contains nine regular levels, one boss level, and one bonus level;
57although you don't technically need to beat all levels to advance to the
58next floor. # In-Game Screenshots
59
60The main menu gives you plenty of options for tuning your system,
61playing main levels, playing the DLC, or exploring the other game modes.
62
63Once you enter a level, you can look around and strategize before
64starting. Clicking any button will start the menu, and you'll have to
65restart if you die. One of the great things about SEUM is that it has
66great keyboard shortcuts. You can quickly smash =R= to restart the level
67or =M= to return to the level menu.
68
69Once you're able to win a level, you'll see the high scores and can
70watch replays of the best scores.
71
72Each regular level also contains a beer in a disguised location that may
73take some out-of-the-box thinking.
content/blog/2021-01-04-fediverse.md deleted −100
@@ -1,100 +0,0 @@
1+++
2date = 2021-01-04
3title = "A Simple Guide to the Fediverse"
4description = ""
5draft = false
6+++
7
8# What is the Fediverse?
9
10The fediverse is a federated universe of servers commonly used for sharing
11content, like social media. So, instead of having to rely on a single
12organization to run the server (e.g. Facebook), the fediverse is a giant
13collection of servers across the world, owned by many people and organizations.
14
15Take a look at this depiction of a federated network. Each server in this photo
16is owned and run by different administrators/owners. Federated networks are best
17explained as email servers: you have an email account that exists on a server
18(e.g. Outlook), your friend has an account on a different server (e.g. GMail),
19and another friend has an account on a third server (e.g. ProtonMail). All three
20of you can talk and communicate back and forth without having to be on the same
21server. However, responsible email admins are there to set rules and control the
22traffic going in/out of the server.
23
24The main objective of this architecture is to decentralize the control within
25the internet connections. For example, if you run your own Mastodon instance,
26you and your users can't be censored or impacted in any way by authorities of
27another Mastodon instance. Some users have praised these features due to recent
28criticism of popular social media websites that may be over-censoring their
29users.
30
31This strategy is great for making sure control of the social web isn't
32controlled by a single organization, but it also has some downsides. If I create
33a Mastodon instance and get a ton of users to sign up, I can shut the server
34down at any time. That means you're at risk of losing the content you've created
35unless you back it up, or the server backs it up for you. Also, depending on the
36software used (e.g. Mastodon, Pixelfed, etc.), censorship may still be an issue
37if the server admins decide they want to censor their users. Now, censorship
38isn't always a bad thing and can even benefit the community as a whole, but
39you'll want to determine which servers align with your idea of proper
40censorship.
41
42However, these are risks that we take when we sign up for any online platform.
43Whatever your reason is for trying out federated social networks, they are part
44of the future of the internet. However, the popularity of these services is
45still yet to be determined, especially with the increased difficulty
46understanding and signing up for these platforms. Perhaps increased regulation
47and litigation against current social media sites will push more users into the
48fediverse.
49
50# Federated Alternatives to Popular Sites
51
52The list below is a small guide that will show you federated alternatives to
53current popular websites. There are many more out there, so go and explore: you
54might just find the perfect home.
55
56## Reddit
57
58- [Lemmy](https://lemmy.ml/instances)
59
60## Twitter/Facebook/Tumblr
61
62- [Mastodon](https://joinmastodon.org)
63- [Diaspora](https://diasporafoundation.org)
64- [Friendica](https://friendi.ca)
65- [GNU Social](https://gnusocial.network)
66- [Pleroma](https://pleroma.social)
67
68## Instagram
69
70- [Pixelfed](https://pixelfed.org)
71
72## Slack/Discord
73
74- [Matrix](https://element.io)
75
76## Youtube/Vimeo
77
78- [Peertube](https://joinpeertube.org)
79
80## Spotify/Soundcloud
81
82- [Funkwhale](https://funkwhale.audio)
83
84## Podcasting
85
86- [Pubcast](https://pubcast.pub)
87
88## Medium/Blogger
89
90- [WriteFreely](https://writefreely.org)
91
92# Get Started
93
94The best way to get started is to simply sign up and learn as you go. If you're
95comfortable signing up through a Mastodon, Pleroma, or Friendica server, here is
96[a list of themed servers](https://fediverse.party/en/portal/servers) to choose
97from. If you're looking for something else, try a web search for a federated
98alternative to your favorite sites.
99
100Find a server that focuses on your passions and start there!
content/blog/2021-01-04-fediverse.org added +100
@@ -0,0 +1,100 @@
1#+date: <2021-01-04>
2#+title: A Simple Guide to the Fediverse
3#+description:
4
5
6* What is the Fediverse?
7
8The fediverse is a federated universe of servers commonly used for
9sharing content, like social media. So, instead of having to rely on a
10single organization to run the server (e.g. Facebook), the fediverse is
11a giant collection of servers across the world, owned by many people and
12organizations.
13
14Take a look at this depiction of a federated network. Each server in
15this photo is owned and run by different administrators/owners.
16Federated networks are best explained as email servers: you have an
17email account that exists on a server (e.g. Outlook), your friend has an
18account on a different server (e.g. GMail), and another friend has an
19account on a third server (e.g. ProtonMail). All three of you can talk
20and communicate back and forth without having to be on the same server.
21However, responsible email admins are there to set rules and control the
22traffic going in/out of the server.
23
24The main objective of this architecture is to decentralize the control
25within the internet connections. For example, if you run your own
26Mastodon instance, you and your users can't be censored or impacted in
27any way by authorities of another Mastodon instance. Some users have
28praised these features due to recent criticism of popular social media
29websites that may be over-censoring their users.
30
31This strategy is great for making sure control of the social web isn't
32controlled by a single organization, but it also has some downsides. If
33I create a Mastodon instance and get a ton of users to sign up, I can
34shut the server down at any time. That means you're at risk of losing
35the content you've created unless you back it up, or the server backs it
36up for you. Also, depending on the software used (e.g. Mastodon,
37Pixelfed, etc.), censorship may still be an issue if the server admins
38decide they want to censor their users. Now, censorship isn't always a
39bad thing and can even benefit the community as a whole, but you'll want
40to determine which servers align with your idea of proper censorship.
41
42However, these are risks that we take when we sign up for any online
43platform. Whatever your reason is for trying out federated social
44networks, they are part of the future of the internet. However, the
45popularity of these services is still yet to be determined, especially
46with the increased difficulty understanding and signing up for these
47platforms. Perhaps increased regulation and litigation against current
48social media sites will push more users into the fediverse.
49
50* Federated Alternatives to Popular Sites
51
52The list below is a small guide that will show you federated
53alternatives to current popular websites. There are many more out there,
54so go and explore: you might just find the perfect home.
55
56** Reddit
57
58- [[https://lemmy.ml/instances][Lemmy]]
59
60** Twitter/Facebook/Tumblr
61
62- [[https://joinmastodon.org][Mastodon]]
63- [[https://diasporafoundation.org][Diaspora]]
64- [[https://friendi.ca][Friendica]]
65- [[https://gnusocial.network][GNU Social]]
66- [[https://pleroma.social][Pleroma]]
67
68** Instagram
69
70- [[https://pixelfed.org][Pixelfed]]
71
72** Slack/Discord
73
74- [[https://element.io][Matrix]]
75
76** Youtube/Vimeo
77
78- [[https://joinpeertube.org][Peertube]]
79
80** Spotify/Soundcloud
81
82- [[https://funkwhale.audio][Funkwhale]]
83
84** Podcasting
85
86- [[https://pubcast.pub][Pubcast]]
87
88** Medium/Blogger
89
90- [[https://writefreely.org][WriteFreely]]
91
92* Get Started
93
94The best way to get started is to simply sign up and learn as you go. If
95you're comfortable signing up through a Mastodon, Pleroma, or Friendica
96server, here is [[https://fediverse.party/en/portal/servers][a list of
97themed servers]] to choose from. If you're looking for something else,
98try a web search for a federated alternative to your favorite sites.
99
100Find a server that focuses on your passions and start there!
content/blog/2021-01-07-ufw.md deleted −217
@@ -1,217 +0,0 @@
1+++
2date = 2021-01-07
3title = "Secure Your Network with the Uncomplicated Firewall (ufw)"
4description = ""
5draft = false
6+++
7
8# Uncomplicated Firewall
9
10Uncomplicated Firewall, also known as ufw, is a convenient and beginner-friendly
11way to enforce OS-level firewall rules. For those who are hosting servers or any
12device that is accessible to the world (i.e., by public IP or domain name), it's
13critical that a firewall is properly implemented and active.
14
15Ufw is available by default in all Ubuntu installations after 8.04 LTS. For
16other distributions, you can look to install ufw or check if there are
17alternative firewalls installed already. There are usually alternatives
18available, such as Fedora's `firewall` and the package available on most
19distributions: `iptables`. Ufw is considered a beginner-friendly front-end to
20iptables.
21
22[Gufw](https://gufw.org) is available as a graphical user interface (GUI)
23application for users who are uncomfortable setting up a firewall through a
24terminal.
25
26# Getting Help
27
28If you need help figuring out commands, remember that you can run the `--help`
29flag to get a list of options.
30
31```sh
32sudo ufw --help
33```
34
35# Set Default State
36
37The proper way to run a firewall is to set a strict default state and slowly
38open up ports that you want to allow. This helps prevent anything malicious from
39slipping through the cracks. The following command prevents all incoming traffic
40(other than the rules we specify later), but you can also set this for outgoing
41connections, if necessary.
42
43```sh
44sudo ufw default deny incoming
45```
46
47You should also allow outgoing traffic if you want to allow the device to
48communicate back to you or other parties. For example, media servers like Plex
49need to be able to send out data related to streaming the media.
50
51```sh
52sudo ufw default allow outgoing
53```
54
55# Adding Port Rules
56
57Now that we've disabled all incoming traffic by default, we need to open up some
58ports (or else no traffic would be able to come in). If you need to be able to
59`ssh` into the machine, you'll need to open up port 22.
60
61```sh
62sudo ufw allow 22
63```
64
65You can also issue more restrictive rules. The following rule will allow `ssh`
66connections only from machines on the local subnet.
67
68```sh
69sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22
70```
71
72If you need to set a rule that isn't tcp, just append your connection type to
73the end of the rule.
74
75```sh
76sudo ufw allow 1900/udp
77```
78
79# Enable ufw
80
81Now that the firewall is configured and ready to go, you can enable the
82firewall.
83
84```sh
85sudo ufw enable
86```
87
88A restart may be required for the firewall to begin operating.
89
90```sh
91sudo reboot now
92```
93
94# Checking Status
95
96Now that the firewall is enabled, let's check and see what the rules look like.
97
98```sh
99sudo ufw status numbered
100```
101
102```txt
103Status: active
104
105 To Action From
106 -- ------ ----
107[ 1] 22 ALLOW IN Anywhere
108[ 2] 22 (v6) ALLOW IN Anywhere (v6)
109```
110
111# Deleting Rules
112
113If you need to delete a rule, you need to know the number associated with that
114rule. Let's delete the first rule in the table above. You'll be asked to confirm
115the deletion as part of this process.
116
117```sh
118sudo ufw delete 1
119```
120
121# Managing App Rules
122
123Luckily, there's a convenient way for installed applications to create files
124that ufw can easily implement so that you don't have to search and find which
125ports your application requires. To see if your device has any applications with
126pre-installed ufw rules, execute the following command:
127
128```sh
129sudo ufw app list
130```
131
132The results should look something like this:
133
134```txt
135Available applications:
136 OpenSSH
137 Samba
138 plexmediaserver
139 plexmediaserver-all
140 plexmediaserver-dlna
141```
142
143If you want to get more information on a specific app rule, use the `info`
144command.
145
146```sh
147sudo ufw app info plexmediaserver-dlna
148```
149
150You'll get a blurb of info back like this:
151
152```txt
153Profile: plexmediaserver-dlna
154Title: Plex Media Server (DLNA)
155Description: The Plex Media Server (additional DLNA capability only)
156
157Ports:
158 1900/udp
159 32469/tcp
160```
161
162You can add or delete app rules the same way that you'd add or delete specific
163port rules.
164
165```sh
166sudo ufw allow plexmediaserver-dlna
167```
168
169```sh
170sudo ufw delete RULE|NUM
171```
172
173# Creating App Rules
174
175If you'd like to create you own app rule, you'll need to create a file in the
176`/etc/ufw/applications.d` directory. Within the file you create, you need to
177make sure the content is properly formatted.
178
179For example, here are the contents my `plexmediaserver` file, which creates
180three distinct app rules for ufw:
181
182```config
183[plexmediaserver]
184title=Plex Media Server (Standard)
185description=The Plex Media Server
186ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp
187
188[plexmediaserver-dlna]
189title=Plex Media Server (DLNA)
190description=The Plex Media Server (additional DLNA capability only)
191ports=1900/udp|32469/tcp
192
193[plexmediaserver-all]
194title=Plex Media Server (Standard + DLNA)
195description=The Plex Media Server (with additional DLNA capability)
196ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp
197```
198
199So, if I wanted to create a custom app rule called "mycustomrule," I'd create a
200file and add my content like this:
201
202```sh
203sudo nano /etc/ufw/applications.d/mycustomrule
204```
205
206```config
207[mycustomrule]
208title=My Custom Rule
209description=This is a temporary ufw app rule.
210ports=88/tcp|9100/udp
211```
212
213Then, I would just enable this rule in ufw.
214
215```sh
216sudo ufw allow mycustomrule
217```
content/blog/2021-01-07-ufw.org added +219
@@ -0,0 +1,219 @@
1#+date: <2021-01-07>
2#+title: Secure Your Network with the Uncomplicated Firewall (ufw)
3#+description:
4
5
6* Uncomplicated Firewall
7
8Uncomplicated Firewall, also known as ufw, is a convenient and
9beginner-friendly way to enforce OS-level firewall rules. For those who
10are hosting servers or any device that is accessible to the world (i.e.,
11by public IP or domain name), it's critical that a firewall is properly
12implemented and active.
13
14Ufw is available by default in all Ubuntu installations after 8.04 LTS.
15For other distributions, you can look to install ufw or check if there
16are alternative firewalls installed already. There are usually
17alternatives available, such as Fedora's =firewall= and the package
18available on most distributions: =iptables=. Ufw is considered a
19beginner-friendly front-end to iptables.
20
21[[https://gufw.org][Gufw]] is available as a graphical user interface
22(GUI) application for users who are uncomfortable setting up a firewall
23through a terminal.
24
25* Getting Help
26
27If you need help figuring out commands, remember that you can run the
28=--help= flag to get a list of options.
29
30#+begin_src sh
31sudo ufw --help
32#+end_src
33
34* Set Default State
35
36The proper way to run a firewall is to set a strict default state and
37slowly open up ports that you want to allow. This helps prevent anything
38malicious from slipping through the cracks. The following command
39prevents all incoming traffic (other than the rules we specify later),
40but you can also set this for outgoing connections, if necessary.
41
42#+begin_src sh
43sudo ufw default deny incoming
44#+end_src
45
46You should also allow outgoing traffic if you want to allow the device
47to communicate back to you or other parties. For example, media servers
48like Plex need to be able to send out data related to streaming the
49media.
50
51#+begin_src sh
52sudo ufw default allow outgoing
53#+end_src
54
55* Adding Port Rules
56
57Now that we've disabled all incoming traffic by default, we need to open
58up some ports (or else no traffic would be able to come in). If you need
59to be able to =ssh= into the machine, you'll need to open up port 22.
60
61#+begin_src sh
62sudo ufw allow 22
63#+end_src
64
65You can also issue more restrictive rules. The following rule will allow
66=ssh= connections only from machines on the local subnet.
67
68#+begin_src sh
69sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22
70#+end_src
71
72If you need to set a rule that isn't tcp, just append your connection
73type to the end of the rule.
74
75#+begin_src sh
76sudo ufw allow 1900/udp
77#+end_src
78
79* Enable ufw
80
81Now that the firewall is configured and ready to go, you can enable the
82firewall.
83
84#+begin_src sh
85sudo ufw enable
86#+end_src
87
88A restart may be required for the firewall to begin operating.
89
90#+begin_src sh
91sudo reboot now
92#+end_src
93
94* Checking Status
95
96Now that the firewall is enabled, let's check and see what the rules
97look like.
98
99#+begin_src sh
100sudo ufw status numbered
101#+end_src
102
103#+begin_src txt
104Status: active
105
106 To Action From
107 -- ------ ----
108[ 1] 22 ALLOW IN Anywhere
109[ 2] 22 (v6) ALLOW IN Anywhere (v6)
110#+end_src
111
112* Deleting Rules
113
114If you need to delete a rule, you need to know the number associated
115with that rule. Let's delete the first rule in the table above. You'll
116be asked to confirm the deletion as part of this process.
117
118#+begin_src sh
119sudo ufw delete 1
120#+end_src
121
122* Managing App Rules
123
124Luckily, there's a convenient way for installed applications to create
125files that ufw can easily implement so that you don't have to search and
126find which ports your application requires. To see if your device has
127any applications with pre-installed ufw rules, execute the following
128command:
129
130#+begin_src sh
131sudo ufw app list
132#+end_src
133
134The results should look something like this:
135
136#+begin_src txt
137Available applications:
138 OpenSSH
139 Samba
140 plexmediaserver
141 plexmediaserver-all
142 plexmediaserver-dlna
143#+end_src
144
145If you want to get more information on a specific app rule, use the
146=info= command.
147
148#+begin_src sh
149sudo ufw app info plexmediaserver-dlna
150#+end_src
151
152You'll get a blurb of info back like this:
153
154#+begin_src txt
155Profile: plexmediaserver-dlna
156Title: Plex Media Server (DLNA)
157Description: The Plex Media Server (additional DLNA capability only)
158
159Ports:
160 1900/udp
161 32469/tcp
162#+end_src
163
164You can add or delete app rules the same way that you'd add or delete
165specific port rules.
166
167#+begin_src sh
168sudo ufw allow plexmediaserver-dlna
169#+end_src
170
171#+begin_src sh
172sudo ufw delete RULE|NUM
173#+end_src
174
175* Creating App Rules
176
177If you'd like to create you own app rule, you'll need to create a file
178in the =/etc/ufw/applications.d= directory. Within the file you create,
179you need to make sure the content is properly formatted.
180
181For example, here are the contents my =plexmediaserver= file, which
182creates three distinct app rules for ufw:
183
184#+begin_src config
185[plexmediaserver]
186title=Plex Media Server (Standard)
187description=The Plex Media Server
188ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp
189
190[plexmediaserver-dlna]
191title=Plex Media Server (DLNA)
192description=The Plex Media Server (additional DLNA capability only)
193ports=1900/udp|32469/tcp
194
195[plexmediaserver-all]
196title=Plex Media Server (Standard + DLNA)
197description=The Plex Media Server (with additional DLNA capability)
198ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp
199#+end_src
200
201So, if I wanted to create a custom app rule called "mycustomrule," I'd
202create a file and add my content like this:
203
204#+begin_src sh
205sudo nano /etc/ufw/applications.d/mycustomrule
206#+end_src
207
208#+begin_src config
209[mycustomrule]
210title=My Custom Rule
211description=This is a temporary ufw app rule.
212ports=88/tcp|9100/udp
213#+end_src
214
215Then, I would just enable this rule in ufw.
216
217#+begin_src sh
218sudo ufw allow mycustomrule
219#+end_src
content/blog/2021-02-19-macos.md deleted −197
@@ -1,197 +0,0 @@
1+++
2date = 2021-02-19
3title = "macOS: Testing Out a New OS"
4description = ""
5draft = false
6+++
7
8# Diving into macOS
9
10After spending nearly 15 years working with Windows and 8 years on Linux, I have
11experienced macOS for the first time. By chance, my spouse happened to buy a new
12MacBook and gifted me their 2013 model. Of course, I still consider my Linux
13desktop to be my daily driver and keep Windows around for gaming needs, but over
14the past week I've found myself using the MacBook more and more for things that
15don't require gaming specs or advanced dev tools.
16
17# Initial Thoughts
18
19Before I move on to the technical aspects of my set-up, I want to take some time
20and express my thoughts on the overall OS.
21
22As expected, the initial computer setup is a breeze with Mac's guided GUI
23installer.
24
25The desktop itself reminds me of GNOME more than anything else I've seen: even
26Pantheon from [ElementaryOS](https://elementary.io/), which people commonly
27refer to as the closest Linux distro to macOS. The desktop toolbar is great and
28far surpasses the utility of the GNOME toolbar due to the fact that the
29extensions and icons _actually work_. I launch macOS and immediately see my
30shortcuts for Tresorit, Bitwarden, and Mullvad pop up as the computer loads.
31
32Even further, the app dock is very useful and will be yet another familiarity
33for GNOME users. I know many people like panels instead of docks, but I've
34always found docks to have a more pleasing UI. However, I had to disable the
35"Show recent applications in Dock" preference; I can't stand items taking up
36precious screen space if I'm not currently using them. On that same note, it's
37taking me some time to get use to the fact that I have to manually quit an app
38or else it will still stay open/active in the dock, even if I've closed out all
39windows for that app (e.g. Firefox).
40
41Overall, I'm having a lot of fun and for users who spend a large majority of
42their time performing basic tasks like web browsing, writing, watching media,
43etc., macOS is a fantastic option.
44
45The rest of this post explains the technicalities of how I set up my CLI
46environment to make me feel more at-home, similar to the environments I set up
47on Fedora, Ubuntu, etc.
48
49# Making it Feel Like Home
50
51If you're someone who uses Linux primarily, no doubt your first thought when
52booting macOS will be the same as mine was: "Where is the terminal and how do I
53set up my favorite utilities?"
54
55Luckily, macOS hasn't completely hidden away the development tools from the
56average user. You can easily find the Terminal app in the Launchpad area, but
57it's probably not what you're used to. I was surprised (and happy) to see that
58the default shell is `zsh`, the shell I use on all of my Linux distros. However,
59the commands are not the same - even the ones you may think are native to the
60shell. Commands like `dir` do not exist, so other native commands like `ls -la`
61or `pwd` are more useful here.
62
63With only a few minutes of installing and tweaking a few packages, I was able to
64recreate a terminal environment that I feel very comfortable using. See the
65image below for a preview of the iTerm2 app with a split view between my macOS
66desktop shell and an SSH session into my server.
67
68# Xcode
69
70My first step was to search the web for any hints on how to get `zsh` back up to
71the state I like, with extensions, themes, etc. My first step was to install the
72CLI tools for [Xcode](https://developer.apple.com/xcode/), Apple's suite of
73development tools.
74
75```sh
76sudo xcode-select -r
77```
78
79```sh
80sudo xcode-select --install
81```
82
83# Homebrew
84
85Next up is to install [Homebrew](https://brew.sh), a nifty package manager for
86macOS.
87
88```sh
89/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
90```
91
92I ran into a permission error when installing Homebrew:
93
94```sh
95Error: Failed to link all completions, docs and manpages:
96 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
97Failed during: /usr/local/bin/brew update --force --quiet
98```
99
100I found that the following permission modification worked like a charm. However,
101I noted that some users online discussed the fact that this solution may not
102work if your system has multiple users who use Homebrew.
103
104```sh
105sudo chown -R $(whoami) $(brew --prefix)/*
106```
107
108Next up is to ensure Homebrew is updated and cleaned.
109
110```sh
111brew update
112```
113
114```sh
115brew cleanup
116```
117
118# iTerm2
119
120Now that I've installed the basic utilities for development, I moved onto
121installing iTerm2, a much better terminal than the default.
122
123```sh
124brew install --cask iterm2
125```
126
127I also used the `Make iTerm2 Default Term` and `Install Shell Integration`
128options in the iTerm2 application menu to make sure I don't run into any issues
129later on with different terminals.
130
131We will also install `zsh` so we can use it in iTerm2.
132
133```sh
134brew install zsh
135```
136
137# Oh-My-Zsh
138
139I've shown the great aspects of [Oh My Zsh](https://ohmyz.sh) in other blog
140posts, so I'll skip over that speech for now. Simply install it and run an
141update.
142
143```sh
144sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
145```
146
147```sh
148omz update
149```
150
151Finally, restart the iTerm2 application to ensure all changes go into effect.
152
153# Oh-My-Zsh Themes
154
155Let's change the theme of the terminal to make it a little more friendly.
156
157```sh
158open ~/.zshrc
159```
160
161The third section of this file should contain a line like the code below. Change
162that theme to [any theme you
163want](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes), save the file, and exit.
164
165```sh
166ZSH_THEME="af-magic"
167```
168
169After changing the `.zshrc` file, you'll need to close your terminal and re-open
170it to see the changes. Optionally, just open a new tab if you're using iTerm2,
171and you'll see the new shell config.
172
173# Oh-My-Zsh Plugins
174
175Of course, my customization of `zsh` would not be complete without
176[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions). This
177will bring up commands you've run in the past as you type them. For example, if
178you've run `ssh user@192.168.1.99` before, the terminal will show this command
179as soon as you start typing it (e.g. `zsh u`), and you can hit the right arrow
180to autocomplete the command.
181
182```sh
183git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
184```
185
186```sh
187open ~/.zshrc
188```
189
190```sh
191# Scroll down the script and edit this line to add zsh-autosuggestions
192plugins=(git zsh-autosuggestions)
193```
194
195Remember: After changing the `.zshrc` file, you'll need to close your terminal
196and re-open it to see the changes. Optionally, just open a new tab if you're
197using iTerm2, and you'll see the new shell config.
content/blog/2021-02-19-macos.org added +203
@@ -0,0 +1,203 @@
1#+date: <2021-02-19>
2#+title: macOS: Testing Out a New OS
3#+description:
4
5
6* Diving into macOS
7
8After spending nearly 15 years working with Windows and 8 years on
9Linux, I have experienced macOS for the first time. By chance, my spouse
10happened to buy a new MacBook and gifted me their 2013 model. Of course,
11I still consider my Linux desktop to be my daily driver and keep Windows
12around for gaming needs, but over the past week I've found myself using
13the MacBook more and more for things that don't require gaming specs or
14advanced dev tools.
15
16* Initial Thoughts
17
18Before I move on to the technical aspects of my set-up, I want to take
19some time and express my thoughts on the overall OS.
20
21As expected, the initial computer setup is a breeze with Mac's guided
22GUI installer.
23
24The desktop itself reminds me of GNOME more than anything else I've
25seen: even Pantheon from [[https://elementary.io/][ElementaryOS]], which
26people commonly refer to as the closest Linux distro to macOS. The
27desktop toolbar is great and far surpasses the utility of the GNOME
28toolbar due to the fact that the extensions and icons /actually work/. I
29launch macOS and immediately see my shortcuts for Tresorit, Bitwarden,
30and Mullvad pop up as the computer loads.
31
32Even further, the app dock is very useful and will be yet another
33familiarity for GNOME users. I know many people like panels instead of
34docks, but I've always found docks to have a more pleasing UI. However,
35I had to disable the "Show recent applications in Dock" preference; I
36can't stand items taking up precious screen space if I'm not currently
37using them. On that same note, it's taking me some time to get use to
38the fact that I have to manually quit an app or else it will still stay
39open/active in the dock, even if I've closed out all windows for that
40app (e.g. Firefox).
41
42Overall, I'm having a lot of fun and for users who spend a large
43majority of their time performing basic tasks like web browsing,
44writing, watching media, etc., macOS is a fantastic option.
45
46The rest of this post explains the technicalities of how I set up my CLI
47environment to make me feel more at-home, similar to the environments I
48set up on Fedora, Ubuntu, etc.
49
50* Making it Feel Like Home
51
52If you're someone who uses Linux primarily, no doubt your first thought
53when booting macOS will be the same as mine was: "Where is the terminal
54and how do I set up my favorite utilities?"
55
56Luckily, macOS hasn't completely hidden away the development tools from
57the average user. You can easily find the Terminal app in the Launchpad
58area, but it's probably not what you're used to. I was surprised (and
59happy) to see that the default shell is =zsh=, the shell I use on all of
60my Linux distros. However, the commands are not the same - even the ones
61you may think are native to the shell. Commands like =dir= do not exist,
62so other native commands like =ls -la= or =pwd= are more useful here.
63
64With only a few minutes of installing and tweaking a few packages, I was
65able to recreate a terminal environment that I feel very comfortable
66using. See the image below for a preview of the iTerm2 app with a split
67view between my macOS desktop shell and an SSH session into my server.
68
69* Xcode
70
71My first step was to search the web for any hints on how to get =zsh=
72back up to the state I like, with extensions, themes, etc. My first step
73was to install the CLI tools for
74[[https://developer.apple.com/xcode/][Xcode]], Apple's suite of
75development tools.
76
77#+begin_src sh
78sudo xcode-select -r
79#+end_src
80
81#+begin_src sh
82sudo xcode-select --install
83#+end_src
84
85* Homebrew
86
87Next up is to install [[https://brew.sh][Homebrew]], a nifty package
88manager for macOS.
89
90#+begin_src sh
91/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
92#+end_src
93
94I ran into a permission error when installing Homebrew:
95
96#+begin_src sh
97Error: Failed to link all completions, docs and manpages:
98 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
99Failed during: /usr/local/bin/brew update --force --quiet
100#+end_src
101
102I found that the following permission modification worked like a charm.
103However, I noted that some users online discussed the fact that this
104solution may not work if your system has multiple users who use
105Homebrew.
106
107#+begin_src sh
108sudo chown -R $(whoami) $(brew --prefix)/*
109#+end_src
110
111Next up is to ensure Homebrew is updated and cleaned.
112
113#+begin_src sh
114brew update
115#+end_src
116
117#+begin_src sh
118brew cleanup
119#+end_src
120
121* iTerm2
122
123Now that I've installed the basic utilities for development, I moved
124onto installing iTerm2, a much better terminal than the default.
125
126#+begin_src sh
127brew install --cask iterm2
128#+end_src
129
130I also used the =Make iTerm2 Default Term= and
131=Install Shell Integration= options in the iTerm2 application menu to
132make sure I don't run into any issues later on with different terminals.
133
134We will also install =zsh= so we can use it in iTerm2.
135
136#+begin_src sh
137brew install zsh
138#+end_src
139
140* Oh-My-Zsh
141
142I've shown the great aspects of [[https://ohmyz.sh][Oh My Zsh]] in other
143blog posts, so I'll skip over that speech for now. Simply install it and
144run an update.
145
146#+begin_src sh
147sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
148#+end_src
149
150#+begin_src sh
151omz update
152#+end_src
153
154Finally, restart the iTerm2 application to ensure all changes go into
155effect.
156
157* Oh-My-Zsh Themes
158
159Let's change the theme of the terminal to make it a little more
160friendly.
161
162#+begin_src sh
163open ~/.zshrc
164#+end_src
165
166The third section of this file should contain a line like the code
167below. Change that theme to
168[[https://github.com/ohmyzsh/ohmyzsh/wiki/Themes][any theme you want]],
169save the file, and exit.
170
171#+begin_src sh
172ZSH_THEME="af-magic"
173#+end_src
174
175After changing the =.zshrc= file, you'll need to close your terminal and
176re-open it to see the changes. Optionally, just open a new tab if you're
177using iTerm2, and you'll see the new shell config.
178
179* Oh-My-Zsh Plugins
180
181Of course, my customization of =zsh= would not be complete without
182[[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]].
183This will bring up commands you've run in the past as you type them. For
184example, if you've run =ssh user@192.168.1.99= before, the terminal will
185show this command as soon as you start typing it (e.g. =zsh u=), and you
186can hit the right arrow to autocomplete the command.
187
188#+begin_src sh
189git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
190#+end_src
191
192#+begin_src sh
193open ~/.zshrc
194#+end_src
195
196#+begin_src sh
197# Scroll down the script and edit this line to add zsh-autosuggestions
198plugins=(git zsh-autosuggestions)
199#+end_src
200
201Remember: After changing the =.zshrc= file, you'll need to close your
202terminal and re-open it to see the changes. Optionally, just open a new
203tab if you're using iTerm2, and you'll see the new shell config.
content/blog/2021-03-19-clone-github-repos.md → content/blog/2021-03-19-clone-github-repos.org renamed +64 −64
@@ -1,58 +1,57 @@
1+++ 1#+date: <2021-03-19>
2date = 2021-03-19 2#+title: How to Clonse All Repositories from a GitHub or Sourcehut Account
3title = "How to Clonse All Repositories from a GitHub or Sourcehut Account" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Cloning from GitHub
9 5
10If you're like me and use a lot of different devices (and sometimes decide to 6* Cloning from GitHub
11just wipe your device and start with a new OS), you probably know the pain of
12cloning all your old code repositories down to your local file system.
13 7
14If you're using GitHub, you can easily clone all of your code back down in just 8If you're like me and use a lot of different devices (and sometimes
15seconds. First, create a bash script. I do so by opening a new file in `nano`, 9decide to just wipe your device and start with a new OS), you probably
16but you can use `gedit`, `vim`, or something else: 10know the pain of cloning all your old code repositories down to your
11local file system.
17 12
18```sh 13If you're using GitHub, you can easily clone all of your code back down
14in just seconds. First, create a bash script. I do so by opening a new
15file in =nano=, but you can use =gedit=, =vim=, or something else:
16
17#+begin_src sh
19nano clone_github_repos.sh 18nano clone_github_repos.sh
20``` 19#+end_src
21 20
22Next, paste in the following information. Note that you can replace the word 21Next, paste in the following information. Note that you can replace the
23`users` in the first line with `orgs` and type an organization's name instead of 22word =users= in the first line with =orgs= and type an organization's
24a user's name. 23name instead of a user's name.
25 24
26```sh 25#+begin_src sh
27CNTX=users; NAME=YOUR-USERNAME; PAGE=1 26CNTX=users; NAME=YOUR-USERNAME; PAGE=1
28curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" | 27curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" |
29 grep -e 'git_url*' | 28 grep -e 'git_url*' |
30 cut -d " -f 4 | 29 cut -d " -f 4 |
31 xargs -L1 git clone 30 xargs -L1 git clone
32``` 31#+end_src
33 32
34Finally, save the bash script and make it executable. 33Finally, save the bash script and make it executable.
35 34
36```sh 35#+begin_src sh
37chmod a+x clone_github_repos.sh 36chmod a+x clone_github_repos.sh
38``` 37#+end_src
39 38
40Now you can run the script and should see the cloning process begin. 39Now you can run the script and should see the cloning process begin.
41 40
42```sh 41#+begin_src sh
43./clone_github_repos.sh 42./clone_github_repos.sh
44``` 43#+end_src
45 44
46# Cloning from Sourcehut 45* Cloning from Sourcehut
47 46
48I haven't fully figured out how to directly incorporate Sourcehut's GraphQL API 47I haven't fully figured out how to directly incorporate Sourcehut's
49into a bash script yet, so this one will take two steps. 48GraphQL API into a bash script yet, so this one will take two steps.
50 49
51First, log-in to Sourcehut and go to their [GraphQL playground for 50First, log-in to Sourcehut and go to their
52Git](https://git.sr.ht/graphql). Next, paste the following query into the left 51[[https://git.sr.ht/graphql][GraphQL playground for Git]]. Next, paste
53box: 52the following query into the left box:
54 53
55```sh 54#+begin_src sh
56query { 55query {
57 me { 56 me {
58 canonicalName 57 canonicalName
@@ -64,26 +63,27 @@ query {
64 } 63 }
65 } 64 }
66} 65}
67``` 66#+end_src
68 67
69The output on the right side will give you an object of all your repositories. 68The output on the right side will give you an object of all your
70Just grab that text and remove all the characters such as quotation marks and 69repositories. Just grab that text and remove all the characters such as
71curly brackets. You will need a single-line list of space-separated values for 70quotation marks and curly brackets. You will need a single-line list of
72the next step. 71space-separated values for the next step.
73 72
74Now let's create the bash script: 73Now let's create the bash script:
75 74
76```sh 75#+begin_src sh
77nano clone_sourcehut_repos.sh 76nano clone_sourcehut_repos.sh
78``` 77#+end_src
79 78
80Next, paste the following bash script in with the list of repositories you 79Next, paste the following bash script in with the list of repositories
81obtained above and replace `your-username` with your username. 80you obtained above and replace =your-username= with your username.
82 81
83Note that this uses the SSH-based Git cloning method (e.g. `git@git...`), so 82Note that this uses the SSH-based Git cloning method
84you'll need to ensure you have set up Sourcehut with your SSH key. 83(e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut
84with your SSH key.
85 85
86```sh 86#+begin_src sh
87repos=(repo1 repo2 repo3) 87repos=(repo1 repo2 repo3)
88 88
89# List all sub-directories in the current directory 89# List all sub-directories in the current directory
@@ -92,36 +92,36 @@ do
92 # Clone 92 # Clone
93 git clone git@git.sr.ht:~your-username/$repo 93 git clone git@git.sr.ht:~your-username/$repo
94done 94done
95``` 95#+end_src
96 96
97Finally, save the bash script and make it executable. 97Finally, save the bash script and make it executable.
98 98
99```sh 99#+begin_src sh
100chmod a+x clone_sourcehut_repos.sh 100chmod a+x clone_sourcehut_repos.sh
101``` 101#+end_src
102 102
103Now you can run the script and should see the cloning process begin. 103Now you can run the script and should see the cloning process begin.
104 104
105```sh 105#+begin_src sh
106./clone_sourcehut_repos.sh 106./clone_sourcehut_repos.sh
107``` 107#+end_src
108 108
109# Moving Repositories to a New Host 109* Moving Repositories to a New Host
110 110
111Now that you have all of your code repositories cloned to your local computer, 111Now that you have all of your code repositories cloned to your local
112you may want to change the remote host (e.g., moving from GitHub to GitLab). To 112computer, you may want to change the remote host (e.g., moving from
113do this, let's create another bash script: 113GitHub to GitLab). To do this, let's create another bash script:
114 114
115```sh 115#+begin_src sh
116nano change_remote_urls.sh 116nano change_remote_urls.sh
117``` 117#+end_src
118 118
119Past the following information and be sure to change the URL information to 119Past the following information and be sure to change the URL information
120whichever host you are moving to. For this example, I am looping through all of 120to whichever host you are moving to. For this example, I am looping
121my cloned GitHub directories and changing them to Sourcehut (e.g. 121through all of my cloned GitHub directories and changing them to
122`<YOUR_NEW_REMOTE_URL>` -> `git@git.sr.ht:~myusername`). 122Sourcehut (e.g. =<YOUR_NEW_REMOTE_URL>= -> =git@git.sr.ht:~myusername=).
123 123
124```sh 124#+begin_src sh
125# List all sub-directories in the current directory 125# List all sub-directories in the current directory
126for dir in */ 126for dir in */
127do 127do
@@ -136,16 +136,16 @@ do
136 # Go back to main directory 136 # Go back to main directory
137 cd .. 137 cd ..
138done 138done
139``` 139#+end_src
140 140
141Finally, save the bash script and make it executable. 141Finally, save the bash script and make it executable.
142 142
143```sh 143#+begin_src sh
144chmod a+x change_remote_urls.sh 144chmod a+x change_remote_urls.sh
145``` 145#+end_src
146 146
147Now you can run the script and should see the cloning process begin. 147Now you can run the script and should see the cloning process begin.
148 148
149```sh 149#+begin_src sh
150./change_remote_urls.sh 150./change_remote_urls.sh
151``` 151#+end_src
content/blog/2021-03-28-gemini-capsule.md deleted −171
@@ -1,171 +0,0 @@
1+++
2date = 2021-03-28
3title = "Launching a Gemini Capsule"
4description = ""
5draft = false
6+++
7
8# What is Gemini?
9
10[Gemini](https://gemini.circumlunar.space/) is an internet protocol introduced
11in June 2019 as an alternative to HTTP(S) or Gopher. In layman's terms, it's an
12alternative way to browse sites (called capsules) that requires a special
13browser. Since Gemini is not standardized as an internet standard, normal web
14browsers won't be able to load a Gemini capsule. Instead, you'll need to use [a
15Gemini-specific browser](https://gemini.circumlunar.space/clients.html).
16
17The content found within a Gemini page is called
18[Gemtext](https://gemini.circumlunar.space/docs/cheatsheet.gmi) and is
19_extremely_ basic (on purpose). Gemini only processes the text, no media content
20like images. However, you're able to style 3 levels of headings, regular text,
21links (which will display on their own line), quotes, and an unordered list.
22
23Here's a complete listing of valid Gemtext:
24
25````txt
26# Heading 1
27## Heading 2
28### Heading 3
29
30Regular text! Lorem ipsum dolor sit amet.
31
32=> https://example.com My Website
33=> gemini://example.com My Gemini Capsule
34
35> "If life were predictable it would cease to be life, and be without flavor." - Eleanor Roosevelt
36
37My List:
38** Item
39** Item
40
41```Anything between three backticks will be rendered as code.```
42````
43
44### Free Option
45
46There are probably numerous websites that allow you to create your personal
47Gemini capsule, but I'm going to focus on the two sites that I have personally
48tested. The first option below, Midnight Pub, allows you to create/edit any
49Gemini files you want in your account. This is essentially a GUI option with a
50built-in text box for editing. The second option below, Sourcehut, allows you to
51use a Git repository and automatic build process to deploy your personal Gemini
52capsule every time you push a commit.
53
54## Midnight Pub - Beginner Friendly
55
56[Midnight Pub](https://midnight.pub/) is a small, virtual community meant to
57reflect the atmosphere of wandering into a small alley pub. The site is built in
58Gemtext and has a server-side process to convert Gemtext to HTML if someone
59loads the site in an HTTP(S) browser.
60
61To create an account, you'll need to email the owner of the website to obtain a
62key. You can find their email on the Midnight Pub homepage. Once registered,
63head to [your account](https://midnight.pub/account) and select [manage
64site](https://midnight.pub/site). This is the screen where you can upload or
65create any files to be displayed on the internet.
66
67For example, I've created both an HTML file and a Gemini file. Remember that
68Gemini is automatically converted to HTML on the Pub, so you don't need an HTML
69version. For example, I created an HTML version to add in some extra styling.
70
71All you need to do is create a page like `index.gmi` and use your Gemini browser
72to head over to your-username.midnight.pub to see the result.
73
74That's all there is to it! Easy enough, right? Let's check out a more advanced
75version in the next section.
76
77# Paid Option
78
79As of 2021, Sourcehut has decided to require users to have a paid account in
80order to utilize their automated build system. For now, paid accounts can be as
81low as $2/month.
82
83## Sourcehut
84
85[Sourcehut](https://sourcehut.org/) is a collection of software development
86tools, but mostly surrounds their hosted Git repository service. Simply put,
87it's a minimal and more private alternative to services like GitHub.
88
89This walkthrough is more advanced and involves things like Git, SSH, the command
90line. If you don't think you know enough to do this, check out my walkthrough on
91creating a Gemini capsule for the Midnight Pub instead.
92
93The first thing you'll need to do is create an SSH key pair, if you don't
94already have one on your system. Once created, grab the contents of `id_rsa.pub`
95and add it to your Sourcehut account settings - this will allow you to push and
96pull code changes without using a username/password.
97
98```sh
99ssh keygen
100```
101
102Next up, let's create a repository with the proper name so that the Sourcehut
103build system will know we want them to host a website for us. Use the following
104format exactly:
105
106```sh
107mkdir your-username.srht.site && cd your-username.srht.site
108```
109
110Now that we've created the repo, let's initialize Git and add the proper remote
111URL.
112
113```sh
114git init
115```
116
117```sh
118git remote add origin git@git.sr.ht:~your-username/your-username.srht.site
119```
120
121Now that our repository is set up and configured, we will need to create at
122least two files:
123
124- `index.gmi`
125- `.build.yml`
126
127For your `.build.yml` file, use the following content and be sure to update the
128`site` line with your username!
129
130```yaml
131image: alpine/latest
132oauth: pages.sr.ht/PAGES:RW
133environment:
134 site: your-username.srht.site
135tasks:
136 - package: |
137 cd $site
138 tar -cvz . > ../site.tar.gz
139 - upload: |
140 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
141```
142
143For the `index.gmi` file, put whatever you want in there and save it. You could
144even just copy and paste the Gemtext cheatsheet.
145
146If you want to serve both HTML and Gemini files from this repository, just add a
147second command to the `upload` section:
148
149```yaml
150- upload: |
151 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
152 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
153```
154
155Lastly, commit your changes and push them to the remote repo.
156
157```sh
158git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD
159```
160
161If you've successfully created the files with the proper format, you'll see the
162terminal print a message that lets you know where the automatic build is taking
163place. For example, here's what the terminal tells me:
164
165```sh
166remote: Build started:
167remote: https://builds.sr.ht/~user/job/689803 [.build.yml]
168```
169
170Now that you've properly built your Sourcehut page, you can browse to
171your-username.srht.site in a Gemini browser and view the final results.
content/blog/2021-03-28-gemini-capsule.org added +178
@@ -0,0 +1,178 @@
1#+date: <2021-03-28>
2#+title: Launching a Gemini Capsule
3#+description:
4
5
6* What is Gemini?
7
8[[https://gemini.circumlunar.space/][Gemini]] is an internet protocol
9introduced in June 2019 as an alternative to HTTP(S) or Gopher. In
10layman's terms, it's an alternative way to browse sites (called
11capsules) that requires a special browser. Since Gemini is not
12standardized as an internet standard, normal web browsers won't be able
13to load a Gemini capsule. Instead, you'll need to use
14[[https://gemini.circumlunar.space/clients.html][a Gemini-specific
15browser]].
16
17The content found within a Gemini page is called
18[[https://gemini.circumlunar.space/docs/cheatsheet.gmi][Gemtext]] and is
19/extremely/ basic (on purpose). Gemini only processes the text, no media
20content like images. However, you're able to style 3 levels of headings,
21regular text, links (which will display on their own line), quotes, and
22an unordered list.
23
24Here's a complete listing of valid Gemtext:
25
26#+begin_src txt
27# Heading 1
28## Heading 2
29### Heading 3
30
31Regular text! Lorem ipsum dolor sit amet.
32
33=> https://example.com My Website
34=> gemini://example.com My Gemini Capsule
35
36> "If life were predictable it would cease to be life, and be without flavor." - Eleanor Roosevelt
37
38My List:
39,** Item
40,** Item
41
42```Anything between three backticks will be rendered as code.```
43#+end_src
44
45*** Free Option
46
47There are probably numerous websites that allow you to create your
48personal Gemini capsule, but I'm going to focus on the two sites that I
49have personally tested. The first option below, Midnight Pub, allows you
50to create/edit any Gemini files you want in your account. This is
51essentially a GUI option with a built-in text box for editing. The
52second option below, Sourcehut, allows you to use a Git repository and
53automatic build process to deploy your personal Gemini capsule every
54time you push a commit.
55
56** Midnight Pub - Beginner Friendly
57
58[[https://midnight.pub/][Midnight Pub]] is a small, virtual community
59meant to reflect the atmosphere of wandering into a small alley pub. The
60site is built in Gemtext and has a server-side process to convert
61Gemtext to HTML if someone loads the site in an HTTP(S) browser.
62
63To create an account, you'll need to email the owner of the website to
64obtain a key. You can find their email on the Midnight Pub homepage.
65Once registered, head to [[https://midnight.pub/account][your account]]
66and select [[https://midnight.pub/site][manage site]]. This is the
67screen where you can upload or create any files to be displayed on the
68internet.
69
70For example, I've created both an HTML file and a Gemini file. Remember
71that Gemini is automatically converted to HTML on the Pub, so you don't
72need an HTML version. For example, I created an HTML version to add in
73some extra styling.
74
75All you need to do is create a page like =index.gmi= and use your Gemini
76browser to head over to your-username.midnight.pub to see the result.
77
78That's all there is to it! Easy enough, right? Let's check out a more
79advanced version in the next section.
80
81* Paid Option
82
83As of 2021, Sourcehut has decided to require users to have a paid
84account in order to utilize their automated build system. For now, paid
85accounts can be as low as $2/month.
86
87** Sourcehut
88
89[[https://sourcehut.org/][Sourcehut]] is a collection of software
90development tools, but mostly surrounds their hosted Git repository
91service. Simply put, it's a minimal and more private alternative to
92services like GitHub.
93
94This walkthrough is more advanced and involves things like Git, SSH, the
95command line. If you don't think you know enough to do this, check out
96my walkthrough on creating a Gemini capsule for the Midnight Pub
97instead.
98
99The first thing you'll need to do is create an SSH key pair, if you
100don't already have one on your system. Once created, grab the contents
101of =id_rsa.pub= and add it to your Sourcehut account settings - this
102will allow you to push and pull code changes without using a
103username/password.
104
105#+begin_src sh
106ssh keygen
107#+end_src
108
109Next up, let's create a repository with the proper name so that the
110Sourcehut build system will know we want them to host a website for us.
111Use the following format exactly:
112
113#+begin_src sh
114mkdir your-username.srht.site && cd your-username.srht.site
115#+end_src
116
117Now that we've created the repo, let's initialize Git and add the proper
118remote URL.
119
120#+begin_src sh
121git init
122#+end_src
123
124#+begin_src sh
125git remote add origin git@git.sr.ht:~your-username/your-username.srht.site
126#+end_src
127
128Now that our repository is set up and configured, we will need to create
129at least two files:
130
131- =index.gmi=
132- =.build.yml=
133
134For your =.build.yml= file, use the following content and be sure to
135update the =site= line with your username!
136
137#+begin_src yaml
138image: alpine/latest
139oauth: pages.sr.ht/PAGES:RW
140environment:
141 site: your-username.srht.site
142tasks:
143 - package: |
144 cd $site
145 tar -cvz . > ../site.tar.gz
146 - upload: |
147 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
148#+end_src
149
150For the =index.gmi= file, put whatever you want in there and save it.
151You could even just copy and paste the Gemtext cheatsheet.
152
153If you want to serve both HTML and Gemini files from this repository,
154just add a second command to the =upload= section:
155
156#+begin_src yaml
157- upload: |
158 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
159 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
160#+end_src
161
162Lastly, commit your changes and push them to the remote repo.
163
164#+begin_src sh
165git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD
166#+end_src
167
168If you've successfully created the files with the proper format, you'll
169see the terminal print a message that lets you know where the automatic
170build is taking place. For example, here's what the terminal tells me:
171
172#+begin_src sh
173remote: Build started:
174remote: https://builds.sr.ht/~user/job/689803 [.build.yml]
175#+end_src
176
177Now that you've properly built your Sourcehut page, you can browse to
178your-username.srht.site in a Gemini browser and view the final results.
content/blog/2021-03-28-vaporwave-vs-outrun.md deleted −126
@@ -1,126 +0,0 @@
1+++
2date = 2021-03-28
3title = "Vaporware vs. Outrun"
4description = ""
5draft = false
6+++
7
8# Overview
9
10When it comes to an aesthetic that originated primarily online, there tends to
11be a lot of confusion around what falls into each specific genre. This post
12discusses Vaporwave and Outrun, which have become almost synonymous in the minds
13of those online who aren't hardcore into these genres of media. More
14specifically, Outrun is largely an unknown term while all aesthetics in these
15two genres are usually attributed to Vaporwave. For example, take a look at the
16image search results for Vaporwave: the results include **a lot** of
17Outrun-themed images. You'll find a similar trend almost everywhere.
18
19![Vaporwave Search
20Results](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png)
21
22# Vaporwave
23
24![Macintosh Plus Album
25Cover](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png)
26
27Okay, so what is Vaporwave? I'm going to just copy-and-paste some general info
28from the Wikipedia article on
29[Vaporwave](https://en.wikipedia.org/wiki/Vaporwave), so that I'm not repeating
30everything you can already search for online:
31
32> Vaporwave is a microgenre of electronic music, a visual art style, and an
33> Internet meme that emerged in the early 2010s. It is defined partly by its
34> slowed-down, chopped and screwed samples of smooth jazz, elevator, R&B, and
35> lounge music from the 1980s and 1990s. The surrounding subculture is sometimes
36> associated with an ambiguous or satirical take on consumer capitalism and pop
37> culture, and tends to be characterized by a nostalgic or surrealist engagement
38> with the popular entertainment, technology and advertising of previous
39> decades. Visually, it incorporates early Internet imagery, late 1990s web
40> design, glitch art, anime, 3D-rendered objects, and cyberpunk tropes in its
41> cover artwork and music videos.
42
43This is an excellent summary, and it helps address my point here: there are
44specific aspects that make Vaporwave unique:
45
46## Time Frame
47
48The time frame for references, logos, etc. focuses mostly on the 1990s in
49Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens, tropical
50plants, classic marble sculptures, and many references from Japan's influence in
51the 90s.
52
53## Art
54
55The color scheme is generally a soft, light palette that uses pastel colors. The
56backdrop will often be in a light-pink or teal blue.
57
58## Music
59
60The musical genre of Vaporwave incorporates soft tunes, lounge music, and sound
61effects that will make the listener reminisce of the 90s. The sounds of the
62genre are generally slower-paced and calming. The major breakthrough artist for
63Vaporwave was [Macintosh Plus](https://en.wikipedia.org/wiki/Vektroid), who
64released the album [Floral
65Shoppe](https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete) in
662011. Another more recent example is the artist
67[sadbert](https://sadbert.bandcamp.com/), whose latest album incorporates the
68upbeat tones of the 1999 Dilbert TV series.
69
70Notice that Vaporwave doesn't include things like racing cars, futuristic
71technology, chrome, or the deep orange/purple color scheme. Vaporwave is a focus
72on the idyllic state of the world when technology was becoming common in
73households, a reality that we have already experienced. Focus on the most
74aesthetically-pleasing parts of that past is a large part of Vaporwave.
75
76# Outrun
77
78![Outrun](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png)
79
80Now, let's get to Outrun. This one is a little trickier since the genre has
81largely been lumped under the Vaporwave title for so long. However, it stems
82from the [Synthwave](https://en.wikipedia.org/wiki/Synthwave) music genre and is
83likely named after the 1986 racer game, [Out
84Run](https://en.wikipedia.org/wiki/Out_Run).
85
86## Time Frame
87
88Outrun can be thought of as a retro-futuristic aesthetic born from the 1980s.
89
90## Art
91
92The color scheme uses a very dark color palette with the main colors being deep
93oranges, blues, and purples. Red edges are common around objects in Outrun art.
94The background of the Outrun aesthetic is almost always a neon grid like you'd
95expect to see in Tron or a 1980s arcade machine.
96
97Classic sports cars, chrome robots, computer generated graphics and fonts, and
98the occasional use of rain or palm trees can be found in Outrun art.
99
100## Music
101
102This aesthetic has a more aggressive and fast-paced style of music, which tends
103to match the subject of the art in this aesthetic.
104
105Outrun enthusiasts love what people in the 1980s thought the future would look
106like. Take a look at a common video game discussed in Outrun circles, Far Cry 3:
107Blood Dragon:
108
109![Fry Cry 3: Blood
110Dragon](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png)
111
112Another example that doesn't force the color scheme as hard as some online art
113does is Kung Fury:
114
115![Kung
116Fury](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png)
117
118![Kung Fury Hacker
119Scene](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png)
120
121# Conclusion
122
123While Vaporwave and Outrun share similarities, they are two distinct aesthetics
124with many important distinctions. Someone who enjoys one may not necessarily
125enjoy the other, so it's important to make sure we properly describe the
126aesthetic we're looking for.
content/blog/2021-03-28-vaporwave-vs-outrun.org added +134
@@ -0,0 +1,134 @@
1#+date: <2021-03-28>
2#+title: Vaporware vs. Outrun
3#+description:
4
5
6* Overview
7
8When it comes to an aesthetic that originated primarily online, there
9tends to be a lot of confusion around what falls into each specific
10genre. This post discusses Vaporwave and Outrun, which have become
11almost synonymous in the minds of those online who aren't hardcore into
12these genres of media. More specifically, Outrun is largely an unknown
13term while all aesthetics in these two genres are usually attributed to
14Vaporwave. For example, take a look at the image search results for
15Vaporwave: the results include *a lot* of Outrun-themed images. You'll
16find a similar trend almost everywhere.
17
18#+caption: Vaporwave Search Results
19[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png]]
20
21* Vaporwave
22
23#+caption: Macintosh Plus Album Cover
24[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png]]
25
26Okay, so what is Vaporwave? I'm going to just copy-and-paste some
27general info from the Wikipedia article on
28[[https://en.wikipedia.org/wiki/Vaporwave][Vaporwave]], so that I'm not
29repeating everything you can already search for online:
30
31#+begin_quote
32Vaporwave is a microgenre of electronic music, a visual art style, and
33an Internet meme that emerged in the early 2010s. It is defined partly
34by its slowed-down, chopped and screwed samples of smooth jazz,
35elevator, R&B, and lounge music from the 1980s and 1990s. The
36surrounding subculture is sometimes associated with an ambiguous or
37satirical take on consumer capitalism and pop culture, and tends to be
38characterized by a nostalgic or surrealist engagement with the popular
39entertainment, technology and advertising of previous decades. Visually,
40it incorporates early Internet imagery, late 1990s web design, glitch
41art, anime, 3D-rendered objects, and cyberpunk tropes in its cover
42artwork and music videos.
43#+end_quote
44
45This is an excellent summary, and it helps address my point here: there
46are specific aspects that make Vaporwave unique:
47
48** Time Frame
49
50The time frame for references, logos, etc. focuses mostly on the 1990s
51in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens,
52tropical plants, classic marble sculptures, and many references from
53Japan's influence in the 90s.
54
55** Art
56
57The color scheme is generally a soft, light palette that uses pastel
58colors. The backdrop will often be in a light-pink or teal blue.
59
60** Music
61
62The musical genre of Vaporwave incorporates soft tunes, lounge music,
63and sound effects that will make the listener reminisce of the 90s. The
64sounds of the genre are generally slower-paced and calming. The major
65breakthrough artist for Vaporwave was
66[[https://en.wikipedia.org/wiki/Vektroid][Macintosh Plus]], who released
67the album
68[[https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete][Floral
69Shoppe]] in 2011. Another more recent example is the artist
70[[https://sadbert.bandcamp.com/][sadbert]], whose latest album
71incorporates the upbeat tones of the 1999 Dilbert TV series.
72
73Notice that Vaporwave doesn't include things like racing cars,
74futuristic technology, chrome, or the deep orange/purple color scheme.
75Vaporwave is a focus on the idyllic state of the world when technology
76was becoming common in households, a reality that we have already
77experienced. Focus on the most aesthetically-pleasing parts of that past
78is a large part of Vaporwave.
79
80* Outrun
81
82#+caption: Outrun
83[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png]]
84
85Now, let's get to Outrun. This one is a little trickier since the genre
86has largely been lumped under the Vaporwave title for so long. However,
87it stems from the [[https://en.wikipedia.org/wiki/Synthwave][Synthwave]]
88music genre and is likely named after the 1986 racer game,
89[[https://en.wikipedia.org/wiki/Out_Run][Out Run]].
90
91** Time Frame
92
93Outrun can be thought of as a retro-futuristic aesthetic born from the
941980s.
95
96** Art
97
98The color scheme uses a very dark color palette with the main colors
99being deep oranges, blues, and purples. Red edges are common around
100objects in Outrun art. The background of the Outrun aesthetic is almost
101always a neon grid like you'd expect to see in Tron or a 1980s arcade
102machine.
103
104Classic sports cars, chrome robots, computer generated graphics and
105fonts, and the occasional use of rain or palm trees can be found in
106Outrun art.
107
108** Music
109
110This aesthetic has a more aggressive and fast-paced style of music,
111which tends to match the subject of the art in this aesthetic.
112
113Outrun enthusiasts love what people in the 1980s thought the future
114would look like. Take a look at a common video game discussed in Outrun
115circles, Far Cry 3: Blood Dragon:
116
117#+caption: Fry Cry 3: Blood Dragon
118[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png]]
119
120Another example that doesn't force the color scheme as hard as some
121online art does is Kung Fury:
122
123#+caption: Kung Fury
124[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png]]
125
126#+caption: Kung Fury Hacker Scene
127[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png]]
128
129* Conclusion
130
131While Vaporwave and Outrun share similarities, they are two distinct
132aesthetics with many important distinctions. Someone who enjoys one may
133not necessarily enjoy the other, so it's important to make sure we
134properly describe the aesthetic we're looking for.
content/blog/2021-03-30-vps-web-server.md deleted −396
@@ -1,396 +0,0 @@
1+++
2date = 2021-03-30
3title = "How to Set Up a VPS Web Server"
4description = ""
5draft = false
6+++
7
8# Shared Hosting vs. VPS
9
10Choosing a place to host a website is one of the most confusing decisions for
11beginner web developers. Even for experienced web devs, choosing between
12different forms of web hosting can be a daunting choice.
13
14First, let's take a look at [shared web
15hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service). Shared web
16hosting is a product where you are purchasing a small piece of a web server that
17is being shared between many websites. As a result, the cost of shared hosting
18is extremely low. You won't have access to the server itself, which means you
19can't install your own software on the server, such as Docker. Usually, you are
20simply allowed to connect your domains to the server, set up domain security,
21and other small utilities.
22
23In contrast, a [virtual private
24server](https://en.wikipedia.org/wiki/Virtual_private_server) (VPS) is a virtual
25machine that replicates the environment of having a dedicated server to
26yourself. You are able to control the virtual server's host names, base file
27system, package manager, etc. Another great upside of a VPS is that since it's
28virtual, the company providing the VPS can dynamically increase the disk size,
29RAM size, or number of CPUs at any time. However, the virtual server is still
30physically located on a server that is shared between multiple virtual servers.
31
32The choice between shared hosting and VPS mostly depends on your skill level
33with system administration. If you're comforting working on a server that is
34mostly left up to you (or you're willing to learn), then a VPS is usually a
35better option. However, shared hosting is a fantastic option for people who
36don't want to have to learn how to manage their server.
37
38# My Situation
39
40I had used shared hosting for approximately 5 years before trying my first VPS.
41I manage a homelab and have had success running a server and performing typical
42sysadmin duties, but I was still hesitant to get a VPS. One fear was that I
43always struggled to properly set up the networking part of a server - DNS and
44hostname configurations were not my friend.
45
46As a little bit of background, I originally used
47[Siteground](https://www.siteground.com) for my initially shared hosting and
48stayed on that platform for at least a year. However, the UI was clunky, and I
49didn't like how they handled certain technical aspects, so I switched to
50[Namecheap](https://www.namecheap.com). Namecheap was great because it is the
51service I primarily use for purchasing domain names, which made it incredibly
52easy to link them to my hosting service. However, it was still mediocre shared
53hosting, and Namecheap is notorious for not letting you use [Let's
54Encrypt](https://letsencrypt.org) to obtain free SSL/TLS certificates; Namecheap
55wants to make you purchase certificates through their store.
56
57Finally, I settled down with [iWebFusion](https://www.iwebfusion.net) for about
58the last year of my shared hosting. This service was pretty great, came with
59free SSL/TLS, and I never had any complaints.
60
61However, I finally grew tired of not being able to install software on my own
62web server. I wanted to be able to try out things like
63[Postmill](https://postmill.xyz) or [Matrix](https://matrix.org). This is
64possible with a VPS, so I decided to grab a new domain name to try it out.
65
66# Getting Started: Buying a VPS
67
68The first step to moving over to a VPS is (you guessed it): finding a VPS
69provider. For my VPSs, I use [1984](https://1984hosting.com) and prefer their
70services much more than any alternative, due to their location (Iceland), their
71[privacy policy](https://1984hosting.com/GDPR/), their respect for GDPR, and the
72ability to remain anonymous if you pay in Bitcoin or Monero.
73
74[Njalla](https://njal.la) is another good, privacy-oriented option for VPS
75services.
76
77You'll have to decide what specifications you want on your VPS. For me, I only
78build and deploy low-resource HTML, PHP, and Python websites. This means I can
79survive on the smallest VPS: 1 CPU, 1GB of RAM, and 25GB SSD for $5.00 per
80month.
81
82As noted above, the great thing about a VPS is you can request your provider to
83increase the resources at any time.
84
85# Configuring DNS Settings
86
87Okay, so now let's get into some actual work that has to be done to get content
88moved from a shared host to a VPS. At this point, I'm assuming you have a shared
89host with website content that you can still access, and you've purchased a new
90VPS and can SSH into that server.
91
92The first change is minor, but it should be done immediately in order to get
93things moving: DNS settings. Go to wherever your DNS settings are handled. If
94your shared host also managed your DNS settings, you'll need to first move that
95DNS over to your new VPS provider. For me, I route my DNS through
96[Gandi](https://www.gandi.net).
97
98Once you know where your DNS settings are, go ahead and update the `A` records
99to match the public IP address of your VPS. For example:
100
101```txt
102A example.com xxx.xxx.xxx.xxx
103A subdomain xxx.xxx.xxx.xxx
104CNAME www example.com.
105```
106
107If you have any other records that require updates, such as MX or TXT records
108for a mail server, be sure to update those accordingly. Personally, I don't host
109my own mail server. I route all mail on my custom domains to
110[Migadu](https://www.migadu.com). Hosting your own email server can become
111complex quickly and is not for beginners.
112
113DNS changes can take up to 48 hours to propagate, so be sure to give it some
114time before assuming you've made an error.
115
116# Server Updates and Packages
117
118Now that the DNS settings have been changed, let's set up our server while we
119wait for the DNS to propagate. First up is to ssh into your server. If you've
120signed up with a service like DigitalOcean, you can add your SSH key to your
121account and to your VPS droplet so that you don't need a password in order to
122SSH.
123
124```sh
125ssh root@xxx.xxx.xxx.xxx
126```
127
128The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache web
129server. If you're working on a different operating system (OS) or want a
130different web server, such as Nginx, you'll have to use different commands to
131set it up.
132
133First, let's update and upgrade our server.
134
135**NOTE:** Since we have logged in to the server as `root` for now, we don't need
136to use the `sudo` modifier before our commands.
137
138```sh
139apt update && apt upgrade -y
140```
141
142# Create A User Account
143
144While being able to use `root` can be beneficial at times, you shouldn't use
145`root` unless you have to.
146
147So let's set up a new user in our system. The `-m` option below tells the OS to
148create a home directory for the new user.
149
150```sh
151adduser USERNAME
152```
153
154Now, create a password for that user.
155
156```sh
157passwd USERNAME
158```
159
160Finally, add the user to the sudoers file, so they can perform priveleged
161commands.
162
163```sh
164usermod -a -G sudo USERNAME
165```
166
167If you are using SSH keys and not passwords, you'll need to copy your SSH key
168from your local machine to the VPS. If you haven't disabled password-based SSH
169yet, the easiest way to do this is `ssh-copy-id` from your local computer (not
170from the VPS):
171
172```sh
173ssh-copy-id testuser@xxx.xxx.xxx.xxx
174```
175
176If you've disabled password-based SSH, you'll need to manually copy your SSH key
177into the `~/.ssh/authorized_keys` file.
178
179# Install Software
180
181Our goal here is to host a web server, so the next step is to install the Apache
182web server and any other packages we need.
183
184From this point on, I will be logged in as a user (not `root`) and will need to
185use the `sudo` modifier for most commands.
186
187```sh
188sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
189sudo apt install apache2
190```
191
192If you need other language support, such as PHP, you'll need to install that
193too.
194
195```sh
196sudo apt install libapache2-mod-php php-dom
197sudo a2enmod php
198sudo systemctl restart apache2
199```
200
201# Website Files & Folders
202
203Next up is to create the directories for the domain(s) we want to be hosted on
204this web server.
205
206```sh
207cd /var/www
208sudo mkdir example.com
209```
210
211We have a folder for `example.com` now, so let's add an `index.html` file and
212put it within a specific `public_html` folder. You don't need this `public_html`
213if you don't want it, but it helps with organizing items related to
214`example.com` that you don't want to publish to the internet.
215
216```sh
217cd example.com
218sudo mkdir public_html && cd public_html
219sudo nano index.html
220```
221
222You can put anything you want in this `index.html` file. If you can't think of
223anything, paste this in there:
224
225```html
226<!DOCTYPE html>
227<html lang="en">
228 <head>
229 <meta charset="utf-8" />
230 <meta name="viewport" content="width=device-width, initial-scale=1" />
231 <title>Hello, world!</title>
232 </head>
233 <body>
234 <h1>Hello, world!</h1>
235 </body>
236</html>
237```
238
239If you want something to be served at `example.com/page01/file.txt`, you'll have
240to create the `page01` directory under the `example.com` directory. For example:
241
242```sh
243cd /var/www/example.com/public_html
244sudo mkdir page01
245sudo nano file.txt
246```
247
248# Apache Configuration
249
250Now, let's set up the files that will tell the server where to find the files
251for `example.com`. We will copy the default configuration file and create our
252own.
253
254```sh
255cd /etc/apache2/sites-available
256sudo cp 000-default.conf example.com.conf
257sudo nano example.com.conf
258```
259
260This configuration file will have a few default lines, but you'll need to edit
261it to look similar to this (settings may change based on your personal needs):
262
263```config
264<VirtualHost *:80>
265 ServerAdmin your-email@email-provider.com
266 ServerName example.com
267 ServerAlias www.example.com
268 DocumentRoot /var/www/example.com/public_html
269 ErrorLog ${APACHE_LOG_DIR}/error.log
270 CustomLog ${APACHE_LOG_DIR}/access.log combined
271</VirtualHost>
272```
273
274Now, enable the configuration for your new site, disable the default
275configuration, and reload the web server.
276
277```sh
278sudo a2ensite example.com.conf
279sudo a2dissite 000-default.conf
280sudo systemctl reload apache2
281```
282
283You can always run a test to make sure no errors or warnings are found in your
284configuration files.
285
286```sh
287sudo apache2ctl configtest
288```
289
290Now, restart the web server entirely. After this, you should be able to browse
291to `http://example.com` and see the HTML content you provided earlier. Note that
292SSL/TLS has not been enabled yet, so you won't be able to use the secure version
293yet (`https://example.com`).
294
295```sh
296sudo systemctl restart apache2
297```
298
299You can repeat this for as many websites as you need. Just create the domain
300folders in `/var/www/`, add the configuration file, enable the configuration,
301and restart `apache2`.
302
303# SSL/TLS Certificates: Serve Websites Over HTTPS
304
305In order to serve secure content, you'll need to obtain SSL/TLS certificates.
306Luckily, there's a free tool called [Certbot](https://certbot.eff.org) that
307helps us with the process.
308
309The first step is to install `snapd` and `core` for Ubuntu.
310
311```sh
312sudo apt install snapd
313sudo snap install core
314sudo snap refresh core
315```
316
317Next, install the `certbot` snap package.
318
319```sh
320sudo snap install --classic certbot
321```
322
323Execute the following command to ensure that the `certbot` command can be run.
324
325```sh
326sudo ln -s /snap/bin/certbot /usr/bin/certbot
327```
328
329Finally, you can run `certbot` one of two ways:
330
3311. run it and let it alter your Apache configuration files automatically to
332 enable HTTPS redirects.
3332. run it and only allow it to create certificates. You'll need to manually
334 alter the config files to enable HTTPS redirects.
335
336Run certbot and allow automatic config changes:
337
338```sh
339sudo certbot --apache
340```
341
342Run certbot for certificates only and don't allow it to alter config files:
343
344```sh
345sudo certbot certonly --apache
346```
347
348The Certbot packages on your system come with a cron job or systemd timer that
349will renew your certificates automatically before they expire. You will not need
350to run Certbot again unless you change your configuration. You can test
351automatic renewal for your certificates by running this command:
352
353```sh
354sudo certbot renew --dry-run
355```
356
357Now, test your domains by going to `https://example.com`.
358
359# Firewall Security
360
361To enable better security on your server, you'll need to enable a basic
362firewall. For Ubuntu, we'll use [the uncomplicated
363firewall](../secure-your-network-with-the-uncomplicated-firewall/).
364
365Now, add the following rules to the firewall allow SSH, Apache, and HTTP(S)
366connections. If you need to, you can enable different ports for specifics
367applications, SFTP, etc.
368
369```sh
370sudo ufw default deny incoming
371sudo ufw default allow outgoing
372sudo ufw allow OpenSSH
373sudo ufw allow Apache
374sudo ufw allow proto tcp from any to any port 80,443
375```
376
377Once you've added all the rules you need, enable the firewall.
378
379```sh
380sudo ufw enable
381```
382
383# Troubleshooting
384
385If you run into any issues during your VPS set-up, be sure to walk back through
386your actions and make sure you didn't miss any steps.
387
388Many websites have fantastic guides to setting up various web servers. This is
389one of the areas [where DigitalOcean
390shines](https://www.digitalocean.com/community/tutorials). For simpler or
391more Linux-oriented questions, I suggest using [Linuxize]
392(<https://linuxize.com>).
393
394If you're getting certain errors (e.g. `500 Internal Server Error`) and need to
395debug locally, you can view the `access.log` and `error.log` files in the
396`/var/log/apache/` directory.
content/blog/2021-03-30-vps-web-server.org added +411
@@ -0,0 +1,411 @@
1#+date: <2021-03-30>
2#+title: How to Set Up a VPS Web Server
3#+description:
4
5
6* Shared Hosting vs. VPS
7
8Choosing a place to host a website is one of the most confusing
9decisions for beginner web developers. Even for experienced web devs,
10choosing between different forms of web hosting can be a daunting
11choice.
12
13First, let's take a look at
14[[https://en.wikipedia.org/wiki/Shared_web_hosting_service][shared web
15hosting]]. Shared web hosting is a product where you are purchasing a
16small piece of a web server that is being shared between many websites.
17As a result, the cost of shared hosting is extremely low. You won't have
18access to the server itself, which means you can't install your own
19software on the server, such as Docker. Usually, you are simply allowed
20to connect your domains to the server, set up domain security, and other
21small utilities.
22
23In contrast, a
24[[https://en.wikipedia.org/wiki/Virtual_private_server][virtual private
25server]] (VPS) is a virtual machine that replicates the environment of
26having a dedicated server to yourself. You are able to control the
27virtual server's host names, base file system, package manager, etc.
28Another great upside of a VPS is that since it's virtual, the company
29providing the VPS can dynamically increase the disk size, RAM size, or
30number of CPUs at any time. However, the virtual server is still
31physically located on a server that is shared between multiple virtual
32servers.
33
34The choice between shared hosting and VPS mostly depends on your skill
35level with system administration. If you're comforting working on a
36server that is mostly left up to you (or you're willing to learn), then
37a 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
39server.
40
41* My Situation
42
43I 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
45performing typical sysadmin duties, but I was still hesitant to get a
46VPS. One fear was that I always struggled to properly set up the
47networking part of a server - DNS and hostname configurations were not
48my friend.
49
50As a little bit of background, I originally used
51[[https://www.siteground.com][Siteground]] for my initially shared
52hosting and stayed on that platform for at least a year. However, the UI
53was clunky, and I didn't like how they handled certain technical
54aspects, so I switched to [[https://www.namecheap.com][Namecheap]].
55Namecheap was great because it is the service I primarily use for
56purchasing domain names, which made it incredibly easy to link them to
57my hosting service. However, it was still mediocre shared hosting, and
58Namecheap is notorious for not letting you use
59[[https://letsencrypt.org][Let's Encrypt]] to obtain free SSL/TLS
60certificates; Namecheap wants to make you purchase certificates through
61their store.
62
63Finally, I settled down with [[https://www.iwebfusion.net][iWebFusion]]
64for about the last year of my shared hosting. This service was pretty
65great, came with free SSL/TLS, and I never had any complaints.
66
67However, I finally grew tired of not being able to install software on
68my own web server. I wanted to be able to try out things like
69[[https://postmill.xyz][Postmill]] or [[https://matrix.org][Matrix]].
70This is possible with a VPS, so I decided to grab a new domain name to
71try it out.
72
73* Getting Started: Buying a VPS
74
75The first step to moving over to a VPS is (you guessed it): finding a
76VPS provider. For my VPSs, I use [[https://1984hosting.com][1984]] and
77prefer their services much more than any alternative, due to their
78location (Iceland), their [[https://1984hosting.com/GDPR/][privacy
79policy]], their respect for GDPR, and the ability to remain anonymous if
80you pay in Bitcoin or Monero.
81
82[[https://njal.la][Njalla]] is another good, privacy-oriented option for
83VPS services.
84
85You'll have to decide what specifications you want on your VPS. For me,
86I only build and deploy low-resource HTML, PHP, and Python websites.
87This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and
8825GB SSD for $5.00 per month.
89
90As noted above, the great thing about a VPS is you can request your
91provider to increase the resources at any time.
92
93* Configuring DNS Settings
94
95Okay, so now let's get into some actual work that has to be done to get
96content moved from a shared host to a VPS. At this point, I'm assuming
97you have a shared host with website content that you can still access,
98and you've purchased a new VPS and can SSH into that server.
99
100The first change is minor, but it should be done immediately in order to
101get things moving: DNS settings. Go to wherever your DNS settings are
102handled. If your shared host also managed your DNS settings, you'll need
103to first move that DNS over to your new VPS provider. For me, I route my
104DNS through [[https://www.gandi.net][Gandi]].
105
106Once you know where your DNS settings are, go ahead and update the =A=
107records to match the public IP address of your VPS. For example:
108
109#+begin_src txt
110A example.com xxx.xxx.xxx.xxx
111A subdomain xxx.xxx.xxx.xxx
112CNAME www example.com.
113#+end_src
114
115If you have any other records that require updates, such as MX or TXT
116records 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
118custom domains to [[https://www.migadu.com][Migadu]]. Hosting your own
119email server can become complex quickly and is not for beginners.
120
121DNS changes can take up to 48 hours to propagate, so be sure to give it
122some time before assuming you've made an error.
123
124* Server Updates and Packages
125
126Now 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
128server. 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
130don't need a password in order to SSH.
131
132#+begin_src sh
133ssh root@xxx.xxx.xxx.xxx
134#+end_src
135
136The 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
138want a different web server, such as Nginx, you'll have to use different
139commands to set it up.
140
141First, let's update and upgrade our server.
142
143*NOTE:* Since we have logged in to the server as =root= for now, we
144don't need to use the =sudo= modifier before our commands.
145
146#+begin_src sh
147apt update && apt upgrade -y
148#+end_src
149
150* Create A User Account
151
152While being able to use =root= can be beneficial at times, you shouldn't
153use =root= unless you have to.
154
155So let's set up a new user in our system. The =-m= option below tells
156the OS to create a home directory for the new user.
157
158#+begin_src sh
159adduser USERNAME
160#+end_src
161
162Now, create a password for that user.
163
164#+begin_src sh
165passwd USERNAME
166#+end_src
167
168Finally, add the user to the sudoers file, so they can perform
169priveleged commands.
170
171#+begin_src sh
172usermod -a -G sudo USERNAME
173#+end_src
174
175If you are using SSH keys and not passwords, you'll need to copy your
176SSH key from your local machine to the VPS. If you haven't disabled
177password-based SSH yet, the easiest way to do this is =ssh-copy-id= from
178your local computer (not from the VPS):
179
180#+begin_src sh
181ssh-copy-id testuser@xxx.xxx.xxx.xxx
182#+end_src
183
184If you've disabled password-based SSH, you'll need to manually copy your
185SSH key into the =~/.ssh/authorized_keys= file.
186
187* Install Software
188
189Our goal here is to host a web server, so the next step is to install
190the Apache web server and any other packages we need.
191
192From this point on, I will be logged in as a user (not =root=) and will
193need to use the =sudo= modifier for most commands.
194
195#+begin_src sh
196sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
197sudo apt install apache2
198#+end_src
199
200If you need other language support, such as PHP, you'll need to install
201that too.
202
203#+begin_src sh
204sudo apt install libapache2-mod-php php-dom
205sudo a2enmod php
206sudo systemctl restart apache2
207#+end_src
208
209* Website Files & Folders
210
211Next up is to create the directories for the domain(s) we want to be
212hosted on this web server.
213
214#+begin_src sh
215cd /var/www
216sudo mkdir example.com
217#+end_src
218
219We have a folder for =example.com= now, so let's add an =index.html=
220file and put it within a specific =public_html= folder. You don't need
221this =public_html= if you don't want it, but it helps with organizing
222items related to =example.com= that you don't want to publish to the
223internet.
224
225#+begin_src sh
226cd example.com
227sudo mkdir public_html && cd public_html
228sudo nano index.html
229#+end_src
230
231You can put anything you want in this =index.html= file. If you can't
232think of anything, paste this in there:
233
234#+begin_src html
235<!DOCTYPE html>
236<html lang="en">
237 <head>
238 <meta charset="utf-8" />
239 <meta name="viewport" content="width=device-width, initial-scale=1" />
240 <title>Hello, world!</title>
241 </head>
242 <body>
243 <h1>Hello, world!</h1>
244 </body>
245</html>
246#+end_src
247
248If you want something to be served at =example.com/page01/file.txt=,
249you'll have to create the =page01= directory under the =example.com=
250directory. For example:
251
252#+begin_src sh
253cd /var/www/example.com/public_html
254sudo mkdir page01
255sudo nano file.txt
256#+end_src
257
258* Apache Configuration
259
260Now, let's set up the files that will tell the server where to find the
261files for =example.com=. We will copy the default configuration file and
262create our own.
263
264#+begin_src sh
265cd /etc/apache2/sites-available
266sudo cp 000-default.conf example.com.conf
267sudo nano example.com.conf
268#+end_src
269
270This configuration file will have a few default lines, but you'll need
271to edit it to look similar to this (settings may change based on your
272personal needs):
273
274#+begin_src config
275<VirtualHost *:80>
276 ServerAdmin your-email@email-provider.com
277 ServerName example.com
278 ServerAlias www.example.com
279 DocumentRoot /var/www/example.com/public_html
280 ErrorLog ${APACHE_LOG_DIR}/error.log
281 CustomLog ${APACHE_LOG_DIR}/access.log combined
282</VirtualHost>
283#+end_src
284
285Now, enable the configuration for your new site, disable the default
286configuration, and reload the web server.
287
288#+begin_src sh
289sudo a2ensite example.com.conf
290sudo a2dissite 000-default.conf
291sudo systemctl reload apache2
292#+end_src
293
294You can always run a test to make sure no errors or warnings are found
295in your configuration files.
296
297#+begin_src sh
298sudo apache2ctl configtest
299#+end_src
300
301Now, restart the web server entirely. After this, you should be able to
302browse to =http://example.com= and see the HTML content you provided
303earlier. Note that SSL/TLS has not been enabled yet, so you won't be
304able to use the secure version yet (=https://example.com=).
305
306#+begin_src sh
307sudo systemctl restart apache2
308#+end_src
309
310You can repeat this for as many websites as you need. Just create the
311domain folders in =/var/www/=, add the configuration file, enable the
312configuration, and restart =apache2=.
313
314* SSL/TLS Certificates: Serve Websites Over HTTPS
315
316In order to serve secure content, you'll need to obtain SSL/TLS
317certificates. Luckily, there's a free tool called
318[[https://certbot.eff.org][Certbot]] that helps us with the process.
319
320The first step is to install =snapd= and =core= for Ubuntu.
321
322#+begin_src sh
323sudo apt install snapd
324sudo snap install core
325sudo snap refresh core
326#+end_src
327
328Next, install the =certbot= snap package.
329
330#+begin_src sh
331sudo snap install --classic certbot
332#+end_src
333
334Execute the following command to ensure that the =certbot= command can
335be run.
336
337#+begin_src sh
338sudo ln -s /snap/bin/certbot /usr/bin/certbot
339#+end_src
340
341Finally, you can run =certbot= one of two ways:
342
3431. run it and let it alter your Apache configuration files automatically
344 to enable HTTPS redirects.
3452. run it and only allow it to create certificates. You'll need to
346 manually alter the config files to enable HTTPS redirects.
347
348Run certbot and allow automatic config changes:
349
350#+begin_src sh
351sudo certbot --apache
352#+end_src
353
354Run certbot for certificates only and don't allow it to alter config
355files:
356
357#+begin_src sh
358sudo certbot certonly --apache
359#+end_src
360
361The Certbot packages on your system come with a cron job or systemd
362timer that will renew your certificates automatically before they
363expire. You will not need to run Certbot again unless you change your
364configuration. You can test automatic renewal for your certificates by
365running this command:
366
367#+begin_src sh
368sudo certbot renew --dry-run
369#+end_src
370
371Now, test your domains by going to =https://example.com=.
372
373* Firewall Security
374
375To enable better security on your server, you'll need to enable a basic
376firewall. For Ubuntu, we'll use
377[[../secure-your-network-with-the-uncomplicated-firewall/][the
378uncomplicated firewall]].
379
380Now, add the following rules to the firewall allow SSH, Apache, and
381HTTP(S) connections. If you need to, you can enable different ports for
382specifics applications, SFTP, etc.
383
384#+begin_src sh
385sudo ufw default deny incoming
386sudo ufw default allow outgoing
387sudo ufw allow OpenSSH
388sudo ufw allow Apache
389sudo ufw allow proto tcp from any to any port 80,443
390#+end_src
391
392Once you've added all the rules you need, enable the firewall.
393
394#+begin_src sh
395sudo ufw enable
396#+end_src
397
398* Troubleshooting
399
400If you run into any issues during your VPS set-up, be sure to walk back
401through your actions and make sure you didn't miss any steps.
402
403Many websites have fantastic guides to setting up various web servers.
404This is one of the areas
405[[https://www.digitalocean.com/community/tutorials][where DigitalOcean
406shines]]. For simpler or more Linux-oriented questions, I suggest using
407[Linuxize] ([[https://linuxize.com]]).
408
409If you're getting certain errors (e.g. =500 Internal Server Error=) and
410need to debug locally, you can view the =access.log= and =error.log=
411files in the =/var/log/apache/= directory.
content/blog/2021-04-17-gemini-server.md deleted −150
@@ -1,150 +0,0 @@
1+++
2date = 2021-04-17
3title = "Hosting a Gemini Server"
4description = ""
5draft = false
6+++
7
8# Similar Article Available
9
10To read more about Gemini and ways to test out this new protocol without your
11own server, see my previous post [Launching a Gemini
12Capsule](../launching-a-gemini-capsule/).
13
14# Preparation
15
16This guide assumes you have access to a server accessible to the world through a
17public IP address and that you own a domain name used for this Gemini capsule.
18
19# Getting Started with Agate
20
21We are going to use [Agate](https://github.com/mbrubeck/agate) for this
22tutorial. This is a basic Gemini server written in Rust. It takes very little
23time and maintenance to get it running.
24
25# Install Dependencies
26
27First, you will need to install the Rust package for your system. On Ubuntu, use
28the following commands (remember to use `sudo` if you are not the root user).
29The Rust installation will give you options to customize the installation; I
30used the default installation options.
31
32```sh
33sudo apt update && sudo apt upgrade -y
34curl https://sh.rustup.rs -sSf | sh
35```
36
37Remember to configure your shell with the new configuration:
38
39```sh
40source $HOME/.cargo/env
41```
42
43Before we install agate, make sure you have the `gcc` package installed:
44
45```sh
46sudo apt install gcc
47```
48
49Next, you'll need to install the agate executable with Rust's Cargo package
50maintainer:
51
52```sh
53cargo install agate
54```
55
56# Create Symlinks
57
58Once Cargo has finished installing all the required packages, symlink the
59executable to your $PATH.
60
61```sh
62sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate
63```
64
65# Using Agate's Built-In Installation Tool
66
67If you're running Ubuntu or Debian, use the Debian installation script found in
68Agate's GitHub repository, under the `tools/debian` folder.
69
70```sh
71git clone https://github.com/mbrubeck/agate
72cd agate/tools/debian
73sudo ./install.sh
74```
75
76# Configure the Gemini Service
77
78We have a little more to do, but since this script tries to immediately run the
79service, it will likely fail with an exit code. Let's add our finishing touches.
80Edit the following file and replace the hostname with your desired URL. You can
81also change the directory where content will be served.
82
83```sh
84sudo nano /etc/systemd/system/gemini.service
85```
86
87```sh
88# Edit these lines to whatever you want - see the next code block for my personal configuration.
89WorkingDirectory=/srv/gemini
90ExecStart=agate --hostname $(uname -n) --lang en
91```
92
93This is my personal config:
94
95```sh
96WorkingDirectory=/var/gemini/
97ExecStart=agate --hostname gemini.example.com --lang en
98```
99
100Since we've altered the systemd configuration files, we have to reload the
101daemon. Let's do that, restart our service, and check its status.
102
103```sh
104sudo systemctl daemon-reload
105sudo systemctl restart gemini.service
106sudo systemctl status gemini.service
107```
108
109# Fixing Systemd Errors
110
111If you're still getting errors, the installation process may not have properly
112enabled the gemini service. Fix it with the following commands.
113
114```sh
115sudo systemctl enable gemini.service
116sudo systemctl restart gemini.service
117sudo systemctl status gemini.service
118```
119
120# Firewall Rules
121
122Great! Our server is now functional and running. The first consideration now is
123that you need to be able to access port 1965 on the server. If you have a
124firewall enabled, you'll need to open that port up.
125
126```sh
127sudo ufw allow 1965
128sudo ufw reload
129```
130
131# Creating Content
132
133Let's create the Gemini capsule. Note that wherever you set the WorkingDirectory
134variable to earlier, Agate will expect you to put your Gemini capsule contents
135in a sub-folder called "content." So, I place my files in "/var/gmi/content."
136I'm going to create that folder now and put a file in there.
137
138```sh
139sudo mkdir /var/gemini/content
140sudo nano /var/gemini/content/index.gmi
141```
142
143You can put whatever you want in the "index.gmi" file, just make sure it's valid
144Gemtext.
145
146# The Results
147
148To view the results, you can use a Gemini browser, such as
149[Lagrange](https://gmi.skyjake.fi/lagrange/) or
150[amfora](https://github.com/makeworld-the-better-one/amfora).
content/blog/2021-04-17-gemini-server.org added +151
@@ -0,0 +1,151 @@
1#+date: <2021-04-17>
2#+title: Hosting a Gemini Server
3#+description:
4
5
6* Similar Article Available
7
8To read more about Gemini and ways to test out this new protocol without
9your own server, see my previous post
10[[../launching-a-gemini-capsule/][Launching a Gemini Capsule]].
11
12* Preparation
13
14This guide assumes you have access to a server accessible to the world
15through a public IP address and that you own a domain name used for this
16Gemini capsule.
17
18* Getting Started with Agate
19
20We are going to use [[https://github.com/mbrubeck/agate][Agate]] for
21this tutorial. This is a basic Gemini server written in Rust. It takes
22very little time and maintenance to get it running.
23
24* Install Dependencies
25
26First, you will need to install the Rust package for your system. On
27Ubuntu, use the following commands (remember to use =sudo= if you are
28not the root user). The Rust installation will give you options to
29customize the installation; I used the default installation options.
30
31#+begin_src sh
32sudo apt update && sudo apt upgrade -y
33curl https://sh.rustup.rs -sSf | sh
34#+end_src
35
36Remember to configure your shell with the new configuration:
37
38#+begin_src sh
39source $HOME/.cargo/env
40#+end_src
41
42Before we install agate, make sure you have the =gcc= package installed:
43
44#+begin_src sh
45sudo apt install gcc
46#+end_src
47
48Next, you'll need to install the agate executable with Rust's Cargo
49package maintainer:
50
51#+begin_src sh
52cargo install agate
53#+end_src
54
55* Create Symlinks
56
57Once Cargo has finished installing all the required packages, symlink
58the executable to your $PATH.
59
60#+begin_src sh
61sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate
62#+end_src
63
64* Using Agate's Built-In Installation Tool
65
66If you're running Ubuntu or Debian, use the Debian installation script
67found in Agate's GitHub repository, under the =tools/debian= folder.
68
69#+begin_src sh
70git clone https://github.com/mbrubeck/agate
71cd agate/tools/debian
72sudo ./install.sh
73#+end_src
74
75* Configure the Gemini Service
76
77We have a little more to do, but since this script tries to immediately
78run the service, it will likely fail with an exit code. Let's add our
79finishing touches. Edit the following file and replace the hostname with
80your desired URL. You can also change the directory where content will
81be served.
82
83#+begin_src sh
84sudo nano /etc/systemd/system/gemini.service
85#+end_src
86
87#+begin_src sh
88# Edit these lines to whatever you want - see the next code block for my personal configuration.
89WorkingDirectory=/srv/gemini
90ExecStart=agate --hostname $(uname -n) --lang en
91#+end_src
92
93This is my personal config:
94
95#+begin_src sh
96WorkingDirectory=/var/gemini/
97ExecStart=agate --hostname gemini.example.com --lang en
98#+end_src
99
100Since we've altered the systemd configuration files, we have to reload
101the daemon. Let's do that, restart our service, and check its status.
102
103#+begin_src sh
104sudo systemctl daemon-reload
105sudo systemctl restart gemini.service
106sudo systemctl status gemini.service
107#+end_src
108
109* Fixing Systemd Errors
110
111If you're still getting errors, the installation process may not have
112properly enabled the gemini service. Fix it with the following commands.
113
114#+begin_src sh
115sudo systemctl enable gemini.service
116sudo systemctl restart gemini.service
117sudo systemctl status gemini.service
118#+end_src
119
120* Firewall Rules
121
122Great! Our server is now functional and running. The first consideration
123now is that you need to be able to access port 1965 on the server. If
124you have a firewall enabled, you'll need to open that port up.
125
126#+begin_src sh
127sudo ufw allow 1965
128sudo ufw reload
129#+end_src
130
131* Creating Content
132
133Let's create the Gemini capsule. Note that wherever you set the
134WorkingDirectory variable to earlier, Agate will expect you to put your
135Gemini capsule contents in a sub-folder called "content." So, I place my
136files in "/var/gmi/content." I'm going to create that folder now and put
137a file in there.
138
139#+begin_src sh
140sudo mkdir /var/gemini/content
141sudo nano /var/gemini/content/index.gmi
142#+end_src
143
144You can put whatever you want in the "index.gmi" file, just make sure
145it's valid Gemtext.
146
147* The Results
148
149To view the results, you can use a Gemini browser, such as
150[[https://gmi.skyjake.fi/lagrange/][Lagrange]] or
151[[https://github.com/makeworld-the-better-one/amfora][amfora]].
content/blog/2021-04-23-php-comment-system.md deleted −261
@@ -1,261 +0,0 @@
1+++
2date = 2021-04-23
3title = "Roll Your Own Static Commenting System in PHP"
4description = ""
5draft = false
6+++
7
8# The Terrible-ness of Commenting Systems
9
10The current state of affairs regarding interactive comment systems is, well,
11terrible. It is especially awful if you're a privacy conscious person who does
12not generally load third-party scripts or frames on the websites you visit.
13
14Even further, many comment systems are charging exorbitant fees for something
15that should be standard.
16
17Of course, there are some really terrible options:
18
19- Facebook Comments
20- Discourse
21
22There are some options that are better but still use too many scripts, frames,
23or social integrations on your web page that could impact some users:
24
25- Disqus
26- Isso
27- Remark42
28
29Lastly, I looked into a few unique ways of generating blog comments, such as
30using Twitter threads or GitHub issues to automatically post issues. However,
31these both rely on external third-party sites that I don't currently use.
32
33# Stay Static with Server-Side Comments
34
35The main issue for my personal use-case is that my blog is completely, 100%
36static. I use PHP on the back-end but website visitors only see HTML and a
37single CSS file. No external javascript and no embedded frames.
38
39So, how do we keep a site static and still allow users to interact with blog
40posts? The key actually pretty simple - I'm already using PHP, so why not rely
41on the classic HTML `<form>` and a PHP script to save the comments somewhere? As
42it turns out, this was a perfect solution for me.
43
44The second issue for my personal use-case is that I am trying to keep the
45contents of my website accessible over time, as described by @brandur, in his
46post entitled [Blog with Markdown + Git, and degrade gracefully through
47time](https://brandur.org/fragments/graceful-degradation-time) .
48
49This means I cannot rely on a database for comments, since I do not rely on a
50database for any other part of my websites.
51
52I blog in plain Markdown files, commit all articles to Git, and ensure that
53future readers will be able to see the source data long after I'm gone, or the
54website has gone offline. However, I still haven't committed any images served
55on my blog to Git, as I'm not entirely sold on Git LFS yet - for now, images can
56be found at [img.cleberg.net](https://img.cleberg.net).
57
58Saving my comments back to the Git repository ensures that another aspect of my
59site will degrade gracefully.
60
61# Create a Comment Form
62
63Okay, let's get started. The first step is to create an HTML form that users can
64see and utilize to submit comments. This is fairly easy and can be changed
65depending on your personal preferences.
66
67Take a look at the code block below for the form I currently use. Note that
68`<current-url>` is replaced automatically in PHP with the current post's URL, so
69that my PHP script used later will know which blog post the comment is related
70to.
71
72The form contains the following structure:
73
741. `<form>` - This is the form and will determine which PHP script to send the
75 comment to.
762. `<section hidden>` - This section is hidden from the user and is used to
77 ensure that we know which blog post sent the comment.
783. `<section>` Display Name (Optional) - Used to accept a display name, if
79 entered.
804. `<section>` Comment (Required) - Used to accept the user's full comment.
81 Markdown is allowed.
825. `<button>` - A button to submit the form.
83
84```html
85<form action="/comment.php" method="POST">
86 <h3>Leave a Comment</h3>
87 <section hidden>
88 <label class="form-label" for="postURL">Post URL</label>
89 <input
90 class="form-control"
91 id="postURL"
92 name="postURL"
93 type="text"
94 value="<current-url>"
95 />
96 </section>
97 <section>
98 <label class="form-label" for="userName">Display Name</label>
99 <input
100 class="form-control"
101 id="userName"
102 name="userName"
103 placeholder="John Doe"
104 type="text"
105 />
106 </section>
107 <section>
108 <label class="form-label" for="userContent">Your Comment</label>
109 <textarea
110 class="form-control"
111 id="userContent"
112 name="userContent"
113 rows="3"
114 placeholder="# Feel free to use Markdown"
115 aria-describedby="commentHelp"
116 required
117 ></textarea>
118 <div id="commentHelp" class="form-text">
119 Comments are saved as Markdown and cannot be edited or deleted.
120 </div>
121 </section>
122 <button type="submit">Submit</button>
123</form>
124```
125
126# Handle Comments via POST
127
128Now that we have a form and can submit comments, we need to create a PHP script
129so that the server can fetch the comment data and save it. Make sure your PHP
130script name matches the name you entered in the `action` field in your form.
131
132See the code block below for the contents of my `comment.php` script. We perform
133the following tasks in this script:
134
1351. Grab the POST data from the HTML form.
1362. Sanitize the comment data with `htmlentities`.
1373. Set the display name to `Anonymous` if it was left blank.
1384. Create a PHP object that combines all of this data.
1395. Check if our destination file `comments.json` exists.
1406. If so, convert the PHP object to JSON and save it to the file.
1417. If the `comments.json` file does not exist, the script will exit with an
142 error. You can alter this to ensure it creates the script, but my source
143 code includes the file by default, so it will always exist.
1448. Finally, send the user back to the `#comments` section of the blog post they
145 just read.
146
147```php
148// Get the content sent from the comment form
149$comment = htmlentities($_POST['userContent']);
150$post_url = $_POST['postURL'];
151
152// Set default values if blank
153if (isset($_POST['userName']) && trim($_POST['userName']) !== "") {
154 $username = $_POST['userName'];
155} else {
156 $username = 'Anonymous';
157}
158
159// Create an empty PHP object
160$user_object = new stdClass();
161
162// Add object content
163$user_object->timestamp = date('Y-m-d H:i:s');
164$user_object->username = $username;
165$user_object->comment = $comment;
166$user_object->post_url = $post_url;
167
168// Append JSON to file
169$file_name = 'comments.json';
170if (file_exists($file_name)) {
171 $source_data = file_get_contents($file_name);
172 $temp_array = json_decode($source_data);
173 array_push($temp_array, $user_object);
174 $json_data = json_encode($temp_array, JSON_PRETTY_PRINT);
175 file_put_contents($file_name, $json_data);
176} else {
177 die('Error: The "comments.json" file does not exist.');
178}
179
180// Send the user back
181header('Location: ' . $post_url . '#comments');
182```
183
184If you're using Apache, make sure the `www-data` user on your server has the
185correct permissions to your website directory or else it will not be able to
186write to `comments.json`.
187
188```sh
189chgrp -R www-data /path/to/website/
190chmod -R g+w comments.json
191```
192
193# Display User Comments
194
195Now that we can submit comments, and they are saved to the `comments.json` file,
196let's make sure we can show those comments in each blog post.
197
198The code block below shows the function I use to decode my `comments.json` file,
199check if the comments apply to the current post, and then display them.
200
201This piece of code should **really** be inside a function (or at least in an
202organized PHP workflow). Don't just copy-and-paste and expect it to work. You
203need to at least supply a `$query` variable depending on the page visited.
204
205```php
206$query = 'your-blog-post.html';
207
208// Load saved comments
209$comments_file = 'comments.json';
210$comments_raw = file_get_contents($comments_file);
211$comments = json_decode($comments_raw);
212$comment_section = '<section id="comments" class="comments"><h3>Comments</h3>';
213foreach ($comments as $comment) {
214 if ($comment->post_url == "https://example.com/post/" . $query) {
215 // Assign metadata to variables
216 $comment_timestamp = $comment->timestamp;
217 $comment_username = $comment->username;
218 $comment_content = $comment->comment;
219
220 // Parse the comment, in case it contains Markdown
221 $comment_md = Parsedown::instance()->text($comment_content);
222 $comment_html = new DOMDocument();
223 $comment_html->loadHTML($comment_md);
224 $comment_html_links = $comment_html->getElementsByTagName('a');
225 foreach ($comment_html_links as $comment_html_link) {
226 $comment_html_link->setAttribute('rel', 'noreferrer');
227 $comment_html_link->setAttribute('target', '_blank');
228 }
229 $comment_secured_html = $comment_html->saveHTML();
230
231 // Apply metadata to comments section
232 $comment_section .= '<div class="user-comment"><div class="row"><label>Timestamp:</label><p>' . $comment_timestamp . '</p></div><div class="row"><label>Name:</label><p>' . $comment_username . '</p></div><div class="row markdown"><label>Comment:</label><div class="comment-markdown">' . $comment_secured_html . '</div></div></div>';
233 }
234}
235
236echo $comment_section;
237```
238
239# Bonus: Create a 'Recent Comments' Page
240
241Finally, the last part of my current system is to create a Recent Comments page
242so that I can easily check-in on my blog and moderate any spam. As an
243alternative, you could use PHP's `mail()` function to email you for each blog
244comment.
245
246The code to do this is literally the same code as the previous section, I just
247make sure it is printed when someone visits `https://example.com/comments/`.
248
249# Possible Enhancements
250
251This comment system is by no means a fully-developed system. I have noted a few
252possible enhancements here that I may implement in the future:
253
254- Create a secure moderator page with user authentication at
255 `https://blog.example.com/mod/`. This page could have the option to edit or
256 delete any comment found in `comments.json`.
257- Create a temporary file, such as `pending_comments.json`, that will store
258 newly-submitted comments and won't display on blog posts until approved by a
259 moderator.
260- Create a `/modlog/` page with a chronological log, showing which moderator
261 approved which comments and why certain comments were rejected.
content/blog/2021-04-23-php-comment-system.org added +272
@@ -0,0 +1,272 @@
1#+date: <2021-04-23>
2#+title: Roll Your Own Static Commenting System in PHP
3#+description:
4
5
6* The Terrible-ness of Commenting Systems
7
8The current state of affairs regarding interactive comment systems is,
9well, terrible. It is especially awful if you're a privacy conscious
10person who does not generally load third-party scripts or frames on the
11websites you visit.
12
13Even further, many comment systems are charging exorbitant fees for
14something that should be standard.
15
16Of course, there are some really terrible options:
17
18- Facebook Comments
19- Discourse
20
21There are some options that are better but still use too many scripts,
22frames, or social integrations on your web page that could impact some
23users:
24
25- Disqus
26- Isso
27- Remark42
28
29Lastly, I looked into a few unique ways of generating blog comments,
30such as using Twitter threads or GitHub issues to automatically post
31issues. However, these both rely on external third-party sites that I
32don't currently use.
33
34* Stay Static with Server-Side Comments
35
36The main issue for my personal use-case is that my blog is completely,
37100% static. I use PHP on the back-end but website visitors only see
38HTML and a single CSS file. No external javascript and no embedded
39frames.
40
41So, how do we keep a site static and still allow users to interact with
42blog posts? The key actually pretty simple - I'm already using PHP, so
43why not rely on the classic HTML =<form>= and a PHP script to save the
44comments somewhere? As it turns out, this was a perfect solution for me.
45
46The second issue for my personal use-case is that I am trying to keep
47the contents of my website accessible over time, as described by
48[cite/t:@brandur], in his post entitled
49[[https://brandur.org/fragments/graceful-degradation-time][Blog with
50Markdown + Git, and degrade gracefully through time]] .
51
52This means I cannot rely on a database for comments, since I do not rely
53on a database for any other part of my websites.
54
55I blog in plain Markdown files, commit all articles to Git, and ensure
56that future readers will be able to see the source data long after I'm
57gone, or the website has gone offline. However, I still haven't
58committed any images served on my blog to Git, as I'm not entirely sold
59on Git LFS yet - for now, images can be found at
60[[https://img.cleberg.net][img.cleberg.net]].
61
62Saving my comments back to the Git repository ensures that another
63aspect of my site will degrade gracefully.
64
65* Create a Comment Form
66
67Okay, let's get started. The first step is to create an HTML form that
68users can see and utilize to submit comments. This is fairly easy and
69can be changed depending on your personal preferences.
70
71Take a look at the code block below for the form I currently use. Note
72that =<current-url>= is replaced automatically in PHP with the current
73post's URL, so that my PHP script used later will know which blog post
74the comment is related to.
75
76The form contains the following structure:
77
781. =<form>= - This is the form and will determine which PHP script to
79 send the comment to.
802. =<section hidden>= - This section is hidden from the user and is used
81 to ensure that we know which blog post sent the comment.
823. =<section>= Display Name (Optional) - Used to accept a display name,
83 if entered.
844. =<section>= Comment (Required) - Used to accept the user's full
85 comment. Markdown is allowed.
865. =<button>= - A button to submit the form.
87
88#+begin_src html
89<form action="/comment.php" method="POST">
90 <h3>Leave a Comment</h3>
91 <section hidden>
92 <label class="form-label" for="postURL">Post URL</label>
93 <input
94 class="form-control"
95 id="postURL"
96 name="postURL"
97 type="text"
98 value="<current-url>"
99 />
100 </section>
101 <section>
102 <label class="form-label" for="userName">Display Name</label>
103 <input
104 class="form-control"
105 id="userName"
106 name="userName"
107 placeholder="John Doe"
108 type="text"
109 />
110 </section>
111 <section>
112 <label class="form-label" for="userContent">Your Comment</label>
113 <textarea
114 class="form-control"
115 id="userContent"
116 name="userContent"
117 rows="3"
118 placeholder="# Feel free to use Markdown"
119 aria-describedby="commentHelp"
120 required
121 ></textarea>
122 <div id="commentHelp" class="form-text">
123 Comments are saved as Markdown and cannot be edited or deleted.
124 </div>
125 </section>
126 <button type="submit">Submit</button>
127</form>
128#+end_src
129
130* Handle Comments via POST
131
132Now that we have a form and can submit comments, we need to create a PHP
133script so that the server can fetch the comment data and save it. Make
134sure your PHP script name matches the name you entered in the =action=
135field in your form.
136
137See the code block below for the contents of my =comment.php= script. We
138perform the following tasks in this script:
139
1401. Grab the POST data from the HTML form.
1412. Sanitize the comment data with =htmlentities=.
1423. Set the display name to =Anonymous= if it was left blank.
1434. Create a PHP object that combines all of this data.
1445. Check if our destination file =comments.json= exists.
1456. If so, convert the PHP object to JSON and save it to the file.
1467. If the =comments.json= file does not exist, the script will exit with
147 an error. You can alter this to ensure it creates the script, but my
148 source code includes the file by default, so it will always exist.
1498. Finally, send the user back to the =#comments= section of the blog
150 post they just read.
151
152#+begin_src php
153// Get the content sent from the comment form
154$comment = htmlentities($_POST['userContent']);
155$post_url = $_POST['postURL'];
156
157// Set default values if blank
158if (isset($_POST['userName']) && trim($_POST['userName']) !== "") {
159 $username = $_POST['userName'];
160} else {
161 $username = 'Anonymous';
162}
163
164// Create an empty PHP object
165$user_object = new stdClass();
166
167// Add object content
168$user_object->timestamp = date('Y-m-d H:i:s');
169$user_object->username = $username;
170$user_object->comment = $comment;
171$user_object->post_url = $post_url;
172
173// Append JSON to file
174$file_name = 'comments.json';
175if (file_exists($file_name)) {
176 $source_data = file_get_contents($file_name);
177 $temp_array = json_decode($source_data);
178 array_push($temp_array, $user_object);
179 $json_data = json_encode($temp_array, JSON_PRETTY_PRINT);
180 file_put_contents($file_name, $json_data);
181} else {
182 die('Error: The "comments.json" file does not exist.');
183}
184
185// Send the user back
186header('Location: ' . $post_url . '#comments');
187#+end_src
188
189If you're using Apache, make sure the =www-data= user on your server has
190the correct permissions to your website directory or else it will not be
191able to write to =comments.json=.
192
193#+begin_src sh
194chgrp -R www-data /path/to/website/
195chmod -R g+w comments.json
196#+end_src
197
198* Display User Comments
199
200Now that we can submit comments, and they are saved to the
201=comments.json= file, let's make sure we can show those comments in each
202blog post.
203
204The code block below shows the function I use to decode my
205=comments.json= file, check if the comments apply to the current post,
206and then display them.
207
208This piece of code should *really* be inside a function (or at least in
209an organized PHP workflow). Don't just copy-and-paste and expect it to
210work. You need to at least supply a =$query= variable depending on the
211page visited.
212
213#+begin_src php
214$query = 'your-blog-post.html';
215
216// Load saved comments
217$comments_file = 'comments.json';
218$comments_raw = file_get_contents($comments_file);
219$comments = json_decode($comments_raw);
220$comment_section = '<section id="comments" class="comments"><h3>Comments</h3>';
221foreach ($comments as $comment) {
222 if ($comment->post_url == "https://example.com/post/" . $query) {
223 // Assign metadata to variables
224 $comment_timestamp = $comment->timestamp;
225 $comment_username = $comment->username;
226 $comment_content = $comment->comment;
227
228 // Parse the comment, in case it contains Markdown
229 $comment_md = Parsedown::instance()->text($comment_content);
230 $comment_html = new DOMDocument();
231 $comment_html->loadHTML($comment_md);
232 $comment_html_links = $comment_html->getElementsByTagName('a');
233 foreach ($comment_html_links as $comment_html_link) {
234 $comment_html_link->setAttribute('rel', 'noreferrer');
235 $comment_html_link->setAttribute('target', '_blank');
236 }
237 $comment_secured_html = $comment_html->saveHTML();
238
239 // Apply metadata to comments section
240 $comment_section .= '<div class="user-comment"><div class="row"><label>Timestamp:</label><p>' . $comment_timestamp . '</p></div><div class="row"><label>Name:</label><p>' . $comment_username . '</p></div><div class="row markdown"><label>Comment:</label><div class="comment-markdown">' . $comment_secured_html . '</div></div></div>';
241 }
242}
243
244echo $comment_section;
245#+end_src
246
247* Bonus: Create a 'Recent Comments' Page
248
249Finally, the last part of my current system is to create a Recent
250Comments page so that I can easily check-in on my blog and moderate any
251spam. As an alternative, you could use PHP's =mail()= function to email
252you for each blog comment.
253
254The code to do this is literally the same code as the previous section,
255I just make sure it is printed when someone visits
256=https://example.com/comments/=.
257
258* Possible Enhancements
259
260This comment system is by no means a fully-developed system. I have
261noted a few possible enhancements here that I may implement in the
262future:
263
264- Create a secure moderator page with user authentication at
265 =https://blog.example.com/mod/=. This page could have the option to
266 edit or delete any comment found in =comments.json=.
267- Create a temporary file, such as =pending_comments.json=, that will
268 store newly-submitted comments and won't display on blog posts until
269 approved by a moderator.
270- Create a =/modlog/= page with a chronological log, showing which
271 moderator approved which comments and why certain comments were
272 rejected.
content/blog/2021-04-28-photography.md deleted −68
@@ -1,68 +0,0 @@
1+++
2date = 2021-04-28
3title = "Jumping Back Into Photography"
4description = ""
5draft = false
6+++
7
8# Why Photography?
9
10I've often wondered why photography is as enticing as it is. You can see
11billions of people around the world taking photographs every single moment of
12the day. New technology often boasts about their photographic capabilities,
13especially smartphones. I would even assume that we live in a world where there
14is never a moment in which a photograph is not being taken somewhere on Earth.
15
16As for myself, I would simply say that I enjoy preserving a memory in physical
17(or digital) form. I've never had the best memory when it comes to recalling
18details of places and people gone by, so it helps to have a frame of reference
19lying around.
20
21Regardless of the reason, I think most people would agree that you simply cannot
22have too many hobbies.
23
24# Older Cameras
25
26I started playing around with the idea of photography when my family purchased a
27Fujifilm camera for family-specific events. I don't recall the specific model,
28but I do recall it was a point-and-shoot camera without an interchangeable lens.
29However, it was of great value to someone, like myself, who couldn't afford any
30other camera. I took about 10,000 shots with that camera over a 3-5 year span.
31Most notably, all of my trips to California were documented through this camera.
32
33When possible, I would borrow my sister's camera, which is a Sony SLT-A58. This
34camera was great and allowed for some of my best early shots, especially those
35taken in Utah's and Nevada's parks.
36
37# My Current Kit
38
39I've finally come to a point in my life where I have the disposable income to
40invest in a solid photography kit. I played around with the idea of a lot of
41different cameras, different types, new vs used, etc. Finally, I settled on the
42[Sony α7 III](https://en.wikipedia.org/wiki/Sony_%CE%B17_III). This camera is
43mirror-less and uses a full-frame image sensor at 24 megapixels. I don't create
44large prints, and I am mostly focused on preserving memories in high quality for
45the next 5-10 years with this camera, so the specifications here are just
46perfect for me.
47
48For lenses, I decided to buy two lenses that could carry me through most
49situations:
50
51- [Vario-Tessar T\*\* FE 24-70 mm F4 ZA
52 OSS](https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z)
53- [Tamron 70-300mm f4.5-6.3 Di III
54 RXD](https://www.tamron-usa.com/product/lenses/a047.html)
55
56In addition, I grabbed a couple [HGX Prime
5767mm](https://www.promaster.com/Product/6725) protection filters for the lenses.
58
59As I delve further into photography and pick up more skills, I will most likely
60go back and grab a lens with a higher f-stop value, such as f/1.8. I toyed with
61the idea of grabbing a 50 mm at `f/1.8`, but decided to keep things in a
62reasonable price range instead.
63
64Finally, I made sure to buy a photography-specific backpack with a rain guard,
65and the zipper on the back panel, to protect the equipment while wearing the
66bag. If you've ever had to haul around a DSLR (or camera of similar heft) in a
67bag that only has a shoulder strap, you'll know the pain it can cause. Putting
68all my equipment in a backpack was an easy decision.
content/blog/2021-04-28-photography.org added +71
@@ -0,0 +1,71 @@
1#+date: <2021-04-28>
2#+title: Jumping Back Into Photography
3#+description:
4
5
6* Why Photography?
7
8I've often wondered why photography is as enticing as it is. You can see
9billions of people around the world taking photographs every single
10moment of the day. New technology often boasts about their photographic
11capabilities, especially smartphones. I would even assume that we live
12in a world where there is never a moment in which a photograph is not
13being taken somewhere on Earth.
14
15As for myself, I would simply say that I enjoy preserving a memory in
16physical (or digital) form. I've never had the best memory when it comes
17to recalling details of places and people gone by, so it helps to have a
18frame of reference lying around.
19
20Regardless of the reason, I think most people would agree that you
21simply cannot have too many hobbies.
22
23* Older Cameras
24
25I started playing around with the idea of photography when my family
26purchased a Fujifilm camera for family-specific events. I don't recall
27the specific model, but I do recall it was a point-and-shoot camera
28without an interchangeable lens. However, it was of great value to
29someone, like myself, who couldn't afford any other camera. I took about
3010,000 shots with that camera over a 3-5 year span. Most notably, all of
31my trips to California were documented through this camera.
32
33When possible, I would borrow my sister's camera, which is a Sony
34SLT-A58. This camera was great and allowed for some of my best early
35shots, especially those taken in Utah's and Nevada's parks.
36
37* My Current Kit
38
39I've finally come to a point in my life where I have the disposable
40income to invest in a solid photography kit. I played around with the
41idea of a lot of different cameras, different types, new vs used, etc.
42Finally, I settled on the
43[[https://en.wikipedia.org/wiki/Sony_%CE%B17_III][Sony α7 III]]. This
44camera is mirror-less and uses a full-frame image sensor at 24
45megapixels. I don't create large prints, and I am mostly focused on
46preserving memories in high quality for the next 5-10 years with this
47camera, so the specifications here are just perfect for me.
48
49For lenses, I decided to buy two lenses that could carry me through most
50situations:
51
52- [[https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z][Vario-Tessar
53 T** FE 24-70 mm F4 ZA OSS]]
54- [[https://www.tamron-usa.com/product/lenses/a047.html][Tamron 70-300mm
55 f4.5-6.3 Di III RXD]]
56
57In addition, I grabbed a couple
58[[https://www.promaster.com/Product/6725][HGX Prime 67mm]] protection
59filters for the lenses.
60
61As I delve further into photography and pick up more skills, I will most
62likely go back and grab a lens with a higher f-stop value, such as
63f/1.8. I toyed with the idea of grabbing a 50 mm at =f/1.8=, but decided
64to keep things in a reasonable price range instead.
65
66Finally, I made sure to buy a photography-specific backpack with a rain
67guard, and the zipper on the back panel, to protect the equipment while
68wearing the bag. If you've ever had to haul around a DSLR (or camera of
69similar heft) in a bag that only has a shoulder strap, you'll know the
70pain it can cause. Putting all my equipment in a backpack was an easy
71decision.
content/blog/2021-05-30-changing-git-authors.md → content/blog/2021-05-30-changing-git-authors.org renamed +26 −28
@@ -1,33 +1,31 @@
1+++ 1#+date: <2021-05-30>
2date = 2021-05-30 2#+title: Changing Git Authors
3title = "Changing Git Authors" 3#+description:
4description = "" 4
5draft = false 5
6+++ 6* Changing Git Author/Email Based on Previously Committed Email
7 7
8# Changing Git Author/Email Based on Previously Committed Email 8Here's the dilemma: You've been committing changes to your git
9 9repository with an incorrect name or email (or multiple repositories),
10Here's the dilemma: You've been committing changes to your git repository with 10and now you want to fix it. Luckily, there's a semi-reliable way to fix
11an incorrect name or email (or multiple repositories), and now you want to fix 11that. While I have never experienced issues with this method, some
12it. Luckily, there's a semi-reliable way to fix that. While I have never 12people have warned that it can mess with historical hashes and integrity
13experienced issues with this method, some people have warned that it can mess 13of commits, so use this method only if you're okay accepting that risk.
14with historical hashes and integrity of commits, so use this method only if
15you're okay accepting that risk.
16 14
17Okay, let's create the bash script: 15Okay, let's create the bash script:
18 16
19```sh 17#+begin_src sh
20nano change_git_authors.sh 18nano change_git_authors.sh
21``` 19#+end_src
22 20
23The following information can be pasted directly into your bash script. The only 21The following information can be pasted directly into your bash script.
24changes you need to make are to the following variables: 22The only changes you need to make are to the following variables:
25 23
26- `OLD_EMAIL` 24- =OLD_EMAIL=
27- `CORRECT_NAME` 25- =CORRECT_NAME=
28- `CORRECT_EMAIL` 26- =CORRECT_EMAIL=
29 27
30```sh 28#+begin_src sh
31#!/bin/sh 29#!/bin/sh
32 30
33# List all sub-directories in the current directory 31# List all sub-directories in the current directory
@@ -60,16 +58,16 @@ do
60 58
61 cd .. 59 cd ..
62done 60done
63``` 61#+end_src
64 62
65Finally, save the bash script and make it executable. 63Finally, save the bash script and make it executable.
66 64
67```sh 65#+begin_src sh
68chmod a+x change_git_authors.sh 66chmod a+x change_git_authors.sh
69``` 67#+end_src
70 68
71Now you can run the script and should see the process begin. 69Now you can run the script and should see the process begin.
72 70
73```sh 71#+begin_src sh
74./change_git_authors.sh 72./change_git_authors.sh
75``` 73#+end_src
content/blog/2021-07-15-delete-gitlab-repos.md → content/blog/2021-07-15-delete-gitlab-repos.org renamed +41 −42
@@ -1,53 +1,51 @@
1+++ 1#+date: <2021-07-15>
2date = 2021-07-15 2#+title: How to Delete All GitLab Repositories
3title = "How t oDelete All GitLab Repositories" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Background
9 5
10Have you ever used GitLab to host your source code, moved to a different host, 6* Background
11and wanted to delete everything from your GitLab account? Well, this post covers
12any scenario where you would want to delete all repositories from your GitLab
13account.
14 7
15For me, I currently maintain around 30 repositories and don't like to manually 8Have you ever used GitLab to host your source code, moved to a different
16delete them whenever I switch host. GitHub has a few different tools online to 9host, and wanted to delete everything from your GitLab account? Well,
17delete all repositories for you, but I have not found anything similar for 10this post covers any scenario where you would want to delete all
18GitLab, so I needed an alternative solution. 11repositories from your GitLab account.
19 12
20# Use a Python Script 13For me, I currently maintain around 30 repositories and don't like to
14manually delete them whenever I switch host. GitHub has a few different
15tools online to delete all repositories for you, but I have not found
16anything similar for GitLab, so I needed an alternative solution.
21 17
22## Requirements 18* Use a Python Script
23 19
24Before we look at the script, make sure you know your GitLab username. Next, 20** Requirements
25[create an authorization
26token](https://gitlab.com/-/profile/personal_access_tokens) so that the Python
27script can delete your repositories. Don't lose this token or else you'll need
28to create a new one.
29 21
30## Create the Script 22Before we look at the script, make sure you know your GitLab username.
23Next, [[https://gitlab.com/-/profile/personal_access_tokens][create an
24authorization token]] so that the Python script can delete your
25repositories. Don't lose this token or else you'll need to create a new
26one.
31 27
32To run a Python script, you must first create it. Open a terminal and enter the 28** Create the Script
33following commands in whichever directory you prefer to store the script. You
34can do the same things in a file manager if you prefer.
35 29
36```sh 30To run a Python script, you must first create it. Open a terminal and
31enter the following commands in whichever directory you prefer to store
32the script. You can do the same things in a file manager if you prefer.
33
34#+begin_src sh
37mkdir delete-gitlab 35mkdir delete-gitlab
38``` 36#+end_src
39 37
40```sh 38#+begin_src sh
41cd delete-gitlab 39cd delete-gitlab
42``` 40#+end_src
43 41
44```sh 42#+begin_src sh
45nano main.py 43nano main.py
46``` 44#+end_src
47 45
48Enter the following code into your `main.py` script. 46Enter the following code into your =main.py= script.
49 47
50```python 48#+begin_src python
51import request 49import request
52import json 50import json
53 51
@@ -101,15 +99,16 @@ def main():
101 99
102if __name__ == "__main__": 100if __name__ == "__main__":
103 main() 101 main()
104``` 102#+end_src
105 103
106Now that you have the proper information, replace `{user-id}` with your GitLab 104Now that you have the proper information, replace ={user-id}= with your
107username and `{auth-token}` with the authorization token you created earlier. 105GitLab username and ={auth-token}= with the authorization token you
106created earlier.
108 107
109Finally, simply run the script and watch the output. You can also use PyCharm 108Finally, simply run the script and watch the output. You can also use
110Community Edition to edit and run the Python script if you don't want to work in 109PyCharm Community Edition to edit and run the Python script if you don't
111a terminal. 110want to work in a terminal.
112 111
113```sh 112#+begin_src sh
114python3 main.py 113python3 main.py
115``` 114#+end_src
content/blog/2021-08-25-audit-sampling.md → content/blog/2021-08-25-audit-sampling.org renamed +126 −124
@@ -1,52 +1,51 @@
1+++ 1#+date: <2021-08-25>
2date = 2021-08-25 2#+title: Audit Sampling with Python
3title = "Audit Sampling with Python" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Introduction 5
6* Introduction
9 7
10For anyone who is familiar with internal auditing, external auditing, or 8For anyone who is familiar with internal auditing, external auditing, or
11consulting, you will understand how tedious audit testing can become when you 9consulting, you will understand how tedious audit testing can become
12are required to test large swaths of data. When we cannot establish an automated 10when you are required to test large swaths of data. When we cannot
13means of testing an entire population, we generate samples to represent the 11establish an automated means of testing an entire population, we
14population of data. This helps ensure we can have a small enough data pool to 12generate samples to represent the population of data. This helps ensure
15test and that our results still represent the population. 13we can have a small enough data pool to test and that our results still
16 14represent the population.
17However, sampling data within the world of audit still seems to confuse quite a 15
18lot of people. While some audit-focused tools have introduced sampling 16However, sampling data within the world of audit still seems to confuse
19functionality (e.g. Wdesk), many audit departments and firms cannot use software 17quite a lot of people. While some audit-focused tools have introduced
20like this due to certain constraints, such as the team's budget or knowledge. 18sampling functionality (e.g. Wdesk), many audit departments and firms
21Here is where this article comes in: we're going to use 19cannot use software like this due to certain constraints, such as the
22[Python](https://www.python.org), a free and open-source programming language, 20team's budget or knowledge. Here is where this article comes in: we're
23to generate random samples from a dataset in order to suffice numerous audit 21going to use [[https://www.python.org][Python]], a free and open-source
24situations. 22programming language, to generate random samples from a dataset in order
25 23to suffice numerous audit situations.
26# Audit Requirements for Sampling 24
27 25* Audit Requirements for Sampling
28Before we get into the details of how to sample with Python, I want to make sure 26
29I discuss the different requirements that auditors may have of samples used 27Before we get into the details of how to sample with Python, I want to
30within their projects. 28make sure I discuss the different requirements that auditors may have of
31 29samples used within their projects.
32## Randomness 30
33 31** Randomness
34First, let's discuss randomness. When testing out new technology to help assist 32
35with audit sampling, you need to understand exactly how your samples are being 33First, let's discuss randomness. When testing out new technology to help
36generated. For example, if the underlying function is just picking every 57th 34assist with audit sampling, you need to understand exactly how your
37element from a list, that's not truly random; it's a systematic form of 35samples are being generated. For example, if the underlying function is
38sampling. Luckily, since Python is open-source, we have access to its codebase. 36just picking every 57th element from a list, that's not truly random;
39Through this blog post, I will be using the [pandas](https://pandas.pydata.org) 37it's a systematic form of sampling. Luckily, since Python is
40module in order to generate the random samples. More specifically, I will be 38open-source, we have access to its codebase. Through this blog post, I
41using the 39will be using the [[https://pandas.pydata.org][pandas]] module in order
42[pandas.DataFrame.sample](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html) 40to generate the random samples. More specifically, I will be using the
41[[https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html][pandas.DataFrame.sample]]
43function provided by Pandas. 42function provided by Pandas.
44 43
45Now that you know what you're using, you can always check out the code behind 44Now that you know what you're using, you can always check out the code
46`pandas.DataFrame.sample`. This function does a lot of work, but we really only 45behind =pandas.DataFrame.sample=. This function does a lot of work, but
47care about the following snippets of code: 46we really only care about the following snippets of code:
48 47
49```python 48#+begin_src python
50# Process random_state argument 49# Process random_state argument
51rs = com.random_state(random_state) 50rs = com.random_state(random_state)
52 51
@@ -58,62 +57,65 @@ if ignore_index:
58result.index = ibase.default_index(len(result)) 57result.index = ibase.default_index(len(result))
59 58
60return result 59return result
61``` 60#+end_src
62 61
63The block of code above shows you that if you assign a `random_state` argument 62The block of code above shows you that if you assign a =random_state=
64when you run the function, that will be used as a seed number in the random 63argument when you run the function, that will be used as a seed number
65generation and will allow you to reproduce a sample, given that nothing else 64in the random generation and will allow you to reproduce a sample, given
66changes. This is critical to the posterity of audit work. After all, how can you 65that nothing else changes. This is critical to the posterity of audit
67say your audit process is adequately documented if the next person can't run the 66work. After all, how can you say your audit process is adequately
68code and get the same sample? The final piece here on randomness is to look at 67documented if the next person can't run the code and get the same
69the [choice](https://docs.python.org/3/library/random.html#random.choice) 68sample? The final piece here on randomness is to look at the
70function used above. This is the crux of the generation and can also be examined 69[[https://docs.python.org/3/library/random.html#random.choice][choice]]
71for more detailed analysis on its reliability. As far as auditing goes, we will 70function used above. This is the crux of the generation and can also be
72trust that these functions are mathematically random. 71examined for more detailed analysis on its reliability. As far as
73 72auditing goes, we will trust that these functions are mathematically
74## Sample Sizes 73random.
75 74
76As mentioned in the intro, sampling is only an effective method of auditing when 75** Sample Sizes
77it truly represents the entire population. While some audit departments or firms 76
78may consider certain judgmental sample sizes to be adequate, you may need to 77As mentioned in the intro, sampling is only an effective method of
79rely on statistically-significant confidence levels of sample testing at certain 78auditing when it truly represents the entire population. While some
80points. I will demonstrate both here. For statistically-significant confidence 79audit departments or firms may consider certain judgmental sample sizes
81levels, most people will assume a 90% - 99% confidence level. In order to 80to be adequate, you may need to rely on statistically-significant
82actually calculate the correct sample size, it is best to use statistical tools 81confidence levels of sample testing at certain points. I will
83due to the tedious math work required. For example, for a population of 1000, 82demonstrate both here. For statistically-significant confidence levels,
84and a 90% confidence level that no more than 5% of the items are nonconforming, 83most people will assume a 90% - 99% confidence level. In order to
85you would sample 45 items. 84actually calculate the correct sample size, it is best to use
86 85statistical tools due to the tedious math work required. For example,
87However, in my personal experience, many audit departments and firms do not use 86for a population of 1000, and a 90% confidence level that no more than
88statistical sampling. Most people use a predetermined, often proprietary, table 875% of the items are nonconforming, you would sample 45 items.
89that will instruct auditors which sample sizes to choose. This allows for 88
90uniform testing and reduces overall workload. See the table below for a common 89However, in my personal experience, many audit departments and firms do
91implementation of sample sizes: 90not use statistical sampling. Most people use a predetermined, often
91proprietary, table that will instruct auditors which sample sizes to
92choose. This allows for uniform testing and reduces overall workload.
93See the table below for a common implementation of sample sizes:
92 94
93Control Frequency Sample Size - High Risk Sample Size - Low Risk 95Control Frequency Sample Size - High Risk Sample Size - Low Risk
94 96
95--- 97--------------
96 98
97More Than Daily 40 25 Daily 40 25 Weekly 12 5 Monthly 5 3 Quarterly 2 2 99More Than Daily 40 25 Daily 40 25 Weekly 12 5 Monthly 5 3 Quarterly 2 2
98Semi-Annually 1 1 Annually 1 1 Ad-hoc 1 1 100Semi-Annually 1 1 Annually 1 1 Ad-hoc 1 1
99 101
100### Sampling with Python & Pandas 102*** Sampling with Python & Pandas
101 103
102In this section, I am going to cover a few basic audit situations that require 104In this section, I am going to cover a few basic audit situations that
103sampling. While some situations may require more effort, the syntax, 105require sampling. While some situations may require more effort, the
104organization, and intellect used remain largely the same. If you've never used 106syntax, organization, and intellect used remain largely the same. If
105Python before, note that lines starting with a '`#`' symbol are called comments, 107you've never used Python before, note that lines starting with a '=#='
106and they will be skipped by Python. I highly recommend taking a quick tutorial 108symbol are called comments, and they will be skipped by Python. I highly
107online to understand the basics of Python if any of the code below is confusing 109recommend taking a quick tutorial online to understand the basics of
108to you. 110Python if any of the code below is confusing to you.
109 111
110## Simple Random Sample 112** Simple Random Sample
111 113
112First, let's look at a simple, random sample. The code block below will import 114First, let's look at a simple, random sample. The code block below will
113the `pandas` module, load a data file, sample the data, and export the sample to 115import the =pandas= module, load a data file, sample the data, and
114a file. 116export the sample to a file.
115 117
116```python 118#+begin_src python
117# Import the Pandas module 119# Import the Pandas module
118import pandas 120import pandas
119 121
@@ -131,14 +133,14 @@ sample = df.sample(n=25, random_state=0)
131 133
132# Save the sample to Excel 134# Save the sample to Excel
133sample.to_excel(file_output) 135sample.to_excel(file_output)
134``` 136#+end_src
135 137
136## Simple Random Sample: Using Multiple Input Files 138** Simple Random Sample: Using Multiple Input Files
137 139
138Now that we've created a simple sample, let's create a sample from multiple 140Now that we've created a simple sample, let's create a sample from
139files. 141multiple files.
140 142
141```python 143#+begin_src python
142# Import the Pandas module 144# Import the Pandas module
143import pandas 145import pandas
144 146
@@ -165,15 +167,15 @@ sample = pandas.concat([sample_01, sample_02, sample_03], ignore_index=True)
165 167
166# Save the sample to Excel 168# Save the sample to Excel
167sample.to_excel(file_output) 169sample.to_excel(file_output)
168``` 170#+end_src
169 171
170## Stratified Random Sample 172** Stratified Random Sample
171 173
172Well, what if you need to sample distinct parts of a single file? For example, 174Well, what if you need to sample distinct parts of a single file? For
173let's write some code to separate our data by "Region" and sample those regions 175example, let's write some code to separate our data by "Region" and
174independently. 176sample those regions independently.
175 177
176```python 178#+begin_src python
177# Import the Pandas module 179# Import the Pandas module
178import pandas 180import pandas
179 181
@@ -199,16 +201,16 @@ sample = pandas.concat([sample_east, sample_west], ignore_index=True)
199 201
200# Save the sample to Excel 202# Save the sample to Excel
201sample.to_excel(file_output) 203sample.to_excel(file_output)
202``` 204#+end_src
203 205
204## Stratified Systematic Sample 206** Stratified Systematic Sample
205 207
206This next example is quite useful if you need audit coverage over a certain time 208This next example is quite useful if you need audit coverage over a
207period. This code will generate samples for each month in the data and combine 209certain time period. This code will generate samples for each month in
208them all together at the end. Obviously, this code can be modified to stratify 210the data and combine them all together at the end. Obviously, this code
209by something other than months, if needed. 211can be modified to stratify by something other than months, if needed.
210 212
211```python 213#+begin_src python
212# Import the Pandas module 214# Import the Pandas module
213import pandas 215import pandas
214 216
@@ -248,21 +250,21 @@ def monthly_stratified_sample(df: pandas.DataFrame, date_column: str, num_select
248sample_size = 3 250sample_size = 3
249sample = monthly_stratified_sample(df, 'Date of Sale', sample_size) 251sample = monthly_stratified_sample(df, 'Date of Sale', sample_size)
250sample.to_excel(file_output) 252sample.to_excel(file_output)
251``` 253#+end_src
252 254
253### Documenting the Results 255*** Documenting the Results
254 256
255Once you've generated a proper sample, there are a few things left to do in 257Once you've generated a proper sample, there are a few things left to do
256order to properly ensure your process is reproducible. 258in order to properly ensure your process is reproducible.
257 259
2581. Document the sample. Make sure the resulting file is readable and includes 2601. Document the sample. Make sure the resulting file is readable and
259 the documentation listed in the next bullet. 261 includes the documentation listed in the next bullet.
2602. Include documentation around the data source, extraction techniques, any 2622. Include documentation around the data source, extraction techniques,
261 modifications made to the data, and be sure to include a copy of the script 263 any modifications made to the data, and be sure to include a copy of
262 itself. 264 the script itself.
2633. Whenever possible, perform a completeness and accuracy test to ensure your 2653. Whenever possible, perform a completeness and accuracy test to ensure
264 sample is coming from a complete and accurate population. To ensure 266 your sample is coming from a complete and accurate population. To
265 completeness, compare the record count from the data source to the record 267 ensure completeness, compare the record count from the data source to
266 count loaded into Python. To ensure accuracy, test a small sample against the 268 the record count loaded into Python. To ensure accuracy, test a small
267 source data (e.g., test 5 sales against the database to see if the details 269 sample against the source data (e.g., test 5 sales against the
268 are accurate). 270 database to see if the details are accurate).
content/blog/2021-10-09-apache-redirect.md deleted −45
@@ -1,45 +0,0 @@
1+++
2date = 2021-10-09
3title = "Apache Redirect HTML Files to a Directory"
4description = ""
5draft = false
6+++
7
8# The Problem
9
10After recently switching static site generators (SSG), my blog URLs changed with
11no option to preserve the classic `.html` extension at the end of my blog post
12URLs.
13
14I really disliked using my old SSG ([Jekyll](https://jekyllrb.com)) and prefer
15my new tool ([Zola](https://www.getzola.org)) much more, so I was determined to
16figure out a way to get the proper redirect set up so that people who find my
17posts online aren't constantly met by 404 errors.
18
19# The Solution
20
21To solve this problem, I really needed to solve two pieces:
22
231. Redirect all blog post URL requests from `/blog/some-post.html` to
24 `/blog/some-post/`.
252. Ensure that no other `.html` files are redirected, such as `index.html`.
26
27After _a lot_ of tweaking and testing, I believe I have finally found the
28solution. 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 `.htaccess` file will do the
37following:
38
391. Turn on the RewriteEngine so that we can modify URLs.
402. Ignore any `index.html` files from the rule we are about to specify.
413. Find any `.html` files within the website directory and redirect it to
42 exclude the file extension.
434. The final piece is adding the trailing slash (`/`) at the end of the URL -
44 you'll notice that I don't have an Apache rule for that since Apache handles
45 that automatically.
content/blog/2021-10-09-apache-redirect.org added +45
@@ -0,0 +1,45 @@
1#+date: <2021-10-09>
2#+title: Apache Redirect HTML Files to a Directory
3#+description:
4
5
6* The Problem
7
8After recently switching static site generators (SSG), my blog URLs
9changed with no option to preserve the classic =.html= extension at the
10end of my blog post URLs.
11
12I really disliked using my old SSG ([[https://jekyllrb.com][Jekyll]])
13and prefer my new tool ([[https://www.getzola.org][Zola]]) much more, so
14I was determined to figure out a way to get the proper redirect set up
15so that people who find my posts online aren't constantly met by 404
16errors.
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 =/blog/some-post.html= to
23 =/blog/some-post/=.
242. Ensure that no other =.html= files are redirected, such as
25 =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#+begin_src conf
31RewriteEngine On
32RewriteCond %{REQUEST_URI} !\index.html$ [NC]
33RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
34#+end_src
35
36This piece of code in the Apache =.conf= or =.htaccess= file will do the
37following:
38
391. Turn on the RewriteEngine so that we can modify URLs.
402. Ignore any =index.html= files from the rule we are about to specify.
413. Find any =.html= files within the website directory and redirect it
42 to exclude the file extension.
434. The final piece is adding the trailing slash (=/=) at the end of the
44 URL - you'll notice that I don't have an Apache rule for that since
45 Apache handles that automatically.
content/blog/2021-12-04-cisa.md deleted −193
@@ -1,193 +0,0 @@
1+++
2date = 2021-12-04
3title = "I Passed the CISA!"
4description = ""
5draft = false
6+++
7
8# What is the CISA?
9
10For those of you lucky enough not to be knee-deep in the world of IT/IS
11Auditing, [CISA](https://www.isaca.org/credentialing/cisa) stands for Certified
12Information Systems Auditor. This certification and exam are part of ISACA's
13suite of certifications. As I often explain it to people like my family, it
14basically means you're employed to use your knowledge of information systems,
15regulations, common threats, risks, etc. in order to assess an organization's
16current control of their risk. If a risk isn't controlled (and the company
17doesn't want to accept the risk), an IS auditor will suggest implementing a
18control to address that risk.
19
20Now, the CISA certification itself is, in my opinion, the main certification for
21this career. While certifications such as the CPA or CISSP are beneficial,
22nothing matches the power of the CISA for an IS auditor when it comes to getting
23hired, getting a raise/bonus, or earning respect in the field.
24
25However, to be honest, I am a skeptic of most certifications. I understand the
26value they hold in terms of how much you need to commit to studying or learning
27on the job, as well as the market value for certifications such as the CISA. But
28I also have known some very ~~incompetent~~ _less than stellar_ auditors who
29have CPAs, CISAs, CIAs, etc.
30
31The same goes for most industries: if a person is good at studying, they can
32earn the certification. However, that knowledge means nothing unless you're
33actually able to use it in real life and perform as expected of a certification
34holder. The challenge comes when people are hired or connected strictly because
35of their certifications or resume; you need to see a person work before you can
36assume them having a CISA means they're better than someone without the CISA.
37
38Okay, rant over. Certifications are generally accepted as a measuring stick of
39commitment and quality of an employee, so I am accepting it too.
40
41# Exam Content
42
43The CISA is broken down into five sections, each weighted with a percentage of
44test questions that may appear.
45
46Since the exam contains 150 questions, here's how those sections break down:
47
48| Exam Section | Percentage of Exam | Questions |
49| --------------- | ------------------ | --------- |
50| 1 | 21% | 32 |
51| 2 | 17% | 26 |
52| 3 | 12% | 18 |
53| 4 | 23% | 34 |
54| 5 | 27% | 40 |
55| **Grand Total** | **100%** | **150** |
56
57# My Studying Habits
58
59This part is a little hard for me to break down into specific detail due to the
60craziness of the last year. While I officially purchased my studying materials
61in December 2020 and opened them to "start studying" in January 2021, I really
62wasn't able to study much due to the demands of my job and personal life.
63
64Let me approach this from a few different viewpoints.
65
66## Study Materials
67
68Let's start by discussing the study materials I purchased. I'll be referring to
69#1 as the CRM and #2 as the QAE.
70
711. [CISA Review Manual, 27th Edition |
72 Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK)
732. [CISA Review Questions, Answers & Explanations Manual, 12th Edition |
74 Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK)
75
76The CRM is an excellent source of information and could honestly be used as a
77reference for most IS auditors as a learning reference during their daily audit
78responsibilities. However, it is **full** of information and can be overloading
79if you're not good at filtering out useless information while studying.
80
81The QAE is the real star of the show here. This book contains 1000 questions,
82separated by exam section, and a practice exam. My only complaint about the QAE
83is that each question is immediately followed with the correct answer and
84explanations below it, which means I had to use something to constantly cover
85the answers while I was studying.
86
87I didn't use the online database version of the QAE, but I've heard that it's
88easier to use than the printed book. However, it is more expensive ($299
89database vs $129 book) which might be important if you're paying for materials
90yourself.
91
92In terms of question difficulty, I felt that the QAE was a good representation
93of the actual exam. I've seen a lot of people online say it wasn't accurate to
94the exam or that it was much easier/harder, but I disagree with all of those.
95The exam was fairly similar to the QAE, just focusing on whichever topics they
96chose for my version of the exam.
97
98If you understand the concepts, skim the CRM (and read in-depth on topics you
99struggle with), and use the QAE to continue practicing exam-like questions, you
100should be fine. I didn't use any online courses, videos, etc. - the ISACA
101materials are more than enough.
102
103## Studying Process
104
105While I was able to briefly read through sections 1 and 2 in early 2021, I had
106to stop and take a break from February/March to September. I switched jobs in
107September, which allowed me a lot more free time to study.
108
109In September, I studied sections 3-5, took notes, and did a quick review of the
110section topics. Once I felt comfortable with my notes, I took a practice exam
111from the QAE manual and scored 70% (105/150).
112
113Here's a breakdown of my initial practice exam:
114
115| Exam Section | Incorrect | Correct | Grand Total | Percent |
116| --------------- | --------- | ------- | ----------- | ------- |
117| 1 | 8 | 25 | 33 | 76% |
118| 2 | 5 | 20 | 25 | 80% |
119| 3 | 6 | 12 | 18 | 67% |
120| 4 | 10 | 23 | 33 | 70% |
121| 5 | 16 | 25 | 41 | 61% |
122| **Grand Total** | **45** | **105** | **150** | **70%** |
123
124As I expected, my toughest sections were related to project management,
125development, implementation, and security.
126
127This just leaves October and November. For these months, I tried to practice
128every few days, doing 10 questions for each section, until the exam. This came
129out to 13 practice sessions, ~140 questions per section, and ~700 questions
130total.
131
132While some practice sessions were worse and some were better, the final results
133were similar to my practice exam results. As you can see below, my averages were
134slightly worse than my practice exam. However, I got in over 700 questions of
135practice and, most importantly, _I read through the explanations every time I
136answered incorrectly and learned from my mistakes_.
137
138| Exam Section | Incorrect | Correct | Grand Total | Percent |
139| --------------- | --------- | ------- | ----------- | ------- |
140| 1 | 33 | 108 | 141 | 77% |
141| 2 | 33 | 109 | 142 | 77% |
142| 3 | 55 | 89 | 144 | 62% |
143| 4 | 52 | 88 | 140 | 63% |
144| 5 | 55 | 85 | 140 | 61% |
145| **Grand Total** | **228** | **479** | **707** | **68%** |
146
147# Results
148
149Now, how do the practice scores reflect my actual results? After all, it's hard
150to tell how good a practice regimen is unless you see how it turns out.
151
152| Exam Section | Section Name | Score |
153| ------------ | ---------------------------------------------------------------- | ----- |
154| 1 | Information Systems Auditing Process | 678 |
155| 2 | Governance and Management of IT | 590 |
156| 3 | Information Systems Acquisition, Development, and Implementation | 721 |
157| 4 | Information Systems Operations and Business Resilience | 643 |
158| 5 | Protection of Information Assets | 511 |
159
160Now, in order to pass the CISA, you need at least 450 on a sliding scale of
161200-800. Personally, I really have no clue what an average CISA score is. After
162a _very_ brief look online, I can see that the high end is usually in the low
163700s. In addition, only about 50-60% of people pass the exam.
164
165Given this information, I feel great about my scores. 616 may not be phenomenal,
166and I wish I had done better on sections 2 & 5, but my practicing seems to have
167worked very well overall.
168
169However, the practice results do not conform to the actual results. Section 2
170was one of my highest practice sections and was my second-lowest score in the
171exam. Conversely, section 3 was my second-lowest practice section and turned out
172to be my highest actual score!
173
174After reflecting, it is obvious that if you have any background on the CISA
175topics at all, the most important part of studying is doing practice questions.
176You really need to understand how to read the questions critically and pick the
177best answer.
178
179# Looking Forward
180
181I am extremely happy that I was finally able to pass the CISA. Looking to the
182future, I'm not sure what's next in terms of professional learning. My current
183company offers internal learning courses, so I will most likely focus on that if
184I need to gain more knowledge in certain areas.
185
186To be fair, even if you pass the CISA, it's hard to become an expert on any
187specific topic found within. My career may take me in a different direction, and
188I might need to focus more on security or networking certifications (or possibly
189building a better analysis/visualization portfolio if I want to go into data
190analysis/science).
191
192All I know is that I am content at the moment and extremely proud of my
193accomplishment.
content/blog/2021-12-04-cisa.org added +205
@@ -0,0 +1,205 @@
1#+date: <2021-12-04>
2#+title: I Passed the CISA!
3#+description:
4
5
6* What is the CISA?
7
8For those of you lucky enough not to be knee-deep in the world of IT/IS
9Auditing, [[https://www.isaca.org/credentialing/cisa][CISA]] stands for
10Certified Information Systems Auditor. This certification and exam are
11part of ISACA's suite of certifications. As I often explain it to people
12like my family, it basically means you're employed to use your knowledge
13of information systems, regulations, common threats, risks, etc. in
14order to assess an organization's current control of their risk. If a
15risk isn't controlled (and the company doesn't want to accept the risk),
16an IS auditor will suggest implementing a control to address that risk.
17
18Now, the CISA certification itself is, in my opinion, the main
19certification for this career. While certifications such as the CPA or
20CISSP are beneficial, nothing matches the power of the CISA for an IS
21auditor when it comes to getting hired, getting a raise/bonus, or
22earning respect in the field.
23
24However, to be honest, I am a skeptic of most certifications. I
25understand the value they hold in terms of how much you need to commit
26to studying or learning on the job, as well as the market value for
27certifications such as the CISA. But I also have known some very
28+incompetent+ /less than stellar/ auditors who have CPAs, CISAs, CIAs,
29etc.
30
31The same goes for most industries: if a person is good at studying, they
32can earn the certification. However, that knowledge means nothing unless
33you're actually able to use it in real life and perform as expected of a
34certification holder. The challenge comes when people are hired or
35connected strictly because of their certifications or resume; you need
36to see a person work before you can assume them having a CISA means
37they're better than someone without the CISA.
38
39Okay, rant over. Certifications are generally accepted as a measuring
40stick of commitment and quality of an employee, so I am accepting it
41too.
42
43* Exam Content
44
45The CISA is broken down into five sections, each weighted with a
46percentage of test questions that may appear.
47
48Since the exam contains 150 questions, here's how those sections break
49down:
50
51| Exam Section | Percentage of Exam | Questions |
52|---------------+--------------------+-----------|
53| 1 | 21% | 32 |
54| 2 | 17% | 26 |
55| 3 | 12% | 18 |
56| 4 | 23% | 34 |
57| 5 | 27% | 40 |
58| *Grand Total* | *100%* | *150* |
59
60* My Studying Habits
61
62This part is a little hard for me to break down into specific detail due
63to the craziness of the last year. While I officially purchased my
64studying materials in December 2020 and opened them to "start studying"
65in January 2021, I really wasn't able to study much due to the demands
66of my job and personal life.
67
68Let me approach this from a few different viewpoints.
69
70** Study Materials
71
72Let's start by discussing the study materials I purchased. I'll be
73referring to #1 as the CRM and #2 as the QAE.
74
751. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK][CISA
76 Review Manual, 27th Edition | Print]]
772. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK][CISA
78 Review Questions, Answers & Explanations Manual, 12th Edition |
79 Print]]
80
81The CRM is an excellent source of information and could honestly be used
82as a reference for most IS auditors as a learning reference during their
83daily audit responsibilities. However, it is *full* of information and
84can be overloading if you're not good at filtering out useless
85information while studying.
86
87The QAE is the real star of the show here. This book contains 1000
88questions, separated by exam section, and a practice exam. My only
89complaint about the QAE is that each question is immediately followed
90with the correct answer and explanations below it, which means I had to
91use something to constantly cover the answers while I was studying.
92
93I didn't use the online database version of the QAE, but I've heard that
94it's easier to use than the printed book. However, it is more expensive
95($299 database vs $129 book) which might be important if you're paying
96for materials yourself.
97
98In terms of question difficulty, I felt that the QAE was a good
99representation of the actual exam. I've seen a lot of people online say
100it wasn't accurate to the exam or that it was much easier/harder, but I
101disagree with all of those. The exam was fairly similar to the QAE, just
102focusing on whichever topics they chose for my version of the exam.
103
104If you understand the concepts, skim the CRM (and read in-depth on
105topics you struggle with), and use the QAE to continue practicing
106exam-like questions, you should be fine. I didn't use any online
107courses, videos, etc. - the ISACA materials are more than enough.
108
109** Studying Process
110
111While I was able to briefly read through sections 1 and 2 in early 2021,
112I had to stop and take a break from February/March to September. I
113switched jobs in September, which allowed me a lot more free time to
114study.
115
116In September, I studied sections 3-5, took notes, and did a quick review
117of the section topics. Once I felt comfortable with my notes, I took a
118practice exam from the QAE manual and scored 70% (105/150).
119
120Here's a breakdown of my initial practice exam:
121
122| Exam Section | Incorrect | Correct | Grand Total | Percent |
123|---------------+-----------+---------+-------------+---------|
124| 1 | 8 | 25 | 33 | 76% |
125| 2 | 5 | 20 | 25 | 80% |
126| 3 | 6 | 12 | 18 | 67% |
127| 4 | 10 | 23 | 33 | 70% |
128| 5 | 16 | 25 | 41 | 61% |
129| *Grand Total* | *45* | *105* | *150* | *70%* |
130
131As I expected, my toughest sections were related to project management,
132development, implementation, and security.
133
134This just leaves October and November. For these months, I tried to
135practice every few days, doing 10 questions for each section, until the
136exam. This came out to 13 practice sessions, ~140 questions per section,
137and ~700 questions total.
138
139While some practice sessions were worse and some were better, the final
140results were similar to my practice exam results. As you can see below,
141my averages were slightly worse than my practice exam. However, I got in
142over 700 questions of practice and, most importantly, /I read through
143the explanations every time I answered incorrectly and learned from my
144mistakes/.
145
146| Exam Section | Incorrect | Correct | Grand Total | Percent |
147|---------------+-----------+---------+-------------+---------|
148| 1 | 33 | 108 | 141 | 77% |
149| 2 | 33 | 109 | 142 | 77% |
150| 3 | 55 | 89 | 144 | 62% |
151| 4 | 52 | 88 | 140 | 63% |
152| 5 | 55 | 85 | 140 | 61% |
153| *Grand Total* | *228* | *479* | *707* | *68%* |
154
155* Results
156
157Now, how do the practice scores reflect my actual results? After all,
158it's hard to tell how good a practice regimen is unless you see how it
159turns out.
160
161| Exam Section | Section Name | Score |
162|--------------+------------------------------------------------------------------+-------|
163| 1 | Information Systems Auditing Process | 678 |
164| 2 | Governance and Management of IT | 590 |
165| 3 | Information Systems Acquisition, Development, and Implementation | 721 |
166| 4 | Information Systems Operations and Business Resilience | 643 |
167| 5 | Protection of Information Assets | 511 |
168
169Now, in order to pass the CISA, you need at least 450 on a sliding scale
170of 200-800. Personally, I really have no clue what an average CISA score
171is. After a /very/ brief look online, I can see that the high end is
172usually in the low 700s. In addition, only about 50-60% of people pass
173the exam.
174
175Given this information, I feel great about my scores. 616 may not be
176phenomenal, and I wish I had done better on sections 2 & 5, but my
177practicing seems to have worked very well overall.
178
179However, the practice results do not conform to the actual results.
180Section 2 was one of my highest practice sections and was my
181second-lowest score in the exam. Conversely, section 3 was my
182second-lowest practice section and turned out to be my highest actual
183score!
184
185After reflecting, it is obvious that if you have any background on the
186CISA topics at all, the most important part of studying is doing
187practice questions. You really need to understand how to read the
188questions critically and pick the best answer.
189
190* Looking Forward
191
192I am extremely happy that I was finally able to pass the CISA. Looking
193to the future, I'm not sure what's next in terms of professional
194learning. My current company offers internal learning courses, so I will
195most likely focus on that if I need to gain more knowledge in certain
196areas.
197
198To be fair, even if you pass the CISA, it's hard to become an expert on
199any specific topic found within. My career may take me in a different
200direction, and I might need to focus more on security or networking
201certifications (or possibly building a better analysis/visualization
202portfolio if I want to go into data analysis/science).
203
204All I know is that I am content at the moment and extremely proud of my
205accomplishment.
content/blog/2022-02-10-leaving-the-office.md deleted −207
@@ -1,207 +0,0 @@
1+++
2date = 2022-02-10
3title = "Leaving Office-Based Work in the Past"
4description = ""
5draft = false
6+++
7
8# The Working World is Changing
9
10There has been a trend for the past few years of companies slowly realizing that
11the pandemic is not just a temporary state that will go away eventually and let
12everything return to the way it was before. In terms of business and employment,
13this means that more and more jobs are being offered as permanently remote
14roles.
15
16I had always dreamt of working from home but thought of it as a fantasy,
17especially since I did not want to move over into the software development
18field. However, I have found that almost all roles being sent to me via
19recruiters are permanently remote (although most are limited to US citizens or
20even region-locked for companies who only operate in select states).
21
22I decided to take a look back at my relatively short career so far and compare
23the positive and negative effects of the different work environments I've been
24in.
25
26# In-Person Offices
27
28## Retail Internship
29
30I started my first job as a management intern at a busy retail pharmacy, working
31my 40-hour weeks on my feet. As these retail stores don't believe in resting or
32sitting down, you can guarantee that you will spend entire shifts standing,
33walking, or running around the store. Unfortunately, I worked at a time when our
34store didn't have enough managers, so I spent the majority of my tenure at the
35store running and breaking a sweat.
36
37Now, things aren't all bad in retail stores like this. It is definitely tiring
38and inefficient to force employees to work constantly, or pretend to work if
39there's nothing to do, and not allow anyone to sit down. However, if you are
40able to operate a retail store with a limited crew and provide enough comfort
41and support, I believe these jobs could be both comfortable and efficient.
42
43## Semi-Private Cubicles
44
45After about a year, I was able to find another internship - this time, it was in
46my field of interest: internal auditing. This was for a life insurance company
47that was well over 100 years old. The age of the company shows if you work
48there, as most people in management are well into their 40s-60s with little to
49no youthful leadership in the company. Likewise, they owned a large headquarters
50in a nice area of town with plenty of space, parking, etc.
51
52One upside is that each person gets their own large L-shaped desk, formed into
53cubicles that house 4 desks/employees. These "pods" of 4-person cubicles are
54linked throughout each floor of the headquarters (except the sales people, who
55had that open-floor concept going on). The walls of the cubicle were tall and
56provided a lot of privacy and sound-proofing, except when I used the standing
57desk feature (I'm over 6 feet tall, so probably not an issue for most people).
58
59I loved this environment, it allowed me to focus on my work with minimal
60distractions, but also allowed easy access, so I could spin around in my chair
61and chat with my friends without leaving my chair. This is the closest I've been
62to a home office environment (which is my personal favorite, as I'll get to
63later in this post).
64
65## Semi-Open Floor Concept
66
67When I shifted to my first full-time internal audit job out of college, I was
68working at a company that was headquartered on a floor in a downtown high-rise
69building. The company was only about 20 years old when I worked there and were
70trying a lot of new things to attract young talent, one of which was a semi-open
71floor concept for the office. My department worked just around the hallway
72corner from the executive offices and used that "modern" layout young tech
73companies started using in the 2000s/2010s.
74
75Each desk was brief, and you could look most coworkers in the face without
76moving from your chair, I hated this so much. Directly to my left was the Chief
77Audit Executive (our department's leading boss), and his desk was pointed so
78that his face would stare straight at my desk all day. I spent more time
79thinking about who was looking at me or checking on me than actually working.
80
81The other annoying part of the open concept they used was that the kitchen area
82and pathways were too close to everyone's desks (since the desks were spread
83out, to provide space or something), so noise and conversation would be constant
84throughout the day while you try to work. For someone like me, who needs silence
85to get work done, that was a non-starter.
86
87## Hotel Office Concept
88
89I currently work for a company remotely (for now) and travel to the office every
90once in a while for events and to help coach the staff underneath me. The office
91I visit uses the hotel desk concept, where you need to check in at a touch
92screen when you enter the office and "rent" a desk for the day. The same goes
93for offices and meeting rooms.
94
95These desks are flat-top only and do not have any walls at all. In addition,
96they're stacked with one row of 4 desks facing another row of 4 desks. These
97pairs of desk rows are repeated through the office.
98
99This means that when I go, I need to rent a random desk or try to remember the
100unique ID numbers on desks I like. Once I rent it, I have to make sure no one
101sat down in that desk without renting it. Then, I can sit down and work, but
102will probably need to adjust the monitors so that I'm not staring in the face of
103the person across from me all day. Finally, I need to wear headphones as this
104environment does nothing to provide you with peace or quiet.
105
106Luckily, you can rent offices with doors that offer quiet and privacy, which can
107be very nice if you have a lot of meetings or webinars on a certain day.
108
109# Home Office
110
111Okay, now let's finally get to the home office concept. I have worked from home
112for a little over two years at this point, across three different
113jobs/employers. Over this time, I have experimented with a plethora of different
114organizational ideas, desks, and room layouts to find what works best for me.
115
116These things might not apply to you, and that's fine. Everyone has a different
117situation, and I really don't think you'll know what works until you try.
118
119## Tip #1
120
121Let's start with my top rule for a home office:
122
123> If you live with others, working in a shared space is not effective.
124
125It just does not work. If you have another person sleeping in your bedroom, it
126is difficult to manage your work schedule with their sleeping/work/school
127schedule. If they wake up after you need to start work, you might wake them up
128or have to suffer the agony of staring at bright screens in a dark room.
129
130In a similar vein, working from a location such as the living room likely won't
131work either. Distractions will come far more frequently: televisions, cooking,
132cleaning, deliveries, etc. If you're like me, you'll end up playing a game
133instead of actually doing any work.
134
135## Tip #2
136
137Okay, the second thing I've discovered that works for me:
138
139> Use the pomodoro method (or something similar) to balance work tasks with
140> personal tasks.
141
142I use a very casual version of the pomodoro method where I will work for 1-2
143hours (usually set in strict intervals like 1, 1.5, 2 hours) and then will allow
144myself 30-60 minutes for personal tasks. This schedule works for me, since my
145work schedule really only comes to 3-6 hours of work per day.
146
147In this case, I'll work through my list of tasks for an hour or two and then
148give myself personal time to get drinks and food, wash dishes, put clothes in
149the washer, get the mail, etc. If you're in a convenient location, this usually
150gives time for things like getting groceries (as long as you're not a slow
151shopper).
152
153## Tip #3
154
155While I listed this one as number three, I don't think I'd accomplish anything
156without it:
157
158> Document everything: even things you didn't before - such as task lists and
159> notes from casual calls or meetings.
160
161I've noticed that staying in an office gave me more constant reminders of
162outstanding tasks or facts I had learned in a conversation. Translating
163everything to a digital world has made me lose a bit of that focus (perhaps
164since I don't have visual reminders?).
165
166Keeping a running task list of all things I have to do - even potential tasks! -
167has helped me keep up without missing anything small. Likewise, keeping notes
168for ALL meetings and calls, no matter how casual/quick, has improved my
169retention immensely. Beyond helping my mental recollection, it has saved me
170numerous times when I need to do a keyword search for some topic that was
171discussed 6+ months ago.
172
173## Tip #4
174
175Okay, last one for now.
176
177> Keep your work area clean.
178
179This one is straightforward, but I know some people struggle with cleanliness or
180may not believe it makes a difference. Trust me, keeping your desk area clean
181and organized makes a huge difference, both mentally and emotionally.
182
183Just think about it, you walk into your home office and see a clean desk with a
184laptop, dock, monitors, keyboard, mouse, and a notepad with a pen on top.
185
186Now imagine the opposite, there's an office with the same equipment, but there
187are clothes hanging on the chair, empty drink bottles, candy wrappers and dirty
188plates. This can take both a mental and emotional toll by bringing constant
189disarray and stress into your working environment.
190
191Just keep things clean each day, and you won't need to do any big cleaning days
192to recover.
193
194# My Preferences
195
196I've talked about the different environments I've worked in and expressed some
197honest thoughts on pros or cons to each, but what do I prefer? Well, if you're
198reading along, you should be able to tell that I much prefer a home office above
199all else.
200
201Being able to control my own day and allot my time as needed has brought a
202calmness to my life and has allowed me to maximize each day. I feel far more
203effective and efficient in a home office than any other office, especially
204open-office layouts.
205
206If I do need to return to an office part-time in the future, I really hope the
207office will have privacy and quietness in order for me to get my work done.
content/blog/2022-02-10-leaving-the-office.org added +231
@@ -0,0 +1,231 @@
1#+date: <2022-02-10>
2#+title: Leaving Office-Based Work in the Past
3#+description:
4
5
6* The Working World is Changing
7
8There has been a trend for the past few years of companies slowly
9realizing that the pandemic is not just a temporary state that will go
10away eventually and let everything return to the way it was before. In
11terms of business and employment, this means that more and more jobs are
12being offered as permanently remote roles.
13
14I had always dreamt of working from home but thought of it as a fantasy,
15especially since I did not want to move over into the software
16development field. However, I have found that almost all roles being
17sent to me via recruiters are permanently remote (although most are
18limited to US citizens or even region-locked for companies who only
19operate in select states).
20
21I decided to take a look back at my relatively short career so far and
22compare the positive and negative effects of the different work
23environments I've been in.
24
25* In-Person Offices
26
27** Retail Internship
28
29I started my first job as a management intern at a busy retail pharmacy,
30working my 40-hour weeks on my feet. As these retail stores don't
31believe in resting or sitting down, you can guarantee that you will
32spend entire shifts standing, walking, or running around the store.
33Unfortunately, I worked at a time when our store didn't have enough
34managers, so I spent the majority of my tenure at the store running and
35breaking a sweat.
36
37Now, things aren't all bad in retail stores like this. It is definitely
38tiring and inefficient to force employees to work constantly, or pretend
39to work if there's nothing to do, and not allow anyone to sit down.
40However, if you are able to operate a retail store with a limited crew
41and provide enough comfort and support, I believe these jobs could be
42both comfortable and efficient.
43
44** Semi-Private Cubicles
45
46After about a year, I was able to find another internship - this time,
47it was in my field of interest: internal auditing. This was for a life
48insurance company that was well over 100 years old. The age of the
49company shows if you work there, as most people in management are well
50into their 40s-60s with little to no youthful leadership in the company.
51Likewise, they owned a large headquarters in a nice area of town with
52plenty of space, parking, etc.
53
54One upside is that each person gets their own large L-shaped desk,
55formed into cubicles that house 4 desks/employees. These "pods" of
564-person cubicles are linked throughout each floor of the headquarters
57(except the sales people, who had that open-floor concept going on). The
58walls of the cubicle were tall and provided a lot of privacy and
59sound-proofing, except when I used the standing desk feature (I'm over 6
60feet tall, so probably not an issue for most people).
61
62I loved this environment, it allowed me to focus on my work with minimal
63distractions, but also allowed easy access, so I could spin around in my
64chair and chat with my friends without leaving my chair. This is the
65closest I've been to a home office environment (which is my personal
66favorite, as I'll get to later in this post).
67
68** Semi-Open Floor Concept
69
70When I shifted to my first full-time internal audit job out of college,
71I was working at a company that was headquartered on a floor in a
72downtown high-rise building. The company was only about 20 years old
73when I worked there and were trying a lot of new things to attract young
74talent, one of which was a semi-open floor concept for the office. My
75department worked just around the hallway corner from the executive
76offices and used that "modern" layout young tech companies started using
77in the 2000s/2010s.
78
79Each desk was brief, and you could look most coworkers in the face
80without moving from your chair, I hated this so much. Directly to my
81left was the Chief Audit Executive (our department's leading boss), and
82his desk was pointed so that his face would stare straight at my desk
83all day. I spent more time thinking about who was looking at me or
84checking on me than actually working.
85
86The other annoying part of the open concept they used was that the
87kitchen area and pathways were too close to everyone's desks (since the
88desks were spread out, to provide space or something), so noise and
89conversation would be constant throughout the day while you try to work.
90For someone like me, who needs silence to get work done, that was a
91non-starter.
92
93** Hotel Office Concept
94
95I currently work for a company remotely (for now) and travel to the
96office every once in a while for events and to help coach the staff
97underneath me. The office I visit uses the hotel desk concept, where you
98need to check in at a touch screen when you enter the office and "rent"
99a desk for the day. The same goes for offices and meeting rooms.
100
101These desks are flat-top only and do not have any walls at all. In
102addition, they're stacked with one row of 4 desks facing another row of
1034 desks. These pairs of desk rows are repeated through the office.
104
105This means that when I go, I need to rent a random desk or try to
106remember the unique ID numbers on desks I like. Once I rent it, I have
107to make sure no one sat down in that desk without renting it. Then, I
108can sit down and work, but will probably need to adjust the monitors so
109that I'm not staring in the face of the person across from me all day.
110Finally, I need to wear headphones as this environment does nothing to
111provide you with peace or quiet.
112
113Luckily, you can rent offices with doors that offer quiet and privacy,
114which can be very nice if you have a lot of meetings or webinars on a
115certain day.
116
117* Home Office
118
119Okay, now let's finally get to the home office concept. I have worked
120from home for a little over two years at this point, across three
121different jobs/employers. Over this time, I have experimented with a
122plethora of different organizational ideas, desks, and room layouts to
123find what works best for me.
124
125These things might not apply to you, and that's fine. Everyone has a
126different situation, and I really don't think you'll know what works
127until you try.
128
129** Tip #1
130
131Let's start with my top rule for a home office:
132
133#+begin_quote
134If you live with others, working in a shared space is not effective.
135#+end_quote
136
137It just does not work. If you have another person sleeping in your
138bedroom, it is difficult to manage your work schedule with their
139sleeping/work/school schedule. If they wake up after you need to start
140work, you might wake them up or have to suffer the agony of staring at
141bright screens in a dark room.
142
143In a similar vein, working from a location such as the living room
144likely won't work either. Distractions will come far more frequently:
145televisions, cooking, cleaning, deliveries, etc. If you're like me,
146you'll end up playing a game instead of actually doing any work.
147
148** Tip #2
149
150Okay, the second thing I've discovered that works for me:
151
152#+begin_quote
153Use the pomodoro method (or something similar) to balance work tasks
154with personal tasks.
155#+end_quote
156
157I use a very casual version of the pomodoro method where I will work for
1581-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and
159then will allow myself 30-60 minutes for personal tasks. This schedule
160works for me, since my work schedule really only comes to 3-6 hours of
161work per day.
162
163In this case, I'll work through my list of tasks for an hour or two and
164then give myself personal time to get drinks and food, wash dishes, put
165clothes in the washer, get the mail, etc. If you're in a convenient
166location, this usually gives time for things like getting groceries (as
167long as you're not a slow shopper).
168
169** Tip #3
170
171While I listed this one as number three, I don't think I'd accomplish
172anything without it:
173
174#+begin_quote
175Document everything: even things you didn't before - such as task lists
176and notes from casual calls or meetings.
177#+end_quote
178
179I've noticed that staying in an office gave me more constant reminders
180of outstanding tasks or facts I had learned in a conversation.
181Translating everything to a digital world has made me lose a bit of that
182focus (perhaps since I don't have visual reminders?).
183
184Keeping a running task list of all things I have to do - even potential
185tasks! - has helped me keep up without missing anything small. Likewise,
186keeping notes for ALL meetings and calls, no matter how casual/quick,
187has improved my retention immensely. Beyond helping my mental
188recollection, it has saved me numerous times when I need to do a keyword
189search for some topic that was discussed 6+ months ago.
190
191** Tip #4
192
193Okay, last one for now.
194
195#+begin_quote
196Keep your work area clean.
197#+end_quote
198
199This one is straightforward, but I know some people struggle with
200cleanliness or may not believe it makes a difference. Trust me, keeping
201your desk area clean and organized makes a huge difference, both
202mentally and emotionally.
203
204Just think about it, you walk into your home office and see a clean desk
205with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen
206on top.
207
208Now imagine the opposite, there's an office with the same equipment, but
209there are clothes hanging on the chair, empty drink bottles, candy
210wrappers and dirty plates. This can take both a mental and emotional
211toll by bringing constant disarray and stress into your working
212environment.
213
214Just keep things clean each day, and you won't need to do any big
215cleaning days to recover.
216
217* My Preferences
218
219I've talked about the different environments I've worked in and
220expressed some honest thoughts on pros or cons to each, but what do I
221prefer? Well, if you're reading along, you should be able to tell that I
222much prefer a home office above all else.
223
224Being able to control my own day and allot my time as needed has brought
225a calmness to my life and has allowed me to maximize each day. I feel
226far more effective and efficient in a home office than any other office,
227especially open-office layouts.
228
229If I do need to return to an office part-time in the future, I really
230hope the office will have privacy and quietness in order for me to get
231my work done.
content/blog/2022-02-10-njalla-dns-api.md → content/blog/2022-02-10-njalla-dns-api.org renamed +81 −79
@@ -1,104 +1,106 @@
1+++ 1#+date: <2022-02-10>
2date = 2022-02-10 2#+title: Dynamic DNS with Njalla API
3title = "Dynamic DNS with Njalla API" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Njalla's API
9 5
10As noted in my recent post about [switching to Njalla from 6* Njalla's API
11Cloudflare](file:///blog/ditching-cloudflare/), I was searching for a way to
12replace my very easy-to-use bash script to [update Cloudflare's DNS via their
13API](file:///blog/cloudflare-dns-api/).
14 7
15To reiterate what I said in those posts, this is a common necessity for those of 8As noted in my recent post about
16us who have non-static IP addresses that can change at any moment due to ISP 9[[file:///blog/ditching-cloudflare/][switching to Njalla from
17policy. 10Cloudflare]], I was searching for a way to replace my very easy-to-use
11bash script to [[file:///blog/cloudflare-dns-api/][update Cloudflare's
12DNS via their API]].
18 13
19In order to keep a home server running smoothly, the server admin needs to have 14To reiterate what I said in those posts, this is a common necessity for
20a process to constantly monitor their public IP address and update their 15those of us who have non-static IP addresses that can change at any
21domain's DNS records if it changes. 16moment due to ISP policy.
22 17
23This post explains how to use Python to update Njalla's DNS records whenever a 18In order to keep a home server running smoothly, the server admin needs
24machine's public IP address changes. 19to have a process to constantly monitor their public IP address and
20update their domain's DNS records if it changes.
25 21
26## Creating a Token 22This post explains how to use Python to update Njalla's DNS records
23whenever a machine's public IP address changes.
27 24
28To use Njalla's API, you will first need to create a token that will be used to 25** Creating a Token
29authenticate you every time you call the API. Luckily, this is very easy to do
30if you have an account with Njalla.
31 26
32Simply go the [API Settings](https://njal.la/settings/api/) page and click the 27To use Njalla's API, you will first need to create a token that will be
33`Add Token` button. Next, enter a name for the token and click `Add`. 28used to authenticate you every time you call the API. Luckily, this is
29very easy to do if you have an account with Njalla.
34 30
35Finally, click the `Manage` button next to your newly created token and copy the 31Simply go the [[https://njal.la/settings/api/][API Settings]] page and
36`API Token` field. 32click the =Add Token= button. Next, enter a name for the token and click
33=Add=.
37 34
38## Finding the Correct API Request 35Finally, click the =Manage= button next to your newly created token and
36copy the =API Token= field.
39 37
40Once you have a token, you're ready to call the Njalla API for any number of 38** Finding the Correct API Request
41requests. For a full listing of available requests, see the [Njalla API
42Documentation](https://njal.la/api/).
43 39
44For this demo, we are using the `list-records` and `edit-record` requests. 40Once 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
42[[https://njal.la/api/][Njalla API Documentation]].
45 43
46The `list-records` request requires the following payload to be sent when 44For this demo, we are using the =list-records= and =edit-record=
47calling the API: 45requests.
46
47The =list-records= request requires the following payload to be sent
48when calling the API:
48 49
49```txt 50#+begin_src txt
50params: { 51params: {
51 domain: string 52 domain: string
52} 53}
53``` 54#+end_src
54 55
55The `edit-record` request requires the following payload to be sent when calling 56The =edit-record= request requires the following payload to be sent when
56the API: 57calling the API:
57 58
58```txt 59#+begin_src txt
59params: { 60params: {
60 domain: string 61 domain: string
61 id: int 62 id: int
62 content: string 63 content: string
63} 64}
64``` 65#+end_src
65 66
66# Server Set-Up 67* Server Set-Up
67 68
68To create this script, we will be using Python. By default, I use Python 3 on my 69To create this script, we will be using Python. By default, I use Python
69servers, so please note that I did not test this in Python 2, and I do not know 703 on my servers, so please note that I did not test this in Python 2,
70if Python 2 will work for this. 71and I do not know if Python 2 will work for this.
71 72
72## Creating the Script 73** Creating the Script
73 74
74First, find a suitable place to create your script. Personally, I just create a 75First, find a suitable place to create your script. Personally, I just
75directory called `ddns` in my home directory: 76create a directory called =ddns= in my home directory:
76 77
77```sh 78#+begin_src sh
78mkdir ~/ddns 79mkdir ~/ddns
79``` 80#+end_src
80 81
81Next, create a Python script file: 82Next, create a Python script file:
82 83
83```sh 84#+begin_src sh
84nano ~/ddns/ddns.py 85nano ~/ddns/ddns.py
85``` 86#+end_src
86 87
87The following code snippet is quite long, so I won't go into depth on each part. 88The following code snippet is quite long, so I won't go into depth on
88However, I suggest you read through the entire script before running it; it is 89each part. However, I suggest you read through the entire script before
89quite simple and contains comments to help explain each code block. 90running it; it is quite simple and contains comments to help explain
91each code block.
90 92
91:warning: **Note**: You will need to update the following variables for this to 93:warning: *Note*: You will need to update the following variables for
92work: 94this to work:
93 95
94- `token`: This is the Njalla API token you created earlier. 96- =token=: This is the Njalla API token you created earlier.
95- `user_domain`: This is the top-level domain you want to modify. 97- =user_domain=: This is the top-level domain you want to modify.
96- `include_subdomains`: Set this to `True` if you also want to modify 98- =include_subdomains=: Set this to =True= if you also want to modify
97 subdomains found under the TLD. 99 subdomains found under the TLD.
98- `subdomains`: If `include_subdomains` = `True`, you can include your list of 100- =subdomains=: If =include_subdomains= = =True=, you can include your
99 subdomains to be modified here. 101 list of subdomains to be modified here.
100 102
101```python 103#+begin_src python
102#!/usr/bin/python 104#!/usr/bin/python
103# -*- coding: utf-8 -*- 105# -*- coding: utf-8 -*-
104# Import Python modules 106# Import Python modules
@@ -177,29 +179,29 @@ for record in data['records']:
177 'does not match Njalla's value of', 179 'does not match Njalla's value of',
178 record['content'], '. Updating...') 180 record['content'], '. Updating...')
179 update_record(user_domain, record['id'], ipv6) 181 update_record(user_domain, record['id'], ipv6)
180``` 182#+end_src
181 183
182## Running the Script 184** Running the Script
183 185
184Once you've created the script and are ready to test it, run the following 186Once you've created the script and are ready to test it, run the
185command: 187following command:
186 188
187```sh 189#+begin_src sh
188python3 ~/ddns/ddns.py 190python3 ~/ddns/ddns.py
189``` 191#+end_src
190 192
191## Setting the Script to Run Automatically 193** Setting the Script to Run Automatically
192 194
193To make sure the scripts run automatically, add it to the `cron` file so that it 195To make sure the scripts run automatically, add it to the =cron= file so
194will run on a schedule. To do this, open the `cron` file: 196that it will run on a schedule. To do this, open the =cron= file:
195 197
196```sh 198#+begin_src sh
197crontab -e 199crontab -e
198``` 200#+end_src
199 201
200In the cron file, paste the following at the bottom of the editor in order to 202In the cron file, paste the following at the bottom of the editor in
201check the IP every five minutes: 203order to check the IP every five minutes:
202 204
203```sh 205#+begin_src sh
204*/5 ** ** ** ** python3 /home/<your_username>/ddns/ddns.py 206,*/5 ** ** ** ** python3 /home/<your_username>/ddns/ddns.py
205``` 207#+end_src
content/blog/2022-02-16-debian-and-nginx.md deleted −164
@@ -1,164 +0,0 @@
1+++
2date = 2022-02-16
3title = "Migrating to a New Web Server Setup with Debian, Nginx, and Agate"
4description = ""
5draft = false
6+++
7
8# Server OS: Debian
9
10I've used various Linux distributions throughout the years, but I've never used
11anything except Ubuntu for my servers. Why? I really have no idea, mostly just
12comfort around the commands and software availability.
13
14However, I have always wanted to try Debian as a server OS after testing it out
15in a VM a few years ago (side-note: I'd love to try Alpine too, but I always
16struggle with compatibility). So, I decided to launch a new VPS and use
17[Debian](https://www.debian.org) 11 as the OS. Spoiler alert: it feels identical
18to Ubuntu for my purposes.
19
20I did the normal things when first launching the VPS, such as adding a new user,
21locking down SSH, etc. If you want to see that level of detail, read my other
22post about [How to Set Up a VPS Web
23Server](https://cleberg.net/blog/how-to-set-up-a-vps-web-server/).
24
25All of this has been similar, apart from small things such as the location of
26users' home folders. No complaints at all from me - Debian seems great.
27
28# Web Server: Nginx
29
30Once I had the baseline server configuration set-up for Debian, I moved on to
31trying out [Nginx](https://nginx.org) as my web server software. This required
32me to install the `nginx` and `ufw` packages, as well as setting up the initial
33UFW config:
34
35```sh
36sudo apt install nginx ufw
37sudo ufw allow 'Nginx Full'
38sudo ufw allow SSH
39sudo ufw enable
40sudo ufw status
41sudo systemctl status nginx
42```
43
44Once I had the firewall set, I moved on to creating the directories and files
45for my website. This is very easy and is basically the same as setting up an
46Apache server, so no struggles here.
47
48```sh
49sudo mkdir -p /var/www/your_domain/html
50sudo chown -R $USER:$USER /var/www/your_domain/html
51sudo chmod -R 755 /var/www/your_domain
52nano /var/www/your_domain/html/index.html
53```
54
55The next part, creating the Nginx configuration files, is quite a bit different
56from Apache. First, you need to create the files in the `sites-available` folder
57and symlink it the `sites-enabled` folder.
58
59Creating the config file for your domain:
60
61```sh
62sudo nano /etc/nginx/sites-available/your_domain
63```
64
65Default content for an Nginx config file:
66
67```sh
68server {
69 listen 80;
70 listen [::]:80;
71
72 root /var/www/your_domain/html;
73 index index.html index.htm index.nginx-debian.html;
74
75 server_name your_domain www.your_domain;
76
77 location / {
78 try_files $uri $uri/ =404;
79 }
80}
81```
82
83Finally, symlink it together:
84
85```sh
86sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
87```
88
89This will make your site available to the public (as long as you have
90`your_domain` DNS records pointed at the server's IP address)!
91
92Next, I used [certbot](https://certbot.eff.org/) to issue an HTTPS certificate
93for my domains using the following commands:
94
95```sh
96sudo apt install snapd; sudo snap install core; sudo snap refresh core
97sudo snap install --classic certbot
98sudo ln -s /snap/bin/certbot /usr/bin/certbot
99sudo certbot --nginx
100```
101
102Now that certbot ran successfully and updated my Nginx config files to include a
103`443` server block of code, I went back in and edited the config file to include
104security HTTP headers. This part is optional, but is recommended for security
105purposes; you can even test a website's HTTP header security at [Security
106Headers](https://securityheaders.com/).
107
108The configuration below shows a set-up where you only want your website to serve
109content from its own domain, except for images and scripts, which may come from
110`nullitics.com`. All other content would be blocked from loading in a browser.
111
112```sh
113sudo nano /etc/nginx/sites-available/your_domain
114```
115
116```sh
117server {
118 ...
119 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'";
120 add_header X-Content-Type-Options "nosniff";
121 add_header X-XSS-Protection "1; mode=block";
122 add_header X-Frame-Options "DENY";
123 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
124 add_header Referrer-Policy "no-referrer";
125 ...
126}
127```
128
129```sh
130sudo systemctl restart nginx
131```
132
133## Nginx vs. Apache
134
135As I stated at the beginning, my historical hesitation with trying Nginx was
136that the differences in configuration formats scared me away from leaving
137Apache. However, I prefer Nginx to Apache for a few reasons:
138
1391. Nginx uses only one config file (`your_domain`) vs. Apache's two-file
140 approach for HTTP vs. HTTPS (`your_domain.conf` and
141 `your_domain-le-ssl.conf`).
1422. Symlinking new configurations files and reloading Nginx are way easier than
143 Apache's process of having to enable headers with `a2enmod mod_headers`,
144 enable PHP with `a2enmod php` (plus any other mods you need), and then
145 enabling sites with `a2ensite`, and THEN reloading Apache.
1463. The contents of the Nginx config files seem more organized and logical with
147 the curly-bracket approach. This is a minor reason, but everything just felt
148 cleaner while I was installing my sites and that had a big quality of life
149 impact on the installation for me.
150
151They're both great software packages, but Nginx just seems more organized and
152easier to use these days. I will certainly be exploring the Nginx docs to see
153what other fun things I can do with all of this.
154
155# Gemini Server: Agate
156
157Finally, I set up the Agate software on this server again to host my Gemini
158server content, using Rust as I have before. You can read my other post for more
159information on installing Agate: [Hosting a Gemini
160Server](https://cleberg.net/blog/hosting-a-gemini-server/).
161
162All in all, Debian + Nginx is very slick and I prefer it over my old combination
163of Ubuntu + Apache (although it's really just Nginx > Apache for me, since
164Debian seems mostly the same as Ubuntu is so far).
content/blog/2022-02-16-debian-and-nginx.org added +167
@@ -0,0 +1,167 @@
1#+date: <2022-02-16>
2#+title: Migrating to a New Web Server Setup with Debian, Nginx, and Agate
3#+description:
4
5
6* Server OS: Debian
7
8I've used various Linux distributions throughout the years, but I've
9never used anything except Ubuntu for my servers. Why? I really have no
10idea, mostly just comfort around the commands and software availability.
11
12However, I have always wanted to try Debian as a server OS after testing
13it out in a VM a few years ago (side-note: I'd love to try Alpine too,
14but I always struggle with compatibility). So, I decided to launch a new
15VPS and use [[https://www.debian.org][Debian]] 11 as the OS. Spoiler
16alert: it feels identical to Ubuntu for my purposes.
17
18I did the normal things when first launching the VPS, such as adding a
19new user, locking down SSH, etc. If you want to see that level of
20detail, read my other post about
21[[https://cleberg.net/blog/how-to-set-up-a-vps-web-server/][How to Set
22Up a VPS Web Server]].
23
24All of this has been similar, apart from small things such as the
25location of users' home folders. No complaints at all from me - Debian
26seems great.
27
28* Web Server: Nginx
29
30Once I had the baseline server configuration set-up for Debian, I moved
31on to trying out [[https://nginx.org][Nginx]] as my web server software.
32This required me to install the =nginx= and =ufw= packages, as well as
33setting up the initial UFW config:
34
35#+begin_src sh
36sudo apt install nginx ufw
37sudo ufw allow 'Nginx Full'
38sudo ufw allow SSH
39sudo ufw enable
40sudo ufw status
41sudo systemctl status nginx
42#+end_src
43
44Once I had the firewall set, I moved on to creating the directories and
45files for my website. This is very easy and is basically the same as
46setting up an Apache server, so no struggles here.
47
48#+begin_src sh
49sudo mkdir -p /var/www/your_domain/html
50sudo chown -R $USER:$USER /var/www/your_domain/html
51sudo chmod -R 755 /var/www/your_domain
52nano /var/www/your_domain/html/index.html
53#+end_src
54
55The next part, creating the Nginx configuration files, is quite a bit
56different from Apache. First, you need to create the files in the
57=sites-available= folder and symlink it the =sites-enabled= folder.
58
59Creating the config file for your domain:
60
61#+begin_src sh
62sudo nano /etc/nginx/sites-available/your_domain
63#+end_src
64
65Default content for an Nginx config file:
66
67#+begin_src sh
68server {
69 listen 80;
70 listen [::]:80;
71
72 root /var/www/your_domain/html;
73 index index.html index.htm index.nginx-debian.html;
74
75 server_name your_domain www.your_domain;
76
77 location / {
78 try_files $uri $uri/ =404;
79 }
80}
81#+end_src
82
83Finally, symlink it together:
84
85#+begin_src sh
86sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
87#+end_src
88
89This will make your site available to the public (as long as you have
90=your_domain= DNS records pointed at the server's IP address)!
91
92Next, I used [[https://certbot.eff.org/][certbot]] to issue an HTTPS
93certificate for my domains using the following commands:
94
95#+begin_src sh
96sudo apt install snapd; sudo snap install core; sudo snap refresh core
97sudo snap install --classic certbot
98sudo ln -s /snap/bin/certbot /usr/bin/certbot
99sudo certbot --nginx
100#+end_src
101
102Now that certbot ran successfully and updated my Nginx config files to
103include a =443= server block of code, I went back in and edited the
104config file to include security HTTP headers. This part is optional, but
105is recommended for security purposes; you can even test a website's HTTP
106header security at [[https://securityheaders.com/][Security Headers]].
107
108The configuration below shows a set-up where you only want your website
109to serve content from its own domain, except for images and scripts,
110which may come from =nullitics.com=. All other content would be blocked
111from loading in a browser.
112
113#+begin_src sh
114sudo nano /etc/nginx/sites-available/your_domain
115#+end_src
116
117#+begin_src sh
118server {
119 ...
120 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'";
121 add_header X-Content-Type-Options "nosniff";
122 add_header X-XSS-Protection "1; mode=block";
123 add_header X-Frame-Options "DENY";
124 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
125 add_header Referrer-Policy "no-referrer";
126 ...
127}
128#+end_src
129
130#+begin_src sh
131sudo systemctl restart nginx
132#+end_src
133
134** Nginx vs. Apache
135
136As I stated at the beginning, my historical hesitation with trying Nginx
137was that the differences in configuration formats scared me away from
138leaving Apache. However, I prefer Nginx to Apache for a few reasons:
139
1401. Nginx uses only one config file (=your_domain=) vs. Apache's two-file
141 approach for HTTP vs. HTTPS (=your_domain.conf= and
142 =your_domain-le-ssl.conf=).
1432. Symlinking new configurations files and reloading Nginx are way
144 easier than Apache's process of having to enable headers with
145 =a2enmod mod_headers=, enable PHP with =a2enmod php= (plus any other
146 mods you need), and then enabling sites with =a2ensite=, and THEN
147 reloading Apache.
1483. The contents of the Nginx config files seem more organized and
149 logical with the curly-bracket approach. This is a minor reason, but
150 everything just felt cleaner while I was installing my sites and that
151 had a big quality of life impact on the installation for me.
152
153They're both great software packages, but Nginx just seems more
154organized and easier to use these days. I will certainly be exploring
155the Nginx docs to see what other fun things I can do with all of this.
156
157* Gemini Server: Agate
158
159Finally, I set up the Agate software on this server again to host my
160Gemini server content, using Rust as I have before. You can read my
161other post for more information on installing Agate:
162[[https://cleberg.net/blog/hosting-a-gemini-server/][Hosting a Gemini
163Server]].
164
165All in all, Debian + Nginx is very slick and I prefer it over my old
166combination of Ubuntu + Apache (although it's really just Nginx > Apache
167for me, since Debian seems mostly the same as Ubuntu is so far).
content/blog/2022-02-17-exiftool.md deleted −59
@@ -1,59 +0,0 @@
1+++
2date = 2022-02-17
3title = "Stripping Image Metadata with Exiftool"
4description = ""
5draft = false
6+++
7
8## Why Strip Metadata?
9
10Okay, so you want to strip metadata from your photos. Perhaps you take pictures
11of very rare birds, and the location metadata is a gold mine for poachers, or
12perhaps you're just privacy-oriented like me and prefer to strip metadata from
13publicly-available images.
14
15There are various components of image metadata that you may want to delete
16before releasing a photo to the public. Here's an incomplete list of things I
17could easily see just by inspecting a photo on my laptop:
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 `exiftool`
31package in Linux to automatically strip metadata from all images in a directory
32(+ subdirectories).
33
34## Installing `exiftool`
35
36First things first: we need to install the tool. I'm running Debian 11 on my
37server (Ubuntu will work the same), so the command is as simple as:
38
39```sh
40sudo apt install exiftool
41```
42
43There are different tools that can accomplish the same thing across
44distributions, but I really only care to test out this one package.
45
46## Recursively Strip Data
47
48I actually use this tool extensively to strip any photos uploaded to the website
49that serves all the images for my blog (`img.cleberg.net`).
50
51The following command is incredibly useful and can be modified to include any
52image extensions that `exiftool` supports:
53
54```sh
55exiftool -r -all= -ext jpg -ext png /path/to/directory/
56```
57
58The output of the command will let you know how many directories were scanned,
59how many images were updated, and how many images were unchanged.
content/blog/2022-02-17-exiftool.org added +59
@@ -0,0 +1,59 @@
1#+date: <2022-02-17>
2#+title: Stripping Image Metadata with Exiftool
3#+description:
4
5
6** Why Strip Metadata?
7
8Okay, so you want to strip metadata from your photos. Perhaps you take
9pictures of very rare birds, and the location metadata is a gold mine
10for poachers, or perhaps you're just privacy-oriented like me and prefer
11to strip metadata from publicly-available images.
12
13There are various components of image metadata that you may want to
14delete before releasing a photo to the public. Here's an incomplete list
15of things I could easily see just by inspecting a photo on my laptop:
16
17- Location (Latitude & Longitude)
18- Dimensions
19- Device Make & Model
20- Color Space
21- Color Profile
22- Focal Length
23- Alpha Channel
24- Red Eye
25- Metering Mode
26- F Number
27
28Regardless of your reasoning, I'm going to explain how I used the
29=exiftool= package in Linux to automatically strip metadata from all
30images in a directory (+ subdirectories).
31
32** Installing =exiftool=
33
34First things first: we need to install the tool. I'm running Debian 11
35on my server (Ubuntu will work the same), so the command is as simple
36as:
37
38#+begin_src sh
39sudo apt install exiftool
40#+end_src
41
42There are different tools that can accomplish the same thing across
43distributions, but I really only care to test out this one package.
44
45** Recursively Strip Data
46
47I actually use this tool extensively to strip any photos uploaded to the
48website that serves all the images for my blog (=img.cleberg.net=).
49
50The following command is incredibly useful and can be modified to
51include any image extensions that =exiftool= supports:
52
53#+begin_src sh
54exiftool -r -all= -ext jpg -ext png /path/to/directory/
55#+end_src
56
57The output of the command will let you know how many directories were
58scanned, how many images were updated, and how many images were
59unchanged.
content/blog/2022-02-20-nginx-caching.md deleted −65
@@ -1,65 +0,0 @@
1+++
2date = 2022-02-20
3title = "Caching Static Content with Nginx"
4description = ""
5draft = false
6+++
7
8# Update Your Nginx Config to Cache Static Files
9
10If you run a website on Nginx that serves static content (i.e., content that is
11not dynamic and changing with interactions from the user), you would likely
12benefit from caching that content on the client-side. If you're used to Apache
13and looking for the Nginx equivalent, this post should help.
14
15Luckily, setting up the cache is as easy as identifying the file types you want
16to cache and determining the expiration length. To include more file types,
17simply use the bar separator (`|`) and type the new file extension you want to
18include.
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 `365d` or even `max`, but
33that is only for stable, infrequently changing websites. As my site often
34changes (i.e., I'm never content with my website), I need to know that my
35readers are seeing the new content without waiting too long.
36
37So, I went ahead and set the expiration date at `30d`, which is short enough to
38refresh for readers but long enough that clients/browsers won't be re-requesting
39the static files too often, hopefully resulting in faster loading times, as
40images should be the only thing slowing down my site.
41
42# Testing Results
43
44To test my changes to the Nginx configuration, I used the [HTTP Header
45Live](https://addons.mozilla.org/en-US/firefox/addon/http-header-live/)
46extension on my Gecko browser and used the sidebar to inspect the headers of a
47recent image from my blog.
48
49In the image below, you can see that the `Cache-Control` header is now present
50and set to 2592000, which is 30 days represented in seconds (30 days \_ 24
51hours/day \_ 60 minutes/hour \*\* 60 seconds/minute = 2,592,000 seconds).
52
53The `Expires` field is now showing 22 March 2022, which is 30 days from the day
54of this post, 20 February 2022.
55
56# Caveats
57
58Remember that this caching system is **client-side**, which means that content
59is only cached for as long as a client allows it. For example, my browser purges
60all caches, data, etc. upon exit, so this caching policy will only work as long
61as my browser remains open and running.
62
63If you need to test updates to your site, you'll need to clear the cache to see
64updates for any file extension you configured. This can often be done with the
65`Shift + F5` or `Ctrl + F5` key combinations in most browsers.
content/blog/2022-02-20-nginx-caching.org added +68
@@ -0,0 +1,68 @@
1#+date: <2022-02-20>
2#+title: Caching Static Content with Nginx
3#+description:
4
5
6* Update Your Nginx Config to Cache Static Files
7
8If you run a website on Nginx that serves static content (i.e., content
9that is not dynamic and changing with interactions from the user), you
10would likely benefit from caching that content on the client-side. If
11you're used to Apache and looking for the Nginx equivalent, this post
12should help.
13
14Luckily, setting up the cache is as easy as identifying the file types
15you want to cache and determining the expiration length. To include more
16file types, simply use the bar separator (=|=) and type the new file
17extension you want to include.
18
19#+begin_src config
20server {
21 ...
22
23 location ~** .(css|js|jpg|jpeg|gif|png|ico)$ {
24 expires 30d;
25 }
26
27 ...
28}
29#+end_src
30
31I have seen some people who prefer to set =expires= as =365d= or even
32=max=, but that is only for stable, infrequently changing websites. As
33my site often changes (i.e., I'm never content with my website), I need
34to know that my readers are seeing the new content without waiting too
35long.
36
37So, I went ahead and set the expiration date at =30d=, which is short
38enough to refresh for readers but long enough that clients/browsers
39won't be re-requesting the static files too often, hopefully resulting
40in faster loading times, as images should be the only thing slowing down
41my site.
42
43* Testing Results
44
45To test my changes to the Nginx configuration, I used the
46[[https://addons.mozilla.org/en-US/firefox/addon/http-header-live/][HTTP
47Header Live]] extension on my Gecko browser and used the sidebar to
48inspect the headers of a recent image from my blog.
49
50In the image below, you can see that the =Cache-Control= header is now
51present and set to 2592000, which is 30 days represented in seconds (30
52days _ 24 hours/day _ 60 minutes/hour ** 60 seconds/minute = 2,592,000
53seconds).
54
55The =Expires= field is now showing 22 March 2022, which is 30 days from
56the day of this post, 20 February 2022.
57
58* Caveats
59
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.
content/blog/2022-02-22-tuesday.md deleted −35
@@ -1,35 +0,0 @@
1+++
2date = 2022-02-22
3title = "Tuesday"
4description = ""
5draft = false
6+++
7
8# Tuesday, Twosday
9
10I'm taking a break from my usual technology-related posts and writing about
11something a little more enjoyable today.
12
13Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is Twosday.
14
15Let's look at everything that fell in place today:
16
171. Written in the `m-dd-yy` or `dd-m-yy` formats, today is 2-22-22 or 22-2-22,
18 which is a neat little palindrome in either format. (The last ubiquitous
19 six-digit palindrome was 1-11-11.)
202. Today is Tuesday, which is why everyone is using the nickname Twosday to call
21 out these similarities.
223. Falling on Tuesday means today is the 2nd day of the week (for most cultures.
23 For the US, it's the 3rd day of the week since we start on Sunday).
244. The only culture I could find with a connection to a `2` is that some Slavic
25 languages derived their version of Tuesday from the Old Church Slavonic word
26 `въторъ`, meaning "the second."
275. Written in the classic monospaced, digital font (think of digital clocks from
28 the 80s/90s), there is nice symmetry to the numbers ([view the image
29 here](https://img.cleberg.net/blog/20220222-tuesday/digital_font.png)!).
306. This one isn't naturally-occurring, but it seems people around the world are
31 celebrating the day. For example, a group is putting together [a wedding of
32 222 couples at the California State Capitol in
33 Sacramento](https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597),
34 concluding at exactly 2:22 PM. These couples will record their marriage dates
35 as 2-22-22 2:22 PM. Tickets were on sale for $222.22.
content/blog/2022-02-22-tuesday.org added +38
@@ -0,0 +1,38 @@
1#+date: <2022-02-22>
2#+title: Tuesday
3#+description:
4
5
6* Tuesday, Twosday
7
8I'm taking a break from my usual technology-related posts and writing
9about something a little more enjoyable today.
10
11Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
12Twosday.
13
14Let's look at everything that fell in place today:
15
161. Written in the =m-dd-yy= or =dd-m-yy= formats, today is 2-22-22 or
17 22-2-22, which is a neat little palindrome in either format. (The
18 last ubiquitous six-digit palindrome was 1-11-11.)
192. Today is Tuesday, which is why everyone is using the nickname Twosday
20 to call out these similarities.
213. Falling on Tuesday means today is the 2nd day of the week (for most
22 cultures. For the US, it's the 3rd day of the week since we start on
23 Sunday).
244. The only culture I could find with a connection to a =2= is that some
25 Slavic languages derived their version of Tuesday from the Old Church
26 Slavonic word =въторъ=, meaning "the second."
275. Written in the classic monospaced, digital font (think of digital
28 clocks from the 80s/90s), there is nice symmetry to the numbers
29 ([[https://img.cleberg.net/blog/20220222-tuesday/digital_font.png][view
30 the image here]]!).
316. This one isn't naturally-occurring, but it seems people around the
32 world are celebrating the day. For example, a group is putting
33 together
34 [[https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597][a
35 wedding of 222 couples at the California State Capitol in
36 Sacramento]], concluding at exactly 2:22 PM. These couples will
37 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
38 for $222.22.
content/blog/2022-03-02-reliable-notes.md deleted −127
@@ -1,127 +0,0 @@
1+++
2date = 2022-03-02
3title = "Easy, Reliable Note-Taking"
4description = ""
5draft = false
6+++
7
8# Choosing Durable File Formats
9
10> TL;DR: Write in a format that can be easily rendered and read in plain-text
11> mode (e.g., `.txt`, `.md`, etc.).
12
13As I've written more and more over the years, I've found that my love of
14note-taking is always growing. Everything I learn or need to remember can be
15written down in a note and saved digitally, with no cost to myself. Unlike paper
16copies that need physical storage space, digital files simply need space on your
17local disk or cloud storage, which is fairly abundant these days.
18
19However, I had a historical struggle with migration of notes between different
20apps that require different formats and applied proprietary styling. This meant
21that I had to go through each note during migration and edit the file to look
22presentable again.
23
24For the last year or two, I have written everything exclusively in
25[Markdown](https://en.wikipedia.org/wiki/Markdown) format. Small notes,
26long-form writings, and even these blog posts are all written in Markdown.
27
28Why Markdown? While I do appreciate the simplicity of plain-text files without
29any formatting, I often need visual cues such as heading and code blocks to keep
30my thoughts straight. Markdown provides a minimal set of styling indicators for
31me to style my notes without adding any proprietary, embedded data into the
32files. If I want a top-level heading, I simply add a hash (`#`) before the line.
33An added bonus is that even if a system doesn't understand Markdown, it will
34render it as plain-text and I can read it just as easily.
35
36For example, here's how TextEdit on macOS will open and display a Markdown file
37in plain-text, since it does contain any features to preview Markdown as HTML:
38
39![Plain Text
40Markdown](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png)
41
42## Saving & Syncing Files
43
44In order to read and edit my notes across platforms, I use my personal cloud
45storage through Tresorit due to its native integration with macOS and iOS file
46managers. In addition, Tresorit works well on Debian-based Linux distros, which
47I used before macOS (and will likely switch back to in a few years).
48
49You can use whatever sync software you want - syncing plain-text or markdown
50files is incredibly easy and fast, since the files are generally tiny.
51
52Since the cloud storage syncs files automatically, there is no need for me to
53sync anything manually or kick-off a sync job to update my files. This means
54that I can edit on mobile, and it takes about 5-10 seconds to see the changes on
55desktop.
56
57### Version Control with Git
58
59A different approach I've contemplated is storing my notes and attachments is
60using a hosted Git repository to track changes to the files. However, I don't
61want to rely on an external service that could potentially see into my data,
62even if the repository is private.
63
64I might just do `git init` locally and then commit my changes each time I write
65or update a note, but that seems to be a lot of work just for tracking changes -
66which I don't necessarily care to know.
67
68### Backups!
69
70One small addition to the storage/sync conversation is the idea of backups.
71Personally, I manually create periodic backups of my entire cloud storage,
72compress it into an archive, and store it on my home server.
73
74To improve my workflow, I am going to be exploring options to automatically
75compress the mounted cloud directory and send it over to my server on a set
76schedule.
77
78## Writing on Desktop
79
80> **Update (06.14.22)**: Since writing this post, I have reverted to simply
81> keeping my `notes` folder open and opening notes individually in TextEdit for
82> a more minimal and relaxing writing experience on the desktop.
83
84The bulk of my writing occurs in a desktop environment, with a full keyboard
85layout and wide screen. I don't illustrate with a smart pen, I rarely use
86embedded images, and I love being able to see all of my notes/directories in a
87sidebar.
88
89With this simple set of requirements, I chose [Obsidian](https://obsidian.md) as
90my desktop text editor. Obsidian has some in-depth tools like a graph view,
91command palette, mentions, etc., but I've found that using it as a simple
92Markdown editor is incredibly easy and straightforward.
93
94Here's an example of how my Markdown notes look when opened in plain-text mode:
95
96![Obsidian Markdown Source
97Mode](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png)
98
99Here's the "live preview" version, where the Markdown is rendered into its HTML
100format:
101
102![Obsidian Markdown Live
103Preview](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png)
104
105### Programming on Desktop
106
107While I was writing this, I realized I should specify that I don't use the same
108editor for writing notes and for writing code. For programming purposes, I use
109[VSCodium](https://vscodium.com) as my development IDE.
110
111## Writing on Mobile
112
113Personally, I write very little on mobile, except when needing to take important
114notes on-the-go. Any long-form writing, journals, etc. are done at home, where I
115always have my laptop available.
116
117I wanted a simple and foolproof editor for iOS, preferably open-source. After a
118long journey of testing the few (& terrible) open-source iOS note-taking apps, I
119finally found a phenomenal one:
120[Runestone](https://github.com/simonbs/runestone). This app is fantastic for
121note-taking, has plenty of optional features, and integrates natively with the
122iOS file manager.
123
124This app opens the iOS file manager and allows you to click any file you want,
125opens it up in an editor, and lets me save and close out of that note.
126
127Quite simple but effective.
content/blog/2022-03-02-reliable-notes.org added +156
@@ -0,0 +1,156 @@
1#+date: <2022-03-02>
2#+title: Easy, Reliable Note-Taking
3#+description:
4
5* Choosing Durable File Formats
6:PROPERTIES:
7:CUSTOM_ID: choosing-durable-file-formats
8:END:
9
10#+begin_quote
11TL;DR: Write in a format that can be easily rendered and read in
12plain-text mode (e.g., =.txt=, =.md=, etc.).
13#+end_quote
14
15As I've written more and more over the years, I've found that my love of
16note-taking is always growing. Everything I learn or need to remember
17can be written down in a note and saved digitally, with no cost to
18myself. Unlike paper copies that need physical storage space, digital
19files simply need space on your local disk or cloud storage, which is
20fairly abundant these days.
21
22However, I had a historical struggle with migration of notes between
23different apps that require different formats and applied proprietary
24styling. This meant that I had to go through each note during migration
25and edit the file to look presentable again.
26
27For the last year or two, I have written everything exclusively in
28[[https://en.wikipedia.org/wiki/Markdown][Markdown]] format. Small
29notes, long-form writings, and even these blog posts are all written in
30Markdown.
31
32Why Markdown? While I do appreciate the simplicity of plain-text files
33without any formatting, I often need visual cues such as heading and
34code blocks to keep my thoughts straight. Markdown provides a minimal
35set of styling indicators for me to style my notes without adding any
36proprietary, embedded data into the files. If I want a top-level
37heading, I simply add a hash (=#=) before the line. An added bonus is
38that even if a system doesn't understand Markdown, it will render it as
39plain-text and I can read it just as easily.
40
41For example, here's how TextEdit on macOS will open and display a
42Markdown file in plain-text, since it does contain any features to
43preview Markdown as HTML:
44
45#+caption: Plain Text Markdown
46[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png]]
47
48** Saving & Syncing Files
49:PROPERTIES:
50:CUSTOM_ID: saving-syncing-files
51:END:
52In order to read and edit my notes across platforms, I use my personal
53cloud storage through Tresorit due to its native integration with macOS
54and iOS file managers. In addition, Tresorit works well on Debian-based
55Linux distros, which I used before macOS (and will likely switch back to
56in a few years).
57
58You can use whatever sync software you want - syncing plain-text or
59markdown files is incredibly easy and fast, since the files are
60generally tiny.
61
62Since the cloud storage syncs files automatically, there is no need for
63me to sync anything manually or kick-off a sync job to update my files.
64This means that I can edit on mobile, and it takes about 5-10 seconds to
65see the changes on desktop.
66
67*** Version Control with Git
68:PROPERTIES:
69:CUSTOM_ID: version-control-with-git
70:END:
71A different approach I've contemplated is storing my notes and
72attachments is using a hosted Git repository to track changes to the
73files. However, I don't want to rely on an external service that could
74potentially see into my data, even if the repository is private.
75
76I might just do =git init= locally and then commit my changes each time
77I write or update a note, but that seems to be a lot of work just for
78tracking changes - which I don't necessarily care to know.
79
80*** Backups!
81:PROPERTIES:
82:CUSTOM_ID: backups
83:END:
84One small addition to the storage/sync conversation is the idea of
85backups. Personally, I manually create periodic backups of my entire
86cloud storage, compress it into an archive, and store it on my home
87server.
88
89To improve my workflow, I am going to be exploring options to
90automatically compress the mounted cloud directory and send it over to
91my server on a set schedule.
92
93** Writing on Desktop
94:PROPERTIES:
95:CUSTOM_ID: writing-on-desktop
96:END:
97
98#+begin_quote
99*Update (06.14.22)*: Since writing this post, I have reverted to simply
100keeping my =notes= folder open and opening notes individually in
101TextEdit for a more minimal and relaxing writing experience on the
102desktop.
103#+end_quote
104
105The bulk of my writing occurs in a desktop environment, with a full
106keyboard layout and wide screen. I don't illustrate with a smart pen, I
107rarely use embedded images, and I love being able to see all of my
108notes/directories in a sidebar.
109
110With this simple set of requirements, I chose
111[[https://obsidian.md][Obsidian]] as my desktop text editor. Obsidian
112has some in-depth tools like a graph view, command palette, mentions,
113etc., but I've found that using it as a simple Markdown editor is
114incredibly easy and straightforward.
115
116Here's an example of how my Markdown notes look when opened in
117plain-text mode:
118
119#+caption: Obsidian Markdown Source Mode
120[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png]]
121
122Here's the "live preview" version, where the Markdown is rendered into
123its HTML format:
124
125#+caption: Obsidian Markdown Live Preview
126[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png]]
127
128*** Programming on Desktop
129:PROPERTIES:
130:CUSTOM_ID: programming-on-desktop
131:END:
132While I was writing this, I realized I should specify that I don't use
133the same editor for writing notes and for writing code. For programming
134purposes, I use [[https://vscodium.com][VSCodium]] as my development
135IDE.
136
137** Writing on Mobile
138:PROPERTIES:
139:CUSTOM_ID: writing-on-mobile
140:END:
141Personally, I write very little on mobile, except when needing to take
142important notes on-the-go. Any long-form writing, journals, etc. are
143done at home, where I always have my laptop available.
144
145I wanted a simple and foolproof editor for iOS, preferably open-source.
146After a long journey of testing the few (& terrible) open-source iOS
147note-taking apps, I finally found a phenomenal one:
148[[https://github.com/simonbs/runestone][Runestone]]. This app is
149fantastic for note-taking, has plenty of optional features, and
150integrates natively with the iOS file manager.
151
152This app opens the iOS file manager and allows you to click any file you
153want, opens it up in an editor, and lets me save and close out of that
154note.
155
156Quite simple but effective.
content/blog/2022-03-03-financial-database.md → content/blog/2022-03-03-financial-database.org renamed +132 −113
@@ -1,66 +1,75 @@
1+++ 1#+date: <2022-03-03>
2date = 2022-03-03 2#+title: Maintaining a Personal Financial Database
3title = "Maintaining a Personal Financial Database" 3#+description:
4description = "" 4
5draft = false 5
6+++ 6* Personal Financial Tracking
7 7:PROPERTIES:
8# Personal Financial Tracking 8:CUSTOM_ID: personal-financial-tracking
9 9:END:
10For the last 6-ish years, I've tracked my finances in a spreadsheet. This is 10For the last 6-ish years, I've tracked my finances in a spreadsheet.
11common practice in the business world, but any good dev will cringe at the 11This is common practice in the business world, but any good dev will
12thought of storing long-term data in a spreadsheet. A spreadsheet is not for 12cringe at the thought of storing long-term data in a spreadsheet. A
13long-term storage or as a source of data to pull data/reports. 13spreadsheet is not for long-term storage or as a source of data to pull
14 14data/reports.
15As I wanted to expand the functionality of my financial data (e.g., adding more 15
16reports), I decided to migrate the data into a database. To run reports, I would 16As I wanted to expand the functionality of my financial data (e.g.,
17query the database and use a language like Python or Javascript to process the 17adding more reports), I decided to migrate the data into a database. To
18data, perform calculations, and visualize the data. 18run reports, I would query the database and use a language like Python
19 19or Javascript to process the data, perform calculations, and visualize
20# SQLite 20the data.
21 21
22When choosing the type of database I wanted to use for this project, I was split 22* SQLite
23between three options: 23:PROPERTIES:
24 24:CUSTOM_ID: sqlite
251. MySQL: The database I have the most experience with and have used for years. 25:END:
262. PostgreSQL: A database I'm new to, but want to learn. 26When choosing the type of database I wanted to use for this project, I
273. SQLite: A database that I've used for a couple projects and have moderate 27was split between three options:
28 experience. 28
29 291. MySQL: The database I have the most experience with and have used for
30I ended up choosing SQLite since it can be maintained within a single `.sqlite` 30 years.
31file, which allows me more flexibility for storage and backup. I keep this file 312. PostgreSQL: A database I'm new to, but want to learn.
32in my cloud storage and pull it up whenever needed. 323. SQLite: A database that I've used for a couple projects and have
33 33 moderate experience.
34## GUI Editing 34
35 35I ended up choosing SQLite since it can be maintained within a single
36Since I didn't want to try and import 1000--1500 records into my new database 36=.sqlite= file, which allows me more flexibility for storage and backup.
37via the command line, I opted to use [DB Browser for SQLite 37I keep this file in my cloud storage and pull it up whenever needed.
38(DB4S)](https://sqlitebrowser.org/) as a GUI tool. This application is 38
39excellent, and I don't see myself going back to the CLI when working in this 39** GUI Editing
40database. 40:PROPERTIES:
41:CUSTOM_ID: gui-editing
42:END:
43Since I didn't want to try and import 1000--1500 records into my new
44database via the command line, I opted to use
45[[https://sqlitebrowser.org/][DB Browser for SQLite (DB4S)]] as a GUI
46tool. This application is excellent, and I don't see myself going back
47to the CLI when working in this database.
41 48
42DB4S allows you to copy a range of cells from a spreadsheet and paste it 49DB4S allows you to copy a range of cells from a spreadsheet and paste it
43straight into the SQL table. I used this process for all 36 accounts, 1290 50straight into the SQL table. I used this process for all 36 accounts,
44account statements, and 126 pay statements. Overall, I'm guessing this took 511290 account statements, and 126 pay statements. Overall, I'm guessing
45anywhere between 4--8 hours. In comparison, it probably took me 2-3 days to 52this took anywhere between 4--8 hours. In comparison, it probably took
46initially create the spreadsheet. 53me 2-3 days to initially create the spreadsheet.
47
48## Schema
49 54
50The schema for this database is actually extremely simple and involves only 55** Schema
51three tables (for now): 56:PROPERTIES:
57:CUSTOM_ID: schema
58:END:
59The schema for this database is actually extremely simple and involves
60only three tables (for now):
52 61
531. Accounts 621. Accounts
542. Statements 632. Statements
553. Payroll 643. Payroll
56 65
57**Accounts** 66*Accounts*
58 67
59The Accounts table contains summary information about an account, such as a car 68The Accounts table contains summary information about an account, such
60loan or a credit card. By viewing this table, you can find high-level data, such 69as a car loan or a credit card. By viewing this table, you can find
61as interest rate, credit line, or owner. 70high-level data, such as interest rate, credit line, or owner.
62 71
63```sql 72#+begin_src sql
64CREATE TABLE "Accounts" ( 73CREATE TABLE "Accounts" (
65 "AccountID" INTEGER NOT NULL UNIQUE, 74 "AccountID" INTEGER NOT NULL UNIQUE,
66 "AccountType" TEXT, 75 "AccountType" TEXT,
@@ -72,16 +81,16 @@ CREATE TABLE "Accounts" (
72 "Co-Owner" TEXT, 81 "Co-Owner" TEXT,
73 PRIMARY KEY("AccountID" AUTOINCREMENT) 82 PRIMARY KEY("AccountID" AUTOINCREMENT)
74) 83)
75``` 84#+end_src
76 85
77**Statements** 86*Statements*
78 87
79The Statements table uses the same unique identifier as the Accounts table, 88The Statements table uses the same unique identifier as the Accounts
80meaning you can join the tables to find a monthly statement for any of the 89table, meaning you can join the tables to find a monthly statement for
81accounts listed in the Accounts table. Each statement has an account ID, 90any of the accounts listed in the Accounts table. Each statement has an
82statement date, and total balance. 91account ID, statement date, and total balance.
83 92
84```sql 93#+begin_src sql
85CREATE TABLE "Statements" ( 94CREATE TABLE "Statements" (
86 "StatementID" INTEGER NOT NULL UNIQUE, 95 "StatementID" INTEGER NOT NULL UNIQUE,
87 "AccountID" INTEGER, 96 "AccountID" INTEGER,
@@ -90,16 +99,16 @@ CREATE TABLE "Statements" (
90 PRIMARY KEY("StatementID" AUTOINCREMENT), 99 PRIMARY KEY("StatementID" AUTOINCREMENT),
91 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") 100 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
92) 101)
93``` 102#+end_src
94 103
95**Payroll** 104*Payroll*
96 105
97The Payroll table is a separate entity, unrelated to the Accounts or Statements 106The Payroll table is a separate entity, unrelated to the Accounts or
98tables. This table contains all information you would find on a pay statement 107Statements tables. This table contains all information you would find on
99from an employer. As you change employers or obtain new perks/benefits, just add 108a pay statement from an employer. As you change employers or obtain new
100new columns to adapt to the new data. 109perks/benefits, just add new columns to adapt to the new data.
101 110
102```sql 111#+begin_src sql
103CREATE TABLE "Payroll" ( 112CREATE TABLE "Payroll" (
104 "PaycheckID" INTEGER NOT NULL UNIQUE, 113 "PaycheckID" INTEGER NOT NULL UNIQUE,
105 "PayDate" TEXT, 114 "PayDate" TEXT,
@@ -129,23 +138,28 @@ CREATE TABLE "Payroll" (
129 "TaxState" NUMERIC, 138 "TaxState" NUMERIC,
130 PRIMARY KEY("PaycheckID" AUTOINCREMENT) 139 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
131) 140)
132``` 141#+end_src
133 142
134## Python Reporting 143** Python Reporting
135 144:PROPERTIES:
136Once I created the database tables and imported all my data, the only step left 145:CUSTOM_ID: python-reporting
137was to create a process to report and visualize on various aspects of the data. 146:END:
138 147Once I created the database tables and imported all my data, the only
139In order to explore and create the reports I'm interested in, I utilized a 148step left was to create a process to report and visualize on various
140two-part process involving Jupyter Notebooks and Python scripts. 149aspects of the data.
141 150
142### Step 1: Jupyter Notebooks 151In order to explore and create the reports I'm interested in, I utilized
143 152a two-part process involving Jupyter Notebooks and Python scripts.
153
154*** Step 1: Jupyter Notebooks
155:PROPERTIES:
156:CUSTOM_ID: step-1-jupyter-notebooks
157:END:
144When I need to explore data, try different things, and re-run my code 158When I need to explore data, try different things, and re-run my code
145cell-by-cell, I use Jupyter Notebooks. For example, I explored the `Accounts` 159cell-by-cell, I use Jupyter Notebooks. For example, I explored the
146table until I found the following useful information: 160=Accounts= table until I found the following useful information:
147 161
148```python 162#+begin_src python
149import sqlite3 163import sqlite3
150import pandas as pd 164import pandas as pd
151import matplotlib 165import matplotlib
@@ -165,14 +179,17 @@ matplotlib.rcParams['legend.labelcolor'] = 'black'
165 179
166# Display graph 180# Display graph
167df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%') 181df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
168``` 182#+end_src
169
170### Step 2: Python Scripts
171 183
172Once I explored enough through the notebooks and had a list of reports I wanted, 184*** Step 2: Python Scripts
173I moved on to create a Python project with the following structure: 185:PROPERTIES:
186:CUSTOM_ID: step-2-python-scripts
187:END:
188Once I explored enough through the notebooks and had a list of reports I
189wanted, I moved on to create a Python project with the following
190structure:
174 191
175```txt 192#+begin_src txt
176finance/ 193finance/
177├── notebooks/ 194├── notebooks/
178│ │ ├── account_summary.ipynb 195│ │ ├── account_summary.ipynb
@@ -189,23 +206,24 @@ finance/
189├── process.py 206├── process.py
190├── requirements.txt 207├── requirements.txt
191└── README.md 208└── README.md
192``` 209#+end_src
193 210
194This structure allows me to: 211This structure allows me to:
195 212
1961. Compile all required python packages into `requirements.txt` for easy 2131. Compile all required python packages into =requirements.txt= for easy
197 installation if I move to a new machine. 214 installation if I move to a new machine.
1982. Activate a virtual environment in `venv/` so I don't need to maintain a 2152. Activate a virtual environment in =venv/= so I don't need to maintain
199 system-wide Python environment just for this project. 216 a system-wide Python environment just for this project.
2003. Keep my `notebooks/` folder to continuously explore the data as I see fit. 2173. Keep my =notebooks/= folder to continuously explore the data as I see
2014. Maintain a local copy of the database in `src/` for easy access. 218 fit.
2025. Export reports, images, HTML files, etc. to `public/`. 2194. Maintain a local copy of the database in =src/= for easy access.
2205. Export reports, images, HTML files, etc. to =public/=.
203 221
204Now, onto the differences between the code in a Jupyter Notebook and the actual 222Now, onto the differences between the code in a Jupyter Notebook and the
205Python files. To create the report in the Notebook snippet above, I created the 223actual Python files. To create the report in the Notebook snippet above,
206following function inside `process.py`: 224I created the following function inside =process.py=:
207 225
208```python 226#+begin_src python
209# Create summary pie chart 227# Create summary pie chart
210def summary_data(accounts: pandas.DataFrame) -> None: 228def summary_data(accounts: pandas.DataFrame) -> None:
211 accounts_01 = accounts[accounts["Owner"] == "Person01"] 229 accounts_01 = accounts[accounts["Owner"] == "Person01"]
@@ -231,19 +249,20 @@ def summary_data(accounts: pandas.DataFrame) -> None:
231 ) 249 )
232 ) 250 )
233 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200) 251 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
234``` 252#+end_src
235 253
236The result? A high-quality pie chart that is read directly by the 254The result? A high-quality pie chart that is read directly by the
237`public/index.html` template I use. 255=public/index.html= template I use.
238 256
239Other charts generated by this project include: 257Other charts generated by this project include:
240 258
241- Charts of account balances over time. 259- Charts of account balances over time.
242- Line chart of effective tax rate (taxes divided by taxable income). 260- Line chart of effective tax rate (taxes divided by taxable income).
243- Salary projections and error limits using past income and inflation rates. 261- Salary projections and error limits using past income and inflation
244- Multi-line chart of gross income, taxable income, and net income. 262 rates.
263- Multi-line chart of gross income, taxable income, and net income.
245 264
246The best thing about this project? I can improve it at any given time, shaping 265The best thing about this project? I can improve it at any given time,
247it into whatever helps me the most for that time. I imagine that I will be 266shaping it into whatever helps me the most for that time. I imagine that
248introducing an asset tracking table soon to track the depreciating value of 267I will be introducing an asset tracking table soon to track the
249cars, houses, etc. Who knows what's next? 268depreciating value of cars, houses, etc. Who knows what's next?
content/blog/2022-03-08-plex-migration.md deleted −218
@@ -1,218 +0,0 @@
1+++
2date = 2022-03-08
3title = "Migrating Plex to a New Server with Nvidia GPU Transcoding"
4description = ""
5draft = false
6+++
7
8# Migration Phases
9
10I recently decided to migrate my server from an old OptiPlex desktop machine to
11a custom-built tower with better hardware in every category. In order to do
12this, I would need to properly migrate a full Plex installation.
13
14The second part of this migration is that the new server uses an Nvidia GPU and
15does not have any integrated graphics, which requires extra work for
16installation, but provides much better hardware transcoding options for Plex.
17
18Therefore, I have broken this migration down into three phases:
19
201. Configure the New Server
212. Migrate Plex Data & Devices
223. Configure GPU Transcoding
23
24# Phase 1: Configure the New Server
25
26## Choosing an OS
27
28In order to migrate Plex to my new server, I first needed to choose an
29appropriate operating system (OS) and install it on the machine. Given that I
30have encountered numerous issues installing other Linux distributions properly
31with Nvidia graphics, I chose [Ubuntu
32Server](https://ubuntu.com/download/server).
33
34The first step is to create a bootable USB with Ubuntu Server. This is easy with
35[Etcher](https://www.balena.io/etcher/), an app that runs on many different
36platforms. Just download the Ubuntu Server `.iso` image, launch Etcher, and
37install the `.iso` on the USB.
38
39Once the USB is created, insert it into my server, reboot, and click `Esc` (or
40any of the `F1-12` keys) until the BIOS menu appears. Finally, launch the USB
41boot drive.
42
43## Booting with Nvidia
44
45In order to install Ubuntu Server with an Nvidia Graphics card (and no
46integrated graphics on this device for some reason), you'll have to configure
47the boot menu to allow different graphics drivers to be loaded.
48
49When booting from the USB, the machine will launch the initial installation
50menu. From this menu, type `e` to view the default command options that come
51with the device - it's a good idea to take a photo of this screen, so you can
52enter these commands on the next screen (along with adding support for Nvidia).
53
54Finally, type `Ctrl + C` to enter the command line. From this command line,
55enter the commands found on the `e` screen. _Remember to add `nomodeset` to the
56`linux ...` line so that your Nvidia device will display the installation
57screens properly!_
58
59Here's an example of the commands I pulled from the `e` screen and entered on
60the command line.
61
62```sh
63setparams 'Install Ubuntu Server'
64setgfxpayload=keep
65linux /casper/vmlinuz quiet nomodeset ---
66initrd /casper/initrd
67boot
68```
69
70Once the machine is rebooted, enter the `e` screen again and add `nomodeset` to
71the `linux ...` line again and press `Ctrl + X` to save the boot options.
72
73The machine is now fully installed and can properly display on an external
74display using the Nvidia GPU.
75
76Always remember to update and upgrade on a new installation:
77
78```sh
79sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
80```
81
82# Phase 2: Migrate Plex Data & Devices
83
84This phase uses the great Plex article on migrations ([Move an Installation to
85Another
86System](https://support.plex.tv/articles/201370363-move-an-install-to-another-system/))
87and adds a bit more information to help with commands and context.
88
89## Terminology
90
91**Source:** The original server that is being replaced.\
92**Destination:** The new server.\
93**Client:** Any application that can be used to modify settings for both
94source/destination.
95
96## Step 01: [Client] Update Settings
97
98Open up a Plex app and _disable_ the `Account` > `Library` > `Empty trash
99automatically after every scan` preference for the source server.
100
101## Step 02: [Destination] Install Plex
102
103Open up the [Plex Media Server download
104page](https://www.plex.tv/media-server-downloads/) and copy the link for the
105appropriate platform.
106
107Execute the following commands on the destination server to install Plex:
108
109```sh
110wget <url>
111sudo dpkg -i <filename>
112sudo systemctl stop plexmediaserver.service
113```
114
115## Step 03: [Source] Stop Plex & Migrate Data
116
117First, stop the Plex service so that no data is created or modified during the
118migration.
119
120```sh
121sudo systemctl stop plexmediaserver.service
122```
123
124Next, copy the data to the new server. To find where the Plex data directory is
125located, Plex has another excellent article available: [Where is the Plex Media
126Server data directory
127located?](https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/).
128
129There are many ways to copy the data to the new server and will largely depend
130on the size of the folder being copied. Personally, my data folder was ~23GB and
131I opted to simply use the `scp` command to copy the files over SSH.
132
133This process was throttled by the old server's slow HDD and ports and took
134approximately 90 minutes to complete. In comparison, moving the data from the
135new server's `home/user/` directory to the `/var/.../Plex Media Server`
136directory took 2-3 minutes.
137
138```sh
139scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
140```
141
142## Step 04: [Destination] Update File Permissions
143
144In case you move the data directory to a common area on the new server, it will
145have to be moved to the proper location before Plex can function properly:
146
147```sh
148mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
149```
150
151To ensure permissions were retained properly, the server will need to show that
152all files and folders in the data directory are owned by `plex:plex` (or
153whichever user is running the Plex application).
154
155```sh
156sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
157```
158
159Finally, start the service and check the status.
160
161```sh
162sudo systemctl start plexmediaserver.service
163sudo systemctl status plexmediaserver.service
164```
165
166## Step 05: [Client] Update Libraries & Metadata
167
168The first step - now that the new server is up and running - is to sign out of
169the client and sign back in. Once this is done, update any library locations, if
170necessary. This was unnecessary in my case since I simply moved my storage
171drives from the source server to the destination server.
172
173Next, perform the following actions in the client:
174
1751. On the left sidebar, click `More` > Three-Dot Menu > `Scan Library Files`
1762. _Enable_ the `Account` > `Library` > `Empty trash automatically after every
177scan` preference for the source server.
1783. On the left sidebar, click `More` > Three-Dot Menu > `Manage Server` > `Empty
179 Trash`
1804. On the left sidebar, click `More` > Three-Dot Menu > `Manage Server` > `Clean
181 Bundles`
1825. On the left sidebar, click `More` > Three-Dot Menu > `Manage Server` >
183 `Optimize Database`
184
185Finally, double-check the Remote Access settings to make sure no changes have
186caused issues with accessing the server from outside the network.
187
188In my case, I use a single port forwarding rule in my router and needed to
189update the Local LAN IP Address to the new server IP address.
190
191# Phase 3: Configure GPU Transcoding
192
193The final piece to the migration is enabling hardware transcoding so that Plex
194can fully utilize the new Nvidia GPU available in the server. The first step is
195to install Nvidia graphics drivers. This process may take a few minutes, but the
196commands are pretty simple:
197
198```sh
199sudo add-apt-repository ppa:graphics-drivers/ppa
200sudo apt update
201sudo apt-get install ubuntu-drivers-common
202sudo ubuntu-drivers autoinstall
203```
204
205Finally, reboot so that the changes are loaded:
206
207```sh
208sudo reboot now
209```
210
211To ensure that the Nvidia graphics drivers are working properly, run the
212following command to view the available GPUs, statistics, and processes:
213
214```sh
215sudo nvidia-smi
216```
217
218Finally, enable hardware transcoding settings in the Plex application to finish the process.
content/blog/2022-03-08-plex-migration.org added +252
@@ -0,0 +1,252 @@
1#+date: <2022-03-08>
2#+title: Migrating Plex to a New Server with Nvidia GPU Transcoding
3#+description:
4
5
6* Migration Phases
7:PROPERTIES:
8:CUSTOM_ID: migration-phases
9:END:
10I recently decided to migrate my server from an old OptiPlex desktop
11machine to a custom-built tower with better hardware in every category.
12In order to do this, I would need to properly migrate a full Plex
13installation.
14
15The second part of this migration is that the new server uses an Nvidia
16GPU and does not have any integrated graphics, which requires extra work
17for installation, but provides much better hardware transcoding options
18for Plex.
19
20Therefore, I have broken this migration down into three phases:
21
221. Configure the New Server
232. Migrate Plex Data & Devices
243. Configure GPU Transcoding
25
26* Phase 1: Configure the New Server
27:PROPERTIES:
28:CUSTOM_ID: phase-1-configure-the-new-server
29:END:
30** Choosing an OS
31:PROPERTIES:
32:CUSTOM_ID: choosing-an-os
33:END:
34In order to migrate Plex to my new server, I first needed to choose an
35appropriate operating system (OS) and install it on the machine. Given
36that I have encountered numerous issues installing other Linux
37distributions properly with Nvidia graphics, I chose
38[[https://ubuntu.com/download/server][Ubuntu Server]].
39
40The first step is to create a bootable USB with Ubuntu Server. This is
41easy with [[https://www.balena.io/etcher/][Etcher]], an app that runs on
42many different platforms. Just download the Ubuntu Server =.iso= image,
43launch Etcher, and install the =.iso= on the USB.
44
45Once the USB is created, insert it into my server, reboot, and click
46=Esc= (or any of the =F1-12= keys) until the BIOS menu appears. Finally,
47launch the USB boot drive.
48
49** Booting with Nvidia
50:PROPERTIES:
51:CUSTOM_ID: booting-with-nvidia
52:END:
53In order to install Ubuntu Server with an Nvidia Graphics card (and no
54integrated graphics on this device for some reason), you'll have to
55configure the boot menu to allow different graphics drivers to be
56loaded.
57
58When booting from the USB, the machine will launch the initial
59installation menu. From this menu, type =e= to view the default command
60options that come with the device - it's a good idea to take a photo of
61this screen, so you can enter these commands on the next screen (along
62with adding support for Nvidia).
63
64Finally, type =Ctrl + C= to enter the command line. From this command
65line, enter the commands found on the =e= screen. /Remember to add
66=nomodeset= to the =linux ...= line so that your Nvidia device will
67display the installation screens properly!/
68
69Here's an example of the commands I pulled from the =e= screen and
70entered on the command line.
71
72#+begin_src sh
73setparams 'Install Ubuntu Server'
74setgfxpayload=keep
75linux /casper/vmlinuz quiet nomodeset ---
76initrd /casper/initrd
77boot
78#+end_src
79
80Once the machine is rebooted, enter the =e= screen again and add
81=nomodeset= to the =linux ...= line again and press =Ctrl + X= to save
82the boot options.
83
84The machine is now fully installed and can properly display on an
85external display using the Nvidia GPU.
86
87Always remember to update and upgrade on a new installation:
88
89#+begin_src sh
90sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
91#+end_src
92
93* Phase 2: Migrate Plex Data & Devices
94:PROPERTIES:
95:CUSTOM_ID: phase-2-migrate-plex-data-devices
96:END:
97This phase uses the great Plex article on migrations
98([[https://support.plex.tv/articles/201370363-move-an-install-to-another-system/][Move
99an Installation to Another System]]) and adds a bit more information to
100help with commands and context.
101
102** Terminology
103:PROPERTIES:
104:CUSTOM_ID: terminology
105:END:
106*Source:* The original server that is being replaced.\\
107*Destination:* The new server.\\
108*Client:* Any application that can be used to modify settings for both
109source/destination.
110
111** Step 01: [Client] Update Settings
112:PROPERTIES:
113:CUSTOM_ID: step-01-client-update-settings
114:END:
115Open up a Plex app and /disable/ the =Account= > =Library= >
116=Empty trash automatically after every scan= preference for the source
117server.
118
119** Step 02: [Destination] Install Plex
120:PROPERTIES:
121:CUSTOM_ID: step-02-destination-install-plex
122:END:
123Open up the [[https://www.plex.tv/media-server-downloads/][Plex Media
124Server download page]] and copy the link for the appropriate platform.
125
126Execute the following commands on the destination server to install
127Plex:
128
129#+begin_src sh
130wget <url>
131sudo dpkg -i <filename>
132sudo systemctl stop plexmediaserver.service
133#+end_src
134
135** Step 03: [Source] Stop Plex & Migrate Data
136:PROPERTIES:
137:CUSTOM_ID: step-03-source-stop-plex-migrate-data
138:END:
139First, stop the Plex service so that no data is created or modified
140during the migration.
141
142#+begin_src sh
143sudo systemctl stop plexmediaserver.service
144#+end_src
145
146Next, copy the data to the new server. To find where the Plex data
147directory is located, Plex has another excellent article available:
148[[https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/][Where
149is the Plex Media Server data directory located?]].
150
151There are many ways to copy the data to the new server and will largely
152depend on the size of the folder being copied. Personally, my data
153folder was ~23GB and I opted to simply use the =scp= command to copy the
154files over SSH.
155
156This process was throttled by the old server's slow HDD and ports and
157took approximately 90 minutes to complete. In comparison, moving the
158data from the new server's =home/user/= directory to the
159=/var/.../Plex Media Server= directory took 2-3 minutes.
160
161#+begin_src sh
162scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
163#+end_src
164
165** Step 04: [Destination] Update File Permissions
166:PROPERTIES:
167:CUSTOM_ID: step-04-destination-update-file-permissions
168:END:
169In case you move the data directory to a common area on the new server,
170it will have to be moved to the proper location before Plex can function
171properly:
172
173#+begin_src sh
174mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
175#+end_src
176
177To ensure permissions were retained properly, the server will need to
178show that all files and folders in the data directory are owned by
179=plex:plex= (or whichever user is running the Plex application).
180
181#+begin_src sh
182sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
183#+end_src
184
185Finally, start the service and check the status.
186
187#+begin_src sh
188sudo systemctl start plexmediaserver.service
189sudo systemctl status plexmediaserver.service
190#+end_src
191
192** Step 05: [Client] Update Libraries & Metadata
193:PROPERTIES:
194:CUSTOM_ID: step-05-client-update-libraries-metadata
195:END:
196The first step - now that the new server is up and running - is to sign
197out of the client and sign back in. Once this is done, update any
198library locations, if necessary. This was unnecessary in my case since I
199simply moved my storage drives from the source server to the destination
200server.
201
202Next, perform the following actions in the client:
203
2041. On the left sidebar, click =More= > Three-Dot Menu >
205 =Scan Library Files=
2062. /Enable/ the =Account= > =Library= >
207 =Empty trash automatically after every scan= preference for the
208 source server.
2093. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
210 > =Empty Trash=
2114. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
212 > =Clean Bundles=
2135. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
214 > =Optimize Database=
215
216Finally, double-check the Remote Access settings to make sure no changes
217have caused issues with accessing the server from outside the network.
218
219In my case, I use a single port forwarding rule in my router and needed
220to update the Local LAN IP Address to the new server IP address.
221
222* Phase 3: Configure GPU Transcoding
223:PROPERTIES:
224:CUSTOM_ID: phase-3-configure-gpu-transcoding
225:END:
226The final piece to the migration is enabling hardware transcoding so
227that Plex can fully utilize the new Nvidia GPU available in the server.
228The first step is to install Nvidia graphics drivers. This process may
229take a few minutes, but the commands are pretty simple:
230
231#+begin_src sh
232sudo add-apt-repository ppa:graphics-drivers/ppa
233sudo apt update
234sudo apt-get install ubuntu-drivers-common
235sudo ubuntu-drivers autoinstall
236#+end_src
237
238Finally, reboot so that the changes are loaded:
239
240#+begin_src sh
241sudo reboot now
242#+end_src
243
244To ensure that the Nvidia graphics drivers are working properly, run the
245following command to view the available GPUs, statistics, and processes:
246
247#+begin_src sh
248sudo nvidia-smi
249#+end_src
250
251Finally, enable hardware transcoding settings in the Plex application to
252finish the process.
content/blog/2022-03-23-cloudflare-dns-api.md → content/blog/2022-03-23-cloudflare-dns-api.org renamed +48 −45
@@ -1,39 +1,42 @@
1+++ 1#+date: <2022-03-23>
2date = 2022-03-23 2#+title: Dynamic DNS with Cloudflare API
3title = "Dynamic DNS with Cloudflare API" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# DDNS: Dynamic DNS
9 5
10If you're hosting a service from a location with dynamic DNS (where your IP may 6* DDNS: Dynamic DNS
11change at any time), you must have a solution to update the DNS so that you can 7:PROPERTIES:
12access your service even when the IP of the server changes. 8:CUSTOM_ID: ddns-dynamic-dns
9:END:
10If you're hosting a service from a location with dynamic DNS (where your
11IP may change at any time), you must have a solution to update the DNS
12so that you can access your service even when the IP of the server
13changes.
13 14
14The process below uses the [Cloudflare API](https://api.cloudflare.com/) to 15The process below uses the [[https://api.cloudflare.com/][Cloudflare
15update DNS `A` and `AAAA` records with the server's current IP. If you use 16API]] to update DNS =A= and =AAAA= records with the server's current IP.
16another DNS provider, you will have to find a way to update your DNS (or find a 17If you use another DNS provider, you will have to find a way to update
17way to get a static IP). 18your DNS (or find a way to get a static IP).
18 19
19First, install `jq` since we will use it in the next script: 20First, install =jq= since we will use it in the next script:
20 21
21```sh 22#+begin_src sh
22sudo apt install jq 23sudo apt install jq
23``` 24#+end_src
24 25
25Next, create a location for your DDNS update scripts and open the first script: 26Next, create a location for your DDNS update scripts and open the first
27script:
26 28
27```sh 29#+begin_src sh
28mkdir ~/ddns 30mkdir ~/ddns
29nano ~/ddns/update.sh 31nano ~/ddns/update.sh
30``` 32#+end_src
31 33
32The following `update.sh` script will take all of your domains and subdomains 34The following =update.sh= script will take all of your domains and
33and check Cloudflare to see if the current `A` and `AAAA` records match your 35subdomains and check Cloudflare to see if the current =A= and =AAAA=
34server's IP address. If not, it will update the records. 36records match your server's IP address. If not, it will update the
37records.
35 38
36```sh 39#+begin_src sh
37# file: update.sh 40# file: update.sh
38#!/bin/bash 41#!/bin/bash
39 42
@@ -55,20 +58,20 @@ do
55 echo -e "\nUpdating $subdomain..." 58 echo -e "\nUpdating $subdomain..."
56 zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh 59 zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh
57done 60done
58``` 61#+end_src
59 62
60Next, open up the `ddns.sh` script. Paste the following into the script and 63Next, open up the =ddns.sh= script. Paste the following into the script
61update the `api_token` and `email` variables. 64and update the =api_token= and =email= variables.
62 65
63```sh 66#+begin_src sh
64nano ~/ddns/ddns.sh 67nano ~/ddns/ddns.sh
65``` 68#+end_src
66 69
67**Note**: If you want your DNS records to be proxied through Cloudflare, find 70*Note*: If you want your DNS records to be proxied through Cloudflare,
68and update the following snippet: `"proxied":false}"` to say `true` instead of 71find and update the following snippet: ="proxied":false}"= to say =true=
69`false`. 72instead of =false=.
70 73
71```sh 74#+begin_src sh
72# file: ddns.sh 75# file: ddns.sh
73#!/bin/bash 76#!/bin/bash
74# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a 77# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
@@ -161,30 +164,30 @@ then
161else 164else
162 echo "There is a problem with either the email or the password" 165 echo "There is a problem with either the email or the password"
163fi 166fi
164``` 167#+end_src
165 168
166Once the script is saved and closed, make the scripts executable: 169Once the script is saved and closed, make the scripts executable:
167 170
168```sh 171#+begin_src sh
169chmod +x ~/ddns/ddns.sh 172chmod +x ~/ddns/ddns.sh
170chmod +x ~/ddns/update.sh 173chmod +x ~/ddns/update.sh
171``` 174#+end_src
172 175
173You can test the script by running it manually: 176You can test the script by running it manually:
174 177
175```sh 178#+begin_src sh
176./update.sh 179./update.sh
177``` 180#+end_src
178 181
179To make sure the scripts run automatically, add it to the `cron` file so that it 182To make sure the scripts run automatically, add it to the =cron= file so
180will run on a schedule. To do this, open the cron file: 183that it will run on a schedule. To do this, open the cron file:
181 184
182```sh 185#+begin_src sh
183crontab -e 186crontab -e
184``` 187#+end_src
185 188
186In the cron file, paste the following at the bottom of the editor: 189In the cron file, paste the following at the bottom of the editor:
187 190
188```sh 191#+begin_src sh
189*/5 ** ** ** ** bash /home/<your_username>/ddns/update.sh 192,*/5 ** ** ** ** bash /home/<your_username>/ddns/update.sh
190``` 193#+end_src
content/blog/2022-03-23-nextcloud-on-ubuntu.md → content/blog/2022-03-23-nextcloud-on-ubuntu.org renamed +93 −73
@@ -1,80 +1,92 @@
1+++ 1#+date: <2022-03-23>
2date = 2022-03-23 2#+title: Self-Hosting Nextcloud
3title = "Self-Hosting Nextcloud" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# What is Nextcloud?
9 5
10[Nextcloud](https://nextcloud.com/) is a self-hosted solution for storage, 6* What is Nextcloud?
11communications, editing, calendar, contacts, and more. 7:PROPERTIES:
8:CUSTOM_ID: what-is-nextcloud
9:END:
10[[https://nextcloud.com/][Nextcloud]] is a self-hosted solution for
11storage, communications, editing, calendar, contacts, and more.
12 12
13This tutorial assumes that you have an Ubuntu server and a domain name 13This tutorial assumes that you have an Ubuntu server and a domain name
14configured to point toward the server. 14configured to point toward the server.
15 15
16# Install Dependencies 16* Install Dependencies
17 17:PROPERTIES:
18:CUSTOM_ID: install-dependencies
19:END:
18To start, you will need to install the packages that Nextcloud requires: 20To start, you will need to install the packages that Nextcloud requires:
19 21
20```sh 22#+begin_src sh
21sudo apt install apache2 mariadb-server libapache2-mod-php7.4 23sudo apt install apache2 mariadb-server libapache2-mod-php7.4
22sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl 24sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl
23sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip 25sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip
24``` 26#+end_src
25
26# Set Up MySQL
27 27
28Next, you will need to log in to MySQL as the `root` user of the machine. 28* Set Up MySQL
29:PROPERTIES:
30:CUSTOM_ID: set-up-mysql
31:END:
32Next, you will need to log in to MySQL as the =root= user of the
33machine.
29 34
30```sh 35#+begin_src sh
31sudo mysql -uroot -p 36sudo mysql -uroot -p
32``` 37#+end_src
33 38
34Once you've logged in, you must create a new user so that Nextcloud can manage 39Once you've logged in, you must create a new user so that Nextcloud can
35the database. You will also create a `nextcloud` database and assign privileges: 40manage the database. You will also create a =nextcloud= database and
41assign privileges:
36 42
37```sql 43#+begin_src sql
38CREATE USER 'username'@'localhost' IDENTIFIED BY 'password'; 44CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
39CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 45CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
40GRANT ALL PRIVILEGES ON nextcloud.** TO 'username'@'localhost'; 46GRANT ALL PRIVILEGES ON nextcloud.** TO 'username'@'localhost';
41FLUSH PRIVILEGES; 47FLUSH PRIVILEGES;
42quit; 48quit;
43``` 49#+end_src
44 50
45# Download & Install Nextcloud 51* Download & Install Nextcloud
46 52:PROPERTIES:
47To download Nextcloud, go the [Nextcloud downloads 53:CUSTOM_ID: download-install-nextcloud
48page](https://nextcloud.com/install/#instructions-server), click on `Archive 54:END:
49File` and right-click the big blue button to copy the link. 55To download Nextcloud, go the
50 56[[https://nextcloud.com/install/#instructions-server][Nextcloud
51Then, go to your server and enter the following commands to download, unzip, and 57downloads page]], click on =Archive File= and right-click the big blue
52move the files to your destination directory. This example uses `example.com` as 58button to copy the link.
53the destination, but you can put it wherever you want to server your files from. 59
54 60Then, go to your server and enter the following commands to download,
55```sh 61unzip, and move the files to your destination directory. This example
62uses =example.com= as the destination, but you can put it wherever you
63want to server your files from.
64
65#+begin_src sh
56wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip 66wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip
57sudo apt install unzip 67sudo apt install unzip
58unzip nextcloud-23.0.3.zip 68unzip nextcloud-23.0.3.zip
59sudo cp -r nextcloud /var/www/example.com 69sudo cp -r nextcloud /var/www/example.com
60``` 70#+end_src
61 71
62# Configure the Apache Web Server 72* Configure the Apache Web Server
63 73:PROPERTIES:
64Now that the database is set up and Nextcloud is installed, you need to set up 74:CUSTOM_ID: configure-the-apache-web-server
65the Apache configuration files to tell the server how to handle requests for 75:END:
66`example.com/nextcloud`. 76Now that the database is set up and Nextcloud is installed, you need to
77set up the Apache configuration files to tell the server how to handle
78requests for =example.com/nextcloud=.
67 79
68First, open the following file in the editor: 80First, open the following file in the editor:
69 81
70```sh 82#+begin_src sh
71sudo nano /etc/apache2/sites-available/nextcloud.conf 83sudo nano /etc/apache2/sites-available/nextcloud.conf
72``` 84#+end_src
73 85
74Once the editor is open, paste the following information in. Then, save and 86Once the editor is open, paste the following information in. Then, save
75close the file. 87and close the file.
76 88
77```config 89#+begin_src config
78<VirtualHost *:80> 90<VirtualHost *:80>
79 DocumentRoot /var/www/example.com 91 DocumentRoot /var/www/example.com
80 ServerName example.com 92 ServerName example.com
@@ -93,57 +105,65 @@ close the file.
93 </IfModule> 105 </IfModule>
94 </Directory> 106 </Directory>
95</VirtualHost> 107</VirtualHost>
96``` 108#+end_src
97 109
98Once the file is saved, enable it with Apache: 110Once the file is saved, enable it with Apache:
99 111
100```sh 112#+begin_src sh
101sudo a2ensite nextcloud.conf 113sudo a2ensite nextcloud.conf
102``` 114#+end_src
103 115
104Next, enable the Apache mods required by Nextcloud: 116Next, enable the Apache mods required by Nextcloud:
105 117
106```sh 118#+begin_src sh
107sudo a2enmod rewrite headers env dir mime 119sudo a2enmod rewrite headers env dir mime
108``` 120#+end_src
109 121
110Finally, restart Apache. If any errors arise, you must solve those before 122Finally, restart Apache. If any errors arise, you must solve those
111continuing. 123before continuing.
112 124
113```sh 125#+begin_src sh
114sudo systemctl restart apache2 126sudo systemctl restart apache2
115``` 127#+end_src
116 128
117For the app to work, you must have the correct file permissions on your 129For the app to work, you must have the correct file permissions on your
118`nextcloud` directory. Set the owner to be `www-data`: 130=nextcloud= directory. Set the owner to be =www-data=:
119 131
120```sh 132#+begin_src sh
121sudo chown -R www-data:www-data /var/www/example.com/nextcloud/ 133sudo chown -R www-data:www-data /var/www/example.com/nextcloud/
122``` 134#+end_src
123
124# DNS
125 135
136* DNS
137:PROPERTIES:
138:CUSTOM_ID: dns
139:END:
126If you do not have a static IP address, you will need to update your DNS 140If you do not have a static IP address, you will need to update your DNS
127settings (at your DNS provider) whenever your dynamic IP address changes. 141settings (at your DNS provider) whenever your dynamic IP address
128 142changes.
129For an example on how I do that with Cloudflare, see my other post: [Updating
130Dynamic DNS with Cloudflare API](../updating-dynamic-dns-with-cloudflare-api/)
131 143
132# Certbot 144For an example on how I do that with Cloudflare, see my other post:
145[[../updating-dynamic-dns-with-cloudflare-api/][Updating Dynamic DNS
146with Cloudflare API]]
133 147
134If you want to serve Nextcloud from HTTPS rather than plain HTTP, use the 148* Certbot
135following commands to issue Let's Encrypt SSL certificates: 149:PROPERTIES:
150:CUSTOM_ID: certbot
151:END:
152If you want to serve Nextcloud from HTTPS rather than plain HTTP, use
153the following commands to issue Let's Encrypt SSL certificates:
136 154
137```sh 155#+begin_src sh
138sudo apt install snapd 156sudo apt install snapd
139sudo snap install core 157sudo snap install core
140sudo snap refresh core 158sudo snap refresh core
141sudo snap install --classic certbot 159sudo snap install --classic certbot
142sudo ln -s /snap/bin/certbot /usr/bin/certbot 160sudo ln -s /snap/bin/certbot /usr/bin/certbot
143sudo certbot --apache 161sudo certbot --apache
144``` 162#+end_src
145 163
146# Results 164* Results
147 165:PROPERTIES:
148Voilà! You're all done and should be able to access Nextcloud from your domain 166:CUSTOM_ID: results
149or IP address. 167:END:
168Voilà! You're all done and should be able to access Nextcloud from your
169domain or IP address.
content/blog/2022-03-24-server-hardening.md deleted −345
@@ -1,345 +0,0 @@
1+++
2date = 2022-03-24
3title = "Hardening a Public-Facing Home Server"
4description = ""
5draft = false
6+++
7
8# Post Updates
9
10> After reviewing this post today (2022-10-04), I noticed quite a few gaps in my
11> write-up and wanted to add a few things, even though this blog is really just
12> a retrospective and knowledge dump for myself. I left things intact and simply
13> crossed them out (~~like this~~) for 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, you
34have to think about the transport of data from `server` to `client`.
35
36Let's start with the actual server itself. Think about the following:
37
38- Do I have a firewall enabled? Do I need to update this to allow new ports or
39 IPs?
40- Do I have an IPS/IDS that may prevent outside traffic?
41- Do I have any other security software installed?
42- Are the services hosted inside Docker containers, behind a reverse proxy, or
43 virtualized? If so, are they configured to allow outside traffic?
44
45Once the data leaves the server, where does it go? In my case, it goes to a
46managed switch. In this case, I asked the following:
47
48- What configurations is the switch using?
49- Am I using VLANs?
50 - Yes, I am using 802.1Q VLANs.
51- Are the VLANs configured properly?
52 - Yes, as shown in the Switch section below, I have a separate VLAN to
53 allow outside traffic to and from the server alone. No other devices,
54 except for a service port, and in that VLAN.
55
56At this point, the data has been processed through the switch. Where does it go
57next? In my case, it's pretty simple: it goes to the router/modem device.
58
59- Does my ISP block any ports that I need?
60 - This is an important step that a lot of people run into when
61 self-hosting at home. Use an online port-checker tool for your IP or
62 call your ISP if you think ports are blocked.
63- Is there a router firewall?
64 - Yes, I checked that it's configured to allow the ports I need to run my
65 services publicly. Common web servers and reverse proxies require ports
66 80 and 443, but other services like media servers or games can require
67 unique ports, so be sure to check the documentation for your 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 accessed
78publicly.
79
80### Server
81
82+The services I run on my server are installed straight into the OS, without any
83use of Docker or VMs, so I don't need any extra application configuration to
84make them accessible to the outside world.+
85
86As of 2022-10-04, the paragraph above is no longer true as I now run a reverse
87proxy with Nginx and host many services inside Docker. However, it doesn't
88change anything regarding this post as I still just need to open ports 80 & 443
89and create the necessary website configuration files.
90
91When creating new services - either installed directly on bare metal or within
92something like Docker - I ensure that I read through the documentation
93thoroughly to understand a few key things: - What network activities should this
94app perform (if any)? Using which ports and protocols? - Does this app require
95any commands/services to be run as `root`? - Does this app log errors,
96authentication failures/successes, or anything else that would be useful for an
97investigation?
98
99For extra security, I use limit all incoming connections to SSH connections
100through my server firewall (`ufw`) and disable common SSH settings. After all of
101that, I use `fail2ban` as a preventative measure against brute-force login
102attempts.
103
104As another piece of security, you can randomize your SSH port to ensure that
105random scanners or attackers can't easily try to force their way into your
106network. For example, you can edit the port rules in your server to block all
107connection requests to port `22` but forward all remote connections from port
108`12345` to your server's port `22`. Then you just need to SSH to your network
109via your randomized port.
110
111## `ufw`
112
113To see how to configure `ufw`, see my other post: [Secure Your Network with the
114Uncomplicated
115Firewall](../secure-your-network-with-the-uncomplicated-firewall/).
116
117The general notion with an on-device firewall is that you want to deny all
118incoming connections by default and then selectively open certain ports for
119services or users that you know need access.
120
121If you know that you will only be logging into this server from a certain set or
122list of IPs, you can always set the firewall to only allow connections to port
12322 from those IPs.
124
125For a quick start to only allow SSH connections to the server, use this:
126
127```sh
128sudo ufw default deny incoming
129sudo ufw default allow outgoing
130sudo ufw allow 22
131sudo ufw enable
132```
133
134## `ssh`
135
1361. Using SSH Keys
137
138 First, make sure you have an SSH keypair generated on the device(s) that
139 you'll be using to log in to the server. If you don't have an SSH key, run
140 this command:
141
142 ```sh
143 ssh-keygen
144 ```
145
146 Now that we have an SSH key, copy it to the server with the following
147 command, which will ask for the user's password before accepting the key:
148
149 ```sh
150 ssh-copy-id my_user@my_server
151 ```
152
153 If you have multiple keys, you'll need to specify which to use. After it's
154 complete, `ssh` back into the server as that user and make sure it doesn't
155 ask for a password.
156
1572. Disable Password & Root Authentication
158
159 Now that we can access the server without a password, we will disable
160 password authentication and disable anyone from using `ssh` to login as
161 `root`.
162
163 To do this, open the `sshd_config` file:
164
165 ```sh
166 sudo nano /etc/ssh/sshd_config
167 ```
168
169 You'll need to update the parameters to the values below. If one of these
170 rules is commented-out or doesn't exist, create the rule at the bottom of
171 the file.
172
173 ```config
174 PermitRootLogin no
175 PasswordAuthentication no
176 PubkeyAuthentication yes
177 ```
178
179 Finally, restart the `ssh` service:
180
181 ```sh
182 sudo systemctl restart sshd.service
183 ```
184
185 To test that everything's working so far, open ANOTHER terminal and try
186 logging in as `root` over SSH. It is very important that you keep your
187 current SSH session open and test with an additional session, or you will
188 lock yourself out at some point and will need to use a recovery method
189 (e.g., hooking monitor up to home server) to get yourself back in.
190
1913. Enable MFA for `ssh`
192
193 This part is optional, but I highly recommend it. So far, we've ensured that
194 no one can log into our user on the server without using our secret key, and
195 we've ensured that no one can log in remotely as `root`. Next, you can
196 enable MFA authentication for `ssh` connections.
197
198 This process involves editing a couple files and installing an MFA package,
199 so I will not include all the details in this post. To see how to configure
200 MFA for `ssh`, see my other post: [Enabling MFA for
201 SSH](../enable-totp-mfa-for-ssh/).
202
203## `fail2ban`
204
205I haven't written a post on how I use `fail2ban`, but it's quite simple. I use
206the default `sshd` jail, but you can always create new jails for respective
207applications or ports. For example, if you use Nginx as your web server, you can
208use the `nginx-http-auth` jail.
209
210In order to get it up and running, use the following commands:
211
212```sh
213sudo apt install fail2ban
214sudo fail2ban-client start sshd
215sudo fail2ban-client status sshd
216```
217
218This should be used as a last-resort defense and shouldn't be a replacement for
219the security measures mentioned above.
220
221# Switch
222
223Between the router and any local devices is my managed switch, which is used to
224create VLANs. The example below shows how I would isolate the VLANs if I were
225starting to host a single service at home.
226
227## 802.1Q VLAN Configuration
228
229In this configuration, port 8 is the public server that needs to be accessed
230from the outside. Port 23 is my 'dedicated service port' for this server. In
231order to SSH to this server, I need to plug my laptop into port 23 or else I
232cannot SSH. Otherwise, I'd need to hook up a monitor and keyboard directly to
233the server to manage it.
234
235|
236
237| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
238| ------- | --------- | ------------ | ------------ | -------------- |
239| 1 | Default | 1-24 | 1-24 | |
240| 2 | Server | 1,8,23 | 1,8,23 | |
241
242## 802.1Q VLAN PVID Setting
243
244Once the VLAN is created, I simply add the `VLAN ID` of `2` as the `PVID` for
245any related ports (in this case, see that ports `8` and `23` have a PVID of
246`2`).
247
248| Port | PVID |
249| ---- | ---- |
250| 1 | 1 |
251| 2 | 1 |
252| 3 | 1 |
253| 4 | 1 |
254| 5 | 1 |
255| 6 | 1 |
256| 7 | 1 |
257| 8 | 2 |
258| 9 | 1 |
259| 10 | 1 |
260| 11 | 1 |
261| 12 | 1 |
262| 13 | 1 |
263| 14 | 1 |
264| 15 | 1 |
265| 16 | 1 |
266| 17 | 1 |
267| 18 | 1 |
268| 19 | 1 |
269| 20 | 1 |
270| 21 | 1 |
271| 22 | 1 |
272| 23 | 2 |
273| 24 | 1 |
274
275# Router
276
277On my router, the configuration was as easy as opening the firewall settings and
278unblocking the ports I needed for my services (e.g., HTTP/S, Plex, SSH, MySQL,
279etc.).
280
281_Since I'm relying on an ISP-provided modem/router combo for now (not by
282choice), I do not use any other advanced settings on my router that would
283inhibit any valid traffic to these services._
284
285The paragraph above regarding the ISP-owned router is no longer accurate as I
286now use the Ubiquiti Unifi Dream Machine Pro as my router. Within this router, I
287enabled port forwarding/firewall rules, segregate the network based on the
288device, and enable traffic restrictions (e.g., silently drop traffic from
289certain countries and threat categories).
290
291If you have the option with your ISP, I recommend using a personal router with
292software that you are familiar with so that you can explore all the options
293available to you.
294
295# Physical Security
296
297One large piece of self-hosting that people generally don't discuss online is
298physical security. However, physical security is very important for everyone who
299hosts a server like this. Exactly _how_ important it is depends on the server
300use/purpose.
301
302If you self-host customer applications that hold protected data (HIPAA, GDPR,
303COPPA, etc.), then physical security is extremely important and cannot be
304ignored. If you simply host a blog and some hobby sites, then it's a relatively
305minor consideration, but one you still need to think about.
306
307## Location
308
309The first consideration is quite simple: location.
310
311- Is the server within a property you own or housed on someone else's
312 property?
313- Is it nearby (in your house, in your work office, in your neighbor's garage,
314 in a storage unit, etc.)?
315- Do you have 24/7 access to the server?
316- Are there climate considerations, such as humidity, fires, tornadoes,
317 monsoons?
318- Do you have emergency equipment nearby in case of emergency?
319
320## Hardware Ownership
321
322Secondly, consider the hardware itself:
323
324- Do you own the server in its entirety?
325- Are any other users able to access the server, even if your data/space is
326 segregated?
327- If you're utilizing a third party, do they have any documentation to show
328 responsibility? This could be a SOC 1/2/3 report, ISO compliance report,
329 internal security/safety documentation.
330
331## Physical Controls
332
333Regardless of who owns the hardware, ensure that there are adequate safeguards
334in place, if necessary. These usually don't apply to small home servers and are
335usually covered already if you're utilizing a third party.
336
337These can include:
338
339- Server bezel locks
340- Server room locks - physical, digital, or biometric authentication
341- Security cameras
342- Raised floors/lowered ceilings with proper guards/gates in-place within the
343 floors or ceilings
344- Security personnel
345- Log sheets and/or guest badges
content/blog/2022-03-24-server-hardening.org added +341
@@ -0,0 +1,341 @@
1#+date: <2022-03-24>
2#+title: Hardening a Public-Facing Home Server
3#+description:
4
5
6* Post Updates
7
8#+begin_quote
9After reviewing this post today (2022-10-04), I noticed quite a few gaps in my
10write-up and wanted to add a few things, even though this blog is really just a
11retrospective and knowledge dump for myself. I left things intact and simply
12crossed them out (+like this+) for posterity.
13#+end_quote
14
15* Planning Data Flows & Security
16
17** My Personal Data Flow
18
19#+begin_src
20 ┌───────┐ ┌─────────────────┐
21 ┌──► VLAN1 ├───► Private Devices │
22 │ └───────┘ └─────────────────┘
23┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
24│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
25└──────────┘ └────────┘ └──────────┘ └────────┘ │
26 │ ┌───────┐ ┌───────────────┐
27 └──► VLAN2 ├───► Public Server │
28 └───────┘ └───────────────┘
29#+end_src
30
31** Thought Process
32
33To serve content from your home server and harden your security posture, you
34have to think about the transport of data from =server= to =client=.
35
36Let's start with the actual server itself. Think about the following:
37
38- Do I have a firewall enabled? Do I need to update this to allow new ports or
39 IPs?
40- Do I have an IPS/IDS that may prevent outside traffic?
41- Do I have any other security software installed?
42- Are the services hosted inside Docker containers, behind a reverse proxy, or
43 virtualized? If so, are they configured to allow outside traffic?
44
45Once the data leaves the server, where does it go? In my case, it goes to a
46managed switch. In this case, I asked the following:
47
48- What configurations is the switch using?
49- Am I using VLANs?
50 - Yes, I am using 802.1Q VLANs.
51- Are the VLANs configured properly?
52 - Yes, as shown in the Switch section below, I have a separate VLAN to allow
53 outside traffic to and from the server alone. No other devices, except for a
54 service port, and in that VLAN.
55
56At this point, the data has been processed through the switch. Where does it go
57next? In my case, it's pretty simple: it goes to the router/modem device.
58
59- Does my ISP block any ports that I need?
60 - This is an important step that a lot of people run into when self-hosting at
61 home. Use an online port-checker tool for your IP or call your ISP if you
62 think ports are blocked.
63- Is there a router firewall?
64 - Yes, I checked that it's configured to allow the ports I need to run my
65 services publicly. Common web servers and reverse proxies require ports 80
66 and 443, but other services like media servers or games can require unique
67 ports, so be sure to check the documentation for your 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 require
75 the use of ports 80 and 443)
76
77Once the data leaves my router, it goes to the upstream ISP and can be accessed
78publicly.
79
80*** Server
81
82The services I run on my server are installed straight into the OS, without any
83use of Docker or VMs, so I don't need any extra application configuration to
84make them accessible to the outside world.+
85
86As of 2022-10-04, the paragraph above is no longer true as I now run a reverse
87proxy with Nginx and host many services inside Docker. However, it doesn't
88change anything regarding this post as I still just need to open ports 80 & 443
89and create the necessary website configuration files.
90
91When creating new services - either installed directly on bare metal or within
92something like Docker - I ensure that I read through the documentation
93thoroughly to understand a few key things: - What network activities should this
94app perform (if any)? Using which ports and protocols? - Does this app require
95any commands/services to be run as =root=? - Does this app log errors,
96authentication failures/successes, or anything else that would be useful for an
97investigation?
98
99For extra security, I use limit all incoming connections to SSH connections
100through my server firewall (=ufw=) and disable common SSH settings. After all of
101that, I use =fail2ban= as a preventative measure against brute-force login
102attempts.
103
104As another piece of security, you can randomize your SSH port to ensure that
105random scanners or attackers can't easily try to force their way into your
106network. For example, you can edit the port rules in your server to block all
107connection requests to port =22= but forward all remote connections from port
108=12345= to your server's port =22=. Then you just need to SSH to your network
109via your randomized port.
110
111** =ufw=
112
113To see how to configure =ufw=, see my other post: [[/blog/ufw.html][Secure Your
114Network with the Uncomplicated Firewall]].
115
116The general notion with an on-device firewall is that you want to deny all
117incoming connections by default and then selectively open certain ports for
118services or users that you know need access.
119
120If you know that you will only be logging into this server from a certain set or
121list of IPs, you can always set the firewall to only allow connections to port
12222 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** =ssh=
134
1351. Using SSH Keys
136
137 First, make sure you have an SSH keypair generated on the device(s) that
138 you'll be using to log in to the server. If you don't have an SSH key, run
139 this command:
140
141 #+begin_src sh
142 ssh-keygen
143 #+end_src
144
145 Now that we have an SSH key, copy it to the server with the following
146 command, which will ask for the user's password before accepting the key:
147
148 #+begin_src sh
149 ssh-copy-id my_user@my_server
150 #+end_src
151
152 If you have multiple keys, you'll need to specify which to use. After it's
153 complete, =ssh= back into the server as that user and make sure it doesn't
154 ask for a password.
155
1562. Disable Password & Root Authentication
157
158 Now that we can access the server without a password, we will disable
159 password authentication and disable anyone from using =ssh= to login as
160 =root=.
161
162 To do this, open the =sshd_config= file:
163
164 #+begin_src sh
165 sudo nano /etc/ssh/sshd_config
166 #+end_src
167
168 You'll need to update the parameters to the values below. If one of these
169 rules is commented-out or doesn't exist, create the rule at the bottom of the
170 file.
171
172 #+begin_src config
173 PermitRootLogin no
174 PasswordAuthentication no
175 PubkeyAuthentication yes
176 #+end_src
177
178 Finally, restart the =ssh= service:
179
180 #+begin_src sh
181 sudo systemctl restart sshd.service
182 #+end_src
183
184 To test that everything's working so far, open ANOTHER terminal and try
185 logging in as =root= over SSH. It is very important that you keep your
186 current SSH session open and test with an additional session, or you will
187 lock yourself out at some point and will need to use a recovery method (e.g.,
188 hooking monitor up to home server) to get yourself back in.
189
1903. Enable MFA for =ssh=
191
192 This part is optional, but I highly recommend it. So far, we've ensured that
193 no one can log into our user on the server without using our secret key, and
194 we've ensured that no one can log in remotely as =root=. Next, you can enable
195 MFA authentication for =ssh= connections.
196
197 This process involves editing a couple files and installing an MFA package,
198 so I will not include all the details in this post. To see how to configure
199 MFA for =ssh=, see my other post: [[/blog/ssh-mfa.html][Enabling MFA for
200 SSH]].
201
202** =fail2ban=
203
204I haven't written a post on how I use =fail2ban=, but it's quite simple. I use
205the default =sshd= jail, but you can always create new jails for respective
206applications or ports. For example, if you use Nginx as your web server, you can
207use the =nginx-http-auth= jail.
208
209In order to get it up and running, use the following commands:
210
211#+begin_src sh
212sudo apt install fail2ban
213sudo fail2ban-client start sshd
214sudo fail2ban-client status sshd
215#+end_src
216
217This should be used as a last-resort defense and shouldn't be a replacement for
218the security measures mentioned above.
219
220* Switch
221
222Between the router and any local devices is my managed switch, which is used to
223create VLANs. The example below shows how I would isolate the VLANs if I were
224starting to host a single service at home.
225
226** 802.1Q VLAN Configuration
227
228In this configuration, port 8 is the public server that needs to be accessed
229from the outside. Port 23 is my 'dedicated service port' for this server. In
230order to SSH to this server, I need to plug my laptop into port 23 or else I
231cannot SSH. Otherwise, I'd need to hook up a monitor and keyboard directly to
232the server to manage it.
233
234| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
235|---------+-----------+--------------+--------------+----------------|
236| 1 | Default | 1-24 | 1-24 | |
237| 2 | Server | 1,8,23 | 1,8,23 | |
238
239** 802.1Q VLAN PVID Setting
240
241Once the VLAN is created, I simply add the =VLAN ID= of =2= as the =PVID= for
242any related ports (in this case, see that ports =8= and =23= have a PVID of
243=2=).
244
245| Port | PVID |
246|------+------|
247| 1 | 1 |
248| 2 | 1 |
249| 3 | 1 |
250| 4 | 1 |
251| 5 | 1 |
252| 6 | 1 |
253| 7 | 1 |
254| 8 | 2 |
255| 9 | 1 |
256| 10 | 1 |
257| 11 | 1 |
258| 12 | 1 |
259| 13 | 1 |
260| 14 | 1 |
261| 15 | 1 |
262| 16 | 1 |
263| 17 | 1 |
264| 18 | 1 |
265| 19 | 1 |
266| 20 | 1 |
267| 21 | 1 |
268| 22 | 1 |
269| 23 | 2 |
270| 24 | 1 |
271
272* Router
273
274On my router, the configuration was as easy as opening the firewall settings and
275unblocking the ports I needed for my services (e.g., HTTP/S, Plex, SSH, MySQL,
276etc.).
277
278Since I'm relying on an ISP-provided modem/router combo for now (not by
279choice), I do not use any other advanced settings on my router that would
280inhibit any valid traffic to these services.
281
282The paragraph above regarding the ISP-owned router is no longer accurate as I
283now use the Ubiquiti Unifi Dream Machine Pro as my router. Within this router, I
284enabled port forwarding/firewall rules, segregate the network based on the
285device, and enable traffic restrictions (e.g., silently drop traffic from
286certain countries and threat categories).
287
288If you have the option with your ISP, I recommend using a personal router with
289software that you are familiar with so that you can explore all the options
290available to you.
291
292* Physical Security
293
294One large piece of self-hosting that people generally don't discuss online is
295physical security. However, physical security is very important for everyone who
296hosts a server like this. Exactly /how/ important it is depends on the server
297use/purpose.
298
299If you self-host customer applications that hold protected data (HIPAA, GDPR,
300COPPA, etc.), then physical security is extremely important and cannot be
301ignored. If you simply host a blog and some hobby sites, then it's a relatively
302minor consideration, but one you still need to think about.
303
304** Location
305
306The first consideration is quite simple: location.
307
308- Is the server within a property you own or housed on someone else's property?
309- Is it nearby (in your house, in your work office, in your neighbor's garage,
310 in a storage unit, etc.)?
311- Do you have 24/7 access to the server?
312- Are there climate considerations, such as humidity, fires, tornadoes,
313 monsoons?
314- Do you have emergency equipment nearby in case of emergency?
315
316** Hardware Ownership
317
318Secondly, consider the hardware itself:
319
320- Do you own the server in its entirety?
321- Are any other users able to access the server, even if your data/space is
322 segregated?
323- If you're utilizing a third party, do they have any documentation to show
324 responsibility? This could be a SOC 1/2/3 report, ISO compliance report,
325 internal security/safety documentation.
326
327** Physical Controls
328
329Regardless of who owns the hardware, ensure that there are adequate safeguards
330in place, if necessary. These usually don't apply to small home servers and are
331usually covered already if you're utilizing a third party.
332
333These can include:
334
335- Server bezel locks
336- Server room locks - physical, digital, or biometric authentication
337- Security cameras
338- Raised floors/lowered ceilings with proper guards/gates in-place within the
339 floors or ceilings
340- Security personnel
341- Log sheets and/or guest badges
content/blog/2022-03-26-ssh-mfa.md → content/blog/2022-03-26-ssh-mfa.org renamed +89 −89
@@ -1,82 +1,82 @@
1+++ 1#+date: <2022-03-26>
2date = 2022-03-26 2#+title: Enable TOTP MFA for SSH
3title = "Enable TOTP MFA for SSH" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Why Do I Need MFA for SSH?
9 5
10If you are a sysadmin of a server anywhere (that includes at home!), you may 6* Why Do I Need MFA for SSH?
11want an added layer of protection against intruders. This is not a replacement
12for other security measures, such as:
13 7
14- Disable root SSH 8If you are a sysadmin of a server anywhere (that includes at home!), you
15- Disable SSH password authentication 9may want an added layer of protection against intruders. This is not a
16- Allow only certain users to login via SSH 10replacement for other security measures, such as:
17- Allow SSH only from certain IPs
18 11
19However, MFA can be added as an additional security measure to ensure that your 12- Disable root SSH
20server is protected. This is especially important if you need to allow password 13- Disable SSH password authentication
21authentication for SSH. 14- Allow only certain users to login via SSH
15- Allow SSH only from certain IPs
22 16
23For more guidance on server security measures, see my other post: [Hardening a 17However, MFA can be added as an additional security measure to ensure
24Public-Facing Home Server](../hardening-a-public-facing-home-server/). 18that your server is protected. This is especially important if you need
19to allow password authentication for SSH.
25 20
26# Install MFA PAM Module 21For more guidance on server security measures, see my other post:
22[[../hardening-a-public-facing-home-server/][Hardening a Public-Facing
23Home Server]].
27 24
28PAM, which stands for Pluggable Authentication Module, is an authentication 25* Install MFA PAM Module
29infrastructure used on Linux systems to authenticate a user. In order to use
30this technology, let's install the `libpam-google-authenticator` package:
31 26
32```sh 27PAM, which stands for Pluggable Authentication Module, is an
28authentication infrastructure used on Linux systems to authenticate a
29user. In order to use this technology, let's install the
30=libpam-google-authenticator= package:
31
32#+begin_src sh
33sudo apt-get update 33sudo apt-get update
34``` 34#+end_src
35 35
36```sh 36#+begin_src sh
37sudo apt-get install libpam-google-authenticator 37sudo apt-get install libpam-google-authenticator
38``` 38#+end_src
39 39
40# Initialize the PAM Module 40* Initialize the PAM Module
41 41
42## Interactive Method 42** Interactive Method
43 43
44Once the package is installed, initialize it and following the interactive 44Once the package is installed, initialize it and following the
45prompts to generate your OTP or TOTP: 45interactive prompts to generate your OTP or TOTP:
46 46
47```sh 47#+begin_src sh
48google-authenticator 48google-authenticator
49``` 49#+end_src
50 50
51If you are not sure how to answer, read the prompts carefully and think about 51If you are not sure how to answer, read the prompts carefully and think
52having to how each situation would affect your normal login attempts. If you are 52about having to how each situation would affect your normal login
53still not sure, use my default responses below. 53attempts. If you are still not sure, use my default responses below.
54 54
55```txt 55#+begin_src txt
56OUTPUT 56OUTPUT
57 57
58Do you want authentication tokens to be time-based (y/n) y 58Do you want authentication tokens to be time-based (y/n) y
59``` 59#+end_src
60 60
61At this point, use an authenticator app somewhere one of your devices to scan 61At this point, use an authenticator app somewhere one of your devices to
62the QR code. Any future login attempts after our upcoming configuration changes 62scan the QR code. Any future login attempts after our upcoming
63will require that TOTP. 63configuration changes will require that TOTP.
64 64
65```txt 65#+begin_src txt
66OUTPUT 66OUTPUT
67 67
68Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y 68Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y
69``` 69#+end_src
70 70
71```txt 71#+begin_src txt
72OUTPUT 72OUTPUT
73 73
74Do you want to disallow multiple uses of the same authentication 74Do you want to disallow multiple uses of the same authentication
75token? This restricts you to one login about every 30s, but it increases 75token? This restricts you to one login about every 30s, but it increases
76your chances to notice or even prevent man-in-the-middle attacks (y/n) y 76your chances to notice or even prevent man-in-the-middle attacks (y/n) y
77``` 77#+end_src
78 78
79```txt 79#+begin_src txt
80OUTPUT 80OUTPUT
81 81
82By default, a new token is generated every 30 seconds by the mobile app. 82By default, a new token is generated every 30 seconds by the mobile app.
@@ -89,30 +89,30 @@ code, the next code) to 17 permitted codes (the 8 previous codes, the current
89code, and the 8 next codes). This will permit for a time skew of up to 4 minutes 89code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
90between client and server. 90between client and server.
91Do you want to do so? (y/n) n 91Do you want to do so? (y/n) n
92``` 92#+end_src
93 93
94```txt 94#+begin_src txt
95OUTPUT 95OUTPUT
96 96
97If the computer that you are logging into isn't hardened against brute-force 97If the computer that you are logging into isn't hardened against brute-force
98login attempts, you can enable rate-limiting for the authentication module. 98login attempts, you can enable rate-limiting for the authentication module.
99By default, this limits attackers to no more than 3 login attempts every 30s. 99By default, this limits attackers to no more than 3 login attempts every 30s.
100Do you want to enable rate-limiting? (y/n) y 100Do you want to enable rate-limiting? (y/n) y
101``` 101#+end_src
102 102
103## Non-Interactive Method 103** Non-Interactive Method
104 104
105If you need to do this quickly, know your responses to the prompts, or are 105If you need to do this quickly, know your responses to the prompts, or
106setting this up for numerous users, the non-interactive method can be much 106are setting this up for numerous users, the non-interactive method can
107faster: 107be much faster:
108 108
109```sh 109#+begin_src sh
110google-authenticator -t -d -f -r 3 -R 30 -w 3 110google-authenticator -t -d -f -r 3 -R 30 -w 3
111``` 111#+end_src
112 112
113The options referenced above are as follows: 113The options referenced above are as follows:
114 114
115```txt 115#+begin_src txt
116google-authenticator [<options>] 116google-authenticator [<options>]
117 -h, --help Print this message 117 -h, --help Print this message
118 -c, --counter-based Set up counter-based (HOTP) verification 118 -c, --counter-based Set up counter-based (HOTP) verification
@@ -132,65 +132,65 @@ google-authenticator [<options>]
132 -w, --window-size=W Set window of concurrently valid codes 132 -w, --window-size=W Set window of concurrently valid codes
133 -W, --minimal-window Disable window of concurrently valid codes 133 -W, --minimal-window Disable window of concurrently valid codes
134 -e, --emergency-codes=N Number of emergency codes to generate 134 -e, --emergency-codes=N Number of emergency codes to generate
135``` 135#+end_src
136 136
137This fully configures the authenticator, saves it to a file, and then outputs 137This fully configures the authenticator, saves it to a file, and then
138the secret key, QR code, and recovery codes. (If you add the flag `-q`, then 138outputs the secret key, QR code, and recovery codes. (If you add the
139there won't be any output). If you use this command in an automated fashion, 139flag =-q=, then there won't be any output). If you use this command in
140make sure your script captures the secret key and/or recovery codes and makes 140an automated fashion, make sure your script captures the secret key
141them available to the user. 141and/or recovery codes and makes them available to the user.
142 142
143# PAM Configuration Settings 143* PAM Configuration Settings
144 144
145Once you've enabled the T/OTP and have it saved to an MFA app on your phone or 145Once you've enabled the T/OTP and have it saved to an MFA app on your
146other device, open the PAM `sshd` file: 146phone or other device, open the PAM =sshd= file:
147 147
148```sh 148#+begin_src sh
149sudo nano /etc/pam.d/sshd 149sudo nano /etc/pam.d/sshd
150``` 150#+end_src
151 151
152You need to do two things in this file. First, add the following lines to the 152You need to do two things in this file. First, add the following lines
153bottom of the file: 153to the bottom of the file:
154 154
155```config 155#+begin_src config
156auth required pam_google_authenticator.so nullok 156auth required pam_google_authenticator.so nullok
157auth required pam_permit.so 157auth required pam_permit.so
158``` 158#+end_src
159 159
160Second, comment-out the following line near the top of the file. 160Second, comment-out the following line near the top of the file.
161 161
162If you leave this line uncommented, every SSH login attempt will ask for the 162If you leave this line uncommented, every SSH login attempt will ask for
163following three authentication factors: 163the following three authentication factors:
164 164
1651. Publickey 1651. Publickey
1662. Password 1662. Password
1673. T/OTP code 1673. T/OTP code
168 168
169```config 169#+begin_src config
170#@include common-auth 170#@include common-auth
171``` 171#+end_src
172 172
173# SSH Configuration Settings 173* SSH Configuration Settings
174 174
175Finally, edit the `sshd_config` file again: 175Finally, edit the =sshd_config= file again:
176 176
177```sh 177#+begin_src sh
178sudo nano /etc/ssh/sshd_config 178sudo nano /etc/ssh/sshd_config
179``` 179#+end_src
180 180
181You'll need to change `ChallengeResponseAuthentication` to yes and add the 181You'll need to change =ChallengeResponseAuthentication= to yes and add
182`AuthenticationMethods` line to the bottom of the file. 182the =AuthenticationMethods= line to the bottom of the file.
183 183
184```config 184#+begin_src config
185ChallengeResponseAuthentication yes 185ChallengeResponseAuthentication yes
186AuthenticationMethods publickey,password publickey,keyboard-interactive 186AuthenticationMethods publickey,password publickey,keyboard-interactive
187``` 187#+end_src
188 188
189Finally, restart the `ssh` service: 189Finally, restart the =ssh= service:
190 190
191```sh 191#+begin_src sh
192sudo systemctl restart sshd.service 192sudo systemctl restart sshd.service
193``` 193#+end_src
194 194
195The next time you log in, you should be greeted with a verification code 195The next time you log in, you should be greeted with a verification code
196request! 196request!
content/blog/2022-04-02-nginx-reverse-proxy.md deleted −226
@@ -1,226 +0,0 @@
1+++
2date = 2022-04-02
3title = "Set-Up a Reverse Proxy with Nginx"
4description = ""
5draft = false
6+++
7
8# What is a Reverse Proxy?
9
10A reverse proxy is a server that is placed between local servers or services and
11clients/users (e.g., the internet). The reverse proxy intercepts all requests
12from clients at the network edge and uses its configuration files to determine
13where each request should be sent.
14
15## A Brief Example
16
17For example, let's say that I run three servers in my home:
18
19- Server01 (`example.com`)
20- Server02 (`service01.example.com`)
21- Server03 (`service02.example.com`)
22
23I also run a reverse proxy in my home that intercepts all public traffic:
24
25- Reverse Proxy
26
27Assume that I have a domain name (`example.com`) that allows clients to request
28websites or services from my home servers.
29
30In this case, the reverse proxy will intercept all traffic from `example.com`
31that enters my network and determine if the client is requesting valid data,
32based on my configuration.
33
34If the user is requesting `example.com` and my configuration files say that
35Server~01~ holds that data, Nginx will send the user to Server~01~. If I were to
36change the configuration so that `example.com` is routed to Server~02~, that
37same user would be sent to Server~02~ instead.
38
39```txt
40┌──────┐ ┌───────────┐
41│ User │─┐ ┌──► Server_01 │
42└──────┘ │ │ └───────────┘
43 │ ┌──────────┐ ┌───────────────┐ │ ┌───────────┐
44 ├────► Internet ├───► Reverse Proxy ├─────├──► Server_02 │
45 │ └──────────┘ └───────────────┘ │ └───────────┘
46┌──────┐ │ │ ┌───────────┐
47│ User │─┘ └──► Server_03 │
48└──────┘ └───────────┘
49```
50
51# Reverse Proxy Options
52
53There are a lot of options when it comes to reverse proxy servers, so I'm just
54going to list a few of the options I've heard recommended over the last few
55years:
56
57- [Nginx](https://nginx.com)
58- [Caddy](https://caddyserver.com)
59- [Traefik](https://traefik.io/)
60- [HAProxy](https://www.haproxy.org/)
61- [Squid](https://ubuntu.com/server/docs/proxy-servers-squid)
62
63In this post, we will be using Nginx as our reverse proxy, running on Ubuntu
64Server 20.04.4 LTS.
65
66# Nginx Reverse Proxy Example
67
68## Local Applications
69
70You may be like me and have a lot of applications running on your local network
71that you'd like to expose publicly with a domain.
72
73In my case, I have services running in multiple Docker containers within a
74single server and want a way to visit those services from anywhere with a URL.
75For example, on my local network, [Dashy](https://dashy.to) runs through port
764000 (`localhost:4000`) and [Uptime
77Kuma](https://github.com/louislam/uptime-kuma) runs through port 3001
78(`localhost:3001`).
79
80In order to expose these services to the public, I will need to do the
81following:
82
831. Set up DNS records for a domain or subdomain (one per service) to point
84 toward the IP address of the server.
852. Open up the server network's HTTP and HTTPS ports (80 & 443) so that the
86 reverse proxy can accept traffic and determine where to send it.
873. Install the reverse proxy software.
884. Configure the reverse proxy to recognize which service should get traffic
89 from any of the domains or subdomains.
90
91## Step 1: DNS Configuration
92
93To start, update your DNS configuration so that you have an `A` record for each
94domain or subdomain.
95
96The `A` records should point toward the public IP address of the server. If you
97don't know the public IP address, log in to the server and run the following
98command:
99
100```sh
101curl ifconfig.co
102```
103
104In the DNS example below, `xxx.xxx.xxx.xxx` is the public IP address of the
105server.
106
107```conf
108example.com A xxx.xxx.xxx.xxx
109uptime.example.com A xxx.xxx.xxx.xxx
110dashy.example.com A xxx.xxx.xxx.xxx
111www CNAME example.com
112```
113
114Finally, ensure the DNS has propagated correctly with [DNS
115Checker](https://dnschecker.org) by entering your domains or subdomains in the
116search box and ensuring the results are showing the correct IP address.
117
118## Step 2: Open Network Ports
119
120This step will be different depending on which router you have in your home. If
121you're not sure, try to visit [192.168.1.1](http://192.168.1.1) in your browser.
122Login credentials are usually written on a sticker somewhere on your
123modem/router.
124
125Once you're able to log in to your router, find the Port Forwarding settings.
126You will need to forward ports `80` and `443` to whichever machine is running
127the reverse proxy.
128
129In my case, the table below shows the port-forwarding rules I've created. In
130this table, `xxx.xxx.xxx.xxx` is the local device IP of the reverse proxy
131server, it will probably be an IP between `192.168.1.1` and `192.168.1.255`.
132
133| NAME | FROM PORT | DEST PORT/IP | ENABLED |
134| ----- | --------- | --------------- | ------- |
135| HTTP | 80 | xxx.xxx.xxx.xxx | TRUE |
136| HTTPS | 443 | xxx.xxx.xxx.xxx | TRUE |
137
138Once configured, these rules will direct all web traffic to your reverse proxy.
139
140## Step 3: Nginx Installation
141
142To install Nginx, simply run the following command:
143
144```sh
145sudo apt install nginx
146```
147
148If you have a firewall enabled, open up ports `80` and `443` on your server so
149that Nginx can accept web traffic from the router.
150
151For example, if you want to use `ufw` for web traffic and SSH, run the following
152commands:
153
154```sh
155sudo ufw allow 'Nginx Full'
156sudo ufw allow SSH
157sudo ufw enable
158```
159
160## Step 4: Nginx Configuration
161
162Now that we have domains pointing toward the server, the only step left is to
163configure the reverse proxy to direct traffic from domains to local services.
164
165To start, you'll need to create a configuration file for each domain in
166`/etc/nginx/sites-available/`. They will look identical except for the
167`server_name` variable and the `proxy_pass` port.
168
169Dashy:
170
171```sh
172nano /etc/nginx/sites-available/dashy.example.com
173```
174
175```config
176server {
177 listen 80;
178 server_name dashy.example.com;
179
180 location / {
181 proxy_pass http://localhost:4000;
182 }
183}
184```
185
186Uptime:
187
188```sh
189nano /etc/nginx/sites-available/uptime.example.com
190```
191
192```config
193server {
194 listen 80;
195 server_name uptime.example.com;
196
197 location / {
198 proxy_pass http://localhost:3001;
199 }
200}
201```
202
203Once the configuration files are created, you will need to enable them with the
204`symlink` command:
205
206```sh
207sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
208```
209
210Voilà! Your local services should now be available through their URLs.
211
212# HTTPS with Certbot
213
214If you've followed along, you'll notice that your services are only available
215via HTTP (not HTTPS).
216
217If you want to enable HTTPS for your new domains, you will need to generate
218SSL/TLS certificates for them. The easiest way to generate certificates on Nginx
219is [Certbot](https://certbot.eff.org):
220
221```sh
222sudo apt install snapd; sudo snap install core; sudo snap refresh core
223sudo snap install --classic certbot
224sudo ln -s /snap/bin/certbot /usr/bin/certbot
225sudo certbot --nginx
226```
content/blog/2022-04-02-nginx-reverse-proxy.org added +230
@@ -0,0 +1,230 @@
1#+date: <2022-04-02>
2#+title: Set-Up a Reverse Proxy with Nginx
3#+description:
4
5
6* What is a Reverse Proxy?
7
8A reverse proxy is a server that is placed between local servers or
9services and clients/users (e.g., the internet). The reverse proxy
10intercepts all requests from clients at the network edge and uses its
11configuration files to determine where each request should be sent.
12
13** A Brief Example
14
15For example, let's say that I run three servers in my home:
16
17- Server01 (=example.com=)
18- Server02 (=service01.example.com=)
19- Server03 (=service02.example.com=)
20
21I also run a reverse proxy in my home that intercepts all public
22traffic:
23
24- Reverse Proxy
25
26Assume that I have a domain name (=example.com=) that allows clients to
27request websites or services from my home servers.
28
29In this case, the reverse proxy will intercept all traffic from
30=example.com= that enters my network and determine if the client is
31requesting valid data, based on my configuration.
32
33If the user is requesting =example.com= and my configuration files say
34that Server_{01} holds that data, Nginx will send the user to
35Server_{01}. If I were to change the configuration so that =example.com=
36is routed to Server_{02}, that same user would be sent to Server_{02}
37instead.
38
39#+begin_src txt
40┌──────┐ ┌───────────┐
41│ User │─┐ ┌──► Server_01 │
42└──────┘ │ │ └───────────┘
43 │ ┌──────────┐ ┌───────────────┐ │ ┌───────────┐
44 ├────► Internet ├───► Reverse Proxy ├─────├──► Server_02 │
45 │ └──────────┘ └───────────────┘ │ └───────────┘
46┌──────┐ │ │ ┌───────────┐
47│ User │─┘ └──► Server_03 │
48└──────┘ └───────────┘
49#+end_src
50
51* Reverse Proxy Options
52
53There are a lot of options when it comes to reverse proxy servers, so
54I'm just going to list a few of the options I've heard recommended over
55the last few years:
56
57- [[https://nginx.com][Nginx]]
58- [[https://caddyserver.com][Caddy]]
59- [[https://traefik.io/][Traefik]]
60- [[https://www.haproxy.org/][HAProxy]]
61- [[https://ubuntu.com/server/docs/proxy-servers-squid][Squid]]
62
63In this post, we will be using Nginx as our reverse proxy, running on
64Ubuntu Server 20.04.4 LTS.
65
66* Nginx Reverse Proxy Example
67
68** Local Applications
69
70You may be like me and have a lot of applications running on your local
71network that you'd like to expose publicly with a domain.
72
73In my case, I have services running in multiple Docker containers within
74a single server and want a way to visit those services from anywhere
75with a URL. For example, on my local network,
76[[https://dashy.to][Dashy]] runs through port 4000 (=localhost:4000=)
77and [[https://github.com/louislam/uptime-kuma][Uptime Kuma]] runs
78through port 3001 (=localhost:3001=).
79
80In order to expose these services to the public, I will need to do the
81following:
82
831. Set up DNS records for a domain or subdomain (one per service) to
84 point toward the IP address of the server.
852. Open up the server network's HTTP and HTTPS ports (80 & 443) so that
86 the reverse proxy can accept traffic and determine where to send it.
873. Install the reverse proxy software.
884. Configure the reverse proxy to recognize which service should get
89 traffic from any of the domains or subdomains.
90
91** Step 1: DNS Configuration
92
93To start, update your DNS configuration so that you have an =A= record
94for each domain or subdomain.
95
96The =A= records should point toward the public IP address of the server.
97If you don't know the public IP address, log in to the server and run
98the following command:
99
100#+begin_src sh
101curl ifconfig.co
102#+end_src
103
104In the DNS example below, =xxx.xxx.xxx.xxx= is the public IP address of
105the server.
106
107#+begin_src conf
108example.com A xxx.xxx.xxx.xxx
109uptime.example.com A xxx.xxx.xxx.xxx
110dashy.example.com A xxx.xxx.xxx.xxx
111www CNAME example.com
112#+end_src
113
114Finally, ensure the DNS has propagated correctly with
115[[https://dnschecker.org][DNS Checker]] by entering your domains or
116subdomains in the search box and ensuring the results are showing the
117correct IP address.
118
119** Step 2: Open Network Ports
120
121This step will be different depending on which router you have in your
122home. If you're not sure, try to visit
123[[http://192.168.1.1][192.168.1.1]] in your browser. Login credentials
124are usually written on a sticker somewhere on your modem/router.
125
126Once you're able to log in to your router, find the Port Forwarding
127settings. You will need to forward ports =80= and =443= to whichever
128machine is running the reverse proxy.
129
130In my case, the table below shows the port-forwarding rules I've
131created. In this table, =xxx.xxx.xxx.xxx= is the local device IP of the
132reverse proxy server, it will probably be an IP between =192.168.1.1=
133and =192.168.1.255=.
134
135| NAME | FROM PORT | DEST PORT/IP | ENABLED |
136|-------+-----------+-----------------+---------|
137| HTTP | 80 | xxx.xxx.xxx.xxx | TRUE |
138| HTTPS | 443 | xxx.xxx.xxx.xxx | TRUE |
139
140Once configured, these rules will direct all web traffic to your reverse
141proxy.
142
143** Step 3: Nginx Installation
144
145To install Nginx, simply run the following command:
146
147#+begin_src sh
148sudo apt install nginx
149#+end_src
150
151If you have a firewall enabled, open up ports =80= and =443= on your
152server so that Nginx can accept web traffic from the router.
153
154For example, if you want to use =ufw= for web traffic and SSH, run the
155following commands:
156
157#+begin_src sh
158sudo ufw allow 'Nginx Full'
159sudo ufw allow SSH
160sudo ufw enable
161#+end_src
162
163** Step 4: Nginx Configuration
164
165Now that we have domains pointing toward the server, the only step left
166is to configure the reverse proxy to direct traffic from domains to
167local services.
168
169To start, you'll need to create a configuration file for each domain in
170=/etc/nginx/sites-available/=. They will look identical except for the
171=server_name= variable and the =proxy_pass= port.
172
173Dashy:
174
175#+begin_src sh
176nano /etc/nginx/sites-available/dashy.example.com
177#+end_src
178
179#+begin_src config
180server {
181 listen 80;
182 server_name dashy.example.com;
183
184 location / {
185 proxy_pass http://localhost:4000;
186 }
187}
188#+end_src
189
190Uptime:
191
192#+begin_src sh
193nano /etc/nginx/sites-available/uptime.example.com
194#+end_src
195
196#+begin_src config
197server {
198 listen 80;
199 server_name uptime.example.com;
200
201 location / {
202 proxy_pass http://localhost:3001;
203 }
204}
205#+end_src
206
207Once the configuration files are created, you will need to enable them
208with the =symlink= command:
209
210#+begin_src sh
211sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
212#+end_src
213
214Voilà! Your local services should now be available through their URLs.
215
216* HTTPS with Certbot
217
218If you've followed along, you'll notice that your services are only
219available via HTTP (not HTTPS).
220
221If you want to enable HTTPS for your new domains, you will need to
222generate SSL/TLS certificates for them. The easiest way to generate
223certificates on Nginx is [[https://certbot.eff.org][Certbot]]:
224
225#+begin_src sh
226sudo apt install snapd; sudo snap install core; sudo snap refresh core
227sudo snap install --classic certbot
228sudo ln -s /snap/bin/certbot /usr/bin/certbot
229sudo certbot --nginx
230#+end_src
content/blog/2022-04-09-pinetime.md deleted −134
@@ -1,134 +0,0 @@
1+++
2date = 2022-04-09
3title = "PineTime: An Open-Source Smart Watch"
4description = ""
5draft = false
6+++
7
8# PineTime Product Information
9
10## Overview
11
12The [PineTime](https://www.pine64.org/pinetime/) is an open-source smartwatch,
13created by [PINE64](https://www.pine64.org). Originally announced in September
142019, this ARM-based watch is a fantastic option for users who want the benefits
15of a modern smartwatch with the backing of open-source components and software.
16
17## Product Specifications
18
19I won't dive into too many details that you can find on [the product
20page](https://www.pine64.org/pinetime/), but I wanted to point out the prices
21for each watch and the primary functions:
22
231. Price:
24
25 - $26.99 (Sealed)
26 - $24.99 (Dev Kit)
27 - $51.98 (One Sealed + One Dev Kit)
28
292. Primary Functionality:
30
31 - Clock (+ Smartphone Sync)
32 - Pedometer
33 - Heart Rate Monitor
34 - Sleep Monitor
35 - Calories burned
36 - Messaging
37 - Smartphone Notifications
38 - Media Controls
39
40# Unboxing
41
42Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and was
43delivered on 2022-03-23. With the current delays on shipping times around the
44world (and the semiconductor shortage), a month for delivery from China seems
45reasonable to me.
46
47The packaging is simple, and the watch comes with instructions, technical
48information, the watch, and a charger (it does not include a USB wall adapter).
49
50The watch itself was able to turn on immediately when I pulled it out of the
51box, but the battery was depleted and required charging right away.
52
53# Software
54
55## Watch OS: InfiniTime
56
57While turning on the watch for the first time, some of the main design choices
58you can see in the watch OS,
59[InfiniTime](https://wiki.pine64.org/wiki/InfiniTime), are:
60
61- A square bezel, not too thin against the sides of the watch.
62- A simple, rubber band.
63- Basic font and screen pixel design.
64- Swipe gestures to access other screens.
65
66The OS itself is fantastic in terms of functionality for me. It does exactly
67what a smartwatch should do - track time, steps, heart rates, and connect to
68another smart device, without being overly burdensome to the user.
69
70My only gripe so far is that it's _really_ difficult to swipe to different
71screens, such as pulling down the notification tray. I'm not sure if this is an
72OS or hardware issue, but it makes it quite hard to quickly move around the
73screens.
74
75However, my absolute favorite design choice is that the button the side turns
76the screen on and off and tilting/waving my wrist doesn't accidentally turn on
77the screen. With other watches, I absolutely hated not being able to turn off
78the raise-to-wake or wave features (i.e., blinding myself while wearing a watch
79at night because I moved my arm).
80
81## iOS App: InfiniLink
82
83Since I am using iOS as my primary mobile device OS, I am using the
84[InfiniLink](https://github.com/xan-m/InfiniLink) app to connect my watch.
85
86This app provides the following for PineTime owners:
87
88- Firmware updates
89- Steps
90- Charts
91- Notifications
92
93Another big feature of InfiniLink is the ability to track pedometer steps in a
94collection of beautiful graphs, with the option to change your step goal and add
95in manual steps.
96
97Finally, there are charts to display the battery percentage and heart rates over
98time. This area also comes with an option to clear data.
99
100# Final Thoughts
101
102## Pros
103
104After wearing my watch for a few weeks, I have mostly positive thoughts about
105the watch so far. In the past, I have owned smartwatches by FitBit, Fossil,
106Apple, etc. - **but I prefer the PineTime over all of those watches**.
107
108The PineTime strips out all the unnecessary features and performs the functions
109that it provides effectively and efficiently.
110
111The battery life is amazing on this device. By default, the watch seems to last
112anywhere from a few days to a week before dying.
113
114And of course, it's open source and backed by some of the most dedicated
115enthusiasts and developers I've seen. Watching the Matrix channel, forums, and
116website have been exciting to see.
117
118## Cons
119
120If I had to complain about anything, it would simply be the small bugs in some
121features that can be contributed to the companion apps more than the watch
122itself.
123
124A regular user would want native notification support out-of-the-box, which is
125the biggest item not working for me at the moment.
126
127My only other complaint is that the battery indicator on the watch doesn't seem
128accurate when it's nearing depletion - it seems that there's a bit of battery
129life left and then my watch is dead very suddenly after. This could just be me
130misinterpreting the battery level icons, but it has fooled me a few times into
131thinking I had more battery left than I actually did.
132
133Other than those small items, I really do love this watch and am glad I replaced
134my Apple Watch with the PineTime.
content/blog/2022-04-09-pinetime.org added +138
@@ -0,0 +1,138 @@
1#+date: <2022-04-09>
2#+title: PineTime: An Open-Source Smart Watch
3#+description:
4
5
6* PineTime Product Information
7
8** Overview
9
10The [[https://www.pine64.org/pinetime/][PineTime]] is an open-source
11smartwatch, created by [[https://www.pine64.org][PINE64]]. Originally
12announced in September 2019, this ARM-based watch is a fantastic option
13for users who want the benefits of a modern smartwatch with the backing
14of open-source components and software.
15
16** Product Specifications
17
18I won't dive into too many details that you can find on
19[[https://www.pine64.org/pinetime/][the product page]], but I wanted to
20point out the prices for each watch and the primary functions:
21
221. Price:
23
24 - $26.99 (Sealed)
25 - $24.99 (Dev Kit)
26 - $51.98 (One Sealed + One Dev Kit)
27
282. Primary Functionality:
29
30 - Clock (+ Smartphone Sync)
31 - Pedometer
32 - Heart Rate Monitor
33 - Sleep Monitor
34 - Calories burned
35 - Messaging
36 - Smartphone Notifications
37 - Media Controls
38
39* Unboxing
40
41Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and
42was delivered on 2022-03-23. With the current delays on shipping times
43around the world (and the semiconductor shortage), a month for delivery
44from China seems reasonable to me.
45
46The packaging is simple, and the watch comes with instructions,
47technical information, the watch, and a charger (it does not include a
48USB wall adapter).
49
50The watch itself was able to turn on immediately when I pulled it out of
51the box, but the battery was depleted and required charging right away.
52
53* Software
54
55** Watch OS: InfiniTime
56
57While turning on the watch for the first time, some of the main design
58choices you can see in the watch OS,
59[[https://wiki.pine64.org/wiki/InfiniTime][InfiniTime]], are:
60
61- A square bezel, not too thin against the sides of the watch.
62- A simple, rubber band.
63- Basic font and screen pixel design.
64- Swipe gestures to access other screens.
65
66The OS itself is fantastic in terms of functionality for me. It does
67exactly what a smartwatch should do - track time, steps, heart rates,
68and connect to another smart device, without being overly burdensome to
69the user.
70
71My only gripe so far is that it's /really/ difficult to swipe to
72different screens, such as pulling down the notification tray. I'm not
73sure if this is an OS or hardware issue, but it makes it quite hard to
74quickly move around the screens.
75
76However, my absolute favorite design choice is that the button the side
77turns the screen on and off and tilting/waving my wrist doesn't
78accidentally turn on the screen. With other watches, I absolutely hated
79not being able to turn off the raise-to-wake or wave features (i.e.,
80blinding myself while wearing a watch at night because I moved my arm).
81
82** iOS App: InfiniLink
83
84Since I am using iOS as my primary mobile device OS, I am using the
85[[https://github.com/xan-m/InfiniLink][InfiniLink]] app to connect my
86watch.
87
88This app provides the following for PineTime owners:
89
90- Firmware updates
91- Steps
92- Charts
93- Notifications
94
95Another big feature of InfiniLink is the ability to track pedometer
96steps in a collection of beautiful graphs, with the option to change
97your step goal and add in manual steps.
98
99Finally, there are charts to display the battery percentage and heart
100rates over time. This area also comes with an option to clear data.
101
102* Final Thoughts
103
104** Pros
105
106After wearing my watch for a few weeks, I have mostly positive thoughts
107about the watch so far. In the past, I have owned smartwatches by
108FitBit, Fossil, Apple, etc. - *but I prefer the PineTime over all of
109those watches*.
110
111The PineTime strips out all the unnecessary features and performs the
112functions that it provides effectively and efficiently.
113
114The battery life is amazing on this device. By default, the watch seems
115to last anywhere from a few days to a week before dying.
116
117And of course, it's open source and backed by some of the most dedicated
118enthusiasts and developers I've seen. Watching the Matrix channel,
119forums, and website have been exciting to see.
120
121** Cons
122
123If I had to complain about anything, it would simply be the small bugs
124in some features that can be contributed to the companion apps more than
125the watch itself.
126
127A regular user would want native notification support out-of-the-box,
128which is the biggest item not working for me at the moment.
129
130My only other complaint is that the battery indicator on the watch
131doesn't seem accurate when it's nearing depletion - it seems that
132there's a bit of battery life left and then my watch is dead very
133suddenly after. This could just be me misinterpreting the battery level
134icons, but it has fooled me a few times into thinking I had more battery
135left than I actually did.
136
137Other than those small items, I really do love this watch and am glad I
138replaced my Apple Watch with the PineTime.
content/blog/2022-06-01-ditching-cloudflare.md deleted −89
@@ -1,89 +0,0 @@
1+++
2date = 2022-06-01
3title = "Ditching Cloudflare for Njalla"
4description = ""
5draft = false
6+++
7
8# Registrar
9
10After spending a year or so using Cloudflare for DNS only - no proxying or
11applications - I spent the last few months using Cloudflare Tunnels and
12Cloudflare Access to protect my self-hosted websites and applications via their
13proxy traffic model.
14
15However, I have never liked using Cloudflare due to their increasingly large
16share of control over web traffic, as well as their business model of being a
17MITM for all of your traffic.
18
19So, as of today, I have switched over to [Njalla](https://njal.la) as my
20registrar and DNS manager. I was able to easily transfer my domains over
21rapidly, with only one domain taking more than 15-30 minutes to propagate.
22
23+I do still have two domains sitting at Cloudflare for the moment while I decide
24if they're worth the higher rates (one domain is 30€ and the other is 45€).+
25
26> **Update (2022.06.03)**: I ended up transferring my final two domains over to
27> Njalla, clearing my Cloudflare account of personal data, and deleting the
28> Cloudflare account entirely. _I actually feel relieved to have moved on to a
29> provider I trust._
30
31# DNS
32
33As noted above, I'm using Njalla exclusively for DNS configurations on my
34domains.
35
36However, the transfer process was not ideal. As soon as the domains transferred
37over, I switched the nameservers from Cloudflare to Njalla and lost most of the
38associated DNS records. So, the majority of the time spent during the migration
39was simply re-typing all the DNS records back in one-by-one.
40
41This would be much simpler if I were able to edit the plain-text format of the
42DNS configuration. I was able to do that at a past registrar (perhaps it was
43[Gandi.net](https://gandi.net/)?) and it made life a lot easier.
44
45## Dynamic DNS Updates
46
47I have built an easy Python script to run (or set-up in `cron` to run
48automatically) that will check my server's IPv4 and IPv6, compare it to Njalla,
49and update the DNS records if they don't match. You can see the full script and
50process in my other post: [Updating Dynamic DNS with Njalla
51API](../njalla-dns-api/).
52
53I haven't used this other method, but I do know that you can create `Dynamic`
54DNS records with Njalla that [work for updating dynamic
55subdomains](https://njal.la/docs/ddns/).
56
57## Njalla's DNS Tool
58
59One neat upside to Njalla is that they have a [DNS lookup
60tool](https://check.njal.la/dns/) that provides a lot of great information for
61those of you (AKA: me) who hate using the `dig` command.
62
63This was very useful for monitoring a couple of my transferred domains to see
64when the changes in nameservers, records, and DNSSEC went into effect.
65
66# Tunnel
67
68Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on
69Cloudflare's servers) and allowed me to mask the private IP address of the
70server hosting my various websites and apps.
71
72However, as I was moving away from Cloudflare, I was not able to find a suitable
73replacement that was both inexpensive and simple. So, I simply went back to
74hosting [my own reverse proxy with
75Nginx](file:///blog/set-up-nginx-reverse-proxy/). With the recent additions of
76Unifi hardware in my server/network rack, I am much more protected against spam
77and malicious attacks at the network edge than I was before I switched to
78Cloudflare.
79
80# Access
81
82Cloudflare Access, another app I used in combination with Cloudflare Tunnel,
83provided an authentication screen that required you to enter valid credentials
84before Cloudflare would forward you to the actual website or app (if the
85website/app has their own authentication, you'd then have to authenticate a
86second time).
87
88I did not replace this service with anything since I only host a handful of
89non-sensitive apps that don't require duplicate authentication.
content/blog/2022-06-01-ditching-cloudflare.org added +95
@@ -0,0 +1,95 @@
1#+date: <2022-06-01>
2#+title: Ditching Cloudflare for Njalla
3#+description:
4
5
6* Registrar
7
8After spending a year or so using Cloudflare for DNS only - no proxying
9or applications - I spent the last few months using Cloudflare Tunnels
10and Cloudflare Access to protect my self-hosted websites and
11applications via their proxy traffic model.
12
13However, I have never liked using Cloudflare due to their increasingly
14large share of control over web traffic, as well as their business model
15of being a MITM for all of your traffic.
16
17So, as of today, I have switched over to [[https://njal.la][Njalla]] as
18my registrar and DNS manager. I was able to easily transfer my domains
19over rapidly, with only one domain taking more than 15-30 minutes to
20propagate.
21
22+I do still have two domains sitting at Cloudflare for the moment while
23I decide if they're worth the higher rates (one domain is 30€ and the
24other is 45€).+
25
26#+begin_quote
27*Update (2022.06.03)*: I ended up transferring my final two domains over
28to Njalla, clearing my Cloudflare account of personal data, and deleting
29the Cloudflare account entirely. /I actually feel relieved to have moved
30on to a provider I trust./
31#+end_quote
32
33* DNS
34
35As noted above, I'm using Njalla exclusively for DNS configurations on
36my domains.
37
38However, the transfer process was not ideal. As soon as the domains
39transferred over, I switched the nameservers from Cloudflare to Njalla
40and lost most of the associated DNS records. So, the majority of the
41time spent during the migration was simply re-typing all the DNS records
42back in one-by-one.
43
44This would be much simpler if I were able to edit the plain-text format
45of the DNS configuration. I was able to do that at a past registrar
46(perhaps it was [[https://gandi.net/][Gandi.net]]?) and it made life a
47lot easier.
48
49** Dynamic DNS Updates
50
51I have built an easy Python script to run (or set-up in =cron= to run
52automatically) that will check my server's IPv4 and IPv6, compare it to
53Njalla, and update the DNS records if they don't match. You can see the
54full script and process in my other post: [[../njalla-dns-api/][Updating
55Dynamic DNS with Njalla API]].
56
57I haven't used this other method, but I do know that you can create
58=Dynamic= DNS records with Njalla that
59[[https://njal.la/docs/ddns/][work for updating dynamic subdomains]].
60
61** Njalla's DNS Tool
62
63One neat upside to Njalla is that they have a
64[[https://check.njal.la/dns/][DNS lookup tool]] that provides a lot of
65great information for those of you (AKA: me) who hate using the =dig=
66command.
67
68This was very useful for monitoring a couple of my transferred domains
69to see when the changes in nameservers, records, and DNSSEC went into
70effect.
71
72* Tunnel
73
74Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on
75Cloudflare's servers) and allowed me to mask the private IP address of
76the server hosting my various websites and apps.
77
78However, as I was moving away from Cloudflare, I was not able to find a
79suitable replacement that was both inexpensive and simple. So, I simply
80went back to hosting [[file:///blog/set-up-nginx-reverse-proxy/][my own
81reverse proxy with Nginx]]. With the recent additions of Unifi hardware
82in my server/network rack, I am much more protected against spam and
83malicious attacks at the network edge than I was before I switched to
84Cloudflare.
85
86* Access
87
88Cloudflare Access, another app I used in combination with Cloudflare
89Tunnel, provided an authentication screen that required you to enter
90valid credentials before Cloudflare would forward you to the actual
91website or app (if the website/app has their own authentication, you'd
92then have to authenticate a second time).
93
94I did not replace this service with anything since I only host a handful
95of non-sensitive apps that don't require duplicate authentication.
content/blog/2022-06-07-self-hosting-freshrss.md deleted −238
@@ -1,238 +0,0 @@
1+++
2date = 2022-06-07
3title = "Self-Hosting FreshRSS"
4description = ""
5draft = false
6+++
7
8# Why RSS?
9
10After noticing that I have collected 50+ blogs as bookmarks, I decided to
11migrate back to using RSS feeds to stay up-to-date with my favorite websites.
12Using RSS allows me to read all of these posts in a single app (on both mobile &
13desktop) and allows me to be notified when new posts are available.
14
15However, I ran into one issue: syncing subscriptions and read/unread posts
16across devices. Since I want to be able to easily read on both mobile and
17desktop, I decided to look for a self-hosted RSS solution.
18
19Thus, I found [FreshRSS](https://www.freshrss.org/) and was able to successfully
20install it on my server in about 30 minutes.
21
22# Documentation
23
24While it's certainly not robust, the [FreshRSS
25documentation](https://freshrss.github.io/FreshRSS/) is helpful for figuring out
26basic information about the service.
27
28However, I wanted to install this service as a Docker container and stumbled
29across the [Docker
30README](https://github.com/FreshRSS/FreshRSS/tree/edge/Docker) within the GitHub
31repository.
32
33This README was the documentation I actually needed. However, as you'll see
34below, I still had to manually edit one file (`config.php`) to access the API
35externally via my RSS apps.
36
37# Installation
38
39## DNS
40
41The first step, as required by any external web service, was assigning a domain
42name to use. I chose to use a subdomain, like `rss.example.com`.
43
44To assign this, I created an `A` record in my DNS settings with the IPv4 address
45of the server and an `AAAA` record with the IPv6 address of the server. Note:
46assigning an IPv6 (`AAAA`) record is optional, but I like to enable IPV6 for my
47services.
48
49```config
50rss.example.com A xxx.xxx.xxx.xxx
51rss.example.com AAAA xxxx:xxxx: ... :xxxx
52```
53
54## Docker
55
56I initially tried to set up a `docker-compose.yml` file with a `.env` file
57because I prefer to have a file I can look back at later to see how I initially
58started the container, but it simply wouldn't work for me. I'm not sure why, but
59I assume I wasn't telling `docker-compose` where the `.env` file was.
60
61Regardless, I chose to simply run the service with `docker run`. See the
62following command for my `docker run` configuration:
63
64```sh
65sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
66 -p 8080:80 \
67 -e TZ=America/Chicago \
68 -e 'CRON_MIN=1,31' \
69 -v freshrss_data:/var/www/FreshRSS/data \
70 -v freshrss_extensions:/var/www/FreshRSS/extensions \
71 --name freshrss \
72 freshrss/freshrss
73```
74
75This started the container successfully and allowed me to visit the FreshRSS
76instance at `localhost:8080`.
77
78## Fresh RSS Set-Up
79
80I **HIGHLY** suggest that you set up your user account prior to exposing this
81service to the public. It's unlikely that someone is trying to access the exact
82domain or IP/port you're assigning here, but as soon as you expose this service,
83the first person to open the URL will be able to create the admin user.
84
85In order to set up your FreshRSS service, open the `localhost:8080` URL in your
86browser (you may need to use a local IP instead of `localhost` if you're
87accessing the page from a different machine on the network - e.g.,
88`192.168.1.20:8080`).
89
90Once the page loads, set up your default user with a strong username and
91password. You may also choose to configure other settings prior to exposing this
92service.
93
94## Nginx Reverse-Proxy
95
96In order to access this service outside my home, I needed to set up a
97reverse-proxy to connect `localhost:8080` to `rss.example.com`.
98
99First, I created a new Nginx configuration file:
100
101```sh
102sudo nano /etc/nginx/sites-available/rss.example.com
103```
104
105Within the config file, I pasted the following code:
106
107```config
108upstream freshrss {
109 server 127.0.0.1:8080;
110 keepalive 64;
111}
112
113server {
114 server_name rss.example.com;
115 listen 80;
116
117 location / {
118 # The final `/` is important.
119 proxy_pass http://localhost:8080/;
120 add_header X-Frame-Options SAMEORIGIN;
121 add_header X-XSS-Protection "1; mode=block";
122 proxy_redirect off;
123 proxy_buffering off;
124 proxy_set_header Host $host;
125 proxy_set_header X-Real-IP $remote_addr;
126 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
127 proxy_set_header X-Forwarded-Proto $scheme;
128 proxy_set_header X-Forwarded-Port $server_port;
129 proxy_read_timeout 90;
130
131 # Forward the Authorization header for the Google Reader API.
132 proxy_set_header Authorization $http_authorization;
133 proxy_pass_header Authorization;
134 }
135}
136```
137
138Finally, restart Nginx and you will be able to access your service via HTTP:
139
140```sh
141sudo systemctl restart nginx.service
142```
143
144## HTTPS
145
146However, I don't want to access my RSS feeds via HTTP. I want it available only
147via HTTPS. In order to do this, I ran the [certbot](https://certbot.eff.org/)
148program to generate SSL certificates for me:
149
150```sh
151sudo certbot --nginx
152```
153
154This process will automatically generate an SSL certificate for you and modify
155the Nginx configuration file to include a redirect from HTTP to HTTPS.
156
157# Post-Installation Fixes
158
159At this point, we have a functional FreshRSS website, available from anywhere
160and secured with HTTPS. However, attempting to connect this service to an RSS
161app resulted in many errors regarding unavailable URLs and incorrect
162credentials.
163
164## API Set-Up
165
166First, you need to open your user profile in FreshRSS (`Settings` > `Profile`)
167and set an API password in the field at the bottom. This is the password you
168will need to provide to your RSS apps.
169
170Once that is set and saved, click the link below the API password field to open
171the API check tool. It should look something like `https://localhost:8080/api/`
172or `https://rss.example.com/api/`.
173
174Within this page, you _should_ see your correct external URL and "PASS" at the
175bottom of each API type. This would mean everything is set up correctly, and you
176can now move on and login to any RSS apps that support self-hosted options.
177
178In my case, the URL showed an internal URL and I had a warning that the
179`base_url` variable may be misconfigured. If this is the case, see the next
180section for a fix.
181
182## Base URL Fix
183
184In order to fix the `base_url` for the API, I opened up my docker container with
185the following command:
186
187```sh
188sudo docker exec -it freshrss bash
189```
190
191Within this container, update the packages and install an editor:
192
193```sh
194apt-get update
195apt-get install nano
196```
197
198Finally, open up `config.php` in the `data` directory:
199
200```sh
201nano data/config.php
202```
203
204Within `config.php`, you will need to update the `base_url` variable and update
205it to match your external URL. In my case, I simply commented-out the incorrect
206URL with `//` and added the correct one on a new line:
207
208```php
209<?php
210 return array (
211 ...
212 // 'base_url' => 'http://localhost:8080',
213 'base_url' => 'https://rss.example.com',
214 ...
215 )
216>
217```
218
219You can now exit the file with `Ctrl + x`, press `y` to save the file, and then
220click `Enter` to keep the same file name.
221
222Finally, just exit out of the docker container:
223
224```sh
225exit
226```
227
228Next, just restart the container:
229
230```sh
231sudo docker restart freshrss
232```
233
234Voilà! Your API check should now "PASS" and you should be able to use one of the
235API URLs in your RSS apps.
236
237In my case, I use [NetNewsWire](https://netnewswire.com) on my desktop and
238phone.
content/blog/2022-06-07-self-hosting-freshrss.org added +243
@@ -0,0 +1,243 @@
1#+date: <2022-06-07>
2#+title: Self-Hosting FreshRSS
3#+description:
4
5
6* Why RSS?
7
8After noticing that I have collected 50+ blogs as bookmarks, I decided
9to migrate back to using RSS feeds to stay up-to-date with my favorite
10websites. Using RSS allows me to read all of these posts in a single app
11(on both mobile & desktop) and allows me to be notified when new posts
12are available.
13
14However, I ran into one issue: syncing subscriptions and read/unread
15posts across devices. Since I want to be able to easily read on both
16mobile and desktop, I decided to look for a self-hosted RSS solution.
17
18Thus, I found [[https://www.freshrss.org/][FreshRSS]] and was able to
19successfully install it on my server in about 30 minutes.
20
21* Documentation
22
23While it's certainly not robust, the
24[[https://freshrss.github.io/FreshRSS/][FreshRSS documentation]] is
25helpful for figuring out basic information about the service.
26
27However, I wanted to install this service as a Docker container and
28stumbled across the
29[[https://github.com/FreshRSS/FreshRSS/tree/edge/Docker][Docker README]]
30within the GitHub repository.
31
32This README was the documentation I actually needed. However, as you'll
33see below, I still had to manually edit one file (=config.php=) to
34access the API externally via my RSS apps.
35
36* Installation
37
38** DNS
39
40The first step, as required by any external web service, was assigning a
41domain name to use. I chose to use a subdomain, like =rss.example.com=.
42
43To assign this, I created an =A= record in my DNS settings with the IPv4
44address of the server and an =AAAA= record with the IPv6 address of the
45server. Note: assigning an IPv6 (=AAAA=) record is optional, but I like
46to enable IPV6 for my services.
47
48#+begin_src config
49rss.example.com A xxx.xxx.xxx.xxx
50rss.example.com AAAA xxxx:xxxx: ... :xxxx
51#+end_src
52
53** Docker
54
55I initially tried to set up a =docker-compose.yml= file with a =.env=
56file because I prefer to have a file I can look back at later to see how
57I initially started the container, but it simply wouldn't work for me.
58I'm not sure why, but I assume I wasn't telling =docker-compose= where
59the =.env= file was.
60
61Regardless, I chose to simply run the service with =docker run=. See the
62following command for my =docker run= configuration:
63
64#+begin_src sh
65sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
66 -p 8080:80 \
67 -e TZ=America/Chicago \
68 -e 'CRON_MIN=1,31' \
69 -v freshrss_data:/var/www/FreshRSS/data \
70 -v freshrss_extensions:/var/www/FreshRSS/extensions \
71 --name freshrss \
72 freshrss/freshrss
73#+end_src
74
75This started the container successfully and allowed me to visit the
76FreshRSS instance at =localhost:8080=.
77
78** Fresh RSS Set-Up
79
80I *HIGHLY* suggest that you set up your user account prior to exposing
81this service to the public. It's unlikely that someone is trying to
82access the exact domain or IP/port you're assigning here, but as soon as
83you expose this service, the first person to open the URL will be able
84to create the admin user.
85
86In order to set up your FreshRSS service, open the =localhost:8080= URL
87in your browser (you may need to use a local IP instead of =localhost=
88if you're accessing the page from a different machine on the network -
89e.g., =192.168.1.20:8080=).
90
91Once the page loads, set up your default user with a strong username and
92password. You may also choose to configure other settings prior to
93exposing this service.
94
95** Nginx Reverse-Proxy
96
97In order to access this service outside my home, I needed to set up a
98reverse-proxy to connect =localhost:8080= to =rss.example.com=.
99
100First, I created a new Nginx configuration file:
101
102#+begin_src sh
103sudo nano /etc/nginx/sites-available/rss.example.com
104#+end_src
105
106Within the config file, I pasted the following code:
107
108#+begin_src config
109upstream freshrss {
110 server 127.0.0.1:8080;
111 keepalive 64;
112}
113
114server {
115 server_name rss.example.com;
116 listen 80;
117
118 location / {
119 # The final `/` is important.
120 proxy_pass http://localhost:8080/;
121 add_header X-Frame-Options SAMEORIGIN;
122 add_header X-XSS-Protection "1; mode=block";
123 proxy_redirect off;
124 proxy_buffering off;
125 proxy_set_header Host $host;
126 proxy_set_header X-Real-IP $remote_addr;
127 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
128 proxy_set_header X-Forwarded-Proto $scheme;
129 proxy_set_header X-Forwarded-Port $server_port;
130 proxy_read_timeout 90;
131
132 # Forward the Authorization header for the Google Reader API.
133 proxy_set_header Authorization $http_authorization;
134 proxy_pass_header Authorization;
135 }
136}
137#+end_src
138
139Finally, restart Nginx and you will be able to access your service via
140HTTP:
141
142#+begin_src sh
143sudo systemctl restart nginx.service
144#+end_src
145
146** HTTPS
147
148However, I don't want to access my RSS feeds via HTTP. I want it
149available only via HTTPS. In order to do this, I ran the
150[[https://certbot.eff.org/][certbot]] program to generate SSL
151certificates for me:
152
153#+begin_src sh
154sudo certbot --nginx
155#+end_src
156
157This process will automatically generate an SSL certificate for you and
158modify the Nginx configuration file to include a redirect from HTTP to
159HTTPS.
160
161* Post-Installation Fixes
162
163At this point, we have a functional FreshRSS website, available from
164anywhere and secured with HTTPS. However, attempting to connect this
165service to an RSS app resulted in many errors regarding unavailable URLs
166and incorrect credentials.
167
168** API Set-Up
169
170First, you need to open your user profile in FreshRSS (=Settings= >
171=Profile=) and set an API password in the field at the bottom. This is
172the password you will need to provide to your RSS apps.
173
174Once that is set and saved, click the link below the API password field
175to open the API check tool. It should look something like
176=https://localhost:8080/api/= or =https://rss.example.com/api/=.
177
178Within this page, you /should/ see your correct external URL and "PASS"
179at the bottom of each API type. This would mean everything is set up
180correctly, and you can now move on and login to any RSS apps that
181support self-hosted options.
182
183In my case, the URL showed an internal URL and I had a warning that the
184=base_url= variable may be misconfigured. If this is the case, see the
185next section for a fix.
186
187** Base URL Fix
188
189In order to fix the =base_url= for the API, I opened up my docker
190container with the following command:
191
192#+begin_src sh
193sudo docker exec -it freshrss bash
194#+end_src
195
196Within this container, update the packages and install an editor:
197
198#+begin_src sh
199apt-get update
200apt-get install nano
201#+end_src
202
203Finally, open up =config.php= in the =data= directory:
204
205#+begin_src sh
206nano data/config.php
207#+end_src
208
209Within =config.php=, you will need to update the =base_url= variable and
210update it to match your external URL. In my case, I simply commented-out
211the incorrect URL with =//= and added the correct one on a new line:
212
213#+begin_src php
214<?php
215 return array (
216 ...
217 // 'base_url' => 'http://localhost:8080',
218 'base_url' => 'https://rss.example.com',
219 ...
220 )
221>
222#+end_src
223
224You can now exit the file with =Ctrl + x=, press =y= to save the file,
225and then click =Enter= to keep the same file name.
226
227Finally, just exit out of the docker container:
228
229#+begin_src sh
230exit
231#+end_src
232
233Next, just restart the container:
234
235#+begin_src sh
236sudo docker restart freshrss
237#+end_src
238
239Voilà! Your API check should now "PASS" and you should be able to use
240one of the API URLs in your RSS apps.
241
242In my case, I use [[https://netnewswire.com][NetNewsWire]] on my desktop
243and phone.
content/blog/2022-06-16-terminal-lifestyle.md deleted −189
@@ -1,189 +0,0 @@
1+++
2date = 2022-06-16
3title = "A Terminal Lifestyle"
4description = ""
5draft = false
6+++
7
8# Text-Based Simplicity
9
10I've detailed my views on web-based minimalism and related topics in other posts
11throughout the years; e.g., JavaScript/CSS bloat slowing down websites that are
12essentially a text document. However, I have never really expanded beyond
13talking about the web and describing how I focus on minimizing distractions in
14other digital environments.
15
16This post is going to set the baseline for how I _try_ to live my digital life.
17It does not necessarily get into my physical life, which is often harder to
18control and contain all the noise in our modern world.
19
20While there are new things to do every day in our digital world, I find that
21keeping a core set of values and interests can ground you and keep you mindful
22of _why_ you are participating in the digital world. For example, if - at your
23core - you have no interest in what strangers think about random topics, it
24would be unwise to start participating in social media. However, I am someone
25who has been dragged in by effective advertising to participate in communities
26that I realize I do not care for.
27
28I won't dive much further into explaining the philosophy of all this, but I will
29link a few helpful articles that may pique your interest if you're in search of
30more meaningful experiences:
31
32- [Mindfulness](https://en.wikipedia.org/wiki/Mindfulness)
33- [Minimalism](https://en.wikipedia.org/wiki/Minimalism)
34- [Stoicism](https://en.wikipedia.org/wiki/Stoicism)
35
36# Living Life in the Terminal
37
38My personal approach to reducing digital distractions and increasing my focus on
39the task at hand is to use a terminal for as much as I possibly can.
40
41Most days, this means that I have a few tabs open constantly in my terminal:
42
431. A web browser
442. A chat client
453. An email client
464. An RSS feed reader
475. A local shell for navigating my computer's files
486. A remote shell for managing servers and other machines
49
50Beyond this, I rarely open other tabs or GUI applications, unless absolutely
51necessary. If you look, you may be surprised what can be accomplished in the
52terminal.
53
54For example, I have moved my music and entertainment downloads to the terminal,
55along with my device VPN connections. I am exploring options for moving my RSS
56subscriptions to something like [Newsboat](https://newsboat.org/), so that I can
57read my daily articles without all the fuss.
58
59Now that we have some examples out of the way, let's dive into the specifics.
60
61## Browsing the Web
62
63I'm going to start off with a hard topic for those who prefer to live in the
64terminal: web browsing. This task is made hard mostly by websites and web apps
65that require JavaScript to run. The other difficult part is that if you're using
66a text-based browser, that means images won't load (hopefully that's obvious).
67
68I am using [Lynx](https://lynx.invisible-island.net), a text-based browser that
69runs quickly and easily in the terminal. Lynx allows me to browser most websites
70by simply typing `g` and then typing in the URL I want.
71
72If you need a search engine while in Lynx, I recommend [DuckDuckGo
73(Lite)](https://lite.duckduckgo.com/lite/), which allows you to search the web
74using their text-only interface.
75
76Eventually, you will run into websites that don't work (or are just too ugly and
77messy) in a text-only mode, and you'll be forced to switch over to a GUI browser
78to look at that site. Personally, I don't mind this as it doesn't happen as
79often as I thought it would.
80
81The only time I need to do this is when I want to browse an image/video-focused
82webpage or if I need to log in to a site, and it doesn't support a text-only
83login page. For example, I am able to easily log in to
84[Sourcehut](https://sr.ht) in lynx.
85
86## Chatting with Friends
87
88After web browsing activities, my main form of terminal communication is Matrix.
89I use the [gomuks](https://docs.mau.fi/gomuks/) client currently.
90
91This was incredibly easy to install on macOS (but I will need to see if it'll be
92just as easy on Linux when my new laptop arrives):
93
94```sh
95brew install gomuks
96```
97
98Once you launch gomuks, it will sync and require your username and password to
99login. After doing so, the only problem I ran into was verifying my gomuks
100client so that I could participate in rooms with E2EE.
101
102Finally, I was able to verify the session by opening the Element desktop app (I
103assume you can do this in the browser and mobile app too, but I'm not sure) and
104manually verifying myself with this process:
105
1061. Open the Element desktop app
1072. Open a room I was a member of
1083. Open the `Room Info` pane
1094. Open the `People` menu and search for myself
1105. Click on my profile name
1116. Click on the session link under the `Security` section and follow the prompts
112 to manually verify the session
113
114Overall, I like gomuks and am able to enjoy all the features I was using in
115Element. The only hiccup I have occurred is manually downloading images to view
116them, which can be annoying.
117
118## Email
119
120Moving email to the terminal has been the hardest of the tasks for me. Unlike
121web browsing, where I can simply decide to not look at a website that does not
122work in the terminal, I cannot simply ignore emails sent to me.
123
124Personally, I am experimenting with [neomutt](https://neomutt.org/) as a
125potential email client.
126
127However, this requires a **TON** of configuration and tweaking to get right.
128Even when I was able to set up neomutt, configure my email account, and
129customize a few personal preferences, a lot of emails still do not display
130correctly (mostly due to HTML and images).
131
132I won't get into the details of configuring `neomutt`; I mostly followed this
133blog post: [Email in the Terminal: Configuring
134Neomutt](https://gideonwolfe.com/posts/workflow/neomutt/intro/).
135
136Finally, I have yet to figure out how to connect my GPG keys to `neomutt`, but
137that's a problem for another day.
138
139## RSS Feed Reader
140
141I have just started using [Newsboat](https://newsboat.org/) to read articles in
142my terminal and have found quick success with it.
143
144The configuration was super easy for this app; I simply installed the app,
145created a file for URLs, and imported my OPML subscriptions that I had exported
146out of my old feed reader:
147
148```sh
149brew install newsboat
150```
151
152```sh
153touch ~/.newsboat/urls
154```
155
156```sh
157newsboat -i=my_subscriptions.opml
158```
159
160## Writing & Programming
161
162Unfortunately, the weak link in my terminal-based environment right now is my
163grasp of the possibilities of editing files within a shell.
164
165I am used to the easy extensions found in VSCodium and Kate, so I am slowly
166learning how to mold the default editing tools to my needs. Currently, this
167means I am using `nano` with the following configuration:
168
169```config
170set breaklonglines
171set autoindent
172set linenumbers
173set tabstospaces
174set tabsize 2
175set fill 80
176```
177
178This configuration allows nano to automatically hard-wrap lines at 80
179characters, autoindent the wrapped lines (if the previous line was indented),
180use 2 spaces per tab, and display line numbers within each file I open.
181
182I am currently looking to see if `vim` or `emacs` would be more useful for my
183current needs, but I'm not in any rush, so I don't expect to find an answer
184anytime soon.
185
186With my current life demands, I am not programming at the moment and have not
187explored the best terminal set-up for programming. However, I have seen many
188peers find success configuring `vim` and `emacs`, so that's where I will start
189when I pick my projects back up.
content/blog/2022-06-16-terminal-lifestyle.org added +201
@@ -0,0 +1,201 @@
1#+date: <2022-06-16>
2#+title: A Terminal Lifestyle
3#+description:
4
5
6* Text-Based Simplicity
7
8I've detailed my views on web-based minimalism and related topics in
9other posts throughout the years; e.g., JavaScript/CSS bloat slowing
10down websites that are essentially a text document. However, I have
11never really expanded beyond talking about the web and describing how I
12focus on minimizing distractions in other digital environments.
13
14This post is going to set the baseline for how I /try/ to live my
15digital life. It does not necessarily get into my physical life, which
16is often harder to control and contain all the noise in our modern
17world.
18
19While there are new things to do every day in our digital world, I find
20that keeping a core set of values and interests can ground you and keep
21you mindful of /why/ you are participating in the digital world. For
22example, if - at your core - you have no interest in what strangers
23think about random topics, it would be unwise to start participating in
24social media. However, I am someone who has been dragged in by effective
25advertising to participate in communities that I realize I do not care
26for.
27
28I won't dive much further into explaining the philosophy of all this,
29but I will link a few helpful articles that may pique your interest if
30you're in search of more meaningful experiences:
31
32- [[https://en.wikipedia.org/wiki/Mindfulness][Mindfulness]]
33- [[https://en.wikipedia.org/wiki/Minimalism][Minimalism]]
34- [[https://en.wikipedia.org/wiki/Stoicism][Stoicism]]
35
36* Living Life in the Terminal
37
38My personal approach to reducing digital distractions and increasing my
39focus on the task at hand is to use a terminal for as much as I possibly
40can.
41
42Most days, this means that I have a few tabs open constantly in my
43terminal:
44
451. A web browser
462. A chat client
473. An email client
484. An RSS feed reader
495. A local shell for navigating my computer's files
506. A remote shell for managing servers and other machines
51
52Beyond this, I rarely open other tabs or GUI applications, unless
53absolutely necessary. If you look, you may be surprised what can be
54accomplished in the terminal.
55
56For example, I have moved my music and entertainment downloads to the
57terminal, along with my device VPN connections. I am exploring options
58for moving my RSS subscriptions to something like
59[[https://newsboat.org/][Newsboat]], so that I can read my daily
60articles without all the fuss.
61
62Now that we have some examples out of the way, let's dive into the
63specifics.
64
65** Browsing the Web
66
67I'm going to start off with a hard topic for those who prefer to live in
68the terminal: web browsing. This task is made hard mostly by websites
69and web apps that require JavaScript to run. The other difficult part is
70that if you're using a text-based browser, that means images won't load
71(hopefully that's obvious).
72
73I am using [[https://lynx.invisible-island.net][Lynx]], a text-based
74browser that runs quickly and easily in the terminal. Lynx allows me to
75browser most websites by simply typing =g= and then typing in the URL I
76want.
77
78If you need a search engine while in Lynx, I recommend
79[[https://lite.duckduckgo.com/lite/][DuckDuckGo (Lite)]], which allows
80you to search the web using their text-only interface.
81
82Eventually, you will run into websites that don't work (or are just too
83ugly and messy) in a text-only mode, and you'll be forced to switch over
84to a GUI browser to look at that site. Personally, I don't mind this as
85it doesn't happen as often as I thought it would.
86
87The only time I need to do this is when I want to browse an
88image/video-focused webpage or if I need to log in to a site, and it
89doesn't support a text-only login page. For example, I am able to easily
90log in to [[https://sr.ht][Sourcehut]] in lynx.
91
92** Chatting with Friends
93
94After web browsing activities, my main form of terminal communication is
95Matrix. I use the [[https://docs.mau.fi/gomuks/][gomuks]] client
96currently.
97
98This was incredibly easy to install on macOS (but I will need to see if
99it'll be just as easy on Linux when my new laptop arrives):
100
101#+begin_src sh
102brew install gomuks
103#+end_src
104
105Once you launch gomuks, it will sync and require your username and
106password to login. After doing so, the only problem I ran into was
107verifying my gomuks client so that I could participate in rooms with
108E2EE.
109
110Finally, I was able to verify the session by opening the Element desktop
111app (I assume you can do this in the browser and mobile app too, but I'm
112not sure) and manually verifying myself with this process:
113
1141. Open the Element desktop app
1152. Open a room I was a member of
1163. Open the =Room Info= pane
1174. Open the =People= menu and search for myself
1185. Click on my profile name
1196. Click on the session link under the =Security= section and follow the
120 prompts to manually verify the session
121
122Overall, I like gomuks and am able to enjoy all the features I was using
123in Element. The only hiccup I have occurred is manually downloading
124images to view them, which can be annoying.
125
126** Email
127
128Moving email to the terminal has been the hardest of the tasks for me.
129Unlike web browsing, where I can simply decide to not look at a website
130that does not work in the terminal, I cannot simply ignore emails sent
131to me.
132
133Personally, I am experimenting with [[https://neomutt.org/][neomutt]] as
134a potential email client.
135
136However, this requires a *TON* of configuration and tweaking to get
137right. Even when I was able to set up neomutt, configure my email
138account, and customize a few personal preferences, a lot of emails still
139do not display correctly (mostly due to HTML and images).
140
141I won't get into the details of configuring =neomutt=; I mostly followed
142this blog post:
143[[https://gideonwolfe.com/posts/workflow/neomutt/intro/][Email in the
144Terminal: Configuring Neomutt]].
145
146Finally, I have yet to figure out how to connect my GPG keys to
147=neomutt=, but that's a problem for another day.
148
149** RSS Feed Reader
150
151I have just started using [[https://newsboat.org/][Newsboat]] to read
152articles in my terminal and have found quick success with it.
153
154The configuration was super easy for this app; I simply installed the
155app, created a file for URLs, and imported my OPML subscriptions that I
156had exported out of my old feed reader:
157
158#+begin_src sh
159brew install newsboat
160#+end_src
161
162#+begin_src sh
163touch ~/.newsboat/urls
164#+end_src
165
166#+begin_src sh
167newsboat -i=my_subscriptions.opml
168#+end_src
169
170** Writing & Programming
171
172Unfortunately, the weak link in my terminal-based environment right now
173is my grasp of the possibilities of editing files within a shell.
174
175I am used to the easy extensions found in VSCodium and Kate, so I am
176slowly learning how to mold the default editing tools to my needs.
177Currently, this means I am using =nano= with the following
178configuration:
179
180#+begin_src config
181set breaklonglines
182set autoindent
183set linenumbers
184set tabstospaces
185set tabsize 2
186set fill 80
187#+end_src
188
189This configuration allows nano to automatically hard-wrap lines at 80
190characters, autoindent the wrapped lines (if the previous line was
191indented), use 2 spaces per tab, and display line numbers within each
192file I open.
193
194I am currently looking to see if =vim= or =emacs= would be more useful
195for my current needs, but I'm not in any rush, so I don't expect to find
196an answer anytime soon.
197
198With my current life demands, I am not programming at the moment and
199have not explored the best terminal set-up for programming. However, I
200have seen many peers find success configuring =vim= and =emacs=, so
201that's where I will start when I pick my projects back up.
content/blog/2022-06-22-daily-poetry.md deleted −213
@@ -1,213 +0,0 @@
1+++
2date = 2022-06-22
3title = "Daily Plaintext Poetry via Email"
4description = ""
5draft = false
6+++
7
8# Source Code
9
10I don't want to bury the lede here, so if you'd like to see the full source code
11I use to email myself plaintext poems daily, visit the repository:
12[daily-poem.git](https://git.sr.ht/~cyborg/daily-poem).
13
14# My Daily Dose of Poetry
15
16Most of my programming projects are small, random projects that are made
17strictly to fix some small problem I have or enhance my quality of life.
18
19In this case, I was looking for a simply and easy way to get a daily dose of
20literature or poetry to read in the mornings.
21
22However, I don't want to sign up for a random mailing list on just any website.
23I also don't want to have to work to find the reading content each morning, as I
24know I would simply give up and stop reading daily.
25
26Thus, I found a way to deliver poetry to myself in plain-text format, on a daily
27basis, and scheduled to deliver automatically.
28
29# Prerequisites
30
31This solution uses Python and email, so the following process requires the
32following to be installed:
33
341. An SMTP server, which can be as easy as installing `mailutils` if you're on a
35 Debian-based distro.
362. Python (& pip!)
373. The following Python packages: `email`, `smtplib`, `json`, and `requests`
38
39# Breaking Down the Logic
40
41I want to break down the logic for this program, as it's quite simple and
42informational.
43
44## Required Packages
45
46This program starts with a simple import of the required packages, so I wanted
47to explain why each package is used:
48
49```python
50from email.mime.text import MIMEText # Required for translating MIMEText
51import smtplib # Required to process the SMTP mail delivery
52import json # Required to parse the poetry API results
53import requests # Required to send out a request to the API
54```
55
56## Sending the API Request
57
58Next, we need to actually send the API request. In my case, I'm calling a random
59poem from the entire API. If you want, you can call specific poems or authors
60from this API.
61
62```python
63json_data = requests.get('https://poetrydb.org/random').json()
64```
65
66This gives us the following result in JSON:
67
68```json
69[
70 {
71 "title": "Sonnet XXII: With Fools and Children",
72 "author": "Michael Drayton",
73 "lines": [
74 "To Folly",
75 "",
76 "With fools and children, good discretion bears;",
77 "Then, honest people, bear with Love and me,",
78 "Nor older yet, nor wiser made by years,",
79 "Amongst the rest of fools and children be;",
80 "Love, still a baby, plays with gauds and toys,",
81 "And, like a wanton, sports with every feather,",
82 "And idiots still are running after boys,",
83 "Then fools and children fitt'st to go together.",
84 "He still as young as when he first was born,",
85 "No wiser I than when as young as he;",
86 "You that behold us, laugh us not to scorn;",
87 "Give Nature thanks you are not such as we.",
88 "Yet fools and children sometimes tell in play",
89 "Some, wise in show, more fools indeed than they."
90 ],
91 "linecount": "15"
92 }
93]
94```
95
96## Parsing the API Results
97
98In order to parse this into a readable format, we need to use the `json` package
99and extract the fields we want. In the example below, I am grabbing every field
100presented by the API.
101
102For the actual poem content, we need to loop over each line in the `lines`
103variable since each line is a separate string by default.
104
105> You _could_ also extract the title or author and make another call out to the
106> API to avoid having to build the plaintext poem with a loop, but it just
107> doesn't make sense to me to send multiple requests when we can create a simple
108> loop on our local machine to work with the data we already have.
109>
110> For
111> [example](https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text),
112> look at the raw data response of this link to see the poem's lines returned in
113> plaintext.
114
115```python
116title = json_data[0]['title']
117author = json_data[0]['author']
118line_count = json_data[0]['linecount']
119lines = ''
120for line in json_data[0]['lines']:
121 lines = lines + line + "\n"
122```
123
124## Composing the Email
125
126Now that I have all the data I need, I just need to compose it into a message
127and prepare the message metadata.
128
129For my daily email, I want to see the title of the poem first, followed by the
130author, then a blank line, and finally the full poem. This code snippet combines
131that data and packages it into a MIMEText container, ready to be emailed.
132
133```python
134msg_body = title + "\n" + author + "\n\n" + lines
135msg = MIMEText(msg_body)
136```
137
138Before we send the email, we need to prepare the metadata (subject, from, to,
139etc.):
140
141```python
142sender_email = 'example@server.local'
143recipient_emails = ['user@example.com']
144msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
145msg['From'] = sender_email
146msg['To'] = recipient_email
147```
148
149## Sending the Email
150
151Now that I have everything ready to be emailed, the last step is to simply
152connect to an SMTP server and send the email out to the recipients. In my case,
153I installed `mailutils` on Ubuntu and let my SMTP server be `localhost`.
154
155```python
156smtp_server = 'localhost'
157s = smtplib.SMTP(smtp_server)
158s.sendmail(sender_email, recipient_emails, msg.as_string())
159s.quit()
160```
161
162# The Result!
163
164Instead of including a screenshot, I've copied the contents of the email that
165was delivered to my inbox below since I set this process up in plaintext format.
166
167```txt
168Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
169From: REDACTED
170To: REDACTED
171Subject: Your Daily Poem (36 lines)
172MIME-Version: 1.0
173Content-Transfer-Encoding: 8bit
174Content-Type: text/plain; charset=utf-8
175
176Sonnet XXII: With Fools and Children
177Michael Drayton
178
179With fools and children, good discretion bears;
180Then, honest people, bear with Love and me,
181Nor older yet, nor wiser made by years,
182Amongst the rest of fools and children be;
183Love, still a baby, plays with gauds and toys,
184And, like a wanton, sports with every feather,
185And idiots still are running after boys,
186Then fools and children fitt'st to go together.
187He still as young as when he first was born,
188No wiser I than when as young as he;
189You that behold us, laugh us not to scorn;
190Give Nature thanks you are not such as we.
191Yet fools and children sometimes tell in play
192Some, wise in show, more fools indeed than they.
193```
194
195# Scheduling the Daily Email
196
197Last, but not least, is scheduling this Python script with `crontab`. To
198schedule a script to run daily, you can add it to the `crontab` file. To do
199this, open `crontab` in editing mode:
200
201```sh
202crontab -e
203```
204
205In the file, simply paste the following snippet at the bottom of the file and
206ensure that the file path is correctly pointing to wherever you saved your
207Python script:
208
209```config
2100 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py
211```
212
213We have now set up the script and scheduled it to run daily at 08:00!
content/blog/2022-06-22-daily-poetry.org added +218
@@ -0,0 +1,218 @@
1#+date: <2022-06-22>
2#+title: Daily Plaintext Poetry via Email
3#+description:
4
5
6* Source Code
7
8I don't want to bury the lede here, so if you'd like to see the full
9source code I use to email myself plaintext poems daily, visit the
10repository: [[https://git.sr.ht/~cyborg/daily-poem][daily-poem.git]].
11
12* My Daily Dose of Poetry
13
14Most of my programming projects are small, random projects that are made
15strictly to fix some small problem I have or enhance my quality of life.
16
17In this case, I was looking for a simply and easy way to get a daily
18dose of literature or poetry to read in the mornings.
19
20However, I don't want to sign up for a random mailing list on just any
21website. I also don't want to have to work to find the reading content
22each morning, as I know I would simply give up and stop reading daily.
23
24Thus, I found a way to deliver poetry to myself in plain-text format, on
25a daily basis, and scheduled to deliver automatically.
26
27* Prerequisites
28
29This solution uses Python and email, so the following process requires
30the following to be installed:
31
321. An SMTP server, which can be as easy as installing =mailutils= if
33 you're on a Debian-based distro.
342. Python (& pip!)
353. The following Python packages: =email=, =smtplib=, =json=, and
36 =requests=
37
38* Breaking Down the Logic
39
40I want to break down the logic for this program, as it's quite simple
41and informational.
42
43** Required Packages
44
45This program starts with a simple import of the required packages, so I
46wanted to explain why each package is used:
47
48#+begin_src python
49from email.mime.text import MIMEText # Required for translating MIMEText
50import smtplib # Required to process the SMTP mail delivery
51import json # Required to parse the poetry API results
52import requests # Required to send out a request to the API
53#+end_src
54
55** Sending the API Request
56
57Next, we need to actually send the API request. In my case, I'm calling
58a random poem from the entire API. If you want, you can call specific
59poems or authors from this API.
60
61#+begin_src python
62json_data = requests.get('https://poetrydb.org/random').json()
63#+end_src
64
65This gives us the following result in JSON:
66
67#+begin_src json
68[
69 {
70 "title": "Sonnet XXII: With Fools and Children",
71 "author": "Michael Drayton",
72 "lines": [
73 "To Folly",
74 "",
75 "With fools and children, good discretion bears;",
76 "Then, honest people, bear with Love and me,",
77 "Nor older yet, nor wiser made by years,",
78 "Amongst the rest of fools and children be;",
79 "Love, still a baby, plays with gauds and toys,",
80 "And, like a wanton, sports with every feather,",
81 "And idiots still are running after boys,",
82 "Then fools and children fitt'st to go together.",
83 "He still as young as when he first was born,",
84 "No wiser I than when as young as he;",
85 "You that behold us, laugh us not to scorn;",
86 "Give Nature thanks you are not such as we.",
87 "Yet fools and children sometimes tell in play",
88 "Some, wise in show, more fools indeed than they."
89 ],
90 "linecount": "15"
91 }
92]
93#+end_src
94
95** Parsing the API Results
96
97In order to parse this into a readable format, we need to use the =json=
98package and extract the fields we want. In the example below, I am
99grabbing every field presented by the API.
100
101For the actual poem content, we need to loop over each line in the
102=lines= variable since each line is a separate string by default.
103
104#+begin_quote
105You /could/ also extract the title or author and make another call out
106to the API to avoid having to build the plaintext poem with a loop, but
107it just doesn't make sense to me to send multiple requests when we can
108create a simple loop on our local machine to work with the data we
109already have.
110
111For
112[[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]],
113look at the raw data response of this link to see the poem's lines
114returned in plaintext.
115#+end_quote
116
117#+begin_src python
118title = json_data[0]['title']
119author = json_data[0]['author']
120line_count = json_data[0]['linecount']
121lines = ''
122for line in json_data[0]['lines']:
123 lines = lines + line + "\n"
124#+end_src
125
126** Composing the Email
127
128Now that I have all the data I need, I just need to compose it into a
129message and prepare the message metadata.
130
131For my daily email, I want to see the title of the poem first, followed
132by the author, then a blank line, and finally the full poem. This code
133snippet combines that data and packages it into a MIMEText container,
134ready to be emailed.
135
136#+begin_src python
137msg_body = title + "\n" + author + "\n\n" + lines
138msg = MIMEText(msg_body)
139#+end_src
140
141Before we send the email, we need to prepare the metadata (subject,
142from, to, etc.):
143
144#+begin_src python
145sender_email = 'example@server.local'
146recipient_emails = ['user@example.com']
147msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
148msg['From'] = sender_email
149msg['To'] = recipient_email
150#+end_src
151
152** Sending the Email
153
154Now 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
156recipients. In my case, I installed =mailutils= on Ubuntu and let my
157SMTP server be =localhost=.
158
159#+begin_src python
160smtp_server = 'localhost'
161s = smtplib.SMTP(smtp_server)
162s.sendmail(sender_email, recipient_emails, msg.as_string())
163s.quit()
164#+end_src
165
166* The Result!
167
168Instead of including a screenshot, I've copied the contents of the email
169that was delivered to my inbox below since I set this process up in
170plaintext format.
171
172#+begin_src txt
173Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
174From: REDACTED
175To: REDACTED
176Subject: Your Daily Poem (36 lines)
177MIME-Version: 1.0
178Content-Transfer-Encoding: 8bit
179Content-Type: text/plain; charset=utf-8
180
181Sonnet XXII: With Fools and Children
182Michael Drayton
183
184With fools and children, good discretion bears;
185Then, honest people, bear with Love and me,
186Nor older yet, nor wiser made by years,
187Amongst the rest of fools and children be;
188Love, still a baby, plays with gauds and toys,
189And, like a wanton, sports with every feather,
190And idiots still are running after boys,
191Then fools and children fitt'st to go together.
192He still as young as when he first was born,
193No wiser I than when as young as he;
194You that behold us, laugh us not to scorn;
195Give Nature thanks you are not such as we.
196Yet fools and children sometimes tell in play
197Some, wise in show, more fools indeed than they.
198#+end_src
199
200* Scheduling the Daily Email
201
202Last, but not least, is scheduling this Python script with =crontab=. To
203schedule a script to run daily, you can add it to the =crontab= file. To
204do this, open =crontab= in editing mode:
205
206#+begin_src sh
207crontab -e
208#+end_src
209
210In the file, simply paste the following snippet at the bottom of the
211file and ensure that the file path is correctly pointing to wherever you
212saved your Python script:
213
214#+begin_src config
2150 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py
216#+end_src
217
218We have now set up the script and scheduled it to run daily at 08:00!
content/blog/2022-06-24-fedora-i3.md deleted −153
@@ -1,153 +0,0 @@
1+++
2date = 2022-06-24
3title = "Rebooting My Love Affair with Linux"
4description = ""
5draft = false
6+++
7
8# Leaving macOS
9
10As I noted [in a recent post](../foss-macos-apps), I have been planning on
11migrating from macOS back to a Linux-based OS. I am happy to say that I have
12finally completed my migration and am now stuck in the wonderful world of Linux
13again.
14
15My decision to leave macOS really came down to just a few important things:
16
17- Apple Security (Gatekeeper) restricting me from running any software I want.
18 Even if you disable Gatekeeper and allow software to bypass the rest of the
19 device installation security, you still have to repeat that process every
20 time the allowed software is updated.
21- macOS sends out nearly constant connections, pings, telemetry, etc. to a
22 myriad of mysterious Apple services. I'm not even going to dive into how
23 many macOS apps have constant telemetry on, as well.
24- Lastly, I just _really_ missed the customization and freedom that comes with
25 Linux. Being able to switch to entirely new kernel, OS, or desktop within
26 minutes is a freedom I took for granted when I switched to macOS.
27
28Now that I've covered macOS, I'm going to move on to more exciting topics: my
29personal choice of OS, DE, and various customizations I'm using.
30
31# Fedora
32
33After trying a ton of distros (I think I booted and tested around 20-25
34distros), I finally landed on [Fedora Linux](https://getfedora.org/). I have
35quite a bit of experience with Fedora and enjoy the `dnf` package manager.
36Fedora allows me to keep up-to-date with recent software (I'm looking at you,
37Debian), but still provides a level of stability you don't find in every distro.
38
39In a very close second place was Arch Linux, as well as its spin-off: Garuda
40Linux (Garuda w/ sway is _beautiful_). Arch is great for compatibility and the
41massive community it has, but I have just never had the time to properly sit
42down and learn the methodology behind their packaging systems.
43
44Basically, everything else I tested was unacceptable in at least one way or
45another. Void (`glibc`) was great, but doesn't support all the software I need.
46Slackware worked well as a tui, but I wasn't skilled enough to get a tiling
47window manager (WM) working on it.
48
49## i3
50
51One of the reasons I settled on Fedora is that it comes with an official i3
52spin. Being able to use a tiling WM, such as i3 or sway, is one of the biggest
53things 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 any of my
56configurations, but nothing big has been configured thus far.
57
58The two main things I have updated in i3wm are natural scrolling and binding my
59brightness keys to the `brightnessctl` program.
60
611. Natural Scrolling
62
63 You can enable natural scrolling by opening the following file:
64
65 ```sh
66 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
67 ```
68
69 Within the `40-libinput.conf` file, find the following input sections and
70 enable the natural scrolling option.
71
72 This is the `pointer` section:
73
74 ```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 ```
83
84 This is the `touchpad` section:
85
86 ```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 ```
95
962. Enabling Brightness Keys
97
98 Likewise, enabling brightness key functionality is as simple as binding the
99 keys to the `brightnessctl` program.
100
101 To do this, open up your i3 config file. Mine is located here:
102
103 ```sh
104 nano /home/<my-user>/.config/i3/config
105 ```
106
107 ```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 ```
112
1133. `polybar`
114
115 Instead of using the default `i3status` bar, I have opted to use `polybar`
116 instead (as you can also see in the screenshot above).
117
118 My config for this menu bar is basically just the default settings with
119 modified colors and an added battery block to quickly show me the machine's
120 battery info.
121
1224. `alacritty`
123
124 Not much to say on this part yet, as I haven't configured it much, but I
125 installed `alacritty` as my default terminal, and I am using `zsh` and the
126 shell.
127
128# Software Choices
129
130Again, I'm not going to say much that I haven't said yet in other blog posts, so
131I'll just do a quick rundown of the apps I installed immediately after I set up
132the environment.
133
134Flatpak Apps:
135
136- Cryptomator
137- pCloud
138- Signal
139
140Fedora Packages:
141
142- gomuks
143- neomutt
144- neofetch
145- Firefox
146 - uBlock Origin
147 - Bitwarden
148 - Stylus
149 - Privacy Redirect
150
151Other:
152
153- exiftool
content/blog/2022-06-24-fedora-i3.org added +156
@@ -0,0 +1,156 @@
1#+date: <2022-06-24>
2#+title: Rebooting My Love Affair with Linux
3#+description:
4
5
6* Leaving macOS
7
8As I noted [[../foss-macos-apps][in a recent post]], I have been
9planning on migrating from macOS back to a Linux-based OS. I am happy to
10say that I have finally completed my migration and am now stuck in the
11wonderful world of Linux again.
12
13My decision to leave macOS really came down to just a few important
14things:
15
16- Apple Security (Gatekeeper) restricting me from running any software I
17 want. Even if you disable Gatekeeper and allow software to bypass the
18 rest of the device installation security, you still have to repeat
19 that process every time the allowed software is updated.
20- macOS sends out nearly constant connections, pings, telemetry, etc. to
21 a myriad of mysterious Apple services. I'm not even going to dive into
22 how many macOS apps have constant telemetry on, as well.
23- Lastly, I just /really/ missed the customization and freedom that
24 comes with Linux. Being able to switch to entirely new kernel, OS, or
25 desktop within minutes is a freedom I took for granted when I switched
26 to macOS.
27
28Now that I've covered macOS, I'm going to move on to more exciting
29topics: my personal choice of OS, DE, and various customizations I'm
30using.
31
32* Fedora
33
34After trying a ton of distros (I think I booted and tested around 20-25
35distros), I finally landed on [[https://getfedora.org/][Fedora Linux]].
36I have quite a bit of experience with Fedora and enjoy the =dnf= package
37manager. Fedora allows me to keep up-to-date with recent software (I'm
38looking at you, Debian), but still provides a level of stability you
39don't find in every distro.
40
41In a very close second place was Arch Linux, as well as its spin-off:
42Garuda Linux (Garuda w/ sway is /beautiful/). Arch is great for
43compatibility and the massive community it has, but I have just never
44had the time to properly sit down and learn the methodology behind their
45packaging systems.
46
47Basically, everything else I tested was unacceptable in at least one way
48or another. Void (=glibc=) was great, but doesn't support all the
49software I need. Slackware worked well as a tui, but I wasn't skilled
50enough to get a tiling window manager (WM) working on it.
51
52** i3
53
54One of the reasons I settled on Fedora is that it comes with an official
55i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
56the biggest things I wanted to do as soon as I adopted Linux again.
57
58I will probably set up a dotfile repository soon, so that I don't lose
59any of my configurations, but nothing big has been configured thus far.
60
61The two main things I have updated in i3wm are natural scrolling and
62binding my brightness keys to the =brightnessctl= program.
63
641. Natural Scrolling
65
66 You can enable natural scrolling by opening the following file:
67
68 #+begin_src sh
69 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
70 #+end_src
71
72 Within the =40-libinput.conf= file, find the following input sections
73 and enable the natural scrolling option.
74
75 This is the =pointer= section:
76
77 #+begin_src conf
78 Section "InputClass"
79 Identifier "libinput pointer catchall"
80 MatchIsPointer "on"
81 MatchDevicePath "/dev/input/event*"
82 Driver "libinput"
83 Option "NaturalScrolling" "True"
84 EndSection
85 #+end_src
86
87 This is the =touchpad= section:
88
89 #+begin_src conf
90 Section "InputClass"
91 Identifier "libinput touchpad catchall"
92 MatchIsTouchpad "on"
93 MatchDevicePath "/dev/input/event*"
94 Driver "libinput"
95 Option "NaturalScrolling" "True"
96 EndSection
97 #+end_src
98
992. Enabling Brightness Keys
100
101 Likewise, enabling brightness key functionality is as simple as
102 binding the keys to the =brightnessctl= program.
103
104 To do this, open up your i3 config file. Mine is located here:
105
106 #+begin_src sh
107 nano /home/<my-user>/.config/i3/config
108 #+end_src
109
110 #+begin_src conf
111 # Use brightnessctl to adjust brightness.
112 bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
113 bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
114 #+end_src
115
1163. =polybar=
117
118 Instead of using the default =i3status= bar, I have opted to use
119 =polybar= instead (as you can also see in the screenshot above).
120
121 My config for this menu bar is basically just the default settings
122 with modified colors and an added battery block to quickly show me
123 the machine's battery info.
124
1254. =alacritty=
126
127 Not much to say on this part yet, as I haven't configured it much,
128 but I installed =alacritty= as my default terminal, and I am using
129 =zsh= and the shell.
130
131* Software Choices
132
133Again, I'm not going to say much that I haven't said yet in other blog
134posts, so I'll just do a quick rundown of the apps I installed
135immediately after I set up the environment.
136
137Flatpak Apps:
138
139- Cryptomator
140- pCloud
141- Signal
142
143Fedora Packages:
144
145- gomuks
146- neomutt
147- neofetch
148- Firefox
149 - uBlock Origin
150 - Bitwarden
151 - Stylus
152 - Privacy Redirect
153
154Other:
155
156- exiftool
content/blog/2022-07-01-git-server.md deleted −624
@@ -1,624 +0,0 @@
1+++
2date = 2022-07-01
3title = "Self-Hosting a Personal Git Server"
4description = ""
5draft = false
6+++
7
8# My Approach to Self-Hosting Git
9
10I have often tried to self-host my Git repositories, but have always fallen
11short when I tried to find a suitable web interface to show on the front-end.
12
13After a few years, I have finally found a combination of methods that allow me
14to easily self-host my projects, view them on the web, and access them from
15anywhere.
16
17Before I dive into the details, I want to state a high-level summary of my
18self-hosted Git approach:
19
20- This method uses the `ssh://` (read & write) and `git://` (read-only)
21 protocols for push and pull access.
22 - For the `git://` protocol, I create a `git-daemon-export-ok` file in any
23 repository that I want to be cloneable by anyone.
24 - The web interface I am using (`cgit`) allows simple HTTP cloning by
25 default. I do not disable this setting as I want beginners to be able to
26 clone one of my repositories even if they don't know the proper method.
27- I am not enabling Smart HTTPS for any repositories. Updates to repositories
28 must be pushed via SSH.
29- Beyond the actual repository management, I am using `cgit` for the front-end
30 web interface.
31 - If you use the `scan-path=<path>` configuration in the `cgitrc`
32 configuration file to automatically find repositories, you can't exclude
33 a repository from `cgit` if it's stored within the path that `cgit`
34 reads. To host private repositories, you'd need to set up another
35 directory that `cgit` can't read.
36
37# Assumptions
38
39For the purposes of this walkthrough, I am assuming you have a URL
40(`git.example.com`) or IP address (`207.84.26.991`) addressed to the server that
41you will be using to host your git repositories.
42
43# Adding a Git User
44
45In order to use the SSH method associated with git, we will need to add a user
46named `git`. If you have used the SSH method for other git hosting sites, you
47are probably used to the following syntax:
48
49```sh
50git clone [user@]server:project.git
51```
52
53The syntax above is an `scp`-like syntax for using SSH on the `git` user on the
54server to access your repository.
55
56Let's delete any remnants of an old `git` user, if any, and create the new user
57account:
58
59```sh
60sudo deluser --remove-home git
61sudo adduser git
62```
63
64## Import Your SSH Keys to the Git User
65
66Once the `git` user is created, you will need to copy your public SSH key on
67your local development machine to the `git` user on the server.
68
69If you don't have an SSH key yet, create one with this command:
70
71```sh
72ssh-keygen
73```
74
75Once you create the key pair, the public should be saved to `~/.ssh/id_rsa.pub`.
76
77If your server still has password-based authentication available, you can copy
78it over to your user's home directory like this:
79
80```sh
81ssh-copy-id git@server
82```
83
84Otherwise, copy it over to any user that you can access.
85
86```sh
87scp ~/.ssh/id_rsa.pub your_user@your_server:
88```
89
90Once on the server, you will need to copy the contents into the `git` user's
91`authorized_keys` file:
92
93```sh
94cat id_rsa.pub > /home/git/.ssh/authorized_keys
95```
96
97## (Optional) Disable Password-Based SSH
98
99If you want to lock down your server and ensure that no one can authenticate in
100via SSH with a password, you will need to edit your SSH configuration.
101
102```sh
103sudo nano /etc/ssh/sshd_config
104```
105
106Within this file, find the following settings and set them to the values I am
107showing below:
108
109```conf
110PermitRootLogin no
111PasswordAuthentication no
112AuthenticationMethods publickey
113```
114
115You may have other Authentication Methods required in your personal set-up, so
116the key here is just to ensure that `AuthenticationMethods` does not allow
117passwords.
118
119### Setting up the Base Directory
120
121Now that we have set up a `git` user to handle all transport methods, we need to
122set up the directory that we will be using as our base of all repositories.
123
124In my case, I am using `/git` as my source folder. To create this folder and
125assign it to the user we created, execute the following commands:
126
127```sh
128sudo mkdir /git
129sudo chown -R git:git /git
130```
131
132### Creating a Test Repository
133
134On your server, switch over to the `git` user in order to start managing git
135files.
136
137```sh
138su git
139```
140
141Once logged-in as the `git` user, go to your base directory and create a test
142repository.
143
144```sh
145cd /git
146mkdir test.git && cd test.git
147git init --bare
148```
149
150If you want to make this repo viewable/cloneable to the public via the `git://`
151protocol, you need to create a `git-daemon-export-ok` file inside the
152repository.
153
154```sh
155touch git-daemon-export-ok
156```
157
158# Change the Login Shell for `git`
159
160To make sure that the `git` user is only used for git operations and nothing
161else, you need to change the user's login shell. To do this, simply use the
162`chsh` command:
163
164```sh
165sudo chsh git
166```
167
168The interactive prompt will ask which shell you want the `git` user to use. You
169must use the following value:
170
171```sh
172/usr/bin/git-shell
173```
174
175Once done, no one will be able to SSH to the `git` user or execute commands
176other than the standard git commands.
177
178# Opening the Firewall
179
180Don't forget to open up ports on the device firewall and network firewall if you
181want to access these repositories publicly. If you're using default ports,
182forward ports `22` (ssh) and `9418` (git) from your router to your server's IP
183address.
184
185If your server also has a firewall, ensure that the firewall allows the same
186ports that are forwarded from the router. For example, if you use `ufw`:
187
188```sh
189sudo ufw allow 22
190sudo ufw allow 9418
191```
192
193## Non-Standard SSH Ports
194
195If you use a non-standard port for SSH, such as `9876`, you will need to create
196an SSH configuration file on your local development machine in order to connect
197to your server's git repositories.
198
199To do this, you'll need to define your custom port on your client machine in
200your `~/.ssh/config` file:
201
202```sh
203nano ~/.ssh/config
204```
205
206```conf
207Host git.example.com
208 # HostName can be a URL or an IP address
209 HostName git.example.com
210 Port 9876
211 User git
212```
213
214## Testing SSH
215
216There are two main syntaxes you can use to manage git over SSH:
217
218- `git clone [user@]server:project.git`
219- `git clone ssh://[user@]server/project.git`
220
221I prefer the first, which is an `scp`-like syntax. To test it, try to clone the
222test repository you set up on the server:
223
224```sh
225git clone git@git.example.com:/git/test.git
226```
227
228# Enabling Read-Only Access
229
230If you want people to be able to clone any repository where you've placed a
231`git-daemon-export-ok` file, you will need to start the git daemon.
232
233To do this on a system with `systemd`, create a service file:
234
235```sh
236sudo nano /etc/systemd/system/git-daemon.service
237```
238
239Inside the `git-daemon.service` file, paste the following:
240
241```conf
242[Unit]
243Description=Start Git Daemon
244
245[Service]
246ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
247
248Restart=always
249RestartSec=500ms
250
251StandardOutput=syslog
252StandardError=syslog
253SyslogIdentifier=git-daemon
254
255User=git
256Group=git
257
258[Install]
259WantedBy=multi-user.target
260```
261
262Once created, enable and start the service:
263
264```sh
265sudo systemctl enable git-daemon.service
266sudo systemctl start git-daemon.service
267```
268
269To clone read-only via the `git://` protocol, you can use the following syntax:
270
271```sh
272git clone git://git.example.com/test.git
273```
274
275# Migrating Repositories
276
277At this point, we have a working git server that works with both SSH and
278read-only access.
279
280For each of the repositories I had hosted a different provider, I executed the
281following commands in order to place a copy on my server as my new source of
282truth:
283
284Server:
285
286```sh
287su git
288mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
289git init --bare
290
291# If you want to make this repo viewable/cloneable to the public
292touch git-daemon-export-ok
293```
294
295Client:
296
297```sh
298git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
299git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
300git push
301```
302
303# Optional Web View: `cgit`
304
305If you want a web viewer for your repositories, you can use various tools, such
306as `gitweb`, `cgit`, or `klaus`. I chose `cgit` due to its simple interface and
307fairly easy set-up (compared to others). Not to mention that the [Linux kernel
308uses `cgit`](https://git.kernel.org/).
309
310## Docker Compose
311
312Instead of using my previous method of using a `docker run` command, I've
313updated this section to use `docker-compose` instead for an easier installation
314and simpler management and configuration.
315
316In order to use Docker Compose, you will set up a `docker-compose.yml` file to
317automatically connect resources like the repositories, `cgitrc`, and various
318files or folders to the `cgit` container you're creating:
319
320```sh
321mkdir ~/cgit && cd ~/cgit
322nano docker-compose.yml
323```
324
325```conf
326# docker-compose.yml
327version: '3'
328
329services:
330 cgit:
331 image: invokr/cgit
332 volumes:
333 - /git:/git
334 - ./cgitrc:/etc/cgitrc
335 - ./logo.png:/var/www/htdocs/cgit/logo.png
336 - ./favicon.png:/var/www/htdocs/cgit/favicon.png
337 - ./filters:/var/www/htdocs/cgit/filters
338 ports:
339 - "8763:80"
340 restart: always
341```
342
343Then, just start the container:
344
345```sh
346sudo docker-compose up -d
347```
348
349Once it's finished installing, you can access the site at `<SERVER_IP>:8763` or
350use a reverse-proxy service to forward `cgit` to a URL, such as
351`git.example.com`. See the next section for more details on reverse proxying a
352URL to a local port.
353
354## Nginx Reverse Proxy
355
356I am using Nginx as my reverse proxy so that the `cgit` Docker container can use
357`git.example.com` as its URL. To do so, I simply created the following
358configuration file:
359
360```sh
361sudo nano /etc/nginx/sites-available/git.example.com
362```
363
364```conf
365server {
366 listen 80;
367 server_name git.example.com;
368
369 if ($host = git.example.com) {
370 return 301 https://$host$request_uri;
371 }
372
373 return 404;
374}
375
376server {
377 server_name git.example.com;
378 listen 443 ssl http2;
379
380 location / {
381 # The final `/` is important.
382 proxy_pass http://localhost:8763/;
383 add_header X-Frame-Options SAMEORIGIN;
384 add_header X-XSS-Protection "1; mode=block";
385 proxy_redirect off;
386 proxy_buffering off;
387 proxy_set_header Host $host;
388 proxy_set_header X-Real-IP $remote_addr;
389 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
390 proxy_set_header X-Forwarded-Proto $scheme;
391 proxy_set_header X-Forwarded-Port $server_port;
392 }
393
394 # INCLUDE ANY SSL CERTS HERE
395 include /etc/letsencrypt/options-ssl-nginx.conf;
396 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
397}
398```
399
400Once created, symlink it and restart the web server.
401
402```sh
403sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
404sudo systemctl restart nginx.service
405```
406
407As we can see below, my site at `git.example.com` is available and running:
408
409## Settings Up Git Details
410
411Once you have `cgit` running, you can add some small details, such as repository
412owners and descriptions by editing the following files within each repository.
413
414Alternatively, you can use the `cgitrc` file to edit these details if you only
415care to edit them for the purpose of seeing them on your website.
416
417The `description` file within the repository on your server will display the
418description online.
419
420```sh
421cd /git/example.git
422nano description
423```
424
425You can add a `[gitweb]` block to the `config` file in order to display the
426owner of the repository.
427
428```sh
429cd /git/example.git
430nano config
431```
432
433```conf
434[gitweb]
435 owner = "YourName"
436```
437
438Note that you can ignore the configuration within each repository and simply set
439up this information in the `cgitrc` file, if you want to do it that way.
440
441## Editing `cgit`
442
443In order to edit certain items within `cgit`, you need to edit the `cgitrc`
444file.
445
446```sh
447nano ~/cgit/cgitrc
448```
449
450Below is an example configuration for `cgitrc`. You can find all the
451configuration options within the [configuration manual]
452(<https://git.zx2c4.com/cgit/plain/cgitrc.5.txt>).
453
454```conf
455css=/cgit.css
456logo=/logo.png
457favicon=/favicon.png
458robots=noindex, nofollow
459
460enable-index-links=1
461enable-commit-graph=1
462enable-blame=1
463enable-log-filecount=1
464enable-log-linecount=1
465enable-git-config=1
466
467clone-url=git://git.example.com/$CGIT_REPO_URL ssh://git@git.example.com:/git/$CGIT_REPO_URL
468
469root-title=My Git Website
470root-desc=My personal git repositories.
471
472# Allow download of tar.gz, tar.bz2 and zip-files
473snapshots=tar.gz tar.bz2 zip
474
475##
476## List of common mimetypes
477##
478mimetype.gif=image/gif
479mimetype.html=text/html
480mimetype.jpg=image/jpeg
481mimetype.jpeg=image/jpeg
482mimetype.pdf=application/pdf
483mimetype.png=image/png
484mimetype.svg=image/svg+xml
485
486# Highlight source code
487# source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.sh
488source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
489
490# Format markdown, restructuredtext, manpages, text files, and html files
491# through the right converters
492about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
493
494##
495## Search for these files in the root of the default branch of repositories
496## for coming up with the about page:
497##
498readme=:README.md
499readme=:readme.md
500readme=:README.mkd
501readme=:readme.mkd
502readme=:README.rst
503readme=:readme.rst
504readme=:README.html
505readme=:readme.html
506readme=:README.htm
507readme=:readme.htm
508readme=:README.txt
509readme=:readme.txt
510readme=:README
511readme=:readme
512
513# Repositories
514
515# Uncomment the following line to scan a path instead of adding repositories manually
516# scan-path=/git
517
518## Test Section
519section=git/test-section
520
521repo.url=test.git
522repo.path=/git/test.git
523repo.readme=:README.md
524repo.owner=John Doe
525repo.desc=An example repository!
526```
527
528## Final Fixes: Syntax Highlighting & README Rendering
529
530After completing my initial install and playing around with it for a few days, I
531noticed two issues:
532
5331. Syntax highlighting did not work when viewing the source code within a file.
5342. The `about` tab within a repository was not rendered to HTML.
535
536The following process fixes these issues. To start, let's go to the `cgit`
537directory where we were editing our configuration file earlier.
538
539```sh
540cd ~/cgit
541```
542
543In here, create two folders that will hold our syntax files:
544
545```sh
546mkdir filters && mkdir filters/html-converters && cd filters
547```
548
549Next, download the default filters:
550
551```sh
552curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
553chmod 755 about-formatting.sh
554curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
555chmod 755 syntax-highlighting.py
556```
557
558Finally, download the HTML conversion files you need. The example below
559downloads the Markdown converter:
560
561```sh
562cd html-converters
563curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
564chmod 755 md2html
565```
566
567If you need other filters or html-converters found within [the cgit project
568files](https://git.zx2c4.com/cgit/tree/filters), repeat the `curl` and `chmod`
569process above for whichever files you need.
570
571However, formatting will not work quite yet since the Docker cgit container
572we're using doesn't have the formatting package installed. You can install this
573easily by install Python 3+ and the `pygments` package:
574
575```sh
576# Enter the container's command line
577sudo docker exec -it cgit bash
578```
579
580```sh
581# Install the necessary packages and then exit
582yum update -y && \
583yum upgrade -y && \
584yum install python3 python3-pip -y && \
585pip3 install markdown pygments && \
586exit
587```
588
589**You will need to enter the cgit docker container and re-run these `yum`
590commands every time you kill and restart the container!**
591
592If not done already, we need to add the following variables to our `cgitrc` file
593in order for `cgit` to know where our filtering files are:
594
595```conf
596# Highlight source code with python pygments-based highlighter
597source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
598
599# Format markdown, restructuredtext, manpages, text files, and html files
600# through the right converters
601about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
602```
603
604Now you should see that syntax highlighting and README rendering to the `about`
605tab is fixed.
606
607## Theming
608
609I won't go into much detail in this section, but you can fully theme your
610installation of `cgit` since you have access to the `cgit.css` file in your web
611root. This is another file you can add as a volume to the `docker-compose.yml`
612file if you want to edit this without entering the container's command line.
613
614### :warning: Remember to Back Up Your Data!
615
616The last thing to note is that running services on your own equipment means that
617you're assuming a level of risk that exists regarding data loss, catastrophes,
618etc. In order to reduce the impact of any such occurrence, I suggest backing up
619your data regularly.
620
621Backups can be automated via `cron`, by hooking your base directory up to a
622cloud provider, or even setting up hooks to push all repository info to git
623mirrors on other git hosts. Whatever the method, make sure that your data
624doesn't vanish in the event that your drives or servers fail.
content/blog/2022-07-01-git-server.org added +638
@@ -0,0 +1,638 @@
1#+date: <2022-07-01>
2#+title: Self-Hosting a Personal Git Server
3#+description:
4
5
6* My Approach to Self-Hosting Git
7
8I have often tried to self-host my Git repositories, but have always
9fallen short when I tried to find a suitable web interface to show on
10the front-end.
11
12After a few years, I have finally found a combination of methods that
13allow me to easily self-host my projects, view them on the web, and
14access them from anywhere.
15
16Before I dive into the details, I want to state a high-level summary of
17my self-hosted Git approach:
18
19- This method uses the =ssh://= (read & write) and =git://= (read-only)
20 protocols for push and pull access.
21 - For the =git://= protocol, I create a =git-daemon-export-ok= file in
22 any repository that I want to be cloneable by anyone.
23 - The web interface I am using (=cgit=) allows simple HTTP cloning by
24 default. I do not disable this setting as I want beginners to be
25 able to clone one of my repositories even if they don't know the
26 proper method.
27- I am not enabling Smart HTTPS for any repositories. Updates to
28 repositories must be pushed via SSH.
29- Beyond the actual repository management, I am using =cgit= for the
30 front-end web interface.
31 - If you use the =scan-path=<path>= configuration in the =cgitrc=
32 configuration file to automatically find repositories, you can't
33 exclude a repository from =cgit= if it's stored within the path that
34 =cgit= reads. To host private repositories, you'd need to set up
35 another directory that =cgit= can't read.
36
37* Assumptions
38
39For the purposes of this walkthrough, I am assuming you have a URL
40(=git.example.com=) or IP address (=207.84.26.991=) addressed to the
41server that you will be using to host your git repositories.
42
43* Adding a Git User
44
45In order to use the SSH method associated with git, we will need to add
46a user named =git=. If you have used the SSH method for other git
47hosting sites, you are probably used to the following syntax:
48
49#+begin_src sh
50git clone [user@]server:project.git
51#+end_src
52
53The syntax above is an =scp=-like syntax for using SSH on the =git= user
54on the server to access your repository.
55
56Let's delete any remnants of an old =git= user, if any, and create the
57new user account:
58
59#+begin_src sh
60sudo deluser --remove-home git
61sudo adduser git
62#+end_src
63
64** Import Your SSH Keys to the Git User
65
66Once the =git= user is created, you will need to copy your public SSH
67key on your local development machine to the =git= user on the server.
68
69If you don't have an SSH key yet, create one with this command:
70
71#+begin_src sh
72ssh-keygen
73#+end_src
74
75Once you create the key pair, the public should be saved to
76=~/.ssh/id_rsa.pub=.
77
78If your server still has password-based authentication available, you
79can copy it over to your user's home directory like this:
80
81#+begin_src sh
82ssh-copy-id git@server
83#+end_src
84
85Otherwise, copy it over to any user that you can access.
86
87#+begin_src sh
88scp ~/.ssh/id_rsa.pub your_user@your_server:
89#+end_src
90
91Once on the server, you will need to copy the contents into the =git=
92user's =authorized_keys= file:
93
94#+begin_src sh
95cat id_rsa.pub > /home/git/.ssh/authorized_keys
96#+end_src
97
98** (Optional) Disable Password-Based SSH
99
100If you want to lock down your server and ensure that no one can
101authenticate in via SSH with a password, you will need to edit your SSH
102configuration.
103
104#+begin_src sh
105sudo nano /etc/ssh/sshd_config
106#+end_src
107
108Within this file, find the following settings and set them to the values
109I am showing below:
110
111#+begin_src conf
112PermitRootLogin no
113PasswordAuthentication no
114AuthenticationMethods publickey
115#+end_src
116
117You may have other Authentication Methods required in your personal
118set-up, so the key here is just to ensure that =AuthenticationMethods=
119does not allow passwords.
120
121*** Setting up the Base Directory
122
123Now that we have set up a =git= user to handle all transport methods, we
124need to set up the directory that we will be using as our base of all
125repositories.
126
127In my case, I am using =/git= as my source folder. To create this folder
128and assign it to the user we created, execute the following commands:
129
130#+begin_src sh
131sudo mkdir /git
132sudo chown -R git:git /git
133#+end_src
134
135*** Creating a Test Repository
136
137On your server, switch over to the =git= user in order to start managing
138git files.
139
140#+begin_src sh
141su git
142#+end_src
143
144Once logged-in as the =git= user, go to your base directory and create a
145test repository.
146
147#+begin_src sh
148cd /git
149mkdir test.git && cd test.git
150git init --bare
151#+end_src
152
153If you want to make this repo viewable/cloneable to the public via the
154=git://= protocol, you need to create a =git-daemon-export-ok= file
155inside the repository.
156
157#+begin_src sh
158touch git-daemon-export-ok
159#+end_src
160
161* Change the Login Shell for =git=
162
163To make sure that the =git= user is only used for git operations and
164nothing else, you need to change the user's login shell. To do this,
165simply use the =chsh= command:
166
167#+begin_src sh
168sudo chsh git
169#+end_src
170
171The interactive prompt will ask which shell you want the =git= user to
172use. You must use the following value:
173
174#+begin_src sh
175/usr/bin/git-shell
176#+end_src
177
178Once done, no one will be able to SSH to the =git= user or execute
179commands other than the standard git commands.
180
181* Opening the Firewall
182
183Don't forget to open up ports on the device firewall and network
184firewall if you want to access these repositories publicly. If you're
185using default ports, forward ports =22= (ssh) and =9418= (git) from your
186router to your server's IP address.
187
188If your server also has a firewall, ensure that the firewall allows the
189same ports that are forwarded from the router. For example, if you use
190=ufw=:
191
192#+begin_src sh
193sudo ufw allow 22
194sudo ufw allow 9418
195#+end_src
196
197** Non-Standard SSH Ports
198
199If you use a non-standard port for SSH, such as =9876=, you will need to
200create an SSH configuration file on your local development machine in
201order to connect to your server's git repositories.
202
203To do this, you'll need to define your custom port on your client
204machine in your =~/.ssh/config= file:
205
206#+begin_src sh
207nano ~/.ssh/config
208#+end_src
209
210#+begin_src conf
211Host git.example.com
212 # HostName can be a URL or an IP address
213 HostName git.example.com
214 Port 9876
215 User git
216#+end_src
217
218** Testing SSH
219
220There are two main syntaxes you can use to manage git over SSH:
221
222- =git clone [user@]server:project.git=
223- =git clone ssh://[user@]server/project.git=
224
225I prefer the first, which is an =scp=-like syntax. To test it, try to
226clone the test repository you set up on the server:
227
228#+begin_src sh
229git clone git@git.example.com:/git/test.git
230#+end_src
231
232* Enabling Read-Only Access
233
234If you want people to be able to clone any repository where you've
235placed a =git-daemon-export-ok= file, you will need to start the git
236daemon.
237
238To do this on a system with =systemd=, create a service file:
239
240#+begin_src sh
241sudo nano /etc/systemd/system/git-daemon.service
242#+end_src
243
244Inside the =git-daemon.service= file, paste the following:
245
246#+begin_src conf
247[Unit]
248Description=Start Git Daemon
249
250[Service]
251ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
252
253Restart=always
254RestartSec=500ms
255
256StandardOutput=syslog
257StandardError=syslog
258SyslogIdentifier=git-daemon
259
260User=git
261Group=git
262
263[Install]
264WantedBy=multi-user.target
265#+end_src
266
267Once created, enable and start the service:
268
269#+begin_src sh
270sudo systemctl enable git-daemon.service
271sudo systemctl start git-daemon.service
272#+end_src
273
274To clone read-only via the =git://= protocol, you can use the following
275syntax:
276
277#+begin_src sh
278git clone git://git.example.com/test.git
279#+end_src
280
281* Migrating Repositories
282
283At this point, we have a working git server that works with both SSH and
284read-only access.
285
286For each of the repositories I had hosted a different provider, I
287executed the following commands in order to place a copy on my server as
288my new source of truth:
289
290Server:
291
292#+begin_src sh
293su git
294mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
295git init --bare
296
297# If you want to make this repo viewable/cloneable to the public
298touch git-daemon-export-ok
299#+end_src
300
301Client:
302
303#+begin_src sh
304git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
305git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
306git push
307#+end_src
308
309* Optional Web View: =cgit=
310
311If you want a web viewer for your repositories, you can use various
312tools, such as =gitweb=, =cgit=, or =klaus=. I chose =cgit= due to its
313simple interface and fairly easy set-up (compared to others). Not to
314mention that the [[https://git.kernel.org/][Linux kernel uses =cgit=]].
315
316** Docker Compose
317
318Instead of using my previous method of using a =docker run= command,
319I've updated this section to use =docker-compose= instead for an easier
320installation and simpler management and configuration.
321
322In order to use Docker Compose, you will set up a =docker-compose.yml=
323file to automatically connect resources like the repositories, =cgitrc=,
324and various files or folders to the =cgit= container you're creating:
325
326#+begin_src sh
327mkdir ~/cgit && cd ~/cgit
328nano docker-compose.yml
329#+end_src
330
331#+begin_src conf
332# docker-compose.yml
333version: '3'
334
335services:
336 cgit:
337 image: invokr/cgit
338 volumes:
339 - /git:/git
340 - ./cgitrc:/etc/cgitrc
341 - ./logo.png:/var/www/htdocs/cgit/logo.png
342 - ./favicon.png:/var/www/htdocs/cgit/favicon.png
343 - ./filters:/var/www/htdocs/cgit/filters
344 ports:
345 - "8763:80"
346 restart: always
347#+end_src
348
349Then, just start the container:
350
351#+begin_src sh
352sudo docker-compose up -d
353#+end_src
354
355Once it's finished installing, you can access the site at
356=<SERVER_IP>:8763= or use a reverse-proxy service to forward =cgit= to a
357URL, such as =git.example.com=. See the next section for more details on
358reverse proxying a URL to a local port.
359
360** Nginx Reverse Proxy
361
362I am using Nginx as my reverse proxy so that the =cgit= Docker container
363can use =git.example.com= as its URL. To do so, I simply created the
364following configuration file:
365
366#+begin_src sh
367sudo nano /etc/nginx/sites-available/git.example.com
368#+end_src
369
370#+begin_src conf
371server {
372 listen 80;
373 server_name git.example.com;
374
375 if ($host = git.example.com) {
376 return 301 https://$host$request_uri;
377 }
378
379 return 404;
380}
381
382server {
383 server_name git.example.com;
384 listen 443 ssl http2;
385
386 location / {
387 # The final `/` is important.
388 proxy_pass http://localhost:8763/;
389 add_header X-Frame-Options SAMEORIGIN;
390 add_header X-XSS-Protection "1; mode=block";
391 proxy_redirect off;
392 proxy_buffering off;
393 proxy_set_header Host $host;
394 proxy_set_header X-Real-IP $remote_addr;
395 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
396 proxy_set_header X-Forwarded-Proto $scheme;
397 proxy_set_header X-Forwarded-Port $server_port;
398 }
399
400 # INCLUDE ANY SSL CERTS HERE
401 include /etc/letsencrypt/options-ssl-nginx.conf;
402 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
403}
404#+end_src
405
406Once created, symlink it and restart the web server.
407
408#+begin_src sh
409sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
410sudo systemctl restart nginx.service
411#+end_src
412
413As we can see below, my site at =git.example.com= is available and
414running:
415
416** Settings Up Git Details
417
418Once you have =cgit= running, you can add some small details, such as
419repository owners and descriptions by editing the following files within
420each repository.
421
422Alternatively, you can use the =cgitrc= file to edit these details if
423you only care to edit them for the purpose of seeing them on your
424website.
425
426The =description= file within the repository on your server will display
427the description online.
428
429#+begin_src sh
430cd /git/example.git
431nano description
432#+end_src
433
434You can add a =[gitweb]= block to the =config= file in order to display
435the owner of the repository.
436
437#+begin_src sh
438cd /git/example.git
439nano config
440#+end_src
441
442#+begin_src conf
443[gitweb]
444 owner = "YourName"
445#+end_src
446
447Note that you can ignore the configuration within each repository and
448simply set up this information in the =cgitrc= file, if you want to do
449it that way.
450
451** Editing =cgit=
452
453In order to edit certain items within =cgit=, you need to edit the
454=cgitrc= file.
455
456#+begin_src sh
457nano ~/cgit/cgitrc
458#+end_src
459
460Below is an example configuration for =cgitrc=. You can find all the
461configuration options within the [configuration manual]
462([[https://git.zx2c4.com/cgit/plain/cgitrc.5.txt]]).
463
464#+begin_src conf
465css=/cgit.css
466logo=/logo.png
467favicon=/favicon.png
468robots=noindex, nofollow
469
470enable-index-links=1
471enable-commit-graph=1
472enable-blame=1
473enable-log-filecount=1
474enable-log-linecount=1
475enable-git-config=1
476
477clone-url=git://git.example.com/$CGIT_REPO_URL ssh://git@git.example.com:/git/$CGIT_REPO_URL
478
479root-title=My Git Website
480root-desc=My personal git repositories.
481
482# Allow download of tar.gz, tar.bz2 and zip-files
483snapshots=tar.gz tar.bz2 zip
484
485##
486## List of common mimetypes
487##
488mimetype.gif=image/gif
489mimetype.html=text/html
490mimetype.jpg=image/jpeg
491mimetype.jpeg=image/jpeg
492mimetype.pdf=application/pdf
493mimetype.png=image/png
494mimetype.svg=image/svg+xml
495
496# Highlight source code
497# source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.sh
498source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
499
500# Format markdown, restructuredtext, manpages, text files, and html files
501# through the right converters
502about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
503
504##
505## Search for these files in the root of the default branch of repositories
506## for coming up with the about page:
507##
508readme=:README.md
509readme=:readme.md
510readme=:README.mkd
511readme=:readme.mkd
512readme=:README.rst
513readme=:readme.rst
514readme=:README.html
515readme=:readme.html
516readme=:README.htm
517readme=:readme.htm
518readme=:README.txt
519readme=:readme.txt
520readme=:README
521readme=:readme
522
523# Repositories
524
525# Uncomment the following line to scan a path instead of adding repositories manually
526# scan-path=/git
527
528## Test Section
529section=git/test-section
530
531repo.url=test.git
532repo.path=/git/test.git
533repo.readme=:README.md
534repo.owner=John Doe
535repo.desc=An example repository!
536#+end_src
537
538** Final Fixes: Syntax Highlighting & README Rendering
539
540After completing my initial install and playing around with it for a few
541days, I noticed two issues:
542
5431. Syntax highlighting did not work when viewing the source code within
544 a file.
5452. The =about= tab within a repository was not rendered to HTML.
546
547The following process fixes these issues. To start, let's go to the
548=cgit= directory where we were editing our configuration file earlier.
549
550#+begin_src sh
551cd ~/cgit
552#+end_src
553
554In here, create two folders that will hold our syntax files:
555
556#+begin_src sh
557mkdir filters && mkdir filters/html-converters && cd filters
558#+end_src
559
560Next, download the default filters:
561
562#+begin_src sh
563curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
564chmod 755 about-formatting.sh
565curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
566chmod 755 syntax-highlighting.py
567#+end_src
568
569Finally, download the HTML conversion files you need. The example below
570downloads the Markdown converter:
571
572#+begin_src sh
573cd html-converters
574curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
575chmod 755 md2html
576#+end_src
577
578If you need other filters or html-converters found within
579[[https://git.zx2c4.com/cgit/tree/filters][the cgit project files]],
580repeat the =curl= and =chmod= process above for whichever files you
581need.
582
583However, formatting will not work quite yet since the Docker cgit
584container we're using doesn't have the formatting package installed. You
585can install this easily by install Python 3+ and the =pygments= package:
586
587#+begin_src sh
588# Enter the container's command line
589sudo docker exec -it cgit bash
590#+end_src
591
592#+begin_src sh
593# Install the necessary packages and then exit
594yum update -y && \
595yum upgrade -y && \
596yum install python3 python3-pip -y && \
597pip3 install markdown pygments && \
598exit
599#+end_src
600
601*You will need to enter the cgit docker container and re-run these =yum=
602commands every time you kill and restart the container!*
603
604If not done already, we need to add the following variables to our
605=cgitrc= file in order for =cgit= to know where our filtering files are:
606
607#+begin_src conf
608# Highlight source code with python pygments-based highlighter
609source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
610
611# Format markdown, restructuredtext, manpages, text files, and html files
612# through the right converters
613about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
614#+end_src
615
616Now you should see that syntax highlighting and README rendering to the
617=about= tab is fixed.
618
619** Theming
620
621I won't go into much detail in this section, but you can fully theme
622your installation of =cgit= since you have access to the =cgit.css= file
623in your web root. This is another file you can add as a volume to the
624=docker-compose.yml= file if you want to edit this without entering the
625container's command line.
626
627*** :warning: Remember to Back Up Your Data!
628
629The last thing to note is that running services on your own equipment
630means that you're assuming a level of risk that exists regarding data
631loss, catastrophes, etc. In order to reduce the impact of any such
632occurrence, I suggest backing up your data regularly.
633
634Backups can be automated via =cron=, by hooking your base directory up
635to a cloud provider, or even setting up hooks to push all repository
636info to git mirrors on other git hosts. Whatever the method, make sure
637that your data doesn't vanish in the event that your drives or servers
638fail.
content/blog/2022-07-14-gnupg.md deleted −300
@@ -1,300 +0,0 @@
1+++
2date = 2022-07-14
3title = "GNU Privacy Guard (GPG)"
4description = ""
5draft = false
6+++
7
8# The History of GPG
9
10[GNU Privacy Guard](https://gnupg.org/), also known as GnuPG and GPG, is a free
11("free" as in both speech and beer) software that fully implements the OpenPGP
12Message Format documented in [RFC 4880](https://www.rfc-editor.org/rfc/rfc4880).
13
14I won't go in-depth on the full history of the software in this post, but it is
15important to understand that GPG is not the same as PGP (Pretty Good Privacy),
16which is a different implementation of RFC 4880. However, GPG was designed to
17interoperate with PGP.
18
19GPG was originally developed in the late 1990s by [Werner
20Koch](https://en.wikipedia.org/wiki/Werner_Koch) and has historically been
21funded generously by the German government.
22
23Now that we have all the high-level info out of the way, let's dive into the
24different aspects of GPG and its uses.
25
26# Encryption Algorithms
27
28GPG supports a wide range of different encryption algorithms, including
29public-key, cipher, hash, and compression algorithms. The support for these
30algorithms has grown since the adoption of the Libgcrypt library in the 2.x
31versions of GPG.
32
33As you will be able to see below in an example of a full key generation with the
34GPG command line tool, GPG recommends the following algorithms to new users:
35
36```sh
37Please select what kind of key you want:
38 (1) RSA and RSA
39 (2) DSA and Elgamal
40 (3) DSA (sign only)
41 (4) RSA (sign only)
42 (9) ECC (sign and encrypt) *default*
43 (10) ECC (sign only)
44```
45
46I am not doing an in-depth explanation here in order to keep the focus on GPG
47and not encryption algorithms. If you want a deep dive into cryptography or
48encryption algorithms, please read my other posts:
49
50- [AES Encryption](../aes-encryption/) (2018)
51- [Cryptography Basics](../cryptography-basics/) (2020)
52
53## Vulnerabilities
54
55As of 2022-07-14, there are a few different vulnerabilities associated with GPG
56or the libraries it uses:
57
58- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one
59 (GPG-generated) ElGamal signature of an arbitrary message is released, one
60 can recover the signer's private key in less than a second on a PC."
61 ([Source](https://www.di.ens.fr/~pnguyen/pub_Ng04.htm))
62- GPG versions prior to 1.4.2.1 contain a false positive signature
63 verification bug.
64 ([Source](https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000211.html))
65- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned data. (
66 [Source](https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html))
67- Libgcrypt, a library used by GPG, contained a bug which enabled full key
68 recovery for RSA-1024 and some RSA-2048 keys. This was resolved in a GPG
69 update in 2017. ([Source](https://lwn.net/Articles/727179/))
70- The [ROCA Vulnerability](https://en.wikipedia.org/wiki/ROCA_vulnerability)
71 affects RSA keys generated by YubiKey 4 tokens.
72 ([Source](https://crocs.fi.muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf))
73- The [SigSpoof Attack](https://en.wikipedia.org/wiki/SigSpoof) allows an
74 attacker to spoof digital signatures.
75 ([Source](https://arstechnica.com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/))
76- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer overflow,
77 fixed in Libgcrypt 1.9.1
78 ([Source](https://web.archive.org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/))
79
80### Platforms
81
82Originally developed as a command-line program for \*nix systems, GPG now has a
83wealth of front-end applications and libraries available for end-users. However,
84the most recommended programs remain the same:
85
86- [GnuPG](https://gnupg.org) for Linux (depending on distro)
87- [Gpg4win](https://gpg4win.org) for Windows
88- [GPGTools](https://gpgtools.org) for macOS
89
90# Creating a Key Pair
91
92In order to create a GPG key pair, a user would first need to install GPG on
93their system. If we're assuming that the user is on Fedora Linux, they would
94execute the following:
95
96```sh
97sudo dnf install gpg
98```
99
100Once installed, a user can create a new key pair with the following command(s):
101
102```sh
103gpg --full-generate-key
104```
105
106GPG will walk the user through an interactive setup that asks for an algorithm
107preference, expiration date, name, and email to associate with this key.
108
109See the following example key set-up for a default key generation using the
110GnuPG command-line interface:
111
112```sh
113gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc.
114This is free software: you are free to change and redistribute it.
115There is NO WARRANTY, to the extent permitted by law.
116
117Please select what kind of key you want:
118 (1) RSA and RSA
119 (2) DSA and Elgamal
120 (3) DSA (sign only)
121 (4) RSA (sign only)
122 (9) ECC (sign and encrypt) *default*
123 (10) ECC (sign only)
124 (14) Existing key from card
125Your selection? 9
126Please select which elliptic curve you want:
127 (1) Curve 25519 *default*
128 (4) NIST P-384
129Your selection? 1
130Please specify how long the key should be valid.
131 0 = key does not expire
132 <n> = key expires in n days
133 <n>w = key expires in n weeks
134 <n>m = key expires in n months
135 <n>y = key expires in n years
136Key is valid for? (0) 0
137Key does not expire at all
138Is this correct? (y/N) y
139
140GnuPG needs to construct a user ID to identify your key.
141
142Real name: John Doe
143Email address: johndoe@example.com
144Comment: test key
145You selected this USER-ID:
146 "John Doe (test key) <johndoe@example.com>"
147
148Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
149We need to generate a lot of random bytes. It is a good idea to perform
150some other action (type on the keyboard, move the mouse, utilize the
151disks) during the prime generation; this gives the random number
152generator a better chance to gain enough entropy.
153We need to generate a lot of random bytes. It is a good idea to perform
154some other action (type on the keyboard, move the mouse, utilize the
155disks) during the prime generation; this gives the random number
156generator a better chance to gain enough entropy.
157gpg: revocation certificate stored as 'example.rev'
158public and secret key created and signed.
159
160pub ed25519 2022-07-14 [SC]
161 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C
162uid John Doe (test key) <johndoe@example.com>
163sub cv25519 2022-07-14 [E]
164```
165
166Please note that GUI apps may differ slightly from the GPG command-line
167interface.
168
169# Common Usage
170
171As noted in RFC 4880, the general functions of OpenPGP are as follows:
172
173- digital signatures
174- encryption
175- compression
176- Radix-64 conversion
177- key management and certificate services
178
179From this, you can probably gather that the main use of GPG is for encrypting
180data and/or signing the data with a key. The purpose of encrypting data with GPG
181is to ensure that no one except the intended recipient(s) can access the data.
182
183Let's explore some specific GPG use-cases.
184
185## Email
186
187One of the more popular uses of GPG is to sign and/or encrypt emails. With the
188use of a GPG keypair, you can encrypt a message, its subject, and even the
189attachments within.
190
191The first process, regarding the signing of a message without any encryption, is
192generally used to provide assurance that an email is truly coming from the
193sender that the message claims. When I send an email, and it's signed with my
194public key, the recipient(s) of the message can verify that the message was
195signed with my personal key.
196
197The second process, regarding the actual encryption of the message and its
198contents, works by using a combination of the sender's keys and the recipient's
199keys. This process may vary slightly by implementation, but it most commonly
200uses asymmetric cryptography, also known as public-key cryptography. In this
201version of encryption, the sender's private key to sign the message and a
202combination of the sender's keys and the recipient's public key to encrypt the
203message.
204
205If two people each have their own private keys and exchange their public keys,
206they can send encrypted messages back and forth with GPG. This is also possible
207with symmetric cryptography, but the process differs since there are no key
208pairs.
209
210Implementation of email encryption varies greatly between email clients, so you
211will need to reference your email client's documentation to ensure you are
212setting it up correctly for that specific client.
213
214## File Encryption
215
216As noted in the section above regarding emails, GPG enables users to be able to
217send a message to each other if they are both set-up with GPG keys. In this
218example, I am going to show how a user could send a file called
219`example_file.txt` to another user via the recipient's email.
220
221The sender would find the file they want to send and execute the following
222command:
223
224```sh
225gpg --encrypt --output example_file.txt.gpg --recipient \
226recipient@example.com example_file.txt
227```
228
229Once received, the recipient can decrypt the file with the following command:
230
231```sh
232gpg --decrypt --output example_file.txt example_file.txt.gpg
233```
234
235## Ownership Signatures
236
237One important aspect of GPG, especially for developers, is the ability to sign
238data without encrypting it. For example, developers often sign code changes when
239they commit the changes back to a central repository, in order to display
240ownership of who made the changes. This allows other users to look at a code
241change and determine that the change was valid.
242
243In order to do this using [Git](https://git-scm.com), the developer simply needs
244to alter the `git commit` command to include the `-S` flag. Here's an example:
245
246```sh
247git commit -S -m "my commit message"
248```
249
250As an expansion of the example above, Git users can configure their environment
251with a default key to use by adding their GPG signature:
252
253```sh
254git config --global user.signingkey XXXXXXXXXXXXXXXX
255```
256
257If you're not sure what your signature is, you can find it titled `sig` in the
258output of this command:
259
260```sh
261gpg --list-signatures
262```
263
264## File Integrity
265
266When a person generates a signature for data, they are allowing users the
267ability to verify the signature on that data in the future to ensure the data
268has not been corrupted. This is most common with software applications hosted on
269the internet - developers provide signatures so that users can verify a website
270was not hijacked and download links replaced with dangerous software.
271
272In order to verify signed data, a user needs to have:
273
2741. The signed data
2752. A signature file
2763. The public GPG key of the signer
277
278Once the signer's public key is imported on the user's system, and they have the
279data and signature, they can verify the data with the following commands:
280
281```sh
282# If the signature is attached to the data
283gpg --verify [signature-file]
284
285# If the signature is detached as a separate file from the data
286gpg --verify [signature-file] [original-file]
287```
288
289### Finding Public Keys
290
291In order to use GPG with others, a user needs to know the other user(s) keys.
292This is easy to do if the user knows the other user(s) in person, but may be
293hard if the relationship is strictly digital. Luckily, there are a few options.
294The first option is to look at a user's web page or social pages if they have
295them.
296
297Otherwise, the best option is to use a keyserver, such as:
298
299- [pgp.mit.edu](https://pgp.mit.edu)
300- [keys.openpgp.org](https://keys.openpgp.org)
content/blog/2022-07-14-gnupg.org added +308
@@ -0,0 +1,308 @@
1#+date: <2022-07-14>
2#+title: GNU Privacy Guard (GPG)
3#+description:
4
5
6* The History of GPG
7
8[[https://gnupg.org/][GNU Privacy Guard]], also known as GnuPG and GPG,
9is a free ("free" as in both speech and beer) software that fully
10implements the OpenPGP Message Format documented in
11[[https://www.rfc-editor.org/rfc/rfc4880][RFC 4880]].
12
13I won't go in-depth on the full history of the software in this post,
14but it is important to understand that GPG is not the same as PGP
15(Pretty Good Privacy), which is a different implementation of RFC 4880.
16However, GPG was designed to interoperate with PGP.
17
18GPG was originally developed in the late 1990s by
19[[https://en.wikipedia.org/wiki/Werner_Koch][Werner Koch]] and has
20historically been funded generously by the German government.
21
22Now that we have all the high-level info out of the way, let's dive into
23the different aspects of GPG and its uses.
24
25* Encryption Algorithms
26
27GPG supports a wide range of different encryption algorithms, including
28public-key, cipher, hash, and compression algorithms. The support for
29these algorithms has grown since the adoption of the Libgcrypt library
30in the 2.x versions of GPG.
31
32As you will be able to see below in an example of a full key generation
33with the GPG command line tool, GPG recommends the following algorithms
34to new users:
35
36#+begin_src sh
37Please select what kind of key you want:
38 (1) RSA and RSA
39 (2) DSA and Elgamal
40 (3) DSA (sign only)
41 (4) RSA (sign only)
42 (9) ECC (sign and encrypt) *default*
43 (10) ECC (sign only)
44#+end_src
45
46I am not doing an in-depth explanation here in order to keep the focus
47on GPG and not encryption algorithms. If you want a deep dive into
48cryptography or encryption algorithms, please read my other posts:
49
50- [[../aes-encryption/][AES Encryption]] (2018)
51- [[../cryptography-basics/][Cryptography Basics]] (2020)
52
53** Vulnerabilities
54
55As of 2022-07-14, there are a few different vulnerabilities associated
56with GPG or the libraries it uses:
57
58- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one
59 (GPG-generated) ElGamal signature of an arbitrary message is released,
60 one can recover the signer's private key in less than a second on a
61 PC." ([[https://www.di.ens.fr/~pnguyen/pub_Ng04.htm][Source]])
62- GPG versions prior to 1.4.2.1 contain a false positive signature
63 verification bug.
64 ([[https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000211.html][Source]])
65- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned
66 data. (
67 [[https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html][Source]])
68- Libgcrypt, a library used by GPG, contained a bug which enabled full
69 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved in
70 a GPG update in 2017. ([[https://lwn.net/Articles/727179/][Source]])
71- The [[https://en.wikipedia.org/wiki/ROCA_vulnerability][ROCA
72 Vulnerability]] affects RSA keys generated by YubiKey 4 tokens.
73 ([[https://crocs.fi.muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf][Source]])
74- The [[https://en.wikipedia.org/wiki/SigSpoof][SigSpoof Attack]] allows
75 an attacker to spoof digital signatures.
76 ([[https://arstechnica.com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/][Source]])
77- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer
78 overflow, fixed in Libgcrypt 1.9.1
79 ([[https://web.archive.org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/][Source]])
80
81*** Platforms
82
83Originally developed as a command-line program for *nix systems, GPG now
84has a wealth of front-end applications and libraries available for
85end-users. However, the most recommended programs remain the same:
86
87- [[https://gnupg.org][GnuPG]] for Linux (depending on distro)
88- [[https://gpg4win.org][Gpg4win]] for Windows
89- [[https://gpgtools.org][GPGTools]] for macOS
90
91* Creating a Key Pair
92
93In order to create a GPG key pair, a user would first need to install
94GPG on their system. If we're assuming that the user is on Fedora Linux,
95they would execute the following:
96
97#+begin_src sh
98sudo dnf install gpg
99#+end_src
100
101Once installed, a user can create a new key pair with the following
102command(s):
103
104#+begin_src sh
105gpg --full-generate-key
106#+end_src
107
108GPG will walk the user through an interactive setup that asks for an
109algorithm preference, expiration date, name, and email to associate with
110this key.
111
112See the following example key set-up for a default key generation using
113the GnuPG command-line interface:
114
115#+begin_src sh
116gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc.
117This is free software: you are free to change and redistribute it.
118There is NO WARRANTY, to the extent permitted by law.
119
120Please select what kind of key you want:
121 (1) RSA and RSA
122 (2) DSA and Elgamal
123 (3) DSA (sign only)
124 (4) RSA (sign only)
125 (9) ECC (sign and encrypt) *default*
126 (10) ECC (sign only)
127 (14) Existing key from card
128Your selection? 9
129Please select which elliptic curve you want:
130 (1) Curve 25519 *default*
131 (4) NIST P-384
132Your selection? 1
133Please specify how long the key should be valid.
134 0 = key does not expire
135 <n> = key expires in n days
136 <n>w = key expires in n weeks
137 <n>m = key expires in n months
138 <n>y = key expires in n years
139Key is valid for? (0) 0
140Key does not expire at all
141Is this correct? (y/N) y
142
143GnuPG needs to construct a user ID to identify your key.
144
145Real name: John Doe
146Email address: johndoe@example.com
147Comment: test key
148You selected this USER-ID:
149 "John Doe (test key) <johndoe@example.com>"
150
151Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
152We need to generate a lot of random bytes. It is a good idea to perform
153some other action (type on the keyboard, move the mouse, utilize the
154disks) during the prime generation; this gives the random number
155generator a better chance to gain enough entropy.
156We need to generate a lot of random bytes. It is a good idea to perform
157some other action (type on the keyboard, move the mouse, utilize the
158disks) during the prime generation; this gives the random number
159generator a better chance to gain enough entropy.
160gpg: revocation certificate stored as 'example.rev'
161public and secret key created and signed.
162
163pub ed25519 2022-07-14 [SC]
164 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C
165uid John Doe (test key) <johndoe@example.com>
166sub cv25519 2022-07-14 [E]
167#+end_src
168
169Please note that GUI apps may differ slightly from the GPG command-line
170interface.
171
172* Common Usage
173
174As noted in RFC 4880, the general functions of OpenPGP are as follows:
175
176- digital signatures
177- encryption
178- compression
179- Radix-64 conversion
180- key management and certificate services
181
182From this, you can probably gather that the main use of GPG is for
183encrypting data and/or signing the data with a key. The purpose of
184encrypting data with GPG is to ensure that no one except the intended
185recipient(s) can access the data.
186
187Let's explore some specific GPG use-cases.
188
189** Email
190
191One of the more popular uses of GPG is to sign and/or encrypt emails.
192With the use of a GPG keypair, you can encrypt a message, its subject,
193and even the attachments within.
194
195The first process, regarding the signing of a message without any
196encryption, is generally used to provide assurance that an email is
197truly coming from the sender that the message claims. When I send an
198email, and it's signed with my public key, the recipient(s) of the
199message can verify that the message was signed with my personal key.
200
201The second process, regarding the actual encryption of the message and
202its contents, works by using a combination of the sender's keys and the
203recipient's keys. This process may vary slightly by implementation, but
204it most commonly uses asymmetric cryptography, also known as public-key
205cryptography. In this version of encryption, the sender's private key to
206sign the message and a combination of the sender's keys and the
207recipient's public key to encrypt the message.
208
209If two people each have their own private keys and exchange their public
210keys, they can send encrypted messages back and forth with GPG. This is
211also possible with symmetric cryptography, but the process differs since
212there are no key pairs.
213
214Implementation of email encryption varies greatly between email clients,
215so you will need to reference your email client's documentation to
216ensure you are setting it up correctly for that specific client.
217
218** File Encryption
219
220As noted in the section above regarding emails, GPG enables users to be
221able to send a message to each other if they are both set-up with GPG
222keys. In this example, I am going to show how a user could send a file
223called =example_file.txt= to another user via the recipient's email.
224
225The sender would find the file they want to send and execute the
226following command:
227
228#+begin_src sh
229gpg --encrypt --output example_file.txt.gpg --recipient \
230recipient@example.com example_file.txt
231#+end_src
232
233Once received, the recipient can decrypt the file with the following
234command:
235
236#+begin_src sh
237gpg --decrypt --output example_file.txt example_file.txt.gpg
238#+end_src
239
240** Ownership Signatures
241
242One important aspect of GPG, especially for developers, is the ability
243to sign data without encrypting it. For example, developers often sign
244code changes when they commit the changes back to a central repository,
245in order to display ownership of who made the changes. This allows other
246users to look at a code change and determine that the change was valid.
247
248In order to do this using [[https://git-scm.com][Git]], the developer
249simply needs to alter the =git commit= command to include the =-S= flag.
250Here's an example:
251
252#+begin_src sh
253git commit -S -m "my commit message"
254#+end_src
255
256As an expansion of the example above, Git users can configure their
257environment with a default key to use by adding their GPG signature:
258
259#+begin_src sh
260git config --global user.signingkey XXXXXXXXXXXXXXXX
261#+end_src
262
263If you're not sure what your signature is, you can find it titled =sig=
264in the output of this command:
265
266#+begin_src sh
267gpg --list-signatures
268#+end_src
269
270** File Integrity
271
272When a person generates a signature for data, they are allowing users
273the ability to verify the signature on that data in the future to ensure
274the data has not been corrupted. This is most common with software
275applications hosted on the internet - developers provide signatures so
276that users can verify a website was not hijacked and download links
277replaced with dangerous software.
278
279In order to verify signed data, a user needs to have:
280
2811. The signed data
2822. A signature file
2833. The public GPG key of the signer
284
285Once the signer's public key is imported on the user's system, and they
286have the data and signature, they can verify the data with the following
287commands:
288
289#+begin_src sh
290# If the signature is attached to the data
291gpg --verify [signature-file]
292
293# If the signature is detached as a separate file from the data
294gpg --verify [signature-file] [original-file]
295#+end_src
296
297*** Finding Public Keys
298
299In order to use GPG with others, a user needs to know the other user(s)
300keys. This is easy to do if the user knows the other user(s) in person,
301but may be hard if the relationship is strictly digital. Luckily, there
302are a few options. The first option is to look at a user's web page or
303social pages if they have them.
304
305Otherwise, the best option is to use a keyserver, such as:
306
307- [[https://pgp.mit.edu][pgp.mit.edu]]
308- [[https://keys.openpgp.org][keys.openpgp.org]]
content/blog/2022-07-25-curseradio.md deleted −98
@@ -1,98 +0,0 @@
1+++
2date = 2022-07-25
3title = "CurseRadio: Listening to the Radio on the Command Line"
4description = ""
5draft = false
6+++
7
8# Overview
9
10While exploring some interesting Linux applications, I stumbled across
11[curseradio](https://github.com/chronitis/curseradio), a command-line radio
12player based on Python.
13
14This application is fantastic and incredibly easy to install, so I wanted to
15dedicate a post today to this app. Let's look at the features within the app and
16then walk through the installation process I took to get `curseradio` working.
17
18# Features
19
20The radio player itself is quite minimal. As you can see in the screenshot
21above, it contains a simple plaintext list of all available categories, which
22can be broken down further and further. In addition, radio shows are available
23for listening, alongside regular radio stations.
24
25For example, the `Sports` > `Pro Basketball` > `Shows` category contains a
26number of specific shows related to Professional Basketball.
27
28Aside from being able to play any of the listed stations/shows, you can make a
29channel your favorite by pressing `f`. It will now show up at the top of the
30radio player in the `Favourites` category.
31
32## Commands/Shortcuts
33
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
46## Dependencies
47
48Before installing `curseradio`, a handful of system and Python packages are
49required. To get started, install `python3`, `pip3`, and `mpv` on your system.
50In this example, I'm using Fedora Linux, which uses the `dnf` package manager.
51You may need to adjust this if you're using a different system.
52
53```sh
54sudo dnf install python3 pip3 mpv
55```
56
57Next, use `pip3` to install `requests`, `xdg`, and `lxml`:
58
59```sh
60pip3 install requests xdg lxml
61```
62
63## Repository Source Installation
64
65Once all the dependencies are installed, we can clone the source code and enter
66that directory:
67
68```sh
69git clone https://github.com/chronitis/curseradio && cd curseradio
70```
71
72Once you're within the `curseradio` directory, you can install the application
73with the provided `setup.py` script.
74
75```sh
76sudo python3 setup.py install
77```
78
79In my case, I ran into a few errors and needed to create the folders that
80curseradio wanted to use for its installation. If you don't get any errors, you
81can skip this and run the app.
82
83```sh
84sudo mkdir /usr/local/lib/python3.10/
85sudo mkdir /usr/local/lib/python3.10/site-packages/
86```
87
88```sh
89sudo chown -R $USER:$USER /usr/local/lib/python3.10/
90```
91
92# Run the Application
93
94Once fully installed without errors, you can run the application!
95
96```sh
97python3 /usr/local/bin/curseradio
98```
content/blog/2022-07-25-curseradio.org added +99
@@ -0,0 +1,99 @@
1#+date: <2022-07-25>
2#+title: CurseRadio: Listening to the Radio on the Command Line
3#+description:
4
5
6* Overview
7
8While exploring some interesting Linux applications, I stumbled across
9[[https://github.com/chronitis/curseradio][curseradio]], a command-line
10radio player based on Python.
11
12This application is fantastic and incredibly easy to install, so I
13wanted to dedicate a post today to this app. Let's look at the features
14within the app and then walk through the installation process I took to
15get =curseradio= working.
16
17* Features
18
19The radio player itself is quite minimal. As you can see in the
20screenshot above, it contains a simple plaintext list of all available
21categories, which can be broken down further and further. In addition,
22radio shows are available for listening, alongside regular radio
23stations.
24
25For example, the =Sports= > =Pro Basketball= > =Shows= category contains
26a number of specific shows related to Professional Basketball.
27
28Aside from being able to play any of the listed stations/shows, you can
29make a channel your favorite by pressing =f=. It will now show up at the
30top of the radio player in the =Favourites= category.
31
32** Commands/Shortcuts
33
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
46** Dependencies
47
48Before installing =curseradio=, a handful of system and Python packages
49are required. To get started, install =python3=, =pip3=, and =mpv= on
50your system. In this example, I'm using Fedora Linux, which uses the
51=dnf= package manager. You may need to adjust this if you're using a
52different system.
53
54#+begin_src sh
55sudo dnf install python3 pip3 mpv
56#+end_src
57
58Next, use =pip3= to install =requests=, =xdg=, and =lxml=:
59
60#+begin_src sh
61pip3 install requests xdg lxml
62#+end_src
63
64** Repository Source Installation
65
66Once all the dependencies are installed, we can clone the source code
67and enter that directory:
68
69#+begin_src sh
70git clone https://github.com/chronitis/curseradio && cd curseradio
71#+end_src
72
73Once you're within the =curseradio= directory, you can install the
74application with the provided =setup.py= script.
75
76#+begin_src sh
77sudo python3 setup.py install
78#+end_src
79
80In my case, I ran into a few errors and needed to create the folders
81that curseradio wanted to use for its installation. If you don't get any
82errors, you can skip this and run the app.
83
84#+begin_src sh
85sudo mkdir /usr/local/lib/python3.10/
86sudo mkdir /usr/local/lib/python3.10/site-packages/
87#+end_src
88
89#+begin_src sh
90sudo chown -R $USER:$USER /usr/local/lib/python3.10/
91#+end_src
92
93* Run the Application
94
95Once fully installed without errors, you can run the application!
96
97#+begin_src sh
98python3 /usr/local/bin/curseradio
99#+end_src
content/blog/2022-07-30-flac-to-opus.md → content/blog/2022-07-30-flac-to-opus.org renamed +68 −68
@@ -1,68 +1,68 @@
1+++ 1#+date: <2022-07-30>
2date = 2022-07-30 2#+title: Recursive Command-Line FLAC to Opus Conversion
3title = "Recursive Command-Line FLAC to Opus Conversion" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Converting FLAC to OPUS 5
6* Converting FLAC to OPUS
9 7
10I am currently rebuilding my music library from scratch so that I can 8I am currently rebuilding my music library from scratch so that I can
11effectively archive all the music I own in the [FLAC file 9effectively archive all the music I own in the
12format](https://en.wikipedia.org/wiki/FLAC), a lossless audio codec. 10[[https://en.wikipedia.org/wiki/FLAC][FLAC file format]], a lossless
11audio codec.
13 12
14However, streaming FLAC files outside the home can be difficult due to the size 13However, streaming FLAC files outside the home can be difficult due to
15of the files, especially if you're using a weak connection. 14the size of the files, especially if you're using a weak connection.
16 15
17So, in order to archive the music in a lossless format and still be able to 16So, in order to archive the music in a lossless format and still be able
18stream it easily, I opted to create a copy of my FLAC files in the [Opus audio 17to stream it easily, I opted to create a copy of my FLAC files in the
19codec](<https://en.wikipedia.org/wiki/Opus_(audio_format)>). This allows me to 18[[https://en.wikipedia.org/wiki/Opus_(audio_format)][Opus audio codec]].
20archive a quality, lossless version of the music and then point my streaming 19This allows me to archive a quality, lossless version of the music and
21service to the smaller, stream-ready version. 20then point my streaming service to the smaller, stream-ready version.
22 21
23## Dependencies 22** Dependencies
24 23
25The process I follow utilizes the `opus-tools` package in Ubuntu. Before 24The process I follow utilizes the =opus-tools= package in Ubuntu. Before
26proceeding, install the package: 25proceeding, install the package:
27 26
28```sh 27#+begin_src sh
29sudo apt install opus-tools 28sudo apt install opus-tools
30``` 29#+end_src
31 30
32If you want to use a different conversion method, such as `ffmpeg` or `avconv`, 31If you want to use a different conversion method, such as =ffmpeg= or
33simply install that package instead. 32=avconv=, simply install that package instead.
34 33
35## Conversion Process 34** Conversion Process
36 35
37The script I'm using is stored in my home directory, but feel free to create it 36The script I'm using is stored in my home directory, but feel free to
38wherever you want. It does not need to be in the same directory as your music 37create it wherever you want. It does not need to be in the same
39files. 38directory as your music files.
40 39
41```sh 40#+begin_src sh
42cd ~ && nano transform.sh 41cd ~ && nano transform.sh
43``` 42#+end_src
44 43
45Once you have your new bash script opened in an editor, go ahead and paste the 44Once you have your new bash script opened in an editor, go ahead and
46following logic into the script. 45paste the following logic into the script.
47 46
48You **MUST** edit the following variables in order for it to work: 47You *MUST* edit the following variables in order for it to work:
49 48
50- `source`: The source directory where your FLAC files are stored. 49- =source=: The source directory where your FLAC files are stored.
51- `dest`: The destination directory where you want the resulting Opus files to 50- =dest=: The destination directory where you want the resulting Opus
52 be stored. 51 files to be stored.
53 52
54You **MAY** want to edit the following variables to suit your needs: 53You *MAY* want to edit the following variables to suit your needs:
55 54
56- `filename`: If you are converting to a file format other than Opus, you'll 55- =filename=: If you are converting to a file format other than Opus,
57 need to edit this so that your resulting files have the correct filename 56 you'll need to edit this so that your resulting files have the correct
58 extension. 57 filename extension.
59- `reldir`: This variable can be edited to strip out more leading directories 58- =reldir=: This variable can be edited to strip out more leading
60 in the file path. As you'll see later, I ignore this for now and simply 59 directories in the file path. As you'll see later, I ignore this for
61 clean it up afterward. 60 now and simply clean it up afterward.
62- `opusenc`: This is the actual conversion process. You may want to edit the 61- =opusenc=: This is the actual conversion process. You may want to edit
63 bitrate to suit your needs. I set mine at 128 but some prefer 160 or higher. 62 the bitrate to suit your needs. I set mine at 128 but some prefer 160
63 or higher.
64 64
65```sh 65#+begin_src sh
66#!/bin/bash 66#!/bin/bash
67## - The IFS takes care of spaces in file and dirnames 67## - The IFS takes care of spaces in file and dirnames
68## - your folders may vary 68## - your folders may vary
@@ -124,51 +124,51 @@ opusenc --vbr --bitrate 128 --date "$DATE" \
124## just for testing 124## just for testing
125# sleep 1 125# sleep 1
126done 126done
127``` 127#+end_src
128 128
129Once you're done, simply save the file and exit your editor. Don't forget to 129Once you're done, simply save the file and exit your editor. Don't
130enable execution of the script: 130forget to enable execution of the script:
131 131
132```sh 132#+begin_src sh
133chmod +x transform.sh 133chmod +x transform.sh
134``` 134#+end_src
135 135
136Finally, you may now run the script: 136Finally, you may now run the script:
137 137
138```sh 138#+begin_src sh
139./transform.sh 139./transform.sh
140``` 140#+end_src
141 141
142If you used `opusenc`, you'll see the conversions happen within the terminal as 142If you used =opusenc=, you'll see the conversions happen within the
143it progresses. You will also see variables printed if you uncommented any of the 143terminal as it progresses. You will also see variables printed if you
144bash script's comments. 144uncommented any of the bash script's comments.
145 145
146## Cleanup 146** Cleanup
147 147
148As I noted above, I didn't customize my `reldir` variable in the script, which 148As I noted above, I didn't customize my =reldir= variable in the script,
149caused my output directory to be `/mnt/music/library/archives` instead of 149which caused my output directory to be =/mnt/music/library/archives=
150`/mnt/music/library`. So, I moved the output up one level and deleted the 150instead of =/mnt/music/library=. So, I moved the output up one level and
151accidental directory. 151deleted the accidental directory.
152 152
153```sh 153#+begin_src sh
154cd /mnt/music/library 154cd /mnt/music/library
155mv archives/** . 155mv archives/** .
156rm -rf archives 156rm -rf archives
157``` 157#+end_src
158 158
159## Check the Resulting Size 159** Check the Resulting Size
160 160
161If you want to see what kind of file size savings you've gained, you can always 161If you want to see what kind of file size savings you've gained, you can
162use the `du` command to check: 162always use the =du= command to check:
163 163
164```sh 164#+begin_src sh
165cd /mnt/music 165cd /mnt/music
166du -h --max-depth=1 . 166du -h --max-depth=1 .
167``` 167#+end_src
168 168
169In my case, my small library went from 78GB to 6.3GB! 169In my case, my small library went from 78GB to 6.3GB!
170 170
171```txt 171#+begin_src txt
17278G ./archives 17278G ./archives
1736.3G ./library 1736.3G ./library
174``` 174#+end_src
content/blog/2022-07-31-bash-it.md deleted −237
@@ -1,237 +0,0 @@
1+++
2date = 2022-07-31
3title = "Upgrade Bash with Bash-It and Ble.sh"
4description = ""
5draft = false
6+++
7
8# Bash
9
10For those who are not familiar,
11[Bash](<https://en.wikipedia.org/wiki/Bash_(Unix_shell)>) is a Unix shell that
12is used as the default login shell for most Linux distributions. This shell and
13command processor should be familiar if you've used Linux (or older version of
14macOS) before.
15
16However, bash is not the only option. There are numerous other shells that
17exist. Here are some popular examples:
18
19- [zsh](https://en.wikipedia.org/wiki/Z_shell)
20- [fish](<https://en.wikipedia.org/wiki/Fish_(Unix_shell)>)
21- [oksh](https://github.com/ibara/oksh)
22- [mksh](https://wiki.gentoo.org/wiki/Mksh)
23- [dash](https://en.wikipedia.org/wiki/Debian_Almquist_shell)
24
25While each shell has its differences, bash is POSIX compliant and the default
26for many Linux users. Because of this, I am going to explore a program called
27`bash-it` below that helps bash users increase the utility of their shell
28without installing a completely new shell.
29
30## Installation
31
32First, if bash is not already installed on your system, you can [download bash
33from GNU](https://www.gnu.org/software/bash/) or use your package manager to
34install it.
35
36For example, this is how you can install bash on Fedora Linux:
37
38```sh
39sudo dnf install bash
40```
41
42If you are not using bash as your default shell, use the `chsh` command to
43change your shell:
44
45```sh
46chsh
47```
48
49You should see a prompt like the one below. If the brackets (`[]`) contain
50`bash` already, you're done, and you can simply continue by hitting the Enter
51key.
52
53If the brackets contain another shell path (e.g. `/usr/bin/zsh`), enter the path
54to the bash program on your system (it's most likely located at
55`/usr/bin/bash`).
56
57```sh
58Changing shell for <user>.
59New shell [/usr/bin/bash]:
60```
61
62You must log out or restart the machine in order for the login shell to be
63refreshed. You can do it now or wait until you're finished customizing the
64shell.
65
66```sh
67sudo reboot now
68```
69
70# Bash-it
71
72As noted on the [Bash-it](https://github.com/Bash-it/bash-it) repository:
73
74> Bash-it is a collection of community Bash commands and scripts for Bash 3.2+.
75> (And a shameless ripoff of oh-my-zsh 😃)
76
77Bash-it makes it easy to install plugins, set up aliases for common commands,
78and easily change the visual theme of your shell.
79
80## Installation
81
82To install the framework, simply copy the repository files and use the
83`install.sh` script provided. If you want, you can (and should!) inspect the
84contents of the installation script before you run it.
85
86```sh
87git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it
88~/.bash_it/install.sh
89```
90
91If you didn't restart your session after making bash the default, and are
92currently working within another shell, be sure to enter a bash session before
93using `bash-it`:
94
95```sh
96bash
97```
98
99## Aliases
100
101Bash-it contains a number of aliases for common commands to help improve
102efficiency in the terminal. To list all available options, use the following
103command:
104
105```sh
106bash-it show aliases
107```
108
109This will provide you a list that looks like the following text block. Within
110this screen, you will be able to see all available options and which ones are
111currently enabled.
112
113```txt
114Alias Enabled? Description
115ag [ ] the silver searcher (ag) aliases
116ansible [ ] ansible abbreviations
117apt [ ] Apt and dpkg aliases for Ubuntu and Debian distros.
118atom [ ] Atom.io editor abbreviations
119bash-it [ ] Aliases for the bash-it command (these aliases are automatically included with the "general" aliases)
120bolt [ ] puppet bolt aliases
121bundler [ ] ruby bundler
122clipboard [ ] xclip shortcuts
123composer [ ] common composer abbreviations
124curl [x] Curl aliases for convenience.
125...
126```
127
128To enable an alias, do:
129
130```sh
131bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all
132```
133
134To disable an alias, do:
135
136```sh
137bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all
138```
139
140## Plugins
141
142Similar to aliases, plugins are available with bash-it. You can find a complete
143list of plugins in the same way as aliases. Simply execute the following:
144
145```sh
146bash-it show plugins
147```
148
149You will see the following output showing enabled and disabled plugins:
150
151```txt
152Plugin Enabled? Description
153alias-completion [ ]
154autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details
155aws [ ] AWS helper functions
156base [x] miscellaneous tools
157basher [ ] initializes basher, the shell package manager
158battery [x] display info about your battery charge level
159blesh [ ] load ble.sh, the Bash line editor!
160boot2docker [ ] Helpers to get Docker setup correctly for boot2docker
161browser [ ] render commandline output in your browser
162```
163
164To enable a plugin, do:
165
166```sh
167bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all
168```
169
170To disable a plugin, do:
171
172```sh
173bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all
174```
175
176## Themes
177
178There are quite a few pre-defined
179[themes](https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes)
180available with bash-it.
181
182To list all themes:
183
184```sh
185ls ~/.bash_it/themes/
186```
187
188To use a new theme, you'll need to edit `.bashrc` and alter the `BASH_IT_THEME`
189variable to your desired theme. For example, I am using the `zork` theme.
190
191```sh
192nano ~/.bashrc
193```
194
195```sh
196export BASH_IT_THEME='zork'
197```
198
199Once you save your changes, you just need to exit your terminal and create a new
200one in order to see your changes to the `.bashrc` file. You can also `source`
201the file to see changes, but I recommend starting a completely new shell
202instead.
203
204### ble.sh
205
206One big feature I was missing in Bash that both `zsh` and `fish` have is an
207autosuggestion feature. To explain: as you type, an autosuggestion feature in
208the shell will offer suggestions in a lighter font color beyond the characters
209already typed. Once you see the command you want, you can click the right arrow
210and have the shell auto-complete that line for you.
211
212Luckily, the [Bash Line Editor](https://github.com/akinomyoga/ble.sh) (ble.sh)
213exists! This program provides a wonderful autosuggestions feature perfectly,
214among other features that I haven't tested yet.
215
216In order to install ble.sh, execute the following:
217
218```sh
219git clone --recursive https://github.com/akinomyoga/ble.sh.git
220make -C ble.sh install PREFIX=~/.local
221echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc
222```
223
224Again, exit the terminal and open a new one in order to see the newly-configured
225shell.
226
227# Restart the Session
228
229Finally, as mentioned above, you'll need to restart the session to ensure that
230your user is using bash by default.
231
232You will also need to exit and re-open a shell (e.g., terminal or terminal tab)
233any time you make changes to the `.bashrc` file.
234
235```sh
236sudo reboot now
237```
content/blog/2022-07-31-bash-it.org added +241
@@ -0,0 +1,241 @@
1#+date: <2022-07-31>
2#+title: Upgrade Bash with Bash-It and Ble.sh
3#+description:
4
5
6* Bash
7
8For those who are not familiar,
9[[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][Bash]] is a Unix
10shell that is used as the default login shell for most Linux
11distributions. This shell and command processor should be familiar if
12you've used Linux (or older version of macOS) before.
13
14However, bash is not the only option. There are numerous other shells
15that exist. Here are some popular examples:
16
17- [[https://en.wikipedia.org/wiki/Z_shell][zsh]]
18- [[https://en.wikipedia.org/wiki/Fish_(Unix_shell)][fish]]
19- [[https://github.com/ibara/oksh][oksh]]
20- [[https://wiki.gentoo.org/wiki/Mksh][mksh]]
21- [[https://en.wikipedia.org/wiki/Debian_Almquist_shell][dash]]
22
23While each shell has its differences, bash is POSIX compliant and the
24default for many Linux users. Because of this, I am going to explore a
25program called =bash-it= below that helps bash users increase the
26utility of their shell without installing a completely new shell.
27
28** Installation
29
30First, if bash is not already installed on your system, you can
31[[https://www.gnu.org/software/bash/][download bash from GNU]] or use
32your package manager to install it.
33
34For example, this is how you can install bash on Fedora Linux:
35
36#+begin_src sh
37sudo dnf install bash
38#+end_src
39
40If you are not using bash as your default shell, use the =chsh= command
41to change your shell:
42
43#+begin_src sh
44chsh
45#+end_src
46
47You should see a prompt like the one below. If the brackets (=[]=)
48contain =bash= already, you're done, and you can simply continue by
49hitting the Enter key.
50
51If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter
52the path to the bash program on your system (it's most likely located at
53=/usr/bin/bash=).
54
55#+begin_src sh
56Changing shell for <user>.
57New shell [/usr/bin/bash]:
58#+end_src
59
60You must log out or restart the machine in order for the login shell to
61be refreshed. You can do it now or wait until you're finished
62customizing the shell.
63
64#+begin_src sh
65sudo reboot now
66#+end_src
67
68* Bash-it
69
70As noted on the [[https://github.com/Bash-it/bash-it][Bash-it]]
71repository:
72
73#+begin_quote
74Bash-it is a collection of community Bash commands and scripts for Bash
753.2+. (And a shameless ripoff of oh-my-zsh 😃)
76#+end_quote
77
78Bash-it makes it easy to install plugins, set up aliases for common
79commands, and easily change the visual theme of your shell.
80
81** Installation
82
83To install the framework, simply copy the repository files and use the
84=install.sh= script provided. If you want, you can (and should!) inspect
85the contents of the installation script before you run it.
86
87#+begin_src sh
88git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it
89~/.bash_it/install.sh
90#+end_src
91
92If you didn't restart your session after making bash the default, and
93are currently working within another shell, be sure to enter a bash
94session before using =bash-it=:
95
96#+begin_src sh
97bash
98#+end_src
99
100** Aliases
101
102Bash-it contains a number of aliases for common commands to help improve
103efficiency in the terminal. To list all available options, use the
104following command:
105
106#+begin_src sh
107bash-it show aliases
108#+end_src
109
110This 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
112which ones are currently enabled.
113
114#+begin_src txt
115Alias Enabled? Description
116ag [ ] the silver searcher (ag) aliases
117ansible [ ] ansible abbreviations
118apt [ ] Apt and dpkg aliases for Ubuntu and Debian distros.
119atom [ ] Atom.io editor abbreviations
120bash-it [ ] Aliases for the bash-it command (these aliases are automatically included with the "general" aliases)
121bolt [ ] puppet bolt aliases
122bundler [ ] ruby bundler
123clipboard [ ] xclip shortcuts
124composer [ ] common composer abbreviations
125curl [x] Curl aliases for convenience.
126...
127#+end_src
128
129To enable an alias, do:
130
131#+begin_src sh
132bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all
133#+end_src
134
135To disable an alias, do:
136
137#+begin_src sh
138bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all
139#+end_src
140
141** Plugins
142
143Similar 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
145following:
146
147#+begin_src sh
148bash-it show plugins
149#+end_src
150
151You will see the following output showing enabled and disabled plugins:
152
153#+begin_src txt
154Plugin Enabled? Description
155alias-completion [ ]
156autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details
157aws [ ] AWS helper functions
158base [x] miscellaneous tools
159basher [ ] initializes basher, the shell package manager
160battery [x] display info about your battery charge level
161blesh [ ] load ble.sh, the Bash line editor!
162boot2docker [ ] Helpers to get Docker setup correctly for boot2docker
163browser [ ] render commandline output in your browser
164#+end_src
165
166To enable a plugin, do:
167
168#+begin_src sh
169bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all
170#+end_src
171
172To disable a plugin, do:
173
174#+begin_src sh
175bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all
176#+end_src
177
178** Themes
179
180There are quite a few pre-defined
181[[https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes][themes]]
182available with bash-it.
183
184To list all themes:
185
186#+begin_src sh
187ls ~/.bash_it/themes/
188#+end_src
189
190To use a new theme, you'll need to edit =.bashrc= and alter the
191=BASH_IT_THEME= variable to your desired theme. For example, I am using
192the =zork= theme.
193
194#+begin_src sh
195nano ~/.bashrc
196#+end_src
197
198#+begin_src sh
199export BASH_IT_THEME='zork'
200#+end_src
201
202Once you save your changes, you just need to exit your terminal and
203create a new one in order to see your changes to the =.bashrc= file. You
204can also =source= the file to see changes, but I recommend starting a
205completely new shell instead.
206
207*** ble.sh
208
209One big feature I was missing in Bash that both =zsh= and =fish= have is
210an autosuggestion feature. To explain: as you type, an autosuggestion
211feature in the shell will offer suggestions in a lighter font color
212beyond the characters already typed. Once you see the command you want,
213you can click the right arrow and have the shell auto-complete that line
214for you.
215
216Luckily, the [[https://github.com/akinomyoga/ble.sh][Bash Line Editor]]
217(ble.sh) exists! This program provides a wonderful autosuggestions
218feature perfectly, among other features that I haven't tested yet.
219
220In order to install ble.sh, execute the following:
221
222#+begin_src sh
223git clone --recursive https://github.com/akinomyoga/ble.sh.git
224make -C ble.sh install PREFIX=~/.local
225echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc
226#+end_src
227
228Again, exit the terminal and open a new one in order to see the
229newly-configured shell.
230
231* Restart the Session
232
233Finally, as mentioned above, you'll need to restart the session to
234ensure that your user is using bash by default.
235
236You will also need to exit and re-open a shell (e.g., terminal or
237terminal tab) any time you make changes to the =.bashrc= file.
238
239#+begin_src sh
240sudo reboot now
241#+end_src
content/blog/2022-08-31-privacy-com-changes.md deleted −92
@@ -1,92 +0,0 @@
1+++
2date = 2022-08-31
3title = "Concerning Changes on Privacy.com"
4description = ""
5draft = false
6+++
7
8# Privacy.com Changes Their Terms
9
10Recently, Privacy.com reached out to their customers regarding a change in their
11terms of use. Further, all customers are required to agree to the changes in
12order to continue using their accounts.
13
14[You can view the new cardholder agreement
15here](https://privacy.com/commercial-cardholder-agreement).
16
17When you log in, you'll be greeted with a pop-up window asking you to review and
18agree to the new terms of use. You will also not be able to open any new cards
19until the terms are agreed to.
20
21## Changing from a "Prepaid Debit" Model to a "Charge Card" Model
22
23The actual content of the changes is interesting. While the historical model of
24using Privacy.com was akin to prepaid debit cards, the new model is very similar
25to a credit card (they use the term "charge card").
26
27I have used Privacy.com for 1-2 years, and the process was always that you would
28create a single-use or merchant-locked card. This card could be used for any
29dollar limit you set and would immediately draw the funds from whatever funding
30source you connected, e.g. PayPal account or a bank account.
31
32The benefit this service provides with a premium account is masking the merchant
33names from your funding source. If you have a concern that your bank account
34uses merchant data from your account, you could direct all charges through
35Privacy.com and set the merchant as one of their pre-set options, such as
36"Smiley's Corner Store" or "NSA Gift Shop."
37
38The new model still works with a bank account as a funding source, but the model
39is changed so that you get a "line of credit" set according to a 14-day billing
40cycle. It seems that Privacy.com will now allow charges to be incurred without
41being immediately paid.
42
43## Daily Payments and Available Credit
44
45Instead of paying as charges are incurred, you must make a "Daily Payment" and
46your "Available Credit" will be locked until you make that payment. There are
47also "End of Billing Cycle Payments" that are assigned a due date.
48
49Further, Privacy.com will decline charges that would cause you to exceed your
50Available Credit or Credit Limit.
51
52One particular interesting section states the following:
53
54> YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER OBLIGATIONS
55> YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT PAY US BACK FOR ANY
56> AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE WILL EXERCISE OUR INTEREST
57> AND DEBIT THE SECURED ACCOUNT, AND YOU WILL LOSE THE MONEY IN THE SECURED
58> ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT FOR MORE INFORMATION.
59
60## Personal Information
61
62Now that Privacy.com is more of a financial institution, they are obligated to
63comply with the [know your
64customer](https://en.wikipedia.org/wiki/Know_your_customer) guidelines/laws.
65
66I did not proceed with the change to my Privacy.com account, but I have heard
67from some peers that the changes require more personal information to be
68submitted, such as SSN. I am not aware of all new personal information required
69or if the funding source is now required to only be a bank account.
70
71## Fees
72
73Luckily, the fees section did not change much. The subscription fees for a
74premium account are still the only fees.
75
76# My Thoughts
77
78Personally, I wiped my personal information from my account and then permanently
79deleted it when I heard about these changes. I have no interest in yet another
80method of credit lending offered by private companies. While I accepted that
81they would have access to my bank account information for the purposes of paying
82off my prepaid debit payments, I have no interest in incurring charges that will
83need to be paid back at a later date. I also have no interest in submitting
84personal information to Privacy.com.
85
86This type of change toward a "buy it now, pay us later" model is concerning, and
87I will be watching Privacy.com to see if they further their interests in the
88credit model as time goes on.
89
90Could we see them start charging interest, fees, etc.? I'm not sure, but this
91change does not inspire confidence in their mission as a privacy-focused
92company.
content/blog/2022-08-31-privacy-com-changes.org added +99
@@ -0,0 +1,99 @@
1#+date: <2022-08-31>
2#+title: Concerning Changes on Privacy.com
3#+description:
4
5
6* Privacy.com Changes Their Terms
7
8Recently, Privacy.com reached out to their customers regarding a change
9in their terms of use. Further, all customers are required to agree to
10the changes in order to continue using their accounts.
11
12[[https://privacy.com/commercial-cardholder-agreement][You can view the
13new cardholder agreement here]].
14
15When you log in, you'll be greeted with a pop-up window asking you to
16review and agree to the new terms of use. You will also not be able to
17open any new cards until the terms are agreed to.
18
19** Changing from a "Prepaid Debit" Model to a "Charge Card" Model
20
21The actual content of the changes is interesting. While the historical
22model of using Privacy.com was akin to prepaid debit cards, the new
23model is very similar to a credit card (they use the term "charge
24card").
25
26I have used Privacy.com for 1-2 years, and the process was always that
27you would create a single-use or merchant-locked card. This card could
28be used for any dollar limit you set and would immediately draw the
29funds from whatever funding source you connected, e.g. PayPal account or
30a bank account.
31
32The benefit this service provides with a premium account is masking the
33merchant names from your funding source. If you have a concern that your
34bank account uses merchant data from your account, you could direct all
35charges through Privacy.com and set the merchant as one of their pre-set
36options, such as "Smiley's Corner Store" or "NSA Gift Shop."
37
38The new model still works with a bank account as a funding source, but
39the model is changed so that you get a "line of credit" set according to
40a 14-day billing cycle. It seems that Privacy.com will now allow charges
41to be incurred without being immediately paid.
42
43** Daily Payments and Available Credit
44
45Instead of paying as charges are incurred, you must make a "Daily
46Payment" and your "Available Credit" will be locked until you make that
47payment. There are also "End of Billing Cycle Payments" that are
48assigned a due date.
49
50Further, Privacy.com will decline charges that would cause you to exceed
51your Available Credit or Credit Limit.
52
53One particular interesting section states the following:
54
55#+begin_quote
56YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER
57OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT
58PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE
59WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL
60LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT
61FOR MORE INFORMATION.
62#+end_quote
63
64** Personal Information
65
66Now that Privacy.com is more of a financial institution, they are
67obligated to comply with the
68[[https://en.wikipedia.org/wiki/Know_your_customer][know your customer]]
69guidelines/laws.
70
71I did not proceed with the change to my Privacy.com account, but I have
72heard from some peers that the changes require more personal information
73to be submitted, such as SSN. I am not aware of all new personal
74information required or if the funding source is now required to only be
75a bank account.
76
77** Fees
78
79Luckily, the fees section did not change much. The subscription fees for
80a premium account are still the only fees.
81
82* My Thoughts
83
84Personally, I wiped my personal information from my account and then
85permanently deleted it when I heard about these changes. I have no
86interest in yet another method of credit lending offered by private
87companies. While I accepted that they would have access to my bank
88account information for the purposes of paying off my prepaid debit
89payments, I have no interest in incurring charges that will need to be
90paid back at a later date. I also have no interest in submitting
91personal information to Privacy.com.
92
93This type of change toward a "buy it now, pay us later" model is
94concerning, and I will be watching Privacy.com to see if they further
95their interests in the credit model as time goes on.
96
97Could we see them start charging interest, fees, etc.? I'm not sure, but
98this change does not inspire confidence in their mission as a
99privacy-focused company.
content/blog/2022-09-17-serenity-os.md deleted −104
@@ -1,104 +0,0 @@
1+++
2date = 2022-09-17
3title = "Serenity OS: Testing Out a Unique System"
4description = ""
5draft = false
6+++
7
8# Overview
9
10[SerenityOS](https://serenityos.org) is a unique operating system (OS) that I
11have seen pop up in my news feed a few times over the last few years, but I have
12never had time to test it out until now.
13
14Testing out this system brought back fond memories of yellowed,
15modem-screeching, 100-pound computers that brought so many fond memories to my
16youth.
17
18Per their website:
19
20> A graphical Unix-like operating system for desktop computers!
21>
22> SerenityOS is a love letter to '90s user interfaces with a custom Unix-like
23> core. It flatters with sincerity by stealing beautiful ideas from various
24> other systems.
25>
26> Roughly speaking, the goal is a marriage between the aesthetic of late-1990s
27> productivity software and the power-user accessibility of late-2000s \*nix.
28>
29> This is a system by us, for us, based on the things we like.
30
31# Building
32
33Your first question may be "Where's the iso?" and the answer is... there are
34none. SerenityOS does not provide pre-built images for testing. You must build
35the images yourself. This seems intentionally to limit participation to only
36those who are truly interested enough to learn how to build the OS.
37
38## Clone
39
40In order to get started, you'll need to clone the source repository:
41
42```sh
43git clone https://github.com/SerenityOS/serenity && cd serenity
44```
45
46## Build
47
48Note that I followed the [Build
49Instructions](https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md)
50in the SerenityOS repository as of commit
51`660d2b53b1206e868d5470eee80b5e62d7e30da7`. Things may have changed since my
52installation, and you should double-check the instructions first.
53
54Regardless, I want to repeat my steps here to illustrate any errors or differing
55commands I needed to run in order to build and run SerenityOS.
56
57Since I am running Fedora, I needed to install these packages in order to build
58the OS images:
59
60```sh
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
62```
63
64Next, make sure you're inside the `serenity` directory created earlier during
65the git cloning process and process to build the toolchain:
66
67```sh
68Meta/serenity.sh rebuild-toolchain
69```
70
71Once the toolchain is built, you can Build and run the OS!
72
73```sh
74Meta/serenity.sh run
75```
76
77After this process is completed, the image should run automatically and launch.
78
79# Issues
80
81I played around in SerenityOS for an hour or two in order to see what I could do
82and had a lot of fun with it. The only issue I ran into was a lack of working
83internet. I didn't try very hard, but I could tell that the main network link
84wasn't connecting to my Fedora host properly.
85
86# Screenshots
87
88The initial launch of the image displays the SerenityOS desktop, with a simple
89terminal already launched.
90
91There is also a "Fire" application (literally just shows fire burning), a
92browser with the local Serenity Browser page loaded, and a text editor.
93
94I also poked around the system utilities and found most tools you'd expect to
95find within a standard desktop, such as a system monitoring tool.
96
97Lastly, I noted that the default desktop contains numerous pre-defined themes to
98choose from. This is a small piece, but it's actually wonderful to see desktop
99developers consider theming directly out of the box rather than using an
100addon-based mentality.
101
102I didn't take a screenshot of the other pre-installed games, but I did spend
103nearly 30 minutes playing Solitaire before remembering that I was supposed to be
104writing a post about the OS.
content/blog/2022-09-17-serenity-os.org added +110
@@ -0,0 +1,110 @@
1#+date: <2022-09-17>
2#+title: Serenity OS: Testing Out a Unique System
3#+description:
4
5
6* Overview
7
8[[https://serenityos.org][SerenityOS]] is a unique operating system (OS)
9that I have seen pop up in my news feed a few times over the last few
10years, but I have never had time to test it out until now.
11
12Testing out this system brought back fond memories of yellowed,
13modem-screeching, 100-pound computers that brought so many fond memories
14to my youth.
15
16Per their website:
17
18#+begin_quote
19A graphical Unix-like operating system for desktop computers!
20
21SerenityOS is a love letter to '90s user interfaces with a custom
22Unix-like core. It flatters with sincerity by stealing beautiful ideas
23from various other systems.
24
25Roughly speaking, the goal is a marriage between the aesthetic of
26late-1990s productivity software and the power-user accessibility of
27late-2000s *nix.
28
29This is a system by us, for us, based on the things we like.
30#+end_quote
31
32* Building
33
34Your first question may be "Where's the iso?" and the answer is... there
35are none. SerenityOS does not provide pre-built images for testing. You
36must build the images yourself. This seems intentionally to limit
37participation to only those who are truly interested enough to learn how
38to build the OS.
39
40** Clone
41
42In order to get started, you'll need to clone the source repository:
43
44#+begin_src sh
45git clone https://github.com/SerenityOS/serenity && cd serenity
46#+end_src
47
48** Build
49
50Note that I followed the
51[[https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md][Build
52Instructions]] in the SerenityOS repository as of commit
53=660d2b53b1206e868d5470eee80b5e62d7e30da7=. Things may have changed
54since my installation, and you should double-check the instructions
55first.
56
57Regardless, I want to repeat my steps here to illustrate any errors or
58differing commands I needed to run in order to build and run SerenityOS.
59
60Since I am running Fedora, I needed to install these packages in order
61to build the OS images:
62
63#+begin_src sh
64sudo 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
65#+end_src
66
67Next, make sure you're inside the =serenity= directory created earlier
68during the git cloning process and process to build the toolchain:
69
70#+begin_src sh
71Meta/serenity.sh rebuild-toolchain
72#+end_src
73
74Once the toolchain is built, you can Build and run the OS!
75
76#+begin_src sh
77Meta/serenity.sh run
78#+end_src
79
80After this process is completed, the image should run automatically and
81launch.
82
83* Issues
84
85I 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
87lack 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.
89
90* Screenshots
91
92The initial launch of the image displays the SerenityOS desktop, with a
93simple terminal already launched.
94
95There is also a "Fire" application (literally just shows fire burning),
96a browser with the local Serenity Browser page loaded, and a text
97editor.
98
99I also poked around the system utilities and found most tools you'd
100expect to find within a standard desktop, such as a system monitoring
101tool.
102
103Lastly, I noted that the default desktop contains numerous pre-defined
104themes to choose from. This is a small piece, but it's actually
105wonderful to see desktop developers consider theming directly out of the
106box rather than using an addon-based mentality.
107
108I didn't take a screenshot of the other pre-installed games, but I did
109spend nearly 30 minutes playing Solitaire before remembering that I was
110supposed to be writing a post about the OS.
content/blog/2022-09-21-graphene-os.md deleted −159
@@ -1,159 +0,0 @@
1+++
2date = 2022-09-21
3title = "Installing Graphene OS on the Pixel 6 Pro"
4description = ""
5draft = false
6+++
7
8# Introduction
9
10After using iOS for a couple of years, I finally took the plunge and purchased a
11Pixel 6 Pro in order to test and use [GrapheneOS] (<https://grapheneos.org>).
12
13The installation process was rather quick once you have the tools and files you
14need. 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 platform
21tools](https://developer.android.com/studio/releases/platform-tools.html). Find
22the Linux download and save the ZIP folder to your preferred location.
23
24Once we've downloaded the files, we will need to unzip them, enter the
25directory, and move the necessary executables to a central location, such as
26`/usr/bin/`. For this installation, we only need the `fastboot` and `adb`
27executables.
28
29```sh
30cd ~/Downloads
31```
32
33```sh
34unzip platform-tools_r33.0.3-linux.zip
35cd platform-tools
36sudo mv fastboot /usr/bin/
37sudo mv adb /usr/bin
38```
39
40## GrapheneOS Files
41
42Next, we need the [GrapheneOS files](https://grapheneos.org/releases) for our
43device and model. For example, the Pixel 6 Pro is codenamed `raven` on the
44release page.
45
46Once we have the links, let's download them to our working directory:
47
48```sh
49curl -O https://releases.grapheneos.org/factory.pub
50curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
51curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
52```
53
541. Validate Integrity
55
56 In order to validate the integrity of the downloaded files, we will need the
57 `signify` package and Graphene's `factory.pub` file.
58
59 ```sh
60 sudo dnf install signify
61 ```
62
63 ```sh
64 curl -O https://releases.grapheneos.org/factory.pub
65 ```
66
67 Then we can validate the files and ensure that no data was corrupted or
68 modified before it was saved to our device.
69
70 ```sh
71 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
72 ```
73
742. Unzip Files
75
76 Once the files are verified, we can unzip the Graphene image and enter the
77 directory:
78
79 ```sh
80 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
81 ```
82
83# Installation Process
84
85## Enable Developer Debugging & OEM Unlock
86
87Before we can actually flash anything to the phone, we will need to enable OEM
88Unlocking, as well as either USB Debugging or Wireless Debugging, depending on
89which method we will be using.
90
91To start, enable developer mode by going to `Settings` > `About` and tapping
92`Build Number` seven (7) times. You may need to enter your PIN to enable this
93mode.
94
95Once developer mode is enabled, go to `Settings` > `System` > `Devloper Options`
96and enable OEM Unlocking, as well as USB or Wireless Debugging. In my case, I
97chose USB Debugging and performed all actions via USB cable.
98
99Once these options are enabled, plug the phone into the computer and execute the
100following command:
101
102```sh
103adb devices
104```
105
106If an unauthorized error occurs, make sure the USB mode on the phone is changed
107from charging to something like "File Transfer" or "PTP." You can find the USB
108mode in the notification tray.
109
110## Reboot Device
111
112Once we have found the device via `adb`, we can either boot into the bootloader
113interface by holding the volume down button while the phone reboots or by
114executing the following command:
115
116```sh
117adb reboot bootloader
118```
119
120## Unlock the Bootloader
121
122The phone will reboot and load the bootloader screen upon startup. At this
123point, we are ready to start the actual flashing of GrapheneOS onto the device.
124
125**NOTE**: In my situation, I needed to use `sudo` with every `fastboot` command,
126but not with `adb` commands. I am not sure if this is standard or a Fedora
127quirk, but I'm documenting my commands verbatim in this post.
128
129First, we start by unlocking the bootloader so that we can load other ROMs:
130
131```sh
132sudo fastboot flashing unlock
133```
134
135## Flashing Factory Images
136
137Once the phone is unlocked, we can flash it with the `flash-all.sh` script found
138inside the `raven-factory-2022091400` folder we entered earlier:
139
140```sh
141sudo ./flash-all.sh
142```
143
144This process should take a few minutes and will print informational messages as
145things progress. Avoid doing anything on the phone while this process is
146operating.
147
148## Lock the Bootloader
149
150If everything was successful, the phone should reboot a few times and finally
151land back on the bootloader screen. At this point, we can re-lock the bootloader
152to enable full verified boot and protect the device from unwanted flashing or
153erasure of data.
154
155```sh
156sudo fastboot flashing lock
157```
158
159Once done, the device will be wiped and ready for a fresh set-up!
content/blog/2022-09-21-graphene-os.org added +164
@@ -0,0 +1,164 @@
1#+date: <2022-09-21>
2#+title: Installing Graphene OS on the Pixel 6 Pro
3#+description:
4
5
6* Introduction
7
8After using iOS for a couple of years, I finally took the plunge and
9purchased a Pixel 6 Pro in order to test and use [GrapheneOS]
10([[https://grapheneos.org]]).
11
12The installation process was rather quick once you have the tools and
13files you need. Overall, it can be done in just a few minutes.
14
15* Gathering Tools & Files
16
17** Android Tools
18
19First, in order to interact with the device, we will need the
20[[https://developer.android.com/studio/releases/platform-tools.html][Android
21platform tools]]. Find the Linux download and save the ZIP folder to
22your preferred location.
23
24Once we've downloaded the files, we will need to unzip them, enter the
25directory, and move the necessary executables to a central location,
26such as =/usr/bin/=. For this installation, we only need the =fastboot=
27and =adb= executables.
28
29#+begin_src sh
30cd ~/Downloads
31#+end_src
32
33#+begin_src sh
34unzip platform-tools_r33.0.3-linux.zip
35cd platform-tools
36sudo mv fastboot /usr/bin/
37sudo mv adb /usr/bin
38#+end_src
39
40** GrapheneOS Files
41
42Next, we need the [[https://grapheneos.org/releases][GrapheneOS files]]
43for our device and model. For example, the Pixel 6 Pro is codenamed
44=raven= on the release page.
45
46Once we have the links, let's download them to our working directory:
47
48#+begin_src sh
49curl -O https://releases.grapheneos.org/factory.pub
50curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
51curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
52#+end_src
53
541. Validate Integrity
55
56 In order to validate the integrity of the downloaded files, we will
57 need the =signify= package and Graphene's =factory.pub= file.
58
59 #+begin_src sh
60 sudo dnf install signify
61 #+end_src
62
63 #+begin_src sh
64 curl -O https://releases.grapheneos.org/factory.pub
65 #+end_src
66
67 Then we can validate the files and ensure that no data was corrupted
68 or modified before it was saved to our device.
69
70 #+begin_src sh
71 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
72 #+end_src
73
742. Unzip Files
75
76 Once the files are verified, we can unzip the Graphene image and
77 enter the directory:
78
79 #+begin_src sh
80 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
81 #+end_src
82
83* Installation Process
84
85** Enable Developer Debugging & OEM Unlock
86
87Before we can actually flash anything to the phone, we will need to
88enable OEM Unlocking, as well as either USB Debugging or Wireless
89Debugging, depending on which method we will be using.
90
91To start, enable developer mode by going to =Settings= > =About= and
92tapping =Build Number= seven (7) times. You may need to enter your PIN
93to enable this mode.
94
95Once developer mode is enabled, go to =Settings= > =System= >
96=Devloper Options= and enable OEM Unlocking, as well as USB or Wireless
97Debugging. In my case, I chose USB Debugging and performed all actions
98via USB cable.
99
100Once these options are enabled, plug the phone into the computer and
101execute the following command:
102
103#+begin_src sh
104adb devices
105#+end_src
106
107If an unauthorized error occurs, make sure the USB mode on the phone is
108changed from charging to something like "File Transfer" or "PTP." You
109can find the USB mode in the notification tray.
110
111** Reboot Device
112
113Once we have found the device via =adb=, we can either boot into the
114bootloader interface by holding the volume down button while the phone
115reboots or by executing the following command:
116
117#+begin_src sh
118adb reboot bootloader
119#+end_src
120
121** Unlock the Bootloader
122
123The phone will reboot and load the bootloader screen upon startup. At
124this point, we are ready to start the actual flashing of GrapheneOS onto
125the device.
126
127*NOTE*: In my situation, I needed to use =sudo= with every =fastboot=
128command, but not with =adb= commands. I am not sure if this is standard
129or a Fedora quirk, but I'm documenting my commands verbatim in this
130post.
131
132First, we start by unlocking the bootloader so that we can load other
133ROMs:
134
135#+begin_src sh
136sudo fastboot flashing unlock
137#+end_src
138
139** Flashing Factory Images
140
141Once the phone is unlocked, we can flash it with the =flash-all.sh=
142script found inside the =raven-factory-2022091400= folder we entered
143earlier:
144
145#+begin_src sh
146sudo ./flash-all.sh
147#+end_src
148
149This process should take a few minutes and will print informational
150messages as things progress. Avoid doing anything on the phone while
151this process is operating.
152
153** Lock the Bootloader
154
155If everything was successful, the phone should reboot a few times and
156finally land back on the bootloader screen. At this point, we can
157re-lock the bootloader to enable full verified boot and protect the
158device from unwanted flashing or erasure of data.
159
160#+begin_src sh
161sudo fastboot flashing lock
162#+end_src
163
164Once done, the device will be wiped and ready for a fresh set-up!
content/blog/2022-10-04-mtp-linux.md deleted −78
@@ -1,78 +0,0 @@
1+++
2date = 2022-10-04
3title = "How to Mount an MTP Mobile Device on Fedora Linux"
4description = ""
5draft = false
6+++
7
8I recently ran into trouble attempting to mount my GrapheneOS phone to my laptop
9running Fedora Linux via the [Media Transfer
10Protocol](https://en.wikipedia.org/wiki/Media_transfer_protocol) (MTP) and
11discovered a simple and effective solution.
12
13# Use a USB 3.0 Port
14
15First, ensure that the device was plugged in to the laptop through a USB 3.0
16port, if possible. From a brief glance online, it seems that USB 2.0 ports may
17cause issues with dropped connections over MTP. This is purely anecdotal since I
18don't have any evidence to link showing that USB 2.0 causes issues, but I can
19confirm that switching to a USB 3.0 port seemed to cut out most of my issues.
20
21# Switch USB Preferences to MTP
22
23Secondly, you need to ensure that the phone's USB preferences/mode is changed to
24MTP or File Transfer once the phone is plugged in. Other modes will not allow
25you to access the phone's file system.
26
27# Install `jmtpfs`
28
29Next, I used the `jmtpfs` package to mount my phone to my laptop. There are
30other packages that exist, but this one worked perfectly for me. On Fedora
31Linux, you can install it like this:
32
33```sh
34sudo dnf install jmtpfs -y
35```
36
37# Create a Mount Point
38
39Once you have the package installed, you just need to create a folder for the
40device to use as a mount point. In my case, I used `/mnt/pixel`:
41
42```sh
43sudo mkdir /mnt/pixel
44sudo chown -R $USER:$USER /mnt/pixel
45```
46
47# Mount & Access the Phone's File System
48
49Finally, plug-in and mount the device, and you should be able to see all storage
50(internal and external) inside your new folder!
51
52```sh
53jmtpfs /mnt/pixel
54```
55
56The output should look something like this:
57
58```sh
59Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP).
60Android device detected, assigning default bug flags
61```
62
63Now you are mounted and can do anything you'd like with the device's files:
64
65```sh
66cd /mnt/pixel
67ls -lha
68```
69
70From here, you will be able to see any internal or external storage available on
71the device:
72
73```sh
74total 0
75drwxr-xr-x. 3 user user 0 Jan 1 1970 .
76drwxr-xr-x. 1 root root 10 Oct 4 13:29 ..
77drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage'
78```
content/blog/2022-10-04-mtp-linux.org added +78
@@ -0,0 +1,78 @@
1#+date: <2022-10-04>
2#+title: How to Mount an MTP Mobile Device on Fedora Linux
3#+description:
4
5
6I recently ran into trouble attempting to mount my GrapheneOS phone to
7my laptop running Fedora Linux via the
8[[https://en.wikipedia.org/wiki/Media_transfer_protocol][Media Transfer
9Protocol]] (MTP) and discovered a simple and effective solution.
10
11* Use a USB 3.0 Port
12
13First, ensure that the device was plugged in to the laptop through a USB
143.0 port, if possible. From a brief glance online, it seems that USB 2.0
15ports may cause issues with dropped connections over MTP. This is purely
16anecdotal since I don't have any evidence to link showing that USB 2.0
17causes issues, but I can confirm that switching to a USB 3.0 port seemed
18to cut out most of my issues.
19
20* Switch USB Preferences to MTP
21
22Secondly, you need to ensure that the phone's USB preferences/mode is
23changed to MTP or File Transfer once the phone is plugged in. Other
24modes will not allow you to access the phone's file system.
25
26* Install =jmtpfs=
27
28Next, I used the =jmtpfs= package to mount my phone to my laptop. There
29are other packages that exist, but this one worked perfectly for me. On
30Fedora Linux, you can install it like this:
31
32#+begin_src sh
33sudo dnf install jmtpfs -y
34#+end_src
35
36* Create a Mount Point
37
38Once you have the package installed, you just need to create a folder
39for the device to use as a mount point. In my case, I used =/mnt/pixel=:
40
41#+begin_src sh
42sudo mkdir /mnt/pixel
43sudo chown -R $USER:$USER /mnt/pixel
44#+end_src
45
46* Mount & Access the Phone's File System
47
48Finally, plug-in and mount the device, and you should be able to see all
49storage (internal and external) inside your new folder!
50
51#+begin_src sh
52jmtpfs /mnt/pixel
53#+end_src
54
55The output should look something like this:
56
57#+begin_src sh
58Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP).
59Android device detected, assigning default bug flags
60#+end_src
61
62Now you are mounted and can do anything you'd like with the device's
63files:
64
65#+begin_src sh
66cd /mnt/pixel
67ls -lha
68#+end_src
69
70From here, you will be able to see any internal or external storage
71available on the device:
72
73#+begin_src sh
74total 0
75drwxr-xr-x. 3 user user 0 Jan 1 1970 .
76drwxr-xr-x. 1 root root 10 Oct 4 13:29 ..
77drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage'
78#+end_src
content/blog/2022-10-04-syncthing.md deleted −169
@@ -1,169 +0,0 @@
1+++
2date = 2022-10-04
3title = "Syncthing: A Minimal Self-Hosted Cloud Storage Solution"
4description = ""
5draft = false
6+++
7
8# An Overview of Syncthing
9
10If you've been looking around the self-hosted cloud storage space for a while,
11you've undoubtedly run into someone suggesting
12[Syncthing](https://syncthing.net) as an option. However, it is an unusual
13alternative for those users out there who are used to having a centralized cloud
14server that serves as the "controller" of the data and interacts with clients on
15devices to fetch files.
16
17This post is a walkthrough of the Syncthing software, how I set up my personal
18storage, and some pros and cons of using the software.
19
20# Installing Syncthing
21
22To install Syncthing, visit the [Downloads](https://syncthing.net/downloads/)
23page or install via your device's package manager.
24
25## Server & Desktop
26
27You can install Syncthing on servers and desktops via the Downloads page linked
28above or via the command-line.
29
30For Debian-based distros:
31
32```sh
33sudo apt install syncthing
34```
35
36For Fedora-based distros:
37
38```sh
39sudo dnf install syncthing
40```
41
42## Mobile
43
44Syncthing for Android is available on
45[F-Droid](https://f-droid.org/packages/com.nutomic.syncthingandroid/) and
46[Google
47Play](https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid).
48Syncthing does not have an official iOS client, but there is a third-party
49client called [Möbius
50Sync](https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216).
51
52# How Does Syncthing Work?
53
54To start, I wanted to include the main marketing blurb from their website:
55
56> Syncthing is a continuous file synchronization program. It synchronizes files
57> between two or more computers in real time, safely protected from prying eyes.
58> Your data is your data alone, and you deserve to choose where it is stored,
59> whether it is shared with some third party, and how it's transmitted over the
60> internet.
61
62Let's break this apart and add in some other details to help explain what
63exactly Syncthing does in order to sync files between devices.
64
65## Local Syncthing Server(s)
66
67Syncthing syncs files between multiple devices by creating a local server on
68each device. These local servers handle a few different things, such as watching
69files and directories for changes, hosting an administrative GUI website, and
70authenticating with connected devices.
71
72You can also start, stop, and restart the Syncthing server via the command-line
73or web dashboard. If you're running Syncthing on a device with `systemd`, you
74can use the following commands:
75
76```sh
77sudo systemctl start syncthing@username.service
78sudo systemctl restart syncthing@username.service
79sudo systemctl stop syncthing@username.service
80```
81
82## Syncthing Dashboard
83
84This biggest part of Syncthing is the admin GUI website that runs on each device
85(note that mobile devices will use the Syncthing app rather than the web GUI).
86The admin GUI is available through the web browser on the local device that is
87running Syncthing - simply go to `http://localhost:8384` or
88`http://127.0.0.1:8384`. This web page is the place where you will change
89settings, add/modify synced files, and add/modify connected devices.
90
91Here's an example web GUI dashboard:
92
93![Syncthing
94Dashboard](https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png)
95
96## Remote Devices
97
98A cloud storage solution wouldn't be very useful if you aren't able to share
99data among various devices. Syncthing does this by sharing Device IDs to connect
100servers, and then by manually sharing Folders with devices that have been
101connected.
102
103For instance, if you have a laptop running Syncthing and then install the
104Syncthing mobile app on a phone, you could scan the laptop's QR code for Device
105ID and then accept the authentication on the laptop's dashboard. Next, you can
106use either device to select a folder for sharing and dictating which device
107should send, receive, or both.
108
109When you connect devices, you can set one device as an "Introducer," which can
110add devices from the introducer to the device list, for mutually shared folders.
111You can also configure Auto Accept, compression, rate limits, and more settings
112per device.
113
114# My Personal Cloud Storage Set-up
115
116Personally, I use a model similar to a traditional cloud storage service. I have
117a "centralized" server running 24/7 that acts as an Introducer for my Syncthing
118network. I think of this as my main storage and all other devices as tertiary
119client devices. I will likely add additional servers as backups as time goes on
120so that I don't have to rely on my laptop or phone as the only backups.
121
122Currently, I have one desktop and one mobile device connected to the network,
123both running intermittently as they are not powered-on 24/7.
124
125The initial set-up of the software was easy enough, but data transfer rates were
126incredibly slow for me due to the Wi-Fi. Instead, I plugged my laptop into the
127ethernet network that my server is on and manually copied my folders over to the
128server with `scp`. Once complete, Syncthing validated that all files were there
129and not missing, and it did not need to transfer any data through the WAN.
130
131As slow as the transfer was going, this probably saved me a few days of waiting
132for my ~100GB sync.
133
134# Pros & Cons
135
136I've put together a short list of pros and cons for Syncthing. I thought about
137my experiences with Nextcloud, WebDAV, proprietary services (Google Drive,
138iCloud, etc.), and privacy-focused cloud solutions (pCloud, Tresorit, etc.).
139
140**Pros:**
141
142- I've faced no data loss at all through my two-month trial run.
143- No third-parties store your data on their servers.
144- You have full control over your data and can take your data and leave at any
145 time.
146- It's possible to encrypt client-side easily with software like Cryptomator.
147- No proprietary clients or mounted volumes, just plain files and folders.
148
149**Cons:**
150
151- The learning curve is steeper than traditional cloud services and is focused
152 on a technical audience.
153- If a device needs to modify files in a Folder, the devices will need to sync
154 ALL files from the folder, which may be large. To avoid size restraints,
155 split large folders into smaller folders for syncing.
156- Syncing can be slow due to the clients/servers initially connecting or
157 re-connecting after sleeping.
158- Multiple personal devices are required and require the user to own or rent
159 them as no third-party servers are involved in the storage of data.
160
161Overall, I've had a great experience with Syncthing so far. I've had no data
162loss, syncing has been quick and easy when changes are made to files, device
163connections are reliable, and I love the freedom of controlling the clients and
164servers as I choose.
165
166Not to mention that I appreciate that I - or someone else - could pull the
167Syncthing [source code](https://github.com/syncthing) and continue
168development/support if the Syncthing Foundation decides to stop developing the
169software or sells the business.
content/blog/2022-10-04-syncthing.org added +179
@@ -0,0 +1,179 @@
1#+date: <2022-10-04>
2#+title: Syncthing: A Minimal Self-Hosted Cloud Storage Solution
3#+description:
4
5
6* An Overview of Syncthing
7
8If you've been looking around the self-hosted cloud storage space for a
9while, you've undoubtedly run into someone suggesting
10[[https://syncthing.net][Syncthing]] as an option. However, it is an
11unusual alternative for those users out there who are used to having a
12centralized cloud server that serves as the "controller" of the data and
13interacts with clients on devices to fetch files.
14
15This post is a walkthrough of the Syncthing software, how I set up my
16personal storage, and some pros and cons of using the software.
17
18* Installing Syncthing
19
20To install Syncthing, visit the
21[[https://syncthing.net/downloads/][Downloads]] page or install via your
22device's package manager.
23
24** Server & Desktop
25
26You can install Syncthing on servers and desktops via the Downloads page
27linked above or via the command-line.
28
29For Debian-based distros:
30
31#+begin_src sh
32sudo apt install syncthing
33#+end_src
34
35For Fedora-based distros:
36
37#+begin_src sh
38sudo dnf install syncthing
39#+end_src
40
41** Mobile
42
43Syncthing for Android is available on
44[[https://f-droid.org/packages/com.nutomic.syncthingandroid/][F-Droid]]
45and
46[[https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid][Google
47Play]]. Syncthing does not have an official iOS client, but there is a
48third-party client called
49[[https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216][Möbius
50Sync]].
51
52* How Does Syncthing Work?
53
54To start, I wanted to include the main marketing blurb from their
55website:
56
57#+begin_quote
58Syncthing is a continuous file synchronization program. It synchronizes
59files between two or more computers in real time, safely protected from
60prying eyes. Your data is your data alone, and you deserve to choose
61where it is stored, whether it is shared with some third party, and how
62it's transmitted over the internet.
63#+end_quote
64
65Let's break this apart and add in some other details to help explain
66what exactly Syncthing does in order to sync files between devices.
67
68** Local Syncthing Server(s)
69
70Syncthing syncs files between multiple devices by creating a local
71server on each device. These local servers handle a few different
72things, such as watching files and directories for changes, hosting an
73administrative GUI website, and authenticating with connected devices.
74
75You can also start, stop, and restart the Syncthing server via the
76command-line or web dashboard. If you're running Syncthing on a device
77with =systemd=, you can use the following commands:
78
79#+begin_src sh
80sudo systemctl start syncthing@username.service
81sudo systemctl restart syncthing@username.service
82sudo systemctl stop syncthing@username.service
83#+end_src
84
85** Syncthing Dashboard
86
87This biggest part of Syncthing is the admin GUI website that runs on
88each device (note that mobile devices will use the Syncthing app rather
89than the web GUI). The admin GUI is available through the web browser on
90the local device that is running Syncthing - simply go to
91=http://localhost:8384= or =http://127.0.0.1:8384=. This web page is the
92place where you will change settings, add/modify synced files, and
93add/modify connected devices.
94
95Here's an example web GUI dashboard:
96
97#+caption: Syncthing Dashboard
98[[https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png]]
99
100** Remote Devices
101
102A cloud storage solution wouldn't be very useful if you aren't able to
103share data among various devices. Syncthing does this by sharing Device
104IDs to connect servers, and then by manually sharing Folders with
105devices that have been connected.
106
107For instance, if you have a laptop running Syncthing and then install
108the Syncthing mobile app on a phone, you could scan the laptop's QR code
109for Device ID and then accept the authentication on the laptop's
110dashboard. Next, you can use either device to select a folder for
111sharing and dictating which device should send, receive, or both.
112
113When you connect devices, you can set one device as an "Introducer,"
114which can add devices from the introducer to the device list, for
115mutually shared folders. You can also configure Auto Accept,
116compression, rate limits, and more settings per device.
117
118* My Personal Cloud Storage Set-up
119
120Personally, I use a model similar to a traditional cloud storage
121service. I have a "centralized" server running 24/7 that acts as an
122Introducer for my Syncthing network. I think of this as my main storage
123and all other devices as tertiary client devices. I will likely add
124additional servers as backups as time goes on so that I don't have to
125rely on my laptop or phone as the only backups.
126
127Currently, I have one desktop and one mobile device connected to the
128network, both running intermittently as they are not powered-on 24/7.
129
130The initial set-up of the software was easy enough, but data transfer
131rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged
132my laptop into the ethernet network that my server is on and manually
133copied my folders over to the server with =scp=. Once complete,
134Syncthing validated that all files were there and not missing, and it
135did not need to transfer any data through the WAN.
136
137As slow as the transfer was going, this probably saved me a few days of
138waiting for my ~100GB sync.
139
140* Pros & Cons
141
142I've put together a short list of pros and cons for Syncthing. I thought
143about my experiences with Nextcloud, WebDAV, proprietary services
144(Google Drive, iCloud, etc.), and privacy-focused cloud solutions
145(pCloud, Tresorit, etc.).
146
147*Pros:*
148
149- I've faced no data loss at all through my two-month trial run.
150- No third-parties store your data on their servers.
151- You have full control over your data and can take your data and leave
152 at any time.
153- It's possible to encrypt client-side easily with software like
154 Cryptomator.
155- No proprietary clients or mounted volumes, just plain files and
156 folders.
157
158*Cons:*
159
160- The learning curve is steeper than traditional cloud services and is
161 focused on a technical audience.
162- If a device needs to modify files in a Folder, the devices will need
163 to sync ALL files from the folder, which may be large. To avoid size
164 restraints, split large folders into smaller folders for syncing.
165- Syncing can be slow due to the clients/servers initially connecting or
166 re-connecting after sleeping.
167- Multiple personal devices are required and require the user to own or
168 rent them as no third-party servers are involved in the storage of
169 data.
170
171Overall, I've had a great experience with Syncthing so far. I've had no
172data loss, syncing has been quick and easy when changes are made to
173files, device connections are reliable, and I love the freedom of
174controlling the clients and servers as I choose.
175
176Not to mention that I appreciate that I - or someone else - could pull
177the Syncthing [[https://github.com/syncthing][source code]] and continue
178development/support if the Syncthing Foundation decides to stop
179developing the software or sells the business.
content/blog/2022-10-22-alpine-linux.md deleted −266
@@ -1,266 +0,0 @@
1+++
2date = 2022-10-22
3title = "Alpine Linux: My New Server OS"
4description = ""
5draft = false
6+++
7
8# Alpine Linux
9
10[Alpine Linux](https://alpinelinux.org) is a very small distro, built on musl
11libc and busybox. It uses ash as the default shell, OpenRC as the init system,
12and apk as the package manager. According to their website, an Alpine container
13"requires no more than 8 MB and a minimal installation to disk requires around
14130 MB of storage." An actual bare metal machine is recommended to have 100 MB
15of RAM and 0-700 MB of storage space.
16
17Historically, I've used Ubuntu's minimal installation image as my server OS for
18the last five years. Ubuntu worked well and helped as my original server
19contained an nVidia GPU and no onboard graphics, so quite a few distros won't
20boot or install without a lot of tinkering.
21
22Alpine has given me a huge increase in performance across my Docker apps and
23Nginx websites. CPU load for the new server I'm using to test Alpine hovers
24around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @ 3.70GHz.
25
26The only services I haven't moved over to Alpine are Plex Media Server and
27Syncthing, which may increase CPU load quite a bit depending on how many streams
28are running.
29
30## Installation
31
32In terms of installation, Alpine has an incredibly useful
33[wiki](https://wiki.alpinelinux.org/wiki/Installation) that will guide a user
34throughout the installation and post-installation processes, as well as various
35other articles and guides.
36
37To install Alpine, find an appropriate [image to
38download](https://alpinelinux.org/downloads/) and flash it to a USB using
39software such as Rufus or Etcher. I opted to use the Standard image for my
40x86~64~ architecture.
41
42Once the USB is ready, plug it into the machine and reboot. Note that you may
43have to use a key such as `Esc` or `F1-12` to access the boot menu. The Alpine
44Linux terminal will load quickly and for a login.
45
46To log in to the installation image, use the `root` account; there is no
47password. Once logged-in, execute the setup command:
48
49```sh
50setup-alpine
51```
52
53The setup script will ask a series of questions to configure the system. Be sure
54to answer carefully or else you may have to re-configure the system after boot.
55
56- Keyboard Layout (Local keyboard language and usage mode, e.g., us and
57 variant of us-nodeadkeys.)
58- Hostname (The name for the computer.)
59- Network (For example, automatic IP address discovery with the "DHCP"
60 protocol.)
61- DNS Servers (Domain Name Servers to query. For privacy reasons, it is NOT
62 recommended to route every local request to servers like Google's 8.8.8.8.)
63- Timezone
64- Proxy (Proxy server to use for accessing the web. Use "none" for direct
65 connections to the internet.)
66- Mirror (From where to download packages. Choose the organization you trust
67 giving your usage patterns to.)
68- SSH (Secure SHell remote access server. "Openssh" is part of the default
69 install image. Use "none" to disable remote login, e.g. on laptops.)
70- NTP (Network Time Protocol client used for keeping the system clock in sync
71 with a time-server. Package "chrony" is part of the default install image.)
72- Disk Mode (Select between diskless (disk="none"), "data" or "sys", as
73 described above.)
74
75Once the setup script is finished, be sure to reboot the machine and remove the
76USB device.
77
78```sh
79reboot
80```
81
82## Post-Installation
83
84There are many things you can do once your Alpine Linux system is up and
85running, and it largely depends on what you'll use the machine for. I'm going to
86walk through my personal post-installation setup for my web server.
87
881. Upgrade the System
89
90 First, login as `root` in order to update and upgrade the system:
91
92 ```sh
93 apk -U upgrade
94 ```
95
962. Adding a User
97
98 I needed to add a user so that I don't need to log in as root. Note that if
99 you're used to using the `sudo` command, you will now need to use the `doas`
100 command on Alpine Linux.
101
102 ```sh
103 apk add doas
104 adduser <username>
105 adduser <username> wheel
106 ```
107
108 You can now log out and log back in using the newly-created user:
109
110 ```sh
111 exit
112 ```
113
1143. Enable Community Packages
115
116 In order to install more common packages that aren't found in the `main`
117 repository, you will need to enable the `community` repository:
118
119 ```sh
120 doas nano /etc/apk/repositories
121 ```
122
123 Uncomment the community line for whichever version of Alpine you're running:
124
125 ```sh
126 /media/usb/apks
127 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
128 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
129 #http://dl-cdn.alpinelinux.org/alpine/edge/main
130 #http://dl-cdn.alpinelinux.org/alpine/edge/community
131 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
132 ```
133
1344. Install Required Packages
135
136 Now that the community packages are available, you can install any packages
137 you need. In my case, I installed the web server packages I need for my
138 services:
139
140 ```sh
141 doas apk add nano nginx docker docker-compose ufw
142 ```
143
1445. SSH
145
146 If you didn't install OpenSSH as part of the installation, you can do so
147 now:
148
149 ```sh
150 doas apk add openssh
151 ```
152
153 Next, either create a new key or copy your SSH key to the server from your
154 current machines:
155
156 ```sh
157 # Create a new key
158 ssh-keygen
159 ```
160
161 If you need to copy an existing SSH key from a current machine:
162
163 ```sh
164 # Copy key from existing machines
165 ssh-copy-id <username>@<ip_address>
166 ```
167
1686. Firewall
169
170 Lastly, I installed `ufw` above as my firewall. To set up, default to deny
171 incoming and allow outgoing connections. Then selectively allow other ports
172 or apps as needed.
173
174 ```sh
175 doas ufw default deny incoming
176 doas ufw default allow outgoing
177 doas ufw allow SSH
178 doas ufw allow "WWW Full"
179 doas ufw allow 9418 # Git server port
180 ```
181
1827. Change Hostname
183
184 If you don't like the hostname set during installation, you just need to
185 edit two files. First, edit the simple hostname file:
186
187 ```sh
188 doas nano /etc/hostname
189 ```
190
191 ```sh
192 <hostname>
193 ```
194
195 Next, edit the `hosts` file:
196
197 ```sh
198 doas nano /etc/hosts
199 ```
200
201 ```sh
202 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
203 ::1 <hostname> <hostname>.local
204 ```
205
206# Nginx Web Server
207
208To set up my web server, I simply created the `www` user and created the
209necessary files.
210
211```sh
212doas adduser -D -g 'www' www
213mkdir /www
214doas mkdir /www
215doas chown -R www:www /var/lib/nginx/
216doas chown -R www:www /www
217```
218
219If you're running a simple webroot, you can alter the main `nginx.conf` file.
220Otherwise, you can drop configuration files in the following directory. You
221don't need to enable or symlink the configuration file like you do in other
222systems.
223
224```sh
225doas nano /etc/nginx/http.d/example_website.conf
226```
227
228Once the configuration is set and pointed at the `/www` directory to serve
229files, enable the Nginx service:
230
231```sh
232# Note that 'default' must be included or Nginx will not start on boot
233doas rc-update add nginx default
234```
235
236# Docker Containers
237
238Docker works exactly the same as other systems. Either execute a `docker run`
239command or create a `docker-compose.yml` file and do `docker-compose up -d`.
240
241# Git Server
242
243I went in-depth on how to self-host a git server in another post: [Self-Hosting
244a Personal Git Server](../git-server/).
245
246However, there are a few differences with Alpine. First note that in order to
247change the `git` user's shell, you must do a few things a little different:
248
249```sh
250doas apk add libuser
251doas touch /etc/login.defs
252doas mkdir /etc/default
253doas touch /etc/default/useradd
254doas lchsh git
255```
256
257# Thoughts on Alpine
258
259So far, I love Alpine Linux. I have no complaints about anything at this point,
260but I'm not completely finished with the migration yet. Once I'm able to upgrade
261my hardware to a rack-mounted server, I will migrate Plex and Syncthing over to
262Alpine as well - possibly putting Plex into a container or VM.
263
264The performance is stellar, the `apk` package manager is seamless, and system
265administration tasks are effortless. My only regret is that I didn't install
266Alpine sooner.
content/blog/2022-10-22-alpine-linux.org added +276
@@ -0,0 +1,276 @@
1#+date: <2022-10-22>
2#+title: Alpine Linux: My New Server OS
3#+description:
4
5
6* Alpine Linux
7
8[[https://alpinelinux.org][Alpine Linux]] is a very small distro, built
9on musl libc and busybox. It uses ash as the default shell, OpenRC as
10the init system, and apk as the package manager. According to their
11website, an Alpine container "requires no more than 8 MB and a minimal
12installation to disk requires around 130 MB of storage." An actual bare
13metal machine is recommended to have 100 MB of RAM and 0-700 MB of
14storage space.
15
16Historically, I've used Ubuntu's minimal installation image as my server
17OS for the last five years. Ubuntu worked well and helped as my original
18server contained an nVidia GPU and no onboard graphics, so quite a few
19distros won't boot or install without a lot of tinkering.
20
21Alpine has given me a huge increase in performance across my Docker apps
22and Nginx websites. CPU load for the new server I'm using to test Alpine
23hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @
243.70GHz.
25
26The only services I haven't moved over to Alpine are Plex Media Server
27and Syncthing, which may increase CPU load quite a bit depending on how
28many streams are running.
29
30** Installation
31
32In terms of installation, Alpine has an incredibly useful
33[[https://wiki.alpinelinux.org/wiki/Installation][wiki]] that will guide
34a user throughout the installation and post-installation processes, as
35well as various other articles and guides.
36
37To install Alpine, find an appropriate
38[[https://alpinelinux.org/downloads/][image to download]] and flash it
39to a USB using software such as Rufus or Etcher. I opted to use the
40Standard image for my x86_{64} architecture.
41
42Once the USB is ready, plug it into the machine and reboot. Note that
43you may have to use a key such as =Esc= or =F1-12= to access the boot
44menu. The Alpine Linux terminal will load quickly and for a login.
45
46To log in to the installation image, use the =root= account; there is no
47password. Once logged-in, execute the setup command:
48
49#+begin_src sh
50setup-alpine
51#+end_src
52
53The setup script will ask a series of questions to configure the system.
54Be sure to answer carefully or else you may have to re-configure the
55system after boot.
56
57- Keyboard Layout (Local keyboard language and usage mode, e.g., us and
58 variant of us-nodeadkeys.)
59- Hostname (The name for the computer.)
60- Network (For example, automatic IP address discovery with the "DHCP"
61 protocol.)
62- DNS Servers (Domain Name Servers to query. For privacy reasons, it is
63 NOT recommended to route every local request to servers like Google's
64 8.8.8.8.)
65- Timezone
66- Proxy (Proxy server to use for accessing the web. Use "none" for
67 direct connections to the internet.)
68- Mirror (From where to download packages. Choose the organization you
69 trust giving your usage patterns to.)
70- SSH (Secure SHell remote access server. "Openssh" is part of the
71 default install image. Use "none" to disable remote login, e.g. on
72 laptops.)
73- NTP (Network Time Protocol client used for keeping the system clock in
74 sync with a time-server. Package "chrony" is part of the default
75 install image.)
76- Disk Mode (Select between diskless (disk="none"), "data" or "sys", as
77 described above.)
78
79Once the setup script is finished, be sure to reboot the machine and
80remove the USB device.
81
82#+begin_src sh
83reboot
84#+end_src
85
86** Post-Installation
87
88There are many things you can do once your Alpine Linux system is up and
89running, and it largely depends on what you'll use the machine for. I'm
90going to walk through my personal post-installation setup for my web
91server.
92
931. Upgrade the System
94
95 First, login as =root= in order to update and upgrade the system:
96
97 #+begin_src sh
98 apk -U upgrade
99 #+end_src
100
1012. Adding a User
102
103 I needed to add a user so that I don't need to log in as root. Note
104 that if you're used to using the =sudo= command, you will now need to
105 use the =doas= command on Alpine Linux.
106
107 #+begin_src sh
108 apk add doas
109 adduser <username>
110 adduser <username> wheel
111 #+end_src
112
113 You can now log out and log back in using the newly-created user:
114
115 #+begin_src sh
116 exit
117 #+end_src
118
1193. Enable Community Packages
120
121 In order to install more common packages that aren't found in the
122 =main= repository, you will need to enable the =community=
123 repository:
124
125 #+begin_src sh
126 doas nano /etc/apk/repositories
127 #+end_src
128
129 Uncomment the community line for whichever version of Alpine you're
130 running:
131
132 #+begin_src sh
133 /media/usb/apks
134 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
135 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
136 #http://dl-cdn.alpinelinux.org/alpine/edge/main
137 #http://dl-cdn.alpinelinux.org/alpine/edge/community
138 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
139 #+end_src
140
1414. Install Required Packages
142
143 Now that the community packages are available, you can install any
144 packages you need. In my case, I installed the web server packages I
145 need for my services:
146
147 #+begin_src sh
148 doas apk add nano nginx docker docker-compose ufw
149 #+end_src
150
1515. SSH
152
153 If you didn't install OpenSSH as part of the installation, you can do
154 so now:
155
156 #+begin_src sh
157 doas apk add openssh
158 #+end_src
159
160 Next, either create a new key or copy your SSH key to the server from
161 your current machines:
162
163 #+begin_src sh
164 # Create a new key
165 ssh-keygen
166 #+end_src
167
168 If you need to copy an existing SSH key from a current machine:
169
170 #+begin_src sh
171 # Copy key from existing machines
172 ssh-copy-id <username>@<ip_address>
173 #+end_src
174
1756. Firewall
176
177 Lastly, I installed =ufw= above as my firewall. To set up, default to
178 deny incoming and allow outgoing connections. Then selectively allow
179 other ports or apps as needed.
180
181 #+begin_src sh
182 doas ufw default deny incoming
183 doas ufw default allow outgoing
184 doas ufw allow SSH
185 doas ufw allow "WWW Full"
186 doas ufw allow 9418 # Git server port
187 #+end_src
188
1897. Change Hostname
190
191 If you don't like the hostname set during installation, you just need
192 to edit two files. First, edit the simple hostname file:
193
194 #+begin_src sh
195 doas nano /etc/hostname
196 #+end_src
197
198 #+begin_src sh
199 <hostname>
200 #+end_src
201
202 Next, edit the =hosts= file:
203
204 #+begin_src sh
205 doas nano /etc/hosts
206 #+end_src
207
208 #+begin_src sh
209 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
210 ::1 <hostname> <hostname>.local
211 #+end_src
212
213* Nginx Web Server
214
215To set up my web server, I simply created the =www= user and created the
216necessary files.
217
218#+begin_src sh
219doas adduser -D -g 'www' www
220mkdir /www
221doas mkdir /www
222doas chown -R www:www /var/lib/nginx/
223doas chown -R www:www /www
224#+end_src
225
226If you're running a simple webroot, you can alter the main =nginx.conf=
227file. Otherwise, you can drop configuration files in the following
228directory. You don't need to enable or symlink the configuration file
229like you do in other systems.
230
231#+begin_src sh
232doas nano /etc/nginx/http.d/example_website.conf
233#+end_src
234
235Once the configuration is set and pointed at the =/www= directory to
236serve files, enable the Nginx service:
237
238#+begin_src sh
239# Note that 'default' must be included or Nginx will not start on boot
240doas rc-update add nginx default
241#+end_src
242
243* Docker Containers
244
245Docker works exactly the same as other systems. Either execute a
246=docker run= command or create a =docker-compose.yml= file and do
247=docker-compose up -d=.
248
249* Git Server
250
251I went in-depth on how to self-host a git server in another post:
252[[../git-server/][Self-Hosting a Personal Git Server]].
253
254However, there are a few differences with Alpine. First note that in
255order to change the =git= user's shell, you must do a few things a
256little different:
257
258#+begin_src sh
259doas apk add libuser
260doas touch /etc/login.defs
261doas mkdir /etc/default
262doas touch /etc/default/useradd
263doas lchsh git
264#+end_src
265
266* Thoughts on Alpine
267
268So far, I love Alpine Linux. I have no complaints about anything at this
269point, but I'm not completely finished with the migration yet. Once I'm
270able to upgrade my hardware to a rack-mounted server, I will migrate
271Plex and Syncthing over to Alpine as well - possibly putting Plex into a
272container or VM.
273
274The performance is stellar, the =apk= package manager is seamless, and
275system administration tasks are effortless. My only regret is that I
276didn't install Alpine sooner.
content/blog/2022-10-30-linux-display-manager.md deleted −77
@@ -1,77 +0,0 @@
1+++
2date = 2022-10-30
3title = "How to Disable or Change the Display Manager on Void Linux"
4description = ""
5draft = false
6+++
7
8# Display Manager Services
9
10In order to change the [display
11manager](https://en.wikipedia.org/wiki/Display_manager) on Void Linux - or any
12other Linux distro - you need to identify the currently enabled display manager.
13
14## Disabling the Current Display Manager
15
16Void Linux only has one ISO available for download with a pre-built display
17manager at the time of this post: the XFCE ISO. If you've installed this
18version, the pre-assigned display manager is `lxdm`. If you installed another
19display manager, replace `lxdm` in the following command with the display
20manager you have installed.
21
22To disable `lxdm`, simply remove the service symlink:
23
24```sh
25sudo rm /var/service/lxdm
26```
27
28## Enabling a New Display Manager
29
30If you want to enable a new display manager, you can do so after `lxdm` is
31disabled. Make sure to replace `<new_display_manager>` with your new DM, such as
32`gdm`, `xdm`, etc.
33
34```sh
35sudo ln -s /etc/sv/<new_display_manager> /var/service
36```
37
38# Set Up `.xinitrc`
39
40Depending on your setup, you may need to create a few X files, such as
41`~/.xinitrc`. For my personal set-up, I created this file to launch the i3wm as
42my desktop.
43
44```sh
45nano ~/.xinitrc
46```
47
48```sh
49#!/bin/sh
50
51exec i3
52```
53
54If you run a desktop other than i3, simply replace `i3` with the shell command
55that launches that desktop.
56
57# Set Up Your Shell Profile
58
59Finally, in order to automatically launch an X session upon login, you will need
60to edit the `.bash_profile` (bash) or `.zprofile` (zsh) files for your shell:
61
62```sh
63nano ~/.zprofile
64```
65
66Add the following snippet to the end of the shell profile file. This will
67execute the `startx` command upon login.
68
69```sh
70if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
71 exec startx
72fi
73```
74
75Alternatively, you can ignore this step and simply choose to manually execute
76`startx` upon login. This can be useful if you have issues with your desktop or
77like to manually launch different desktops by choice.
content/blog/2022-10-30-linux-display-manager.org added +77
@@ -0,0 +1,77 @@
1#+date: <2022-10-30>
2#+title: How to Disable or Change the Display Manager on Void Linux
3#+description:
4
5
6* Display Manager Services
7
8In order to change the
9[[https://en.wikipedia.org/wiki/Display_manager][display manager]] on
10Void Linux - or any other Linux distro - you need to identify the
11currently enabled display manager.
12
13** Disabling the Current Display Manager
14
15Void Linux only has one ISO available for download with a pre-built
16display manager at the time of this post: the XFCE ISO. If you've
17installed this version, the pre-assigned display manager is =lxdm=. If
18you installed another display manager, replace =lxdm= in the following
19command with the display manager you have installed.
20
21To disable =lxdm=, simply remove the service symlink:
22
23#+begin_src sh
24sudo rm /var/service/lxdm
25#+end_src
26
27** Enabling a New Display Manager
28
29If you want to enable a new display manager, you can do so after =lxdm=
30is disabled. Make sure to replace =<new_display_manager>= with your new
31DM, such as =gdm=, =xdm=, etc.
32
33#+begin_src sh
34sudo ln -s /etc/sv/<new_display_manager> /var/service
35#+end_src
36
37* Set Up =.xinitrc=
38
39Depending on your setup, you may need to create a few X files, such as
40=~/.xinitrc=. For my personal set-up, I created this file to launch the
41i3wm as my desktop.
42
43#+begin_src sh
44nano ~/.xinitrc
45#+end_src
46
47#+begin_src sh
48#!/bin/sh
49
50exec i3
51#+end_src
52
53If you run a desktop other than i3, simply replace =i3= with the shell
54command that launches that desktop.
55
56* Set Up Your Shell Profile
57
58Finally, in order to automatically launch an X session upon login, you
59will need to edit the =.bash_profile= (bash) or =.zprofile= (zsh) files
60for your shell:
61
62#+begin_src sh
63nano ~/.zprofile
64#+end_src
65
66Add the following snippet to the end of the shell profile file. This
67will execute the =startx= command upon login.
68
69#+begin_src sh
70if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
71 exec startx
72fi
73#+end_src
74
75Alternatively, you can ignore this step and simply choose to manually
76execute =startx= upon login. This can be useful if you have issues with
77your desktop or like to manually launch different desktops by choice.
content/blog/2022-11-07-self-hosting-matrix.md → content/blog/2022-11-07-self-hosting-matrix.org renamed +88 −85
@@ -1,103 +1,104 @@
1+++ 1#+date: <2022-11-07>
2date = 2022-11-07 2#+title: Self-Hosting Matrix Synapse on Alpine Linux
3title = "Self-Hosting Matrix Synapse on Alpine Linux" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Synapse 5
6* Synapse
9 7
10If you're reading this, you likely know that 8If you're reading this, you likely know that
11[Synapse](https://github.com/matrix-org/synapse/) is a popular 9[[https://github.com/matrix-org/synapse/][Synapse]] is a popular
12[Matrix](https://matrix.org/) home server software that allows users to run 10[[https://matrix.org/][Matrix]] home server software that allows users
13their own Matrix home server. 11to run their own Matrix home server.
14 12
15This post is a short guide describing how I was able to get Synapse working in a 13This post is a short guide describing how I was able to get Synapse
16minimally-usable state on Alpine Linux. 14working in a minimally-usable state on Alpine Linux.
17 15
18# Installation Process 16* Installation Process
19 17
20## Dependencies 18** Dependencies
21 19
22First, since there is no Alpine-specific package for Synapse, we need to ensure 20First, since there is no Alpine-specific package for Synapse, we need to
23that Alpine has the required dependencies for the Python-based installation 21ensure that Alpine has the required dependencies for the Python-based
24method. 22installation method.
25 23
26```sh 24#+begin_src sh
27doas apk -U update 25doas apk -U update
28doas apk add python3 py3-virtualenv 26doas apk add python3 py3-virtualenv
29``` 27#+end_src
30 28
31Next, we need to set up a Python virtual environment for Synapse: 29Next, we need to set up a Python virtual environment for Synapse:
32 30
33```sh 31#+begin_src sh
34mkdir -p ~/synapse && cd ~/synapse 32mkdir -p ~/synapse && cd ~/synapse
35virtualenv -p python3 ~/synapse/env 33virtualenv -p python3 ~/synapse/env
36source ~/synapse/env/bin/activate 34source ~/synapse/env/bin/activate
37pip install --upgrade pip 35pip install --upgrade pip
38pip install --upgrade setuptools 36pip install --upgrade setuptools
39pip install matrix-synapse 37pip install matrix-synapse
40``` 38#+end_src
41 39
42## Running Synapse 40** Running Synapse
43 41
44Once installed, running Synapse is easy. Simply execute the following command, 42Once installed, running Synapse is easy. Simply execute the following
45replacing `example.com` with the domain name that will be used with this home 43command, replacing =example.com= with the domain name that will be used
46server. This will generate the configuration files needed to run the server. 44with this home server. This will generate the configuration files needed
45to run the server.
47 46
48```sh 47#+begin_src sh
49python -m synapse.app.homeserver \ 48python -m synapse.app.homeserver \
50 --server-name example.com \ 49 --server-name example.com \
51 --config-path homeserver.yaml \ 50 --config-path homeserver.yaml \
52 --generate-config \ 51 --generate-config \
53 --report-stats=no 52 --report-stats=no
54``` 53#+end_src
55 54
56Once the configuration is generated, we can start up the Synapse server: 55Once the configuration is generated, we can start up the Synapse server:
57 56
58```sh 57#+begin_src sh
59synctl start 58synctl start
60``` 59#+end_src
61 60
62## Configuring Synapse 61** Configuring Synapse
63 62
64To make any change to Synapse, we need to edit the `YAML` configuration file: 63To make any change to Synapse, we need to edit the =YAML= configuration
64file:
65 65
66```sh 66#+begin_src sh
67nano ~/synapse/homeserver.yaml 67nano ~/synapse/homeserver.yaml
68``` 68#+end_src
69 69
70For now, we just need to ensure the `server_name` is accurate. However, there 70For now, we just need to ensure the =server_name= is accurate. However,
71are a lot of other configuration options found in the [Configuring 71there are a lot of other configuration options found in the
72Synapse](https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html) 72[[https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html][Configuring
73documentation that can be enabled/disabled at any point. 73Synapse]] documentation that can be enabled/disabled at any point.
74 74
75```yaml 75#+begin_src yaml
76server_name: "example.com" 76server_name: "example.com"
77``` 77#+end_src
78 78
79Make sure to restart Synapse when you make changes to the configuration: 79Make sure to restart Synapse when you make changes to the configuration:
80 80
81```sh 81#+begin_src sh
82synctl restart 82synctl restart
83``` 83#+end_src
84 84
85## Nginx Reverse-Proxy 85** Nginx Reverse-Proxy
86 86
87To ensure that Synapse is reachable from the public, we need to connect our 87To ensure that Synapse is reachable from the public, we need to connect
88domain to the Synapse server. In my case, I use a Nginx reverse-proxy for this 88our domain to the Synapse server. In my case, I use a Nginx
89purpose. 89reverse-proxy for this purpose.
90 90
91To use Nginx, we need to create a reverse-proxy configuration file: 91To use Nginx, we need to create a reverse-proxy configuration file:
92 92
93```sh 93#+begin_src sh
94doas nano /etc/nginx/http.d/example.com.conf 94doas nano /etc/nginx/http.d/example.com.conf
95``` 95#+end_src
96 96
97If you already have TLS certificates for this domain (`example.com`), you can 97If you already have TLS certificates for this domain (=example.com=),
98simply use the SSL configuration and point toward your TLS certificates. 98you can simply use the SSL configuration and point toward your TLS
99certificates.
99 100
100```conf 101#+begin_src conf
101server { 102server {
102 listen 443 ssl http2; 103 listen 443 ssl http2;
103 listen [::]:443 ssl http2; 104 listen [::]:443 ssl http2;
@@ -136,14 +137,15 @@ server {
136 listen 80; 137 listen 80;
137 return 404; 138 return 404;
138} 139}
139``` 140#+end_src
140 141
141If you need to generate TLS certificates (I recommend 142If you need to generate TLS certificates (I recommend
142[Certbot](https://certbot.eff.org/)), you'll need a more minimal Nginx conf file 143[[https://certbot.eff.org/][Certbot]]), you'll need a more minimal Nginx
143before you can use the TLS-enabled example above. Instead, use this 144conf file before you can use the TLS-enabled example above. Instead, use
144configuration file during the Certbot certificate generation process: 145this configuration file during the Certbot certificate generation
146process:
145 147
146```conf 148#+begin_src conf
147server { 149server {
148 server_name example.com; 150 server_name example.com;
149 location / { 151 location / {
@@ -151,62 +153,63 @@ server {
151 } 153 }
152 listen 80; 154 listen 80;
153} 155}
154``` 156#+end_src
155 157
156Once you're done editing the Nginx conf file, restart Nginx: 158Once you're done editing the Nginx conf file, restart Nginx:
157 159
158```sh 160#+begin_src sh
159doas rc-service nginx restart 161doas rc-service nginx restart
160``` 162#+end_src
161 163
162If you still need to generate TLS certificates, run `certbot` now and obtain the 164If you still need to generate TLS certificates, run =certbot= now and
163certificates. Certbot will ask if you want to use a webroot or spin up a 165obtain the certificates. Certbot will ask if you want to use a webroot
164temporary web server. I **highly** recommend using the temporary web server due 166or spin up a temporary web server. I *highly* recommend using the
165to the many issues with using a webroot. 167temporary web server due to the many issues with using a webroot.
166 168
167You will need to stop Nginx in order to user the temporary web server option 169You will need to stop Nginx in order to user the temporary web server
168with Certbot: 170option with Certbot:
169 171
170```sh 172#+begin_src sh
171# Stop Nginx so certbot can spin up a temp webserver for cert generation 173# Stop Nginx so certbot can spin up a temp webserver for cert generation
172doas rc-service nginx stop 174doas rc-service nginx stop
173doas certbot certonly -v 175doas certbot certonly -v
174doas rc-service nginx start 176doas rc-service nginx start
175``` 177#+end_src
176 178
177## Open Firewall & Router Ports 179** Open Firewall & Router Ports
178 180
179If you use a firewall on the server, open the `8448` port for discovery and 181If you use a firewall on the server, open the =8448= port for discovery
180federation, as well as the normal web server ports if you're using a reverse 182and federation, as well as the normal web server ports if you're using a
181proxy. If you want additional services, such as voice calls, you will need to 183reverse proxy. If you want additional services, such as voice calls, you
182read the Synapse documentation to see which ports need to be opened for those 184will need to read the Synapse documentation to see which ports need to
183features. 185be opened for those features.
184 186
185Here's an example of the Universal Firewall (UFW) software: 187Here's an example of the Universal Firewall (UFW) software:
186 188
187```sh 189#+begin_src sh
188# Matrix port 190# Matrix port
189doas ufw allow 8448 191doas ufw allow 8448
190# Standard web server ports 192# Standard web server ports
191doas ufw allow "Nginx Full" 193doas ufw allow "Nginx Full"
192``` 194#+end_src
193 195
194Remember to forward any Synapse ports, such as `8448`, `80`, and `443`, in your 196Remember to forward any Synapse ports, such as =8448=, =80=, and =443=,
195Router from the internet to your server's IP address. 197in your Router from the internet to your server's IP address.
196 198
197## Adding Matrix Users 199** Adding Matrix Users
198 200
199Finally, if you didn't enable public registration in the `homeserver.yaml` file, 201Finally, if you didn't enable public registration in the
200you can manually create users via the command-line: 202=homeserver.yaml= file, you can manually create users via the
203command-line:
201 204
202```sh 205#+begin_src sh
203cd ~/synapse 206cd ~/synapse
204register_new_matrix_user -c homeserver.yaml 207register_new_matrix_user -c homeserver.yaml
205``` 208#+end_src
206 209
207Remember that the format for federated Matrix usernames is 210Remember that the format for federated Matrix usernames is
208`@username:example.com` when logging in to client applications. 211=@username:example.com= when logging in to client applications.
209 212
210Once Synapse is running, and you have a username, you are ready to log in to a 213Once Synapse is running, and you have a username, you are ready to log
211Matrix client and start sending messages, joining rooms, and utilizing your very 214in to a Matrix client and start sending messages, joining rooms, and
212own Matrix server. 215utilizing your very own Matrix server.
content/blog/2022-11-11-nginx-tmp-errors.md deleted −77
@@ -1,77 +0,0 @@
1+++
2date = 2022-11-11
3title = "Fixing Permission Errors in /var/lib/nginx"
4description = ""
5draft = false
6+++
7
8_This is a brief post so that I personally remember the solution as it has
9occurred multiple times for me._
10
11# The Problem
12
13After migrating to a new server OS, I started receiving quite a few permission
14errors like the one below. These popped up for various different websites I'm
15serving via Nginx on this server, but did not prevent the website from loading.
16
17I found the errors in the standard log file:
18
19```sh
20cat /var/log/nginx/error.log
21```
22
23```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/"
25```
26
27You can see that the error is `13: Permission denied` and it occurs in the
28`/var/lib/nginx/tmp/` directory. In my case, I had thousands of errors where
29Nginx was denied permission to read/write files in this directory.
30
31So how do I fix it?
32
33# The Solution
34
35In order to resolve the issue, I had to ensure the `/var/lib/nginx` directory is
36owned by Nginx. Mine was owned by the `www` user and Nginx was not able to read
37or write files within that directory. This prevented Nginx from caching
38temporary files.
39
40```sh
41# Alpine Linux
42doas chown -R nginx:nginx /var/lib/nginx
43
44# Other Distros
45sudo chown -R nginx:nginx /var/lib/nginx
46```
47
48You _may_ also be able to change the `proxy_temp_path` in your Nginx config, but
49I did not try this. Here's a suggestion I found online that may work if the
50above solution does not:
51
52```sh
53nano /etc/nginx/http.d/example.com.conf
54```
55
56```conf
57server {
58 ...
59
60 # Set the proxy_temp_path to your preference, make sure it's owned by the
61 # `nginx` user
62 proxy_temp_path /tmp;
63
64 ...
65}
66```
67
68Finally, restart Nginx and your server should be able to cache temporary files
69again.
70
71```sh
72# Alpine Linux (OpenRC)
73doas rc-service nginx restart
74
75# Other Distros (systemd)
76sudo systemctl restart nginx
77```
content/blog/2022-11-11-nginx-tmp-errors.org added +77
@@ -0,0 +1,77 @@
1#+date: <2022-11-11>
2#+title: Fixing Permission Errors in /var/lib/nginx
3#+description:
4
5
6/This is a brief post so that I personally remember the solution as it
7has occurred multiple times for me./
8
9* The Problem
10
11After migrating to a new server OS, I started receiving quite a few
12permission errors like the one below. These popped up for various
13different websites I'm serving via Nginx on this server, but did not
14prevent the website from loading.
15
16I found the errors in the standard log file:
17
18#+begin_src sh
19cat /var/log/nginx/error.log
20#+end_src
21
22#+begin_src sh
232022/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/"
24#+end_src
25
26You can see that the error is =13: Permission denied= and it occurs in
27the =/var/lib/nginx/tmp/= directory. In my case, I had thousands of
28errors where Nginx was denied permission to read/write files in this
29directory.
30
31So how do I fix it?
32
33* The Solution
34
35In order to resolve the issue, I had to ensure the =/var/lib/nginx=
36directory is owned by Nginx. Mine was owned by the =www= user and Nginx
37was not able to read or write files within that directory. This
38prevented Nginx from caching temporary files.
39
40#+begin_src sh
41# Alpine Linux
42doas chown -R nginx:nginx /var/lib/nginx
43
44# Other Distros
45sudo chown -R nginx:nginx /var/lib/nginx
46#+end_src
47
48You /may/ also be able to change the =proxy_temp_path= in your Nginx
49config, but I did not try this. Here's a suggestion I found online that
50may work if the above solution does not:
51
52#+begin_src sh
53nano /etc/nginx/http.d/example.com.conf
54#+end_src
55
56#+begin_src conf
57server {
58 ...
59
60 # Set the proxy_temp_path to your preference, make sure it's owned by the
61 # `nginx` user
62 proxy_temp_path /tmp;
63
64 ...
65}
66#+end_src
67
68Finally, restart Nginx and your server should be able to cache temporary
69files again.
70
71#+begin_src sh
72# Alpine Linux (OpenRC)
73doas rc-service nginx restart
74
75# Other Distros (systemd)
76sudo systemctl restart nginx
77#+end_src
content/blog/2022-11-27-server-build.md deleted −143
@@ -1,143 +0,0 @@
1+++
2date = 2022-11-27
3title = "Building a Custom Rack-Mounted Server"
4description = ""
5draft = false
6+++
7
8# The Dilemma
9
10For years, I have been using desktops and a Raspberry Pi as the backbone of my
11homelab. I have always wanted to move toward a single dedicated server that
12could handle all of my tasks, but was often put off by the complexity of the
13choices (and financial roadblocks at some times).
14
15However, after purchasing a small server rack this past year, I have been
16researching to see what kind of rack-mounted server I could buy. I initially
17bought a Dell R720XD loaded up with everything I could ever want in a server -
18but I did not care for it. It was far too loud, and the age of the CPU/RAM was
19something I wanted to improve upon.
20
21After returning the R720XD, I decided that I wanted to build my own server with
22modern, consumer-grade PC components. This time, I am very happy with the
23results of my server.
24
25# Components
26
27I'll start by listing all the components I used for this server build:
28
29- **Case**: [Rosewill RSV-R4100U 4U Server Chassis Rackmount
30 Case](https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825)
31- **Motherboard**: [NZXT B550](https://nzxt.com/product/n7-b550)
32- **CPU**: AMD Ryzen 7 5700G with Radeon Graphics
33- **GPU**: N/A - I specifically chose one of the few AMD CPUs that support
34 onboard graphics.
35- **RAM**: 64GB RAM (2x32GB) _Max of 128GB RAM on this motherboard_
36- **Boot Drive**: Western Digital 500GB M.2 NVME SSD
37- **HDD Bay**:
38 - 10TB WD White _(shucked, moved from previous server)_
39 - 8TB WD White _(shucked, moved from previous server)_
40 - 2 x 8TB WD Red Plus _(Black Friday lined up perfectly with this build,
41 so I grabbed two of these)_
42- **PSU**: Corsair RM850 PSU
43- **Extras**:
44 - Corsair TM3Q Thermal Paste
45 - Noctua 120mm fan _(replacement for front case fan)_
46 - 2 x Noctua 80mm fans _(replacement for rear case fans)_
47 - CableMatters 6Gbps SATA Cables
48
49# Building the Server
50
51This took quite a while for me to build (in my opinion of time), totaling around
523 hours from start to finish. The case has some peculiar construction, so you
53have to completely remove the ODD & HDD cages to install the motherboard and
54other components first.
55
56Now, I've never built a computer of any kind before, so I was quite nervous.
57Personally, the only challenging part was getting the CPU cooler to screw into
58the motherboard without sliding the thermal paste around too much underneath.
59I'm still not entirely sure if I did a great job of it, but nothing's broken
60yet.
61
62The main components were all fine and simple. However, installing the hard
63drives is slightly tedious as I need to power off the server and completely
64unscrew the HDD cage to install or remove any drives. Additionally, the drives
65are screwed directly into the metal cage with small screws, which are quite a
66bit different from the HDD trays I'm used to in other machines.
67
68Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay dealing
69with the tedium of removing the cage to install new drives.
70
71# Software
72
73I'm not going to dive into the software as I have done so in other recent posts.
74However, I wanted to note that I am using Alpine Linux on this server and
75hosting most services inside Docker. No virtual machines (VMs) and very few
76bare-metal services.
77
78# The Results
79
80How did my build turn out? Well, after migrating my other servers and their
81services over, I found that my server is blazing fast. The heaviest of my
82applications, Plex, is handled with ease. Even 4k streaming seems to be
83effortless.
84
85I am very happy with the results and will likely continue to improve on this
86server as the years go by rather than buying another used server online.
87
88## Mistakes I Made
89
90This post wouldn't be complete unless I wrote about the mistakes I made while
91building. The only real mistake I made beyond a "whoops I dropped a screw"
92related to airflow and fan direction.
93
94While installing the two new hard drives that showed up on 2022-11-30 and
95getting ready to install the case in my rack, I noticed that the hard drive
96temperatures were quite high.
97
98I used the `smartctl` command for each of my drives (`/dev/sda` through
99`/dev/sdd`):
100
101```sh
102doas smartctl -a /dev/sda | grep Temperature_Celsius
103```
104
105The results were unusual - all four drives were idling at ~44-46 degrees
106Celsius. The only drive that was cooler was my 10TB drive, which was at 38
107degrees Celsius. I noted that this 10TB drive was also closest to the case fan.
108
109```sh
110ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
111# /dev/sda
112194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46)
113# /dev/sdb
114194 Temperature_Celsius 0x0002 141 141 000 Old_age Always - 46 (Min/Max 21/48)
115# /dev/sdc
116194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61)
117# /dev/sdd
118194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56)
119```
120
121After looking to see if I could fit more fans into the case, I noticed that the
122120mm fan used for intake from the front of the case was actually pushing air
123out of the case by mistake. This fan sits right in front of the hard drive bay.
124
125Once I flipped the fan around to act as an intake fan, the temperatures dropped
126immediately! They are now idling at ~31-33 degrees Celsius. A single fan
127spinning the wrong way caused my drives to idle 10-15 degrees higher than they
128should have.
129
130```sh
131ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
132# /dev/sda
133194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56)
134# /dev/sdb
135194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 19/61)
136# /dev/sdc
137194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48)
138# /dev/sdd
139194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46)
140```
141
142This was a silly error to make, but I'm glad I found it today before I screwed
143the case into the rack and made things a lot more tedious to fix.
content/blog/2022-11-27-server-build.org added +147
@@ -0,0 +1,147 @@
1#+date: <2022-11-27>
2#+title: Building a Custom Rack-Mounted Server
3#+description:
4
5
6* The Dilemma
7
8For years, I have been using desktops and a Raspberry Pi as the backbone
9of my homelab. I have always wanted to move toward a single dedicated
10server that could handle all of my tasks, but was often put off by the
11complexity of the choices (and financial roadblocks at some times).
12
13However, after purchasing a small server rack this past year, I have
14been researching to see what kind of rack-mounted server I could buy. I
15initially bought a Dell R720XD loaded up with everything I could ever
16want in a server - but I did not care for it. It was far too loud, and
17the age of the CPU/RAM was something I wanted to improve upon.
18
19After returning the R720XD, I decided that I wanted to build my own
20server with modern, consumer-grade PC components. This time, I am very
21happy with the results of my server.
22
23* Components
24
25I'll start by listing all the components I used for this server build:
26
27- *Case*:
28 [[https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825][Rosewill
29 RSV-R4100U 4U Server Chassis Rackmount Case]]
30- *Motherboard*: [[https://nzxt.com/product/n7-b550][NZXT B550]]
31- *CPU*: AMD Ryzen 7 5700G with Radeon Graphics
32- *GPU*: N/A - I specifically chose one of the few AMD CPUs that support
33 onboard graphics.
34- *RAM*: 64GB RAM (2x32GB) /Max of 128GB RAM on this motherboard/
35- *Boot Drive*: Western Digital 500GB M.2 NVME SSD
36- *HDD Bay*:
37 - 10TB WD White /(shucked, moved from previous server)/
38 - 8TB WD White /(shucked, moved from previous server)/
39 - 2 x 8TB WD Red Plus /(Black Friday lined up perfectly with this
40 build, so I grabbed two of these)/
41- *PSU*: Corsair RM850 PSU
42- *Extras*:
43 - Corsair TM3Q Thermal Paste
44 - Noctua 120mm fan /(replacement for front case fan)/
45 - 2 x Noctua 80mm fans /(replacement for rear case fans)/
46 - CableMatters 6Gbps SATA Cables
47
48* Building the Server
49
50This 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
52construction, so you have to completely remove the ODD & HDD cages to
53install the motherboard and other components first.
54
55Now, I've never built a computer of any kind before, so I was quite
56nervous. Personally, the only challenging part was getting the CPU
57cooler to screw into the motherboard without sliding the thermal paste
58around too much underneath. I'm still not entirely sure if I did a great
59job of it, but nothing's broken yet.
60
61The main components were all fine and simple. However, installing the
62hard drives is slightly tedious as I need to power off the server and
63completely unscrew the HDD cage to install or remove any drives.
64Additionally, the drives are screwed directly into the metal cage with
65small screws, which are quite a bit different from the HDD trays I'm
66used to in other machines.
67
68Seeing 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.
70
71* Software
72
73I'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
75this server and hosting most services inside Docker. No virtual machines
76(VMs) and very few bare-metal services.
77
78* The Results
79
80How did my build turn out? Well, after migrating my other servers and
81their services over, I found that my server is blazing fast. The
82heaviest of my applications, Plex, is handled with ease. Even 4k
83streaming seems to be effortless.
84
85I 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
87online.
88
89** Mistakes I Made
90
91This 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
93a screw" related to airflow and fan direction.
94
95While 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
97hard drive temperatures were quite high.
98
99I used the =smartctl= command for each of my drives (=/dev/sda= through
100=/dev/sdd=):
101
102#+begin_src sh
103doas smartctl -a /dev/sda | grep Temperature_Celsius
104#+end_src
105
106The results were unusual - all four drives were idling at ~44-46 degrees
107Celsius. The only drive that was cooler was my 10TB drive, which was at
10838 degrees Celsius. I noted that this 10TB drive was also closest to the
109case fan.
110
111#+begin_src sh
112ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
113# /dev/sda
114194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46)
115# /dev/sdb
116194 Temperature_Celsius 0x0002 141 141 000 Old_age Always - 46 (Min/Max 21/48)
117# /dev/sdc
118194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61)
119# /dev/sdd
120194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56)
121#+end_src
122
123After 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
125actually pushing air out of the case by mistake. This fan sits right in
126front of the hard drive bay.
127
128Once I flipped the fan around to act as an intake fan, the temperatures
129dropped 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
131higher than they should have.
132
133#+begin_src sh
134ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
135# /dev/sda
136194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56)
137# /dev/sdb
138194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 19/61)
139# /dev/sdc
140194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48)
141# /dev/sdd
142194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46)
143#+end_src
144
145This 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
147fix.
content/blog/2022-11-29-nginx-referrer-ban-list.md deleted −136
@@ -1,136 +0,0 @@
1+++
2date = 2022-11-29
3title = "Creating a Referrer Ban List in Nginx"
4description = ""
5draft = false
6+++
7
8# Creating the Ban List
9
10In order to ban list referral domains or websites with Nginx, you need to create
11a ban list file. The file below will accept regexes for different domains or
12websites you wish to block.
13
14First, create the file in your nginx directory:
15
16```sh
17doas nano /etc/nginx/banlist.conf
18```
19
20Next, paste the following contents in and fill out the regexes with whichever
21domains you're blocking.
22
23```conf
24# /etc/nginx/banlist.conf
25
26map $http_referer $bad_referer {
27 hostnames;
28
29 default 0;
30
31 # Put regexes for undesired referrers here
32 "~news.ycombinator.com" 1;
33}
34```
35
36# Configuring Nginx
37
38In order for the ban list to work, Nginx needs to know it exists and how to
39handle it. For this, edit the `nginx.conf` file.
40
41```sh
42doas nano /etc/nginx/nginx.conf
43```
44
45Within this file, find the `http` block and add your ban list file location to
46the end of the block.
47
48```conf
49# /etc/nginx/nginx.conf
50
51http {
52 ...
53
54 # Include ban list
55 include /etc/nginx/banlist.conf;
56}
57```
58
59# Enabling the Ban List
60
61Finally, we need to take action when a bad referral site is found. To do so,
62edit the configuration file for your website. For example, I have all website
63configuration files in the `http.d` directory. You may have them in the
64`sites-available` directory on some distributions.
65
66```sh
67doas nano /etc/nginx/http.d/example.com.conf
68```
69
70Within each website's configuration file, edit the `server` blocks that are
71listening to ports 80 and 443 and create a check for the `$bad_referrer`
72variable we created in the ban list file.
73
74If a matching site is found, you can return any [HTTP Status
75Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) you want. Code
76403 (Forbidden) is logical in this case since you are preventing a client
77connection due to a banned domain.
78
79```conf
80server {
81 ...
82
83 # If a referral site is banned, return an error
84 if ($bad_referer) {
85 return 403;
86 }
87
88 ...
89}
90```
91
92# Restart Nginx
93
94Lastly, restart Nginx to enable all changes made.
95
96```sh
97doas rc-service nginx restart
98```
99
100# Testing Results
101
102In order to test the results, let's curl the contents of our site. To start,
103I'll curl the site normally:
104
105```sh
106curl https://cleberg.net
107```
108
109The HTML contents of the page come back successfully:
110
111```html
112<!doctype html>...</html>
113```
114
115Next, let's include a banned referrer:
116
117```sh
118curl --referer https://news.ycombinator.com https://cleberg.net
119```
120
121This time, I'm met with a 403 Forbidden response page. That means we are
122successful and any clients being referred from a banned domain will be met with
123this same response code.
124
125```html
126<html>
127 <head>
128 <title>403 Forbidden</title>
129 </head>
130 <body>
131 <center><h1>403 Forbidden</h1></center>
132 <hr />
133 <center>nginx</center>
134 </body>
135</html>
136```
content/blog/2022-11-29-nginx-referrer-ban-list.org added +134
@@ -0,0 +1,134 @@
1#+date: <2022-11-29>
2#+title: Creating a Referrer Ban List in Nginx
3#+description:
4
5
6* Creating the Ban List
7
8In order to ban list referral domains or websites with Nginx, you need
9to create a ban list file. The file below will accept regexes for
10different domains or websites you wish to block.
11
12First, create the file in your nginx directory:
13
14#+begin_src sh
15doas nano /etc/nginx/banlist.conf
16#+end_src
17
18Next, paste the following contents in and fill out the regexes with
19whichever domains you're blocking.
20
21#+begin_src conf
22# /etc/nginx/banlist.conf
23
24map $http_referer $bad_referer {
25 hostnames;
26
27 default 0;
28
29 # Put regexes for undesired referrers here
30 "~news.ycombinator.com" 1;
31}
32#+end_src
33
34* Configuring Nginx
35
36In order for the ban list to work, Nginx needs to know it exists and how
37to handle it. For this, edit the =nginx.conf= file.
38
39#+begin_src sh
40doas nano /etc/nginx/nginx.conf
41#+end_src
42
43Within this file, find the =http= block and add your ban list file
44location to the end of the block.
45
46#+begin_src conf
47# /etc/nginx/nginx.conf
48
49http {
50 ...
51
52 # Include ban list
53 include /etc/nginx/banlist.conf;
54}
55#+end_src
56
57* Enabling the Ban List
58
59Finally, we need to take action when a bad referral site is found. To do
60so, edit the configuration file for your website. For example, I have
61all website configuration files in the =http.d= directory. You may have
62them in the =sites-available= directory on some distributions.
63
64#+begin_src sh
65doas nano /etc/nginx/http.d/example.com.conf
66#+end_src
67
68Within each website's configuration file, edit the =server= blocks that
69are listening to ports 80 and 443 and create a check for the
70=$bad_referrer= variable we created in the ban list file.
71
72If a matching site is found, you can return any
73[[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes][HTTP Status
74Code]] you want. Code 403 (Forbidden) is logical in this case since you
75are preventing a client connection due to a banned domain.
76
77#+begin_src conf
78server {
79 ...
80
81 # If a referral site is banned, return an error
82 if ($bad_referer) {
83 return 403;
84 }
85
86 ...
87}
88#+end_src
89
90* Restart Nginx
91
92Lastly, restart Nginx to enable all changes made.
93
94#+begin_src sh
95doas rc-service nginx restart
96#+end_src
97
98* Testing Results
99
100In order to test the results, let's curl the contents of our site. To
101start, I'll curl the site normally:
102
103#+begin_src sh
104curl https://cleberg.net
105#+end_src
106
107The HTML contents of the page come back successfully:
108
109#+begin_src html
110<!doctype html>...</html>
111#+end_src
112
113Next, let's include a banned referrer:
114
115#+begin_src sh
116curl --referer https://news.ycombinator.com https://cleberg.net
117#+end_src
118
119This time, I'm met with a 403 Forbidden response page. That means we are
120successful and any clients being referred from a banned domain will be
121met with this same response code.
122
123#+begin_src html
124<html>
125 <head>
126 <title>403 Forbidden</title>
127 </head>
128 <body>
129 <center><h1>403 Forbidden</h1></center>
130 <hr />
131 <center>nginx</center>
132 </body>
133</html>
134#+end_src
content/blog/2022-12-01-nginx-compression.md deleted −76
@@ -1,76 +0,0 @@
1+++
2date = 2022-12-01
3title = "Enable GZIP Compression in Nginx"
4description = ""
5draft = false
6+++
7
8# Text Compression
9
10Text compression allows a web server to serve text-based resources faster than
11uncompressed data. This can speed up things like First Contentful Paint, Tie to
12Interactive, and Speed Index.
13
14# Enable Nginx Compression with gzip
15
16In order to enable text compression on Nginx, we need to enable it within the
17configuration file:
18
19```sh
20nano /etc/nginx/nginx.conf
21```
22
23Within the `http` block, find the section that shows something like the block
24below. This is the default gzip configuration I found in my `nginx.conf` file on
25Alpine Linux 3.17. Yours may look slightly different, just make sure that you're
26not creating any duplicate 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 for
51Nginx. Here are the full explanations:
52
53- `gzip` -- Enables or disables gzipping of responses.
54- `gzip_vary` -- Enables or disables inserting the "Vary: Accept-Encoding"
55 response header field if the directives gzip, gzip~static~, or gunzip are
56 active.
57- `gzip_min_length` -- Sets the minimum length of a response that will be
58 gzipped. The length is determined only from the "Content-Length" response
59 header field.
60- `gzip_proxied` -- Enables or disables gzipping of responses for proxied
61 requests depending on the request and response. The fact that the request is
62 proxied is determined by the presence of the "Via" request header field.
63- `gzip_types` -- Enables gzipping of responses for the specified MIME types
64 in addition to "text/html". The special value "\*" matches any MIME type
65 (0.8.29). Responses with the "text/html" type are always compressed.
66- `gzip_disable` -- Disables gzipping of responses for requests with
67 "User-Agent" header fields matching any of the specified regular
68 expressions.
69 - The special mask "msie6" (0.7.12) corresponds to the regular expression
70 "MSIE [4-6].", but works faster. Starting from version 0.8.11, "MSIE
71 6.0; ... SV1" is excluded from this mask.
72
73More information on these directives and their options can be found on the
74[Module
75ngx~httpgzipmodule~](https://nginx.org/en/docs/http/ngx_http_gzip_module.html)
76page in Nginx's documentation.
content/blog/2022-12-01-nginx-compression.org added +76
@@ -0,0 +1,76 @@
1#+date: <2022-12-01>
2#+title: Enable GZIP Compression in Nginx
3#+description:
4
5
6* Text Compression
7
8Text compression allows a web server to serve text-based resources
9faster than uncompressed data. This can speed up things like First
10Contentful Paint, Tie to Interactive, and Speed Index.
11
12* Enable Nginx Compression with gzip
13
14In order to enable text compression on Nginx, we need to enable it
15within the configuration file:
16
17#+begin_src sh
18nano /etc/nginx/nginx.conf
19#+end_src
20
21Within the =http= block, find the section that shows something like the
22block below. This is the default gzip configuration I found in my
23=nginx.conf= file on Alpine Linux 3.17. Yours may look slightly
24different, just make sure that you're not creating any duplicate gzip
25options.
26
27#+begin_src conf
28# Enable gzipping of responses.
29#gzip on;
30
31# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
32gzip_vary on;
33#+end_src
34
35Remove the default gzip lines and replace them with the following:
36
37#+begin_src conf
38# Enable gzipping of responses.
39gzip on;
40gzip_vary on;
41gzip_min_length 10240;
42gzip_proxied expired no-cache no-store private auth;
43gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
44gzip_disable "MSIE [1-6]";
45#+end_src
46
47* Explanations of ngx_{httpgzipmodule} Options
48
49Each of the lines above enables a different aspect of the gzip response
50for Nginx. Here are the full explanations:
51
52- =gzip= -- Enables or disables gzipping of responses.
53- =gzip_vary= -- Enables or disables inserting the "Vary:
54 Accept-Encoding" response header field if the directives gzip,
55 gzip_{static}, or gunzip are active.
56- =gzip_min_length= -- Sets the minimum length of a response that will
57 be gzipped. The length is determined only from the "Content-Length"
58 response header field.
59- =gzip_proxied= -- Enables or disables gzipping of responses for
60 proxied requests depending on the request and response. The fact that
61 the request is proxied is determined by the presence of the "Via"
62 request header field.
63- =gzip_types= -- Enables gzipping of responses for the specified MIME
64 types in addition to "text/html". The special value "*" matches any
65 MIME type (0.8.29). Responses with the "text/html" type are always
66 compressed.
67- =gzip_disable= -- Disables gzipping of responses for requests with
68 "User-Agent" header fields matching any of the specified regular
69 expressions.
70 - The special mask "msie6" (0.7.12) corresponds to the regular
71 expression "MSIE [4-6].", but works faster. Starting from version
72 0.8.11, "MSIE 6.0; ... SV1" is excluded from this mask.
73
74More information on these directives and their options can be found on
75the [[https://nginx.org/en/docs/http/ngx_http_gzip_module.html][Module
76ngx_{httpgzipmodule}]] page in Nginx's documentation.
content/blog/2022-12-07-nginx-wildcard-redirect.md → content/blog/2022-12-07-nginx-wildcard-redirect.org renamed +49 −49
@@ -1,26 +1,24 @@
1+++ 1#+date: <2022-12-07>
2date = 2022-12-07 2#+title: Redirect Nginx Subdomains and Trailing Content with Regex
3title = "Redirect Nginx Subdomains and Trailing Content with Regex" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Problem
9 5
10I recently migrated domains and replaced the old webpage with a simple info page 6* Problem
11with instructions to users on how to edit their bookmarks and URLs to get to the
12page they were seeking.
13 7
14This was not ideal as it left the work up to the user and may have caused 8I recently migrated domains and replaced the old webpage with a simple
15friction for users who accessed my RSS feed. 9info page with instructions to users on how to edit their bookmarks and
10URLs to get to the page they were seeking.
16 11
17# Solution 12This was not ideal as it left the work up to the user and may have
13caused friction for users who accessed my RSS feed.
18 14
19Instead, I finally found a solution that allows me to redirect both subdomains 15* Solution
20AND trailing content. For example, both of these URLs now redirect properly
21using the logic I'll explain below:
22 16
23```txt 17Instead, I finally found a solution that allows me to redirect both
18subdomains AND trailing content. For example, both of these URLs now
19redirect properly using the logic I'll explain below:
20
21#+begin_src txt
24# Example 1 - Simple base domain redirect with trailing content 22# Example 1 - Simple base domain redirect with trailing content
25https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ 23https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
26 24
@@ -28,23 +26,23 @@ https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
28https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 26https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
29-> 27->
30https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 28https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
31``` 29#+end_src
32 30
33Go ahead, try the URLs if you want to test them. 31Go ahead, try the URLs if you want to test them.
34 32
35## Nginx Config 33** Nginx Config
36 34
37To make this possible. I needed to configure a proper redirect scheme in my 35To make this possible. I needed to configure a proper redirect scheme in
38Nginx configuration. 36my Nginx configuration.
39 37
40```sh 38#+begin_src sh
41doas nano /etc/nginx/http.d/domain1.conf 39doas nano /etc/nginx/http.d/domain1.conf
42``` 40#+end_src
43 41
44Within this file, I had one block configured to redirect HTTP requests to HTTPS 42Within this file, I had one block configured to redirect HTTP requests
45for the base domain and all subdomains. 43to HTTPS for the base domain and all subdomains.
46 44
47```conf 45#+begin_src conf
48server { 46server {
49 listen [::]:80; 47 listen [::]:80;
50 listen 80; 48 listen 80;
@@ -60,13 +58,14 @@ server {
60 58
61 return 404; 59 return 404;
62} 60}
63``` 61#+end_src
64 62
65For the base domain, I have another `server` block dedicated to redirecting all 63For the base domain, I have another =server= block dedicated to
66base domain requests. You can see that the `rewrite` line is instructing Nginx 64redirecting all base domain requests. You can see that the =rewrite=
67to gather all trailing content and append it to the new `domain2.com` URL. 65line is instructing Nginx to gather all trailing content and append it
66to the new =domain2.com= URL.
68 67
69```conf 68#+begin_src conf
70server { 69server {
71 listen [::]:443 ssl http2; 70 listen [::]:443 ssl http2;
72 listen 443 ssl http2; 71 listen 443 ssl http2;
@@ -78,20 +77,21 @@ server {
78 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 77 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
79 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 78 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
80} 79}
81``` 80#+end_src
82 81
83Finally, the tricky part is figuring out how to tell Nginx to redirect while 82Finally, the tricky part is figuring out how to tell Nginx to redirect
84keeping both a subdomain and trailing content intact. I found that the easiest 83while keeping both a subdomain and trailing content intact. I found that
85way to do this is to give it a `server` block of its own. 84the easiest way to do this is to give it a =server= block of its own.
86 85
87Within this block, we need to do some regex on the `server_name` line before we 86Within this block, we need to do some regex on the =server_name= line
88can rewrite anything. This creates a variable called `subdomain`. 87before we can rewrite anything. This creates a variable called
88=subdomain=.
89 89
90Once the server gets to the `rewrite` line, it pulls the `subdomain` variable 90Once the server gets to the =rewrite= line, it pulls the =subdomain=
91from above and uses it on the new `domain2.com` domain before appending the 91variable from above and uses it on the new =domain2.com= domain before
92trailing content (`$request_uri`). 92appending the trailing content (=$request_uri=).
93 93
94```conf 94#+begin_src conf
95server { 95server {
96 listen [::]:443 ssl http2; 96 listen [::]:443 ssl http2;
97 listen 443 ssl http2; 97 listen 443 ssl http2;
@@ -103,17 +103,17 @@ server {
103 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 103 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
104 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 104 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
105} 105}
106``` 106#+end_src
107 107
108That's all there is to it. With this, I simply restarted Nginx and watched the 108That's all there is to it. With this, I simply restarted Nginx and
109redirections work in-action. 109watched the redirections work in-action.
110 110
111```sh 111#+begin_src sh
112doas rc-service nginx restart 112doas rc-service nginx restart
113``` 113#+end_src
114 114
115Looking back on it, I wish I had done this sooner. Who knows how many people 115Looking back on it, I wish I had done this sooner. Who knows how many
116went looking for my sites or bookmarks and gave up when they saw the redirect 116people went looking for my sites or bookmarks and gave up when they saw
117instructions page. 117the redirect instructions page.
118 118
119Oh well, it's done now. Live and learn. 119Oh well, it's done now. Live and learn.
content/blog/2022-12-17-st.md deleted −91
@@ -1,91 +0,0 @@
1+++
2date = 2022-12-17
3title = "Simple Terminal"
4description = ""
5draft = false
6+++
7
8# st
9
10[st](https://st.suckless.org) standards for Simple Terminal, a simple terminal
11implementation for X made by the [suckless](https://suckless.org) team.
12
13This post walks through the dependencies needed and process to build and install
14`st` on Fedora Workstation.
15
16## Obtain Files
17
18To start, obtain the source files for `st` via `git clone`.
19
20```sh
21mkdir ~/suckless && cd ~/suckless
22git clone https://git.suckless.org/st && cd st
23```
24
25## Dependencies
26
27Once you have the files and are in the `st` directory, ensure the following
28packages are installed.
29
30```sh
31sudo dnf update && sudo dnf upgrade
32sudo dnf install gcc patch libX11-devel libXft-devel
33```
34
35## Building
36
37Before building, ensure that you read the README file.
38
39```sh
40cat README
41```
42
43Once you've read the instructions, open the `config.mk` file and ensure it
44matches your setup. If you're not sure, leave the default options within the
45file.
46
47Finally, you can build `st` with the following command. Ensure you run as root
48(e.g., `sudo`) or else you may not end up with a usable application file.
49
50```sh
51sudo make clean install
52```
53
54## Customization (Patches)
55
56Note that customizing `st` requires you to modify the source files or to
57download one of the [available patches](https://st.suckless.org/patches/) for
58suckless.org.
59
60If you've already installed `st` and want to customize or install a patch, start
61by uninstalling the current program.
62
63```sh
64cd ~/suckless/st
65sudo make uninstall
66```
67
68Next, grab the `<path>.diff` file from the page of the patch you chose. For
69example, I will be using the
70[defaultfontsize](https://st.suckless.org/patches/defaultfontsize/) patch in the
71below example.
72
73```sh
74wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
75```
76
77Once the file is downloaded inside the `st` folder, apply the patch and
78re-install the program. You may need to install the `patch` command if you don't
79have it installed already (you should have installed it above).
80
81```sh
82patch -i st-defaultfontsize-20210225-4ef0cbd.diff
83sudo make clean install
84```
85
86Once installed, you can use the default font size patch to launch `st` with any
87font size you wish:
88
89```sh
90st -z 16
91```
content/blog/2022-12-17-st.org added +92
@@ -0,0 +1,92 @@
1#+date: <2022-12-17>
2#+title: Simple Terminal
3#+description:
4
5
6* st
7
8[[https://st.suckless.org][st]] standards for Simple Terminal, a simple
9terminal implementation for X made by the
10[[https://suckless.org][suckless]] team.
11
12This post walks through the dependencies needed and process to build and
13install =st= on Fedora Workstation.
14
15** Obtain Files
16
17To start, obtain the source files for =st= via =git clone=.
18
19#+begin_src sh
20mkdir ~/suckless && cd ~/suckless
21git clone https://git.suckless.org/st && cd st
22#+end_src
23
24** Dependencies
25
26Once you have the files and are in the =st= directory, ensure the
27following packages are installed.
28
29#+begin_src sh
30sudo dnf update && sudo dnf upgrade
31sudo dnf install gcc patch libX11-devel libXft-devel
32#+end_src
33
34** Building
35
36Before building, ensure that you read the README file.
37
38#+begin_src sh
39cat README
40#+end_src
41
42Once you've read the instructions, open the =config.mk= file and ensure
43it matches your setup. If you're not sure, leave the default options
44within the file.
45
46Finally, you can build =st= with the following command. Ensure you run
47as root (e.g., =sudo=) or else you may not end up with a usable
48application file.
49
50#+begin_src sh
51sudo make clean install
52#+end_src
53
54** Customization (Patches)
55
56Note that customizing =st= requires you to modify the source files or to
57download one of the [[https://st.suckless.org/patches/][available
58patches]] for suckless.org.
59
60If you've already installed =st= and want to customize or install a
61patch, start by uninstalling the current program.
62
63#+begin_src sh
64cd ~/suckless/st
65sudo make uninstall
66#+end_src
67
68Next, grab the =<path>.diff= file from the page of the patch you chose.
69For example, I will be using the
70[[https://st.suckless.org/patches/defaultfontsize/][defaultfontsize]]
71patch in the below example.
72
73#+begin_src sh
74wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
75#+end_src
76
77Once the file is downloaded inside the =st= folder, apply the patch and
78re-install the program. You may need to install the =patch= command if
79you don't have it installed already (you should have installed it
80above).
81
82#+begin_src sh
83patch -i st-defaultfontsize-20210225-4ef0cbd.diff
84sudo make clean install
85#+end_src
86
87Once installed, you can use the default font size patch to launch =st=
88with any font size you wish:
89
90#+begin_src sh
91st -z 16
92#+end_src
content/blog/2022-12-23-alpine-desktop.md → content/blog/2022-12-23-alpine-desktop.org renamed +104 −102
@@ -1,90 +1,89 @@
1+++ 1#+date: <2022-12-23>
2date = 2022-12-23 2#+title: Alpine Linux as a Desktop OS
3title = "Alpine Linux as a Desktop OS" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Isn't Alpine Linux for Servers?
9 5
10This is a question I see a lot when people are presented with an example of 6* Isn't Alpine Linux for Servers?
11Alpine Linux running as a desktop OS.
12 7
13While Alpine is small, fast, and minimal, that doesn't stop it from functioning 8This is a question I see a lot when people are presented with an example
14at a productive level for desktop users. 9of Alpine Linux running as a desktop OS.
15 10
16This post is documentation of how I installed and modified Alpine Linux to 11While Alpine is small, fast, and minimal, that doesn't stop it from
17become my daily desktop OS. 12functioning at a productive level for desktop users.
18 13
19# Installation 14This post is documentation of how I installed and modified Alpine Linux
15to become my daily desktop OS.
20 16
21Note that I cover the installation of Alpine Linux in my other post, so I won't 17* Installation
22repeat it here: [Alpine Linux: My New Server OS](../alpine-linux/).
23 18
24Basically, get a bootable USB or whatever you prefer with Alpine on it, boot the 19Note that I cover the installation of Alpine Linux in my other post, so
25ISO, and run the setup script. 20I won't repeat it here: [[../alpine-linux/][Alpine Linux: My New Server
21OS]].
26 22
27```sh 23Basically, get a bootable USB or whatever you prefer with Alpine on it,
24boot the ISO, and run the setup script.
25
26#+begin_src sh
28setup-alpine 27setup-alpine
29``` 28#+end_src
30 29
31Once you have gone through all the options and installer finishes without 30Once you have gone through all the options and installer finishes
32errors, reboot. 31without errors, reboot.
33 32
34```sh 33#+begin_src sh
35reboot 34reboot
36``` 35#+end_src
37 36
38# Initial Setup 37* Initial Setup
39 38
40Once Alpine is installed and the machine has rebooted, login is as root 39Once 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 here, you should 40initially or =su= to root once you log in as your user. From here, you
42start by updating and upgrading the system in case the ISO was not fully 41should start by updating and upgrading the system in case the ISO was
43up-to-date. 42not fully up-to-date.
44 43
45```sh 44#+begin_src sh
46# Update and upgrade system 45# Update and upgrade system
47apk -U update && apk -U upgrade 46apk -U update && apk -U upgrade
48 47
49# Add an editor so we can enable the community repository 48# Add an editor so we can enable the community repository
50apk add nano 49apk add nano
51``` 50#+end_src
52 51
53You need to uncomment the `community` repository for your version of Alpine 52You need to uncomment the =community= repository for your version of
54Linux. 53Alpine Linux.
55 54
56For v3.17, the `repositories` file should look like this: 55For v3.17, the =repositories= file should look like this:
57 56
58```sh 57#+begin_src sh
59nano /etc/apk/repositories 58nano /etc/apk/repositories
60``` 59#+end_src
61 60
62```conf 61#+begin_src conf
63#/media/sda/apks 62#/media/sda/apks
64http://mirrors.gigenet.com/alpinelinux/v3.17/main 63http://mirrors.gigenet.com/alpinelinux/v3.17/main
65http://mirrors.gigenet.com/alpinelinux/v3.17/community 64http://mirrors.gigenet.com/alpinelinux/v3.17/community
66#http://mirrors.gigenet.com/alpinelinux/edge/main 65#http://mirrors.gigenet.com/alpinelinux/edge/main
67#http://mirrors.gigenet.com/alpinelinux/edge/community 66#http://mirrors.gigenet.com/alpinelinux/edge/community
68#http://mirrors.gigenet.com/alpinelinux/edge/testing 67#http://mirrors.gigenet.com/alpinelinux/edge/testing
69``` 68#+end_src
70 69
71```sh 70#+begin_src sh
72# Add the rest of your packages 71# Add the rest of your packages
73apk add linux-firmware iwd doas git curl wget 72apk add linux-firmware iwd doas git curl wget
74 73
75# Add yourself to the wheel group so you can use the doas command 74# Add yourself to the wheel group so you can use the doas command
76adduser $USER wheel 75adduser $USER wheel
77``` 76#+end_src
78 77
79# Window Manager (Desktop) 78* Window Manager (Desktop)
80 79
81The [Sway installation guide](https://wiki.alpinelinux.org/wiki/Sway) has 80The [[https://wiki.alpinelinux.org/wiki/Sway][Sway installation guide]]
82everything you need to get Sway working on Alpine. 81has everything you need to get Sway working on Alpine.
83 82
84However, I'll include a brief list of the commands I ran and their purpose for 83However, I'll include a brief list of the commands I ran and their
85posterity here. 84purpose for posterity here.
86 85
87```sh 86#+begin_src sh
88# Add eudev and set it up 87# Add eudev and set it up
89apk add eudev 88apk add eudev
90setup-devd udev 89setup-devd udev
@@ -121,21 +120,21 @@ apk add \ # Install optional dependencies:
121 swaylock swaylockd \ # lockscreen tool 120 swaylock swaylockd \ # lockscreen tool
122 swaybg \ # wallpaper daemon 121 swaybg \ # wallpaper daemon
123 swayidle # idle management (DPMS) daemon 122 swayidle # idle management (DPMS) daemon
124``` 123#+end_src
125 124
126Once you have the packages installed and set-up, you need to export the 125Once you have the packages installed and set-up, you need to export the
127`XDG_RUNTIME_DIR` upon login. To do this, edit your `.profile` file. 126=XDG_RUNTIME_DIR= upon login. To do this, edit your =.profile= file.
128 127
129If you use another shell, such as `zsh`, you need to edit that shell's profile 128If you use another shell, such as =zsh=, you need to edit that shell's
130(e.g., `~/.zprofile`)! 129profile (e.g., =~/.zprofile=)!
131 130
132```sh 131#+begin_src sh
133nano ~/.profile 132nano ~/.profile
134``` 133#+end_src
135 134
136Within the file, paste this: 135Within the file, paste this:
137 136
138```sh 137#+begin_src sh
139if test -z "${XDG_RUNTIME_DIR}"; then 138if test -z "${XDG_RUNTIME_DIR}"; then
140 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir 139 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir
141 if ! test -d "${XDG_RUNTIME_DIR}"; then 140 if ! test -d "${XDG_RUNTIME_DIR}"; then
@@ -143,19 +142,20 @@ if test -z "${XDG_RUNTIME_DIR}"; then
143 chmod 0700 "${XDG_RUNTIME_DIR}" 142 chmod 0700 "${XDG_RUNTIME_DIR}"
144 fi 143 fi
145fi 144fi
146``` 145#+end_src
147 146
148Once that's complete, you can launch Sway manually. 147Once that's complete, you can launch Sway manually.
149 148
150```sh 149#+begin_src sh
151dbus-run-session -- sway 150dbus-run-session -- sway
152``` 151#+end_src
153 152
154## Personal Touches 153** Personal Touches
155 154
156I also added the following packages, per my personal preferences and situation. 155I also added the following packages, per my personal preferences and
156situation.
157 157
158```sh 158#+begin_src sh
159doas apk add brightnessctl \ # Brightness controller 159doas apk add brightnessctl \ # Brightness controller
160 zsh \ # Shell 160 zsh \ # Shell
161 firefox \ # Browser 161 firefox \ # Browser
@@ -165,90 +165,92 @@ doas apk add brightnessctl \ # Brightness controller
165 neomutt \ # CLI email client 165 neomutt \ # CLI email client
166 thunderbird \ # GUI email client 166 thunderbird \ # GUI email client
167 gnupg # GPG key manager 167 gnupg # GPG key manager
168``` 168#+end_src
169 169
170From here, I use my Syncthing storage to pull all the configuration files I 170From here, I use my Syncthing storage to pull all the configuration
171stored from prior desktops, such as [my 171files I stored from prior desktops, such as
172dotfiles](https://git.sr.ht/~cyborg/dotfiles). 172[[https://git.sr.ht/~cyborg/dotfiles][my dotfiles]].
173 173
174# Resolving Issues 174* Resolving Issues
175 175
176## WiFi Issues 176** WiFi Issues
177 177
178I initially tried to set up my Wi-Fi the standard way with `iwd`, but it didn't 178I initially tried to set up my Wi-Fi the standard way with =iwd=, but it
179work. 179didn't work.
180 180
181Here is what I initially tried (I did all of this as `root`): 181Here is what I initially tried (I did all of this as =root=):
182 182
183```sh 183#+begin_src sh
184apk add iwd 184apk add iwd
185rc-service iwd start 185rc-service iwd start
186iwctl station wlan0 connect <SSID> # This will prompt for the password 186iwctl station wlan0 connect <SSID> # This will prompt for the password
187rc-update add iwd boot && rc-update add dbus boot 187rc-update add iwd boot && rc-update add dbus boot
188``` 188#+end_src
189 189
190Then, I added the Wi-Fi entry to the bottom of the networking interface file: 190Then, I added the Wi-Fi entry to the bottom of the networking interface
191file:
191 192
192```sh 193#+begin_src sh
193nano /etc/network/interfaces 194nano /etc/network/interfaces
194``` 195#+end_src
195 196
196```conf 197#+begin_src conf
197auto wlan0 198auto wlan0
198iface wlan0 inet dhcp 199iface wlan0 inet dhcp
199``` 200#+end_src
200 201
201Finally, restart the networking service: 202Finally, restart the networking service:
202 203
203```sh 204#+begin_src sh
204rc-service networking restart 205rc-service networking restart
205``` 206#+end_src
206 207
207My Wi-Fi interface would receive an IP address from the router, but it could not 208My Wi-Fi interface would receive an IP address from the router, but it
208ping anything in the network. To solve the Wi-Fi issues, I originally upgraded 209could not ping anything in the network. To solve the Wi-Fi issues, I
209to Alpine's `edge` repositories, which was unnecessary. 210originally upgraded to Alpine's =edge= repositories, which was
211unnecessary.
210 212
211Really, the solution was to enable the `NameResolvingService=resolvconf` in 213Really, the solution was to enable the =NameResolvingService=resolvconf=
212`/etc/iwd/main.conf`. 214in =/etc/iwd/main.conf=.
213 215
214```sh 216#+begin_src sh
215doas nano /etc/iwd/main.conf 217doas nano /etc/iwd/main.conf
216``` 218#+end_src
217 219
218```conf 220#+begin_src conf
219[Network] 221[Network]
220 222
221NameResolvingService=resolvconf 223NameResolvingService=resolvconf
222``` 224#+end_src
223 225
224Once I finished this process, my Wi-Fi is working flawlessly. 226Once I finished this process, my Wi-Fi is working flawlessly.
225 227
226## Sound Issues 228** Sound Issues
227 229
228Same as with the Wi-Fi, I had no sound and could not control the mute/unmute or 230Same as with the Wi-Fi, I had no sound and could not control the
229volume buttons on my laptop. 231mute/unmute or volume buttons on my laptop.
230 232
231To resolve this, I installed 233To resolve this, I installed
232[pipewire](https://wiki.alpinelinux.org/wiki/PipeWire). 234[[https://wiki.alpinelinux.org/wiki/PipeWire][pipewire]].
233 235
234```sh 236#+begin_src sh
235# Add your user to the following groups 237# Add your user to the following groups
236addgroup $USER audio 238addgroup $USER audio
237addgroup $USER video 239addgroup $USER video
238 240
239# Install pipewire and other useful packages 241# Install pipewire and other useful packages
240apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa 242apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa
241``` 243#+end_src
242 244
243Finally, I needed to add `/usr/libexec/pipewire-launcher` to my 245Finally, I needed to add =/usr/libexec/pipewire-launcher= to my
244`.config/sway/config` file so that Pipewire would run every time I launched 246=.config/sway/config= file so that Pipewire would run every time I
245sway. 247launched sway.
246 248
247```sh 249#+begin_src sh
248nano ~/.config/sway/config 250nano ~/.config/sway/config
249``` 251#+end_src
250 252
251```conf 253#+begin_src conf
252# Run pipewire audio server 254# Run pipewire audio server
253exec /usr/libexec/pipewire-launcher 255exec /usr/libexec/pipewire-launcher
254 256
@@ -257,10 +259,10 @@ bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT
257bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% 259bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5%
258bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle 260bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle
259bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle 261bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle
260``` 262#+end_src
261 263
262Note that I do not use bluetooth or screen sharing, so I won't cover those 264Note that I do not use bluetooth or screen sharing, so I won't cover
263options in this post. 265those options in this post.
264 266
265Other than these issues, I have a working Alpine desktop. No other complaints 267Other than these issues, I have a working Alpine desktop. No other
266thus far! 268complaints thus far!
content/blog/2023-01-03-recent-website-changes.md deleted −82
@@ -1,82 +0,0 @@
1+++
2date = 2023-01-03
3title = "Recent Website Changes"
4description = ""
5draft = false
6+++
7
8# The State of This Website
9
10Over the years, this website has changed URLs, styles, content focus, and much
11more. However, it seems that I am never done, as I am yet again changing this
12page to fit my current needs and wants.
13
14While this site was already minimal (~12kb), it contained a lot of disorganized
15content and some poorly chosen color schemes.
16
17The recent updates attempt to fix these items while focusing on what I truly
18care about here: the content within each page.
19
20# Recent Changes
21
22I've made quite a few changes to the style of this website today, both in the
23CSS and in the HTML.
24
25Here are some of the key takeaways from today's changes:
26
271. Reduce the width of the website from `60em` to `40em`.
282. Remove breadcrumb navigation and replaced with a simple "Return Home" link
29 when visiting anything other than the homepage.
303. Remove syntax highlighting from code blocks. CSS now loads from a single
31 file.
324. Move blog posts on the homepage to the top, pushing tertiary content down.
335. Update font-family from `monospace` to `sans-serif` for readability.
34
35# Future Focus
36
37## Accessibility
38
39My main focus amidst all the changes this past year was accessibility. I tried
40to improve the accessibility of all pages on this site as well as I know how.
41For example, I added aria labels to identify different navigation menus in
42November.
43
44I want to continue this in 2023 and learn more about the accessibility features
45I can utilize to help those who may need them.
46
47## Content First
48
49Beyond accessibility, I am making a concerted effort to bring the content of
50this site to the forefront and push the tertiary information (e.g., About Me,
51Services, etc.) down below the content.
52
53Further, I want to review and edit previous blog posts for grammar, accuracy,
54dead links, and more. Where necessary, I may even add a blurb at the top of old
55posts that are no longer accurate or helpful.
56
57## Style
58
59As always, I will be searching for ways to reduce distractions and bring a
60minimalistic approach to this site. While there are certainly more drastic
61measures I could take, such as making this a [Shinobi
62Website](https://shinobi.bt.ht) or a [No CSS Website](https://nocss.club), I
63prefer to keep some modern features here.
64
65Two ideas have been floating around in my head, but I am not sure how I feel
66about these approaches yet:
67
681. Going all-in with Emacs org-mode and creating a script to auto-publish all my
69 blog posts from org-mode to plain HTML files and then letting my CSS style it
70 after the fact.
712. Focus on publishing for Gemini or Gopher and then utilize a conversion
72 program to translate the pages to HTML.
73
74# Looking Back
75
76As I sit here and finalize the changes, as well as this post, I am happy with
77the current state of this website. It's certainly a long way from my first
78attempts (parallax background images, anyone?) and it's good to see the
79progress.
80
81I can only hope and work hard to ensure that I make more meaningful progress by
82this time next year.
content/blog/2023-01-03-recent-website-changes.org added +81
@@ -0,0 +1,81 @@
1#+date: <2023-01-03>
2#+title: Recent Website Changes
3#+description:
4
5
6* The State of This Website
7
8Over the years, this website has changed URLs, styles, content focus,
9and much more. However, it seems that I am never done, as I am yet again
10changing this page to fit my current needs and wants.
11
12While this site was already minimal (~12kb), it contained a lot of
13disorganized content and some poorly chosen color schemes.
14
15The recent updates attempt to fix these items while focusing on what I
16truly care about here: the content within each page.
17
18* Recent Changes
19
20I've made quite a few changes to the style of this website today, both
21in the CSS and in the HTML.
22
23Here are some of the key takeaways from today's changes:
24
251. Reduce the width of the website from =60em= to =40em=.
262. Remove breadcrumb navigation and replaced with a simple "Return Home"
27 link when visiting anything other than the homepage.
283. Remove syntax highlighting from code blocks. CSS now loads from a
29 single file.
304. Move blog posts on the homepage to the top, pushing tertiary content
31 down.
325. Update font-family from =monospace= to =sans-serif= for readability.
33
34* Future Focus
35
36** Accessibility
37
38My main focus amidst all the changes this past year was accessibility. I
39tried to improve the accessibility of all pages on this site as well as
40I know how. For example, I added aria labels to identify different
41navigation menus in November.
42
43I want to continue this in 2023 and learn more about the accessibility
44features I can utilize to help those who may need them.
45
46** Content First
47
48Beyond accessibility, I am making a concerted effort to bring the
49content of this site to the forefront and push the tertiary information
50(e.g., About Me, Services, etc.) down below the content.
51
52Further, I want to review and edit previous blog posts for grammar,
53accuracy, dead links, and more. Where necessary, I may even add a blurb
54at the top of old posts that are no longer accurate or helpful.
55
56** Style
57
58As always, I will be searching for ways to reduce distractions and bring
59a minimalistic approach to this site. While there are certainly more
60drastic measures I could take, such as making this a
61[[https://shinobi.bt.ht][Shinobi Website]] or a [[https://nocss.club][No
62CSS Website]], I prefer to keep some modern features here.
63
64Two ideas have been floating around in my head, but I am not sure how I
65feel about these approaches yet:
66
671. Going all-in with Emacs org-mode and creating a script to
68 auto-publish all my blog posts from org-mode to plain HTML files and
69 then letting my CSS style it after the fact.
702. Focus on publishing for Gemini or Gopher and then utilize a
71 conversion program to translate the pages to HTML.
72
73* Looking Back
74
75As I sit here and finalize the changes, as well as this post, I am happy
76with the current state of this website. It's certainly a long way from
77my first attempts (parallax background images, anyone?) and it's good to
78see the progress.
79
80I can only hope and work hard to ensure that I make more meaningful
81progress by this time next year.
content/blog/2023-01-05-mass-unlike-tumblr-posts.md deleted −92
@@ -1,92 +0,0 @@
1+++
2date = 2023-01-05
3title = " How to Easily Mass Unlike Tumblr Posts with Javascript"
4description = ""
5draft = false
6+++
7
8# The Dilemma
9
10The dilemma I had was pretty simple: I wanted to unlike all the posts I have
11liked on Tumblr so that I could follow a new focus on blogs and start fresh.
12Otherwise, Tumblr will keep recommending content based on your previous likes.
13
14# The Solution
15
16I searched the web for a while and noted that most solutions referenced Tumblr
17setting and dashboard pages that no longer exist. Additionally, I did not want
18to install a third party extension to do this, as some suggested.
19
20Luckily, I used Javascript for a while a few years ago and figured it would be
21easy enough to script a solution, as long as Tumblr had a system for the unlike
22buttons.
23
24## Identifying Unlike Buttons
25
26Tumblr's unlike buttons are structured as you can see in the following code
27block. All unlike buttons have an `aria-label` with a value of `Unlike`.
28
29```html
30<button class="TRX6J" aria-label="Unlike">
31 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1">
32 <svg
33 xmlns="http://www.w3.org/2000/svg"
34 height="21"
35 width="23"
36 role="presentation"
37 >
38 <use href="#managed-icon__like-filled"></use>
39 </svg>
40 </span>
41</button>
42```
43
44## Running a Script to Unlike All Likes
45
46To run this script, you will need to load the [Likes \|
47Tumblr](https://www.tumblr.com/likes) page while logged in to your account.
48
49Further, be sure to scroll down to the bottom and force Tumblr to load more
50posts so that this script unlikes more posts at a time.
51
52Once you are logged in and the page is loaded, open the Developer Tools and be
53sure you're on the "Console" tab. It should look something like this (this is in
54Firefox, Chromium should be similar):
55
56![Firefox Dev
57!Tools](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png)
58
59All you need to do is paste the following snippet into the dev console. This
60code will collect all unlike buttons (`elements`) and then click each button to
61unlike it.
62
63Optionally, you can comment-out the line `elements[i].click();` and uncomment
64the `console.log()` lines to simply print out information without performing any
65actions. This can be useful to debug issues or confirm that the code below isn't
66doing anything you don't want it to.
67
68```javascript
69const elements = document.querySelectorAll('[aria-label="Unlike"]');
70// console.log(elements); // 👉 [button]
71
72for (let i = 0; i < elements.length; i++) {
73 // console.log(elements[i]);
74 elements[i].click();
75}
76```
77
78# Results
79
80The results were quick for my situation, as it unliked ~200 posts within 2-3
81seconds. I am not sure how this will perform on larger sets of likes (or if
82Tumblr has a limit to unliking posts).
83
84You can see the below screenshot showing that I pasted the snippet into the
85console, pressed Enter, and then the posts are automatically unliked.
86
87![Script
88!Results](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png)
89
90Thinking about this further, I would bet that this would be fairly simple to
91package into a browser add-on so that users could install the add-on, go to
92their Likes page, and click a button to run the script. Food for thought.
content/blog/2023-01-05-mass-unlike-tumblr-posts.org added +96
@@ -0,0 +1,96 @@
1#+date: <2023-01-05>
2#+title: How to Easily Mass Unlike Tumblr Posts with Javascript
3#+description:
4
5
6* The Dilemma
7
8The dilemma I had was pretty simple: I wanted to unlike all the posts I
9have liked on Tumblr so that I could follow a new focus on blogs and
10start fresh. Otherwise, Tumblr will keep recommending content based on
11your previous likes.
12
13* The Solution
14
15I searched the web for a while and noted that most solutions referenced
16Tumblr setting and dashboard pages that no longer exist. Additionally, I
17did not want to install a third party extension to do this, as some
18suggested.
19
20Luckily, I used Javascript for a while a few years ago and figured it
21would be easy enough to script a solution, as long as Tumblr had a
22system for the unlike buttons.
23
24** Identifying Unlike Buttons
25
26Tumblr's unlike buttons are structured as you can see in the following
27code block. All unlike buttons have an =aria-label= with a value of
28=Unlike=.
29
30#+begin_src html
31<button class="TRX6J" aria-label="Unlike">
32 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1">
33 <svg
34 xmlns="http://www.w3.org/2000/svg"
35 height="21"
36 width="23"
37 role="presentation"
38 >
39 <use href="#managed-icon__like-filled"></use>
40 </svg>
41 </span>
42</button>
43#+end_src
44
45** Running a Script to Unlike All Likes
46
47To run this script, you will need to load the
48[[https://www.tumblr.com/likes][Likes | Tumblr]] page while logged in to
49your account.
50
51Further, be sure to scroll down to the bottom and force Tumblr to load
52more posts so that this script unlikes more posts at a time.
53
54Once you are logged in and the page is loaded, open the Developer Tools
55and be sure you're on the "Console" tab. It should look something like
56this (this is in Firefox, Chromium should be similar):
57
58#+caption: Firefox Dev !Tools
59[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png]]
60
61All you need to do is paste the following snippet into the dev console.
62This code will collect all unlike buttons (=elements=) and then click
63each button to unlike it.
64
65Optionally, you can comment-out the line =elements[i].click();= and
66uncomment the =console.log()= lines to simply print out information
67without performing any actions. This can be useful to debug issues or
68confirm that the code below isn't doing anything you don't want it to.
69
70#+begin_src javascript
71const elements = document.querySelectorAll('[aria-label="Unlike"]');
72// console.log(elements); // 👉 [button]
73
74for (let i = 0; i < elements.length; i++) {
75 // console.log(elements[i]);
76 elements[i].click();
77}
78#+end_src
79
80* Results
81
82The results were quick for my situation, as it unliked ~200 posts within
832-3 seconds. I am not sure how this will perform on larger sets of likes
84(or if Tumblr has a limit to unliking posts).
85
86You can see the below screenshot showing that I pasted the snippet into
87the console, pressed Enter, and then the posts are automatically
88unliked.
89
90#+caption: Script !Results
91[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png]]
92
93Thinking about this further, I would bet that this would be fairly
94simple to package into a browser add-on so that users could install the
95add-on, go to their Likes page, and click a button to run the script.
96Food for thought.
content/blog/2023-01-08-fedora-login-manager.md deleted −45
@@ -1,45 +0,0 @@
1+++
2date = 2023-01-08
3title = "How to Remove the Login Manager from Fedora i3"
4description = ""
5draft = false
6+++
7
8# Fedora i3's Login Manager
9
10Since I use the i3 spin of Fedora Workstation, I don't like to have a login
11manager installed by default. As of the current version of Fedora i3, the
12default login manager is LightDM.
13
14If this is no longer the case, you can search for currently-installed packages
15with the following command and see if you can identify a different login
16manager.
17
18```sh
19sudo dnf list installed
20```
21
22# Removing the Login Manager
23
24In order to remove the login manager, simply uninstall the package.
25
26```sh
27sudo dnf remove lightdm
28```
29
30# Launching i3 Manually
31
32In order to launch i3 manually, you need to set up your X session properly. To
33start, create or edit the `~/.xinitrc` file to include the following at the
34bottom.
35
36```config
37exec i3
38```
39
40Now, whenever you log in to the TTY, you can launch your desktop with the
41following command.
42
43```sh
44startx
45```
content/blog/2023-01-08-fedora-login-manager.org added +43
@@ -0,0 +1,43 @@
1#+date: <2023-01-08>
2#+title: How to Remove the Login Manager from Fedora i3
3#+description:
4
5
6* Fedora i3's Login Manager
7
8Since I use the i3 spin of Fedora Workstation, I don't like to have a
9login manager installed by default. As of the current version of Fedora
10i3, the default login manager is LightDM.
11
12If this is no longer the case, you can search for currently-installed
13packages with the following command and see if you can identify a
14different login manager.
15
16#+begin_src sh
17sudo dnf list installed
18#+end_src
19
20* Removing the Login Manager
21
22In order to remove the login manager, simply uninstall the package.
23
24#+begin_src sh
25sudo dnf remove lightdm
26#+end_src
27
28* Launching i3 Manually
29
30In order to launch i3 manually, you need to set up your X session
31properly. To start, create or edit the =~/.xinitrc= file to include the
32following at the bottom.
33
34#+begin_src config
35exec i3
36#+end_src
37
38Now, whenever you log in to the TTY, you can launch your desktop with
39the following command.
40
41#+begin_src sh
42startx
43#+end_src
content/blog/2023-01-21-flatpak-symlinks.md deleted −46
@@ -1,46 +0,0 @@
1+++
2date = 2023-01-21
3title = "Running Flatpak Apps with Symlinks"
4description = ""
5draft = false
6+++
7
8# Running Flatpak Apps Should Be Faster
9
10If you're like me and use Flatpak for those pesky apps that cannot run on your
11system for one reason or another, you likely get annoyed with opening a terminal
12and manually running the Flatpak app with the lengthy `flatpak run ...` command.
13
14In the past, I manually created aliases in my `.zshrc` file for certain apps.
15For example, an alias would look like the example below.
16
17This would allow me to run the command fast within the terminal, but it wouldn't
18allow me to run it in an application launcher.
19
20```sh
21# ~/.zshrc
22alias librewolf = "flatpak run io.gitlab.librewolf-community"
23```
24
25However, I now use a much faster and better method that integrates with the
26tiling WMs I use and their application launchers - `dmenu` and `bemenu`.
27
28# Creating Symlinks for Flatpak Apps
29
30Let's use the example of Librewolf below. I can install the application like so:
31
32```sh
33flatpak install flathub io.gitlab.librewolf-community
34```
35
36Once installed, I can create a symlink to link the flatpak app to my new symlink
37in a location commonly included in your PATH. In this case, I chose `/usr/bin`.
38You may need to choose a different location if `/usr/bin` isn't in your PATH.
39
40```sh
41ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf
42```
43
44Once complete, you should be able to launch the app using the command name you
45chose above in the symlink (`librewolf`) from a terminal or from your
46application launcher!
content/blog/2023-01-21-flatpak-symlinks.org added +48
@@ -0,0 +1,48 @@
1#+date: <2023-01-21>
2#+title: Running Flatpak Apps with Symlinks
3#+description:
4
5
6* Running Flatpak Apps Should Be Faster
7
8If you're like me and use Flatpak for those pesky apps that cannot run
9on your system for one reason or another, you likely get annoyed with
10opening a terminal and manually running the Flatpak app with the lengthy
11=flatpak run ...= command.
12
13In the past, I manually created aliases in my =.zshrc= file for certain
14apps. For example, an alias would look like the example below.
15
16This would allow me to run the command fast within the terminal, but it
17wouldn't allow me to run it in an application launcher.
18
19#+begin_src sh
20# ~/.zshrc
21alias librewolf = "flatpak run io.gitlab.librewolf-community"
22#+end_src
23
24However, I now use a much faster and better method that integrates with
25the tiling WMs I use and their application launchers - =dmenu= and
26=bemenu=.
27
28* Creating Symlinks for Flatpak Apps
29
30Let's use the example of Librewolf below. I can install the application
31like so:
32
33#+begin_src sh
34flatpak install flathub io.gitlab.librewolf-community
35#+end_src
36
37Once installed, I can create a symlink to link the flatpak app to my new
38symlink in a location commonly included in your PATH. In this case, I
39chose =/usr/bin=. You may need to choose a different location if
40=/usr/bin= isn't in your PATH.
41
42#+begin_src sh
43ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf
44#+end_src
45
46Once complete, you should be able to launch the app using the command
47name you chose above in the symlink (=librewolf=) from a terminal or
48from your application launcher!
content/blog/2023-01-23-random-wireguard.md → content/blog/2023-01-23-random-wireguard.org renamed +53 −53
@@ -1,47 +1,47 @@
1+++ 1#+date: <2023-01-23>
2date = 2023-01-23 2#+title: Connecting to a Random Mullvad Wireguard Host on Boot
3title = "Connecting to a Random Mullvad Wireguard Host on Boot" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Mullvad Wireguard
9 5
10If you're using an OS that does not support one of Mullvad's apps, you're likely 6* Mullvad Wireguard
11using the Wireguard configuration files instead.
12 7
13If not, the first step is to visit Mullvad's [Wireguard configuration 8If you're using an OS that does not support one of Mullvad's apps,
14files](https://mullvad.net/en/account/#/wireguard-config) page and download a 9you're likely using the Wireguard configuration files instead.
15ZIP of the configuration files you want to use.
16 10
17Personally, I downloaded all configuration files across the world and chose my 11If not, the first step is to visit Mullvad's
18connections using the script below. 12[[https://mullvad.net/en/account/#/wireguard-config][Wireguard
13configuration files]] page and download a ZIP of the configuration files
14you want to use.
19 15
20Once the files are downloaded, unzip them and move them to your preferred 16Personally, I downloaded all configuration files across the world and
21location: 17chose my connections using the script below.
22 18
23```sh 19Once the files are downloaded, unzip them and move them to your
20preferred location:
21
22#+begin_src sh
24cd Downloads 23cd Downloads
25unzip mullvad_wireguard_linux_all_all.zip 24unzip mullvad_wireguard_linux_all_all.zip
26mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/ 25mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/
27``` 26#+end_src
28 27
29### Creating a Script to Connect to a Random Host 28*** Creating a Script to Connect to a Random Host
30 29
31Once you have a folder of Wireguard configuration files from Mullvad, you can 30Once you have a folder of Wireguard configuration files from Mullvad,
32create a script to randomly connect to any one of the locations. 31you can create a script to randomly connect to any one of the locations.
33 32
34Start by creating a shell script - mine is called `vpn.sh`. 33Start by creating a shell script - mine is called =vpn.sh=.
35 34
36```sh 35#+begin_src sh
37nano ~/vpn.sh 36nano ~/vpn.sh
38``` 37#+end_src
39 38
40Within this script, you can paste the following info. Note that I specify `us-*` 39Within this script, you can paste the following info. Note that I
41in my script, which means that it will only consider US-based VPN locations. You 40specify =us-*= in my script, which means that it will only consider
42can alter this or simply change it `*` to consider all locations. 41US-based VPN locations. You can alter this or simply change it =*= to
42consider all locations.
43 43
44```sh 44#+begin_src sh
45#!/bin/sh 45#!/bin/sh
46 46
47ls /home/$USER/mullvad/us-** |sort -R |tail -n 1 |while read file; do 47ls /home/$USER/mullvad/us-** |sort -R |tail -n 1 |while read file; do
@@ -52,19 +52,19 @@ ls /home/$USER/mullvad/us-** |sort -R |tail -n 1 |while read file; do
52 printf "\n\nPrinting new IP info:\n" 52 printf "\n\nPrinting new IP info:\n"
53 curl https://am.i.mullvad.net/connected 53 curl https://am.i.mullvad.net/connected
54done 54done
55``` 55#+end_src
56 56
57Once you've modified the script to your liking, add executable permissions and 57Once you've modified the script to your liking, add executable
58run the script: 58permissions and run the script:
59 59
60```sh 60#+begin_src sh
61chmod +x ~/vpn.sh 61chmod +x ~/vpn.sh
62~/vpn.sh 62~/vpn.sh
63``` 63#+end_src
64 64
65The output should look like the following: 65The output should look like the following:
66 66
67```txt 67#+begin_src txt
68doas (user@host) password: 68doas (user@host) password:
69 69
70# ... The script will process all of the iptables and wg commands here 70# ... The script will process all of the iptables and wg commands here
@@ -73,40 +73,40 @@ Created Mullvad wireguard connection with file: /home/user/mullvad/us-nyc-wg-210
73 73
74Printing new IP info: 74Printing new IP info:
75You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99 75You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99
76``` 76#+end_src
77 77
78That's all there is to it. You can see your new location and IP via the `printf` 78That's all there is to it. You can see your new location and IP via the
79and `curl` commands included in the script. 79=printf= and =curl= commands included in the script.
80 80
81You can also go to the [Connection Check ​\| 81You can also go to the [[https://mullvad.net/en/check/][Connection Check
82Mullvad](https://mullvad.net/en/check/) page to see if you are fully connected 82​| Mullvad]] page to see if you are fully connected to Mullvad and if any
83to Mullvad and if any leaks exist. 83leaks exist.
84 84
85# Disconnecting from the Wireguard Connection 85* Disconnecting from the Wireguard Connection
86 86
87If you forget which connection you're using, you can execute the following 87If you forget which connection you're using, you can execute the
88command to see where Wireguard is currently connected: 88following command to see where Wireguard is currently connected:
89 89
90```sh 90#+begin_src sh
91wg show 91wg show
92``` 92#+end_src
93 93
94This command will show you the Wireguard interfaces and should output a 94This command will show you the Wireguard interfaces and should output a
95connection like so: `interface: us-lax-wg-104`. 95connection like so: =interface: us-lax-wg-104=.
96 96
97Once you have this, just disconnect using that files' full path: 97Once you have this, just disconnect using that files' full path:
98 98
99```sh 99#+begin_src sh
100wg-quick down /home/user/mullvad/us-lax-wg-104.conf 100wg-quick down /home/user/mullvad/us-lax-wg-104.conf
101``` 101#+end_src
102 102
103I have a TODO item on figuring out how to easily export an environment variable 103I have a TODO item on figuring out how to easily export an environment
104that contains the configuration file's full name, so that I can just execute the 104variable that contains the configuration file's full name, so that I can
105following: 105just execute the following:
106 106
107```sh 107#+begin_src sh
108# 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
109wg-quick down $file 109wg-quick down $file
110``` 110#+end_src
111 111
112If 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-28-self-hosting-wger.md deleted −142
@@ -1,142 +0,0 @@
1+++
2date = 2023-01-28
3title = "Self-Hosting Wger Workout Manager"
4description = ""
5draft = false
6+++
7
8# Wger: The Self-Hosted Workout Manager
9
10[Wger Workout Manager](https://wger.de) is a fitness tracking tool for those who
11enjoy self-hosting their data. You can also register an account on their main
12website if you'd prefer to try without self-hosting.
13
14## Features
15
16I didn't see a full listing of features anywhere, so I compiled this list of my
17own after installing wger:
18
191. Dashboard
20
21 - Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, & last
22 5 Weight Logs
23
242. Training
25
26 - Workout Log
27 - Workout Schedule
28 - Calendar (shows weight logs and Bad/Neutral/Good days)
29 - Gallery (shows images you upload)
30 - Workout templates
31 - Public templates
32 - Exercises
33
343. Nutrition
35
36 - Nutrition plans
37 - BMI calculator
38 - Daily calories calculator
39 - Ingredient overview
40
414. Body Weight
42
43 - Weight overview
44
45## Documentation
46
47In order to self-host wger, I opted to use the Docker version of the
48application. You can read the README within the
49[wger-project/docker](https://github.com/wger-project/docker) project on GitHub
50for information and installation instructions.
51
52## Installation
53
54To start the installation, I created a folder for wger and started creating the
55three necessary files:
56
57```sh
58mkdir ~/wger && mkdir ~/wger/config
59touch ~/wger/docker-compose.yml && \
60touch ~/wger/config/prod.env && \
61touch ~/wger/config/nginx.conf
62```
63
64Once you have the folders and files created, you will need to copy the contents
65of the `docker-compose.yml`, `prod.env`, and `nginx.conf` from the GitHub link
66above.
67
68A few notes to explain the changes I made to the default files:
69
70- I updated the `ALLOW_REGISTRAION` variable in `prod.env` to `False` after I
71 created an account via my LAN connection, **before** I connected this app to
72 a publicly-available domain.
73- I uncommented and updated `CSRF_TRUSTED_ORIGINS` to be equal to the public
74 version of this app: `https://wger.example.com`.
75- I updated the port within `docker-compose.yml`, within the `nginx` block.
76 The port I updated this to will be reflected in my nginx configuration file
77 on the server (NOT the wger nginx.conf file).
78
79## Deploy
80
81Once all files are created and modified to your needs, simply start the
82container.
83
84```sh
85docker-compose up -d
86```
87
88You can now visit the website on your LAN by going to `localhost:YOUR_PORT` or
89by the server's IP, if you're not on the same machine that is running the
90container.
91
92If you wish to connect this app to a public domain name, you'll need to point an
93`A` DNS record from the domain to your server's public IP. You'll then need to
94create a configuration file for whichever web server or reverse proxy you're
95using.
96
97Wger's README suggests the following reverse proxy configuration for Nginx:
98
99```conf
100upstream wger {
101 # This port should match the port in the `nginx` block of docker-compose.yml
102 # If the container is running on this same machine, replace this with
103 # server 127.0.0.1:8080
104 server 123.456.789.0:8080;
105}
106
107server {
108 listen 80;
109 listen [::]:443 ssl;
110 listen 443 ssl;
111
112 location / {
113 proxy_pass http://wger;
114 proxy_set_header Host $http_host;
115 proxy_set_header X-Real-IP $remote_addr;
116 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
117 proxy_set_header X-Forwarded-Proto $scheme;
118 proxy_redirect off;
119 }
120
121 server_name my.domain.example.com;
122
123 ssl_certificate /path/to/https/certificate.crt;
124 ssl_certificate_key /path/to/https/certificate.key;
125}
126```
127
128# Thoughts on Wger
129
130I'm still playing around with the app itself, but it seems to be a solid
131all-around workout manager, weight log, and food log.
132
133I like that the weight log graph is fluid and updates quickly. You can also
134import or export data in CSV format if you'd like to move your data elsewhere.
135
136The workout manager is slightly odd, as it requires you to enter sets and reps
137for each exercise when you enter it into the plan. Then, when you add a log
138entry for performing a workout, you then add what you actually performed, in
139terms of reps and weight.
140
141I haven't tried the food log yet and I likely will not, at least for a while. I
142have no need for a food log or calorie tracker at the moment.
content/blog/2023-01-28-self-hosting-wger.org added +143
@@ -0,0 +1,143 @@
1#+date: <2023-01-28>
2#+title: Self-Hosting Wger Workout Manager
3#+description:
4
5
6* Wger: The Self-Hosted Workout Manager
7
8[[https://wger.de][Wger Workout Manager]] is a fitness tracking tool for
9those who enjoy self-hosting their data. You can also register an
10account on their main website if you'd prefer to try without
11self-hosting.
12
13** Features
14
15I didn't see a full listing of features anywhere, so I compiled this
16list of my own after installing wger:
17
181. Dashboard
19
20 - Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, &
21 last 5 Weight Logs
22
232. Training
24
25 - Workout Log
26 - Workout Schedule
27 - Calendar (shows weight logs and Bad/Neutral/Good days)
28 - Gallery (shows images you upload)
29 - Workout templates
30 - Public templates
31 - Exercises
32
333. Nutrition
34
35 - Nutrition plans
36 - BMI calculator
37 - Daily calories calculator
38 - Ingredient overview
39
404. Body Weight
41
42 - Weight overview
43
44** Documentation
45
46In order to self-host wger, I opted to use the Docker version of the
47application. You can read the README within the
48[[https://github.com/wger-project/docker][wger-project/docker]] project
49on GitHub for information and installation instructions.
50
51** Installation
52
53To start the installation, I created a folder for wger and started
54creating the three necessary files:
55
56#+begin_src sh
57mkdir ~/wger && mkdir ~/wger/config
58touch ~/wger/docker-compose.yml && \
59touch ~/wger/config/prod.env && \
60touch ~/wger/config/nginx.conf
61#+end_src
62
63Once you have the folders and files created, you will need to copy the
64contents of the =docker-compose.yml=, =prod.env=, and =nginx.conf= from
65the GitHub link above.
66
67A few notes to explain the changes I made to the default files:
68
69- I updated the =ALLOW_REGISTRAION= variable in =prod.env= to =False=
70 after I created an account via my LAN connection, *before* I connected
71 this app to a publicly-available domain.
72- I uncommented and updated =CSRF_TRUSTED_ORIGINS= to be equal to the
73 public version of this app: =https://wger.example.com=.
74- I updated the port within =docker-compose.yml=, within the =nginx=
75 block. The port I updated this to will be reflected in my nginx
76 configuration file on the server (NOT the wger nginx.conf file).
77
78** Deploy
79
80Once all files are created and modified to your needs, simply start the
81container.
82
83#+begin_src sh
84docker-compose up -d
85#+end_src
86
87You can now visit the website on your LAN by going to
88=localhost:YOUR_PORT= or by the server's IP, if you're not on the same
89machine that is running the container.
90
91If you wish to connect this app to a public domain name, you'll need to
92point an =A= DNS record from the domain to your server's public IP.
93You'll then need to create a configuration file for whichever web server
94or reverse proxy you're using.
95
96Wger's README suggests the following reverse proxy configuration for
97Nginx:
98
99#+begin_src conf
100upstream wger {
101 # This port should match the port in the `nginx` block of docker-compose.yml
102 # If the container is running on this same machine, replace this with
103 # server 127.0.0.1:8080
104 server 123.456.789.0:8080;
105}
106
107server {
108 listen 80;
109 listen [::]:443 ssl;
110 listen 443 ssl;
111
112 location / {
113 proxy_pass http://wger;
114 proxy_set_header Host $http_host;
115 proxy_set_header X-Real-IP $remote_addr;
116 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
117 proxy_set_header X-Forwarded-Proto $scheme;
118 proxy_redirect off;
119 }
120
121 server_name my.domain.example.com;
122
123 ssl_certificate /path/to/https/certificate.crt;
124 ssl_certificate_key /path/to/https/certificate.key;
125}
126#+end_src
127
128* Thoughts on Wger
129
130I'm still playing around with the app itself, but it seems to be a solid
131all-around workout manager, weight log, and food log.
132
133I like that the weight log graph is fluid and updates quickly. You can
134also import or export data in CSV format if you'd like to move your data
135elsewhere.
136
137The workout manager is slightly odd, as it requires you to enter sets
138and reps for each exercise when you enter it into the plan. Then, when
139you add a log entry for performing a workout, you then add what you
140actually performed, in terms of reps and weight.
141
142I haven't tried the food log yet and I likely will not, at least for a
143while. I have no need for a food log or calorie tracker at the moment.
content/blog/2023-02-02-exploring-hare.md deleted −161
@@ -1,161 +0,0 @@
1+++
2date = 2023-02-02
3title = "Exploring the Hare Programming Language"
4description = ""
5draft = false
6+++
7
8# A Quick Note
9
10By no means am I a professional developer, so this post will be rather short. I
11won't be going into depth on the specification or anything that technical.
12
13Instead, I will simply be talking about how I (a relatively basic hobbyist
14programmer) have been playing with Hare and what intrigues me about the
15language.
16
17# Hare
18
19The [Hare](https://harelang.org) programming language is a straightforward
20language that should look familiar if you've ever programmed with C, Rust, or
21other languages that aim to build software at the system-level.
22
23The Hare homepage states the following:
24
25> Hare is a systems programming language designed to be simple, stable, and
26> robust. Hare uses a static type system, manual memory management, and minimal
27> runtime. It is well-suited to writing operating systems, system tools,
28> compilers, networking software, and other low-level, high performance tasks.
29
30I have found this all to be true while playing with it for the first time today.
31In the next few sections, I'm going to walk through my installation and first
32program.
33
34## Installation
35
36I'm currently running Alpine Linux on my Thinkpad, so the installation was quite
37easy as there is a package for Hare in the `apk` repositories.
38
39```sh
40doas apk add hare hare-doc
41```
42
43However, I was able to install Hare from scratch on Fedora Linux a short while
44ago, which was also very easy to do. If you need further instructions and Hare
45doesn't have a package on your system, take a look at the [Hare
46Installation](https://harelang.org/installation/) page.
47
48## Creating a Test Project
49
50In order to play with the language, I created
51[hare-test](https://git.sr.ht/~cyborg/learning-hare) and will be putting any of my
52Hare-related adventures in here.
53
54> **Update:** I also created a simple Hare program for creating a file from user
55> input:
56> [files.ha](hhttps://git.sr.ht/~cyborg/learning-hare/blob/main/files/files.ha)
57
58Luckily, Hare doesn't require any complex set-up tools or build environment.
59Once you have Hare installed, you simply need to create a file ending with `.ha`
60and you can run a Hare program.
61
62I created a file called `rgb.ha` in order to test out the random number
63generation and passing parameters between functions.
64
65```sh
66nano rgb.ha
67```
68
69Within this file, I was able to easily import a few of the [standard library
70modules](https://harelang.org/tutorials/stdlib/): `fmt`, `math::random`, and
71`datetime`.
72
73With these modules, I created two functions:
74
751. `main`: This function calls the `generate_rgb` function and then prints out
76 the returned values.
772. `generate_rgb`: This function uses the current Unix epoch time to generate a
78 pseudo-random value and uses this value to create three more random values
79 between 0 and 255. These three numbers represent a color in RGB format.
80
81> **Note**: Some syntax coloring may look odd, as Zola currently doesn't have a
82> syntax highlighting theme for Hare. Instead, I'm using the C theme, which may
83> not be exactly accurate when coloring the code below.
84
85```c
86use datetime;
87use fmt;
88use math::random;
89
90export fn main() void = {
91 const rgb = generate_rgb();
92 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
93};
94
95fn generate_rgb() []u64 = {
96 // Use the current Unix epoch time as the seed value
97 let datetime = datetime::epochunix(&datetime::now());
98
99 // Generate initial pseudo-random value
100 // You must cast the datetime from int to u64
101 let x = random::init(datetime: u64);
102
103 // Generate RGB values between (0, 255) using pseudo-random init value
104 let r = random::u64n(&x, 255);
105 let g = random::u64n(&x, 255);
106 let b = random::u64n(&x, 255);
107
108 // Structure data as array and return
109 let rgb_array: [3]u64 = [r, g, b];
110 return rgb_array;
111};
112```
113
114## Running a Program
115
116Once you have a Hare file written and ready to run, you simply need to run it:
117
118```sh
119hare run file.ha
120```
121
122You can also compile the program into an executable:
123
124```sh
125hare build -o example file.ha
126./example
127```
128
129## Initial Thoughts
130
1311. Documentation Improvements Would Help
132
133 While I was able to piece everything together eventually, the biggest
134 downfall right now in Hare's documentation. For such a new project, the
135 documentation is in a great spot. However, bare specifications don't help as
136 much as a brief examples section would.
137
138 For example, it took me a while to figure out what the `u64n` function was
139 looking for. I could tell that it took two parameters and the second was my
140 max value (255), but couldn't figure out what the first value should be.
141 Eventually, I inspected the `random.ha` file in the [Hare source
142 code](https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha)
143 and found the test suite that helped me discover that it needed an `init()`
144 value in the form of `&var`.
145
1462. More Basic Modules
147
148 This is another point that comes from Hare being new and awaiting more
149 contributions, but there are some basic functions that I would personally
150 enjoy seeing in Hare, such as one to convert decimal (base 10) values to
151 hexadecimal (base 16).
152
153 If I'm feeling comfortable with my math, I may work on the list of functions
154 I want and see if any can make it into the Hare source code.
155
1563. Overall Thoughts
157
158 Overall, I actually really enjoy Hare. It's not as tedious to get a project
159 up and running as Rust, but it's also simpler and more user-friendly than
160 learning C. I am going to continue playing with it and see if I can make
161 anything of particular value.
content/blog/2023-02-02-exploring-hare.org added +172
@@ -0,0 +1,172 @@
1#+date: <2023-02-02>
2#+title: Exploring the Hare Programming Language
3#+description:
4
5
6* A Quick Note
7
8By no means am I a professional developer, so this post will be rather
9short. I won't be going into depth on the specification or anything that
10technical.
11
12Instead, I will simply be talking about how I (a relatively basic
13hobbyist programmer) have been playing with Hare and what intrigues me
14about the language.
15
16* Hare
17
18The [[https://harelang.org][Hare]] programming language is a
19straightforward language that should look familiar if you've ever
20programmed with C, Rust, or other languages that aim to build software
21at the system-level.
22
23The Hare homepage states the following:
24
25#+begin_quote
26Hare is a systems programming language designed to be simple, stable,
27and robust. Hare uses a static type system, manual memory management,
28and minimal runtime. It is well-suited to writing operating systems,
29system tools, compilers, networking software, and other low-level, high
30performance tasks.
31#+end_quote
32
33I have found this all to be true while playing with it for the first
34time today. In the next few sections, I'm going to walk through my
35installation and first program.
36
37** Installation
38
39I'm currently running Alpine Linux on my Thinkpad, so the installation
40was quite easy as there is a package for Hare in the =apk= repositories.
41
42#+begin_src sh
43doas apk add hare hare-doc
44#+end_src
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 look
49at the [[https://harelang.org/installation/][Hare Installation]] page.
50
51** Creating a Test Project
52
53In order to play with the language, I created
54[[https://git.sr.ht/~cyborg/learning-hare][hare-test]] and will be
55putting any of my Hare-related adventures in here.
56
57#+begin_quote
58*Update:* I also created a simple Hare program for creating a file from
59user input:
60[[https://git.sr.ht/~cyborg/learning-hare/blob/main/files/files.ha][files.ha]]
61#+end_quote
62
63Luckily, Hare doesn't require any complex set-up tools or build
64environment. Once you have Hare installed, you simply need to create a
65file ending with =.ha= and you can run a Hare program.
66
67I created a file called =rgb.ha= in order to test out the random number
68generation and passing parameters between functions.
69
70#+begin_src sh
71nano rgb.ha
72#+end_src
73
74Within this file, I was able to easily import a few of the
75[[https://harelang.org/tutorials/stdlib/][standard library modules]]:
76=fmt=, =math::random=, and =datetime=.
77
78With these modules, I created two functions:
79
801. =main=: This function calls the =generate_rgb= function and then
81 prints out the returned values.
822. =generate_rgb=: This function uses the current Unix epoch time to
83 generate a pseudo-random value and uses this value to create three
84 more random values between 0 and 255. These three numbers represent a
85 color in RGB format.
86
87#+begin_quote
88*Note*: Some syntax coloring may look odd, as Zola currently doesn't
89have a syntax highlighting theme for Hare. Instead, I'm using the C
90theme, which may not be exactly accurate when coloring the code below.
91#+end_quote
92
93#+begin_src C
94use datetime;
95use fmt;
96use math::random;
97
98export fn main() void = {
99 const rgb = generate_rgb();
100 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
101};
102
103fn generate_rgb() []u64 = {
104 // Use the current Unix epoch time as the seed value
105 let datetime = datetime::epochunix(&datetime::now());
106
107 // Generate initial pseudo-random value
108 // You must cast the datetime from int to u64
109 let x = random::init(datetime: u64);
110
111 // Generate RGB values between (0, 255) using pseudo-random init value
112 let r = random::u64n(&x, 255);
113 let g = random::u64n(&x, 255);
114 let b = random::u64n(&x, 255);
115
116 // Structure data as array and return
117 let rgb_array: [3]u64 = [r, g, b];
118 return rgb_array;
119};
120#+end_src
121
122** Running a Program
123
124Once you have a Hare file written and ready to run, you simply need to
125run it:
126
127#+begin_src sh
128hare run file.ha
129#+end_src
130
131You can also compile the program into an executable:
132
133#+begin_src sh
134hare build -o example file.ha
135./example
136#+end_src
137
138** Initial Thoughts
139
1401. Documentation Improvements Would Help
141
142 While I was able to piece everything together eventually, the biggest
143 downfall right now in Hare's documentation. For such a new project,
144 the documentation is in a great spot. However, bare specifications
145 don't help as much as a brief examples section would.
146
147 For example, it took me a while to figure out what the =u64n=
148 function was looking for. I could tell that it took two parameters
149 and the second was my max value (255), but couldn't figure out what
150 the first value should be. Eventually, I inspected the =random.ha=
151 file in the
152 [[https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha][Hare
153 source code]] and found the test suite that helped me discover that
154 it needed an =init()= value in the form of =&var=.
155
1562. More Basic Modules
157
158 This is another point that comes from Hare being new and awaiting
159 more contributions, but there are some basic functions that I would
160 personally enjoy seeing in Hare, such as one to convert decimal
161 (base 10) values to hexadecimal (base 16).
162
163 If I'm feeling comfortable with my math, I may work on the list of
164 functions I want and see if any can make it into the Hare source
165 code.
166
1673. Overall Thoughts
168
169 Overall, I actually really enjoy Hare. It's not as tedious to get a
170 project up and running as Rust, but it's also simpler and more
171 user-friendly than learning C. I am going to continue playing with it
172 and see if I can make anything of particular value.
content/blog/2023-05-22-burnout.md deleted −45
@@ -1,45 +0,0 @@
1+++
2date = 2023-05-22
3title = "RE: Burnout"
4description = ""
5draft = false
6+++
7
8# RE: Burnout
9
10I recently read [Drew DeVault's post on
11burnout](https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html) around the
12same time I was pulling out of a burnout rut myself earlier this month. Finally,
13seeing the light at the end of my burnout tunnel made me want to write my first
14post back on this topic.
15
16# Busy Seasons on Busy Seasons
17
18My career deals with busy seasons, generally driven by client demand. This last
19year, I dealt with a harsh busy season from Aug to Oct 2022 to issue a few SOC
20reports for the period ending 2022-09-30. Immediately following that, I had to
21pivot and found another busy season from Oct to Jan for financial statement
22audits ending on 2022-12-31. Then again, supporting other clients from Jan to
23Mar 2023, followed by my current client workload aiming for SOC reports due on
242023-06-30.
25
26The result? A busy season that has lasted from August 2022 through today. I will
27likely be rushing throughout the next month or two before I have a brief break
28and need to focus on the 2023-09-30 SOC reports again. While auditing and
29consulting always involve a busy season, this is the first time I've had one
30last 9+ months without a break.
31
32While it's been tough, I have a handful of breaks pre-planned throughout this
33next cycle and should be able to moderate the level of commitment required for
34each client.
35
36# Refocusing
37
38Outside of work, I finally have time to work on hobbies such as this website,
39programming, athletics, games, etc.
40
41You may have noticed my absence if you're in the same channels, forums, and
42rooms that I am, but I should finally be active again.
43
44I'm hoping to break an item out of my backlog soon and start working on building
45a new project or hack around with a stale one.
content/blog/2023-05-22-burnout.org added +44
@@ -0,0 +1,44 @@
1#+date: <2023-05-22>
2#+title: RE: Burnout
3#+description:
4
5
6* RE: Burnout
7
8I recently read
9[[https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html][Drew
10DeVault's post on burnout]] around the same time I was pulling out of a
11burnout rut myself earlier this month. Finally, seeing the light at the
12end of my burnout tunnel made me want to write my first post back on
13this topic.
14
15* Busy Seasons on Busy Seasons
16
17My 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
19issue a few SOC reports for the period ending 2022-09-30. Immediately
20following that, I had to pivot and found another busy season from Oct to
21Jan for financial statement audits ending on 2022-12-31. Then again,
22supporting other clients from Jan to Mar 2023, followed by my current
23client workload aiming for SOC reports due on 2023-06-30.
24
25The result? A busy season that has lasted from August 2022 through
26today. I will likely be rushing throughout the next month or two before
27I 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
29is the first time I've had one last 9+ months without a break.
30
31While it's been tough, I have a handful of breaks pre-planned throughout
32this next cycle and should be able to moderate the level of commitment
33required for each client.
34
35* Refocusing
36
37Outside of work, I finally have time to work on hobbies such as this
38website, programming, athletics, games, etc.
39
40You may have noticed my absence if you're in the same channels, forums,
41and rooms that I am, but I should finally be active again.
42
43I'm hoping to break an item out of my backlog soon and start working on
44building a new project or hack around with a stale one.
content/blog/2023-06-08-goaccess-geoip.md deleted −52
@@ -1,52 +0,0 @@
1+++
2date = 2023-06-08
3title = "Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data"
4description = ""
5draft = false
6+++
7
8# Overview
9
10[GoAccess](https://goaccess.io/) is an open source real-time web log analyzer
11and interactive viewer that runs in a terminal in \*nix systems or through your
12browser.
13
14# Installation
15
16To start, you'll need to install GoAccess for your OS. Here's an example for
17Debian-based distros:
18
19```sh
20sudo apt install goaccess
21```
22
23Next, find any number of the MaxMind GeoIP database files on GitHub or another
24file hosting website. We're going to use P3TERX's version in this example:
25
26```sh
27wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
28```
29
30Be sure to save this file in an easy to remember location!
31
32# Usage
33
34In order to utilize the full capabilities of GoAccess and MMDB, start with the
35command template below and customize as necessary. This will export an HTML view
36of the GoAccess dashboard, showing all relevant information related to that
37site's access log. You can also omit the `-o output.html` parameter if you
38prefer to view the data within the CLI instead of creating an HTML file.
39
40With the addition of the GeoIP Database parameter, section `16 - Geo Location`
41will be added with the various countries that are associated with the collected
42IP addresses.
43
44```sh
45zcat /var/log/nginx/example.access.log.*.gz | goaccess \
46--geoip-database=/home/user/GeoLite2-City.mmdb \
47--date-format=%d/%b/%Y \
48--time-format=%H:%M:%S \
49--log-format=COMBINED \
50-o output.html \
51/var/log/nginx/example.access.log -
52```\ No newline at end of file
content/blog/2023-06-08-goaccess-geoip.org added +52
@@ -0,0 +1,52 @@
1#+date: <2023-06-08>
2#+title: Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data
3#+description:
4
5
6* Overview
7
8[[https://goaccess.io/][GoAccess]] is an open source real-time web log
9analyzer and interactive viewer that runs in a terminal in *nix systems
10or through your browser.
11
12* Installation
13
14To start, you'll need to install GoAccess for your OS. Here's an example
15for Debian-based distros:
16
17#+begin_src sh
18sudo apt install goaccess
19#+end_src
20
21Next, find any number of the MaxMind GeoIP database files on GitHub or
22another file hosting website. We're going to use P3TERX's version in
23this example:
24
25#+begin_src sh
26wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
27#+end_src
28
29Be sure to save this file in an easy to remember location!
30
31* Usage
32
33In order to utilize the full capabilities of GoAccess and MMDB, start
34with the command template below and customize as necessary. This will
35export an HTML view of the GoAccess dashboard, showing all relevant
36information related to that site's access log. You can also omit the
37=-o output.html= parameter if you prefer to view the data within the CLI
38instead of creating an HTML file.
39
40With the addition of the GeoIP Database parameter, section
41=16 - Geo Location= will be added with the various countries that are
42associated with the collected IP addresses.
43
44#+begin_src sh
45zcat /var/log/nginx/example.access.log.*.gz | goaccess \
46--geoip-database=/home/user/GeoLite2-City.mmdb \
47--date-format=%d/%b/%Y \
48--time-format=%H:%M:%S \
49--log-format=COMBINED \
50-o output.html \
51/var/log/nginx/example.access.log -
52#+end_src
content/blog/2023-06-08-self-hosting-baikal.md → content/blog/2023-06-08-self-hosting-baikal.org renamed +71 −71
@@ -1,34 +1,32 @@
1+++ 1#+date: <2023-06-08>
2date = 2023-06-08 2#+title: Self-Hosting Baikal Server (CalDAV & CardDAV)
3title = "Self-Hosting Baikal Server (CalDAV & CardDAV)" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# What is Baikal?
9 5
10[Baikal](https://sabre.io/baikal/) is a lightweight CalDAV + CardDAV server that 6* What is Baikal?
11you can self-host on your own machine. While I have tried (& failed) to get this
12CalDAV + CardDAV server running before, it was quite easy this time. Not really
13sure what I did differently this time, but I'm documenting my process here to
14ensure I don't forget.
15 7
16# Installation 8[[https://sabre.io/baikal/][Baikal]] is a lightweight CalDAV + CardDAV
9server that you can self-host on your own machine. While I have tried (&
10failed) to get this CalDAV + CardDAV server running before, it was quite
11easy this time. Not really sure what I did differently this time, but
12I'm documenting my process here to ensure I don't forget.
17 13
18First, create a folder on your server and open a `docker-compose.yml` file for 14* Installation
19editing:
20 15
21```sh 16First, create a folder on your server and open a =docker-compose.yml=
17file for editing:
18
19#+begin_src sh
22mkdir baikal && cd baikal 20mkdir baikal && cd baikal
23nano docker-compose.yml 21nano docker-compose.yml
24``` 22#+end_src
25 23
26Within this file, you'll need to paste the information below. You can customize 24Within this file, you'll need to paste the information below. You can
27the `ports` section to use any port on your server to pass through to port 80 in 25customize the =ports= section to use any port on your server to pass
28the container. You can also edit the `volumes` section to use docker volumes 26through to port 80 in the container. You can also edit the =volumes=
29instead of local folders. 27section to use docker volumes instead of local folders.
30 28
31```conf 29#+begin_src conf
32version: "2" 30version: "2"
33services: 31services:
34 baikal: 32 baikal:
@@ -39,58 +37,59 @@ services:
39 volumes: 37 volumes:
40 - ./config:/var/www/baikal/config 38 - ./config:/var/www/baikal/config
41 - ./data:/var/www/baikal/Specific 39 - ./data:/var/www/baikal/Specific
42``` 40#+end_src
43 41
44Once finished with editing, save and close the file. Then, launch the docker 42Once finished with editing, save and close the file. Then, launch the
45container: 43docker container:
46 44
47```sh 45#+begin_src sh
48sudo docker-compose up -d 46sudo docker-compose up -d
49``` 47#+end_src
50 48
51# Intial Setup 49* Intial Setup
52 50
53As long as no issues came up when starting the container, you should be able to 51As long as no issues came up when starting the container, you should be
54visit the server's set-up page at `http://<server_ip>:<port>`. The application 52able to visit the server's set-up page at =http://<server_ip>:<port>=.
55will ask you to create an administrator account and choose the database type for 53The application will ask you to create an administrator account and
56your storage. Personally, I opted to use SQLite. 54choose the database type for your storage. Personally, I opted to use
55SQLite.
57 56
58Make sure the administrator credentials are adequate to protect against common 57Make sure the administrator credentials are adequate to protect against
59attacks. 58common attacks.
60 59
61# Creating Users 60* Creating Users
62 61
63Once you've set up the application, you will be greeted by the Dashboard page, 62Once you've set up the application, you will be greeted by the Dashboard
64which will show the version of the app, status of the admin/CalDAV/CardDAV 63page, which will show the version of the app, status of the
65services, and the number of users, calendars, events, address books, and 64admin/CalDAV/CardDAV services, and the number of users, calendars,
66contacts. 65events, address books, and contacts.
67 66
68To create a new user, navigate to the `Users and resources` page. This process 67To create a new user, navigate to the =Users and resources= page. This
69is as simple as entering a username, password, and email. 68process is as simple as entering a username, password, and email.
70 69
71Once a user has been created, you can create any number of calendars and address 70Once a user has been created, you can create any number of calendars and
72books for user, as well as inspect their information. 71address books for user, as well as inspect their information.
73 72
74# Setting Up a Public URL 73* Setting Up a Public URL
75 74
76Once your application is working locally, you can open access remotely via a URL 75Once your application is working locally, you can open access remotely
77by using a reverse-proxy like Nginx. 76via a URL by using a reverse-proxy like Nginx.
78 77
79As a prerequisite, you must own a domain name and set up DNS records to point a 78As a prerequisite, you must own a domain name and set up DNS records to
80domain name to the server hosting Baikal. 79point a domain name to the server hosting Baikal.
81 80
82Start by navigating to your web server's configuration directory and create a 81Start by navigating to your web server's configuration directory and
83new file for this application. 82create a new file for this application.
84 83
85```sh 84#+begin_src sh
86cd /etc/nginx/sites-available/ 85cd /etc/nginx/sites-available/
87nano dav 86nano dav
88``` 87#+end_src
89 88
90Within this file, paste in the configuration from below and change 89Within this file, paste in the configuration from below and change
91`dav.example.com` to match the URL you'll be using. 90=dav.example.com= to match the URL you'll be using.
92 91
93```conf 92#+begin_src conf
94server { 93server {
95 if ($host ~ ^[^.]+\.example\.com$) { 94 if ($host ~ ^[^.]+\.example\.com$) {
96 return 301 https://$host$request_uri; 95 return 301 https://$host$request_uri;
@@ -120,34 +119,35 @@ server {
120 include /etc/letsencrypt/options-ssl-nginx.conf; 119 include /etc/letsencrypt/options-ssl-nginx.conf;
121 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 120 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
122} 121}
123``` 122#+end_src
124 123
125For Nginx on Ubuntu, you'll need to symlink the configuration file to the 124For Nginx on Ubuntu, you'll need to symlink the configuration file to
126`sites-enabled` directory and then restart Nginx. 125the =sites-enabled= directory and then restart Nginx.
127 126
128```sh 127#+begin_src sh
129sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav 128sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav
130sudo systemctl restart nginx.service 129sudo systemctl restart nginx.service
131``` 130#+end_src
132 131
133At this point, the Baikal server should be available over the internet at the 132At this point, the Baikal server should be available over the internet
134URL configured above! 133at the URL configured above!
135 134
136# Configuring Clients 135* Configuring Clients
137 136
138Lastly, configuring clients and applications is essential to making sure the DAV 137Lastly, configuring clients and applications is essential to making sure
139server is being fully utilized. 138the DAV server is being fully utilized.
140 139
141You can also use the `Users and resources` page to inspect a user's personal 140You can also use the =Users and resources= page to inspect a user's
142link to their calendars and address books by clicking the info (i) button. It 141personal link to their calendars and address books by clicking the info
143will show a URI like `/dav.php/calendars/your-user/default/`. 142(i) button. It will show a URI like
143=/dav.php/calendars/your-user/default/=.
144 144
145However, I found that the following URL works for most applications: 145However, I found that the following URL works for most applications:
146`/dav.php/principals/your-user/`. 146=/dav.php/principals/your-user/=.
147 147
148I used the `principals` URL above for Thunderbird (calendar, tasks, and 148I used the =principals= URL above for Thunderbird (calendar, tasks, and
149contacts), as well as iOS (calendar, tasks, and contacts) and everything works 149contacts), as well as iOS (calendar, tasks, and contacts) and everything
150flawlessly so far. 150works flawlessly so far.
151 151
152Syncing is quick between the server and clients, and I haven't seen any 152Syncing is quick between the server and clients, and I haven't seen any
153disruptions in the service or data integrity. 153disruptions in the service or data integrity.
content/blog/2023-06-18-unifi-ip-blocklist.md deleted −70
@@ -1,70 +0,0 @@
1+++
2date = 2023-06-18
3title = "Block IP Addresses and Subnets with Unifi Network Firewall"
4description = ""
5draft = false
6+++
7
8# Identifying Abusive IPs
9
10If you're like me and use Unifi network equipment at the edge of the network you
11manage, you may know that Unifi is only somewhat decent at identifying and
12blocking IPs that represent abusive or threat actors.
13
14While Unifi has a [threat
15management](https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management)
16tool inside their Network application, it can be lacking in functionality and
17identification. For example, I have my UDM Pro set to identify and block almost
18all categories of threats available within the Unifi settings. However, I
19regularly identify abusive actors on my web server via the server logs.
20
21In addition, I have identified IP addresses and subnets directly within Unifi's
22logs that the UDM did not block for whatever reason.
23
24This guide is meant to be another step in the process to manually block abusive
25IP addresses or subnets that you have identified but are not being automatically
26blocked yet.
27
28# Create an IP Group Profile
29
30To start, login to the Unifi machine's web GUI and navigate to the Network app > Settings > Profiles.
31
32Within this page, choose the `IP Groups` tab and click `Create New`.
33
34Each IP Group profile can be used as one of three options:
35
361. Port Group
372. IPv4 Address/Subnet
383. IPv6 Address/Subnet
39
40In this example, I'm creating an IPv4 Address/Subnet group and adding a few
41different IP addresses and a subnet. Once you've added all IP addresses and
42subnets, click the `Apply` button that should appear at the bottom.
43
44At this point, the IPv4 Address/Subnet has been created but not yet used.
45
46# Drop IP Group Profile via the Unifi Firewall
47
48To instruct the Unifi machine to block the profile we just created, we need to
49navigate to the Network app > Settings > Firewall & Security.
50
51Within this screen, find the Firewall Rules table and click `Create Entry`. This
52entry should contain the following settings:
53
54- Type: `Internet In`
55- Description: `<Your Custom Rule>`
56- Rule Applied: `Before Predefined Rules`
57- Action: `Drop`
58- Source Type: `Port/IP Group`
59- IPv4 Address Group: `<Name of the Group Profile You Created Above>`
60
61Customize the remaining configurations to your liking, and then save and enable
62the firewall rule.
63
64Once enabled, the Unifi machine will be able to drop all incoming connections
65from the defined IP addresses and subnets within the created profile.
66
67> As a personal aside to this topic, I'm looking for a convenient way to update
68> the firewall rules or profiles remotely (within the LAN) from the web server
69> to accelerate this process. If you have an idea on how to automatically update
70> Unifi IP groups or firewall rules, let me know!
content/blog/2023-06-18-unifi-ip-blocklist.org added +75
@@ -0,0 +1,75 @@
1#+date: <2023-06-18>
2#+title: Block IP Addresses and Subnets with Unifi Network Firewall
3#+description:
4
5
6* Identifying Abusive IPs
7
8If you're like me and use Unifi network equipment at the edge of the
9network you manage, you may know that Unifi is only somewhat decent at
10identifying and blocking IPs that represent abusive or threat actors.
11
12While Unifi has a
13[[https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management][threat
14management]] tool inside their Network application, it can be lacking in
15functionality and identification. For example, I have my UDM Pro set to
16identify and block almost all categories of threats available within the
17Unifi settings. However, I regularly identify abusive actors on my web
18server via the server logs.
19
20In addition, I have identified IP addresses and subnets directly within
21Unifi's logs that the UDM did not block for whatever reason.
22
23This guide is meant to be another step in the process to manually block
24abusive IP addresses or subnets that you have identified but are not
25being automatically blocked yet.
26
27* Create an IP Group Profile
28
29To start, login to the Unifi machine's web GUI and navigate to the
30Network app > Settings > Profiles.
31
32Within this page, choose the =IP Groups= tab and click =Create New=.
33
34Each IP Group profile can be used as one of three options:
35
361. Port Group
372. IPv4 Address/Subnet
383. IPv6 Address/Subnet
39
40In this example, I'm creating an IPv4 Address/Subnet group and adding a
41few different IP addresses and a subnet. Once you've added all IP
42addresses and subnets, click the =Apply= button that should appear at
43the bottom.
44
45At this point, the IPv4 Address/Subnet has been created but not yet
46used.
47
48* Drop IP Group Profile via the Unifi Firewall
49
50To instruct the Unifi machine to block the profile we just created, we
51need to navigate to the Network app > Settings > Firewall & Security.
52
53Within this screen, find the Firewall Rules table and click
54=Create Entry=. This entry should contain the following settings:
55
56- Type: =Internet In=
57- Description: =<Your Custom Rule>=
58- Rule Applied: =Before Predefined Rules=
59- Action: =Drop=
60- Source Type: =Port/IP Group=
61- IPv4 Address Group: =<Name of the Group Profile You Created Above>=
62
63Customize the remaining configurations to your liking, and then save and
64enable the firewall rule.
65
66Once enabled, the Unifi machine will be able to drop all incoming
67connections from the defined IP addresses and subnets within the created
68profile.
69
70#+begin_quote
71As a personal aside to this topic, I'm looking for a convenient way to
72update the firewall rules or profiles remotely (within the LAN) from the
73web server to accelerate this process. If you have an idea on how to
74automatically update Unifi IP groups or firewall rules, let me know!
75#+end_quote
content/blog/2023-06-20-audit-review-template.md deleted −77
@@ -1,77 +0,0 @@
1+++
2date = 2023-06-20
3title = "Audit Testing Review Template"
4description = ""
5draft = false
6+++
7
8# Overview
9
10This post is a _very_ brief overview on the basic process to review audit test
11results, focusing on work done as part of a financial statement audit (FSA) or
12service organization controls (SOC) report.
13
14While there are numerous different things to review and look for - all varying
15wildly depending on the report, client, and tester - this list serves as a solid
16base foundation for a reviewer.
17
18I have used this throughout my career as a starting point to my reviews, and it
19has worked wonders for creating a consistent and objective template to my
20reviews. The goal is to keep this base high-level enough to be used on a wide
21variety of engagements, while still ensuring that all 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 upon first
28 use.
294. [ ] All supporting documents must cross-reference to the lead sheet and
30 vice-versa.
315. [ ] Verify that the control has been adequately tested:
32 - [ ] **Test of Design**: Did the tester obtain information regarding how
33 the control should perform normally and abnormally (e.g., emergency
34 scenarios)?
35 - [ ] **Test of Operating Effectiveness**: Did the tester inquire, observe,
36 inspect, or re-perform sufficient evidence to support their conclusion
37 over the control? Inquiry alone is not adequate!
386. [ ] For any information used in the control, whether by the control operator
39 or by the tester, did the tester appropriately document the source
40 (system or person), extraction method, parameters, and completeness and
41 accuracy (C&A)?
42 - [ ] For any reports, queries, etc. used in the extraction, did the tester
43 include a copy and notate C&A considerations?
447. [ ] Did the tester document the specific criteria that the control is being
45 tested against?
468. [ ] Did the tester notate in the supporting documents where each criterion
47 was satisfied?
489. [ ] If testing specific policies or procedures, are the documents adequate?
49 - [ ] e.g., a test to validate that a review of policy XYZ occurs
50 periodically should also evaluate the sufficiency of the policy
51 itself, if meant to cover the risk that such a policy does not exist
52 and is not reviewed.
5310. [ ] Does the test cover the appropriate period under review?
54 - [ ] If the test is meant to cover only a portion of the audit period, do
55 other controls exist to mitigate the risks that exist for the
56 remainder of the period?
5711. [ ] For any computer-aided audit tools (CAATs) or other automation
58 techniques used in the test, is the use of such tools explained and
59 appropriately documented?
6012. [ ] If prior-period documentation exists, are there any missing pieces of
61 evidence that would further enhance the quality of the test?
6213. [ ] Was any information discovered during the walkthrough or inquiry phase
63 that was not incorporated into the test?
6414. [ ] Are there new rules or expectations from your company's internal
65 guidance or your regulatory bodies that would affect the audit approach
66 for this control?
6715. [ ] Was an exception, finding, or deficiency identified as a result of this
68 test?
69 - [ ] Was the control deficient in design, operation, or both?
70 - [ ] What was the root cause of the finding?
71 - [ ] Does the finding indicate other findings or potential fraud?
72 - [ ] What's the severity and scope of the finding?
73 - [ ] Do other controls exist as a form of compensation against the
74 finding's severity, and do they mitigate the risk within the control
75 objective?
76 - [ ] Does the finding exist at the end of the period, or was it resolved
77 within the audit period?
content/blog/2023-06-20-audit-review-template.org added +78
@@ -0,0 +1,78 @@
1#+date: <2023-06-20>
2#+title: Audit Testing Review Template
3#+description:
4
5
6* Overview
7
8This post is a /very/ brief overview on the basic process to review
9audit test results, focusing on work done as part of a financial
10statement audit (FSA) or service organization controls (SOC) report.
11
12While there are numerous different things to review and look for - all
13varying wildly depending on the report, client, and tester - this list
14serves as a solid base foundation for a reviewer.
15
16I have used this throughout my career as a starting point to my reviews,
17and it has worked wonders for creating a consistent and objective
18template to my reviews. The goal is to keep this base high-level enough
19to be used on a wide variety of engagements, while still ensuring that
20all key areas are covered.
21
22* Review Template
23
241. [ ] Check all documents for spelling and grammar.
252. [ ] Ensure all acronyms are fully explained upon first use.
263. [ ] For all people referenced, use their full names and job titles
27 upon first use.
284. [ ] All supporting documents must cross-reference to the lead sheet
29 and vice-versa.
305. [ ] Verify that the control has been adequately tested:
31 - [ ] *Test of Design*: Did the tester obtain information regarding
32 how the control should perform normally and abnormally (e.g.,
33 emergency scenarios)?
34 - [ ] *Test of Operating Effectiveness*: Did the tester inquire,
35 observe, inspect, or re-perform sufficient evidence to support
36 their conclusion over the control? Inquiry alone is not adequate!
376. [ ] For any information used in the control, whether by the control
38 operator or by the tester, did the tester appropriately document the
39 source (system or person), extraction method, parameters, and
40 completeness and accuracy (C&A)?
41 - [ ] For any reports, queries, etc. used in the extraction, did the
42 tester include a copy and notate C&A considerations?
437. [ ] Did the tester document the specific criteria that the control is
44 being tested against?
458. [ ] Did the tester notate in the supporting documents where each
46 criterion was satisfied?
479. [ ] If testing specific policies or procedures, are the documents
48 adequate?
49 - [ ] e.g., a test to validate that a review of policy XYZ occurs
50 periodically should also evaluate the sufficiency of the policy
51 itself, if meant to cover the risk that such a policy does not
52 exist and is not reviewed.
5310. [ ] Does the test cover the appropriate period under review?
54 - [ ] If the test is meant to cover only a portion of the audit
55 period, do other controls exist to mitigate the risks that exist
56 for the remainder of the period?
5711. [ ] For any computer-aided audit tools (CAATs) or other automation
58 techniques used in the test, is the use of such tools explained and
59 appropriately documented?
6012. [ ] If prior-period documentation exists, are there any missing
61 pieces of evidence that would further enhance the quality of the
62 test?
6313. [ ] Was any information discovered during the walkthrough or inquiry
64 phase that was not incorporated into the test?
6514. [ ] Are there new rules or expectations from your company's internal
66 guidance or your regulatory bodies that would affect the audit
67 approach for this control?
6815. [ ] Was an exception, finding, or deficiency identified as a result
69 of this test?
70 - [ ] Was the control deficient in design, operation, or both?
71 - [ ] What was the root cause of the finding?
72 - [ ] Does the finding indicate other findings or potential fraud?
73 - [ ] What's the severity and scope of the finding?
74 - [ ] Do other controls exist as a form of compensation against the
75 finding's severity, and do they mitigate the risk within the
76 control objective?
77 - [ ] Does the finding exist at the end of the period, or was it
78 resolved within the audit period?
content/blog/2023-06-23-byobu.md → content/blog/2023-06-23-byobu.org renamed +26 −26
@@ -1,44 +1,44 @@
1+++ 1#+date: <2023-06-23>
2date = 2023-06-23 2#+title: Byobu
3title = "Byobu" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Byobu
9 5
10[byobu](https://www.byobu.org) is a command-line tool that allows you to use 6* Byobu
11numerous screens within a single terminal emulator instance. More specifically,
12it's a text based window manager, using either `screen` or `tmux`.
13 7
14This post is mostly just a self-reference as I explore byobu, so I may come back 8[[https://www.byobu.org][byobu]] is a command-line tool that allows you
15later and update this post with more content. 9to use numerous screens within a single terminal emulator instance. More
10specifically, it's a text based window manager, using either =screen= or
11=tmux=.
16 12
17## Screenshot 13This post is mostly just a self-reference as I explore byobu, so I may
14come back later and update this post with more content.
18 15
19Take a look below at my current multi-window set-up in byobu while I write this 16** Screenshot
20blog post:
21 17
22![byobu](https://img.cleberg.net/blog/20230623-byobu/byobu.png) 18Take a look below at my current multi-window set-up in byobu while I
19write this blog post:
23 20
24### Keybindings 21#+caption: byobu
22[[https://img.cleberg.net/blog/20230623-byobu/byobu.png]]
25 23
26You can open the help menu with either of the following commands; they will both 24*** Keybindings
27open the same manpage:
28 25
29```sh 26You can open the help menu with either of the following commands; they
27will both open the same manpage:
28
29#+begin_src sh
30byobu --help 30byobu --help
31# or 31# or
32man byobu 32man byobu
33``` 33#+end_src
34 34
35While the manpage contains a ton of information about the functionality of byobu 35While the manpage contains a ton of information about the functionality
36(such as status notifications, sessions, and windows), the first location to 36of byobu (such as status notifications, sessions, and windows), the
37explore should be the keybindings section. 37first location to explore should be the keybindings section.
38 38
39The keybindings are configured as follows: 39The keybindings are configured as follows:
40 40
41```txt 41#+begin_src txt
42byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or 42byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or
43within .screenrc if byobu-export was used). The common key bindings are: 43within .screenrc if byobu-export was used). The common key bindings are:
44 44
@@ -66,4 +66,4 @@ Ctrl-a R - Reload profile
66Ctrl-a ! - Toggle key bindings on and off 66Ctrl-a ! - Toggle key bindings on and off
67Ctrl-a k - Kill the current window 67Ctrl-a k - Kill the current window
68Ctrl-a ~ - Save the current window's scrollback buffer 68Ctrl-a ~ - Save the current window's scrollback buffer
69``` 69#+end_src
content/blog/2023-06-23-self-hosting-convos.md deleted −161
@@ -1,161 +0,0 @@
1+++
2date = 2023-06-23
3title = "Self-Hosting Convos IRC Web Client"
4description = ""
5draft = false
6+++
7
8# Convos
9
10[Convos](https://convos.chat/) is an always-online web client for IRC. It has a
11few features that made it attractive to me as a self-hosted option:
12
13- Extremely simple Docker Compose installation method.
14- Runs in the background and monitors chats even while you're not logged in.
15- Neatly organized sidebar for conversation and client settings.
16- Ability to connect to different hosts and create profiles for hosts.
17- By default, registration is closed to the public. You can enable public
18 registration on the Settings page or generate invitation links on the Users
19 page.
20- Customization of the client theme, organization name and URL, admin email,
21 and video service.
22
23# Docker Installation
24
25To install Convos, simply create a directory and a `docker-compose.yml` file:
26
27```sh
28mkdir ~/convos && cd ~/convos
29nano docker-compose.yml
30```
31
32With the `docker-compose.yml` file open, paste the configuration below into the
33file. You can customize the host port to be something unique, such as
34`21897:3000`. You can also change the `data` folder to be a docker volume
35instead, if you prefer.
36
37```config
38version: '3'
39
40services:
41 convos:
42 image: 'convos/convos:stable'
43 ports:
44 - '3000:3000'
45 volumes:
46 - './data:/data'
47 environment:
48 - CONVOS_REVERSE_PROXY=1
49 restart: always
50```
51
52Save the `docker-compose.yml` file and bring the container up:
53
54```sh
55sudo docker-compose up -d
56```
57
58At this point, Convos is available at `<server_ip>:3000` but not available to
59the public.
60
61# Nginx Reverse Proxy
62
63If you're using Nginx, you can create a configuration file for the convos
64application. Start by opening the file:
65
66```sh
67nano /etc/nginx/sites-available/convos
68```
69
70Within the nginx configuration file, paste the following content and be sure to
71update `convos.example.com` to match your domain and `127.0.0.1:3000` to match
72the port you opened in the `docker-compose.yml` file.
73
74```config
75# Host and port where convos is running
76upstream convos_upstream { server 127.0.0.1:3000; }
77
78server {
79 if ($host ~ ^[^.]+\.example\.com$) {
80 return 301 https://$host$request_uri;
81 }
82
83 listen [::]:80;
84 listen 80;
85 server_name convos.example.com;
86 return 404;
87}
88
89server {
90 listen [::]:443 ssl http2;
91 listen 443 ssl http2;
92 server_name convos.example.com;
93 access_log /var/log/nginx/convos.access.log;
94 error_log /var/log/nginx/convos.error.log;
95
96 location / {
97 proxy_pass http://convos_upstream;
98 proxy_http_version 1.1;
99 proxy_set_header Upgrade $http_upgrade;
100 proxy_set_header Connection "upgrade";
101 client_max_body_size 0;
102 proxy_set_header Host $host;
103 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
104 proxy_set_header X-Request-Base "$scheme://$host/";
105 }
106
107 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
108 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
109 include /etc/letsencrypt/options-ssl-nginx.conf;
110 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
111}
112```
113
114Once the file is saved, link it to the `sites-enabled` directory and restart
115Nginx.
116
117```sh
118sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos
119sudo systemctl restart nginx.service
120```
121
122# Screenshots
123
124See the screenshots below for an example of the Convos homepage and an excerpt
125of the chat screen.
126
127There are numerous themes to choose from; the theme shown in the images below is
128Dracula.
129
130![Convos Home](https://img.cleberg.net/blog/20230623-convos/convos_home.png)
131
132![Convos Chat](https://img.cleberg.net/blog/20230623-convos/convos_chat.png)
133
134# Registering a Nickname
135
136Hop into the server channel so that you can message NickServ. In the case of
137Convos, the default sever is libera.chat. Simply click the `libera` conversation
138at the top of the sidebar to open it. Once the chat is open, you can claim a
139nickname by typing:
140
141```txt
142/nick <nick>
143```
144
145If the nickname is available, and you'd like to register the nickname to
146yourself, you'll need to type another command:
147
148```txt
149/msg NickServ REGISTER
150<password> <email>
151```
152
153On libera.chat, the server will send a confirmation email with a command that
154you must message in IRC to verify registration of the nickname:
155
156```txt
157/msg NickServ VERIFY REGISTER <nick> <verification_code>
158```
159
160Once entered, the server should confirm registration of the nickname to the
161supplied email with the password specified.
content/blog/2023-06-23-self-hosting-convos.org added +165
@@ -0,0 +1,165 @@
1#+date: <2023-06-23>
2#+title: Self-Hosting Convos IRC Web Client
3#+description:
4
5
6* Convos
7
8[[https://convos.chat/][Convos]] is an always-online web client for IRC.
9It has a few features that made it attractive to me as a self-hosted
10option:
11
12- Extremely simple Docker Compose installation method.
13- Runs in the background and monitors chats even while you're not logged
14 in.
15- Neatly organized sidebar for conversation and client settings.
16- Ability to connect to different hosts and create profiles for hosts.
17- By default, registration is closed to the public. You can enable
18 public registration on the Settings page or generate invitation links
19 on the Users page.
20- Customization of the client theme, organization name and URL, admin
21 email, and video service.
22
23* Docker Installation
24
25To install Convos, simply create a directory and a =docker-compose.yml=
26file:
27
28#+begin_src sh
29mkdir ~/convos && cd ~/convos
30nano docker-compose.yml
31#+end_src
32
33With the =docker-compose.yml= file open, paste the configuration below
34into the file. You can customize the host port to be something unique,
35such as =21897:3000=. You can also change the =data= folder to be a
36docker volume instead, if you prefer.
37
38#+begin_src config
39version: '3'
40
41services:
42 convos:
43 image: 'convos/convos:stable'
44 ports:
45 - '3000:3000'
46 volumes:
47 - './data:/data'
48 environment:
49 - CONVOS_REVERSE_PROXY=1
50 restart: always
51#+end_src
52
53Save the =docker-compose.yml= file and bring the container up:
54
55#+begin_src sh
56sudo docker-compose up -d
57#+end_src
58
59At this point, Convos is available at =<server_ip>:3000= but not
60available to the public.
61
62* Nginx Reverse Proxy
63
64If you're using Nginx, you can create a configuration file for the
65convos application. Start by opening the file:
66
67#+begin_src sh
68nano /etc/nginx/sites-available/convos
69#+end_src
70
71Within the nginx configuration file, paste the following content and be
72sure to update =convos.example.com= to match your domain and
73=127.0.0.1:3000= to match the port you opened in the
74=docker-compose.yml= file.
75
76#+begin_src config
77# Host and port where convos is running
78upstream convos_upstream { server 127.0.0.1:3000; }
79
80server {
81 if ($host ~ ^[^.]+\.example\.com$) {
82 return 301 https://$host$request_uri;
83 }
84
85 listen [::]:80;
86 listen 80;
87 server_name convos.example.com;
88 return 404;
89}
90
91server {
92 listen [::]:443 ssl http2;
93 listen 443 ssl http2;
94 server_name convos.example.com;
95 access_log /var/log/nginx/convos.access.log;
96 error_log /var/log/nginx/convos.error.log;
97
98 location / {
99 proxy_pass http://convos_upstream;
100 proxy_http_version 1.1;
101 proxy_set_header Upgrade $http_upgrade;
102 proxy_set_header Connection "upgrade";
103 client_max_body_size 0;
104 proxy_set_header Host $host;
105 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
106 proxy_set_header X-Request-Base "$scheme://$host/";
107 }
108
109 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
110 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
111 include /etc/letsencrypt/options-ssl-nginx.conf;
112 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
113}
114#+end_src
115
116Once the file is saved, link it to the =sites-enabled= directory and
117restart Nginx.
118
119#+begin_src sh
120sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos
121sudo systemctl restart nginx.service
122#+end_src
123
124* Screenshots
125
126See the screenshots below for an example of the Convos homepage and an
127excerpt of the chat screen.
128
129There are numerous themes to choose from; the theme shown in the images
130below is Dracula.
131
132#+caption: Convos Home
133[[https://img.cleberg.net/blog/20230623-convos/convos_home.png]]
134
135#+caption: Convos Chat
136[[https://img.cleberg.net/blog/20230623-convos/convos_chat.png]]
137
138* Registering a Nickname
139
140Hop into the server channel so that you can message NickServ. In the
141case of Convos, the default sever is libera.chat. Simply click the
142=libera= conversation at the top of the sidebar to open it. Once the
143chat is open, you can claim a nickname by typing:
144
145#+begin_src txt
146/nick <nick>
147#+end_src
148
149If the nickname is available, and you'd like to register the nickname to
150yourself, you'll need to type another command:
151
152#+begin_src txt
153/msg NickServ REGISTER
154<password> <email>
155#+end_src
156
157On libera.chat, the server will send a confirmation email with a command
158that you must message in IRC to verify registration of the nickname:
159
160#+begin_src txt
161/msg NickServ VERIFY REGISTER <nick> <verification_code>
162#+end_src
163
164Once entered, the server should confirm registration of the nickname to
165the supplied email with the password specified.
content/blog/2023-06-28-backblaze-b2.md deleted −181
@@ -1,181 +0,0 @@
1+++
2date = 2023-06-28
3title = "Getting Started with Backblaze B2 Cloud Storage"
4description = ""
5draft = false
6+++
7
8# Overview
9
10Backblaze [B2 Cloud Storage](https://www.backblaze.com/b2/cloud-storage.html) is
11an inexpensive and reliable on-demand cloud storage and backup solution.
12
13The service starts at $5/TB/month ($0.005/GB/month) with a download rate of
14$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: [Pricing
25Organized 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 `/home/`
29directory and various other server configs that fall under 10 GB total,
30Backblaze is a great solution from a financial perspective.
31
32# Create An Account
33
34To start with Backblaze, you'll need to [create a free
35account](https://www.backblaze.com/b2/sign-up.html) - no payment method is
36required to sign up.
37
38Once you have an account, you can test out the service with their web GUI, their
39mobile app, or their CLI tool. I'm going to use the CLI tool below to test a
40file upload and then sync an entire directory to my Backblaze bucket.
41
42# Create a Bucket
43
44Before you can start uploading, you need to create a bucket. If you're familiar
45with other object storage services, this will feel familiar. If not, it's pretty
46simple to create one.
47
48As their webpage says:
49
50> A bucket is a container that holds files that are uploaded into B2 Cloud
51> Storage. The bucket name must be globally unique and must have a minimum of 6
52> characters. A limit of 100 buckets may be created per account. An unlimited
53> number of files may be uploaded into a bucket.
54
55Once you click the `Create a Bucket` button on their webpage or mobile app, you
56need 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 object
64lock disabled.
65
66Once your bucket is created, you can test the upload/download feature on their
67web GUI or mobile app! At this point, you have a fully functional bucket and
68account.
69
70# Linux CLI Tool
71
72## Installation
73
74To install the `b2` CLI tool, you'll need to download it from the [CLI
75Tools](https://www.backblaze.com/docs/cloud-storage-command-line-tools) page. I
76recommend copying the URL from the link that says `Linux` and using wget to
77download it, as shown below.
78
79Once downloaded, make the file executable and move it to a location on your
80`$PATH`, so that you can execute that command from anywhere on the machine.
81
82```sh
83wget <b2_cli_url>
84chmod +x b2_linux
85mv b2_linux /usr/bin/b2
86```
87
88## Log In
89
90The first step after installation is to log in. To do this, execute the
91following command and provide your `<applicationKeyId>` and `<applicationKey>`.
92
93If you don't want to provide these values in the command itself, you can simply
94execute the base command and it will request them in an interactive prompt.
95
96```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```
103
104## Upload a Test File
105
106In order to test the functionality of the CLI tool, I'll start by uploading a
107single test file to the bucket I created above. We can do this with the
108`upload_file` function.
109
110The command is issued as follows:
111
112```sh
113b2 upload_file <bucket_name> <local_file> <remote_file>
114```
115
116In my situation, I executed the following command with my username.
117
118```sh
119b2 upload_file my_unique_bucket /home/<user>/test.md test.md
120```
121
122To confirm that the file was uploaded successfully, list the files in your
123bucket:
124
125```sh
126b2 ls <bucket_name>
127```
128
129```txt
130test.md
131```
132
133## Sync a Directory
134
135If you have numerous files, you can use the `sync` function to perform
136functionality similar to `rsync`, where you can check what's in your bucket and
137sync anything that is new or modified.
138
139The command is issued as follows:
140
141```sh
142b2 sync <source file location> <B2 bucket destination>
143```
144
145In my case, I can sync my user's entire home directory to my bucket without
146specifying any of the files directly:
147
148```sh
149b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
150```
151
152# Caveats
153
154## Timing of Updates to the Web GUI
155
156When performing actions over a bucket, there is a slight delay in the web GUI
157when inspecting a bucket or its file. Note that simple actions such as uploading
158or deleting files may have a delay of a few minutes up to 24 hours. In my
159experience (\<10 GB and ~20,000 files), any actions took only a few minutes to
160update across clients.
161
162## Symlinks
163
164Note that symlinks are resolved by b2, so if you have a link from
165`/home/<user>/nas-storage` that symlinks out to a `/mnt/nas-storage` folder that
166has 10TB of data, `b2` will resolve that link and start uploading all 10TB of
167data linked within the folder.
168
169If you're not sure if you have any symlinks, a symlink will look like this (note
170the `->` symbol):
171
172```sh
173> ls -lha
174lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
175```
176
177You can recursively find symlink in a path with the following command:
178
179```sh
180ls -lR /path/to/search | grep '^l'
181```
content/blog/2023-06-28-backblaze-b2.org added +186
@@ -0,0 +1,186 @@
1#+date: <2023-06-28>
2#+title: Getting Started with Backblaze B2 Cloud Storage
3#+description:
4
5
6* Overview
7
8Backblaze [[https://www.backblaze.com/b2/cloud-storage.html][B2 Cloud
9Storage]] is an inexpensive and reliable on-demand cloud storage and
10backup solution.
11
12The service starts at $5/TB/month ($0.005/GB/month) with a download rate
13of $0.01/GB/month.
14
15However, there are free tiers:
16
17- The first 10 GB of storage is free.
18- The first 1 GB of data downloaded each day is free.
19- Class A transactions are free.
20- The first 2500 Class B transactions each day are free.
21- The first 2500 Class C transactions each day are free.
22
23You can see which API calls fall into categories A, B, or C here:
24[[https://www.backblaze.com/b2/b2-transactions-price.html][Pricing
25Organized by API Calls]].
26
27For someone like me, who wants an offsite backup of their server's
28=/home/= directory and various other server configs that fall under 10
29GB total, Backblaze is a great solution from a financial perspective.
30
31* Create An Account
32
33To start with Backblaze, you'll need to
34[[https://www.backblaze.com/b2/sign-up.html][create a free account]] -
35no payment method is required to sign up.
36
37Once you have an account, you can test out the service with their web
38GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool
39below to test a file upload and then sync an entire directory to my
40Backblaze bucket.
41
42* Create a Bucket
43
44Before you can start uploading, you need to create a bucket. If you're
45familiar with other object storage services, this will feel familiar. If
46not, it's pretty simple to create one.
47
48As their webpage says:
49
50#+begin_quote
51A bucket is a container that holds files that are uploaded into B2 Cloud
52Storage. The bucket name must be globally unique and must have a minimum
53of 6 characters. A limit of 100 buckets may be created per account. An
54unlimited number of files may be uploaded into a bucket.
55#+end_quote
56
57Once you click the =Create a Bucket= button on their webpage or mobile
58app, 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 from the
77[[https://www.backblaze.com/docs/cloud-storage-command-line-tools][CLI
78Tools]] page. I recommend copying the URL from the link that says
79=Linux= and using wget to download it, as shown below.
80
81Once downloaded, make the file executable and move it to a location on
82your =$PATH=, so that you can execute that command from anywhere on the
83machine.
84
85#+begin_src sh
86wget <b2_cli_url>
87chmod +x b2_linux
88mv b2_linux /usr/bin/b2
89#+end_src
90
91** Log In
92
93The first step after installation is to log in. To do this, execute the
94following command and provide your =<applicationKeyId>= and
95=<applicationKey>=.
96
97If you don't want to provide these values in the command itself, you can
98simply execute the base command and it will request them in an
99interactive prompt.
100
101#+begin_src sh
102# if you want to provide the keys directly:
103b2 authorize-account [<applicationKeyId>] [<applicationKey>]
104
105# or, if you don't want your keys in your shell history:
106b2 authorize-account
107#+end_src
108
109** Upload a Test File
110
111In order to test the functionality of the CLI tool, I'll start by
112uploading a single test file to the bucket I created above. We can do
113this with the =upload_file= function.
114
115The command is issued as follows:
116
117#+begin_src sh
118b2 upload_file <bucket_name> <local_file> <remote_file>
119#+end_src
120
121In my situation, I executed the following command with my username.
122
123#+begin_src sh
124b2 upload_file my_unique_bucket /home/<user>/test.md test.md
125#+end_src
126
127To confirm that the file was uploaded successfully, list the files in
128your bucket:
129
130#+begin_src sh
131b2 ls <bucket_name>
132#+end_src
133
134#+begin_src txt
135test.md
136#+end_src
137
138** Sync a Directory
139
140If you have numerous files, you can use the =sync= function to perform
141functionality similar to =rsync=, where you can check what's in your
142bucket and sync anything that is new or modified.
143
144The command is issued as follows:
145
146#+begin_src sh
147b2 sync <source file location> <B2 bucket destination>
148#+end_src
149
150In my case, I can sync my user's entire home directory to my bucket
151without specifying any of the files directly:
152
153#+begin_src sh
154b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
155#+end_src
156
157* Caveats
158
159** Timing of Updates to the Web GUI
160
161When performing actions over a bucket, there is a slight delay in the
162web GUI when inspecting a bucket or its file. Note that simple actions
163such as uploading or deleting files may have a delay of a few minutes up
164to 24 hours. In my experience (<10 GB and ~20,000 files), any actions
165took only a few minutes to update across clients.
166
167** Symlinks
168
169Note that symlinks are resolved by b2, so if you have a link from
170=/home/<user>/nas-storage= that symlinks out to a =/mnt/nas-storage=
171folder that has 10TB of data, =b2= will resolve that link and start
172uploading all 10TB of data linked within the folder.
173
174If you're not sure if you have any symlinks, a symlink will look like
175this (note the =->= symbol):
176
177#+begin_src sh
178> ls -lha
179lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
180#+end_src
181
182You can recursively find symlink in a path with the following command:
183
184#+begin_src sh
185ls -lR /path/to/search | grep '^l'
186#+end_src
content/blog/2023-06-30-self-hosting-voyager.md → content/blog/2023-06-30-self-hosting-voyager.org renamed +50 −50
@@ -1,54 +1,53 @@
1+++ 1#+date: <2023-06-30>
2date = 2023-06-30 2#+title: Self-Hosting Voyager - A Lemmy Web Client
3title = "Self-Hosting Voyager - A Lemmy Web Client" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Installation Guide
9 5
10[Voyager](https://github.com/aeharding/voyager) is a mobile-first Lemmy web 6* Installation Guide
11client, based on iOS design standards. It follows very closely to Apollo's
12design.
13 7
14This post is a guide showing how I was able to build and launch my own instance 8[[https://github.com/aeharding/voyager][Voyager]] is a mobile-first
15of Voyager via Docker Compose. 9Lemmy web client, based on iOS design standards. It follows very closely
10to Apollo's design.
16 11
17## Clone the Repository 12This post is a guide showing how I was able to build and launch my own
13instance of Voyager via Docker Compose.
14
15** Clone the Repository
18 16
19Start by cloning the repository and entering it: 17Start by cloning the repository and entering it:
20 18
21```sh 19#+begin_src sh
22git clone https://github.com/aeharding/voyager 20git clone https://github.com/aeharding/voyager
23cd voyager 21cd voyager
24``` 22#+end_src
25 23
26## Build the Image 24** Build the Image
27 25
28With this repository, you can build the image yourself without any further 26With this repository, you can build the image yourself without any
29configuration. When complete, it'll give you the image ID for you to run. 27further configuration. When complete, it'll give you the image ID for
28you to run.
30 29
31```sh 30#+begin_src sh
32sudo docker build . 31sudo docker build .
33# Successfully built 5f00723cb5be 32# Successfully built 5f00723cb5be
34``` 33#+end_src
35 34
36With the image ID above, you can run the container and pass the requested port 35With the image ID above, you can run the container and pass the
37`5314` through or use a custom port, if you wish. 36requested port =5314= through or use a custom port, if you wish.
38 37
39You can also set the `CUSTOM_LEMMY_SERVERS` environment variable if you want to 38You can also set the =CUSTOM_LEMMY_SERVERS= environment variable if you
40add to the default suggested login servers. This must be set with a comma 39want to add to the default suggested login servers. This must be set
41separated list of suggested servers. The first instance in the list will be the 40with a comma separated list of suggested servers. The first instance in
42default view for logged-out users. 41the list will be the default view for logged-out users.
43 42
44I will be using a `docker-compose.yml` file to run this container, instead of a 43I will be using a =docker-compose.yml= file to run this container,
45`docker run` command. 44instead of a =docker run= command.
46 45
47```sh 46#+begin_src sh
48nano docker-compose.yml 47nano docker-compose.yml
49``` 48#+end_src
50 49
51```conf 50#+begin_src conf
52version: "2" 51version: "2"
53services: 52services:
54 voyager: 53 voyager:
@@ -58,29 +57,30 @@ services:
58 - "<custom_port>:5314" 57 - "<custom_port>:5314"
59 environment: 58 environment:
60 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org 59 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org
61``` 60#+end_src
62 61
63```sh 62#+begin_src sh
64sudo docker-compose up -d 63sudo docker-compose up -d
65``` 64#+end_src
66 65
67The web app will now be available at the following address: 66The web app will now be available at the following address:
68`<machine_ip>:<custom_port>`. If you are running it on your local device, try 67=<machine_ip>:<custom_port>=. If you are running it on your local
69`localhost:<custom_port>`. 68device, try =localhost:<custom_port>=.
70 69
71## Reverse Proxy 70** Reverse Proxy
72 71
73If you want to visit this app via an external URL or domain name, you'll need to 72If you want to visit this app via an external URL or domain name, you'll
74set up a reverse proxy. The example below uses Nginx as a reverse proxy. 73need to set up a reverse proxy. The example below uses Nginx as a
74reverse proxy.
75 75
76Simply create the configuration file, paste the contents below, save the file, 76Simply create the configuration file, paste the contents below, save the
77symlink the file, and restart Nginx. 77file, symlink the file, and restart Nginx.
78 78
79```sh 79#+begin_src sh
80sudo nano /etc/nginx/sites-available/voyager 80sudo nano /etc/nginx/sites-available/voyager
81``` 81#+end_src
82 82
83```conf 83#+begin_src conf
84server { 84server {
85 if ($host ~ ^[^.]+\.example\.com$) { 85 if ($host ~ ^[^.]+\.example\.com$) {
86 return 301 https://$host$request_uri; 86 return 301 https://$host$request_uri;
@@ -110,14 +110,14 @@ server {
110 include /etc/letsencrypt/options-ssl-nginx.conf; 110 include /etc/letsencrypt/options-ssl-nginx.conf;
111 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 111 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
112} 112}
113``` 113#+end_src
114 114
115```sh 115#+begin_src sh
116sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager 116sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager
117sudo systemctl restart nginx.service 117sudo systemctl restart nginx.service
118``` 118#+end_src
119 119
120The site will now be available at the `server_name` you specified above! 120The site will now be available at the =server_name= you specified above!
121 121
122You can visit my instance at [voyager.cleberg.net](https://voyager.cleberg.net) 122You can visit my instance at
123for an example. 123[[https://voyager.cleberg.net][voyager.cleberg.net]] for an example.
content/blog/2023-07-12-wireguard-lan.md → content/blog/2023-07-12-wireguard-lan.org renamed +62 −58
@@ -1,31 +1,30 @@
1+++ 1#+date: <2023-07-12>
2date = 2023-07-12 2#+title: Enable LAN Access in Mullvad Wireguard Configuration Files
3title = "Enable LAN Access in Mullvad Wireguard Configuration Files" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Download Configuration Files from Mullvad
9 5
10To begin, you'll need [Wireguard configuration files from 6* Download Configuration Files from Mullvad
11Mullvad](https://mullvad.net/account/wireguard-config). You can choose any of
12the options as you download them. For example, I enabled the kill switch,
13selected all countries, and selected a few content filters.
14 7
15Once downloaded, unzip the files and move them to the Wireguard folder on your 8To begin, you'll need
16system. 9[[https://mullvad.net/account/wireguard-config][Wireguard configuration
10files from Mullvad]]. You can choose any of the options as you download
11them. For example, I enabled the kill switch, selected all countries,
12and selected a few content filters.
17 13
18```sh 14Once downloaded, unzip the files and move them to the Wireguard folder
15on your system.
16
17#+begin_src sh
19cd ~/Downloads 18cd ~/Downloads
20unzip mullvad_wireguard_linux_all_all.zip 19unzip mullvad_wireguard_linux_all_all.zip
21doas mv *.conf /etc/wireguard/ 20doas mv *.conf /etc/wireguard/
22``` 21#+end_src
23 22
24## Configuration File Layout 23** Configuration File Layout
25 24
26The default configuration files will look something like this: 25The default configuration files will look something like this:
27 26
28```conf 27#+begin_src conf
29[Interface] 28[Interface]
30# Device: <redacted> 29# Device: <redacted>
31PrivateKey = <redacted> 30PrivateKey = <redacted>
@@ -38,42 +37,46 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
38PublicKey = <redacted> 37PublicKey = <redacted>
39AllowedIPs = <redacted> 38AllowedIPs = <redacted>
40Endpoint = <redacted> 39Endpoint = <redacted>
41``` 40#+end_src
42 41
43> Note: If you didn't select the kill switch option, you won't see the `PostUp` 42#+begin_quote
44> and `PreDown` lines. In this case, you'll need to modify the script below to 43Note: If you didn't select the kill switch option, you won't see the
45> simply append those lines to the `[Interface]` block. 44=PostUp= and =PreDown= lines. In this case, you'll need to modify the
45script below to simply append those lines to the =[Interface]= block.
46#+end_quote
46 47
47# Editing the Configuration Files 48* Editing the Configuration Files
48 49
49Once you have the files, you'll need to edit them and replace the `PostUp` and 50Once you have the files, you'll need to edit them and replace the
50`PreDown` lines to enable LAN access. 51=PostUp= and =PreDown= lines to enable LAN access.
51 52
52I recommend that you do this process as root, since you'll need to be able to 53I recommend that you do this process as root, since you'll need to be
53access files in `/etc/wireguard`, which are generally owned by root. You can 54able to access files in =/etc/wireguard=, which are generally owned by
54also try using `sudo` or `doas`, but I didn't test that scenario so you may need 55root. You can also try using =sudo= or =doas=, but I didn't test that
55to adjust, as necessary. 56scenario so you may need to adjust, as necessary.
56 57
57```sh 58#+begin_src sh
58su 59su
59``` 60#+end_src
60 61
61Create the Python file that we'll be using to update the Wireguard configuration 62Create the Python file that we'll be using to update the Wireguard
62files. 63configuration files.
63 64
64```sh 65#+begin_src sh
65nano replace.py 66nano replace.py
66``` 67#+end_src
67 68
68Within the Python file, copy and paste the logic below. This script will open a 69Within the Python file, copy and paste the logic below. This script will
69directory, loop through every configuration file within the directory, and 70open a directory, loop through every configuration file within the
70replace the `PostUp` and `PreDown` lines with the new LAN-enabled iptables 71directory, and replace the =PostUp= and =PreDown= lines with the new
71commands. 72LAN-enabled iptables commands.
72 73
73> Note: If your LAN is on a subnet other than `192.168.1.0/24`, you'll need to 74#+begin_quote
74> update the Python script below appropriately. 75Note: If your LAN is on a subnet other than =192.168.1.0/24=, you'll
76need to update the Python script below appropriately.
77#+end_quote
75 78
76```python 79#+begin_src python
77import os 80import os
78import fileinput 81import fileinput
79 82
@@ -92,25 +95,25 @@ for file in os.listdir(dir):
92 print(line, end="") 95 print(line, end="")
93 96
94print("--- done ---") 97print("--- done ---")
95``` 98#+end_src
96 99
97Once you're done, save and close the file. You can now run the Python script and 100Once you're done, save and close the file. You can now run the Python
98watch as each file is updated. 101script and watch as each file is updated.
99 102
100```sh 103#+begin_src sh
101python3 replace.py 104python3 replace.py
102``` 105#+end_src
103 106
104To confirm it worked, you can `cat` one of the configuration files to inspect 107To confirm it worked, you can =cat= one of the configuration files to
105the new logic and connect to one to test it out. 108inspect the new logic and connect to one to test it out.
106 109
107```sh 110#+begin_src sh
108cat /etc/wireguard/us-chi-wg-001.conf 111cat /etc/wireguard/us-chi-wg-001.conf
109``` 112#+end_src
110 113
111The configuration files should now look like this: 114The configuration files should now look like this:
112 115
113```conf 116#+begin_src conf
114[Interface] 117[Interface]
115# Device: <redacted> 118# Device: <redacted>
116PrivateKey = <redacted> 119PrivateKey = <redacted>
@@ -123,19 +126,20 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
123PublicKey = <redacted> 126PublicKey = <redacted>
124AllowedIPs = <redacted> 127AllowedIPs = <redacted>
125Endpoint = <redacted> 128Endpoint = <redacted>
126``` 129#+end_src
127 130
128If you connect to a Wireguard interface, such as `us-chi-wg-001`, you can test 131If you connect to a Wireguard interface, such as =us-chi-wg-001=, you
129your SSH functionality and see that it works even while on the VPN. 132can test your SSH functionality and see that it works even while on the
133VPN.
130 134
131```sh 135#+begin_src sh
132wg-quick up us-chi-wg-001 136wg-quick up us-chi-wg-001
133ssh user@lan-host 137ssh user@lan-host
134``` 138#+end_src
135 139
136To confirm your VPN connection, you can curl Mullvad's connection API: 140To confirm your VPN connection, you can curl Mullvad's connection API:
137 141
138```sh 142#+begin_src sh
139curl https://am.i.mullvad.net/connected 143curl https://am.i.mullvad.net/connected
140# You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted> 144# You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted>
141``` 145#+end_src
content/blog/2023-07-19-plex-transcoder-errors.md → content/blog/2023-07-19-plex-transcoder-errors.org renamed +38 −37
@@ -1,61 +1,62 @@
1+++ 1#+date: <2023-07-19>
2date = 2023-07-19 2#+title: How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.'
3title = "How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.'" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Plex Transcoder Error
9 5
10Occasionally, you may see an error in your Plex client that references a failure 6* Plex Transcoder Error
11with the transcoder conversion process. The specific error wording is:
12 7
13```txt 8Occasionally, you may see an error in your Plex client that references a
9failure with the transcoder conversion process. The specific error
10wording is:
11
12#+begin_src txt
14Conversion failed. The transcoder failed to start up. 13Conversion failed. The transcoder failed to start up.
15``` 14#+end_src
16 15
17# Debugging the Cause 16* Debugging the Cause
18 17
19In order to get a better look at what is causing the error, I'm going to observe 18In order to get a better look at what is causing the error, I'm going to
20the Plex console while the error occurs. To do this, open the Plex web client, 19observe the Plex console while the error occurs. To do this, open the
21go to `Settings` > `Manage` > `Console`. Now, try to play the title again and 20Plex web client, go to =Settings= > =Manage= > =Console=. Now, try to
22watch to see which errors occur. 21play the title again and watch to see which errors occur.
23 22
24In my case, you can see the errors below are related to a subtitle file (`.srt`) 23In my case, you can see the errors below are related to a subtitle file
25causing the transcoder to crash. 24(=.srt=) causing the transcoder to crash.
26 25
27```txt 26#+begin_src txt
28Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt 27Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt
29Jul 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 28Jul 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
30Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID. 29Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID.
31Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available 30Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available
32Jul 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" 31Jul 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"
33``` 32#+end_src
34 33
35# Solving the Error 34* Solving the Error
36 35
37In my case, I simply removed the subtitle file because it was not critical to 36In my case, I simply removed the subtitle file because it was not
38keep. You may also avoid this by turning off subtitles if you don't want to 37critical to keep. You may also avoid this by turning off subtitles if
39delete the file. 38you don't want to delete the file.
40 39
41```sh 40#+begin_src sh
42rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt" 41rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt"
43``` 42#+end_src
44 43
45Once the subtitle is removed from the directory or subtitles are turned off, try 44Once the subtitle is removed from the directory or subtitles are turned
46to play the title again. At this point, it should play without error. If not, 45off, try to play the title again. At this point, it should play without
47reopen or refresh your Plex client and the Plex server: 46error. If not, reopen or refresh your Plex client and the Plex server:
48 47
49```sh 48#+begin_src sh
50sudo systemctl restart plexmediaserver.service 49sudo systemctl restart plexmediaserver.service
51``` 50#+end_src
52 51
53# Related Discussion 52* Related Discussion
54 53
55Looking at [a related Plex forum 54Looking at
56post](https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2), 55[[https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2][a
57it seems that `.srt` subtitles are the core issue here. However, Plex does not 56related Plex forum post]], it seems that =.srt= subtitles are the core
58seem to have a resolution that fixes these errors. 57issue here. However, Plex does not seem to have a resolution that fixes
58these errors.
59 59
60Unfortunately, I would suggest converting subtitle formats, burning the 60Unfortunately, I would suggest converting subtitle formats, burning the
61subtitles into the title, or simply removing subtitles when they cause crashes. 61subtitles into the title, or simply removing subtitles when they cause
62crashes.
content/blog/2023-08-18-agile-auditing.md deleted −150
@@ -1,150 +0,0 @@
1+++
2date = 2023-08-18
3title = "Agile Auditing: An Introduction"
4description = ""
5draft = false
6+++
7
8# What is Agile Auditing?
9
10[Agile](https://en.wikipedia.org/wiki/Agile_software_development), the
11collaborative philosophy behind many software development methods, has been
12picking up steam as a beneficial tool to use in the external and internal
13auditing world.
14
15This blog post will walk through commonly used terms within Agile, Scrum, and
16Kanban in order to translate these terms and roles into audit-specific terms.
17
18Whether your team is in charge of a financial statement audit, an attestation
19(SOC 1, SOC 2, etc.), or a unique internal audit, the terms used throughout this
20post 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 breaking
27> the project into phases and emphasizes continuous collaboration and
28> improvement. Teams follow a cycle of planning, executing, and evaluating.
29
30While this approach may seem familiar to what audit teams have historically
31done, an audit team must make distinct changes in their mentality and how they
32approach and manage a project.
33
34## Agile Values
35
36The Agile Manifesto, written in 2001 at a summit in Utah, contain a set of four
37main values that comprise the Agile approach:
38
391. Individuals and interactions over processes and tools.
402. Working software over comprehensive documentation.
413. Customer collaboration over contract negotiation.
424. Responding to change over following a plan.
43
44Beyond the four values, [twelve
45principles](https://agilemanifesto.org/principles.html) were also written as
46part of the summit.
47
48In order to relate these values to an audit or attestation engagement, we need
49to shift the focus from software development to the main goal of an engagement:
50completing sufficient audit testing to address to relevant risks over the
51processes and controls at hand.
52
53Audit Examples:
54
55- Engagement teams must value the team members, client contacts, and their
56 interactions over the historical processes and tools that have been used.
57- Engagement teams must value a final report that contains sufficient audit
58 documentation over excessive documentation or scope creep.
59- Engagement teams must collaborate with the audit clients as much as feasible
60 to ensure that both sides are constantly updated with current knowledge of
61 the engagement's status and any potential findings, rather than waiting for
62 pre-set meetings or the end of the engagement to communicate.
63- Engagement teams must be able to respond to change in an engagement's
64 schedule, scope, or environment to ensure that the project is completed in a
65 timely manner and that all relevant areas are tested.
66 - In terms of an audit department's portfolio, they must be able to
67 respond to changes in their company's or client's environment and be
68 able to dynamically change their audit plan accordingly.
69
70# Scrum
71
72The above section discusses the high-level details of the Agile philosophy and
73how an audit team can potentially mold that mindset into the audit world, but
74how does a team implement these ideas?
75
76There are many methods that use an Agile mindset, but I prefer
77[Scrum](<https://en.wikipedia.org/wiki/Scrum_(software_development)>). Scrum is
78a framework based on Agile that enables a team to work through a project through
79a series of roles, ceremonies, artifacts, and values.
80
81Let's dive into each of these individually.
82
83## Scrum Team
84
85A scrum project is only as good as the team running the project. Standard scrum
86teams are separated into three distinct areas:
87
881. **Product Owner (Client Contact)**: The client contact is the audit
89 equivalent of the product owner in Scrum. They are responsible for partnering
90 with the engagement or audit team to ensure progress is being made,
91 priorities are established, and clear guidance is given when questions or
92 findings arise within each sprint.
932. **Scrum Master (Engagement Lead)**: The engagement or audit team lead is
94 responsible for coaching the team and the client contact on the scrum
95 process, tracking team progress against plan, scheduling necessary resources,
96 and helping remove obstacles.
973. **Scrum Developers (Engagement Members)**: The engagement or audit team is
98 the set of team members responsible for getting the work done. These team
99 members will work on each task, report progress, resolve obstacles, and
100 collaborate with other team members and the client contact to ensure goals
101 are being met.
102
103## Scrum Ceremonies
104
105Scrum ceremonies are events that are performed on a regular basis.
106
1071. **Sprint Planning**: The team works together to plan the upcoming sprint goal
108 and which user stories (tasks) will be added to the sprint to achieve that
109 goal.
1102. **Sprint**: The time period, typically at least one week and no more than one
111 month in length, where the team works on the stories and anything in the
112 backlog.
1133. **Daily Scrum**: A very short meeting held each day, typically 15 minutes, to
114 quickly emphasize alignment on the sprint goal and plan the next 24 hours.
115 Each team member may share what they did the day before, what they'll do
116 today, and any obstacles to their work.
1174. **Sprint Review**: At the end of each sprint, the team will gather and
118 discuss the progress, obstacles, and backlog from the previous sprint.
1195. **Sprint Retrospective**: More specific than the sprint review, the
120 retrospective is meant to discuss what worked and what did not work during
121 the sprint. This may be processes, tools, people, or even things related to
122 the Scrum ceremonies.
123
124One additional ceremony that may be applicable is organizing the backlog. This
125is typically the responsibility of the engagement leader and is meant to
126prioritize and clarify what needs to be done to complete items in the backlog.
127
128## Artifacts
129
130While artifacts are generally not customizable in the audit world (i.e., each
131control test must include some kind of working paper with evidence supporting
132the test results), I wanted to include some quick notes on associating scrum
133artifact terms with an audit.
134
1351. **Product Backlog**: This is the overall backlog of unfinished audit tasks
136 from all prior sprints.
1372. **Sprint Backlog**: This is the backlog of unfinished audit tasks from one
138 individual sprint.
1393. **Increment**: This is the output of each sprint - generally this is best
140 thought of as any documentation prepared during the sprint, such as risk
141 assessments, control working papers, deficiency analysis, etc.
142
143# Kanban
144
145Last but not least, Kanban is a methodology that relies on boards to categorize
146work into distinct, descriptive categories that allow an agile or scrum team to
147effectively plan the work of a sprint or project.
148
149See Atlassian's [Kanban](https://www.atlassian.com/agile/kanban) page for more
150information.
content/blog/2023-08-18-agile-auditing.org added +157
@@ -0,0 +1,157 @@
1#+date: <2023-08-18>
2#+title: Agile Auditing: An Introduction
3#+description:
4
5
6* What is Agile Auditing?
7
8[[https://en.wikipedia.org/wiki/Agile_software_development][Agile]], the
9collaborative philosophy behind many software development methods, has
10been picking up steam as a beneficial tool to use in the external and
11internal auditing world.
12
13This blog post will walk through commonly used terms within Agile,
14Scrum, and Kanban in order to translate these terms and roles into
15audit-specific terms.
16
17Whether your team is in charge of a financial statement audit, an
18attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms
19used throughout this post should still apply.
20
21* Agile
22
23To start, I'll take a look at Agile.
24
25#+begin_quote
26The Agile methodology is a project management approach that involves
27breaking the project into phases and emphasizes continuous collaboration
28and improvement. Teams follow a cycle of planning, executing, and
29evaluating.
30#+end_quote
31
32While this approach may seem familiar to what audit teams have
33historically done, an audit team must make distinct changes in their
34mentality and how they approach and manage a project.
35
36** Agile Values
37
38The Agile Manifesto, written in 2001 at a summit in Utah, contain a set
39of four main values that comprise the Agile approach:
40
411. Individuals and interactions over processes and tools.
422. Working software over comprehensive documentation.
433. Customer collaboration over contract negotiation.
444. Responding to change over following a plan.
45
46Beyond the four values,
47[[https://agilemanifesto.org/principles.html][twelve principles]] were
48also written as part of the summit.
49
50In order to relate these values to an audit or attestation engagement,
51we need to shift the focus from software development to the main goal of
52an engagement: completing sufficient audit testing to address to
53relevant risks over the processes and controls at hand.
54
55Audit Examples:
56
57- Engagement teams must value the team members, client contacts, and
58 their interactions over the historical processes and tools that have
59 been used.
60- Engagement teams must value a final report that contains sufficient
61 audit documentation over excessive documentation or scope creep.
62- Engagement teams must collaborate with the audit clients as much as
63 feasible to ensure that both sides are constantly updated with current
64 knowledge of the engagement's status and any potential findings,
65 rather than waiting for pre-set meetings or the end of the engagement
66 to communicate.
67- Engagement teams must be able to respond to change in an engagement's
68 schedule, scope, or environment to ensure that the project is
69 completed in a timely manner and that all relevant areas are tested.
70 - In terms of an audit department's portfolio, they must be able to
71 respond to changes in their company's or client's environment and be
72 able to dynamically change their audit plan accordingly.
73
74* Scrum
75
76The above section discusses the high-level details of the Agile
77philosophy and how an audit team can potentially mold that mindset into
78the audit world, but how does a team implement these ideas?
79
80There are many methods that use an Agile mindset, but I prefer
81[[https://en.wikipedia.org/wiki/Scrum_(software_development)][Scrum]].
82Scrum is a framework based on Agile that enables a team to work through
83a project through a series of roles, ceremonies, artifacts, and values.
84
85Let's dive into each of these individually.
86
87** Scrum Team
88
89A scrum project is only as good as the team running the project.
90Standard scrum teams are separated into three distinct areas:
91
921. *Product Owner (Client Contact)*: The client contact is the audit
93 equivalent of the product owner in Scrum. They are responsible for
94 partnering with the engagement or audit team to ensure progress is
95 being made, priorities are established, and clear guidance is given
96 when questions or findings arise within each sprint.
972. *Scrum Master (Engagement Lead)*: The engagement or audit team lead
98 is responsible for coaching the team and the client contact on the
99 scrum process, tracking team progress against plan, scheduling
100 necessary resources, and helping remove obstacles.
1013. *Scrum Developers (Engagement Members)*: The engagement or audit team
102 is the set of team members responsible for getting the work done.
103 These team members will work on each task, report progress, resolve
104 obstacles, and collaborate with other team members and the client
105 contact to ensure goals are being met.
106
107** Scrum Ceremonies
108
109Scrum ceremonies are events that are performed on a regular basis.
110
1111. *Sprint Planning*: The team works together to plan the upcoming
112 sprint goal and which user stories (tasks) will be added to the
113 sprint to achieve that goal.
1142. *Sprint*: The time period, typically at least one week and no more
115 than one month in length, where the team works on the stories and
116 anything in the backlog.
1173. *Daily Scrum*: A very short meeting held each day, typically 15
118 minutes, to quickly emphasize alignment on the sprint goal and plan
119 the next 24 hours. Each team member may share what they did the day
120 before, what they'll do today, and any obstacles to their work.
1214. *Sprint Review*: At the end of each sprint, the team will gather and
122 discuss the progress, obstacles, and backlog from the previous
123 sprint.
1245. *Sprint Retrospective*: More specific than the sprint review, the
125 retrospective is meant to discuss what worked and what did not work
126 during the sprint. This may be processes, tools, people, or even
127 things related to the Scrum ceremonies.
128
129One additional ceremony that may be applicable is organizing the
130backlog. This is typically the responsibility of the engagement leader
131and is meant to prioritize and clarify what needs to be done to complete
132items in the backlog.
133
134** Artifacts
135
136While artifacts are generally not customizable in the audit world (i.e.,
137each control test must include some kind of working paper with evidence
138supporting the test results), I wanted to include some quick notes on
139associating scrum artifact terms with an audit.
140
1411. *Product Backlog*: This is the overall backlog of unfinished audit
142 tasks from all prior sprints.
1432. *Sprint Backlog*: This is the backlog of unfinished audit tasks from
144 one individual sprint.
1453. *Increment*: This is the output of each sprint - generally this is
146 best thought of as any documentation prepared during the sprint, such
147 as risk assessments, control working papers, deficiency analysis,
148 etc.
149
150* Kanban
151
152Last but not least, Kanban is a methodology that relies on boards to
153categorize work into distinct, descriptive categories that allow an
154agile or scrum team to effectively plan the work of a sprint or project.
155
156See Atlassian's [[https://www.atlassian.com/agile/kanban][Kanban]] page
157for more information.
content/blog/2023-09-15-self-hosting-gitweb.md → content/blog/2023-09-15-self-hosting-gitweb.org renamed +30 −30
@@ -1,36 +1,35 @@
1+++ 1#+date: <2023-09-15>
2date = 2023-09-15 2#+title: Self-Hosting Gitweb via Nginx
3title = "Self-Hosting Gitweb via Nginx" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Overview
9 5
10[GitWeb](https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb) is a simple 6* Overview
11web-based visualizer for git repositories. By default, GitWeb will only run with
12the `lighttpd` or `webrick` web servers.
13 7
14However, this guide will show you how to keep GitWeb running in the background 8[[https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb][GitWeb]] is a
15and display information for all repositories in a chosen directory. 9simple web-based visualizer for git repositories. By default, GitWeb
10will only run with the =lighttpd= or =webrick= web servers.
16 11
17# Install Dependencies 12However, this guide will show you how to keep GitWeb running in the
13background and display information for all repositories in a chosen
14directory.
15
16* Install Dependencies
18 17
19To start, you'll need install the following packages: 18To start, you'll need install the following packages:
20 19
21```sh 20#+begin_src sh
22sudo apt install git gitweb fcgiwrap nginx 21sudo apt install git gitweb fcgiwrap nginx
23``` 22#+end_src
24 23
25# Configure Nginx 24* Configure Nginx
26 25
27Once installed, create an Nginx configuration file. 26Once installed, create an Nginx configuration file.
28 27
29```sh 28#+begin_src sh
30sudo nano /etc/nginx/sites-available/git.example.com 29sudo nano /etc/nginx/sites-available/git.example.com
31``` 30#+end_src
32 31
33```conf 32#+begin_src conf
34server { 33server {
35 listen 80; 34 listen 80;
36 server_name example.com; 35 server_name example.com;
@@ -49,23 +48,24 @@ server {
49 index index.cgi; 48 index index.cgi;
50 } 49 }
51} 50}
52``` 51#+end_src
53 52
54To make the configuration active, you need to symlink it and then restart Nginx. 53To make the configuration active, you need to symlink it and then
54restart Nginx.
55 55
56```sh 56#+begin_src sh
57sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com 57sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com
58sudo systemctl restart nginx.service 58sudo systemctl restart nginx.service
59``` 59#+end_src
60 60
61The GitWeb application should now be available via the URL you set in the Nginx 61The GitWeb application should now be available via the URL you set in
62configuration above. 62the Nginx configuration above.
63 63
64# Customize GitWeb 64* Customize GitWeb
65 65
66If you need to, you can customize many things about Gitweb by editing the 66If you need to, you can customize many things about Gitweb by editing
67[gitweb.conf](https://git-scm.com/docs/gitweb.conf) file. 67the [[https://git-scm.com/docs/gitweb.conf][gitweb.conf]] file.
68 68
69```sh 69#+begin_src sh
70sudo nano /etc/gitweb.conf 70sudo nano /etc/gitweb.conf
71``` 71#+end_src
content/blog/2023-09-19-audit-sql-scripts.md → content/blog/2023-09-19-audit-sql-scripts.org renamed +62 −59
@@ -1,24 +1,23 @@
1+++ 1#+date: <2023-09-19>
2date = 2023-09-19 2#+title: Useful SQL Scripts for Auditing Logical Access
3title = "Useful SQL Scripts for Auditing Logical Access" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Overview
9 5
10When you have to scope a database into your engagement, you may be curious how 6* Overview
11to best extract the information from the database. While there are numerous
12different methods to extract this type of information, I'm going to show an
13example of how to gather all users and privileges from three main database
14types: Oracle, Microsoft SQL, and MySQL.
15 7
16# Oracle 8When you have to scope a database into your engagement, you may be
9curious how to best extract the information from the database. While
10there are numerous different methods to extract this type of
11information, I'm going to show an example of how to gather all users and
12privileges from three main database types: Oracle, Microsoft SQL, and
13MySQL.
17 14
18You can use the following SQL script to see all users and their privileges in an 15* Oracle
19Oracle database:
20 16
21```sql 17You can use the following SQL script to see all users and their
18privileges in an Oracle database:
19
20#+begin_src sql
22SELECT 21SELECT
23 grantee AS "User", 22 grantee AS "User",
24 privilege AS "Privilege" 23 privilege AS "Privilege"
@@ -34,37 +33,39 @@ FROM
34 dba_tab_privs 33 dba_tab_privs
35WHERE 34WHERE
36 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs); 35 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs);
37``` 36#+end_src
38 37
39This script queries the `dba_sys_privs` and `dba_tab_privs` views to retrieve 38This script queries the =dba_sys_privs= and =dba_tab_privs= views to
40system and table-level privileges respectively. It then combines the results 39retrieve system and table-level privileges respectively. It then
41using `UNION ALL` to show all users and their associated privileges. Please note 40combines the results using =UNION ALL= to show all users and their
42that this method does not extract information from the `dba_role_privs` table - 41associated privileges. Please note that this method does not extract
43use the method below for that data. 42information from the =dba_role_privs= table - use the method below for
43that data.
44 44
45Please note that you might need appropriate privileges (e.g., DBA privileges) to 45Please note that you might need appropriate privileges (e.g., DBA
46access these views, and you should exercise caution when querying system tables 46privileges) to access these views, and you should exercise caution when
47in a production Oracle database. 47querying system tables in a production Oracle database.
48 48
49## Alternative Oracle Query 49** Alternative Oracle Query
50 50
51You can also extract each table's information separately and perform processing 51You can also extract each table's information separately and perform
52outside the database to explore and determine the information necessary for the 52processing outside the database to explore and determine the information
53audit: 53necessary for the audit:
54 54
55```sql 55#+begin_src sql
56SELECT ** FROM sys.dba_role_privs; 56SELECT ** FROM sys.dba_role_privs;
57SELECT ** FROM sys.dba_sys_privs; 57SELECT ** FROM sys.dba_sys_privs;
58SELECT ** FROM sys.dba_tab_privs; 58SELECT ** FROM sys.dba_tab_privs;
59SELECT ** FROM sys.dba_users; 59SELECT ** FROM sys.dba_users;
60``` 60#+end_src
61 61
62# Microsoft SQL 62* Microsoft SQL
63 63
64You can use the following SQL script to see all users and their privileges in a 64You can use the following SQL script to see all users and their
65Microsoft SQL Server database ([source](https://stackoverflow.com/a/30040784)): 65privileges in a Microsoft SQL Server database
66([[https://stackoverflow.com/a/30040784][source]]):
66 67
67```sql 68#+begin_src sql
68/* 69/*
69Security Audit Report 70Security Audit Report
701) List all access provisioned to a sql user or windows user/group directly 711) List all access provisioned to a sql user or windows user/group directly
@@ -95,7 +96,7 @@ ObjectName : Name of the object that the user/role is assigned permissions
95 definitions. 96 definitions.
96ColumnName : Name of the column of the object that the user/role is assigned permissions on. This value 97ColumnName : Name of the column of the object that the user/role is assigned permissions on. This value
97 is only populated if the object is a table, view or a table value function. 98 is only populated if the object is a table, view or a table value function.
98*/ 99,*/
99 100
100--List all access provisioned to a sql user or windows user/group directly 101--List all access provisioned to a sql user or windows user/group directly
101SELECT 102SELECT
@@ -209,41 +210,43 @@ ORDER BY
209 perm.[permission_name], 210 perm.[permission_name],
210 perm.[state_desc], 211 perm.[state_desc],
211 obj.type_desc--perm.[class_desc] 212 obj.type_desc--perm.[class_desc]
212``` 213#+end_src
213 214
214# MySQL 215* MySQL
215 216
216You can use the following SQL script to see all users and their privileges in a 217You can use the following SQL script to see all users and their
217MySQL database: 218privileges in a MySQL database:
218 219
219```sh 220#+begin_src sh
220mysql -u root -p 221mysql -u root -p
221``` 222#+end_src
222 223
223Find all users and hosts with access to the database: 224Find all users and hosts with access to the database:
224 225
225```sql 226#+begin_src sql
226SELECT ** FROM information_schema.user_privileges; 227SELECT ** FROM information_schema.user_privileges;
227``` 228#+end_src
228 229
229This script retrieves user information and their associated database-level 230This script retrieves user information and their associated
230privileges from the `information_schema.user_privileges` table in MySQL. It 231database-level privileges from the =information_schema.user_privileges=
231lists various privileges such as SELECT, INSERT, UPDATE, DELETE, CREATE, and 232table in MySQL. It lists various privileges such as SELECT, INSERT,
232more for each user and database combination. 233UPDATE, DELETE, CREATE, and more for each user and database combination.
233 234
234Please note that you may need appropriate privileges (e.g., `SELECT` privileges 235Please note that you may need appropriate privileges (e.g., =SELECT=
235on `information_schema.user_privileges`) to access this information in a MySQL 236privileges on =information_schema.user_privileges=) to access this
236database. Additionally, some privileges like GRANT OPTION, EXECUTE, EVENT, and 237information in a MySQL database. Additionally, some privileges like
237TRIGGER may not be relevant for all users and databases. 238GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all
239users and databases.
238 240
239## Alternative MySQL Query 241** Alternative MySQL Query
240 242
241You can also grab individual sets of data from MySQL if you prefer to join them 243You can also grab individual sets of data from MySQL if you prefer to
242after extraction. I have marked the queries below with `SELECT ...` and excluded 244join them after extraction. I have marked the queries below with
243most `WHERE` clauses for brevity. You should determine the relevant privileges 245=SELECT ...= and excluded most =WHERE= clauses for brevity. You should
244in-scope and query for those privileges to reduce the length of time to query. 246determine the relevant privileges in-scope and query for those
247privileges to reduce the length of time to query.
245 248
246```sql 249#+begin_src sql
247-- Global Permissions 250-- Global Permissions
248SELECT ... FROM mysql.user; 251SELECT ... FROM mysql.user;
249 252
@@ -262,4 +265,4 @@ WHERE db = @db_name;
262-- Password Configuration 265-- Password Configuration
263SHOW GLOBAL VARIABLES LIKE 'validate_password%'; 266SHOW GLOBAL VARIABLES LIKE 'validate_password%';
264SHOW VARIABLES LIKE 'validate_password%'; 267SHOW VARIABLES LIKE 'validate_password%';
265``` 268#+end_src
content/blog/2023-10-04-digital-minimalism.md deleted −99
@@ -1,99 +0,0 @@
1+++
2date = 2023-10-04
3title = "Digital Minimalism"
4description = ""
5draft = false
6+++
7
8I've written [a note about minimalism](file:///wiki/#digital-garden) before, but
9I wanted to dedicate some time to reflect on digital minimalism and how I've
10been able to minimize the impact of digital devices in my life.
11
12> These changes crept up on us and happened fast, before we had a chance to step
13> back and ask what we really wanted out of the rapid advances of the past
14> decade. We added new technologies to the periphery of our experience for minor
15> reasons, then woke one morning to discover that they had colonized the core of
16> our daily life. We didn't, in other words, sign up for the digital world in
17> which we're currently entrenched; we seem to have stumbled backward into it.
18>
19> _(Digital Minimalism, 2019)_
20
21# The Principles of Digital Minimalism
22
23As noted in Cal Newport's book, _Digital Minimalism_, there are three main
24principles to digital minimalism that I tend to agree with:
25
261. Clutter is costly.
27 - Digital minimalists recognize that cluttering their time and attention
28 with too many devices, apps, and services creates an overall negative
29 cost that can swamp the small benefits that each individual item
30 provides in isolation.
312. Optimization is important.
32 - Digital minimalists believe that deciding a particular technology
33 supports something they value is only the first step. To truly extract
34 its full potential benefit, it's necessary to think carefully about how
35 they'll use the technology.
363. Intentionality is satisfying.
37 - Digital minimalists derive significant satisfaction from their general
38 commitment to being more intentional about how they engage with new
39 technologies. This source of satisfaction is independent of the specific
40 decisions they make and is one of the biggest reasons that minimalism
41 tends to be immensely meaningful to its practitioners.
42
43# Taking Action
44
45In order to put the logic into practice, I've created a few new habits and
46continued performing old habits that are working well:
47
48## Using Devices With Intention
49
50- I already rarely use "social media", mostly limited to forums such as Hacker
51 News and Tildes, so I've just tweaked my behavior to stop looking for
52 content in those places when I'm bored.
53- Use devices with intention. Each time I pick up a digital device, there
54 should be an intention to use the device to improve my current situation. No
55 more endless scrolling or searching for something to interest me.
56
57## Prevent Distractions
58
59- Disable (most) notifications on all devices. I spent 15-30 minutes going
60 through the notifications on my phone, watch, and computer to ensure that
61 only a select few apps have the ability to interrupt me: Calendar, Messages,
62 Phone, Reminders, & Signal.
63- Disable badges for any apps except the ones mentioned in the bullet above.
64- Set-up focus profiles across devices so that I can enable different modes,
65 such as Personal when I only want to see notifications from people I care
66 about or Do Not Disturb, where absolutely nothing can interrupt me.
67- Clean up my home screens. This one was quite easy as I already maintain a
68 minimalist set-up, but I went extreme by limiting my phone to just eight
69 apps on the home screen and four in the dock. If I need another app, I'll
70 have to search or use the app library.
71- Remove the work profile from my phone. This was a tough decision as having
72 my work profile on my device definitely makes my life easier at times, but
73 it also has quite a negative effect when I'm "always online" and can see the
74 notifications and team activity 24/7. I believe creating a distinct barrier
75 between my work and personal devices will be beneficial in the end.
76
77## Creating Alternative Activities
78
79This is the most difficult piece, as most of my hobbies and interests lie in the
80digital world. However, I'm making a concerted effort to put devices down unless
81necessary and force myself to perform other activities in the physical world
82instead.
83
84I've started with a few basics that are always readily available to me:
85
86- Do a chore, such as organizing or cleaning.
87- Read a book, study a piece of art, etc.
88- Exercise or get outdoors.
89- Participate in a hobby, such as photography, birding, disc golf, etc.
90- Let yourself be bored and wander into creativity.
91
92# Making Progress
93
94I'll be taking notes as I continue down this journey and hope to see positive
95trends. I've always been a minimalist in the physical world and it feels
96refreshing to filter out the clutter that has come to dominate my digital life
97over the years.
98
99I'm excited to see where this journey leads.
content/blog/2023-10-04-digital-minimalism.org added +106
@@ -0,0 +1,106 @@
1#+date: <2023-10-04>
2#+title: Digital Minimalism
3#+description:
4
5
6I've written [[file:///wiki/#digital-garden][a note about minimalism]]
7before, but I wanted to dedicate some time to reflect on digital
8minimalism and how I've been able to minimize the impact of digital
9devices in my life.
10
11#+begin_quote
12These changes crept up on us and happened fast, before we had a chance
13to step back and ask what we really wanted out of the rapid advances of
14the past decade. We added new technologies to the periphery of our
15experience for minor reasons, then woke one morning to discover that
16they had colonized the core of our daily life. We didn't, in other
17words, sign up for the digital world in which we're currently
18entrenched; we seem to have stumbled backward into it.
19
20/(Digital Minimalism, 2019)/
21#+end_quote
22
23* The Principles of Digital Minimalism
24
25As noted in Cal Newport's book, /Digital Minimalism/, there are three
26main principles to digital minimalism that I tend to agree with:
27
281. Clutter is costly.
29 - Digital minimalists recognize that cluttering their time and
30 attention with too many devices, apps, and services creates an
31 overall negative cost that can swamp the small benefits that each
32 individual item provides in isolation.
332. Optimization is important.
34 - Digital minimalists believe that deciding a particular technology
35 supports something they value is only the first step. To truly
36 extract its full potential benefit, it's necessary to think
37 carefully about how they'll use the technology.
383. Intentionality is satisfying.
39 - Digital minimalists derive significant satisfaction from their
40 general commitment to being more intentional about how they engage
41 with new technologies. This source of satisfaction is independent
42 of the specific decisions they make and is one of the biggest
43 reasons that minimalism tends to be immensely meaningful to its
44 practitioners.
45
46* Taking Action
47
48In order to put the logic into practice, I've created a few new habits
49and continued performing old habits that are working well:
50
51** Using Devices With Intention
52
53- I already rarely use "social media", mostly limited to forums such as
54 Hacker News and Tildes, so I've just tweaked my behavior to stop
55 looking for content in those places when I'm bored.
56- Use devices with intention. Each time I pick up a digital device,
57 there should be an intention to use the device to improve my current
58 situation. No more endless scrolling or searching for something to
59 interest me.
60
61** Prevent Distractions
62
63- Disable (most) notifications on all devices. I spent 15-30 minutes
64 going through the notifications on my phone, watch, and computer to
65 ensure that only a select few apps have the ability to interrupt me:
66 Calendar, Messages, Phone, Reminders, & Signal.
67- Disable badges for any apps except the ones mentioned in the bullet
68 above.
69- Set-up focus profiles across devices so that I can enable different
70 modes, such as Personal when I only want to see notifications from
71 people I care about or Do Not Disturb, where absolutely nothing can
72 interrupt me.
73- Clean up my home screens. This one was quite easy as I already
74 maintain a minimalist set-up, but I went extreme by limiting my phone
75 to just eight apps on the home screen and four in the dock. If I need
76 another app, I'll have to search or use the app library.
77- Remove the work profile from my phone. This was a tough decision as
78 having my work profile on my device definitely makes my life easier at
79 times, but it also has quite a negative effect when I'm "always
80 online" and can see the notifications and team activity 24/7. I
81 believe creating a distinct barrier between my work and personal
82 devices will be beneficial in the end.
83
84** Creating Alternative Activities
85
86This is the most difficult piece, as most of my hobbies and interests
87lie in the digital world. However, I'm making a concerted effort to put
88devices down unless necessary and force myself to perform other
89activities in the physical world instead.
90
91I've started with a few basics that are always readily available to me:
92
93- Do a chore, such as organizing or cleaning.
94- Read a book, study a piece of art, etc.
95- Exercise or get outdoors.
96- Participate in a hobby, such as photography, birding, disc golf, 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 and
103it feels refreshing to filter out the clutter that has come to dominate
104my digital life over the years.
105
106I'm excited to see where this journey leads.
content/blog/2023-10-11-self-hosting-authelia.md → content/blog/2023-10-11-self-hosting-authelia.org renamed +111 −109
@@ -1,55 +1,54 @@
1+++ 1#+date: <2023-10-11>
2date = 2023-10-11 2#+title: Self-Hosting Authelia
3title = "Self-Hosting Authelia" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Overview
9 5
10[Authelia](https://www.authelia.com/) is an open-source authentication service 6* Overview
11that allows you to place a portal between end users on the internet and
12self-hosted services on your server.
13 7
14You can require one factor (username+password) or two factor authentication for 8[[https://www.authelia.com/][Authelia]] is an open-source authentication
15any such user before allowing them to access a specific service on your domain. 9service that allows you to place a portal between end users on the
10internet and self-hosted services on your server.
11
12You can require one factor (username+password) or two factor
13authentication for any such user before allowing them to access a
14specific service on your domain.
16 15
17This guide will walk through a standard installation of Authelia for 16This guide will walk through a standard installation of Authelia for
18`example.com`, using `auth.example.com` as Authelia's authentication domain and 17=example.com=, using =auth.example.com= as Authelia's authentication
19`teddit.example.com` as the website we want to protect behind the authentication 18domain and =teddit.example.com= as the website we want to protect behind
20portal. 19the authentication portal.
21 20
22# Prerequisites 21* Prerequisites
23 22
24This guide assumes you have the following already set-up: 23This guide assumes you have the following already set-up:
25 24
26- A registered domain with DNS pointing to your server. 25- A registered domain with DNS pointing to your server.
27- A subdomain for Authelia (`auth.example.com`) and a subdomain to protect via 26- A subdomain for Authelia (=auth.example.com=) and a subdomain to
28 Authelia (`app.example.com`). 27 protect via Authelia (=app.example.com=).
29- A working Nginx web server. 28- A working Nginx web server.
30- Docker and docker-compose installed. 29- Docker and docker-compose installed.
31 30
32# Installation 31* Installation
33 32
34This guide will walk through each installation step one-by-one, starting with 33This guide will walk through each installation step one-by-one, starting
35the container and finishing by cleaning up external access via an Nginx reverse 34with the container and finishing by cleaning up external access via an
36proxy. 35Nginx reverse proxy.
37 36
38## Docker-Compose 37** Docker-Compose
39 38
40To start, create a directory for Authelia and create a `docker-compose.yml` 39To start, create a directory for Authelia and create a
41file. 40=docker-compose.yml= file.
42 41
43```sh 42#+begin_src sh
44mkdir ~/authelia 43mkdir ~/authelia
45nano ~/authelia/docker-compose.yml 44nano ~/authelia/docker-compose.yml
46``` 45#+end_src
47 46
48Within this file, paste the following content. If you prefer a different local 47Within this file, paste the following content. If you prefer a different
49port, modify the port on the left side of the colon on the `9091:9091` line. Be 48local port, modify the port on the left side of the colon on the
50sure to modify the `TZ` variable to your timezone. 49=9091:9091= line. Be sure to modify the =TZ= variable to your timezone.
51 50
52```yml 51#+begin_src yml
53version: "3.3" 52version: "3.3"
54 53
55services: 54services:
@@ -62,51 +61,53 @@ services:
62 - 9091:9091 61 - 9091:9091
63 environment: 62 environment:
64 - TZ=America/Chicago 63 - TZ=America/Chicago
65``` 64#+end_src
66 65
67Start the container with docker-compose: 66Start the container with docker-compose:
68 67
69```sh 68#+begin_src sh
70sudo docker-compose up -d 69sudo docker-compose up -d
71``` 70#+end_src
72 71
73After the first start, the container will automatically exit and require you to 72After the first start, the container will automatically exit and require
74modify the app's configuration files before continuing. Read on to learn more. 73you to modify the app's configuration files before continuing. Read on
74to learn more.
75 75
76## Authelia Configuration 76** Authelia Configuration
77 77
78To configure Authelia before we restart the container, we need to open the 78To configure Authelia before we restart the container, we need to open
79`config` directory and modify the files. Start by editing the 79the =config= directory and modify the files. Start by editing the
80`configuration.yml` file, where all of Authelia's settings are stored. 80=configuration.yml= file, where all of Authelia's settings are stored.
81 81
82My personal preference is to copy the original configuration file to a backup 82My personal preference is to copy the original configuration file to a
83file and edit a fresh copy. 83backup file and edit a fresh copy.
84 84
85```sh 85#+begin_src sh
86sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk 86sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk
87sudo nano ~/authelia/config/configuration.yml 87sudo nano ~/authelia/config/configuration.yml
88``` 88#+end_src
89 89
90Within the blank `configuration.yml` file, paste the following information. You 90Within the blank =configuration.yml= file, paste the following
91will need to make quite a few updates, so be sure to read each line carefully 91information. You will need to make quite a few updates, so be sure to
92and modify as necessary. 92read each line carefully and modify as necessary.
93 93
94The major required changes are: 94The major required changes are:
95 95
96- Any instances of `example.com` should be replaced by your domain. 96- Any instances of =example.com= should be replaced by your domain.
97- `jwt_secret` - Use the `pwgen 40 1` command to generate a secret for 97- =jwt_secret= - Use the =pwgen 40 1= command to generate a secret for
98 yourself. 98 yourself.
99- `access_control` - Set the Authelia domain to bypass here, as well as any 99- =access_control= - Set the Authelia domain to bypass here, as well as
100 subdomains you want to protect. 100 any subdomains you want to protect.
101- `session` > `secret` - Use the `pwgen 40 1` command to generate a secret for 101- =session= > =secret= - Use the =pwgen 40 1= command to generate a
102 yourself. 102 secret for yourself.
103- `regulation` - Set the variables here to restrict login attempts and bans. 103- =regulation= - Set the variables here to restrict login attempts and
104- `storage` > `encryption_key` - Use the `pwgen 40 1` command to generate a 104 bans.
105 secret for yourself. 105- =storage= > =encryption_key= - Use the =pwgen 40 1= command to
106- `smtp` - If you have access to an SMTP service, set up the information here 106 generate a secret for yourself.
107 to active outgoing emails. 107- =smtp= - If you have access to an SMTP service, set up the information
108 108 here to active outgoing emails.
109```yml 109
110#+begin_src yml
110# yamllint disable rule:comments-indentation 111# yamllint disable rule:comments-indentation
111--- 112---
112############################################################################### 113###############################################################################
@@ -194,25 +195,26 @@ notifier:
194 tls: 195 tls:
195 skip_verify: false 196 skip_verify: false
196 minimum_version: TLS1.2 197 minimum_version: TLS1.2
197``` 198#+end_src
198 199
199## Authelia Users 200** Authelia Users
200 201
201Next, create the users file for authentication. 202Next, create the users file for authentication.
202 203
203```sh 204#+begin_src sh
204sudo nano ~/authelia/config/users_database.yml 205sudo nano ~/authelia/config/users_database.yml
205``` 206#+end_src
206 207
207Within the file, you will need to create an entry for each user that needs 208Within the file, you will need to create an entry for each user that
208access to Authelia. The `my_username` entry will be the username used on the 209needs access to Authelia. The =my_username= entry will be the username
209login page. 210used on the login page.
210 211
211To generate the password, go to [Argon2 Hash Generator](https://argon2.online), 212To generate the password, go to [[https://argon2.online][Argon2 Hash
212generate a random salt, and make sure the rest of the settings match the 213Generator]], generate a random salt, and make sure the rest of the
213`authentication_backend` section of `configuration.yml` file. 214settings match the =authentication_backend= section of
215=configuration.yml= file.
214 216
215```yaml 217#+begin_src yaml
216users: 218users:
217 my_username: 219 my_username:
218 displayname: "My User" 220 displayname: "My User"
@@ -223,31 +225,31 @@ users:
223 groups: 225 groups:
224 - admins 226 - admins
225 - dev 227 - dev
226``` 228#+end_src
227 229
228Once the app is configured, restart the container from scratch. 230Once the app is configured, restart the container from scratch.
229 231
230```sh 232#+begin_src sh
231cd ~/authelia 233cd ~/authelia
232sudo docker-compose down && sudo docker-compose up -d 234sudo docker-compose down && sudo docker-compose up -d
233``` 235#+end_src
234 236
235## Nginx: Authelia Domain 237** Nginx: Authelia Domain
236 238
237Once the container is running and configured, the final step is to configure 239Once the container is running and configured, the final step is to
238external access to the server via Nginx reverse proxy. 240configure external access to the server via Nginx reverse proxy.
239 241
240Start by creating the Authelia domain. 242Start by creating the Authelia domain.
241 243
242```sh 244#+begin_src sh
243sudo nano /etc/nginx/sites-available/auth 245sudo nano /etc/nginx/sites-available/auth
244``` 246#+end_src
245 247
246Within this file, paste the following information and be sure to update 248Within this file, paste the following information and be sure to update
247`example.com` to your domain. Make sure the `$upstream_authelia` variable 249=example.com= to your domain. Make sure the =$upstream_authelia=
248matches the location of your Authelia container. 250variable matches the location of your Authelia container.
249 251
250```conf 252#+begin_src conf
251server { 253server {
252 if ($host ~ ^[^.]+\.example\.com$) { 254 if ($host ~ ^[^.]+\.example\.com$) {
253 return 301 https://$host$request_uri; 255 return 301 https://$host$request_uri;
@@ -299,30 +301,30 @@ server {
299 } 301 }
300 302
301} 303}
302``` 304#+end_src
303 305
304Next, symlink the file and restart Nginx. If there are errors, be sure to 306Next, symlink the file and restart Nginx. If there are errors, be sure
305resolve those before moving on. 307to resolve those before moving on.
306 308
307```sh 309#+begin_src sh
308sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth 310sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth
309sudo systemctl restart nginx.service 311sudo systemctl restart nginx.service
310``` 312#+end_src
311 313
312## Nginx: Protected Domain(s) 314** Nginx: Protected Domain(s)
313 315
314Now that Authelia is accessible externally, you need to configure the domain you 316Now that Authelia is accessible externally, you need to configure the
315intend to protect with Authelia. In this example, I'm protecting 317domain you intend to protect with Authelia. In this example, I'm
316`teddit.example.com`. 318protecting =teddit.example.com=.
317 319
318Similar to the process above, paste the content and update the relevant 320Similar to the process above, paste the content and update the relevant
319variables. 321variables.
320 322
321```sh 323#+begin_src sh
322sudo nano /etc/nginx/sites-available/teddit 324sudo nano /etc/nginx/sites-available/teddit
323``` 325#+end_src
324 326
325```conf 327#+begin_src conf
326server { 328server {
327 if ($host ~ ^[^.]+\.example\.com$) { 329 if ($host ~ ^[^.]+\.example\.com$) {
328 return 301 https://$host$request_uri; 330 return 301 https://$host$request_uri;
@@ -424,26 +426,26 @@ server {
424 proxy_buffers 64 256k; 426 proxy_buffers 64 256k;
425 } 427 }
426} 428}
427``` 429#+end_src
428 430
429Same as before, symlink the file and restart Nginx. 431Same as before, symlink the file and restart Nginx.
430 432
431```sh 433#+begin_src sh
432sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit 434sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit
433sudo systemctl restart nginx.service 435sudo systemctl restart nginx.service
434``` 436#+end_src
435 437
436# Results 438* Results
437 439
438When visiting the protected domain, you will now be redirected to your 440When visiting the protected domain, you will now be redirected to your
439authentication domain and presented with the Authelia login portal. 441authentication domain and presented with the Authelia login portal.
440 442
441![Authelia 443#+caption: Authelia Portal
442Portal](https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png) 444[[https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png]]
443 445
444Once you've successfully authenticated, you can visit your authentication domain 446Once you've successfully authenticated, you can visit your
445directly and see that you're currently authenticated to any domain protected by 447authentication domain directly and see that you're currently
446Authelia. 448authenticated to any domain protected by Authelia.
447 449
448![Authelia 450#+caption: Authelia Success
449Success](https://img.cleberg.net/blog/20231010-authelia/authelia_success.png) 451[[https://img.cleberg.net/blog/20231010-authelia/authelia_success.png]]
content/blog/2023-10-15-alpine-ssh-hardening.md → content/blog/2023-10-15-alpine-ssh-hardening.org renamed +35 −37
@@ -1,69 +1,67 @@
1+++ 1#+date: <2023-10-15>
2date = 2023-10-15 2#+title: SSH Hardening for Alpine Linux
3title = "SSH Hardening for Alpine Linux" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Overview 5
6* Overview
9 7
10This guide follows the standard 8This guide follows the standard
11[ssh-audit](https://www.ssh-audit.com/hardening_guides.html) hardening guide, 9[[https://www.ssh-audit.com/hardening_guides.html][ssh-audit]] hardening
12tweaked for Alpine Linux. 10guide, tweaked for Alpine Linux.
13 11
14# Hardening Guide 12* Hardening Guide
15 13
16These steps must be performed as root. You can try to use `doas` or `sudo`, but 14These steps must be performed as root. You can try to use =doas= or
17there may be issues. 15=sudo=, but there may be issues.
18 16
191. Re-generate the RSA and ED25519 keys 171. Re-generate the RSA and ED25519 keys
20 18
21```sh 19#+begin_src sh
22rm /etc/ssh/ssh_host_* 20rm /etc/ssh/ssh_host_*
23ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" 21ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
24ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" 22ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
25``` 23#+end_src
26 24
272. Remove small Diffie-Hellman moduli 252. [@2] Remove small Diffie-Hellman moduli
28 26
29```sh 27#+begin_src sh
30awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe 28awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe
31mv /etc/ssh/moduli.safe /etc/ssh/moduli 29mv /etc/ssh/moduli.safe /etc/ssh/moduli
32``` 30#+end_src
33 31
343. Enable the RSA and ED25519 HostKey directives in the /etc/ssh/sshd~config~ 323. [@3] Enable the RSA and ED25519 HostKey directives in the
35 file 33 /etc/ssh/sshd_{config} file
36 34
37```sh 35#+begin_src sh
38sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config 36sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config
39``` 37#+end_src
40 38
414. Restrict supported key exchange, cipher, and MAC algorithms 394. [@4] Restrict supported key exchange, cipher, and MAC algorithms
42 40
43```sh 41#+begin_src sh
44echo -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 42echo -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
45``` 43#+end_src
46 44
475. Include the /etc/ssh/sshd~config~.d directory 455. [@5] Include the /etc/ssh/sshd_{config}.d directory
48 46
49```sh 47#+begin_src sh
50echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config 48echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config
51``` 49#+end_src
52 50
536. Restart OpenSSH server 516. [@6] Restart OpenSSH server
54 52
55```sh 53#+begin_src sh
56rc-service sshd restart 54rc-service sshd restart
57``` 55#+end_src
58 56
59# Testing SSH 57* Testing SSH
60 58
61You can test the results with the `ssh-audit` python script. 59You can test the results with the =ssh-audit= python script.
62 60
63```sh 61#+begin_src sh
64pip3 install ssh-audit 62pip3 install ssh-audit
65ssh-audit localhost 63ssh-audit localhost
66``` 64#+end_src
67 65
68If everything succeeded, the results will show as all green. If anything is 66If everything succeeded, the results will show as all green. If anything
69yellow, orange, or red, you may need to tweak additional settings. 67is yellow, orange, or red, you may need to tweak additional settings.
content/blog/2023-10-17-self-hosting-anonymousoverflow.md → content/blog/2023-10-17-self-hosting-anonymousoverflow.org renamed +39 −40
@@ -1,34 +1,33 @@
1+++ 1#+date: <2023-10-17>
2date = 2023-10-17 2#+title: Self-Hosting AnonymousOverflow
3title = "Self-Hosting AnonymousOverflow" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Overview 5
6* Overview
9 7
10I recently launched an instance of AnonymousOverflow at 8I recently launched an instance of AnonymousOverflow at
11[ao.cleberg.net](https://ao.cleberg.net) and wanted to write a brief post on how 9[[https://ao.cleberg.net][ao.cleberg.net]] and wanted to write a brief
12easy it is to install with Docker Compose and Nginx. 10post on how easy it is to install with Docker Compose and Nginx.
13 11
14This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse proxy. 12This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse
13proxy.
15 14
16# Installation 15* Installation
17 16
18## Docker Compose 17** Docker Compose
19 18
20To install AnonymousOverflow, start by creating a directory for the application 19To install AnonymousOverflow, start by creating a directory for the
21and create its `docker-compose.yml` file. 20application and create its =docker-compose.yml= file.
22 21
23```sh 22#+begin_src sh
24mkdir ~/anonymousoverflow && cd ~/anonymousoverflow 23mkdir ~/anonymousoverflow && cd ~/anonymousoverflow
25nano docker-compose.yml 24nano docker-compose.yml
26``` 25#+end_src
27 26
28Within this file, paste the following information. Be sure to change the 27Within this file, paste the following information. Be sure to change the
29`APP_URL`, `JWT_SIGNING_SECRET`, and `ports` to match your needs. 28=APP_URL=, =JWT_SIGNING_SECRET=, and =ports= to match your needs.
30 29
31```yaml 30#+begin_src yaml
32version: "3" 31version: "3"
33 32
34services: 33services:
@@ -41,31 +40,31 @@ services:
41 ports: 40 ports:
42 - "9380:8080" 41 - "9380:8080"
43 restart: "always" 42 restart: "always"
44``` 43#+end_src
45 44
46Save and exit the file when complete. You can now launch the container and 45Save and exit the file when complete. You can now launch the container
47access it via your local network. 46and access it via your local network.
48 47
49```sh 48#+begin_src sh
50sudo docker-compose up -d 49sudo docker-compose up -d
51``` 50#+end_src
52 51
53## Nginx Reverse Proxy 52** Nginx Reverse Proxy
54 53
55If you want to access this service outside the local network, I recommend using 54If you want to access this service outside the local network, I
56Nginx as a reverse proxy. 55recommend using Nginx as a reverse proxy.
57 56
58Let's start by creating a configuration file. 57Let's start by creating a configuration file.
59 58
60```sh 59#+begin_src sh
61sudo nano /etc/nginx/sites-available/ao 60sudo nano /etc/nginx/sites-available/ao
62``` 61#+end_src
63 62
64Within this file, paste the following content and repace `ao.example.com` with 63Within this file, paste the following content and repace
65your URL. You may need to update the SSL certificate statements if your 64=ao.example.com= with your URL. You may need to update the SSL
66certificates are in a different location. 65certificate statements if your certificates are in a different location.
67 66
68```conf 67#+begin_src conf
69server { 68server {
70 if ($host ~ ^[^.]+\.cleberg\.net$) { 69 if ($host ~ ^[^.]+\.cleberg\.net$) {
71 return 301 https://$host$request_uri; 70 return 301 https://$host$request_uri;
@@ -117,16 +116,16 @@ server {
117 proxy_buffers 64 256k; 116 proxy_buffers 64 256k;
118 } 117 }
119} 118}
120``` 119#+end_src
121 120
122Save and exit the file when complete. On Ubuntu, you will need to symlink the 121Save and exit the file when complete. On Ubuntu, you will need to
123configuration file before it will be recognized by Nginx. Once complete, simply 122symlink the configuration file before it will be recognized by Nginx.
124restart the web server. 123Once complete, simply restart the web server.
125 124
126```sh 125#+begin_src sh
127sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao 126sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao
128sudo systemctl restart nginx.service 127sudo systemctl restart nginx.service
129``` 128#+end_src
130 129
131The website will now be available publicly. Visit [my 130The website will now be available publicly. Visit
132instance](https://ao.cleberg.net) for an example. 131[[https://ao.cleberg.net][my instance]] for an example.
content/blog/2023-11-08-scli.md deleted −147
@@ -1,147 +0,0 @@
1+++
2date = 2023-11-08
3title = "Installing scli on Alpine Linux (musl)"
4description = ""
5draft = false
6+++
7
8[scli](https://github.com/isamert/scli) is a command-line tool that allows you
9to connect to your Signal messenger account. This program utilizes a two-pane
10display that shows you chats on the left and the focused conversation on the
11right.
12
13This guide will show you how to install `scli` and its dependencies on Alpine
14Linux, which requires some extra work due to musl.
15
16If you're using a non-musl system, you can likely following the `scli` README
17and download the packaged binaries for an easier installation process.
18
19# Dependencies
20
21In order to use `scli`, you need a few dependencies:
22
23- `openjdk17-jre` - Used as a dependency for the `signal-cli` tool. Version
24 may vary.
25- `signal-cli` - Used as the backbone of the `scli` tool.
26- `findutils` - Replaces the standard Busybox version of `xargs`.
27- `urwid` - A console user interface library for Python.
28- `urwid-readline` - For GNU emacs-like keybinds on the input line.
29- `qrencode` - Displays a QR code in the terminal to link the device using
30 your phone. Not necessary if you're only linking on desktop and can
31 copy/paste the connection URL.
32
33Let's start by installing the packages available via Alpine's repositories. Be
34sure to install the latest version of `openjdk`. If you run into Java-related
35issues, uninstall `openjdk` and install an older version.
36
37```sh
38doas apk add openjdk17-jre findutils qrencode
39```
40
41Next, let's install `signal-cli`. Be sure to export the version of `signal-cli`
42that you want. I use version `0.12.4` below, but that may be outdated by the
43time you're reading this.
44
45```sh
46export VERSION="0.12.4"
47wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
48doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
49doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
50```
51
52Finally, install the `urwid` packages using the Python packaging utility.
53
54```sh
55pip3 install urwid urwid-readline
56```
57
58# Installation
59
60Now that we have all of the dependencies we need, we can install `scli`. Start
61by simply cloning the repository.
62
63```sh
64git clone https://github.com/isamert/scli
65```
66
67When I cloned this repository on 2023-11-08, I found a bug in the logic that
68required a fix. You must edit the `scli` file and replace the one instance of
69`RLIMIT_OFILE` with `RLIMIT_NOFILE`.
70
71```sh
72cd scli
73nano scli
74```
75
76Once complete, you can move this program to anywhere on your `$PATH`. I chose
77the following directory.
78
79```sh
80doas mv scli /usr/local/bin/scli
81```
82
83# Initial Setup
84
85Now that everything is installed, we can login and configure the client. Start
86by generating a connection link.
87
88```sh
89signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
90```
91
92This will generate a connection link and related QR code for you to use to link
93the devices together. Once complete, **wait patiently** for the connection
94process to finish.
95
96Once it completes, it will exit and return you to the prompt. From here, you
97need to perform an initial `receive` command to start things off. The `USERNAME`
98variable should be your phone number, such as `+15551237890`.
99
100```sh
101signal-cli -u USERNAME receive
102```
103
104Also be sure to test the daemon to ensure it works properly. If no errors occur,
105it's working. If you run into errors because you're not running a DBUS session,
106see my notes below.
107
108```sh
109signal-cli -u USERNAME daemon
110```
111
112Once the initial reception is complete, you are ready to use `scli`.
113
114This process will differ depending on your desktop environment (DE). If you are
115running a DE, you likely have a DBUS session running already and can simply
116launch the program.
117
118However, if you're like me and running your computer straight on the TTY without
119a DE, you'll need to start a DBUS session for this program.
120
121```sh
122# If you're not running a DBUS session yet, you need to start one for scli
123dbus-run-session -- scli
124
125# OR - If you're already running a DBUS session, simply run scli
126scli
127```
128
129# Configuration
130
131Lastly, there are a number of configuration options that you can pass via the
132command or in the `~/.config/sclirc` file. See the Github README for more
133information on configuration options.
134
135```sh
136nano ~/.config/sclirc
137```
138
139```conf
140# ~/.config/sclirc
141
142wrap-at = 80
143enable-notifications = true
144```
145
146That's it! Following this guide, I have a functional `scli` program that
147successfully sends messages to my contacts and myself!
content/blog/2023-11-08-scli.org added +149
@@ -0,0 +1,149 @@
1#+date: <2023-11-08>
2#+title: Installing scli on Alpine Linux (musl)
3#+description:
4
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
19
20In order to use =scli=, you need a few dependencies:
21
22- =openjdk17-jre= - Used as a dependency for the =signal-cli= tool.
23 Version may vary.
24- =signal-cli= - Used as the backbone of the =scli= tool.
25- =findutils= - Replaces the standard Busybox version of =xargs=.
26- =urwid= - A console user interface library for Python.
27- =urwid-readline= - For GNU emacs-like keybinds on the input line.
28- =qrencode= - Displays a QR code in the terminal to link the device
29 using your phone. Not necessary if you're only linking on desktop and
30 can copy/paste the connection URL.
31
32Let's start by installing the packages available via Alpine's
33repositories. Be sure to install the latest version of =openjdk=. If you
34run into Java-related issues, uninstall =openjdk= and install an older
35version.
36
37#+begin_src sh
38doas apk add openjdk17-jre findutils qrencode
39#+end_src
40
41Next, let's install =signal-cli=. Be sure to export the version of
42=signal-cli= that you want. I use version =0.12.4= below, but that may
43be outdated by the time you're reading this.
44
45#+begin_src sh
46export VERSION="0.12.4"
47wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
48doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
49doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
50#+end_src
51
52Finally, install the =urwid= packages using the Python packaging
53utility.
54
55#+begin_src sh
56pip3 install urwid urwid-readline
57#+end_src
58
59* Installation
60
61Now that we have all of the dependencies we need, we can install =scli=.
62Start by simply cloning the repository.
63
64#+begin_src sh
65git clone https://github.com/isamert/scli
66#+end_src
67
68When I cloned this repository on 2023-11-08, I found a bug in the logic
69that required a fix. You must edit the =scli= file and replace the one
70instance of =RLIMIT_OFILE= with =RLIMIT_NOFILE=.
71
72#+begin_src sh
73cd scli
74nano scli
75#+end_src
76
77Once complete, you can move this program to anywhere on your =$PATH=. I
78chose the following directory.
79
80#+begin_src sh
81doas mv scli /usr/local/bin/scli
82#+end_src
83
84* Initial Setup
85
86Now that everything is installed, we can login and configure the client.
87Start by generating a connection link.
88
89#+begin_src sh
90signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
91#+end_src
92
93This will generate a connection link and related QR code for you to use
94to link the devices together. Once complete, *wait patiently* for the
95connection process to finish.
96
97Once it completes, it will exit and return you to the prompt. From here,
98you need to perform an initial =receive= command to start things off.
99The =USERNAME= variable should be your phone number, such as
100=+15551237890=.
101
102#+begin_src sh
103signal-cli -u USERNAME receive
104#+end_src
105
106Also be sure to test the daemon to ensure it works properly. If no
107errors occur, it's working. If you run into errors because you're not
108running a DBUS session, see my notes below.
109
110#+begin_src sh
111signal-cli -u USERNAME daemon
112#+end_src
113
114Once the initial reception is complete, you are ready to use =scli=.
115
116This process will differ depending on your desktop environment (DE). If
117you are running a DE, you likely have a DBUS session running already and
118can simply launch the program.
119
120However, if you're like me and running your computer straight on the TTY
121without a DE, you'll need to start a DBUS session for this program.
122
123#+begin_src sh
124# If you're not running a DBUS session yet, you need to start one for scli
125dbus-run-session -- scli
126
127# OR - If you're already running a DBUS session, simply run scli
128scli
129#+end_src
130
131* Configuration
132
133Lastly, there are a number of configuration options that you can pass
134via the command or in the =~/.config/sclirc= file. See the Github README
135for more information on configuration options.
136
137#+begin_src sh
138nano ~/.config/sclirc
139#+end_src
140
141#+begin_src conf
142# ~/.config/sclirc
143
144wrap-at = 80
145enable-notifications = true
146#+end_src
147
148That's it! Following this guide, I have a functional =scli= program that
149successfully sends messages to my contacts and myself!
content/blog/2023-12-03-unifi-nextdns.md → content/blog/2023-12-03-unifi-nextdns.org renamed +80 −79
@@ -1,102 +1,103 @@
1+++ 1#+date: <2023-12-03>
2date = 2023-12-03 2#+title: How to Install NextDNS on the Unifi Dream Machine
3title = "How to Install NextDNS on the Unifi Dream Machine" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# Overview 5
6* Overview
9 7
10I recently installed NextDNS on my Unifi Dream Machine router using the 8I recently installed NextDNS on my Unifi Dream Machine router using the
11[UnifiOS](https://github.com/nextdns/nextdns/wiki/UnifiOS) wiki page on 9[[https://github.com/nextdns/nextdns/wiki/UnifiOS][UnifiOS]] wiki page
12NextDNS's GitHub repository. 10on NextDNS's GitHub repository.
13 11
14As a result of this, I wanted to write down the process in case the wiki or 12As a result of this, I wanted to write down the process in case the wiki
15installer ever gets lost. 13or installer ever gets lost.
16 14
17# Wiki 15* Wiki
18 16
19The following is copied from the wiki page linked above, with one difference in 17The following is copied from the wiki page linked above, with one
20the `ssh` command. 18difference in the =ssh= command.
21 19
22Install instructions for Unifi Dream Machine (UDM) standard and pro routers. 20Install instructions for Unifi Dream Machine (UDM) standard and pro
21routers.
23 22
24## Install 23** Install
25 24
26Enable SSH: 25Enable SSH:
27 26
28- Go to your unifi admin interface and select your device (not the controller 27- Go to your unifi admin interface and select your device (not the
29 settings, but the Dream Machine settings) 28 controller settings, but the Dream Machine settings)
30- Click on "Settings" at the bottom of the page 29- Click on "Settings" at the bottom of the page
31- Go to the "Advanced" section on the left pan 30- Go to the "Advanced" section on the left pan
32- Enable SSH 31- Enable SSH
33- Set a SSH password 32- Set a SSH password
34 33
35Connect to your router using `ssh root@xxx.xxx.xxx.xxx` with the password you 34Connect to your router using =ssh root@xxx.xxx.xxx.xxx= with the
36configured. 35password you configured.
37 36
38Run the following command and follow the instructions: 37Run the following command and follow the instructions:
39 38
40```sh 39#+begin_src sh
41sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"' 40sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
42``` 41#+end_src
43 42
44Note: Queries from the UDM itself won't be routed to NextDNS nor encrypted due 43Note: Queries from the UDM itself won't be routed to NextDNS nor
45to current system limitation. All traffic from other devices on then network 44encrypted due to current system limitation. All traffic from other
46will. 45devices on then network will.
47 46
48## Upgrade 47** Upgrade
49 48
50To upgrade to the last version, simply re-run the installer above. If a new 49To upgrade to the last version, simply re-run the installer above. If a
51version is available, the upgrade action will added to the list of possible 50new version is available, the upgrade action will added to the list of
52actions. 51possible actions.
53 52
54## Uninstall 53** Uninstall
55 54
56To uninstall, re-run the installer above and select "Remove" in the menu. 55To uninstall, re-run the installer above and select "Remove" in the
56menu.
57 57
58## Troubleshooting 58** Troubleshooting
59 59
60If the installation fail, please the installer in debug mode and contact us at 60If the installation fail, please the installer in debug mode and contact
61team@nextdns.io with the transcript of the installation: 61us at team@nextdns.io with the transcript of the installation:
62 62
63```sh 63#+begin_src sh
64sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"' 64sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
65``` 65#+end_src
66 66
67### Content Filtering Conflict 67*** Content Filtering Conflict
68 68
69NextDNS CLI and the UDM Content Filtering or the Ad Blocking features are 69NextDNS CLI and the UDM Content Filtering or the Ad Blocking features
70incompatible. If you want to use NextDNS CLI, please make sure they are 70are incompatible. If you want to use NextDNS CLI, please make sure they
71disabled. 71are disabled.
72 72
73To disable Content Filtering, go to Settings > Network, then for each network, 73To disable Content Filtering, go to Settings > Network, then for each
74set the Content Filtering feature to None 74network, set the Content Filtering feature to None
75 75
76To disable Ad Blocking, go to Settings > Application Firewall. In the General 76To disable Ad Blocking, go to Settings > Application Firewall. In the
77tab, uncheck the Ad Blocking checkbox. 77General tab, uncheck the Ad Blocking checkbox.
78 78
79### APT Error 79*** APT Error
80 80
81If you get an apt error as follow: 81If you get an apt error as follow:
82 82
83```sh 83#+begin_src sh
84E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80] 84E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80]
85``` 85#+end_src
86 86
87You may try to following: 87You may try to following:
88 88
89```sh 89#+begin_src sh
90sed -i -e 's/deb.debian.org/archive.debian.org/g' \ 90sed -i -e 's/deb.debian.org/archive.debian.org/g' \
91 -e 's|security.debian.org|archive.debian.org/|g' \ 91 -e 's|security.debian.org|archive.debian.org/|g' \
92 -e '/stretch-updates/d' /etc/apt/sources.list 92 -e '/stretch-updates/d' /etc/apt/sources.list
93``` 93#+end_src
94 94
95## install.sh 95** install.sh
96 96
97Here are the contents of the `install.sh` file used above, as of 2023-12-03: 97Here are the contents of the =install.sh= file used above, as of
982023-12-03:
98 99
99```sh 100#+begin_src sh
100#!/bin/sh 101#!/bin/sh
101 102
102main() { 103main() {
@@ -321,13 +322,13 @@ install_bin() {
321 fi 322 fi
322 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path" 323 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path"
323 case "$INSTALL_RELEASE" in 324 case "$INSTALL_RELEASE" in
324 */*) 325 ,*/*)
325 # Snapshot 326 # Snapshot
326 branch=${INSTALL_RELEASE%/*} 327 branch=${INSTALL_RELEASE%/*}
327 hash=${INSTALL_RELEASE#*/} 328 hash=${INSTALL_RELEASE#*/}
328 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz" 329 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
329 ;; 330 ;;
330 *) 331 ,*)
331 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz" 332 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz"
332 ;; 333 ;;
333 esac 334 esac
@@ -629,12 +630,12 @@ install_type() {
629 echo "$FORCE_INSTALL_TYPE"; return 0 630 echo "$FORCE_INSTALL_TYPE"; return 0
630 fi 631 fi
631 case "$INSTALL_RELEASE" in 632 case "$INSTALL_RELEASE" in
632 */*) 633 ,*/*)
633 case $OS in 634 case $OS in
634 ubios) 635 ubios)
635 echo "ubios_snapshot"; return 0 636 echo "ubios_snapshot"; return 0
636 ;; 637 ;;
637 *) 638 ,*)
638 # Snapshot mode always use binary install 639 # Snapshot mode always use binary install
639 echo "bin"; return 0 640 echo "bin"; return 0
640 ;; 641 ;;
@@ -662,7 +663,7 @@ install_type() {
662 . /etc/os-release 663 . /etc/os-release
663 major=$(echo "$VERSION_ID" | cut -d. -f1) 664 major=$(echo "$VERSION_ID" | cut -d. -f1)
664 case $major in 665 case $major in
665 *[!0-9]*) 666 ,*[!0-9]*)
666 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then 667 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then
667 # No opkg support before 19.07.0-rc2 668 # No opkg support before 19.07.0-rc2
668 echo "bin" 669 echo "bin"
@@ -671,7 +672,7 @@ install_type() {
671 echo "openwrt" 672 echo "openwrt"
672 fi 673 fi
673 ;; 674 ;;
674 *) 675 ,*)
675 if [ "$major" -lt 19 ]; then 676 if [ "$major" -lt 19 ]; then
676 # No opkg support before 19.07.0-rc2 677 # No opkg support before 19.07.0-rc2
677 echo "bin" 678 echo "bin"
@@ -717,7 +718,7 @@ install_type() {
717 # TODO: pkg for xbps 718 # TODO: pkg for xbps
718 echo "bin" 719 echo "bin"
719 ;; 720 ;;
720 *) 721 ,*)
721 log_error "Unsupported installation for $(detect_os)" 722 log_error "Unsupported installation for $(detect_os)"
722 return 1 723 return 1
723 ;; 724 ;;
@@ -869,7 +870,7 @@ ask_bool() {
869 false) 870 false)
870 msg="$msg [y|N]: " 871 msg="$msg [y|N]: "
871 ;; 872 ;;
872 *) 873 ,*)
873 msg="$msg (y/n): " 874 msg="$msg (y/n): "
874 esac 875 esac
875 while true; do 876 while true; do
@@ -887,7 +888,7 @@ ask_bool() {
887 echo "false" 888 echo "false"
888 return 0 889 return 0
889 ;; 890 ;;
890 *) 891 ,*)
891 echo "Invalid input, use yes or no" 892 echo "Invalid input, use yes or no"
892 ;; 893 ;;
893 esac 894 esac
@@ -899,12 +900,12 @@ detect_endiannes() {
899 # Some firmwares do not contain hexdump, for those, try to detect endianness 900 # Some firmwares do not contain hexdump, for those, try to detect endianness
900 # differently. 901 # differently.
901 case $(cat /proc/cpuinfo) in 902 case $(cat /proc/cpuinfo) in
902 *BCM5300*) 903 ,*BCM5300*)
903 # RT-AC66U does not support Merlin version over 380.70 which 904 # RT-AC66U does not support Merlin version over 380.70 which
904 # lacks hexdump command. 905 # lacks hexdump command.
905 echo "le" 906 echo "le"
906 ;; 907 ;;
907 *) 908 ,*)
908 log_error "Cannot determine endianness" 909 log_error "Cannot determine endianness"
909 return 1 910 return 1
910 ;; 911 ;;
@@ -935,10 +936,10 @@ detect_goarch() {
935 arm) 936 arm)
936 # FreeBSD does not include arm version 937 # FreeBSD does not include arm version
937 case "$(sysctl -b hw.model 2>/dev/null)" in 938 case "$(sysctl -b hw.model 2>/dev/null)" in
938 *A9*) 939 ,*A9*)
939 echo "armv7" 940 echo "armv7"
940 ;; 941 ;;
941 *) 942 ,*)
942 # Unknown version, fallback to the lowest 943 # Unknown version, fallback to the lowest
943 echo "armv5" 944 echo "armv5"
944 ;; 945 ;;
@@ -962,7 +963,7 @@ detect_goarch() {
962 # "out of memory allocating heap arena metadata". 963 # "out of memory allocating heap arena metadata".
963 echo "armv7" 964 echo "armv7"
964 ;; 965 ;;
965 *) 966 ,*)
966 echo "arm64" 967 echo "arm64"
967 ;; 968 ;;
968 esac 969 esac
@@ -974,7 +975,7 @@ detect_goarch() {
974 # TODO: detect hardfloat 975 # TODO: detect hardfloat
975 echo "$(uname -m)$(detect_endiannes)_softfloat" 976 echo "$(uname -m)$(detect_endiannes)_softfloat"
976 ;; 977 ;;
977 *) 978 ,*)
978 log_error "Unsupported GOARCH: $(uname -m)" 979 log_error "Unsupported GOARCH: $(uname -m)"
979 return 1 980 return 1
980 ;; 981 ;;
@@ -1001,7 +1002,7 @@ detect_goos() {
1001 OpenBSD) 1002 OpenBSD)
1002 echo "openbsd" 1003 echo "openbsd"
1003 ;; 1004 ;;
1004 *) 1005 ,*)
1005 log_error "Unsupported GOOS: $(uname -s)" 1006 log_error "Unsupported GOOS: $(uname -s)"
1006 return 1 1007 return 1
1007 esac 1008 esac
@@ -1078,7 +1079,7 @@ detect_os() {
1078 OpenBSD) 1079 OpenBSD)
1079 echo "openbsd"; return 0 1080 echo "openbsd"; return 0
1080 ;; 1081 ;;
1081 *) 1082 ,*)
1082 esac 1083 esac
1083 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)" 1084 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)"
1084 return 1 1085 return 1
@@ -1097,7 +1098,7 @@ guess_host_type() {
1097 darwin|steamos) 1098 darwin|steamos)
1098 echo "workstation" 1099 echo "workstation"
1099 ;; 1100 ;;
1100 *) 1101 ,*)
1101 echo "unsure" 1102 echo "unsure"
1102 ;; 1103 ;;
1103 esac 1104 esac
@@ -1156,7 +1157,7 @@ bin_location() {
1156 steamos) 1157 steamos)
1157 echo "$HOME/.local/bin/nextdns" 1158 echo "$HOME/.local/bin/nextdns"
1158 ;; 1159 ;;
1159 *) 1160 ,*)
1160 log_error "Unknown bin location for $OS" 1161 log_error "Unknown bin location for $OS"
1161 ;; 1162 ;;
1162 esac 1163 esac
@@ -1164,12 +1165,12 @@ bin_location() {
1164 1165
1165is_version_current() { 1166is_version_current() {
1166 case "$INSTALL_RELEASE" in 1167 case "$INSTALL_RELEASE" in
1167 */*) 1168 ,*/*)
1168 # Snapshot 1169 # Snapshot
1169 hash=${INSTALL_RELEASE#*/} 1170 hash=${INSTALL_RELEASE#*/}
1170 test "0.0.0-$hash" = "$CURRENT_RELEASE" 1171 test "0.0.0-$hash" = "$CURRENT_RELEASE"
1171 ;; 1172 ;;
1172 *) 1173 ,*)
1173 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE" 1174 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE"
1174 ;; 1175 ;;
1175 esac 1176 esac
@@ -1194,7 +1195,7 @@ get_release() {
1194 curl) cmd="curl -A curl -s" ;; 1195 curl) cmd="curl -A curl -s" ;;
1195 wget) cmd="wget -qO- -U curl" ;; 1196 wget) cmd="wget -qO- -U curl" ;;
1196 openssl) cmd="openssl_get" ;; 1197 openssl) cmd="openssl_get" ;;
1197 *) 1198 ,*)
1198 log_error "Cannot retrieve latest version" 1199 log_error "Cannot retrieve latest version"
1199 return 1200 return
1200 ;; 1201 ;;
@@ -1246,4 +1247,4 @@ openssl_get() {
1246 1247
1247umask 0022 1248umask 0022
1248main 1249main
1249``` 1250#+end_src
content/blog/2024-01-08-dont-say-hello.md deleted −29
@@ -1,29 +0,0 @@
1+++
2date = 2024-01-08
3title = "Don't Say Hello"
4description = ""
5draft = false
6+++
7
8I recently came back from a winter break and have started working again... only
9to immediately run into the dilemma of people sending me cliffhanger messages
10again.
11
12# No Hello
13
14A year or two ago, I discovered [no hello](https://nohello.net/en/) and have
15thought about it often since then. I've even sent it to a few people since then
16(who wouldn't take offense to it).
17
18I work in a fast-paced environment where efficiency is extremely valuable.
19Therefore, I have always held a deep displeasure for conversations where people
20start with "Hello" and then.
21
22I searched back through my work messages and found that I received ~50 messages
23from ~10 people last year from people that contained "hi", "hey", or "hello" and
24did not contain any indication of the purpose of the conversation. I also
25noticed that a few of the users were responsible for the large majority of the
26cliffhangers.
27
28There's no real point to this post, just a desparate request for people to
29please stop doing this.
content/blog/2024-01-08-dont-say-hello.org added +27
@@ -0,0 +1,27 @@
1#+date: <2024-01-08>
2#+title: Don't Say Hello
3#+description:
4
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
11
12A year or two ago, I discovered [[https://nohello.net/en/][no hello]]
13and have thought about it often since then. I've even sent it to a few
14people since then (who wouldn't take offense to it).
15
16I work in a fast-paced environment where efficiency is extremely
17valuable. Therefore, I have always held a deep displeasure for
18conversations where people start with "Hello" and then.
19
20I searched back through my work messages and found that I received ~50
21messages from ~10 people last year from people that contained "hi",
22"hey", or "hello" and did not contain any indication of the purpose of
23the conversation. I also noticed that a few of the users were
24responsible for the large majority of the cliffhangers.
25
26There's no real point to this post, just a desparate request for people
27to please stop doing this.
content/blog/2024-01-09-macos-customization.md deleted −147
@@ -1,147 +0,0 @@
1+++
2date = 2024-01-09
3title = "Customizing macOS"
4description = ""
5draft = false
6+++
7
8I have been using macOS more than Linux lately, so I wrote this post to describe
9some simple options to customize macOS beyond the normal built-in settings menu.
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
45To install color schemes, such as the Dracula scheme, you visit [iTerm
46Themes](https://iterm2colorschemes.com/) and follow their installation
47instructions to install any of the themes.
48
49# Desktop
50
51## Window Management
52
53[yabai](https://github.com/koekeishiya/yabai) is a tiling window manager for
54macOS. While other window managers exist, I found that most of them struggled to
55create logical layouts and to allow me to easily move windows around the screen.
56
57Some advanced settings for yabai are only available if partially disable System
58Integrity Protection (SIP). However, I chose not to do this and it hasn't
59affected my basic usage of yabai at all.
60
61Refer to the [yabai
62wiki](<https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)>)
63for installation instructions. You will need to ensure that yabai is allowed to
64access the accessibility and screen recording APIs.
65
66## Keyboard Shortcuts
67
68[skhd](https://github.com/koekeishiya/skhd) is a simple hotkey daemon that
69allows you to define hotkeys in a file for usage on your system.
70
71Installation is simple:
72
73```sh
74brew install koekeishiya/formulae/skhd
75skhd --start-service
76```
77
78After installation, be sure to allow `skhd` access to the accessibility API in
79the macOS privacy settings.
80
81You can configure your hotkeys in the `~/.config/skhd/skhdrc` file:
82
83```sh
84nano ~/.config/skhd/skhdrc
85```
86
87For example, I have hotkeys to open my browser and terminal:
88
89```conf
90# Terminal
91cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
92
93# Browser
94cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
95```
96
97## Widgets
98
99[uebersicht](https://github.com/felixhageloh/uebersicht/) is a handy
100desktop-based widget tool with a plethora of community-made widgets available in
101the [widgets gallery](https://tracesof.net/uebersicht-widgets/). You can also
102write your own widgets with this tool.
103
104To install, simply download the latest release from the [uebersicht
105website](https://tracesof.net/uebersicht/) and copy it to the Applications
106folder.
107
108See below for an example of the
109[Mond](https://tracesof.net/uebersicht-widgets/#Mond) widget in action.
110
111## Status Bar
112
113[SketchyBar](https://github.com/FelixKratz/SketchyBar) is a customizable
114replacement for the macOS status or menu bar.
115
116You can browse a discussion where various users shared their
117[configurations](https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top)
118for inspiration or to copy their dotfiles.
119
120## Dock
121
122The easiest way to customize the dock is to install
123[uBar](https://ubarapp.com/), which uses a Windows-like menu bar as the default
124style.
125
126However, the built-in macOS dock cannot be disabled and can only be set to
127"always hidden". This can be annoying as it will pop out any time your mouse
128cursor passes closely to the dock edge of the screen. Because of this, I simply
129use the built-in dock instead of customizing it with third-party software.
130
131Regardless, see below for the default installation style of uBar.
132
133## Application Icons
134
135You can also customize the icon of any application in macOS, which will show up
136in Finder, the Dock, Launchpad, search results, etc. I recommend using
137[macOSicons](https://macosicons.com/) to download icons you want, and then apply
138them by following this process.
139
1401. Open the Finder application.
1412. Navigate to the `Applications` folder.
1423. Right-click an application of your choice, and select `Get Info`.
1434. Drag the image you downloaded on top of the application's icon at the top of
144 information window (you will see a green "plus" symbol when you're hovering
145 over it).
1465. Release the new icon on top of the old icon and it will update!
147
content/blog/2024-01-09-macos-customization.org added +151
@@ -0,0 +1,151 @@
1#+date: <2024-01-09>
2#+title: Customizing macOS
3#+description:
4
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
15** Package Management
16
17To install a lot of software on macOS, you will need
18[[https://brew.sh/][Homebrew]]. You can use their installation script to
19get started. Simply open the =Terminal= application and paste the
20following snippet:
21
22#+begin_src sh
23/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
24#+end_src
25
26This will allow you to easily install and manage applications and other
27software easily through the =brew= command.
28
29** Terminal
30
31If you're serious about customizing your macOS system, I highly
32recommend installing a terminal emulator that you like and if you're not
33comfortable on the command line yet, start learning. A lot of
34customization options require you to edit hidden files, which is easiest
35in a terminal.
36
37There are options like iTerm2, Kitty, Alacritty, Hyper, Warp, or the
38built-in Terminal app.
39
40I use [[https://iterm2.com/][iTerm2]], which can be installed with
41Homebrew:
42
43#+begin_src sh
44brew install iterm2
45#+end_src
46
47To install color schemes, such as the Dracula scheme, you visit
48[[https://iterm2colorschemes.com/][iTerm Themes]] and follow their
49installation instructions to install any of the themes.
50
51* Desktop
52
53** Window Management
54
55[[https://github.com/koekeishiya/yabai][yabai]] is a tiling window
56manager for macOS. While other window managers exist, I found that most
57of them struggled to create logical layouts and to allow me to easily
58move windows around the screen.
59
60Some advanced settings for yabai are only available if partially disable
61System Integrity Protection (SIP). However, I chose not to do this and
62it hasn't affected my basic usage of yabai at all.
63
64Refer to the
65[[https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)][yabai
66wiki]] for installation instructions. You will need to ensure that yabai
67is allowed to access the accessibility and screen recording APIs.
68
69** Keyboard Shortcuts
70
71[[https://github.com/koekeishiya/skhd][skhd]] is a simple hotkey daemon
72that allows you to define hotkeys in a file for usage on your system.
73
74Installation is simple:
75
76#+begin_src sh
77brew install koekeishiya/formulae/skhd
78skhd --start-service
79#+end_src
80
81After installation, be sure to allow =skhd= access to the accessibility
82API in the macOS privacy settings.
83
84You can configure your hotkeys in the =~/.config/skhd/skhdrc= file:
85
86#+begin_src sh
87nano ~/.config/skhd/skhdrc
88#+end_src
89
90For example, I have hotkeys to open my browser and terminal:
91
92#+begin_src conf
93# Terminal
94cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
95
96# Browser
97cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
98#+end_src
99
100** Widgets
101
102[[https://github.com/felixhageloh/uebersicht/][uebersicht]] is a handy
103desktop-based widget tool with a plethora of community-made widgets
104available in the [[https://tracesof.net/uebersicht-widgets/][widgets
105gallery]]. You can also write your own widgets with this tool.
106
107To install, simply download the latest release from the
108[[https://tracesof.net/uebersicht/][uebersicht website]] and copy it to
109the Applications folder.
110
111See below for an example of the
112[[https://tracesof.net/uebersicht-widgets/#Mond][Mond]] widget in
113action.
114
115** Status Bar
116
117[[https://github.com/FelixKratz/SketchyBar][SketchyBar]] is a
118customizable replacement for the macOS status or menu bar.
119
120You can browse a discussion where various users shared their
121[[https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top][configurations]]
122for inspiration or to copy their dotfiles.
123
124** Dock
125
126The easiest way to customize the dock is to install
127[[https://ubarapp.com/][uBar]], which uses a Windows-like menu bar as
128the default style.
129
130However, the built-in macOS dock cannot be disabled and can only be set
131to "always hidden". This can be annoying as it will pop out any time
132your mouse cursor passes closely to the dock edge of the screen. Because
133of this, I simply use the built-in dock instead of customizing it with
134third-party software.
135
136Regardless, see below for the default installation style of uBar.
137
138** Application Icons
139
140You can also customize the icon of any application in macOS, which will
141show up in Finder, the Dock, Launchpad, search results, etc. I recommend
142using [[https://macosicons.com/][macOSicons]] to download icons you
143want, and then apply them by following this process.
144
1451. Open the Finder application.
1462. Navigate to the =Applications= folder.
1473. Right-click an application of your choice, and select =Get Info=.
1484. Drag the image you downloaded on top of the application's icon at the
149 top of information window (you will see a green "plus" symbol when
150 you're hovering over it).
1515. Release the new icon on top of the old icon and it will update!
content/blog/2024-01-13-local-llm.md deleted −92
@@ -1,92 +0,0 @@
1+++
2date = 2024-01-13
3title = "Running Local LLMs on macOS and iOS"
4description = ""
5draft = false
6+++
7
8# Requirements
9
10I've recently started playing with large language models (LLMs), mostly in the
11popular chatbot form, as part of my job and have decided to see if there's a
12consistent and reliable way to interact with these models on Apple devices
13without sacrificing privacy or requiring in-depth technical setup.
14
15My requirements for this test:
16
17- Open source platform
18- On-device model files
19- Minimal required configuration
20- Preferably pre-built, but a simple build process is acceptable
21
22I tested a handful of apps and have summarized my favorite (so far) for macOS
23and iOS below.
24
25> TL;DR - Here are the two that met my requirements and I have found the easiest
26> to install and use so far:
27
28- macOS: [Ollama](https://ollama.ai/)
29- iOS : [LLM Farm](https://llmfarm.site/)
30
31# macOS
32
33[Ollama](https://ollama.ai/) is a simple Go application for macOS and Linux that
34can run various LLMs locally.
35
36For macOS, you can download the pplication on the [Ollama download
37page](https://ollama.ai/download/mac) and install it by unzipping the
38`Ollama.app` file and moving it to the `Applications` folder.
39
40If you prefer the command line, you can run these commands after the download
41finished:
42
43```sh
44cd ~/Downloads && \
45unzip Ollama-darwin.zip && \
46mv ~/Downloads/Ollama.app /Applications/
47```
48
49After running the app, the app will ask you to open a terminal and run the
50default `llama2` model, which will open an interactive chat session in the
51terminal. You can startfully using the application at this point.
52
53If you don't want to use the default `llama2` model, you can download and run
54additional models found on the [Models](https://ollama.ai/library) page.
55
56To see the information for the currently-used model, you can run the `/show
57info` command in the chat.
58
59## Community Integrations
60
61I highly recommend browsing the [Community
62Integrations](https://github.com/jmorganca/ollama#community-integrations)
63section of the project to see how you would prefer to extend Ollama beyond a
64simple command-line interface. There are options for APIs, browser UIs, advanced
65terminal configurations, and more.
66
67# iOS
68
69While there are a handful of decent macOS options, it was quite difficult to
70find an iOS app that offered an open source platform without an extensive
71configuration and building process. I found LLM Farm to be decent enough in
72quality to sit at the top of my list - however, it's definitely not user
73friendly enough for me to consider using it on a daily basis.
74
75[LLM Farm](https://llmfarm.site/) is available on TestFlight, so there's no
76manual build process required. However, you can view the [LLMFarm
77repository](https://github.com/guinmoon/LLMFarm) if you wish.
78
79The caveat is that you will have to manually download the model files from the
80links in the
81[models.md](https://github.com/guinmoon/LLMFarm/blob/main/models.md) file to
82your iPhone to use the app - there's currently no option in the app to reach out
83and grab the latest version of any supported model.
84
85Once you have a file downloaded, you simply create a new chat and select the
86downloaded model file and ensure the inference matches the requirement in the
87`models.md` file.
88
89[Enchanted](https://github.com/AugustDev/enchanted) is also an iOS for private
90AI models, but it requires a public-facing Ollama API, which did not meet my "on
91device requirement." Nonetheless, it's an interesting looking app and I will
92likely set it up to test soon.
content/blog/2024-01-13-local-llm.org added +96
@@ -0,0 +1,96 @@
1#+date: <2024-01-13>
2#+title: Running Local LLMs on macOS and iOS
3#+description:
4
5
6* Requirements
7
8I've recently started playing with large language models (LLMs), mostly
9in the popular chatbot form, as part of my job and have decided to see
10if there's a consistent and reliable way to interact with these models
11on Apple devices without sacrificing privacy or requiring in-depth
12technical 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
22macOS and iOS below.
23
24#+begin_quote
25TL;DR - Here are the two that met my requirements and I have found the
26easiest to install and use so far:
27#+end_quote
28
29- macOS: [[https://ollama.ai/][Ollama]]
30- iOS : [[https://llmfarm.site/][LLM Farm]]
31
32* macOS
33
34[[https://ollama.ai/][Ollama]] is a simple Go application for macOS and
35Linux that can run various LLMs locally.
36
37For macOS, you can download the pplication on the
38[[https://ollama.ai/download/mac][Ollama download page]] and install it
39by unzipping the =Ollama.app= file and moving it to the =Applications=
40folder.
41
42If you prefer the command line, you can run these commands after the
43download finished:
44
45#+begin_src sh
46cd ~/Downloads && \
47unzip Ollama-darwin.zip && \
48mv ~/Downloads/Ollama.app /Applications/
49#+end_src
50
51After running the app, the app will ask you to open a terminal and run
52the default =llama2= model, which will open an interactive chat session
53in the terminal. You can startfully using the application at this point.
54
55If you don't want to use the default =llama2= model, you can download
56and run additional models found on the
57[[https://ollama.ai/library][Models]] page.
58
59To see the information for the currently-used model, you can run the
60=/show info= command in the chat.
61
62** Community Integrations
63
64I highly recommend browsing the
65[[https://github.com/jmorganca/ollama#community-integrations][Community
66Integrations]] section of the project to see how you would prefer to
67extend Ollama beyond a simple command-line interface. There are options
68for APIs, browser UIs, advanced terminal configurations, and more.
69
70* iOS
71
72While there are a handful of decent macOS options, it was quite
73difficult to find an iOS app that offered an open source platform
74without an extensive configuration and building process. I found LLM
75Farm to be decent enough in quality to sit at the top of my list -
76however, it's definitely not user friendly enough for me to consider
77using it on a daily basis.
78
79[[https://llmfarm.site/][LLM Farm]] is available on TestFlight, so
80there's no manual build process required. However, you can view the
81[[https://github.com/guinmoon/LLMFarm][LLMFarm repository]] if you wish.
82
83The caveat is that you will have to manually download the model files
84from the links in the
85[[https://github.com/guinmoon/LLMFarm/blob/main/models.md][models.md]]
86file to your iPhone to use the app - there's currently no option in the
87app to reach out and grab the latest version of any supported model.
88
89Once you have a file downloaded, you simply create a new chat and select
90the downloaded model file and ensure the inference matches the
91requirement in the =models.md= file.
92
93[[https://github.com/AugustDev/enchanted][Enchanted]] is also an iOS for
94private AI models, but it requires a public-facing Ollama API, which did
95not meet my "on device requirement." Nonetheless, it's an interesting
96looking app and I will likely set it up to test soon.
content/blog/2024-01-26-audit-dashboard.md deleted −150
@@ -1,150 +0,0 @@
1+++
2date = 2024-01-26
3title = "Building an Audit Status Dashboard"
4description = ""
5draft = false
6+++
7
8Alteryx and Power BI are powerful tools that can help turn your old-school audit
9trackers into interactive tools that provide useful insights and potential
10action plans.
11
12With these tools, we are going to build a dashboard that can effectively
13communicate project status.
14
15# Requirements
16
17This project assumes the following:
18
19- You have access to Alteryx Designer and Power BI Desktop.
20 - If you only have Power BI Desktop, you may need to perform some analysis
21 in Power BI instead of Alteryx.
22- Your data is in a format that can be imported into Alteryx and/or Power BI.
23- You have a basic understanding of data types and visualization.
24
25# Alteryx: Data Preparation & Analysis
26
27## Import Data
28
29With Alteryx, importing data is easy with the use of the `Input Data` tool.
30Simply drag this tool onto the canvas from the `In/Out` tab in the Ribbon to
31create it as a node.
32
33You can choose the File Format manually or simply connect to your file/database
34and let Alteryx determine the format for you. For this example, we will be
35importing an Excel file and changing the `Start Data Import on Line` variable to
36`2`.
37
38## Transform Data
39
40Next, let's replace null data and remove whitespace to clean up our data. We can
41do this with the `Data Cleansing` tool in the `Preparation` tab in the Ribbon.
42
43Ensure that the following options are enabled:
44
45- Replace Nulls
46 - Replace with Blanks (String Fields)
47 - Replace with 0 (Numeric Fields)
48- Remove Unwanted Characters
49 - Leading and Trailing Whitespace
50
51For our next step, we will transform the date fields from strings to datetime
52format. Add a `Datetime` tool for each field you want to transform - in the
53example below, I am using the tool twice for the "Started On" and "Submitted On"
54fields.
55
56Now that the dates are in the correct format, let's perform a calculation based
57on those fields. Start by adding a `Filter` tool, naming a new Output Column,
58and pasting the formula below into it (the two fields used in this formula must
59match the output of the `Datetime` tools above):
60
61```txt
62DateTimeDiff([SubmittedOn_Out],[StartedOn_Out], "days")
63`
64
65## Export Data
66
67Finalize the process by exporting the transformed data set to a new file, for
68use in the following visualization step.
69
70# Power BI: Data Visualization
71
72## Import Data
73
74To start, open the Power BI Desktop application. Upon first use, Power BI will
75ask if you want to open an existing dashboard or import new data.
76
77As we are creating our first dashboard, let's import our data. In my example
78below, I'm importing data from the "Tracker" sheet of the Excel file I'm using
79for this project.
80
81During this process, I also imported the export from the Alteryx workflow above.
82Therefore, we have two different files available for use in our dashboard.
83
84## Add Visuals
85
86To create the dashboard, you will need to follow the list instructions
87below and format as needed.
88
89Instructions to create the visuals above:
90
91- `Text Box`: Explain the name and purpose of the dashboard. You can also add
92 images and logos at the top of the dashboard.
93- `Donut Chart`: Overall status of the project.
94 - `Legend`: Status
95 - `Values`: Count of Status
96- `Stacked Column Chart`: Task count by assignee.
97 - `X-axis`: Preparer
98 - `Y-axis`: Count of Control ID
99 - `Legend`: Status
100- `Treemap`: Top N client submitters by average days to submit.
101 - `Details`: Preparer
102 - `Values`: Sum of Avg~DaysToSubmit~
103- `Line Chart`: Projected vs. actual hours over time.
104- `Clustered Bar Chart`: Projected vs. actual hours per person.
105- `Slicer & Table` - Upcoming due dates.
106 - `Slicer`:
107 - `Values`: Date Due
108 - `Table`:
109 - `Columns`: Count of Control ID, Date Due, Preparer, Status
110
111## Format the Dashboard
112
113You can choose a theme in the View tab of the Ribbon. You can even browse for
114custom JSON files that define themes, such as ones found online or custom ones
115created by your organization.
116
117For each visual, you can click the `Format` button in the `Visualizations` side
118pane and explore the options. You can custom options such as:
119
120- Visual
121 - Legend
122 - Colors
123 - Data labels
124 - Category labels
125- General
126 - Properties
127 - Title
128 - Effects
129 - Header icons
130 - Tooltips
131 - Alt text
132
133You can always look online for inspiration when trying to decide how best to
134organize and style your dashboard.
135
136# Sharing the Results
137
138Generally, you have a few different options for sharing your dashboards with
139others:
140
1411. Export the dashboard as a PDF in the file menu of Power BI. This will export
142 all tabs and visuals as they are set when the export button is pressed. You
143 will lose all interactivity with this option.
1442. Send the full Power BI file to those you wish to share the dashboard. This
145 will retain all settings and interactivity. However, you will also need to
146 send the source files if they need to refresh the dashboard and you will need
147 to re-send the files if you make updates.
1483. Store the dashboard in a synced location, such as a shared drive or Microsoft
149 Teams. Depending on how a user configures their local Windows paths, the data
150 source paths may not be compatible for all users with such a setup.
content/blog/2024-01-26-audit-dashboard.org added +153
@@ -0,0 +1,153 @@
1#+date: <2024-01-26>
2#+title: Building an Audit Status Dashboard
3#+description:
4
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 a dashboard that can effectively
11communicate project status.
12
13* Requirements
14
15This project assumes the following:
16
17- You have access to Alteryx Designer and Power BI Desktop.
18 - If you only have Power BI Desktop, you may need to perform some
19 analysis in Power BI instead of Alteryx.
20- Your data is in a format that can be imported into Alteryx and/or
21 Power BI.
22- You have a basic understanding of data types and visualization.
23
24* Alteryx: Data Preparation & Analysis
25
26** Import Data
27
28With Alteryx, importing data is easy with the use of the =Input Data=
29tool. Simply drag this tool onto the canvas from the =In/Out= tab in the
30Ribbon to create it as a node.
31
32You can choose the File Format manually or simply connect to your
33file/database and let Alteryx determine the format for you. For this
34example, we will be importing an Excel file and changing the
35=Start Data Import on Line= variable to =2=.
36
37** Transform Data
38
39Next, let's replace null data and remove whitespace to clean up our
40data. We can do this with the =Data Cleansing= tool in the =Preparation=
41tab in the Ribbon.
42
43Ensure that the following options are enabled:
44
45- Replace Nulls
46 - Replace with Blanks (String Fields)
47 - Replace with 0 (Numeric Fields)
48- Remove Unwanted Characters
49 - Leading and Trailing Whitespace
50
51For our next step, we will transform the date fields from strings to
52datetime format. Add a =Datetime= tool for each field you want to
53transform - in the example below, I am using the tool twice for the
54"Started On" and "Submitted On" fields.
55
56Now that the dates are in the correct format, let's perform a
57calculation based on those fields. Start by adding a =Filter= tool,
58naming a new Output Column, and pasting the formula below into it (the
59two fields used in this formula must match the output of the =Datetime=
60tools above):
61
62``=txt DateTimeDiff([SubmittedOn_Out],[StartedOn_Out], "days")=
63
64** Export Data
65
66Finalize the process by exporting the transformed data set to a new
67file, for use in the following visualization step.
68
69* Power BI: Data Visualization
70
71** Import Data
72
73To start, open the Power BI Desktop application. Upon first use, Power
74BI will ask if you want to open an existing dashboard or import new
75data.
76
77As we are creating our first dashboard, let's import our data. In my
78example below, I'm importing data from the "Tracker" sheet of the Excel
79file I'm using for this project.
80
81During this process, I also imported the export from the Alteryx
82workflow above. Therefore, we have two different files available for use
83in our dashboard.
84
85** Add Visuals
86
87To create the dashboard, you will need to follow the list instructions
88below and format as needed.
89
90Instructions to create the visuals above:
91
92- =Text Box=: Explain the name and purpose of the dashboard. You can
93 also add images and logos at the top of the dashboard.
94- =Donut Chart=: Overall status of the project.
95 - =Legend=: Status
96 - =Values=: Count of Status
97- =Stacked Column Chart=: Task count by assignee.
98 - =X-axis=: Preparer
99 - =Y-axis=: Count of Control ID
100 - =Legend=: Status
101- =Treemap=: Top N client submitters by average days to submit.
102 - =Details=: Preparer
103 - =Values=: Sum of Avg_{DaysToSubmit}
104- =Line Chart=: Projected vs. actual hours over time.
105- =Clustered Bar Chart=: Projected vs. actual hours per person.
106- =Slicer & Table= - Upcoming due dates.
107 - =Slicer=:
108 - =Values=: Date Due
109 - =Table=:
110 - =Columns=: Count of Control ID, Date Due, Preparer, Status
111
112** Format the Dashboard
113
114You can choose a theme in the View tab of the Ribbon. You can even
115browse for custom JSON files that define themes, such as ones found
116online or custom ones created by your organization.
117
118For each visual, you can click the =Format= button in the
119=Visualizations= side pane and explore the options. You can custom
120options such as:
121
122- Visual
123 - Legend
124 - Colors
125 - Data labels
126 - Category labels
127- General
128 - Properties
129 - Title
130 - Effects
131 - Header icons
132 - Tooltips
133 - Alt text
134
135You can always look online for inspiration when trying to decide how
136best to organize and style your dashboard.
137
138* Sharing the Results
139
140Generally, you have a few different options for sharing your dashboards
141with others:
142
1431. Export the dashboard as a PDF in the file menu of Power BI. This will
144 export all tabs and visuals as they are set when the export button is
145 pressed. You will lose all interactivity with this option.
1462. Send the full Power BI file to those you wish to share the dashboard.
147 This will retain all settings and interactivity. However, you will
148 also need to send the source files if they need to refresh the
149 dashboard and you will need to re-send the files if you make updates.
1503. Store the dashboard in a synced location, such as a shared drive or
151 Microsoft Teams. Depending on how a user configures their local
152 Windows paths, the data source paths may not be compatible for all
153 users with such a setup.
content/blog/2024-01-27-tableau-dashboard.md deleted −148
@@ -1,148 +0,0 @@
1+++
2date = 2024-01-27
3title = "Data Visualization: Mapping Omaha Crime Data with Tableau"
4description = ""
5draft = false
6+++
7
8In this project, I am going to show you how to use Tableau Public for free to
9create simple dashboards.
10
11I will be creating simple visuals from an Omaha crime data set and combining
12them to create the dashboard below. You can view this dashboard interactively
13online here: [Omaha Crime Data (2015 -
142023)](https://public.tableau.com/app/profile/c.c7042/viz/OmahaCrimeData2015-2023/OmahaCrimeData2015-2023#1).
15
16![Tableau
17Dashboard](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png)
18
19# Gather the Data
20
21You can download incident data from the Omaha Police Department on their
22[Incident Data
23Download](https://police.cityofomaha.org/crime-information/incident-data-download)
24page. They currently have files for the years 2015 through 2023.
25
26Each file will be downloaded as a CSV file, approximately 3 MB - 8 MB.
27
28# Clean and Transform the Data
29
30I have used Python to combine the files into a single CSV file, as well as
31adding a custom `datetime` column. You could do this step in any software you
32prefer, but I prefer Python as its free, easy to use, and has a plethora of
33support resources online.
34
35Start by opening a terminal, navigating to your Downloads directory, and
36creating a python script.
37
38```sh
39cd ~/Downloads
40nano data_processing.py
41```
42
43Within the Python script, paste the following:
44
45```python
46# Import modules
47import pandas as pd
48import glob
49import os
50
51# Import the data
52path = r"~/Downloads/*.csv"
53files = glob.glob(path)
54
55list = []
56
57for file in files:
58 df_tmp = pd.read_csv(file)
59 li.append(df_tmp)
60
61df = pd.concat(list, axis=0, ignore_index=True)
62
63# Create a combined datetime column
64df["datetime"] = pd.to_datetime(
65 df["date"] + " " + df["time"],
66 format="%m/%d/%Y %H:%M:%S"
67)
68df.head()
69
70# Export the combined data
71df.to_csv(r"~/Downloads/combined_incidents.csv")
72```
73
74Once pasted, save and close the file. You can execute the file like so:
75
76```sh
77python3 data_processing.py
78```
79
80After this, you should have a combined data file that contains all incidents
81between 2015 and 2023. Mine was approximately 55 MB.
82
83# Tableau Public
84
85[Tableau Public](https://public.tableau.com/) is a free-to-use web application
86that allows you to create visualizations by uploading data sources. Note that
87there's no way to keep the data and visualizations private, so don't upload
88anything private.
89
90After creating an account, you can click the `Create` > `Web Authoring` link to
91create your first visualization.
92
93## Upload the Data
94
95Once you've opened your first project, Tableau will ask you to connect to your
96data. For this project, click the `Upload from computer` button and select the
97CSV file previously combined in the step above.
98
99Once connected, you can refresh the preview of the data with the `Refresh Data
100Source` button in the toolbar.
101
102If you need to edit any of the data types, column names, etc., you can do so
103now. Once complete, generate an extract so that you can start creating
104visualizations.
105
106## Create Visualizations
107
108To start, create a worksheet in the toolbar at the bottom of the screen.
109
110Within this screen, select a column from the `Data` side bar on the left and
111drag it into the `Columns` or `Rows` area of the canvas.
112
113See below for the map visualization. You can recreate this by adding the
114following fields:
115
116- `Columns`: Lon
117- `Rows`: Lat
118- `Marks`:
119 - Description
120 - Datetime
121- `Filters`: Datetime
122
123You can repeat this process for each visualization you want to create. Explore
124your options by dragging data fields to different areas and by opening the field
125options to explore what operations can be performed on different data types
126(e.g., average, count, etc.).
127
128## Create Dashboard
129
130To create a dashboard, click the button on the toolbar at the bottom of the
131screen. Within the dashboard, drag each sheet from the left side bar onto the
132dashboard canvas.
133
134## Formatting
135
136You can explore a ton of different formatting options throughout the worksheets
137and dashboard. Specifically for maps, you can alter the map layers, background,
138and visible features through the `Map` menu in the top file menu of the editing
139screen.
140
141In the finished dashboard below, I opted for a dark mode with a map that showed
142county lines and city names.
143
144There's a ton of other options available to be used in a dashboard like this,
145but this project shows a quick preview of what you can do in Tableau Public.
146
147![Tableau
148Dashboard](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png)
content/blog/2024-01-27-tableau-dashboard.org added +148
@@ -0,0 +1,148 @@
1#+date: <2024-01-27>
2#+title: Data Visualization: Mapping Omaha Crime Data with Tableau
3#+description:
4
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
19
20You can download incident data from the Omaha Police Department on their
21[[https://police.cityofomaha.org/crime-information/incident-data-download][Incident
22Data Download]] page. They currently have files for the years 2015
23through 2023.
24
25Each file will be downloaded as a CSV file, approximately 3 MB - 8 MB.
26
27* Clean and Transform the Data
28
29I have used Python to combine the files into a single CSV file, as well
30as adding a custom =datetime= column. You could do this step in any
31software you prefer, but I prefer Python as its free, easy to use, and
32has a plethora of support resources online.
33
34Start by opening a terminal, navigating to your Downloads directory, and
35creating a python script.
36
37#+begin_src sh
38cd ~/Downloads
39nano data_processing.py
40#+end_src
41
42Within the Python script, paste the following:
43
44#+begin_src python
45# Import modules
46import pandas as pd
47import glob
48import os
49
50# Import the data
51path = r"~/Downloads/*.csv"
52files = glob.glob(path)
53
54list = []
55
56for file in files:
57 df_tmp = pd.read_csv(file)
58 li.append(df_tmp)
59
60df = pd.concat(list, axis=0, ignore_index=True)
61
62# Create a combined datetime column
63df["datetime"] = pd.to_datetime(
64 df["date"] + " " + df["time"],
65 format="%m/%d/%Y %H:%M:%S"
66)
67df.head()
68
69# Export the combined data
70df.to_csv(r"~/Downloads/combined_incidents.csv")
71#+end_src
72
73Once pasted, save and close the file. You can execute the file like so:
74
75#+begin_src sh
76python3 data_processing.py
77#+end_src
78
79After this, you should have a combined data file that contains all
80incidents between 2015 and 2023. Mine was approximately 55 MB.
81
82* Tableau Public
83
84[[https://public.tableau.com/][Tableau Public]] is a free-to-use web
85application that allows you to create visualizations by uploading data
86sources. Note that there's no way to keep the data and visualizations
87private, so don't upload anything private.
88
89After creating an account, you can click the =Create= > =Web Authoring=
90link to create your first visualization.
91
92** Upload the Data
93
94Once you've opened your first project, Tableau will ask you to connect
95to your data. For this project, click the =Upload from computer= button
96and select the CSV file previously combined in the step above.
97
98Once connected, you can refresh the preview of the data with the
99=Refresh Data Source= button in the toolbar.
100
101If you need to edit any of the data types, column names, etc., you can
102do so now. Once complete, generate an extract so that you can start
103creating visualizations.
104
105** Create Visualizations
106
107To start, create a worksheet in the toolbar at the bottom of the screen.
108
109Within this screen, select a column from the =Data= side bar on the left
110and drag it into the =Columns= or =Rows= area of the canvas.
111
112See below for the map visualization. You can recreate this by adding the
113following fields:
114
115- =Columns=: Lon
116- =Rows=: Lat
117- =Marks=:
118 - Description
119 - Datetime
120- =Filters=: Datetime
121
122You can repeat this process for each visualization you want to create.
123Explore your options by dragging data fields to different areas and by
124opening the field options to explore what operations can be performed on
125different data types (e.g., average, count, etc.).
126
127** Create Dashboard
128
129To create a dashboard, click the button on the toolbar at the bottom of
130the screen. Within the dashboard, drag each sheet from the left side bar
131onto the dashboard canvas.
132
133** Formatting
134
135You can explore a ton of different formatting options throughout the
136worksheets and dashboard. Specifically for maps, you can alter the map
137layers, background, and visible features through the =Map= menu in the
138top file menu of the editing screen.
139
140In the finished dashboard below, I opted for a dark mode with a map that
141showed county lines and city names.
142
143There's a ton of other options available to be used in a dashboard like
144this, but this project shows a quick preview of what you can do in
145Tableau Public.
146
147#+caption: Tableau Dashboard
148[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png]]
content/blog/2024-02-06-zfs.md → content/blog/2024-02-06-zfs.org renamed +140 −131
@@ -1,70 +1,70 @@
1+++ 1#+date: <2024-02-06>
2date = 2024-02-06 2#+title: How to Create a ZFS Pool on Ubuntu Linux
3title = "How to Create a ZFS Pool on Ubuntu Linux" 3#+description:
4description = "" 4
5draft = false
6+++
7 5
8This 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
9snapshots on Ubuntu Server. 7snapshots on Ubuntu Server.
10 8
11I found the following pages very helpful while going through this process: 9I found the following pages very helpful while going through this
10process:
12 11
13- [Setup a ZFS storage 12- [[https://ubuntu.com/tutorials/setup-zfs-storage-pool][Setup a ZFS
14 pool](https://ubuntu.com/tutorials/setup-zfs-storage-pool) 13 storage pool]]
15- [Kernel/Reference/ZFS](https://wiki.ubuntu.com/Kernel/Reference/ZFS) 14- [[https://wiki.ubuntu.com/Kernel/Reference/ZFS][Kernel/Reference/ZFS]]
16- [ZFS for 15- [[https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/][ZFS for
17 Dummies](https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/) 16 Dummies]]
18 17
19# Installation 18* Installation
20 19
21To start, I installed the ZFS package with the following command: 20To start, I installed the ZFS package with the following command:
22 21
23```sh 22#+begin_src sh
24sudo apt install zfsutils-linux 23sudo apt install zfsutils-linux
25``` 24#+end_src
26 25
27Once installed, you can check the version to see if it installed correctly. 26Once installed, you can check the version to see if it installed
27correctly.
28 28
29```sh 29#+begin_src sh
30> zsf --version 30> zsf --version
31 31
32zfs-2.1.5-1ubuntu6~22.04.2 32zfs-2.1.5-1ubuntu6~22.04.2
33zfs-kmod-2.1.5-1ubuntu6~22.04.1 33zfs-kmod-2.1.5-1ubuntu6~22.04.1
34``` 34#+end_src
35 35
36# ZFS Configuration 36* ZFS Configuration
37 37
38Now that ZFS is installed, we can create and configure the pool. 38Now that ZFS is installed, we can create and configure the pool.
39 39
40You have various options for configuring ZFS pools that all come different pros 40You have various options for configuring ZFS pools that all come
41and cons. I suggest visiting the links at the top of this post or searching 41different pros and cons. I suggest visiting the links at the top of this
42online for the best configuration for your use-case. 42post or searching online for the best configuration for your use-case.
43 43
44- Striped VDEVs (Raid0) 44- Striped VDEVs (Raid0)
45- Mirrored VDEVs (Raid1) 45- Mirrored VDEVs (Raid1)
46- Striped Mirrored VDEVs (Raid10) 46- Striped Mirrored VDEVs (Raid10)
47- RAIDz (Raid5) 47- RAIDz (Raid5)
48- RAIDz2 (Raidd6) 48- RAIDz2 (Raidd6)
49- RAIDz3 49- RAIDz3
50- Nested RAIDz (Raid50, Raid60) 50- Nested RAIDz (Raid50, Raid60)
51 51
52I will be using Raid10 in this guide. However, the majority of the steps are the 52I will be using Raid10 in this guide. However, the majority of the steps
53same regardless of your chosen pool configuration. 53are the same regardless of your chosen pool configuration.
54 54
55## Creating the Pool 55** Creating the Pool
56 56
57To start, let's list the disks available to use. You can use `fdisk` command to 57To start, let's list the disks available to use. You can use =fdisk=
58see all available disks. 58command to see all available disks.
59 59
60```sh 60#+begin_src sh
61sudo fdisk -l 61sudo fdisk -l
62``` 62#+end_src
63 63
64Or, if you currently have them mounted, you can use the `df` command to view 64Or, if you currently have them mounted, you can use the =df= command to
65your disks. 65view your disks.
66 66
67```sh 67#+begin_src sh
68> sudo df -h 68> sudo df -h
69 69
70Filesystem Size Used Avail Use% Mounted on 70Filesystem Size Used Avail Use% Mounted on
@@ -75,34 +75,35 @@ Filesystem Size Used Avail Use% Mounted on
75/dev/sdd1 9.1T 8.7T 435G 96% /mnt/white-01 75/dev/sdd1 9.1T 8.7T 435G 96% /mnt/white-01
76/dev/sde1 7.3T 28K 6.9T 1% /mnt/red-03 76/dev/sde1 7.3T 28K 6.9T 1% /mnt/red-03
77/dev/sdf1 7.3T 28K 6.9T 1% /mnt/red-04 77/dev/sdf1 7.3T 28K 6.9T 1% /mnt/red-04
78``` 78#+end_src
79 79
80If you're going to use mounted disks, make sure to umount them before creating 80If you're going to use mounted disks, make sure to umount them before
81the pool. 81creating the pool.
82 82
83```sh 83#+begin_src sh
84sudo umount /dev/sda1 84sudo umount /dev/sda1
85sudo umount /dev/sdb1 85sudo umount /dev/sdb1
86``` 86#+end_src
87 87
88Now that I've identified the disks I want to use and have them unmounted, let's 88Now that I've identified the disks I want to use and have them
89create the pool. For this example, I will call it `tank`. 89unmounted, let's create the pool. For this example, I will call it
90=tank=.
90 91
91```sh 92#+begin_src sh
92sudo zpool create -f -m /mnt/pool tank mirror /dev/sda /dev/sdb 93sudo zpool create -f -m /mnt/pool tank mirror /dev/sda /dev/sdb
93``` 94#+end_src
94 95
95See below for the results of the new ZFS pool named `tank`, with a vdev 96See below for the results of the new ZFS pool named =tank=, with a vdev
96automatically named `mirror-0`. 97automatically named =mirror-0=.
97 98
98```sh 99#+begin_src sh
99> zfs list 100> zfs list
100 101
101NAME USED AVAIL REFER MOUNTPOINT 102NAME USED AVAIL REFER MOUNTPOINT
102tank 396K 7.14T 96K /tank 103tank 396K 7.14T 96K /tank
103``` 104#+end_src
104 105
105```sh 106#+begin_src sh
106> zpool status 107> zpool status
107 108
108 pool: tank 109 pool: tank
@@ -116,75 +117,78 @@ config:
116 sdb ONLINE 0 0 0 117 sdb ONLINE 0 0 0
117 118
118errors: No known data errors 119errors: No known data errors
119``` 120#+end_src
120 121
121We can also look at the mounted filesystem to see where the pool is mounted and 122We can also look at the mounted filesystem to see where the pool is
122some quick stats. 123mounted and some quick stats.
123 124
124```sh 125#+begin_src sh
125> df -h 126> df -h
126 127
127Filesystem Size Used Avail Use% Mounted on 128Filesystem Size Used Avail Use% Mounted on
128... 129...
129tank 7.2T 128K 7.2T 1% /tank 130tank 7.2T 128K 7.2T 1% /tank
130``` 131#+end_src
131 132
132## Expanding the Pool 133** Expanding the Pool
133 134
134If you want to expand this pool, you will need to add a new VDEV to the pool. 135If you want to expand this pool, you will need to add a new VDEV to the
135Since I am using 2 disks per VDEV, I will need to add a new 2-disk VDEV to the 136pool. Since I am using 2 disks per VDEV, I will need to add a new 2-disk
136existing `tank` pool. 137VDEV to the existing =tank= pool.
137 138
138```sh 139#+begin_src sh
139sudo zpool add tank mirror /dev/sdX /dev/sdY 140sudo zpool add tank mirror /dev/sdX /dev/sdY
140``` 141#+end_src
141 142
142If you're adding disks of different sizes, you'll need to use the `-f` flag. 143If you're adding disks of different sizes, you'll need to use the =-f=
143Keep in mind that the max size will be limited to the smallest disk added. 144flag. Keep in mind that the max size will be limited to the smallest
145disk added.
144 146
145```sh 147#+begin_src sh
146sudo zpool add -f tank mirror /dev/sdX /dev/sdY 148sudo zpool add -f tank mirror /dev/sdX /dev/sdY
147``` 149#+end_src
148 150
149I added two 8TB hard drives and this process took around 10 seconds to complete. 151I added two 8TB hard drives and this process took around 10 seconds to
152complete.
150 153
151When viewing the pool again, you can see that the pool has now doubled in size. 154When viewing the pool again, you can see that the pool has now doubled
152We have 14.3 TB useable space and the same space used for mirroring. 155in size. We have 14.3 TB useable space and the same space used for
156mirroring.
153 157
154```sh 158#+begin_src sh
155> zfs list 159> zfs list
156 160
157NAME USED AVAIL REFER MOUNTPOINT 161NAME USED AVAIL REFER MOUNTPOINT
158tank 145G 14.3T 104K /tank 162tank 145G 14.3T 104K /tank
159tank/cloud 145G 14.3T 145G /tank/cloud 163tank/cloud 145G 14.3T 145G /tank/cloud
160tank/media 96K 14.3T 96K /tank/media 164tank/media 96K 14.3T 96K /tank/media
161``` 165#+end_src
162 166
163### Converting Disks 167*** Converting Disks
164 168
165Some disks, such as NTFS-formatted drives, will need to be partitioned and 169Some disks, such as NTFS-formatted drives, will need to be partitioned
166formatted prior to being added to the pool. 170and formatted prior to being added to the pool.
167 171
168Start by identifying the disks you want to format and add to the pool. 172Start by identifying the disks you want to format and add to the pool.
169 173
170```sh 174#+begin_src sh
171sudo fdisk -l | grep /dev 175sudo fdisk -l | grep /dev
172``` 176#+end_src
173 177
174I am going to format my `/dev/sdc` and `/dev/sdd` disks with the `fdisk` 178I am going to format my =/dev/sdc= and =/dev/sdd= disks with the =fdisk=
175command. 179command.
176 180
177See below for instructions on how to use `fdisk`. Here's what I did to create 181See below for instructions on how to use =fdisk=. Here's what I did to
178basic Linux formatted disks: 182create basic Linux formatted disks:
179 183
180- `g` : Create GPT partition table 184- =g= : Create GPT partition table
181- `n` : Create a new partition, hit Enter for all default options 185- =n= : Create a new partition, hit Enter for all default options
182- `t` : Change partition type to `20` for `Linux filesystem` 186- =t= : Change partition type to =20= for =Linux filesystem=
183- `w` : Write the changes to disk and exit 187- =w= : Write the changes to disk and exit
184 188
185I repeated this process for both disks. 189I repeated this process for both disks.
186 190
187```sh 191#+begin_src sh
188> sudo fdisk /dev/sdc 192> sudo fdisk /dev/sdc
189 193
190Welcome to fdisk (util-linux 2.37.2). 194Welcome to fdisk (util-linux 2.37.2).
@@ -230,95 +234,100 @@ Help:
230 G create a new empty SGI (IRIX) partition table 234 G create a new empty SGI (IRIX) partition table
231 o create a new empty DOS partition table 235 o create a new empty DOS partition table
232 s create a new empty Sun partition table 236 s create a new empty Sun partition table
233``` 237#+end_src
234 238
235Once the drives are formatted, we can add these disks to the pool. 239Once the drives are formatted, we can add these disks to the pool.
236 240
237```sh 241#+begin_src sh
238sudo zpool add tank mirror /dev/sdc /dev/sdd 242sudo zpool add tank mirror /dev/sdc /dev/sdd
239``` 243#+end_src
240 244
241When we list the pool again, we can see that our size is now updated to 245When we list the pool again, we can see that our size is now updated to
242approximately 22TB. This represents my hard drives totalling 45.6TB when shown 246approximately 22TB. This represents my hard drives totalling 45.6TB when
243with `fdisk -l`, with a Raid10 configuration using 22TB for mirroring and 22TB 247shown with =fdisk -l=, with a Raid10 configuration using 22TB for
244of useable space. 248mirroring and 22TB of useable space.
245 249
246```sh 250#+begin_src sh
247> zfs list 251> zfs list
248 252
249NAME USED AVAIL REFER MOUNTPOINT 253NAME USED AVAIL REFER MOUNTPOINT
250tank 145G 21.7T 104K /tank 254tank 145G 21.7T 104K /tank
251tank/cloud 145G 21.7T 145G /tank/cloud 255tank/cloud 145G 21.7T 145G /tank/cloud
252tank/media 145GT 21.7T 96K /tank/media 256tank/media 145GT 21.7T 96K /tank/media
253``` 257#+end_src
254 258
255## Creating Datasets 259** Creating Datasets
256 260
257According to [ZFS 261According to
258Terminology](https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html), a 262[[https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html][ZFS
259`dataset` can refer to "clones, file systems, snapshots, and volumes. 263Terminology]], a =dataset= can refer to “clones, file systems,
264snapshots, and volumes.
260 265
261For this guide, I will use the `dataset` term to refer to file systems created 266For this guide, I will use the =dataset= term to refer to file systems
262under a pool. 267created under a pool.
263 268
264Within my `tank` pool, I am going to create some datasets to help organize my 269Within my =tank= pool, I am going to create some datasets to help
265files. This will give me location to store data rather than simply dumping 270organize my files. This will give me location to store data rather than
266everything at the `/tank/` location. 271simply dumping everything at the =/tank/= location.
267 272
268```sh 273#+begin_src sh
269sudo zfs create tank/cloud 274sudo zfs create tank/cloud
270sudo zfs create tank/media 275sudo zfs create tank/media
271``` 276#+end_src
272 277
273Once created, you can see these datasets in the output of your pool list: 278Once created, you can see these datasets in the output of your pool
279list:
274 280
275```sh 281#+begin_src sh
276> zfs list 282> zfs list
277NAME USED AVAIL REFER MOUNTPOINT 283NAME USED AVAIL REFER MOUNTPOINT
278tank 752K 7.14T 104K /tank 284tank 752K 7.14T 104K /tank
279tank/cloud 96K 7.14T 96K /tank/cloud 285tank/cloud 96K 7.14T 96K /tank/cloud
280tank/media 96K 7.14T 96K /tank/media 286tank/media 96K 7.14T 96K /tank/media
281``` 287#+end_src
282 288
283## Creating Snapshots 289** Creating Snapshots
284 290
285Next, let's create our first snapshot. We can do this by calling the `snapshot` 291Next, let's create our first snapshot. We can do this by calling the
286command and give it an output name. I will be throwing the current date and time 292=snapshot= command and give it an output name. I will be throwing the
287into my example. 293current date and time into my example.
288 294
289```sh 295#+begin_src sh
290sudo zfs snapshot tank@$(date '+%Y-%m-%d_%H-%M') 296sudo zfs snapshot tank@$(date '+%Y-%m-%d_%H-%M')
291``` 297#+end_src
292 298
293We can list the snapshots in our pool with the following command: 299We can list the snapshots in our pool with the following command:
294 300
295```sh 301#+begin_src sh
296> zfs list -t snapshot 302> zfs list -t snapshot
297NAME USED AVAIL REFER MOUNTPOINT 303NAME USED AVAIL REFER MOUNTPOINT
298tank@2024-02-06_19-41 0B - 104K - 304tank@2024-02-06_19-41 0B - 104K -
299``` 305#+end_src
300 306
301## Destroy Snapshots 307** Destroy Snapshots
302 308
303You can always destroy snapshots that are no longer needed: 309You can always destroy snapshots that are no longer needed:
304 310
305```sh 311#+begin_src sh
306sudo zfs destroy tank@2024-02-06_19-41 312sudo zfs destroy tank@2024-02-06_19-41
307``` 313#+end_src
308 314
309Once deleted, they will no longer appear in the list: 315Once deleted, they will no longer appear in the list:
310 316
311```sh 317#+begin_src sh
312> zfs list -t snapshot 318> zfs list -t snapshot
313no datasets available 319no datasets available
314``` 320#+end_src
315 321
316# My Thoughts on ZFS So Far 322* My Thoughts on ZFS So Far
317 323
318- I sacrificed 25TB to be able to mirror my data, but I feel more comfortable 324- I sacrificed 25TB to be able to mirror my data, but I feel more
319 with the potential to save my data by quickly replacing a disk if I need to. 325 comfortable with the potential to save my data by quickly replacing a
320- The set-up was surprisingly easy and fast. 326 disk if I need to.
321- Disk I/O is fast as well. I was worried that the data transfer speeds would 327- The set-up was surprisingly easy and fast.
322 be slower due to the RAID configuration. 328- Disk I/O is fast as well. I was worried that the data transfer speeds
323- Media streaming and transcoding has seen no noticeable drop in performance. 329 would be slower due to the RAID configuration.
324- My only limitation really is the number of HDD bays in my server HDD cage. 330- Media streaming and transcoding has seen no noticeable drop in
331 performance.
332- My only limitation really is the number of HDD bays in my server HDD
333 cage.
content/blog/2024-02-13-ubuntu-emergency-mode.md deleted −72
@@ -1,72 +0,0 @@
1+++
2date = 2024-02-13
3title = "Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File"
4description = ""
5draft = false
6+++
7
8# The Problem
9
10I recently [migrated my hard drives to a ZFS pool](../zfs/) and found myself
11stuck in Ubuntu's emergency mode after the first reboot I performed after
12creating the ZFS pool.
13
14My server was stuck in the boot process and showed the following error on the
15screen:
16
17```txt
18You are in emergency mode.
19After logging in, type "journalctl -xb" to view system logs,
20"systemctl reboot" to reboot, "systemctl default"
21or ^D to try again to boot into default mode".
22```
23
24After rebooting the server and watching the logs scroll on a monitor, I noticed
25the root cause was related to a very long search for certain drives. I kept
26seeing errors like this:
27
28```txt
29[ TIME ] Timed out waiting of device dev-disk-by/[disk-uuid]
30```
31
32I realized that I had not removed the `/etc/fstab` references that asked Ubuntu
33to mount two disks on boot, but I had recently changed those disks to be part of
34my ZFS pool instead. Therefore, Ubuntu was trying to identify and mount a disk
35that was not available.
36
37Now that we have an idea of the issue, let's move to solution.
38
39# The Solution
40
41In order to fix the issue, I waited until I was allowed to type the root user's
42password, and then I executed the following command:
43
44```sh
45nano /etc/fstab
46```
47
48Within the `fstab` file, I needed to comment/remove the following lines at the
49bottom of the file. You can comment-out a line by prepending a `#` symbol at the
50beginning of the line. You can also delete the line entirely.
51
52```conf
53# What it looked like when running into the issue:
54UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
55UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
56
57# What I changed it to, in order to fix the issue:
58# UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
59# UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
60```
61
62Once removing the lines above from the `/etc/fstab` file, save and exit the file
63by hitting the `Ctrl` + `x` key combo.
64
65You can now hit `Ctrl` + `D` to continue, or reboot:
66
67```sh
68systemctl reboot
69```
70
71Once rebooted, I was able to watch the machine boot properly and launch to the
72TTY login screen without errors!
content/blog/2024-02-13-ubuntu-emergency-mode.org added +71
@@ -0,0 +1,71 @@
1#+date: <2024-02-13>
2#+title: Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File
3#+description:
4
5
6* The Problem
7
8I recently [[../zfs/][migrated my hard drives to a ZFS pool]] and found
9myself stuck in Ubuntu's emergency mode after the first reboot I
10performed after creating the ZFS pool.
11
12My server was stuck in the boot process and showed the following error
13on the screen:
14
15#+begin_src txt
16You are in emergency mode.
17After logging in, type "journalctl -xb" to view system logs,
18"systemctl reboot" to reboot, "systemctl default"
19or ^D to try again to boot into default mode".
20#+end_src
21
22After rebooting the server and watching the logs scroll on a monitor, I
23noticed the root cause was related to a very long search for certain
24drives. I kept seeing errors like this:
25
26#+begin_src txt
27[ TIME ] Timed out waiting of device dev-disk-by/[disk-uuid]
28#+end_src
29
30I realized that I had not removed the =/etc/fstab= references that asked
31Ubuntu to mount two disks on boot, but I had recently changed those
32disks to be part of my ZFS pool instead. Therefore, Ubuntu was trying to
33identify and mount a disk that was not available.
34
35Now that we have an idea of the issue, let's move to solution.
36
37* The Solution
38
39In order to fix the issue, I waited until I was allowed to type the root
40user's password, and then I executed the following command:
41
42#+begin_src sh
43nano /etc/fstab
44#+end_src
45
46Within the =fstab= file, I needed to comment/remove the following lines
47at the bottom of the file. You can comment-out a line by prepending a
48=#= symbol at the beginning of the line. You can also delete the line
49entirely.
50
51#+begin_src 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#+end_src
60
61Once removing the lines above from the =/etc/fstab= file, save and exit
62the file by hitting the =Ctrl= + =x= key combo.
63
64You can now hit =Ctrl= + =D= to continue, or reboot:
65
66#+begin_src sh
67systemctl reboot
68#+end_src
69
70Once rebooted, I was able to watch the machine boot properly and launch
71to the TTY login screen without errors!
content/blog/2024-02-21-self-hosting-otter-wiki.md → content/blog/2024-02-21-self-hosting-otter-wiki.org renamed +44 −44
@@ -1,39 +1,39 @@
1+++ 1#+date: <2024-02-21>
2date = 2024-02-21 2#+title: Self-Hosting an Otter Wiki
3title = "Self-Hosting an Otter Wiki" 3#+description:
4description = ""
5draft = false
6+++
7 4
8# An Otter Wiki
9 5
10[An Otter Wiki](https://otterwiki.com/) is an easy to use wiki software that 6* An Otter Wiki
11takes almost no effort to set up and maintain.
12 7
13# Installation 8[[https://otterwiki.com/][An Otter Wiki]] is an easy to use wiki
9software that takes almost no effort to set up and maintain.
10
11* Installation
14 12
15To install An Otter Wiki, I'm going to use Docker Compose to create the 13To install An Otter Wiki, I'm going to use Docker Compose to create the
16container and then use Nginx as a reverse proxy to allow external access via a 14container and then use Nginx as a reverse proxy to allow external access
17subdomain I own. 15via a subdomain I own.
18 16
19## Docker Compose 17** Docker Compose
20 18
21Start by creating a directory for the container's files. 19Start by creating a directory for the container's files.
22 20
23```sh 21#+begin_src sh
24mkdir ~/otterwiki 22mkdir ~/otterwiki
25``` 23#+end_src
26 24
27Next, create the `docker-compose.yml` file to define the container's parameters. 25Next, create the =docker-compose.yml= file to define the container's
26parameters.
28 27
29```sh 28#+begin_src sh
30nano ~/otterwiki/docker-compose.yml 29nano ~/otterwiki/docker-compose.yml
31``` 30#+end_src
32 31
33Within the file, paste the following content. You can read the project's 32Within the file, paste the following content. You can read the project's
34documentation if you want to further override or customize the container. 33documentation if you want to further override or customize the
34container.
35 35
36```conf 36#+begin_src conf
37version: '3' 37version: '3'
38services: 38services:
39 otterwiki: 39 otterwiki:
@@ -45,33 +45,33 @@ services:
45 - 8337:80 45 - 8337:80
46 volumes: 46 volumes:
47 - ./app-data:/app-data 47 - ./app-data:/app-data
48``` 48#+end_src
49 49
50Once the file is saved and closed, you can launch the container. 50Once the file is saved and closed, you can launch the container.
51 51
52```sh 52#+begin_src sh
53cd ~/otterwiki 53cd ~/otterwiki
54sudo docker-compose up -d 54sudo docker-compose up -d
55``` 55#+end_src
56 56
57The container is now available at `localhost:8337`. Next, we will use Nginx to 57The container is now available at =localhost:8337=. Next, we will use
58serve this app externally. 58Nginx to serve this app externally.
59 59
60## Nginx 60** Nginx
61 61
62To access the app externally, let's set up a reverse proxy. I'll start by 62To access the app externally, let's set up a reverse proxy. I'll start
63creating the Nginx configuration file for my wiki. 63by creating the Nginx configuration file for my wiki.
64 64
65```sh 65#+begin_src sh
66sudo nano /etc/nginx/sites-available/wiki 66sudo nano /etc/nginx/sites-available/wiki
67``` 67#+end_src
68 68
69Within the file, I have saved the following content. This assumes you already 69Within the file, I have saved the following content. This assumes you
70have a TLS/SSL cert to use with this subdomain. If not, simply remove the 70already have a TLS/SSL cert to use with this subdomain. If not, simply
71`ssl_*` variables, remove the `80` server block, and change the `443` server 71remove the =ssl_*= variables, remove the =80= server block, and change
72block to `80` to serve the app without SSL. 72the =443= server block to =80= to serve the app without SSL.
73 73
74```conf 74#+begin_src conf
75server { 75server {
76 if ($host ~ ^[^.]+\.example\.com$) { 76 if ($host ~ ^[^.]+\.example\.com$) {
77 return 301 https://$host$request_uri; 77 return 301 https://$host$request_uri;
@@ -105,18 +105,18 @@ server {
105 proxy_set_header X-Forwarded-Host $http_host; 105 proxy_set_header X-Forwarded-Host $http_host;
106 } 106 }
107} 107}
108``` 108#+end_src
109 109
110Save and close the configuration file. On Nginx, we need to symlink the file to 110Save and close the configuration file. On Nginx, we need to symlink the
111enable it. 111file to enable it.
112 112
113```sh 113#+begin_src sh
114sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki 114sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki
115``` 115#+end_src
116 116
117Once enabled, restart the Nginx server to start serving the app externally. 117Once enabled, restart the Nginx server to start serving the app
118externally.
118 119
119```sh 120#+begin_src sh
120sudo systemctl restart nginx.service 121sudo systemctl restart nginx.service
121``` 122#+end_src
122
content/blog/2024-03-13-doom-emacs.md deleted −343
@@ -1,343 +0,0 @@
1+++
2date = 2024-03-13
3title = "Doom Emacs & Org-Mode"
4description = ""
5draft = false
6+++
7
8## Screenshots
9
10These screenshots are showing a project opened with projectile, a treemacs side
11pane open with the project contents, multiple buffers tiled next to each other,
12and the help pane open at the bottomm.
13
14The themes are `doom-homage-white` and `doom-homage-black`.
15
16![Doom Emacs Light
17Mode](https://img.cleberg.net/blog/20240314-doom-emacs/light.png)
18
19![Doom Emacs Dark
20Mode](https://img.cleberg.net/blog/20240314-doom-emacs/dark.png)
21
22## Getting Started
23
24I have been switching back and forth between
25[markdown](https://en.wikipedia.org/wiki/Markdown) and
26[org-mode](https://en.wikipedia.org/wiki/Org-mode) recently for my personal note
27taking, wiki, and even this blog. As a result, I have been stumbling further
28into the world of Emacs and found myself at a point where I now prefer to do
29most of my basic editing within Emacs.
30
31I'll leave the markdown vs. org-mode debate for another post, but I love
32org-mode's extensibility and interactive nature within Emacs, but it becomes
33very unwieldy in any other client implementation of org-mode - especially on
34iOS. On the flip side, markdown is limited in functionality and fractured into
35different standards, but it's simple and popular enough that there are a
36plethora of great clients to choose from that will get the job done.
37
38For now, I want to focus on how I have been using Emacs and some of the things
39that would have helped me learn it faster had I known where to start.
40
41### Installation
42
43This post focuses on [Doom Emacs](https://github.com/doomemacs/doomemacs), which
44is an Emacs framework that provides an alternative experience to the vanilla
45[GNU Emacs](https://www.gnu.org/software/emacs/).
46
47The [Getting Start
48Guide](https://github.com/doomemacs/doomemacs/blob/master/docs/getting_started.org)
49has an extremely detailed walkthrough of installation for all systems, so please
50refer to that guide for up-to-date instructions.
51
52I chose to install on macOS, using the Homebrew option with the
53`railwaycat/emacsmacport` version of Emacs.
54
55Once the program is installed, you can run the program by typing `emacs` in a
56terminal. If you installed a version of Emacs that supports both a GUI and TUI,
57you will have to run `emacs -nw` to get the TUI instead of the default GUI.
58
59### Configuration
60
61Once installed, you can configure Doom by editing the files within the
62`~/.doom.d/` directory. This directory holds four files:
63
641. `config.el` - Personal configuration file
652. `custom.el` - Custom set variables
663. `init.el` - Doom modules and load order, must run `doom sync` after
67 modifying
684. `packages.el` - Declare packages to install in this file, then run `doom
69sync` to install
70
71I only needed a few customizations for my configuration, so I'll list them
72below.
73
74```lisp
75;; ~/.doom.d/config.el
76(setq doom-theme 'doom-homage-black)
77(setq display-line-numbers-type t)
78(setq org-directory "~/Documents/Notes/")
79
80;; lengthy org-publish directives at the bottom of the file
81```
82
83```lisp
84;; ~/.doom.d/init.el
85(doom! :input
86 :completion
87 company ; the ultimate code completion backend
88 vertico ; the search engine of the future
89
90 :ui
91 doom ; what makes DOOM look the way it does
92 doom-dashboard ; a nifty splash screen for Emacs
93 (emoji +unicode) ; 🙂
94 hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
95 minimap ; show a map of the code on the side
96 modeline ; snazzy, Atom-inspired modeline, plus API
97 ophints ; highlight the region an operation acts on
98 (popup +defaults) ; tame sudden yet inevitable temporary windows
99 tabs ; a tab bar for Emacs
100 treemacs ; a project drawer, like neotree but cooler
101 (vc-gutter +pretty) ; vcs diff in the fringe
102 vi-tilde-fringe ; fringe tildes to mark beyond EOB
103 workspaces ; tab emulation, persistence & separate workspaces
104
105 :editor
106 (evil +everywhere); come to the dark side, we have cookies
107 file-templates ; auto-snippets for empty files
108 fold ; (nigh) universal code folding
109 snippets ; my elves. They type so I don't have to
110
111 :emacs
112 dired ; making dired pretty [functional]
113 electric ; smarter, keyword-based electric-indent
114 undo ; persistent, smarter undo for your inevitable mistakes
115 vc ; version-control and Emacs, sitting in a tree
116
117 :term
118 term ; basic terminal emulator for Emacs
119
120 :checkers
121 syntax ; tasing you for every semicolon you forget
122
123 :tools
124 (eval +overlay) ; run code, run (also, repls)
125 lookup ; navigate your code and its documentation
126 magit ; a git porcelain for Emacs
127
128 :os
129 (:if (featurep :system 'macos) macos) ; improve compatibility with macOS
130
131 :lang
132 common-lisp ; if you've seen one lisp, you've seen them all
133 emacs-lisp ; drown in parentheses
134 markdown ; writing docs for people to ignore
135 org ; organize your plain life in plain text
136 python ; beautiful is better than ugly
137 sh ; she sells {ba,z,fi}sh shells on the C xor
138
139 :app
140 irc ; how neckbeards socialize
141 (rss +org) ; emacs as an RSS reader
142
143 (default +bindings +smartparens))
144```
145
146If you're editing these files within Doom directly, remember to run `SPC h r r`
147to reload the configuration. Also remember to run `doom sync` for any changes to
148the `init.el` or `packages.el` files.
149
150## Basic Functionality
151
152I kept a cheat sheet note open at first with all of the basic functions typed
153out, copied as I went through the tutorial. After a little while, I no longer
154needed it. I highly recommend writing down the most applicable shortcuts for
155your preferred functionality and refer back to it until you've memorized it.
156
157Memorizing the shortcuts will differ based on the type of Emacs framework being
158used. Personally, migrating from vanilla Emacs to Doom Emacs simplified
159everything by a large factor and instantly enabled me to start working on my
160projects, eliminating most of the hurdles I was running into. The vanilla emacs
161hotkeys became obnoxious and I actually stopped using Emacs entirely for about a
162month before trying Doom.
163
164For me, the first logical step is to interact with the local filesystem. To do
165this, I needed to know how to open directories, open files, save files, discard
166changes, close files, and switch between open files. Here are some example
167shortcuts I've written down in order to accomplish file-based actions.
168
169| Doom Hotkey | Emacs Hotkey | Description |
170| --------------- | ------------ | -------------------------------------- |
171| `SPC :` | `C-x` | Run functions |
172| `SPC f f` | `C-x f` | Open file in buffer |
173| `SPC f d` | `C-x d` | Open directory with `dired` |
174| `i` | `C-x C-q` | Edit current buffer (insert mode) |
175| `q` | `C-x C-q` | Quit out of insert mode |
176| `SPC f s` | `C-x s` | Save current buffer |
177| `SPC b k` | `C-x k` | Kill current buffer |
178| `SPC w h/j/k/l` | `C-x o`[^1] | Move left/down/up/right to next buffer |
179
180[^1]: Doom's evil-window functionality is a bit different from GNU Emacs, but
181 you can always switch to the "other" buffer with `C-x o` or `C-x b` to get a
182 list of buffers to select.
183
184In general, when in Doom, you can press `SPC` and wait a second for the help
185pane to appear with all available hotkey options. For example, you can press
186`SPC`, wait for the help pane, and then select a key such as `g` to enter the
187git help pane and explore further command options.
188
189## Editing
190
191Next in my process is to dive into editing for any languages I'm currently
192using. In this post, I will just cover Markdown and Org-Mode but I have also
193been slowly adoping some Python and general web dev tools as well.
194
195### Markdown
196
197![Markdown
198Preview](https://img.cleberg.net/blog/20240314-doom-emacs/markdown.png)
199
200Markdown is fairly simple as the syntax is limited, so just make sure the
201`~/.doom.d/init.el` includes the `markdown` declaration in the `:lang` section.
202
203This package includes the following hotkey menus. The insert and toggle menu
204expands further, allowing you to insert various markdown elements and toggle
205things like link hiding.
206
207| Doom Hotkey | Function |
208| ---------------------------- | ------------------------ |
209| `SPC m '` | markdown-edit-code-block |
210| `SPC m e` | markdown-export |
211| `SPC m i` | +insert |
212| `SPC m o` | markdown-open |
213| `SPC m p` | markdown-preview |
214| `SPC m t` | +toggle |
215| `SPC : markdown-table-align` | markdown-table-align |
216
217### Org-Mode
218
219![Org-Mode Preview](https://img.cleberg.net/blog/20240314-doom-emacs/org.png)
220
221Similar to the markdown section above, ensure that the `~/.doom.d/init.el`
222includes the `org` declaration in the `:lang` section.
223
224There are a few hot keys, but a quick search with `SPC : org` shows that there
225are 865 possible org-related functions you can run. I won't possibly be able to
226list them all, so I will simply cover a few of the basic commands I use myself.
227
228| Doom Hotkey | Function |
229| -------------- | ------------------------------------- |
230| `SPC m t` | org-todo |
231| `SPC n t` | org-todo-list |
232| `SPC o A` | org-agenda |
233| `SPC X` | org-capture |
234| `SPC m p p` | org-priority |
235| `SPC m d s` | org-schedule |
236| `TAB` | org-cycle |
237| `SHIFT TAB` | Collapse/open all headings in buffer |
238| `M-q` | Format/wrap current section |
239| `M-Left/Right` | Demote/promote current heading |
240| `M-Down/Up` | Shift current heading section down/up |
241
2421. Org-Publish
243
244 Org includes a [publishing management
245 system](https://orgmode.org/manual/Publishing.html) by default that allows
246 you to export org files to Org, iCalendar, HTML, LaTex, Markdown, ODT, and
247 Plain Text. Most of these can be exported into another buffer and opened, or
248 simply to an external file.
249
250 While inside an org file, simply run `SPC m e` or `M-x org-export-dispatch`
251 to open the export menu. This menu will show all options and ask you to
252 select an option. If you want to export to HTML, simply press `h` and then
253 `H` (As HTML buffer), `h` (As HTML file), or `o` (As HTML file and open).
254
2552. Projects
256
257 Some publishing options are easier with a defined project in Emacs. To
258 create a project within Emacs, I use two methods:
259
260 1. Add the project via the projectile command `SPC p a`. Does not always
261 work for me.
262 2. Add an empty `.projectile` file in the project root.
263
264 Once a project has been created, you can create custom publishing actions
265 within your `~/.doom.d/config.el` file. For example, here's a test project I
266 created to try and convert this blog to org-mode recently.
267
268 ```lisp
269 ;; org-publish
270 (require 'ox-publish)
271
272 (defun my/org-sitemap-date-entry-format (entry style project) "Format ENTRY in
273 org-publish PROJECT Sitemap format ENTRY ENTRY STYLE format that includes
274 date." (let ((filename (org-publish-find-title entry project))) (if (= (length
275 filename) 0) (format "*%s*" entry) (format "{{{timestamp(%s)}}}
276 [[file:%s][%s]]" (format-time-string "%Y-%m-%d" (org-publish-find-date entry
277 project)) entry filename))))
278
279 (setq org-export-global-macros '(("timestamp" . "@@html:<time datetime='[$1]'
280 class='timestamp'>[$1]</time>@@")))
281
282 (setq org-publish-project-alist
283 `(("blog"
284 :base-directory "~/Source/cleberg.net/"
285 :base-extension "org"
286 :recursive t
287 :publishing-directory "~/Source/cleberg.net/public/"
288 :publishing-function org-html-publish-to-html
289 ;; HTML5
290 :html-doctype "html5"
291 :html-html5-fancy t
292 ;; Disable some Org's HTML defaults
293 :html-head-include-scripts nil
294 :html-head-include-default-style nil
295 :section-numbers nil
296 :with-title nil
297 ;; Sitemap
298 :auto-sitemap t
299 :sitemap-title: "Sitemap"
300 :sitemap-sort-files anti-chronologically
301 ; :sitemap-function my/org-sitemap-date-entry-format
302 ;; Customize HTML output
303 :html-divs ((preamble "header" "preamble")
304 (content "main" "content")
305 (postamble "footer" "postamble"))
306 :html-head "<meta name='theme-color' content='#111' media='(prefers-color-scheme: dark)'>
307 <meta name='theme-color' content='#fff' media='(prefers-color-scheme: light)'>
308 <link rel='stylesheet' href='/syntax-theme-dark.css' media='(prefers-color-scheme: dark)'>
309 <link rel='stylesheet' href='/syntax-theme-light.css' media='(prefers-color-scheme: light)'>
310 <link rel='stylesheet' href='/styles.css' type='text/css'>"
311 :html-preamble "<nav class='site-nav' aria-label='site-nav' role='navigation'>
312 <ul>
313 <li><a href='/'>Home</a></li>
314 <li><a href='/blog/'>Blog</a></li>
315 <li><a href='/services/'>Services</a></li>
316 <li><a href='/wiki/'>Wiki</a></li>
317 </ul></nav>
318 <h1>%t</h1>
319 <time datetime='%d'>%d</time>"
320 :html-postamble "
321 <p>Last build: %T</p>
322 <p>Created with %c</p>"
323 )
324
325 ("static"
326 :base-directory "~/Source/cleberg.net/static/"
327 :base-extension "css\\|txt\\|jpg\\|gif\\|png"
328 :recursive t
329 :publishing-directory "~/Source/cleberg.net/public/"
330 :publishing-function org-publish-attachment)
331
332 ("cleberg.net" :components ("blog" "static"))))
333 ```
334
335## General Thoughts
336
337I have enjoyed Doom Emacs (far more than GNU Emacs) and will likely continue to
338use it as my main editor for the time being. Org-Mode is certainly the largest
339factor here, as I far prefer it over Markdown due to its inherent features and
340detailed markup options. However, working with org-mode on iOS has been a pain
341and I will have to see if there's an easier way to resolve those issues or if
342going back to separate Markdown, Reminders, and Calendar apps is easier to work
343with than an all-in-one org solution.
content/blog/2024-03-13-doom-emacs.org added +360
@@ -0,0 +1,360 @@
1#+date: <2024-03-13>
2#+title: Doom Emacs & Org-Mode
3#+description:
4
5
6** Screenshots
7
8These screenshots are showing a project opened with projectile, a
9treemacs side pane open with the project contents, multiple buffers
10tiled next to each other, and the help pane open at the bottomm.
11
12The themes are =doom-homage-white= and =doom-homage-black=.
13
14#+caption: Doom Emacs Light Mode
15[[https://img.cleberg.net/blog/20240314-doom-emacs/light.png]]
16
17#+caption: Doom Emacs Dark Mode
18[[https://img.cleberg.net/blog/20240314-doom-emacs/dark.png]]
19
20** Getting Started
21
22I have been switching back and forth between
23[[https://en.wikipedia.org/wiki/Markdown][markdown]] and
24[[https://en.wikipedia.org/wiki/Org-mode][org-mode]] recently for my
25personal note taking, wiki, and even this blog. As a result, I have been
26stumbling further into the world of Emacs and found myself at a point
27where I now prefer to do most of my basic editing within Emacs.
28
29I'll leave the markdown vs. org-mode debate for another post, but I love
30org-mode's extensibility and interactive nature within Emacs, but it
31becomes very unwieldy in any other client implementation of org-mode -
32especially on iOS. On the flip side, markdown is limited in
33functionality and fractured into different standards, but it's simple
34and popular enough that there are a plethora of great clients to choose
35from that will get the job done.
36
37For now, I want to focus on how I have been using Emacs and some of the
38things that would have helped me learn it faster had I known where to
39start.
40
41*** Installation
42
43This post focuses on [[https://github.com/doomemacs/doomemacs][Doom
44Emacs]], which is an Emacs framework that provides an alternative
45experience to the vanilla [[https://www.gnu.org/software/emacs/][GNU
46Emacs]].
47
48The
49[[https://github.com/doomemacs/doomemacs/blob/master/docs/getting_started.org][Getting
50Start Guide]] has an extremely detailed walkthrough of installation for
51all systems, so please refer to that guide for up-to-date instructions.
52
53I chose to install on macOS, using the Homebrew option with the
54=railwaycat/emacsmacport= version of Emacs.
55
56Once the program is installed, you can run the program by typing =emacs=
57in a terminal. If you installed a version of Emacs that supports both a
58GUI and TUI, you will have to run =emacs -nw= to get the TUI instead of
59the default GUI.
60
61*** Configuration
62
63Once installed, you can configure Doom by editing the files within the
64=~/.doom.d/= directory. This directory holds four files:
65
661. =config.el= - Personal configuration file
672. =custom.el= - Custom set variables
683. =init.el= - Doom modules and load order, must run =doom sync= after
69 modifying
704. =packages.el= - Declare packages to install in this file, then run
71 =doom sync= to install
72
73I only needed a few customizations for my configuration, so I'll list
74them below.
75
76#+begin_src lisp
77;; ~/.doom.d/config.el
78(setq doom-theme 'doom-homage-black)
79(setq display-line-numbers-type t)
80(setq org-directory "~/Documents/Notes/")
81
82;; lengthy org-publish directives at the bottom of the file
83#+end_src
84
85#+begin_src lisp
86;; ~/.doom.d/init.el
87(doom! :input
88 :completion
89 company ; the ultimate code completion backend
90 vertico ; the search engine of the future
91
92 :ui
93 doom ; what makes DOOM look the way it does
94 doom-dashboard ; a nifty splash screen for Emacs
95 (emoji +unicode) ; 🙂
96 hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
97 minimap ; show a map of the code on the side
98 modeline ; snazzy, Atom-inspired modeline, plus API
99 ophints ; highlight the region an operation acts on
100 (popup +defaults) ; tame sudden yet inevitable temporary windows
101 tabs ; a tab bar for Emacs
102 treemacs ; a project drawer, like neotree but cooler
103 (vc-gutter +pretty) ; vcs diff in the fringe
104 vi-tilde-fringe ; fringe tildes to mark beyond EOB
105 workspaces ; tab emulation, persistence & separate workspaces
106
107 :editor
108 (evil +everywhere); come to the dark side, we have cookies
109 file-templates ; auto-snippets for empty files
110 fold ; (nigh) universal code folding
111 snippets ; my elves. They type so I don't have to
112
113 :emacs
114 dired ; making dired pretty [functional]
115 electric ; smarter, keyword-based electric-indent
116 undo ; persistent, smarter undo for your inevitable mistakes
117 vc ; version-control and Emacs, sitting in a tree
118
119 :term
120 term ; basic terminal emulator for Emacs
121
122 :checkers
123 syntax ; tasing you for every semicolon you forget
124
125 :tools
126 (eval +overlay) ; run code, run (also, repls)
127 lookup ; navigate your code and its documentation
128 magit ; a git porcelain for Emacs
129
130 :os
131 (:if (featurep :system 'macos) macos) ; improve compatibility with macOS
132
133 :lang
134 common-lisp ; if you've seen one lisp, you've seen them all
135 emacs-lisp ; drown in parentheses
136 markdown ; writing docs for people to ignore
137 org ; organize your plain life in plain text
138 python ; beautiful is better than ugly
139 sh ; she sells {ba,z,fi}sh shells on the C xor
140
141 :app
142 irc ; how neckbeards socialize
143 (rss +org) ; emacs as an RSS reader
144
145 (default +bindings +smartparens))
146#+end_src
147
148If you're editing these files within Doom directly, remember to run
149=SPC h r r= to reload the configuration. Also remember to run
150=doom sync= for any changes to the =init.el= or =packages.el= files.
151
152** Basic Functionality
153
154I kept a cheat sheet note open at first with all of the basic functions
155typed out, copied as I went through the tutorial. After a little while,
156I no longer needed it. I highly recommend writing down the most
157applicable shortcuts for your preferred functionality and refer back to
158it until you've memorized it.
159
160Memorizing the shortcuts will differ based on the type of Emacs
161framework being used. Personally, migrating from vanilla Emacs to Doom
162Emacs simplified everything by a large factor and instantly enabled me
163to start working on my projects, eliminating most of the hurdles I was
164running into. The vanilla emacs hotkeys became obnoxious and I actually
165stopped using Emacs entirely for about a month before trying Doom.
166
167For me, the first logical step is to interact with the local filesystem.
168To do this, I needed to know how to open directories, open files, save
169files, discard changes, close files, and switch between open files. Here
170are some example shortcuts I've written down in order to accomplish
171file-based actions.
172
173| Doom Hotkey | Emacs Hotkey | Description |
174|-----------------+---------------+----------------------------------------|
175| =SPC := | =C-x= | Run functions |
176| =SPC f f= | =C-x f= | Open file in buffer |
177| =SPC f d= | =C-x d= | Open directory with =dired= |
178| =i= | =C-x C-q= | Edit current buffer (insert mode) |
179| =q= | =C-x C-q= | Quit out of insert mode |
180| =SPC f s= | =C-x s= | Save current buffer |
181| =SPC b k= | =C-x k= | Kill current buffer |
182| =SPC w h/j/k/l= | =C-x o=[fn:2] | Move left/down/up/right to next buffer |
183
184In general, when in Doom, you can press =SPC= and wait a second for the
185help pane to appear with all available hotkey options. For example, you
186can press =SPC=, wait for the help pane, and then select a key such as
187=g= to enter the git help pane and explore further command options.
188
189** Editing
190
191Next in my process is to dive into editing for any languages I'm
192currently using. In this post, I will just cover Markdown and Org-Mode
193but I have also been slowly adoping some Python and general web dev
194tools as well.
195
196*** Markdown
197
198#+caption: Markdown Preview
199[[https://img.cleberg.net/blog/20240314-doom-emacs/markdown.png]]
200
201Markdown is fairly simple as the syntax is limited, so just make sure
202the =~/.doom.d/init.el= includes the =markdown= declaration in the
203=:lang= section.
204
205This package includes the following hotkey menus. The insert and toggle
206menu expands further, allowing you to insert various markdown elements
207and toggle things like link hiding.
208
209| Doom Hotkey | Function |
210|------------------------------+--------------------------|
211| =SPC m '= | markdown-edit-code-block |
212| =SPC m e= | markdown-export |
213| =SPC m i= | +insert |
214| =SPC m o= | markdown-open |
215| =SPC m p= | markdown-preview |
216| =SPC m t= | +toggle |
217| =SPC : markdown-table-align= | markdown-table-align |
218
219*** Org-Mode
220
221#+caption: Org-Mode Preview
222[[https://img.cleberg.net/blog/20240314-doom-emacs/org.png]]
223
224Similar to the markdown section above, ensure that the
225=~/.doom.d/init.el= includes the =org= declaration in the =:lang=
226section.
227
228There are a few hot keys, but a quick search with =SPC : org= shows that
229there are 865 possible org-related functions you can run. I won't
230possibly be able to list them all, so I will simply cover a few of the
231basic commands I use myself.
232
233| Doom Hotkey | Function |
234|----------------+---------------------------------------|
235| =SPC m t= | org-todo |
236| =SPC n t= | org-todo-list |
237| =SPC o A= | org-agenda |
238| =SPC X= | org-capture |
239| =SPC m p p= | org-priority |
240| =SPC m d s= | org-schedule |
241| =TAB= | org-cycle |
242| =SHIFT TAB= | Collapse/open all headings in buffer |
243| =M-q= | Format/wrap current section |
244| =M-Left/Right= | Demote/promote current heading |
245| =M-Down/Up= | Shift current heading section down/up |
246
2471. Org-Publish
248
249 Org includes a
250 [[https://orgmode.org/manual/Publishing.html][publishing management
251 system]] by default that allows you to export org files to Org,
252 iCalendar, HTML, LaTex, Markdown, ODT, and Plain Text. Most of these
253 can be exported into another buffer and opened, or simply to an
254 external file.
255
256 While inside an org file, simply run =SPC m e= or
257 =M-x org-export-dispatch= to open the export menu. This menu will
258 show all options and ask you to select an option. If you want to
259 export to HTML, simply press =h= and then =H= (As HTML buffer), =h=
260 (As HTML file), or =o= (As HTML file and open).
261
2622. Projects
263
264 Some publishing options are easier with a defined project in Emacs.
265 To create a project within Emacs, I use two methods:
266
267 1. Add the project via the projectile command =SPC p a=. Does not
268 always work for me.
269 2. Add an empty =.projectile= file in the project root.
270
271 Once a project has been created, you can create custom publishing
272 actions within your =~/.doom.d/config.el= file. For example, here's a
273 test project I created to try and convert this blog to org-mode
274 recently.
275
276 #+begin_src lisp
277 ;; org-publish
278 (require 'ox-publish)
279
280 (defun my/org-sitemap-date-entry-format (entry style project) "Format ENTRY in
281 org-publish PROJECT Sitemap format ENTRY ENTRY STYLE format that includes
282 date." (let ((filename (org-publish-find-title entry project))) (if (= (length
283 filename) 0) (format "*%s*" entry) (format "{{{timestamp(%s)}}}
284 [[file:%s][%s]]" (format-time-string "%Y-%m-%d" (org-publish-find-date entry
285 project)) entry filename))))
286
287 (setq org-export-global-macros '(("timestamp" . "@@html:<time datetime='[$1]'
288 class='timestamp'>[$1]</time>@@")))
289
290 (setq org-publish-project-alist
291 `(("blog"
292 :base-directory "~/Source/cleberg.net/"
293 :base-extension "org"
294 :recursive t
295 :publishing-directory "~/Source/cleberg.net/public/"
296 :publishing-function org-html-publish-to-html
297 ;; HTML5
298 :html-doctype "html5"
299 :html-html5-fancy t
300 ;; Disable some Org's HTML defaults
301 :html-head-include-scripts nil
302 :html-head-include-default-style nil
303 :section-numbers nil
304 :with-title nil
305 ;; Sitemap
306 :auto-sitemap t
307 :sitemap-title: "Sitemap"
308 :sitemap-sort-files anti-chronologically
309 ; :sitemap-function my/org-sitemap-date-entry-format
310 ;; Customize HTML output
311 :html-divs ((preamble "header" "preamble")
312 (content "main" "content")
313 (postamble "footer" "postamble"))
314 :html-head "<meta name='theme-color' content='#111' media='(prefers-color-scheme: dark)'>
315 <meta name='theme-color' content='#fff' media='(prefers-color-scheme: light)'>
316 <link rel='stylesheet' href='/syntax-theme-dark.css' media='(prefers-color-scheme: dark)'>
317 <link rel='stylesheet' href='/syntax-theme-light.css' media='(prefers-color-scheme: light)'>
318 <link rel='stylesheet' href='/styles.css' type='text/css'>"
319 :html-preamble "<nav class='site-nav' aria-label='site-nav' role='navigation'>
320 <ul>
321 <li><a href='/'>Home</a></li>
322 <li><a href='/blog/'>Blog</a></li>
323 <li><a href='/services/'>Services</a></li>
324 <li><a href='/wiki/'>Wiki</a></li>
325 </ul></nav>
326 <h1>%t</h1>
327 <time datetime='%d'>%d</time>"
328 :html-postamble "
329 <p>Last build: %T</p>
330 <p>Created with %c</p>"
331 )
332
333 ("static"
334 :base-directory "~/Source/cleberg.net/static/"
335 :base-extension "css\\|txt\\|jpg\\|gif\\|png"
336 :recursive t
337 :publishing-directory "~/Source/cleberg.net/public/"
338 :publishing-function org-publish-attachment)
339
340 ("cleberg.net" :components ("blog" "static"))))
341 #+end_src
342
343** General Thoughts
344
345I have enjoyed Doom Emacs (far more than GNU Emacs) and will likely
346continue to use it as my main editor for the time being. Org-Mode is
347certainly the largest factor here, as I far prefer it over Markdown due
348to its inherent features and detailed markup options. However, working
349with org-mode on iOS has been a pain and I will have to see if there's
350an easier way to resolve those issues or if going back to separate
351Markdown, Reminders, and Calendar apps is easier to work with than an
352all-in-one org solution.
353
354[fn:1] Doom's evil-window functionality is a bit different from GNU
355 Emacs, but you can always switch to the "other" buffer with
356 =C-x o= or =C-x b= to get a list of buffers to select.
357
358[fn:2] Doom's evil-window functionality is a bit different from GNU
359 Emacs, but you can always switch to the "other" buffer with
360 =C-x o= or =C-x b= to get a list of buffers to select.
content/blog/2024-03-15-self-hosting-ddns-updater.md → content/blog/2024-03-15-self-hosting-ddns-updater.org renamed +91 −86
@@ -1,73 +1,78 @@
1+++ 1#+date: <2024-03-15>
2date = 2024-03-15 2#+title: Self-Hosting DDNS Updater
3title = "Self-Hosting DDNS Updater" 3#+description:
4description = ""
5draft = false
6+++
7 4
8![DDNS Updater Web
9View](https://img.cleberg.net/blog/20240315-ddns-updater/ddns.png)
10 5
11[DDNS Updater](https://github.com/qdm12/ddns-updater) is a program to keep DNS A 6#+caption: DDNS Updater Web View
12and/or AAAA records updated for multiple DNS providers. 7[[https://img.cleberg.net/blog/20240315-ddns-updater/ddns.png]]
13 8
14If you've read any of my other posts, you'll notice that I have been searching 9[[https://github.com/qdm12/ddns-updater][DDNS Updater]] is a program to
15for and using a few different DDNS updating solutions for years. You'll also 10keep DNS A and/or AAAA records updated for multiple DNS providers.
16notice that I love any projects that offer a Docker Compose solution. 11
12If you've read any of my other posts, you'll notice that I have been
13searching for and using a few different DDNS updating solutions for
14years. You'll also notice that I love any projects that offer a Docker
15Compose solution.
17 16
18Luckily, DDNS Upater fits both of these preferences. 17Luckily, DDNS Upater fits both of these preferences.
19 18
20## Installation 19** Installation
21 20
22To get started, always make sure to review the project's 21To get started, always make sure to review the project's
23[README](https://github.com/qdm12/ddns-updater/blob/master/README.md). I'll be 22[[https://github.com/qdm12/ddns-updater/blob/master/README.md][README]].
24documenting my steps below, but they may have changed by the time you read this. 23I'll be documenting my steps below, but they may have changed by the
24time you read this.
25 25
26The first step is to set up the directories and files required for the project. 26The first step is to set up the directories and files required for the
27project.
27 28
28```sh 29#+begin_src sh
29mkdir ~/ddns-updater 30mkdir ~/ddns-updater
30mkdir ~/ddns-updater/data 31mkdir ~/ddns-updater/data
31touch ~/ddns-updater/data/config.json 32touch ~/ddns-updater/data/config.json
32``` 33#+end_src
33 34
34### Configuration 35*** Configuration
35 36
36The main configuration you need to update is the `data/config.json` file. There 37The main configuration you need to update is the =data/config.json=
37is a large list of supported providers in the README, but I'm going to use 38file. There is a large list of supported providers in the README, but
38Cloudflare in this example. 39I'm going to use Cloudflare in this example.
39 40
40```sh 41#+begin_src sh
41nano ~/ddns-updater/data/config.json 42nano ~/ddns-updater/data/config.json
42``` 43#+end_src
43 44
44When setting up the configuration for Cloudflare, you'll need the following: 45When setting up the configuration for Cloudflare, you'll need the
45 46following:
46- Required Parameters 47
47 - `"zone_identifier"` is the Zone ID of your site from the domain overview 48- Required Parameters
48 page 49 - ="zone_identifier"= is the Zone ID of your site from the domain
49 - `"host"` is your host and can be `"@"`, a subdomain or the wildcard 50 overview page
50 `"*"`. See [this issue comment for 51 - ="host"= is your host and can be ="@"=, a subdomain or the wildcard
51 context](https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949). 52 ="*"=. See
52 - `"ttl"` integer value for record TTL in seconds (specify 1 for 53 [[https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949][this
53 automatic) 54 issue comment for context]].
54 - One of the following ([how to find API 55 - ="ttl"= integer value for record TTL in seconds (specify 1 for
55 keys](https://developers.cloudflare.com/fundamentals/api/get-started/)): 56 automatic)
56 - Email `"email"` and Global API Key `"key"` 57 - One of the following
57 - User service key `"user_service_key"` 58 ([[https://developers.cloudflare.com/fundamentals/api/get-started/][how
58 - API Token `"token"`, configured with DNS edit permissions for your 59 to find API keys]]):
59 DNS name's zone 60 - Email ="email"= and Global API Key ="key"=
60- Optional Parameters 61 - User service key ="user_service_key"=
61 - `"proxied"` can be set to `true` to use the proxy services of Cloudflare 62 - API Token ="token"=, configured with DNS edit permissions for your
62 - `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or 63 DNS name's zone
63 `ipv4 or ipv6` (update one of the two, depending on the public ip 64- Optional Parameters
64 found). It defaults to `ipv4 or ipv6`. 65 - ="proxied"= can be set to =true= to use the proxy services of
65 - `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can 66 Cloudflare
66 be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it 67 - ="ip_version"= can be =ipv4= (A records), or =ipv6= (AAAA records)
67 defaults to no suffix and the raw public IPv6 address obtained is used 68 or =ipv4 or ipv6= (update one of the two, depending on the public
68 in the record updating. 69 ip found). It defaults to =ipv4 or ipv6=.
69 70 - ="ipv6_suffix"= is the IPv6 interface identifier suffix to use. It
70```conf 71 can be for example =0:0:0:0:72ad:8fbb:a54e:bedd/64=. If left empty,
72 it defaults to no suffix and the raw public IPv6 address obtained is
73 used in the record updating.
74
75#+begin_src conf
71{ 76{
72 "settings": [ 77 "settings": [
73 { 78 {
@@ -83,12 +88,12 @@ When setting up the configuration for Cloudflare, you'll need the following:
83 } 88 }
84 ] 89 ]
85} 90}
86``` 91#+end_src
87 92
88Once you have configured the provider of your choice, correct the file and 93Once you have configured the provider of your choice, correct the file
89directory permissions and ownership. 94and directory permissions and ownership.
90 95
91```sh 96#+begin_src sh
92cd ~/ddns_updater 97cd ~/ddns_updater
93# Owned by user ID of Docker container (1000) 98# Owned by user ID of Docker container (1000)
94chown -R 1000 data 99chown -R 1000 data
@@ -96,18 +101,18 @@ chown -R 1000 data
96chmod 700 data 101chmod 700 data
97# read access only 102# read access only
98chmod 400 data/config.json 103chmod 400 data/config.json
99``` 104#+end_src
100 105
101### Docker Compose 106*** Docker Compose
102 107
103After creating the project structure, let's create the `docker-compose.yml` 108After creating the project structure, let's create the
104file. 109=docker-compose.yml= file.
105 110
106```sh 111#+begin_src sh
107nano ~/ddns_-pdater/docker-compose.yml 112nano ~/ddns_-pdater/docker-compose.yml
108``` 113#+end_src
109 114
110```config 115#+begin_src config
111version: "3.7" 116version: "3.7"
112services: 117services:
113 ddns-updater: 118 ddns-updater:
@@ -143,34 +148,34 @@ services:
143 - LOG_CALLER=hidden 148 - LOG_CALLER=hidden
144 - SHOUTRRR_ADDRESSES= 149 - SHOUTRRR_ADDRESSES=
145 restart: always 150 restart: always
146``` 151#+end_src
147 152
148After configuring your preferences in the `docker-compose.yml`, launch the 153After configuring your preferences in the =docker-compose.yml=, launch
149container. 154the container.
150 155
151```sh 156#+begin_src sh
152cd ~/ddns-updater 157cd ~/ddns-updater
153sudo docker-compose up -d 158sudo docker-compose up -d
154``` 159#+end_src
155 160
156If you've launched this on your local machine, you can launch `localhost:8097` 161If you've launched this on your local machine, you can launch
157in your browser to see the results. 162=localhost:8097= in your browser to see the results.
158 163
159### Nginx Reverse Proxy 164*** Nginx Reverse Proxy
160 165
161If you launched this service on a server, other machine, or just want to access 166If you launched this service on a server, other machine, or just want to
162it remotely via a domain name, you can use Nginx as a reverse proxy to expose 167access it remotely via a domain name, you can use Nginx as a reverse
163the service publicly. 168proxy to expose the service publicly.
164 169
165Start by creating the Nginx configuration file. 170Start by creating the Nginx configuration file.
166 171
167```sh 172#+begin_src sh
168sudo nano /etc/nginx/sites-available/ddns 173sudo nano /etc/nginx/sites-available/ddns
169``` 174#+end_src
170 175
171Here's a basic example that should work properly. 176Here's a basic example that should work properly.
172 177
173```conf 178#+begin_src conf
174server { 179server {
175 # If using 443, remember to include your ssl_certificate 180 # If using 443, remember to include your ssl_certificate
176 # and ssl_certificate_key 181 # and ssl_certificate_key
@@ -186,12 +191,12 @@ server {
186 # see the full example below if necessary 191 # see the full example below if necessary
187 } 192 }
188} 193}
189``` 194#+end_src
190 195
191Here's a full example that uses my Authelia authentication service to require 196Here's a full example that uses my Authelia authentication service to
192authentication before someone can access the web page. 197require authentication before someone can access the web page.
193 198
194```conf 199#+begin_src conf
195server { 200server {
196 if ($host ~ ^[^.]+\.example\.com$) { 201 if ($host ~ ^[^.]+\.example\.com$) {
197 return 301 https://$host$request_uri; 202 return 301 https://$host$request_uri;
@@ -298,13 +303,13 @@ server {
298 # real_ip_recursive on; 303 # real_ip_recursive on;
299 } 304 }
300} 305}
301``` 306#+end_src
302 307
303When complete, simply link the file and restart the web server. 308When complete, simply link the file and restart the web server.
304 309
305```sh 310#+begin_src sh
306sudo ln -s /etc/nginx/sites-available/ddns /etc/nginx/sites-enabled/ddns 311sudo ln -s /etc/nginx/sites-available/ddns /etc/nginx/sites-enabled/ddns
307sudo systemctl restart nginx.service 312sudo systemctl restart nginx.service
308``` 313#+end_src
309 314
310Your ddns-updater service will now be available via `ddns.example.com`! 315Your ddns-updater service will now be available via =ddns.example.com=!
content/blog/2024-03-29-org-blog.md → content/blog/2024-03-29-org-blog.org renamed +109 −104
@@ -1,80 +1,82 @@
1+++ 1#+date: <2024-03-29>
2date = 2024-03-29 2#+title: Blogging in Org-Mode
3title = "Blogging in Org-Mode" 3#+description:
4description = "" 4
5draft = false 5
6+++ 6First and foremost, apologies to those who subscribe via RSS as I know
7 7that my feed duplicated itself when I moved this blog over to org-mode
8First and foremost, apologies to those who subscribe via RSS as I know that my 8last night.
9feed duplicated itself when I moved this blog over to org-mode last night. 9
10 10This post focuses specifically on the configuration and tools I use to
11This post focuses specifically on the configuration and tools I use to blog from 11blog from Emacs with Org-Mode and does not focus on Emacs or Org-Mode
12Emacs with Org-Mode and does not focus on Emacs or Org-Mode themselves. Refer to 12themselves. Refer to the post I wrote about
13the post I wrote about [Doom Emacs & 13[[file:///blog/doom-emacs-org-mode.html][Doom Emacs & Org-Mode]] for
14Org-Mode](file:///blog/doom-emacs-org-mode.html) for more information about my 14more information about my base Emacs configuration.
15base Emacs configuration. 15
16 16* Weblorg
17# Weblorg 17
18 18The first step in blogging with Org-Mode is to choose a method to
19The first step in blogging with Org-Mode is to choose a method to convert the 19convert the source files to HTML and publish them. The Worg site
20source files to HTML and publish them. The Worg site maintains a nice list of 20maintains a nice list of
21[Blogs and Wikis with Org](https://orgmode.org/worg/org-blog-wiki.html), but the 21[[https://orgmode.org/worg/org-blog-wiki.html][Blogs and Wikis with
22tools are inevitably different and opinionated, so you'll need to find what 22Org]], but the tools are inevitably different and opinionated, so you'll
23works for you. 23need to find what works for you.
24 24
25I tried using Jekyll, Hugo, ox-hugo, Nikola, Blorg, org-static-blog, and the 25I tried using Jekyll, Hugo, ox-hugo, Nikola, Blorg, org-static-blog, and
26native org-publish functions before finally settling on Weblorg. For one reason 26the native org-publish functions before finally settling on Weblorg. For
27or another, the other solutions were a drastic step down from my previous 27one reason or another, the other solutions were a drastic step down from
28workflow that used [Zola](https://www.getzola.org/) with Markdown content. 28my previous workflow that used [[https://www.getzola.org/][Zola]] with
29 29Markdown content.
30[Weblorg](https://github.com/emacs-love/weblorg) is a static site generator for 30
31[org-mode](https://orgmode.org/), built for use within 31[[https://github.com/emacs-love/weblorg][Weblorg]] is a static site
32[Emacs](https://www.gnu.org/software/emacs/). Since it's written in Emacs Lisp, 32generator for [[https://orgmode.org/][org-mode]], built for use within
33there's no need to install other languages or frameworks to get started. More 33[[https://www.gnu.org/software/emacs/][Emacs]]. Since it's written in
34than that, you can write in any editor you please and simply invoke the Emacs 34Emacs Lisp, there's no need to install other languages or frameworks to
35build process with the `--script` parameter instead of requiring you to blog 35get started. More than that, you can write in any editor you please and
36inside Emacs. 36simply invoke the Emacs build process with the =--script= parameter
37 37instead of requiring you to blog inside Emacs.
38## Installation 38
39 39** Installation
40The [Getting Started](https://emacs.love/weblorg/doc/index.html) page details 40
41broad installation requirements. I am using Doom Emacs on macOS, which requires 41The [[https://emacs.love/weblorg/doc/index.html][Getting Started]] page
42you to add the package to the `~/.doom.d/packages.el` file and configure the 42details broad installation requirements. I am using Doom Emacs on macOS,
43`publish.el` file slightly differently. 43which requires you to add the package to the =~/.doom.d/packages.el=
44 44file and configure the =publish.el= file slightly differently.
45To start, add the `htmlize` and `weblorg` packages to Doom, sync the changes, 45
46and reload. 46To start, add the =htmlize= and =weblorg= packages to Doom, sync the
47 47changes, and reload.
48```sh 48
49#+begin_src sh
49nano ~/.doom.d/packages.el 50nano ~/.doom.d/packages.el
50``` 51#+end_src
51 52
52```lisp 53#+begin_src lisp
53(package! htmlize) 54(package! htmlize)
54(package! weblorg) 55(package! weblorg)
55``` 56#+end_src
56 57
57```sh 58#+begin_src sh
58doom sync 59doom sync
59``` 60#+end_src
60 61
61Either re-open Emacs or hit `SPC h r r` to reload the changes. 62Either re-open Emacs or hit =SPC h r r= to reload the changes.
62 63
63## Configuration 64** Configuration
64 65
65Now that I've installed weblorg, I need to configure the project. I'll start by 66Now that I've installed weblorg, I need to configure the project. I'll
66navigating to my site's source code and creating a `publish.el` file. 67start by navigating to my site's source code and creating a =publish.el=
68file.
67 69
68```sh 70#+begin_src sh
69cd ~/Source/cleberg.net && nano publish.el 71cd ~/Source/cleberg.net && nano publish.el
70``` 72#+end_src
71 73
72Since I'm using Doom, Emacs will not automatically load the packages I need 74Since I'm using Doom, Emacs will not automatically load the packages I
73later in the build process. To compensate, my `publish.el` file needs to 75need later in the build process. To compensate, my =publish.el= file
74explicitly tell Emacs where Doom stores the `htmlize`, `weblorg`, and 76needs to explicitly tell Emacs where Doom stores the =htmlize=,
75`templatel` packages. 77=weblorg=, and =templatel= packages.
76 78
77```lisp 79#+begin_src lisp
78;; explicity load packages since I'm using Doom Emacs 80;; explicity load packages since I'm using Doom Emacs
79(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/emacs-htmlize") 81(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/emacs-htmlize")
80(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/weblorg") 82(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/weblorg")
@@ -164,9 +166,9 @@ explicitly tell Emacs where Doom stores the `htmlize`, `weblorg`, and
164 :name "rss" 166 :name "rss"
165 :input-pattern "content/blog/*.org" 167 :input-pattern "content/blog/*.org"
166 :input-aggregate #'weblorg-input-aggregate-all-desc 168 :input-aggregate #'weblorg-input-aggregate-all-desc
167 :template "atom.xml" 169 :template "feed.xml"
168 :output ".build/atom.xml" 170 :output ".build/feed.xml"
169 :url "/atom.xml") 171 :url "/feed.xml")
170 172
171;; route for static assets that also copies files to .build directory 173;; route for static assets that also copies files to .build directory
172(weblorg-copy-static 174(weblorg-copy-static
@@ -175,19 +177,21 @@ explicitly tell Emacs where Doom stores the `htmlize`, `weblorg`, and
175 177
176;; fire the engine and export all the files declared in the routes above 178;; fire the engine and export all the files declared in the routes above
177(weblorg-export) 179(weblorg-export)
178``` 180#+end_src
179 181
180# Project 182* Project
181 183
182## Structure 184** Structure
183 185
184The project structure for weblorg is highly customizable and the main 186The project structure for weblorg is highly customizable and the main
185restriction is that the `publish.el` file must point to the correct paths. 187restriction is that the =publish.el= file must point to the correct
188paths.
186 189
187For my blog, I prefer to keep the blog content out of the top-level directory. 190For my blog, I prefer to keep the blog content out of the top-level
188This results in the following structure (shortened for brevity): 191directory. This results in the following structure (shortened for
192brevity):
189 193
190```txt 194#+begin_src txt
191.build/ 195.build/
192content/ 196content/
193 blog/ 197 blog/
@@ -211,55 +215,56 @@ theme/
211 wiki.html 215 wiki.html
212build.sh 216build.sh
213publish.el 217publish.el
214``` 218#+end_src
215 219
216This is simply my preferred structure and you can alter it to fit your needs. 220This is simply my preferred structure and you can alter it to fit your
217The key here really is that you can customize at will, as long as the 221needs. The key here really is that you can customize at will, as long as
218`publish.el` file matches. 222the =publish.el= file matches.
219 223
220## Build & Deploy 224** Build & Deploy
221 225
222Once you're content with the status of the project, you're ready to build and 226Once you're content with the status of the project, you're ready to
223deploy the blog. 227build and deploy the blog.
224 228
225My process utilizes a `build.sh` script that combines the steps I take every 229My process utilizes a =build.sh= script that combines the steps I take
226time. 230every time.
227 231
228```sh 232#+begin_src sh
229touch build.sh && chmod +x build.sh && nano build.sh 233touch build.sh && chmod +x build.sh && nano build.sh
230``` 234#+end_src
231 235
232Within this script, I do the following: 236Within this script, I do the following:
233 237
2341. Remove any files within the `.build` directory that I use to store published 2381. Remove any files within the =.build= directory that I use to store
235 files. 239 published files.
2362. Set the environment variable to `prod` to ensure the `base_url` matches my 2402. Set the environment variable to =prod= to ensure the =base_url=
237 configuration in `publish.el`. 241 matches my configuration in =publish.el=.
2383. Build the site with Emacs & `publish.el`. 2423. Build the site with Emacs & =publish.el=.
2394. Use `scp` to copy files to my site's public directory on my server. 2434. Use =scp= to copy files to my site's public directory on my server.
240 244
241```sh 245#+begin_src sh
242rm -rf .build/* && \ 246rm -rf .build/* && \
243ENV=prod emacs --script publish.el && \ 247ENV=prod emacs --script publish.el && \
244scp -r .build/* ubuntu:/var/www/cleberg.net/ 248scp -r .build/* ubuntu:/var/www/cleberg.net/
245``` 249#+end_src
246 250
247### Time to Build 251*** Time to Build
248 252
249My only current complaints are: 253My only current complaints are:
250 254
2511. Errors messages are not helpful. It takes work to determine what the error is 2551. Errors messages are not helpful. It takes work to determine what the
252 and where it's coming from. I generally have to sit and watch the build 256 error is and where it's coming from. I generally have to sit and
253 process to see the file that weblorg pubslishes right before the error 257 watch the build process to see the file that weblorg pubslishes right
254 occurred. 258 before the error occurred.
2552. The build process re-builds every single file on each run, which takes a long 2592. The build process re-builds every single file on each run, which
256 time for a blog of my size. See below for the last time I measured. 260 takes a long time for a blog of my size. See below for the last time
261 I measured.
257 262
258```sh 263#+begin_src sh
259> time ./build.sh 264> time ./build.sh
260 265
261./build.sh 35.46s user 0.59s system 85% cpu 41.965 total 266./build.sh 35.46s user 0.59s system 85% cpu 41.965 total
262``` 267#+end_src
263 268
264Overall, I have thoroughly enjoyed using weblog and will continue to use it 269Overall, I have thoroughly enjoyed using weblog and will continue to use
265going forward until I find something better. 270it going forward until I find something better.
content/blog/2024-04-06-convert-onenote-to-markdown.md deleted −95
@@ -1,95 +0,0 @@
1+++
2date = 2024-04-06
3title = "Convert OneNote to Markdown or Org-Mode"
4description = ""
5draft = false
6+++
7
8If you're looking to convert your OneNote content to another format, such as
9Markdown or Org-Mode, you're in luck. I use a solution that doesn't require
10other programs, such as Evernote or Notion. Personally, I used this solution on
11a managed corporate laptop that doesn't allow installation of other programs
12like these.
13
14This solution uses OneNote and Pandoc on Windows 10.
15
16# Export OneNote Content to Word
17
18To start, export any pages or tabs from OneNote to the Word format (`.docx`):
19
201. Open OneNote desktop.
212. Select `File` and then `Export`.
223. Select the scope of content to export, such as `Tab` or `Page`.
234. Name and save the file in an easy to remember location. I recommend your
24 Downloads or Desktop folder.
25
26See below for a visual walkthrough of the export process.
27
28# Download Pandoc
29
30Start by downloading Pandoc from their [GitHub
31releases](https://github.com/jgm/pandoc/releases) page. I cannot install `.msi`
32files on my corporate laptop, so I downloaded the
33`pandoc-3.1.12.3-windows-x86_64.zip` file, which contains a simple `.exe` file
34that you do not need to install - you will simply run it from the command line
35below.
36
37Once downloaded, unzip the archive and move the `pandoc.exe` file to the same
38folder where your Word documents were saved above. If you prefer, you can move
39this file to an easier location, such as `C:\Users\youruser\Downloads`.
40
41# Convert Word to Markdown
42
43In this example, I will be converting the Word documents to Markdown, but Pandoc
44supports [a ton of different formats for
45conversion](https://github.com/jgm/pandoc?tab=readme-ov-file#the-universal-markup-converter).
46Choose the format you prefer and then modify the following commands as needed.
47
48To perform the conversion, open the Command Prompt. If you can't find it, open
49the start menu and search for it.
50
51Within the command prompt, navigate to the directory where you stored the
52`pandoc.exe` file and the Word documents.
53
54```ps1
55cd "C:\Users\yourusername\Downloads"
56```
57
58You can verify that you're in the correct directory with the `dir` command.
59
60```ps1
61dir
62```
63
64Once you have verified that you have the command prompt open in the correct
65directory with the `pandoc.exe` and the Word documents, you can run the
66following loop to convert all Word documents to Markdown.
67
68```ps1
69for %f in (*.docx) do (pandoc.exe --extract-media=. --wrap=preserve "%f" -o "%f.md")
70```
71
72This loop will perform the following actions:
73
741. Find all documents matching the pattern `*.docx`, which means all Word
75 documents ending with that file extension.
762. Iterate through all files found in step 1.
773. For each file, perform the pandoc command.
784. Within the pandoc command, `--extract-media` saves all media found in the
79 files to the current folder, with pandoc automatically creating a `media`
80 subfolder to hold all images.
815. Within the pandoc command, `--wrap=preserve` will attempt to prseerve the
82 wrapping from the source document.
836. Within the pandoc command, the final step is to specify the output path with
84 `-o`. This option adds the `.md` file extension to recognize the output files
85 as Markdown files.
86
87If you want to export to another format, simply specify the `-f=`/`--from=` and
88`-t=`/`--to=` options.
89
90For example, you can convert the Word document to org-mode. You can also convert
91to one format and subsequently convert to other formats as needed.
92
93```ps1
94pandoc.exe -f docx -t org file.docx
95```
content/blog/2024-04-06-convert-onenote-to-markdown.org added +98
@@ -0,0 +1,98 @@
1#+date: <2024-04-06>
2#+title: Convert OneNote to Markdown or Org-Mode
3#+description:
4
5
6If you're looking to convert your OneNote content to another format,
7such as Markdown or Org-Mode, you're in luck. I use a solution that
8doesn't require other programs, such as Evernote or Notion. Personally,
9I used this solution on a managed corporate laptop that doesn't allow
10installation of other programs like these.
11
12This solution uses OneNote and Pandoc on Windows 10.
13
14* Export OneNote Content to Word
15
16To start, export any pages or tabs from OneNote to the Word format
17(=.docx=):
18
191. Open OneNote desktop.
202. Select =File= and then =Export=.
213. Select the scope of content to export, such as =Tab= or =Page=.
224. Name and save the file in an easy to remember location. I recommend
23 your Downloads or Desktop folder.
24
25See below for a visual walkthrough of the export process.
26
27* Download Pandoc
28
29Start by downloading Pandoc from their
30[[https://github.com/jgm/pandoc/releases][GitHub releases]] page. I
31cannot install =.msi= files on my corporate laptop, so I downloaded the
32=pandoc-3.1.12.3-windows-x86_64.zip= file, which contains a simple
33=.exe= file that you do not need to install - you will simply run it
34from the command line below.
35
36Once downloaded, unzip the archive and move the =pandoc.exe= file to the
37same folder where your Word documents were saved above. If you prefer,
38you can move this file to an easier location, such as
39=C:\Users\youruser\Downloads=.
40
41* Convert Word to Markdown
42
43In this example, I will be converting the Word documents to Markdown,
44but Pandoc supports
45[[https://github.com/jgm/pandoc?tab=readme-ov-file#the-universal-markup-converter][a
46ton of different formats for conversion]]. Choose the format you prefer
47and then modify the following commands as needed.
48
49To perform the conversion, open the Command Prompt. If you can't find
50it, open the start menu and search for it.
51
52Within the command prompt, navigate to the directory where you stored
53the =pandoc.exe= file and the Word documents.
54
55#+begin_src ps1
56cd "C:\Users\yourusername\Downloads"
57#+end_src
58
59You can verify that you're in the correct directory with the =dir=
60command.
61
62#+begin_src ps1
63dir
64#+end_src
65
66Once you have verified that you have the command prompt open in the
67correct directory with the =pandoc.exe= and the Word documents, you can
68run the following loop to convert all Word documents to Markdown.
69
70#+begin_src ps1
71for %f in (*.docx) do (pandoc.exe --extract-media=. --wrap=preserve "%f" -o "%f.md")
72#+end_src
73
74This loop will perform the following actions:
75
761. Find all documents matching the pattern =*.docx=, which means all
77 Word documents ending with that file extension.
782. Iterate through all files found in step 1.
793. For each file, perform the pandoc command.
804. Within the pandoc command, =--extract-media= saves all media found in
81 the files to the current folder, with pandoc automatically creating a
82 =media= subfolder to hold all images.
835. Within the pandoc command, =--wrap=preserve= will attempt to prseerve
84 the wrapping from the source document.
856. Within the pandoc command, the final step is to specify the output
86 path with =-o=. This option adds the =.md= file extension to
87 recognize the output files as Markdown files.
88
89If you want to export to another format, simply specify the
90=-f==/=--from== and =-t==/=--to== options.
91
92For example, you can convert the Word document to org-mode. You can also
93convert to one format and subsequently convert to other formats as
94needed.
95
96#+begin_src ps1
97pandoc.exe -f docx -t org file.docx
98#+end_src
content/blog/2024-04-08-docker-local-web-server.md deleted −140
@@ -1,140 +0,0 @@
1+++
2date = 2024-04-08
3title = "Using Docker Desktop to Create a Development Web Server"
4description = ""
5draft = false
6+++
7
8When developing websites locally, I often use a simple Python web server to
9observe the changes.
10
11```sh
12python3 -m http.server
13```
14
15However, this approach has its limitations. For example, this approach does not
16enable logging or access controls. You also need to customize `SimpleHTTPServer`
17if you have advanced needs from your web server.
18
19So, I went to find an alternative that is almost as easy and far more extensible
20and found Docker Desktop to be a suitable replacement.
21
22# Docker Desktop
23
24## Installation
25
26[Docker Desktop](https://www.docker.com/products/docker-desktop/) is a desktop
27GUI for the phenomenal Docker container software. This allows you to manage
28containers, images, volumes, environments, and extensions via an easy-to-use
29GUI.
30
31To install, open the link above and click the `Download` button for your
32platform. I'm going through this process on an M2 Macbook, so I downloaded the
33Mac - Apple Chip version.
34
35Open the installer and follow the installation process until the application
36finishes the installation process.
37
38![Docker Desktop on
39macOS](https://img.cleberg.net/blog/20240408-docker-local-web-server/docker-desktop.png)
40
41## Creating an Nginx Container
42
43I prefer to use the command line to create containers, so the following commands
44will be input via the terminal.
45
46The following command will create a container, using the `nginx` image:
47
481. `-d`: Run this container as a daemon (detach)
492. `-p`: Allocate a port in the format `<external>:<internal>`
503. `-i`: Keep STDIN open even if not attached
514. `-t`: Allocate a pseudo-TTY
525. `-p`: Allocate a port in the format `<external>:<internal>`
536. `--rm`: Remove the container once it's done running
54
55```sh
56docker run -it --rm -d -p 8000:80 --name web nginx
57```
58
59You can navigate to <http://localhost:8000> to see the resulting page.
60
61![Default Nginx
62Container](https://img.cleberg.net/blog/20240408-docker-local-web-server/default-container.png)
63
64## Customizing the Nginx Container
65
66Now that I have a container running the Nginx web server, I need to link some
67volumes so that I can modify the site configuration and provide the web files to
68serve.
69
70Let's start with the new command, which adds two volumes:
71
721. `<your_content>:/usr/share/nginx/html`: This is the directory where you will
73 provide the web pages for the server to serve.
742. `<your_config>:/etc/nginx/conf.d/default.conf`: This is the Nginx
75 configuration file for your site.
76
77To see the updates, you can delete the previous container in the GUI or run
78`docker stop web` to stop the container. Once stopped, you can run the new
79`docker run` command below.
80
81```sh
82docker run -it -d -p 8000:80 --name web -v ~/Source/cleberg.net/.build:/usr/share/nginx/html -v ~/Source/cleberg.net/nginx-config.conf:/etc/nginx/conf.d/default.conf nginx
83```
84
85Here's an example of my development configuration file.
86
87```conf
88# nginx-config.conf
89server {
90 server_name cleberg.net www.cleberg.net;
91
92 root /usr/share/nginx/html;
93 index index.html;
94 autoindex on;
95
96 access_log /var/log/nginx/cleberg.net.access.log;
97 error_log /var/log/nginx/cleberg.net.error.log;
98
99 location / {
100 try_files $uri $uri/ =404;
101 }
102
103 listen [::]:80;
104 listen 80;
105}
106```
107
108![](https://img.cleberg.net/blog/20240408-docker-local-web-server/custom-container.png)
109
110# Customizing Deployment Actions
111
112I am currently blogging with [weblorg](https://emacs.love/weblorg/), which uses
113a custom `publish.el` file to build the static site. Within this file, I have
114configured my deployment process to check for the `ENV` variable in thesh and if
115it's set to `prod`, the script will set the base URLs to `https://cleberg.net`.
116If not, it sets the base URLs to `localhost:8000` (which matches the port used
117in the container above).
118
119Therefore, I have modified my `build.sh` script to build with `localhost` URLs
120if `ENV` is not set to `prod`. It also prevents the build process from sending
121the built files to the production web server.
122
123```sh
124#!/bin/bash
125
126if [ "$ENV" == "prod" ]; then
127 echo "Environment = Production" && \
128 rm -rf .build/* && \
129 emacs --script publish.el && \
130 scp -r .build/* ubuntu:/var/www/cleberg.net/
131else
132 echo "Environment = Development" && \
133 rm -rf .build/* && \
134 emacs --script publish.el
135fi
136```
137
138You can modify the container in numerous ways and this approach allows you to
139create complex scenarios for your web development purposes. I highly recommend
140switching over to a container-based approach for simple, local web development.
content/blog/2024-04-08-docker-local-web-server.org added +139
@@ -0,0 +1,139 @@
1#+date: <2024-04-08>
2#+title: Using Docker Desktop to Create a Development Web Server
3#+description:
4
5
6When developing websites locally, I often use a simple Python web server
7to observe the changes.
8
9#+begin_src sh
10python3 -m http.server
11#+end_src
12
13However, this approach has its limitations. For example, this approach
14does not enable logging or access controls. You also need to customize
15=SimpleHTTPServer= if you have advanced needs from your web server.
16
17So, I went to find an alternative that is almost as easy and far more
18extensible and found Docker Desktop to be a suitable replacement.
19
20* Docker Desktop
21
22** Installation
23
24[[https://www.docker.com/products/docker-desktop/][Docker Desktop]] is a
25desktop GUI for the phenomenal Docker container software. This allows
26you to manage containers, images, volumes, environments, and extensions
27via an easy-to-use GUI.
28
29To install, open the link above and click the =Download= button for your
30platform. I'm going through this process on an M2 Macbook, so I
31downloaded the Mac - Apple Chip version.
32
33Open the installer and follow the installation process until the
34application finishes the installation process.
35
36#+caption: Docker Desktop on macOS
37[[https://img.cleberg.net/blog/20240408-docker-local-web-server/docker-desktop.png]]
38
39** Creating an Nginx Container
40
41I prefer to use the command line to create containers, so the following
42commands will be input via the terminal.
43
44The following command will create a container, using the =nginx= image:
45
461. =-d=: Run this container as a daemon (detach)
472. =-p=: Allocate a port in the format =<external>:<internal>=
483. =-i=: Keep STDIN open even if not attached
494. =-t=: Allocate a pseudo-TTY
505. =-p=: Allocate a port in the format =<external>:<internal>=
516. =--rm=: Remove the container once it's done running
52
53#+begin_src sh
54docker run -it --rm -d -p 8000:80 --name web nginx
55#+end_src
56
57You can navigate to [[http://localhost:8000]] to see the resulting page.
58
59#+caption: Default Nginx Container
60[[https://img.cleberg.net/blog/20240408-docker-local-web-server/default-container.png]]
61
62** Customizing the Nginx Container
63
64Now that I have a container running the Nginx web server, I need to link
65some volumes so that I can modify the site configuration and provide the
66web files to serve.
67
68Let's start with the new command, which adds two volumes:
69
701. =<your_content>:/usr/share/nginx/html=: This is the directory where
71 you will provide the web pages for the server to serve.
722. =<your_config>:/etc/nginx/conf.d/default.conf=: This is the Nginx
73 configuration file for your site.
74
75To see the updates, you can delete the previous container in the GUI or
76run =docker stop web= to stop the container. Once stopped, you can run
77the new =docker run= command below.
78
79#+begin_src sh
80docker run -it -d -p 8000:80 --name web -v ~/Source/cleberg.net/.build:/usr/share/nginx/html -v ~/Source/cleberg.net/nginx-config.conf:/etc/nginx/conf.d/default.conf nginx
81#+end_src
82
83Here's an example of my development configuration file.
84
85#+begin_src conf
86# nginx-config.conf
87server {
88 server_name cleberg.net www.cleberg.net;
89
90 root /usr/share/nginx/html;
91 index index.html;
92 autoindex on;
93
94 access_log /var/log/nginx/cleberg.net.access.log;
95 error_log /var/log/nginx/cleberg.net.error.log;
96
97 location / {
98 try_files $uri $uri/ =404;
99 }
100
101 listen [::]:80;
102 listen 80;
103}
104#+end_src
105
106[[https://img.cleberg.net/blog/20240408-docker-local-web-server/custom-container.png]]
107
108* Customizing Deployment Actions
109
110I am currently blogging with [[https://emacs.love/weblorg/][weblorg]],
111which uses a custom =publish.el= file to build the static site. Within
112this file, I have configured my deployment process to check for the
113=ENV= variable in thesh and if it's set to =prod=, the script will set
114the base URLs to =https://cleberg.net=. If not, it sets the base URLs to
115=localhost:8000= (which matches the port used in the container above).
116
117Therefore, I have modified my =build.sh= script to build with
118=localhost= URLs if =ENV= is not set to =prod=. It also prevents the
119build process from sending the built files to the production web server.
120
121#+begin_src sh
122#!/bin/bash
123
124if [ "$ENV" == "prod" ]; then
125 echo "Environment = Production" && \
126 rm -rf .build/* && \
127 emacs --script publish.el && \
128 scp -r .build/* ubuntu:/var/www/cleberg.net/
129else
130 echo "Environment = Development" && \
131 rm -rf .build/* && \
132 emacs --script publish.el
133fi
134#+end_src
135
136You can modify the container in numerous ways and this approach allows
137you to create complex scenarios for your web development purposes. I
138highly recommend switching over to a container-based approach for
139simple, local web development.
content/blog/2024-04-18-mu4e.md → content/blog/2024-04-18-mu4e.org renamed +97 −93
@@ -1,83 +1,82 @@
1+++ 1#+date: <2024-04-18>
2date = 2024-04-18 2#+title: Email in Doom Emacs with Mu4e on macOS
3title = "Email in Doom Emacs with Mu4e on macOS" 3#+description:
4description = ""
5draft = false
6+++
7 4
8This post was heavily inspired by [Email setup in Emacs with Mu4e on
9macOS](https://macowners.club/posts/email-emacs-mu4e-macos/), but with my own
10tweaks for a single-account configuration and some Doom-specific configurations.
11 5
12# Overview 6This post was heavily inspired by
7[[https://macowners.club/posts/email-emacs-mu4e-macos/][Email setup in
8Emacs with Mu4e on macOS]], but with my own tweaks for a single-account
9configuration and some Doom-specific configurations.
13 10
14[Mu4e](https://github.com/emacsmirror/mu4e) is an Emacs-based email client based 11* Overview
15on [mu](https://www.djcbsoftware.nl/code/mu/), an indexer that stores email in
16the Maildir format.
17 12
18This blog post covers the installation of Mu4e in Doom Emacs on macOS. This 13[[https://github.com/emacsmirror/mu4e][Mu4e]] is an Emacs-based email
19guide should be very similar to GNU Emacs and Linux-based systems, with a few 14client based on [[https://www.djcbsoftware.nl/code/mu/][mu]], an indexer
20tweaks required in the various configuration files. 15that stores email in the Maildir format.
21 16
22# Installation 17This blog post covers the installation of Mu4e in Doom Emacs on macOS.
18This guide should be very similar to GNU Emacs and Linux-based systems,
19with a few tweaks required in the various configuration files.
23 20
24## Prerequisites 21* Installation
25 22
26### Create the Folder Structure 23** Prerequisites
24
25*** Create the Folder Structure
27 26
28Start by creating the base folder structure: 27Start by creating the base folder structure:
29 28
30```sh 29#+begin_src sh
31mkdir ~/.maildir 30mkdir ~/.maildir
32mkdir ~/.maildir/example # use whatever name you want to call your email account 31mkdir ~/.maildir/example # use whatever name you want to call your email account
33mkdir ~/.maildir/certificates # used to store system root certificates 32mkdir ~/.maildir/certificates # used to store system root certificates
34``` 33#+end_src
35 34
36### Store Email Account Passwords in macOS Keychain 35*** Store Email Account Passwords in macOS Keychain
37 36
38Next, I will be using the macOS Keychain to store my email account passwords 37Next, I will be using the macOS Keychain to store my email account
39using the command below. 38passwords using the command below.
40 39
41```sh 40#+begin_src sh
42security add-generic-password -s mu4e-example -a you@example.com -w 41security add-generic-password -s mu4e-example -a you@example.com -w
43``` 42#+end_src
44 43
45This will prompt you to input your password twice to confirm. Keep the 44This will prompt you to input your password twice to confirm. Keep the
46`mu4e-example` name in mind, as you will need to reference it later in the IMAP 45=mu4e-example= name in mind, as you will need to reference it later in
47and SMTP configuration files. 46the IMAP and SMTP configuration files.
48 47
49### Store Root Certificates 48*** Store Root Certificates
50 49
51In order to use IMAP and SMTP, we need to provide certificates to the local 50In order to use IMAP and SMTP, we need to provide certificates to the
52services. We will use the macOS defaults for this. 51local services. We will use the macOS defaults for this.
53 52
541. Open `Keychain Access.app`. 531. Open =Keychain Access.app=.
552. Select `System Roots` in the sidebar. 542. Select =System Roots= in the sidebar.
563. Select all items with `CMD + a`. 553. Select all items with =CMD + a=.
574. Export selected items with `SHIFT + CMD + a` to the file 564. Export selected items with =SHIFT + CMD + a= to the file
58 `~/.maildir/certificates/root-certificates.pem`. 57 =~/.maildir/certificates/root-certificates.pem=.
59 58
60### Install Dependencies 59*** Install Dependencies
61 60
62Install `mbsync` (via `isync`) to fetch emails via IMAP, `mu` to index emails, 61Install =mbsync= (via =isync=) to fetch emails via IMAP, =mu= to index
63and `msmtp` to send emails via SMTP. 62emails, and =msmtp= to send emails via SMTP.
64 63
65```sh 64#+begin_src sh
66brew install mu isync msmtp 65brew install mu isync msmtp
67``` 66#+end_src
68 67
69## Installing Mu4e 68** Installing Mu4e
70 69
71Within Doom Emacs, we can install Mu4e by enabling the package. 70Within Doom Emacs, we can install Mu4e by enabling the package.
72 71
73```sh 72#+begin_src sh
74nano ~/.doom.d/init.el 73nano ~/.doom.d/init.el
75``` 74#+end_src
76 75
77In this file, uncomment the `mu4e` line within the `:email` section. You can 76In this file, uncomment the =mu4e= line within the =:email= section. You
78also enable the `+org` and `+gmail` options if you prefer. 77can also enable the =+org= and =+gmail= options if you prefer.
79 78
80```lisp 79#+begin_src lisp
81(doom! :input 80(doom! :input
82 ... 81 ...
83 :email 82 :email
@@ -85,34 +84,35 @@ also enable the `+org` and `+gmail` options if you prefer.
85 ;;(mu4e +org +gmail) 84 ;;(mu4e +org +gmail)
86 ... 85 ...
87 (default +bindings +smartparens)) 86 (default +bindings +smartparens))
88``` 87#+end_src
89 88
90# Configuration 89* Configuration
91 90
92As an overall suggestion, I create the following configuration files in the 91As an overall suggestion, I create the following configuration files in
93`~/.maildir` directory and using symlinks to their proper locations so that I 92the =~/.maildir= directory and using symlinks to their proper locations
94can backup and restore these files easily. 93so that I can backup and restore these files easily.
95 94
96```sh 95#+begin_src sh
97touch ~/.maildir/.mbsyncrc && \ 96touch ~/.maildir/.mbsyncrc && \
98touch ~/.maildir/.msmtprc && \ 97touch ~/.maildir/.msmtprc && \
99ln -s /Users/username/.maildir/.mbsyncrc /Users/username/.mbsyncrc && \ 98ln -s /Users/username/.maildir/.mbsyncrc /Users/username/.mbsyncrc && \
100ln -s /Users/username/.maildir/.msmtprc /Users/username/.msmtprc 99ln -s /Users/username/.maildir/.msmtprc /Users/username/.msmtprc
101``` 100#+end_src
102 101
103You can also create these files in your home directory and skip the symlinking 102You can also create these files in your home directory and skip the
104process above. 103symlinking process above.
105 104
106## IMAP 105** IMAP
107 106
108Next, let's configure `mbsync` in the file created above. Paste the following 107Next, let's configure =mbsync= in the file created above. Paste the
109information and customize it to match your mail provider's information. 108following information and customize it to match your mail provider's
109information.
110 110
111```sh 111#+begin_src sh
112nano ~/.maildir/.mbsyncrc 112nano ~/.maildir/.mbsyncrc
113``` 113#+end_src
114 114
115```conf 115#+begin_src conf
116IMAPAccount example 116IMAPAccount example
117Host imap.example.com 117Host imap.example.com
118User dummy@example.com 118User dummy@example.com
@@ -138,18 +138,19 @@ Create Near
138Sync All 138Sync All
139Expunge Both 139Expunge Both
140SyncState * 140SyncState *
141``` 141#+end_src
142 142
143## SMTP 143** SMTP
144 144
145Next, let's configured `msmtprc` in the file created above. Paste the following 145Next, let's configured =msmtprc= in the file created above. Paste the
146information and customize it to match your mail provider's information. 146following information and customize it to match your mail provider's
147information.
147 148
148```sh 149#+begin_src sh
149nano ~/.maildir/.msmtprc 150nano ~/.maildir/.msmtprc
150``` 151#+end_src
151 152
152```conf 153#+begin_src conf
153# Set default values for all the accounts. 154# Set default values for all the accounts.
154defaults 155defaults
155logfile ~/.maildir/msmtp.log 156logfile ~/.maildir/msmtp.log
@@ -171,18 +172,18 @@ tls_starttls off
171# ====================================================================== 172# ======================================================================
172 173
173account default : startmail 174account default : startmail
174``` 175#+end_src
175 176
176## Doom Emacs 177** Doom Emacs
177 178
178Finally, we need to configure Doom Emacs to use the proper packages and set some 179Finally, we need to configure Doom Emacs to use the proper packages and
179variables and functions. 180set some variables and functions.
180 181
181```sh 182#+begin_src sh
182nano ~/.doom.d/config.el 183nano ~/.doom.d/config.el
183``` 184#+end_src
184 185
185```lisp 186#+begin_src lisp
186;; load packages and programs 187;; load packages and programs
187(use-package mu4e 188(use-package mu4e
188 :load-path "/Users/username/.emacs.d/modules/email/mu4e/") 189 :load-path "/Users/username/.emacs.d/modules/email/mu4e/")
@@ -266,40 +267,43 @@ nano ~/.doom.d/config.el
266 267
267;; mu4e address completion 268;; mu4e address completion
268(add-hook 'mu4e-compose-mode-hook 'company-mode) 269(add-hook 'mu4e-compose-mode-hook 'company-mode)
269``` 270#+end_src
270 271
271Be sure to sync Doom to update the current configurations. 272Be sure to sync Doom to update the current configurations.
272 273
273```sh 274#+begin_src sh
274doom sync 275doom sync
275``` 276#+end_src
276 277
277If you have Doom open, execute `SPC h r r` to reload the new configurations. 278If you have Doom open, execute =SPC h r r= to reload the new
279configurations.
278 280
279# Initial Sync 281* Initial Sync
280 282
281Once you have configured all of the relevant files, you can perform an initial 283Once you have configured all of the relevant files, you can perform an
282sync. Note that you can perform syncing within Mu4e itself after this. 284initial sync. Note that you can perform syncing within Mu4e itself after
285this.
283 286
284```sh 287#+begin_src sh
285mbsync -aV 288mbsync -aV
286``` 289#+end_src
287 290
288Once you sync the data, you can index the emails. 291Once you sync the data, you can index the emails.
289 292
290```sh 293#+begin_src sh
291mu init -m ~/.maildir --my-address you@example.com && \ 294mu init -m ~/.maildir --my-address you@example.com && \
292mu index 295mu index
293``` 296#+end_src
294 297
295The emails will now to be ready to use! 298The emails will now to be ready to use!
296 299
297# Screenshots 300* Screenshots
298
299You can now launch Doom and open Mu4e with `SPC o m`. You can also explore the
300Mu4e options with `SPC : mu4e`.
301 301
302The home page shows various options and metadata about the account you've opened. 302You can now launch Doom and open Mu4e with =SPC o m=. You can also
303explore the Mu4e options with =SPC : mu4e=.
303 304
304![Mu4e Home Page](https://img.cleberg.net/blog/20240418-mu4e/mu4e.png) 305The home page shows various options and metadata about the account
306you've opened.
305 307
308#+caption: Mu4e Home Page
309[[https://img.cleberg.net/blog/20240418-mu4e/mu4e.png]]
content/blog/2024-05-03-ubuntu-on-macos.md deleted −96
@@ -1,96 +0,0 @@
1+++
2date = 2024-05-03
3title = "Running Ubuntu Linux on macOS with OrbStack"
4description = "Learn how to run Linux machines as applications on macOS with minimal hassle."
5draft = false
6+++
7
8Being a macOS user who previously used Linux for many years, I often find myself
9searching for alternatives to the Linux-native tools and methods that I had
10become used to over the years.
11
12Luckily, there's an option to simply use Linux directly on your macOS device
13without having to boot the whole device into Linux or having to SSH into another
14Linux machine.
15
16In this post, I use [OrbStack](https://orbstack.dev/) to create an Ubuntu Linux
17machine to take advantage of the Linux system.
18
19## Installing OrbStack
20
21OrbStack has a [download page](https://orbstack.dev/download) if you prefer to
22use `*.dmg` files to install applications.
23
24Personally, I prefer to install everything through Homebrew. For OrbStack, run
25the following to install the application via Homebrew:
26
27```sh
28brew install orbstack
29```
30
31## Creating a Linux Machine
32
33Once installed, you can launch OrbStack and create a Linux machine in a matter
34of seconds. The creation dialog defaults to Ubuntu.
35
36![OrbStack's Linux Machine Creation
37Page](https://img.cleberg.net/blog/20240503-ubuntu-on-macos/create.png)
38
39## Launching the Machine
40
41Once created, the dialog will close and you will be presented with your new list
42of machines. Simply double-click on a machine to run it.
43
44When you run the machine for the first time, it may warn you that a program is
45attempting to run a shell script. You can choose to manually inspect this shell
46script via the provided path if you wish. When you're comfortable with the
47script, you can toggle the `Suppress this message permanently` button and click
48OK to run the machine.
49
50> **Quick Tip**!
51>
52> You will always be able to launch the OrbStack application and
53> control the machine via the Machines page, but you can also use the OrbStack
54> icon in the macOS menu bar to quickly control the machine without launching
55> OrbStack itself.
56
57## Working in Linux
58
59Once successful, OrbStack will launch your default terminal and log you into the
60Linux machine you created above.
61
62In the example below, we can see that the machine is using `Linux ubuntu 6.9.6`
63and operates similar to a fresh Ubuntu install on bare metal.
64
65![Ubuntu CLI](https://img.cleberg.net/blog/20240503-ubuntu-on-macos/cli.png)
66
67You can also click the machine's folder icon in OrbStack to open the machine's
68file system within macOS's native Files app. Within Files, you can double-click
69to open any files and they will open in the macOS defaults for those file types
70(e.g., TextEdit).
71
72![Ubuntu Files](https://img.cleberg.net/blog/20240503-ubuntu-on-macos/files.png)
73
74Here's an example of opening the `test.md` file in the screenshot above, which
75defaults to opening in Xcode on my system.
76
77![Ubuntu Files](https://img.cleberg.net/blog/20240503-ubuntu-on-macos/xcode.png)
78
79### Test Examples
80
81As a quick example of the system, I installed `neofetch`, switched to the `zsh`
82shell, and ran a few common commands.
83
84![Test Examples](https://img.cleberg.net/blog/20240503-ubuntu-on-macos/test.png)
85
86## Thoughts
87
88The installation, creation, and launch processes are seamless and provide an
89easy way to test or use Linux on macOS with little to no hassle.
90
91The shell and file integrations are incredibly snappy and I have not experienced
92any lag so far. Further, being able to open files in the Files app allows for me
93to use macOS native applications, such as TextEdit for quick editing without
94having to resort to other programs that may not be as familiar to macOS users.
95
96I love this method and will be happily looking for further use cases to apply.
content/blog/2024-05-03-ubuntu-on-macos.org added +105
@@ -0,0 +1,105 @@
1#+date: <2024-05-03>
2#+title: Running Ubuntu Linux on macOS with OrbStack
3#+description: Learn how to run Linux machines as applications on macOS with minimal hassle.
4
5
6Being a macOS user who previously used Linux for many years, I often
7find myself searching for alternatives to the Linux-native tools and
8methods that I had become used to over the years.
9
10Luckily, there's an option to simply use Linux directly on your macOS
11device without having to boot the whole device into Linux or having to
12SSH into another Linux machine.
13
14In this post, I use [[https://orbstack.dev/][OrbStack]] to create an
15Ubuntu Linux machine to take advantage of the Linux system.
16
17** Installing OrbStack
18
19OrbStack has a [[https://orbstack.dev/download][download page]] if you
20prefer to use =*.dmg= files to install applications.
21
22Personally, I prefer to install everything through Homebrew. For
23OrbStack, run the following to install the application via Homebrew:
24
25#+begin_src sh
26brew install orbstack
27#+end_src
28
29** Creating a Linux Machine
30
31Once installed, you can launch OrbStack and create a Linux machine in a
32matter of seconds. The creation dialog defaults to Ubuntu.
33
34#+caption: OrbStack's Linux Machine Creation Page
35[[https://img.cleberg.net/blog/20240503-ubuntu-on-macos/create.png]]
36
37** Launching the Machine
38
39Once created, the dialog will close and you will be presented with your
40new list of machines. Simply double-click on a machine to run it.
41
42When you run the machine for the first time, it may warn you that a
43program is attempting to run a shell script. You can choose to manually
44inspect this shell script via the provided path if you wish. When you're
45comfortable with the script, you can toggle the
46=Suppress this message permanently= button and click OK to run the
47machine.
48
49#+begin_quote
50*Quick Tip*!
51
52You will always be able to launch the OrbStack application and control
53the machine via the Machines page, but you can also use the OrbStack
54icon in the macOS menu bar to quickly control the machine without
55launching OrbStack itself.
56#+end_quote
57
58** Working in Linux
59
60Once successful, OrbStack will launch your default terminal and log you
61into the Linux machine you created above.
62
63In the example below, we can see that the machine is using
64=Linux ubuntu 6.9.6= and operates similar to a fresh Ubuntu install on
65bare metal.
66
67#+caption: Ubuntu CLI
68[[https://img.cleberg.net/blog/20240503-ubuntu-on-macos/cli.png]]
69
70You can also click the machine's folder icon in OrbStack to open the
71machine's file system within macOS's native Files app. Within Files, you
72can double-click to open any files and they will open in the macOS
73defaults for those file types (e.g., TextEdit).
74
75#+caption: Ubuntu Files
76[[https://img.cleberg.net/blog/20240503-ubuntu-on-macos/files.png]]
77
78Here's an example of opening the =test.md= file in the screenshot above,
79which defaults to opening in Xcode on my system.
80
81#+caption: Ubuntu Files
82[[https://img.cleberg.net/blog/20240503-ubuntu-on-macos/xcode.png]]
83
84*** Test Examples
85
86As a quick example of the system, I installed =neofetch=, switched to
87the =zsh= shell, and ran a few common commands.
88
89#+caption: Test Examples
90[[https://img.cleberg.net/blog/20240503-ubuntu-on-macos/test.png]]
91
92** Thoughts
93
94The installation, creation, and launch processes are seamless and
95provide an easy way to test or use Linux on macOS with little to no
96hassle.
97
98The shell and file integrations are incredibly snappy and I have not
99experienced any lag so far. Further, being able to open files in the
100Files app allows for me to use macOS native applications, such as
101TextEdit for quick editing without having to resort to other programs
102that may not be as familiar to macOS users.
103
104I love this method and will be happily looking for further use cases to
105apply.
content/blog/2024-06-19-deprecated-trusted-gpg-fix.md → content/blog/2024-06-19-deprecated-trusted-gpg-fix.org renamed +56 −56
@@ -1,49 +1,49 @@
1+++ 1#+date: <2024-06-19 08:00:00>
2date = 2024-06-19 08:00:00 2#+title: Fixing Ubuntu Error: 'Key is stored in legacy trusted.gpg keyring'
3title = "Fixing Ubuntu Error: 'Key is stored in legacy trusted.gpg keyring'" 3#+description: Learn how to update GPG keys from the trusted.gpg keyring in Ubuntu.
4description = "Learn how to update GPG keys from the trusted.gpg keyring in Ubuntu."
5+++
6 4
7## System Warning
8 5
9When running an update on an Ubuntu system, you may have run into a system 6** System Warning
10warning that looks like the example below.
11 7
12```txt 8When running an update on an Ubuntu system, you may have run into a
9system warning that looks like the example below.
10
11#+begin_src txt
13W: https://dl.yarnpkg.com/debian/dists/stable/InRelease: Key is stored in legacy 12W: https://dl.yarnpkg.com/debian/dists/stable/InRelease: Key is stored in legacy
14trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in 13trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in
15apt-key(8) for details. 14apt-key(8) for details.
16``` 15#+end_src
17 16
18While this example references the `yarn` package, the warning message is the 17While this example references the =yarn= package, the warning message is
19same for any repository using the deprecated `trusted.gpg` key ring. 18the same for any repository using the deprecated =trusted.gpg= key ring.
20 19
21The issue arises from managing keys with the `apt-key` command, which utilizes 20The issue arises from managing keys with the =apt-key= command, which
22the `/etc/apt/trusted.gpg` file by default. Instead, Ubuntu has moved to 21utilizes the =/etc/apt/trusted.gpg= file by default. Instead, Ubuntu has
23managing key rings with individual `.gpg` files in the `/etc/apt/trusted.gpg.d/` 22moved to managing key rings with individual =.gpg= files in the
24directory. 23=/etc/apt/trusted.gpg.d/= directory.
25 24
26To fix this issue, let's check to see which keys are using the `trusted.gpg` key 25To fix this issue, let's check to see which keys are using the
27ring and move them into their own dedicated key ring. 26=trusted.gpg= key ring and move them into their own dedicated key ring.
28 27
29## Finding All Keys in the Keyring 28** Finding All Keys in the Keyring
30 29
31Let's start by simply listing the keys used by the `apt` commands. To do this, 30Let's start by simply listing the keys used by the =apt= commands. To do
32run the following command. 31this, run the following command.
33 32
34```sh 33#+begin_src sh
35sudo apt-key list 34sudo apt-key list
36``` 35#+end_src
37 36
38This command will show an output similar to the one below. You may see 37This command will show an output similar to the one below. You may see
39additional keys in the `/etc/apt/trusted.gpg.d/` directory - this is where we 38additional keys in the =/etc/apt/trusted.gpg.d/= directory - this is
40will be moving any keys currently found in the `trusted.gpg` key ring. 39where we will be moving any keys currently found in the =trusted.gpg=
40key ring.
41 41
42In the below example, we can see that this system has four different GPG keys 42In the below example, we can see that this system has four different GPG
43stored within the `trusted.gpg` key ring. Let's go ahead and move them into 43keys stored within the =trusted.gpg= key ring. Let's go ahead and move
44their own files. 44them into their own files.
45 45
46```txt 46#+begin_src txt
47Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead 47Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead
48(see apt-key(8)). 48(see apt-key(8)).
49 49
@@ -67,32 +67,32 @@ uid [ unknown] nginx signing key <signing-key-2@nginx.com>
67pub rsa4096 2024-05-29 [SC] 67pub rsa4096 2024-05-29 [SC]
68 9E9B E90E ACBC DE69 FE9B 204C BCDC D8A3 8D88 A2B3 68 9E9B E90E ACBC DE69 FE9B 204C BCDC D8A3 8D88 A2B3
69uid [ unknown] nginx signing key <signing-key-3@nginx.com> 69uid [ unknown] nginx signing key <signing-key-3@nginx.com>
70``` 70#+end_src
71 71
72## Moving Keys to the Proper Location 72** Moving Keys to the Proper Location
73 73
74### Exporting Keys to New Files 74*** Exporting Keys to New Files
75 75
76Now that we know the keys, we will need to move them into their own key ring. We 76Now that we know the keys, we will need to move them into their own key
77can do this by copying the last eight (8) characters from the key's signature 77ring. We can do this by copying the last eight (8) characters from the
78and exporting it from this key ring into its own. 78key's signature and exporting it from this key ring into its own.
79 79
80Using the yarn example from the beginning, here's the command to move this key 80Using the yarn example from the beginning, here's the command to move
81into its own key ring. 81this key into its own key ring.
82 82
83```sh 83#+begin_src sh
84sudo apt-key export 86E50310 | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/yarn.gpg 84sudo apt-key export 86E50310 | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/yarn.gpg
85``` 85#+end_src
86 86
87You can repeat this process for any other keys, such as the `nginx` keys in the 87You can repeat this process for any other keys, such as the =nginx= keys
88example above. 88in the example above.
89 89
90### Cleaning Up 90*** Cleaning Up
91 91
92If you run `sudo apt-key list` again, you should see the keys within their own 92If you run =sudo apt-key list= again, you should see the keys within
93key rings: 93their own key rings:
94 94
95```txt 95#+begin_src txt
96/etc/apt/trusted.gpg.d/nginx-archive-keyring.gpg 96/etc/apt/trusted.gpg.d/nginx-archive-keyring.gpg
97------------------------------------------------ 97------------------------------------------------
98pub rsa4096 2024-05-29 [SC] 98pub rsa4096 2024-05-29 [SC]
@@ -115,19 +115,19 @@ uid [ unknown] Yarn Packaging <yarn@dan.cx>
115sub rsa4096 2016-10-05 [E] 115sub rsa4096 2016-10-05 [E]
116sub rsa4096 2019-01-02 [S] [expires: 2026-01-23] 116sub rsa4096 2019-01-02 [S] [expires: 2026-01-23]
117sub rsa4096 2019-01-11 [S] [expires: 2026-01-23] 117sub rsa4096 2019-01-11 [S] [expires: 2026-01-23]
118``` 118#+end_src
119 119
120Once you have verified that the keys are valid and stored in their own key 120Once you have verified that the keys are valid and stored in their own
121rings, you can archive the `trusted.gpg` file and run a system update to test 121key rings, you can archive the =trusted.gpg= file and run a system
122the new files. 122update to test the new files.
123 123
124```sh 124#+begin_src sh
125sudo mv /etc/apt/trusted.gpg /etc/apt/trusted.gpg.bkp 125sudo mv /etc/apt/trusted.gpg /etc/apt/trusted.gpg.bkp
126sudo apt update 126sudo apt update
127``` 127#+end_src
128
129Once you've verified that updates work as expected and that the keys are working
130as intended, you can delete the `.bkp` file created above. If you're storing
131keys that are not easily re-attainable, I suggest keeping the `.bkp` file stored
132in a safe location until you are positive that you no longer need it.
133 128
129Once you've verified that updates work as expected and that the keys are
130working as intended, you can delete the =.bkp= file created above. If
131you're storing keys that are not easily re-attainable, I suggest keeping
132the =.bkp= file stored in a safe location until you are positive that
133you no longer need it.
content/blog/2024-07-11-emacs-on-ipad.md deleted −65
@@ -1,65 +0,0 @@
1+++
2date = 2024-07-11T20:24:02
3title = "Emacs on iPadOS"
4description = "Learn how to install and use Emacs on the Apple Silicon iPad natively."
5draft = false
6+++
7
8This post describes the process to install and use Emacs on the iPad Air 13-inch
9(M2). The iPad used in this post is running iPadOS 17.6.
10
11## Shell Application
12
13In order to use Emacs on an iPad, you will need a terminal emulator application.
14I recommend [iSH](https://apps.apple.com/us/app/ish-shell/id1436902243), since
15it runs a version of Alpine Linux within the app itself and will allow you to
16install packages that you need.
17
18![iSH Application](https://img.cleberg.net/blog/20240711-emacs-on-ipad/ish.png)
19
20## Require Packages
21
22I started by adding the required packages directly within iSH. Emacs should
23install dependencies by default, but I include a few other packages that I use
24in my terminal as well.
25
26```sh
27apk add emacs ripgrep fd findutils
28```
29
30![Package
31!Installation](https://img.cleberg.net/blog/20240711-emacs-on-ipad/dependencies.png)
32
33## Emacs
34
35Once this is complete, you should be able to run Emacs natively on your iPad.
36It's effective, but can be slow at times.
37
38I attempted to also install Doom Emacs, which technically worked, but was so
39incredibly slow and buggy that I was not even able to take screenshots. Someone
40smarter than me could likely get it to work with a little tinkering.
41
42![Emacs](https://img.cleberg.net/blog/20240711-emacs-on-ipad/emacs.png)
43
44### MELPA
45
46You also have to remember to hook up MELPA yourself in the `.emacs` file to be
47able to search through their 5700+ packages instead of just ELPA packages. If
48you don't, you will only have access to ELPA packages like the ones below.
49
50![package-install](https://img.cleberg.net/blog/20240711-emacs-on-ipad/melpa.png)
51
52Once you have MELPA, you can install packages like the `dashboard` package shown
53below.
54
55![emacs-dashboard](https://img.cleberg.net/blog/20240711-emacs-on-ipad/dashboard.png)
56
57### Speed
58
59While Emacs will run on my iPad, it's not perfect. The largest issue on my
60iPad is speed - loading Emacs takes 6-7 seconds and installing the `magit`
61package took 129 seconds.
62
63I haven't played around enough to optimize loading times and poke around to see
64why the network requests take so long, but it's a big enough issue that I
65wouldn't see casual Emacs users dealing with the lag.
content/blog/2024-07-11-emacs-on-ipad.org added +70
@@ -0,0 +1,70 @@
1#+date: <2024-07-11 Thu 20:24:02>
2#+title: Emacs on iPadOS
3#+description: Learn how to install and use Emacs on the Apple Silicon iPad natively.
4
5
6This post describes the process to install and use Emacs on the iPad Air
713-inch (M2). The iPad used in this post is running iPadOS 17.6.
8
9** Shell Application
10
11In order to use Emacs on an iPad, you will need a terminal emulator
12application. I recommend
13[[https://apps.apple.com/us/app/ish-shell/id1436902243][iSH]], since it
14runs a version of Alpine Linux within the app itself and will allow you
15to install packages that you need.
16
17#+caption: iSH Application
18[[https://img.cleberg.net/blog/20240711-emacs-on-ipad/ish.png]]
19
20** Require Packages
21
22I started by adding the required packages directly within iSH. Emacs
23should install dependencies by default, but I include a few other
24packages that I use in my terminal as well.
25
26#+begin_src sh
27apk add emacs ripgrep fd findutils
28#+end_src
29
30#+caption: Package !Installation
31[[https://img.cleberg.net/blog/20240711-emacs-on-ipad/dependencies.png]]
32
33** Emacs
34
35Once this is complete, you should be able to run Emacs natively on your
36iPad. It's effective, but can be slow at times.
37
38I attempted to also install Doom Emacs, which technically worked, but
39was so incredibly slow and buggy that I was not even able to take
40screenshots. Someone smarter than me could likely get it to work with a
41little tinkering.
42
43#+caption: Emacs
44[[https://img.cleberg.net/blog/20240711-emacs-on-ipad/emacs.png]]
45
46*** MELPA
47
48You also have to remember to hook up MELPA yourself in the =.emacs= file
49to be able to search through their 5700+ packages instead of just ELPA
50packages. If you don't, you will only have access to ELPA packages like
51the ones below.
52
53#+caption: package-install
54[[https://img.cleberg.net/blog/20240711-emacs-on-ipad/melpa.png]]
55
56Once you have MELPA, you can install packages like the =dashboard=
57package shown below.
58
59#+caption: emacs-dashboard
60[[https://img.cleberg.net/blog/20240711-emacs-on-ipad/dashboard.png]]
61
62*** Speed
63
64While Emacs will run on my iPad, it's not perfect. The largest issue on
65my iPad is speed - loading Emacs takes 6-7 seconds and installing the
66=magit= package took 129 seconds.
67
68I haven't played around enough to optimize loading times and poke around
69to see why the network requests take so long, but it's a big enough
70issue that I wouldn't see casual Emacs users dealing with the lag.
content/blog/_index.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Blog"
3sort_by = "date"
4template = "blog.html"
5+++\ No newline at end of file
content/index.org added +2
@@ -0,0 +1,2 @@
1#+title: cleberg.net
2#+description: Just a blip of ones and zeroes.
content/salary.md deleted −62
@@ -1,62 +0,0 @@
1+++
2title = "Salary"
3draft = false
4+++
5
6## Salary Transparency
7
8The data below details the base salary information for each job I've held. This
9information is posted publicly to ensure others in my position have a solid
10reference point when determining if their current or proposed salary is
11appropriate.
12
13While sites like Glassdoor are locking salary data behind a paywall, LinkedIn is
14discontinuing LinkedIn Salary, and helpful websites like Big 4 Transparency are
15extremely rare, I wanted to provide my personal data publicly and freely to
16those who need it.
17
18I have seen what can happen when great employees don't know the market values
19for their skills and I happily help those in my teams, so I'm happy to extend
20this information to the online community.
21
22As a final note, there are numerous reasons that people in the same role are
23paid differently (expertise, years of experience, certifications, education,
24etc.) and that the data in this table should only be used as a single point of
25reference, not the whole story.
26
27## Salary Data
28
29Note: When in a role that gives periodic raises, I will create a new record with
30the new base salary in the table below. See the KPMG records for an example of a
31raise 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
46[[source data](/static/salary.csv)]
47
48## Salary Visualization
49
50See the graph below for a visualization of each job's salary over time, as well
51as percentage increases or decreases between jobs.
52
53![Salary Visualization](https://img.cleberg.net/blog/salary/salary.png)
54
55This page was inspired by [Xe](https://xeiaso.net/salary-transparency/), and I'm
56quoting the following wording from them as I want to reiterate this piece:
57
58> Please consider publishing your salary data like this as well. By open,
59> voluntary transparency we can help to end stigmas around discussing pay and
60> help ensure that the next generations of people in tech are treated fairly.
61> Stigmas thrive in darkness but die in the light of day. You can help end the
62> stigma by playing your cards out in the open like this.
content/salary/index.org added +70
@@ -0,0 +1,70 @@
1#+title: Salary
2#+slug: index
3
4** Salary Transparency
5:PROPERTIES:
6:CUSTOM_ID: salary-transparency
7:END:
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:PROPERTIES:
29:CUSTOM_ID: salary-data
30:END:
31Note: When in a role that gives periodic raises, I will create a new
32record with the new base salary in the table below. See the KPMG records
33for an example of a raise while in the same role.
34
35| Title | Company | Location | Start | End | Salary |
36|------------------------------------------------+------------------------+----------------+---------+---------+----------|
37| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2023-10 | Current | $116,700 |
38| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2022-06 | 2023-10 | $110,000 |
39| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | $89,500 |
40| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | $72,000 |
41| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | $65,000 |
42| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | $58,000 |
43| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | $20/hour |
44| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | $16/hour |
45| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | $7/hour |
46| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | $14/hour |
47
48[[[/static/salary.csv][source data]]]
49
50** Salary Visualization
51:PROPERTIES:
52:CUSTOM_ID: salary-visualization
53:END:
54See the graph below for a visualization of each job's salary over time,
55as well as percentage increases or decreases between jobs.
56
57#+caption: Salary Visualization
58[[https://img.cleberg.net/blog/salary/salary.png]]
59
60This page was inspired by
61[[https://xeiaso.net/salary-transparency/][Xe]], and I'm quoting the
62following wording from them as I want to reiterate this piece:
63
64#+begin_quote
65Please consider publishing your salary data like this as well. By open,
66voluntary transparency we can help to end stigmas around discussing pay
67and help ensure that the next generations of people in tech are treated
68fairly. Stigmas thrive in darkness but die in the light of day. You can
69help end the stigma by playing your cards out in the open like this.
70#+end_quote
content/services.md deleted −21
@@ -1,21 +0,0 @@
1+++
2title = "Services"
3draft = false
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
10 password/secret sharing
11- [Invidious](https://invidious.cleberg.net/) - A YouTube proxy
12- [Office](https://office.cleberg.net/) - The world's smallest office suite
13- [Org-Live](https://org.cleberg.net/) - A basic org-mode editor for the web I
14 built
15- [Rimgo](https://rimgo.cleberg.net/) - An alternative frontend for Imgur
16- [SearXNG](https://search.cleberg.net/) - A privacy-respecting, open
17 metasearch engine
18
19See the [git
20log](https://git.sr.ht/~cyborg/cleberg.net/log/main/item/content/services.md) if
21you want to see changes that have been made.
content/services/index.org added +23
@@ -0,0 +1,23 @@
1#+title: Services
2#+slug: index
3
4- [[https://ao.cleberg.net/][AnonymousOverflow]] - A StackOverflow proxy
5- [[https://cyberchef.cleberg.net/][CyberChef]] - The Cyber Swiss Army
6 Knife
7- [[https://files.cleberg.net/][FileArchive]] - An interesting file
8 archive
9- [[https://paste.cleberg.net/][FlashPaper]] - One-time encrypted
10 password/secret sharing
11- [[https://invidious.cleberg.net/][Invidious]] - A YouTube proxy
12- [[https://office.cleberg.net/][Office]] - The world's smallest office
13 suite
14- [[https://org.cleberg.net/][Org-Live]] - A basic org-mode editor for
15 the web I built
16- [[https://rimgo.cleberg.net/][Rimgo]] - An alternative frontend for
17 Imgur
18- [[https://search.cleberg.net/][SearXNG]] - A privacy-respecting, open
19 metasearch engine
20
21See the
22[[https://git.sr.ht/~cyborg/cleberg.net/log/main/item/content/services.md][git
23log]] if you want to see changes that have been made.
content/wiki/_index.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Wiki"
3sort_by = "title"
4template = "wiki.html"
5+++\ No newline at end of file
content/wiki/ai.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "AI"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/audit.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Audit"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/automation.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Automation"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/blogroll.md deleted −40
@@ -1,40 +0,0 @@
1+++
2title = "Blogroll"
3updated = 2024-04-12
4draft = false
5+++
6
7## Aggregators
8
9- [1MB Club](https://1mb.club/)
10- [250KB Club](https://250kb.club/)
11- [512KB Club](https://512kb.club/)
12- [Darktheme Club](https://darktheme.club/)
13- [No CSS Club](https://nocss.club/)
14- [No-JS Club](https://no-js.club/)
15- [Ye Olde Blogroll](https://blogroll.org/)
16
17## Plain Text
18
19A list of various plaintext websites and lists.
20
21- [A List Of Text-Only & Minimalist News
22 Sites](https://greycoder.com/a-list-of-text-only-new-sites/)
23- [Harvard Law Review](https://harvardlawreview.org/)
24- [Hyperlinked Text](https://sjmulder.nl/en/textonly.html)
25- [Plain-text web
26 design](https://medium.com/@letsworkshop/plain-text-web-design-a78ccaf9dbc0)
27- [Plaintext World](https://plaintextworld.com/)
28- [txt2web](https://github.com/masonarmand/txt2web)
29- [Words](https://justinjackson.ca/words.html)
30
31## Webrings
32
33Instead of listing my personal favorites, I'm just going to drop a link to
34[brisay's webring list](https://brisray.com/web/webring-list.htm), which
35contains 237 webrings for a total of 7078 websites, as of 2024-03-15.
36
37## Everything Else
38
39- [Dead Simple Sites](https://deadsimplesites.com/)
40- [Planet Emacslife](https://planet.emacslife.com/)
content/wiki/blogroll.org added +46
@@ -0,0 +1,46 @@
1#+title: Blogroll
2#+date: <2024-04-12>
3
4** Aggregators
5:PROPERTIES:
6:CUSTOM_ID: aggregators
7:END:
8- [[https://1mb.club/][1MB Club]]
9- [[https://250kb.club/][250KB Club]]
10- [[https://512kb.club/][512KB Club]]
11- [[https://darktheme.club/][Darktheme Club]]
12- [[https://nocss.club/][No CSS Club]]
13- [[https://no-js.club/][No-JS Club]]
14- [[https://blogroll.org/][Ye Olde Blogroll]]
15
16** Plain Text
17:PROPERTIES:
18:CUSTOM_ID: plain-text
19:END:
20A list of various plaintext websites and lists.
21
22- [[https://greycoder.com/a-list-of-text-only-new-sites/][A List Of
23 Text-Only & Minimalist News Sites]]
24- [[https://harvardlawreview.org/][Harvard Law Review]]
25- [[https://sjmulder.nl/en/textonly.html][Hyperlinked Text]]
26- [[https://medium.com/@letsworkshop/plain-text-web-design-a78ccaf9dbc0][Plain-text
27 web design]]
28- [[https://plaintextworld.com/][Plaintext World]]
29- [[https://github.com/masonarmand/txt2web][txt2web]]
30- [[https://justinjackson.ca/words.html][Words]]
31
32** Webrings
33:PROPERTIES:
34:CUSTOM_ID: webrings
35:END:
36Instead of listing my personal favorites, I'm just going to drop a link
37to [[https://brisray.com/web/webring-list.htm][brisay's webring list]],
38which contains 237 webrings for a total of 7078 websites, as of
392024-03-15.
40
41** Everything Else
42:PROPERTIES:
43:CUSTOM_ID: everything-else
44:END:
45- [[https://deadsimplesites.com/][Dead Simple Sites]]
46- [[https://planet.emacslife.com/][Planet Emacslife]]
content/wiki/cli.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "CLI"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/data.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Data"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/editors.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Editors"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/foss.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "FOSS"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/git.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Git"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/hardware.md deleted −117
@@ -1,117 +0,0 @@
1+++
2title = "Hardware"
3updated = 2024-03-16
4draft = false
5+++
6
7## Desktop
8
9### macOS
10
11Probably should have added more RAM but Macbooks are stupid expensive.
12
13| Category | Details |
14| -------- | ----------------------------------------------------- |
15| Model | [Macbook Pro 16"](https://www.apple.com/macbook-pro/) |
16| CPU | Apple M2 Pro |
17| RAM | 16GB |
18| Storage | 512GB SSD |
19
20### Linux
21
22A beauty.
23
24| Category | Details |
25| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
26| Model | [Lenovo ThinkPad E15 Gen 4, model 21ED0048US](<https://www.lenovo.com/us/en/p/laptops/thinkpad/thinkpade/thinkpad--e15-gen-4-(15-inch-amd)/len101t0023>) |
27| CPU | AMD Ryzen 5 5625U with Radeon Graphics |
28| RAM | 16 GB |
29| Storage | 256 GB SSD |
30
31## Mobile
32
33Previously used a Pixel 6 & Pixel 7 with GrapheneOS.
34
35| Category | Details |
36| -------- | --------------------------------------------------------- |
37| Model | [iPhone 15 Pro Max](https://www.apple.com/iphone-15-pro/) |
38| CPU | A17 Pro |
39| RAM | 8GB |
40| Storage | 256GB |
41
42## Homelab
43
44I run a small homelab with a mix of consumer (compute/storage) and enterprise
45(network) hardware. I try to keep the lab energy efficient and quiet as my top
46priorities.
47
48### IoT
49
50A collection of mainly smart lights, sensors, and smart appliances. My first
51preference is to disable all networking for new smart devices or simply not
52connect internet in the first place (e.g. I never enable internet on my smart
53TVs). If the smart device requires LAN access, I will connect the device to my
54guest-restricted IoT network. As a last resort, I will set-up the internet but
55monitor the DNS lookups via NextDNS and forcibly block any domains I do not want
56the device to be using. If the device is egregious or shady, I'll just sell it
57and either replace it or live without it.
58
59- Other Appliances (washer, dryer, humidifier, fans, etc.)
60- [Roomba
61 i7+](https://about.irobot.com/sitecore/content/north-america/irobot-us/home/roomba/i7-series)
62- [Philips Hue A19
63 Bulbs](https://www.philips-hue.com/en-us/p/hue-white-and-color-ambiance-a19---e26-smart-bulb---60-w--3-pack-/046677562786)
64 x 15
65- [Philips Hue Play Light
66 Bars](https://www.philips-hue.com/en-us/p/hue-bundle-play-blk-ext/33001)
67- [Philips Hue Smart
68 Bridge](https://www.philips-hue.com/en-us/p/hue-bridge/046677458478) + play
69 light bars and a ton of bulbs
70- [UP
71 Chime](https://store.ui.com/us/en/collections/unifi-camera-security-special-chime)
72- [UP-Sense](https://store.ui.com/us/en/collections/unifi-camera-security-special-sensor)
73 x 2
74- [USP-Plug](https://store.ui.com/us/en/products/unifi-smart-power)
75- [UVC G4
76 Instant](https://store.ui.com/us/en/collections/unifi-camera-security-compact-wifi-connected)
77 x 3
78- [UVC G4 Doorbell
79 Pro](https://store.ui.com/us/en/collections/unifi-camera-security-special-wifi-doorbell)
80
81### Network
82
83A rack-mounted Dream Machine Pro, connected downstream to an access point, mesh
84extender, and a couple ethernet switches.
85
86- [UDM-Pro](https://store.ui.com/us/en/collections/unifi-dream-machine/products/udm-pro)
87- [USW-24-PoE](https://store.ui.com/us/en/collections/unifi-switching-standard-power-over-ethernet/products/usw-24-poe)
88- [USW-Lite-8-PoE](https://store.ui.com/us/en/collections/unifi-switching-utility-poe/products/usw-lite-8-poe)
89- [U6-Pro](https://store.ui.com/us/en/collections/unifi-wifi-flagship-high-capacity/products/u6-pro)
90- [U6-Extender](https://store.ui.com/us/en/collections/unifi-wifi-inwall-outlet-mesh)
91- [USW 24-Port Patch
92 Panel](https://store.ui.com/us/en/collections/unifi-accessory-tech-installations-rackmount/products/uacc-rack-panel-patch-blank-24)
93
94### Servers
95
961. Rack-Mount Server
97
98 I wasn't happy with using low-powered PCs as servers and I knew I did not
99 want the ear-shattering enterprise rack-mounted servers, so I built my own.
100
101 | Category | Details |
102 | ------------------ | -------------------------------------- |
103 | Case | Rosewill RSV-R4100U 4U |
104 | Motherboard | NZXT B550 |
105 | CPU | AMD Ryzen 7 5700G with Radeon Graphics |
106 | RAM | 64GB RAM (2x32GB) |
107 | Storage (On-board) | Western Digital 500GB M.2 NVME SSD |
108 | Storage (HDD Bay) | 48TB HDD |
109 | PSU | Corsair RM850 PSU |
110
1112. Other
112
113 These ran as my main servers before I built the rack-mounted server above. I
114 have shut these down indefinitely for now as I have no use for them.
115
116 - Dell OptiPlex
117 - Raspberry Pi 4
content/wiki/hardware.org added +130
@@ -0,0 +1,130 @@
1#+title: Hardware
2#+date: <2024-03-16>
3
4** Desktop
5:PROPERTIES:
6:CUSTOM_ID: desktop
7:END:
8*** macOS
9:PROPERTIES:
10:CUSTOM_ID: macos
11:END:
12Probably should have added more RAM but Macbooks are stupid expensive.
13
14| Category | Details |
15|----------+---------------------------------------------------------|
16| Model | [[https://www.apple.com/macbook-pro/][Macbook Pro 16”]] |
17| CPU | Apple M2 Pro |
18| RAM | 16GB |
19| Storage | 512GB SSD |
20
21*** Linux
22:PROPERTIES:
23:CUSTOM_ID: linux
24:END:
25A beauty.
26
27| Category | Details |
28|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------|
29| Model | [[https://www.lenovo.com/us/en/p/laptops/thinkpad/thinkpade/thinkpad--e15-gen-4-(15-inch-amd)/len101t0023][Lenovo ThinkPad E15 Gen 4, model 21ED0048US]] |
30| CPU | AMD Ryzen 5 5625U with Radeon Graphics |
31| RAM | 16 GB |
32| Storage | 256 GB SSD |
33
34** Mobile
35:PROPERTIES:
36:CUSTOM_ID: mobile
37:END:
38Previously used a Pixel 6 & Pixel 7 with GrapheneOS.
39
40| Category | Details |
41|----------+-------------------------------------------------------------|
42| Model | [[https://www.apple.com/iphone-15-pro/][iPhone 15 Pro Max]] |
43| CPU | A17 Pro |
44| RAM | 8GB |
45| Storage | 256GB |
46
47** Homelab
48:PROPERTIES:
49:CUSTOM_ID: homelab
50:END:
51I run a small homelab with a mix of consumer (compute/storage) and
52enterprise (network) hardware. I try to keep the lab energy efficient
53and quiet as my top priorities.
54
55*** IoT
56:PROPERTIES:
57:CUSTOM_ID: iot
58:END:
59A collection of mainly smart lights, sensors, and smart appliances. My
60first preference is to disable all networking for new smart devices or
61simply not connect internet in the first place (e.g. I never enable
62internet on my smart TVs). If the smart device requires LAN access, I
63will connect the device to my guest-restricted IoT network. As a last
64resort, I will set-up the internet but monitor the DNS lookups via
65NextDNS and forcibly block any domains I do not want the device to be
66using. If the device is egregious or shady, I'll just sell it and either
67replace it or live without it.
68
69- Other Appliances (washer, dryer, humidifier, fans, etc.)
70- [[https://about.irobot.com/sitecore/content/north-america/irobot-us/home/roomba/i7-series][Roomba
71 i7+]]
72- [[https://www.philips-hue.com/en-us/p/hue-white-and-color-ambiance-a19---e26-smart-bulb---60-w--3-pack-/046677562786][Philips
73 Hue A19 Bulbs]] x 15
74- [[https://www.philips-hue.com/en-us/p/hue-bundle-play-blk-ext/33001][Philips
75 Hue Play Light Bars]]
76- [[https://www.philips-hue.com/en-us/p/hue-bridge/046677458478][Philips
77 Hue Smart Bridge]] + play light bars and a ton of bulbs
78- [[https://store.ui.com/us/en/collections/unifi-camera-security-special-chime][UP
79 Chime]]
80- [[https://store.ui.com/us/en/collections/unifi-camera-security-special-sensor][UP-Sense]]
81 x 2
82- [[https://store.ui.com/us/en/products/unifi-smart-power][USP-Plug]]
83- [[https://store.ui.com/us/en/collections/unifi-camera-security-compact-wifi-connected][UVC
84 G4 Instant]] x 3
85- [[https://store.ui.com/us/en/collections/unifi-camera-security-special-wifi-doorbell][UVC
86 G4 Doorbell Pro]]
87
88*** Network
89:PROPERTIES:
90:CUSTOM_ID: network
91:END:
92A rack-mounted Dream Machine Pro, connected downstream to an access
93point, mesh extender, and a couple ethernet switches.
94
95- [[https://store.ui.com/us/en/collections/unifi-dream-machine/products/udm-pro][UDM-Pro]]
96- [[https://store.ui.com/us/en/collections/unifi-switching-standard-power-over-ethernet/products/usw-24-poe][USW-24-PoE]]
97- [[https://store.ui.com/us/en/collections/unifi-switching-utility-poe/products/usw-lite-8-poe][USW-Lite-8-PoE]]
98- [[https://store.ui.com/us/en/collections/unifi-wifi-flagship-high-capacity/products/u6-pro][U6-Pro]]
99- [[https://store.ui.com/us/en/collections/unifi-wifi-inwall-outlet-mesh][U6-Extender]]
100- [[https://store.ui.com/us/en/collections/unifi-accessory-tech-installations-rackmount/products/uacc-rack-panel-patch-blank-24][USW
101 24-Port Patch Panel]]
102
103*** Servers
104:PROPERTIES:
105:CUSTOM_ID: servers
106:END:
1071. Rack-Mount Server
108
109 I wasn't happy with using low-powered PCs as servers and I knew I did
110 not want the ear-shattering enterprise rack-mounted servers, so I
111 built my own.
112
113 | Category | Details |
114 |--------------------+----------------------------------------|
115 | Case | Rosewill RSV-R4100U 4U |
116 | Motherboard | NZXT B550 |
117 | CPU | AMD Ryzen 7 5700G with Radeon Graphics |
118 | RAM | 64GB RAM (2x32GB) |
119 | Storage (On-board) | Western Digital 500GB M.2 NVME SSD |
120 | Storage (HDD Bay) | 48TB HDD |
121 | PSU | Corsair RM850 PSU |
122
1232. Other
124
125 These ran as my main servers before I built the rack-mounted server
126 above. I have shut these down indefinitely for now as I have no use
127 for them.
128
129 - Dell OptiPlex
130 - Raspberry Pi 4
content/wiki/internet.md → content/wiki/internet.org renamed +50 −44
@@ -1,38 +1,38 @@
1+++ 1#+title: Internet
2title = "Internet" 2#+date: <2024-05-02>
3updated = 2024-05-02 3
4draft = false 4** Gemini
5+++ 5:PROPERTIES:
6 6:CUSTOM_ID: gemini
7## Gemini 7:END:
8 8The [[https://geminiprotocol.net/][Gemini Protocol]] is a network of
9The [Gemini Protocol](https://geminiprotocol.net/) is a network of
10interconnected plaintext documents. If you've used 9interconnected plaintext documents. If you've used
11[Gopher](https://en.wikipedia.org/wiki/Gopher_(protocol)) before, Gemini will 10[[https://en.wikipedia.org/wiki/Gopher_(protocol)][Gopher]] before,
12feel very familiar. 11Gemini will feel very familiar.
13
14Instead of web sites, Gemini uses the term "capsules" that can be access via
15`gemini://` links.
16
17In order to browse Gemini capsules, you will need a client capable of supporting
18Gemini. The Gemini Protocol website has a [Gemini
19software](https://geminiprotocol.net/software/) page with a list of clients for
20each platform.
21 12
13Instead of web sites, Gemini uses the term "capsules" that can be access
14via =gemini://= links.
22 15
23### Gemtext 16In order to browse Gemini capsules, you will need a client capable of
17supporting Gemini. The Gemini Protocol website has a
18[[https://geminiprotocol.net/software/][Gemini software]] page with a
19list of clients for each platform.
24 20
25[Gemtext](https://geminiprotocol.net/docs/cheatsheet.gmi) is a plaintext syntax, 21*** Gemtext
26very similar to Markdown. 22:PROPERTIES:
23:CUSTOM_ID: gemtext
24:END:
25[[https://geminiprotocol.net/docs/cheatsheet.gmi][Gemtext]] is a
26plaintext syntax, very similar to Markdown.
27 27
28To quote the Gemini Protocol's documentation: 28To quote the Gemini Protocol's documentation:
29 29
30- Long lines get wrapped by the client to fit the screen. 30- Long lines get wrapped by the client to fit the screen.
31- Short lines _don't_ get joined together. 31- Short lines /don't/ get joined together.
32- Write paragraphs as single long lines. 32- Write paragraphs as single long lines.
33- Blank lines are rendered verbatim. 33- Blank lines are rendered verbatim.
34 34
35```md 35#+begin_src md
36[//]: <> (Headings) 36[//]: <> (Headings)
37# Heading 37# Heading
38## Sub-heading 38## Sub-heading
@@ -54,16 +54,19 @@ To quote the Gemini Protocol's documentation:
54Any line starting with three backticks (```) will tell the client to toggle to 54Any line starting with three backticks (```) will tell the client to toggle to
55"preformatted mode", which disables the client's logical checks to render links, 55"preformatted mode", which disables the client's logical checks to render links,
56headings, etc. and will render the text as-is. 56headings, etc. and will render the text as-is.
57``` 57#+end_src
58 58
59## Gopher 59** Gopher
60 60:PROPERTIES:
61The [Gopher Protocol](https://en.wikipedia.org/wiki/Gopher_(protocol)) is a 61:CUSTOM_ID: gopher
62communication protocol, similar to Gemini, that allows for browsing of any ones 62:END:
63of [the 14 supported item 63The [[https://en.wikipedia.org/wiki/Gopher_(protocol)][Gopher Protocol]]
64types](https://datatracker.ietf.org/doc/html/rfc1436#section-3.8): 64is a communication protocol, similar to Gemini, that allows for browsing
65 65of any ones of
66```txt 66[[https://datatracker.ietf.org/doc/html/rfc1436#section-3.8][the 14
67supported item types]]:
68
69#+begin_src txt
670 Item is a file 700 Item is a file
681 Item is a directory 711 Item is a directory
692 Item is a CSO phone-book server 722 Item is a CSO phone-book server
@@ -80,14 +83,17 @@ types](https://datatracker.ietf.org/doc/html/rfc1436#section-3.8):
80T Item points to a text-based tn3270 session. 83T Item points to a text-based tn3270 session.
81g Item is a GIF format graphics file. 84g Item is a GIF format graphics file.
82I Item is some kind of image file. Client decides how to display. 85I Item is some kind of image file. Client decides how to display.
83``` 86#+end_src
84
85### Simplicity is Intentional
86 87
87Gopher is [meant to be 88*** Simplicity is Intentional
88simplistic](https://datatracker.ietf.org/doc/html/rfc1436#section-4). 89:PROPERTIES:
90:CUSTOM_ID: simplicity-is-intentional
91:END:
92Gopher is
93[[https://datatracker.ietf.org/doc/html/rfc1436#section-4][meant to be
94simplistic]].
89 95
90```txt 96#+begin_src txt
91As far as possible we desire any new features to be carried as new 97As far as possible we desire any new features to be carried as new
92protocols that will be hidden behind new document-types. The 98protocols that will be hidden behind new document-types. The
93internet Gopher philosophy is: 99internet Gopher philosophy is:
@@ -110,4 +116,4 @@ internet Gopher philosophy is:
110 (c) The well-tempered client should do something reasonable with 116 (c) The well-tempered client should do something reasonable with
111 funny characters received in text; filter them out, leave them in, 117 funny characters received in text; filter them out, leave them in,
112 whatever. 118 whatever.
113```\ No newline at end of file 119#+end_src
content/wiki/ios.md deleted −204
@@ -1,204 +0,0 @@
1+++
2title = "iOS"
3updated = 2024-05-01
4draft = false
5+++
6
7Related:
8
9- [Hardware](/wiki/hardware.html)
10
11My primary mobile OS. Currently running iOS 17. This wiki page contains most of
12the apps I have used at one point or another across my different iPhones.
13
14(`*`) = My favorites
15
16## Configuration
17
18### Display
19
20- Light Mode
21 - 10:00 to 16:00
22- Dark Mode
23 - 16:00 to 10:00
24
25### Focus Modes
26
27- Personal Focus
28 - 06:00 to 21:00
29 - Allow Notifications From:
30 - Alarms
31 - Calendar
32 - Contacts (1 person)
33 - Messages
34 - Phone
35 - Reminders
36 - Signal
37 - UniFi Protect
38- Sleep Focus
39 - 21:00 to 06:00
40 - Allow Notifications From:
41 - Alarms
42 - Contacts (1 person)
43 - Reminders
44 - Signal
45
46### Privacy & Security
47
48I generally follow the [principle of least
49privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) by only
50permitting the bare minimum privileges and revoking as soon as they are no
51longer required.
52
53Here's the baseline I start with:
54
55- Disable:
56 - Analytics & Improvements
57 - Apple Advertising
58 - Apple ID > Sign-In & Security > Two-Factor Authentication
59 - Location Services > System Services > Product Improvement
60 - Tracking > Allow Apps to Request to Track
61 - Safari > Advanced > Privacy Preserving Ad Measurement
62- Enable:
63 - Apple ID > iCloud > Advanced Data Protection
64 - Apple ID > Personal Information > Communication Preferences
65 - App Privacy Report
66 - Location Services only for Camera, Find My, UDisc, & WiFiman (`While
67 Using`)
68 - Safari > Prevent Cross-Site Tracking
69 - Safari > Hide IP Address
70 - Safari > Advanced > Advanced Tracking and Fingerprinting Protection
71
72## Native Apps
73
74### Business
75
76- [Element](https://apps.apple.com/us/app/element-messenger/id1083446067) - A
77 cross-platform messenger, based on Matrix
78- [LinkedIn](https://apps.apple.com/us/app/linkedin-network-job-finder/id288429040)
79 - One of the only social media apps I use
80
81### Developer Tools
82
83- [Harbour](https://testflight.apple.com/join/F2vK7xo4) - Easily manage your
84 Portainer service
85- [iSH](https://apps.apple.com/us/app/ish-shell/id1436902243) - A local shell
86 with SSH functionality
87
88### Entertainment
89
90- [Plex](https://apps.apple.com/us/app/plex-watch-live-tv-and-movies/id383457673)
91 - A client for the Plex Media Server
92- [Steam](https://apps.apple.com/us/app/steam-mobile/id495369748) - The top
93 gaming marketplace for computers
94
95### Lifestyle
96
97- [Home](https://apps.apple.com/us/app/home/id1110145103) (`*`) - Apple
98 homekit powered smart home manager
99- [Hue](https://apps.apple.com/us/app/philips-hue/id1055281310) - Philips Hue
100 smart home manager
101- [iRobot](https://apps.apple.com/us/app/irobot-home/id1012014442) - Manage
102 iRobot Roomba devices
103- [UniFi Protect](https://apps.apple.com/us/app/unifi-protect/id1392492235) -
104 View and manage most UniFi Protect cameras and settings
105
106### Music
107
108- [Apple Music](https://apps.apple.com/us/app/apple-music/id1108187390) (`*`)
109 - Apple's native music streaming app
110- [Plexamp](https://apps.apple.com/us/app/plexamp/id1500797510) (`*`) -
111 Top-notch music app for your Plex Media Server, with a neural network that
112 provides excellent radio/shuffle suggestions
113
114### Navigation
115
116- [deedum](https://apps.apple.com/us/app/deedum/id1546810946) - A Gemini
117 Protocol browser
118
119### News
120
121- [NetNewsWire](https://apps.apple.com/us/app/netnewswire-rss-reader/id1480640210)
122 - A free and open source RSS reader for Mac, iPhone, and iPad
123
124### Photo & Video
125
126- [Aislingeach](https://testflight.apple.com/join/Q6WyyEpS) - A quick way to
127 generate and rate images from the Stable Horde
128- [Unsplash](https://apps.apple.com/us/app/unsplash/id1290631746) - Premium
129 images, mostly free
130
131### Productivity
132
133- [beorg](https://apps.apple.com/us/app/beorg-to-do-list-agenda/id1238649962)
134 - An org-mode editor, outline, and scheduler with paid extensions
135- [Bitwarden](https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744)
136 (`*`) - An open source password manager
137- [Bitwarden
138 Authenticator](https://apps.apple.com/us/app/bitwarden-authenticator/id6497335175)
139 (`*`) - Generate 2FA on your device
140- [Cryptomator](https://apps.apple.com/us/app/cryptomator/id1560822163) - A
141 cross-platform encryption program
142- [Obsidian](https://apps.apple.com/us/app/obsidian-connected-notes/id1557175442)
143 (`*`) - A nice Markdown-based editor based on a "vault" structure. Offers a
144 paid sync solution and community extensions
145- [Strongbox](https://apps.apple.com/us/app/strongbox-password-manager/id897283731)
146 - Keepass password manager for iOS & macOS
147- [UniFi Network](https://apps.apple.com/us/app/unifi/id1057750338) - View and
148 manage most UniFi Network settings
149
150### Safari Extensions
151
152- [AdGuard](https://apps.apple.com/us/app/adguard-adblock-privacy/id1047223162)
153 - Ad blocker
154- [Dark
155 Reader](https://apps.apple.com/us/app/dark-reader-for-safari/id1438243180) -
156 Dark mode for all the sites
157- [PiPifier](https://apps.apple.com/us/app/pipifier/id1234771095) - Force
158 videos to support PiP
159- [Privacy
160 Redirect](https://apps.apple.com/us/app/privacy-redirect/id1578144015) -
161 Redirect select websites to others, usually to privacy-focused alternatives
162
163### Social Networking
164
165- [MultiTab T](https://apps.apple.com/us/app/multitab-for-tumblr/id1071533778)
166 (`*`) - A gallery-based Tumblr client with some unique features, such as tab
167 history and sync
168- [Signal](https://apps.apple.com/us/app/signal-private-messenger/id874139669)
169 (`*`) - A simple, powerful, and secure messenger
170- [Three Cheers](https://testflight.apple.com/join/mpVk1qIy) - A client for
171 Tildes.net with a design focus that matches the intent of Tildes
172- [Voyager](https://apps.apple.com/us/app/voyager-for-lemmy/id6451429762) - A
173 Lemmy client
174
175### Sports
176
177- [Apple Sports](https://apps.apple.com/us/app/apple-sports/id6446788829) -
178 Apple's new sports app - lacks notifications and live events
179- [UDisc](https://apps.apple.com/us/app/udisc-disc-golf/id1072228953) - Disc
180 golf course maps, score cards, and more
181
182### Utilities
183
184- [Backblaze](https://apps.apple.com/us/app/backblaze/id628638330) - Quickly
185 view and manage Backblaze b2 cloud storage
186- [Mullvad VPN](https://apps.apple.com/us/app/mullvad-vpn/id1488466513) (`*`)
187 - A private VPN service
188- [OTP Auth](https://apps.apple.com/us/app/otp-auth/id659877384) (`*`) - A
189 minimalistic OTP app with support for biometrics, custom icons,
190 import/export, and iCloud sync
191- [Plex Dash](https://apps.apple.com/us/app/plex-dash/id1500797677) - Stats
192 about your Plex Media Server
193- [Safari](https://apps.apple.com/us/app/safari/id1146562112) - iOS default
194 browser
195- [Unifi WiFiman](https://apps.apple.com/us/app/ubiquiti-wifiman/id1385561119)
196 - Create visual layouts of WiFi strength and save heat maps to your phone
197
198## Web Apps & Shortcuts
199
200- [Brutalist Report](https://brutalist.report/) - Minimal news aggregator
201- [\_Cyber.Report](https://cyber.report/) - Cybersecurity news aggregator
202- [Hacker News](https://news.ycombinator.com/) - Mostly technical news
203- [NextDNS](https://nextdns.io/) - NextDNS statistics dashboard
204- [Readspike](https://readspike.com/) - Minimal news aggregator
content/wiki/ios.org added +246
@@ -0,0 +1,246 @@
1#+title: iOS
2#+date: <2024-05-01>
3
4Related:
5
6- [[/wiki/hardware.html][Hardware]]
7
8My primary mobile OS. Currently running iOS 17. This wiki page contains
9most of the apps I have used at one point or another across my different
10iPhones.
11
12(=*=) = My favorites
13
14** Configuration
15:PROPERTIES:
16:CUSTOM_ID: configuration
17:END:
18*** Display
19:PROPERTIES:
20:CUSTOM_ID: display
21:END:
22- Light Mode
23 - 10:00 to 16:00
24- Dark Mode
25 - 16:00 to 10:00
26
27*** Focus Modes
28:PROPERTIES:
29:CUSTOM_ID: focus-modes
30:END:
31- Personal Focus
32 - 06:00 to 21:00
33 - Allow Notifications From:
34 - Alarms
35 - Calendar
36 - Contacts (1 person)
37 - Messages
38 - Phone
39 - Reminders
40 - Signal
41 - UniFi Protect
42- Sleep Focus
43 - 21:00 to 06:00
44 - Allow Notifications From:
45 - Alarms
46 - Contacts (1 person)
47 - Reminders
48 - Signal
49
50*** Privacy & Security
51:PROPERTIES:
52:CUSTOM_ID: privacy-security
53:END:
54I generally follow the
55[[https://en.wikipedia.org/wiki/Principle_of_least_privilege][principle
56of least privilege]] by only permitting the bare minimum privileges and
57revoking as soon as they are no longer required.
58
59Here's the baseline I start with:
60
61- Disable:
62 - Analytics & Improvements
63 - Apple Advertising
64 - Apple ID > Sign-In & Security > Two-Factor Authentication
65 - Location Services > System Services > Product Improvement
66 - Tracking > Allow Apps to Request to Track
67 - Safari > Advanced > Privacy Preserving Ad Measurement
68- Enable:
69 - Apple ID > iCloud > Advanced Data Protection
70 - Apple ID > Personal Information > Communication Preferences
71 - App Privacy Report
72 - Location Services only for Camera, Find My, UDisc, & WiFiman
73 (=While Using=)
74 - Safari > Prevent Cross-Site Tracking
75 - Safari > Hide IP Address
76 - Safari > Advanced > Advanced Tracking and Fingerprinting Protection
77
78** Native Apps
79:PROPERTIES:
80:CUSTOM_ID: native-apps
81:END:
82*** Business
83:PROPERTIES:
84:CUSTOM_ID: business
85:END:
86- [[https://apps.apple.com/us/app/element-messenger/id1083446067][Element]] -
87 A cross-platform messenger, based on Matrix
88- [[https://apps.apple.com/us/app/linkedin-network-job-finder/id288429040][LinkedIn]]
89 - One of the only social media apps I use
90
91*** Developer Tools
92:PROPERTIES:
93:CUSTOM_ID: developer-tools
94:END:
95- [[https://testflight.apple.com/join/F2vK7xo4][Harbour]] - Easily
96 manage your Portainer service
97- [[https://apps.apple.com/us/app/ish-shell/id1436902243][iSH]] - A
98 local shell with SSH functionality
99
100*** Entertainment
101:PROPERTIES:
102:CUSTOM_ID: entertainment
103:END:
104- [[https://apps.apple.com/us/app/plex-watch-live-tv-and-movies/id383457673][Plex]]
105 - A client for the Plex Media Server
106- [[https://apps.apple.com/us/app/steam-mobile/id495369748][Steam]] -
107 The top gaming marketplace for computers
108
109*** Lifestyle
110:PROPERTIES:
111:CUSTOM_ID: lifestyle
112:END:
113- [[https://apps.apple.com/us/app/home/id1110145103][Home]] (=*=) -
114 Apple homekit powered smart home manager
115- [[https://apps.apple.com/us/app/philips-hue/id1055281310][Hue]] -
116 Philips Hue smart home manager
117- [[https://apps.apple.com/us/app/irobot-home/id1012014442][iRobot]] -
118 Manage iRobot Roomba devices
119- [[https://apps.apple.com/us/app/unifi-protect/id1392492235][UniFi
120 Protect]] - View and manage most UniFi Protect cameras and settings
121
122*** Music
123:PROPERTIES:
124:CUSTOM_ID: music
125:END:
126- [[https://apps.apple.com/us/app/apple-music/id1108187390][Apple
127 Music]] (=*=)
128 - Apple's native music streaming app
129- [[https://apps.apple.com/us/app/plexamp/id1500797510][Plexamp]]
130 (=*=) - Top-notch music app for your Plex Media Server, with a neural
131 network that provides excellent radio/shuffle suggestions
132
133*** Navigation
134:PROPERTIES:
135:CUSTOM_ID: navigation
136:END:
137- [[https://apps.apple.com/us/app/deedum/id1546810946][deedum]] - A
138 Gemini Protocol browser
139
140*** News
141:PROPERTIES:
142:CUSTOM_ID: news
143:END:
144- [[https://apps.apple.com/us/app/netnewswire-rss-reader/id1480640210][NetNewsWire]]
145 - A free and open source RSS reader for Mac, iPhone, and iPad
146
147*** Photo & Video
148:PROPERTIES:
149:CUSTOM_ID: photo-video
150:END:
151- [[https://testflight.apple.com/join/Q6WyyEpS][Aislingeach]] - A quick
152 way to generate and rate images from the Stable Horde
153- [[https://apps.apple.com/us/app/unsplash/id1290631746][Unsplash]] -
154 Premium images, mostly free
155
156*** Productivity
157:PROPERTIES:
158:CUSTOM_ID: productivity
159:END:
160- [[https://apps.apple.com/us/app/beorg-to-do-list-agenda/id1238649962][beorg]]
161 - An org-mode editor, outline, and scheduler with paid extensions
162- [[https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744][Bitwarden]]
163 (=*=) - An open source password manager
164- [[https://apps.apple.com/us/app/bitwarden-authenticator/id6497335175][Bitwarden
165 Authenticator]] (=*=) - Generate 2FA on your device
166- [[https://apps.apple.com/us/app/cryptomator/id1560822163][Cryptomator]] -
167 A cross-platform encryption program
168- [[https://apps.apple.com/us/app/obsidian-connected-notes/id1557175442][Obsidian]]
169 (=*=) - A nice Markdown-based editor based on a "vault" structure.
170 Offers a paid sync solution and community extensions
171- [[https://apps.apple.com/us/app/strongbox-password-manager/id897283731][Strongbox]]
172 - Keepass password manager for iOS & macOS
173- [[https://apps.apple.com/us/app/unifi/id1057750338][UniFi Network]] -
174 View and manage most UniFi Network settings
175
176*** Safari Extensions
177:PROPERTIES:
178:CUSTOM_ID: safari-extensions
179:END:
180- [[https://apps.apple.com/us/app/adguard-adblock-privacy/id1047223162][AdGuard]]
181 - Ad blocker
182- [[https://apps.apple.com/us/app/dark-reader-for-safari/id1438243180][Dark
183 Reader]] - Dark mode for all the sites
184- [[https://apps.apple.com/us/app/pipifier/id1234771095][PiPifier]] -
185 Force videos to support PiP
186- [[https://apps.apple.com/us/app/privacy-redirect/id1578144015][Privacy
187 Redirect]] - Redirect select websites to others, usually to
188 privacy-focused alternatives
189
190*** Social Networking
191:PROPERTIES:
192:CUSTOM_ID: social-networking
193:END:
194- [[https://apps.apple.com/us/app/multitab-for-tumblr/id1071533778][MultiTab
195 T]] (=*=) - A gallery-based Tumblr client with some unique features,
196 such as tab history and sync
197- [[https://apps.apple.com/us/app/signal-private-messenger/id874139669][Signal]]
198 (=*=) - A simple, powerful, and secure messenger
199- [[https://testflight.apple.com/join/mpVk1qIy][Three Cheers]] - A
200 client for Tildes.net with a design focus that matches the intent of
201 Tildes
202- [[https://apps.apple.com/us/app/voyager-for-lemmy/id6451429762][Voyager]] -
203 A Lemmy client
204
205*** Sports
206:PROPERTIES:
207:CUSTOM_ID: sports
208:END:
209- [[https://apps.apple.com/us/app/apple-sports/id6446788829][Apple
210 Sports]] - Apple's new sports app - lacks notifications and live
211 events
212- [[https://apps.apple.com/us/app/udisc-disc-golf/id1072228953][UDisc]] -
213 Disc golf course maps, score cards, and more
214
215*** Utilities
216:PROPERTIES:
217:CUSTOM_ID: utilities
218:END:
219- [[https://apps.apple.com/us/app/backblaze/id628638330][Backblaze]] -
220 Quickly view and manage Backblaze b2 cloud storage
221- [[https://apps.apple.com/us/app/mullvad-vpn/id1488466513][Mullvad
222 VPN]] (=*=)
223 - A private VPN service
224- [[https://apps.apple.com/us/app/otp-auth/id659877384][OTP Auth]]
225 (=*=) - A minimalistic OTP app with support for biometrics, custom
226 icons, import/export, and iCloud sync
227- [[https://apps.apple.com/us/app/plex-dash/id1500797677][Plex Dash]] -
228 Stats about your Plex Media Server
229- [[https://apps.apple.com/us/app/safari/id1146562112][Safari]] - iOS
230 default browser
231- [[https://apps.apple.com/us/app/ubiquiti-wifiman/id1385561119][Unifi
232 WiFiman]]
233 - Create visual layouts of WiFi strength and save heat maps to your
234 phone
235
236** Web Apps & Shortcuts
237:PROPERTIES:
238:CUSTOM_ID: web-apps-shortcuts
239:END:
240- [[https://brutalist.report/][Brutalist Report]] - Minimal news
241 aggregator
242- [[https://cyber.report/][_Cyber.Report]] - Cybersecurity news
243 aggregator
244- [[https://news.ycombinator.com/][Hacker News]] - Mostly technical news
245- [[https://nextdns.io/][NextDNS]] - NextDNS statistics dashboard
246- [[https://readspike.com/][Readspike]] - Minimal news aggregator
content/wiki/linux.md deleted −51
@@ -1,51 +0,0 @@
1+++
2title = "Linux"
3updated = 2024-03-07
4draft = true
5+++
6
7Related:
8
9- [Hardware](/wiki/hardware/)
10
11## Alpine Linux
12
13My secondary laptop OS.
14
15## Ubuntu Server
16
17My primary server OS.
18
19- Applications
20 - CrowdSec
21 - Netdata
22 - Nginx
23 - Plex Media Server
24- Docker Containers
25 - AnonymousOverflow
26 - Authelia
27 - Convos
28 - CyberChef
29 - FlashPaper
30 - FreshRSS
31 - GotHub
32 - Gramps
33 - Invidious
34 - Libreddit
35 - NocoDB
36 - OtterWiki
37 - Portainer
38 - rgit
39 - Scrutiny
40 - SearXNG
41 - Stash
42 - Tautulli
43 - Teddit
44 - Watchtower
45- Utilities
46 - byobu
47 - Certbot
48 - exiftool
49 - fail2ban
50 - ufw
51 - zfs
content/wiki/macos.md deleted −206
@@ -1,206 +0,0 @@
1+++
2title = "macOS"
3updated = 2024-05-01
4draft = false
5+++
6
7Related:
8
9- [Hardware](/wiki/hardware/)
10
11My primary OS. Currently running macOS Sonoma 14. This wiki page contains most
12of the apps I have used at one point or another across my different Macbooks.
13
14(`*`) = My favorites
15
16## Configuration
17
18### Disable System Services
19
20- [Disabling and Enabling System Integrity
21 Protection](https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection)
22- Disable Gatekeeper: `sudo spctl --master-disable`
23
24### Dotfiles
25
26These are probably out of date, but they give a general idea of how I configure
27my machine.
28
29```conf
30# ~/.zshrc
31export PATH="/opt/homebrew/bin:$PATH"
32export PATH="$HOME/.emacs.d/bin:$PATH"
33export EDITOR="/opt/homebrew/bin/emacs -nw"
34ZSH_THEME="bureau"
35plugins=(git zsh-autosuggestions)
36source $ZSH/oh-my-zsh.sh
37```
38
39```conf
40# ~/.zprofile
41eval "$(/opt/homebrew/bin/brew shellenv)"
42```
43
44```conf
45# ~/.config/skhd/skhdrc
46cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
47cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
48```
49
50```conf
51# ~/.config/yabai/yabairc
52yabai -m config \
53 mouse_follows_focus off \
54 focus_follows_mouse off \
55 window_origin_display default \
56 window_placement second_child \
57 window_zoom_persist on \
58 window_shadow on \
59 window_animation_duration 0.0 \
60 window_animation_frame_rate 120 \
61 window_opacity_duration 0.0 \
62 active_window_opacity 1.0 \
63 normal_window_opacity 0.90 \
64 window_opacity off \
65 insert_feedback_color 0xffd75f5f \
66 split_ratio 0.50 \
67 split_type auto \
68 auto_balance off \
69 top_padding 15 \
70 bottom_padding 15 \
71 left_padding 15 \
72 right_padding 15 \
73 window_gap 10 \
74 layout bsp \
75 mouse_modifier fn \
76 mouse_action1 move \
77 mouse_action2 resize \
78 mouse_drop_action swap
79echo "yabai configuration loaded.."
80```
81
82## Software
83
84### Browsers
85
86- [Librewolf](https://librewolf.net/) (`*`) - Custom version of Firefox,
87 focused on privacy and security
88 - [Bitwarden](https://bitwarden.com/) - An open source password manager
89 - [Dark Reader](https://darkreader.org/) - Dark mode for all the websites
90 - [Libredirect](https://libredirect.github.io/) - Automatic web
91 redirections
92 - [Strongbox](https://strongboxsafe.com/) - Keepass password manager for
93 iOS & macOS
94 - [uBlock Origin](https://ublockorigin.com/) - Free, open-source ad
95 content blocker
96- [Ungoogled
97 Chromium](https://github.com/ungoogled-software/ungoogled-chromium) - Google
98 Chromium, sans integration with Google
99- [eww](https://www.gnu.org/software/emacs/manual/html_mono/eww.html) - Emacs
100 Web Wowser, for TUI browsing
101
102### Command Line Tools
103
104- [doggo](https://github.com/mr-karan/doggo) - Command-line DNS Client for
105 Humans
106- [duf](https://github.com/muesli/duf) - Disk Usage/Free Utility - a better
107 'df' alternative
108- [dust](https://github.com/bootandy/dust) - A more intuitive version of du in
109 rust
110- [eza](https://github.com/eza-community/eza) - A modern alternative to ls
111- [macchina](https://github.com/Macchina-CLI/macchina) - A system information
112 frontend with an emphasis on performance.
113- [yt-dlp](https://github.com/yt-dlp/yt-dlp) - A youtube-dl fork with
114 additional features and fixes
115
116### Communications
117
118- [Element](https://element.io/) (`*`) - Matrix's default GUI client
119- [gomuks](https://github.com/tulir/gomuks) - A terminal based Matrix client
120- [Thunderbird](https://www.thunderbird.net/) (`*`) - An open source email
121 client by Mozilla
122- [Signal](https://signal.org/) (`*`) - A simple, powerful, and secure
123 messenger
124
125### Development
126
127- [Docker Desktop](https://www.docker.com/products/docker-desktop/) - Docker
128 containers for your desktop
129 - [open-webui](https://github.com/open-webui/open-webui) - User-friendly
130 WebUI for LLMs
131- [iTerm2](https://iterm2.com/) (`*`) - The best terminal for macOS, hands
132 down
133- [Podman Desktop](https://podman-desktop.io/) (`*`) - Open source tool for
134 containers and Kubernetes
135- [Xcode](https://developer.apple.com/xcode/) - Apple's IDE
136- [zsh](https://en.wikipedia.org/wiki/Z_shell) (`*`) - My shell preference due
137 to its plugin and theme community
138 - [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)
139 - Fish-like autosuggestions for zsh
140 - [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting)
141 - Fish shell like syntax highlighting for Zsh
142
143### Editors
144
145- [Doom Emacs](https://github.com/doomemacs/doomemacs) (`*`) - An Emacs
146 framework, great for working in org-mode
147- [Obsidian](https://obsidian.md/) - A nice Markdown-based editor based on a
148 "vault" structure. Offers a paid sync solution and community extensions
149- [Standard Notes](https://standardnotes.com/) - A simple text editor focused
150 on privacy and security. Offers a paid sync solution and community
151 extensions
152- [VSCodium](https://vscodium.com/) - VS Code without proprietary blobs
153
154### Media
155
156- [Luminar](https://skylum.com/luminar) - Luminar offers top-notch photo
157 editing features
158- [Minecraft](https://www.minecraft.net/) - Block mining simulator
159- [NetNewsWire](https://netnewswire.com/) - A free and open source RSS reader
160 for Mac, iPhone, and iPad
161- [Plex](https://www.plex.tv/) (`*`) - Desktop client for the Plex Media
162 Server
163- [Steam](https://store.steampowered.com/) - The top gaming marketplace for
164 computers
165- [Transmission](https://transmissionbt.com/) (`*`) - A Fast, Easy and Free
166 Bittorrent Client
167- [VLC](https://www.videolan.org/vlc/) - A free and open source cross-platform
168 multimedia player
169
170### Package Management
171
172- [Homebrew](https://brew.sh/) (`*`) - The Missing Package Manager for macOS
173 (or Linux)
174- [MacPorts](https://www.macports.org/) - A system to compile, install, and
175 manage open source software
176
177### Utilities
178
179- [BetterDisplay](https://betterdisplay.pro/) - Allows you to tweak a ton of
180 features of built-in and external screens, such as scaling, configuration
181 overrides, and color/brightness upscaling
182- [Bitwarden](https://bitwarden.com/) - An open source password manager
183- [Ice](https://icemenubar.app/) (`*`)- A powerful menu bar management tool
184- [LittleSnitch](https://obdev.at/products/littlesnitch/index.html) - Shows
185 all network connections on your Macbook, including system and privileged
186 services
187- [MicroSnitch](https://obdev.at/products/microsnitch/index.html) - Camera &
188 microphone monitoring and alterting service
189- [Mullvad](https://mullvad.net/) (`*`) - A private VPN service
190- [Ollama](https://ollama.com/) - Run Llama 2, Code Llama, and other models
191 locally on your machine
192 - [Ollama Swift](https://github.com/kghandour/Ollama-SwiftUI) - User
193 Interface made for Ollama.ai using Swift
194- [OrbStack](https://orbstack.dev/) - A fast and convenient GUI to manage
195 Docker contains and Linux VMs
196- [Raycast](https://www.raycast.com/) - A collection of tools and shortcuts,
197 an alternative to Spotlight
198- [skhd](https://github.com/koekeishiya/skhd) (`*`) - Simple hotkey daemon for
199 macOS
200- [Strongbox](https://strongboxsafe.com/) - Keepass password manager for iOS &
201 macOS
202- [Syncthing](https://syncthing.net/) (`*`) - Continuous file synchronization
203- [TinkerTool](https://www.bresink.com/osx/TinkerTool.html) - Unlock hidden
204 configuration options for macOS
205- [yabai](https://github.com/koekeishiya/yabai) (`*`) - Automatic window
206 tiling
content/wiki/macos.org added +236
@@ -0,0 +1,236 @@
1#+title: macOS
2#+date: <2024-05-01>
3
4Related:
5
6- [[/wiki/hardware/][Hardware]]
7
8My primary OS. Currently running macOS Sonoma 14. This wiki page
9contains most of the apps I have used at one point or another across my
10different Macbooks.
11
12(=*=) = My favorites
13
14** Configuration
15:PROPERTIES:
16:CUSTOM_ID: configuration
17:END:
18*** Disable System Services
19:PROPERTIES:
20:CUSTOM_ID: disable-system-services
21:END:
22- [[https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection][Disabling
23 and Enabling System Integrity Protection]]
24- Disable Gatekeeper: =sudo spctl --master-disable=
25
26*** Dotfiles
27:PROPERTIES:
28:CUSTOM_ID: dotfiles
29:END:
30These are probably out of date, but they give a general idea of how I
31configure my machine.
32
33#+begin_src conf
34# ~/.zshrc
35export PATH="/opt/homebrew/bin:$PATH"
36export PATH="$HOME/.emacs.d/bin:$PATH"
37export EDITOR="/opt/homebrew/bin/emacs -nw"
38ZSH_THEME="bureau"
39plugins=(git zsh-autosuggestions)
40source $ZSH/oh-my-zsh.sh
41#+end_src
42
43#+begin_src conf
44# ~/.zprofile
45eval "$(/opt/homebrew/bin/brew shellenv)"
46#+end_src
47
48#+begin_src conf
49# ~/.config/skhd/skhdrc
50cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
51cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
52#+end_src
53
54#+begin_src conf
55# ~/.config/yabai/yabairc
56yabai -m config \
57 mouse_follows_focus off \
58 focus_follows_mouse off \
59 window_origin_display default \
60 window_placement second_child \
61 window_zoom_persist on \
62 window_shadow on \
63 window_animation_duration 0.0 \
64 window_animation_frame_rate 120 \
65 window_opacity_duration 0.0 \
66 active_window_opacity 1.0 \
67 normal_window_opacity 0.90 \
68 window_opacity off \
69 insert_feedback_color 0xffd75f5f \
70 split_ratio 0.50 \
71 split_type auto \
72 auto_balance off \
73 top_padding 15 \
74 bottom_padding 15 \
75 left_padding 15 \
76 right_padding 15 \
77 window_gap 10 \
78 layout bsp \
79 mouse_modifier fn \
80 mouse_action1 move \
81 mouse_action2 resize \
82 mouse_drop_action swap
83echo "yabai configuration loaded.."
84#+end_src
85
86** Software
87:PROPERTIES:
88:CUSTOM_ID: software
89:END:
90*** Browsers
91:PROPERTIES:
92:CUSTOM_ID: browsers
93:END:
94- [[https://librewolf.net/][Librewolf]] (=*=) - Custom version of
95 Firefox, focused on privacy and security
96 - [[https://bitwarden.com/][Bitwarden]] - An open source password
97 manager
98 - [[https://darkreader.org/][Dark Reader]] - Dark mode for all the
99 websites
100 - [[https://libredirect.github.io/][Libredirect]] - Automatic web
101 redirections
102 - [[https://strongboxsafe.com/][Strongbox]] - Keepass password manager
103 for iOS & macOS
104 - [[https://ublockorigin.com/][uBlock Origin]] - Free, open-source ad
105 content blocker
106- [[https://github.com/ungoogled-software/ungoogled-chromium][Ungoogled
107 Chromium]] - Google Chromium, sans integration with Google
108- [[https://www.gnu.org/software/emacs/manual/html_mono/eww.html][eww]] -
109 Emacs Web Wowser, for TUI browsing
110
111*** Command Line Tools
112:PROPERTIES:
113:CUSTOM_ID: command-line-tools
114:END:
115- [[https://github.com/mr-karan/doggo][doggo]] - Command-line DNS Client
116 for Humans
117- [[https://github.com/muesli/duf][duf]] - Disk Usage/Free Utility - a
118 better 'df' alternative
119- [[https://github.com/bootandy/dust][dust]] - A more intuitive version
120 of du in rust
121- [[https://github.com/eza-community/eza][eza]] - A modern alternative
122 to ls
123- [[https://github.com/Macchina-CLI/macchina][macchina]] - A system
124 information frontend with an emphasis on performance.
125- [[https://github.com/yt-dlp/yt-dlp][yt-dlp]] - A youtube-dl fork with
126 additional features and fixes
127
128*** Communications
129:PROPERTIES:
130:CUSTOM_ID: communications
131:END:
132- [[https://element.io/][Element]] (=*=) - Matrix's default GUI client
133- [[https://github.com/tulir/gomuks][gomuks]] - A terminal based Matrix
134 client
135- [[https://www.thunderbird.net/][Thunderbird]] (=*=) - An open source
136 email client by Mozilla
137- [[https://signal.org/][Signal]] (=*=) - A simple, powerful, and secure
138 messenger
139
140*** Development
141:PROPERTIES:
142:CUSTOM_ID: development
143:END:
144- [[https://www.docker.com/products/docker-desktop/][Docker Desktop]] -
145 Docker containers for your desktop
146 - [[https://github.com/open-webui/open-webui][open-webui]] -
147 User-friendly WebUI for LLMs
148- [[https://iterm2.com/][iTerm2]] (=*=) - The best terminal for macOS,
149 hands down
150- [[https://podman-desktop.io/][Podman Desktop]] (=*=) - Open source
151 tool for containers and Kubernetes
152- [[https://developer.apple.com/xcode/][Xcode]] - Apple's IDE
153- [[https://en.wikipedia.org/wiki/Z_shell][zsh]] (=*=) - My shell
154 preference due to its plugin and theme community
155 - [[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]]
156 - Fish-like autosuggestions for zsh
157 - [[https://github.com/zsh-users/zsh-syntax-highlighting][zsh-syntax-highlighting]]
158 - Fish shell like syntax highlighting for Zsh
159
160*** Editors
161:PROPERTIES:
162:CUSTOM_ID: editors
163:END:
164- [[https://github.com/doomemacs/doomemacs][Doom Emacs]] (=*=) - An
165 Emacs framework, great for working in org-mode
166- [[https://obsidian.md/][Obsidian]] - A nice Markdown-based editor
167 based on a "vault" structure. Offers a paid sync solution and
168 community extensions
169- [[https://standardnotes.com/][Standard Notes]] - A simple text editor
170 focused on privacy and security. Offers a paid sync solution and
171 community extensions
172- [[https://vscodium.com/][VSCodium]] - VS Code without proprietary
173 blobs
174
175*** Media
176:PROPERTIES:
177:CUSTOM_ID: media
178:END:
179- [[https://skylum.com/luminar][Luminar]] - Luminar offers top-notch
180 photo editing features
181- [[https://www.minecraft.net/][Minecraft]] - Block mining simulator
182- [[https://netnewswire.com/][NetNewsWire]] - A free and open source RSS
183 reader for Mac, iPhone, and iPad
184- [[https://www.plex.tv/][Plex]] (=*=) - Desktop client for the Plex
185 Media Server
186- [[https://store.steampowered.com/][Steam]] - The top gaming
187 marketplace for computers
188- [[https://transmissionbt.com/][Transmission]] (=*=) - A Fast, Easy and
189 Free Bittorrent Client
190- [[https://www.videolan.org/vlc/][VLC]] - A free and open source
191 cross-platform multimedia player
192
193*** Package Management
194:PROPERTIES:
195:CUSTOM_ID: package-management
196:END:
197- [[https://brew.sh/][Homebrew]] (=*=) - The Missing Package Manager for
198 macOS (or Linux)
199- [[https://www.macports.org/][MacPorts]] - A system to compile,
200 install, and manage open source software
201
202*** Utilities
203:PROPERTIES:
204:CUSTOM_ID: utilities
205:END:
206- [[https://betterdisplay.pro/][BetterDisplay]] - Allows you to tweak a
207 ton of features of built-in and external screens, such as scaling,
208 configuration overrides, and color/brightness upscaling
209- [[https://bitwarden.com/][Bitwarden]] - An open source password
210 manager
211- [[https://icemenubar.app/][Ice]] (=*=)- A powerful menu bar management
212 tool
213- [[https://obdev.at/products/littlesnitch/index.html][LittleSnitch]] -
214 Shows all network connections on your Macbook, including system and
215 privileged services
216- [[https://obdev.at/products/microsnitch/index.html][MicroSnitch]] -
217 Camera & microphone monitoring and alterting service
218- [[https://mullvad.net/][Mullvad]] (=*=) - A private VPN service
219- [[https://ollama.com/][Ollama]] - Run Llama 2, Code Llama, and other
220 models locally on your machine
221 - [[https://github.com/kghandour/Ollama-SwiftUI][Ollama Swift]] - User
222 Interface made for Ollama.ai using Swift
223- [[https://orbstack.dev/][OrbStack]] - A fast and convenient GUI to
224 manage Docker contains and Linux VMs
225- [[https://www.raycast.com/][Raycast]] - A collection of tools and
226 shortcuts, an alternative to Spotlight
227- [[https://github.com/koekeishiya/skhd][skhd]] (=*=) - Simple hotkey
228 daemon for macOS
229- [[https://strongboxsafe.com/][Strongbox]] - Keepass password manager
230 for iOS & macOS
231- [[https://syncthing.net/][Syncthing]] (=*=) - Continuous file
232 synchronization
233- [[https://www.bresink.com/osx/TinkerTool.html][TinkerTool]] - Unlock
234 hidden configuration options for macOS
235- [[https://github.com/koekeishiya/yabai][yabai]] (=*=) - Automatic
236 window tiling
content/wiki/management.md deleted −11
@@ -1,11 +0,0 @@
1+++
2title = "Management"
3updated = 2024-03-07
4draft = true
5+++
6
7## Time Management
8
9## People Management
10
11## Project Management
content/wiki/minimalism.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Minimalism"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/networking.md deleted −27
@@ -1,27 +0,0 @@
1+++
2title = "Networking"
3updated = 2024-03-07
4draft = true
5+++
6
7## Bluetooth
8
9## DNS
10
11## Gemini
12
13## Gopher
14
15## HTTP
16
17## IoT
18
19## Nginx
20
21## SSH
22
23## SSL
24
25## VPN
26
27## Wi-Fi
content/wiki/plaintext.md deleted −11
@@ -1,11 +0,0 @@
1+++
2title = "Plaintext"
3updated = 2024-03-07
4draft = true
5+++
6
7## Markdown
8
9## Org-Mode
10
11## AsciiDoc
content/wiki/privacy.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Privacy"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/python.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Python"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/security.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Security"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/selfhosted.md deleted −5
@@ -1,5 +0,0 @@
1+++
2title = "Self-Hosted"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/windows.md deleted −18
@@ -1,18 +0,0 @@
1+++
2title = "Windows"
3updated = 2024-03-07
4draft = true
5+++
6
7## Windows (Work)
8
9I am required to use a Windows 10 laptop for work, but I have some
10limited customization options.
11
12- Alteryx
13- Notepad++
14- Obsidian
15- Power BI
16- Task Scheduler
17 - Auto Dark Mode
18 - Auto Light Mode
publish.el added +98
@@ -0,0 +1,98 @@
1;; explicity load packages since I'm using Doom Emacs
2(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/emacs-htmlize")
3(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/weblorg")
4(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/templatel")
5(require 'htmlize)
6(require 'weblorg)
7
8;; defaults to http://localhost:8000
9;; ENV=prod emacs --script publish.el
10(if (string= (getenv "ENV") "prod")
11 (setq weblorg-default-url "https://cleberg.net"))
12
13(weblorg-site
14 :theme nil
15 :template-vars '(("site_name" . "cleberg.net")
16 ("site_owner" . "Christian Cleberg <hello@cleberg.net>")
17 ("site_description" . "Just a blip of ones and zeroes.")))
18
19;; route for rendering the index page of the website
20(weblorg-route
21 :name "index"
22 :input-pattern "content/index.org"
23 :template "index.html"
24 :output ".build/index.html"
25 :url "/")
26
27;; route for rendering each post
28(weblorg-route
29 :name "blog"
30 :input-pattern "content/blog/*.org"
31 :template "post.html"
32 :output ".build/blog/{{ slug }}.html"
33 :url "/blog/{{ slug }}.html")
34
35;; route for rendering the index page of the blog
36(weblorg-route
37 :name "blog-index"
38 :input-pattern "content/blog/*.org"
39 :input-aggregate #'weblorg-input-aggregate-all-desc
40 :template "blog.html"
41 :output ".build/blog/index.html"
42 :url "/blog/")
43
44;; route for rendering each wiki post
45(weblorg-route
46 :name "wiki"
47 :input-pattern "content/wiki/*.org"
48 :template "post.html"
49 :output ".build/wiki/{{ slug }}.html"
50 :url "/wiki/{{ slug }}.html")
51
52;; route for rendering the index page of the wiki
53(weblorg-route
54 :name "wiki-index"
55 :input-pattern "content/wiki/*.org"
56 :input-aggregate #'weblorg-input-aggregate-all
57 :template "wiki.html"
58 :output ".build/wiki/index.html"
59 :url "/wiki/")
60
61;; route for rendering each page
62(weblorg-route
63 :name "pages"
64 :input-pattern "content/*.org"
65 :template "page.html"
66 :output ".build/{{ slug }}.html"
67 :url "/{{ slug }}.html")
68
69(weblorg-route
70 :name "salary"
71 :input-pattern "content/salary/*.org"
72 :template "page.html"
73 :output ".build/salary/{{ slug }}.html"
74 :url "/salary/{{ slug }}.html")
75
76(weblorg-route
77 :name "services"
78 :input-pattern "content/services/*.org"
79 :template "page.html"
80 :output ".build/services/{{ slug }}.html"
81 :url "/services/{{ slug }}.html")
82
83;; RSS Feed
84(weblorg-route
85 :name "rss"
86 :input-pattern "content/blog/*.org"
87 :input-aggregate #'weblorg-input-aggregate-all-desc
88 :template "feed.xml"
89 :output ".build/feed.xml"
90 :url "/feed.xml")
91
92;; route for static assets that also copies files to .build directory
93(weblorg-copy-static
94 :output ".build/{{ file }}"
95 :url "/{{ file }}")
96
97;; fire the engine and export all the files declared in the routes above
98(weblorg-export)
static/syntax-theme-dark.css deleted −280
@@ -1,280 +0,0 @@
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 deleted −407
@@ -1,407 +0,0 @@
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 −53
@@ -1,53 +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:
9 dark)">
10 <meta name="theme-color" content="#fff" media="(prefers-color-scheme:
11 light)">
12 {% if page %} {% if page.description %}
13 <meta name="description" content="{{ page.description }}">
14 {% elif config.description %}
15 <meta name="description" content="{{ config.description }}">
16 {% endif %} {% elif config.description %}
17 <meta name="description" content="{{ config.description }}" /> {% endif
18 %}
19
20 {% block rss %}
21 <link rel="alternate" type="application/atom+xml" title="RSS" href="{{
22 get_url(path="atom.xml", trailing_slash=false) }}">
23 {% endblock %}
24
25 {% block css %}
26 <link rel="stylesheet" href="/styles.min.css">
27 {% endblock css %}
28
29 {% block extra_head %}{% endblock extra_head %}
30</head>
31<body>
32 <nav class="site-nav" aria-label="site-nav" role="navigation">
33 <ul>
34 <li><a href="/">Home</a></li>
35 <li><a href="/blog/">Blog</a></li>
36 <li><a href="/services/">Services</a></li>
37 <li><a href="/wiki/">Wiki</a></li>
38 </ul>
39 </nav>
40
41 <main>{% block content %}{% endblock content %}</main>
42
43 <footer>
44 <p>Last build: &lt;{{ now() | date(format='%Y-%m-%d %a %H:%M:%S') }}&gt;</p>
45 <p>
46 <a href="https://git.sr.ht/~cyborg/cleberg.net"
47 target="_blank" rel="noopener">Source Code</a>
48 &middot;
49 <a href="/atom.xml">Atom RSS Feed</a>
50 </p>
51 </footer>
52</body>
53</html>
templates/blog.html deleted −20
@@ -1,20 +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
7reader.</p>
8{% set post_date = "1970-01-01" | date(format="%Y") %}
9{% for page in section.pages %}
10{% if post_date != page.date | date(format="%Y") %}
11{% set_global post_date = page.date | date(format="%Y") %}
12 <h2>{{ post_date }}</h2>
13 {% endif %}
14 <div class="post">
15 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{
16 page.date | date(format='%Y-%m-%d') }}</time>
17 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
18 </div>
19{% endfor %}
20{% endblock content %}
templates/index.html deleted −58
@@ -1,58 +0,0 @@
1{% extends "base.html" %} {% block content %}
2<section>
3 <h1>cleberg.net</h1>
4 <pre>pub rsa4096 2022-11-16 [SC]
5 <a href="/gpg.txt">3917 973F B159 BBB8 6194 5385 6945 1A51 7AC0 CB37</a>
6uid [ultimate] Christian Cleberg &lt;hello@cleberg.net&gt;</pre>
7</section>
8<section>
9 <h2>Recent Blog Posts</h2>
10 {% set section = get_section(path="blog/_index.md") %} {% set i = 0 %} {% for
11 page in section.pages %} {% if i < 3%} {% set_global i = i + 1%}
12 <div class="post">
13 <time
14 datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S')
15 }}"
16 >{{ page.date | date(format='%Y-%m-%d') }}</time
17 >
18 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
19 </div>
20 {% endif %} {% 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="https://cv.cleberg.net">Curriculum Vitae</a></li>
28 <li><a href="/salary/">Salary</a></li>
29 <li><a href="/services/">Services</a></li>
30 <li><a href="/wiki/">Wiki</a></li>
31 </ul>
32</section>
33<section>
34 <h2>Contact</h2>
35 <ul>
36 <li>hello [at] cleberg.net (<a href="/gpg.txt">GPG</a>)</li>
37 <li>
38 cyb0rg on
39 <a href="https://libera.chat" target="_blank" rel="noopener"
40 >libera.chat</a
41 >
42 </li>
43 <li>
44 <a
45 href="https://matrix.to/#/@cyborg:matrix.tchncs.de"
46 target="_blank"
47 rel="noopener"
48 >@cyborg:@matrix.tchncs.de</a
49 >
50 </li>
51 <li>
52 <a href="https://git.sr.ht/~cyborg/" target="_blank" rel="noopener"
53 >Sourcehut</a
54 >
55 </li>
56 </ul>
57</section>
58{% endblock content %}
templates/page.html deleted −55
@@ -1,55 +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"
9media="(prefers-color-scheme: dark)">
10<link rel="stylesheet" href="/syntax-theme-light.css"
11media="(prefers-color-scheme: light)">
12<link rel="stylesheet" href="/styles.css">
13{% endblock css %}
14
15{% block content %}
16<section class="post-metadata">
17 <h1>{{ page.title }}</h1>
18 {% if page.date %}
19 <p><time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{
20 page.date }}</time></p>
21 {% endif %} {% if page.updated %}
22 <p>Last Updated: <time datetime="{{ page.updated | date(format='%Y-%m-%d
23 %H:%M:%S') }}">{{ page.updated }}</time></p>
24 {% endif %}
25 <p>{{ page.word_count }} words; {{ page.reading_time }} minute(s)</p>
26{% if page.toc %}
27 <details open>
28 <summary>Table of Contents</summary>
29 <nav class="page-nav" aria-label="page-nav" role="navigation">
30 <ul>
31 {% for h1 in page.toc %}
32 <li>
33 <a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
34 {% if h1.children %}
35 <ul>
36 {% for h2 in h1.children %}
37 <li>
38 <a href="{{ h2.permalink | safe }}">{{
39 h2.title }}</a>
40 </li>
41 {% endfor %}
42 </ul>
43 {% endif %}
44 </li>
45 {% endfor %}
46 </ul>
47 </nav>
48 </details>
49{% endif %}
50</section>
51
52<article role="article">
53{{ page.content | safe }}
54</article>
55{% endblock content %}
templates/wiki.html deleted −16
@@ -1,16 +0,0 @@
1{% extends "base.html" %}
2
3{% block content %}
4<h1>{{ section.title }}</h1>
5<p>Use <code>Ctrl + F</code> to search wiki pages for keywords.</p>
6<div class="wiki-post">
7 <p>Wiki Page</p>
8 <p>Last Updated</p>
9</div>
10{% for page in section.pages %}
11 <div class="wiki-post">
12 <p><a href="/wiki/{{ page.slug }}/">{{ page.title }}</a></p>
13 <time datetime="{{ page.updated | date(format='%Y-%m-%d %H:%M:%S')
14 }}">{{ page.updated | date(format='%Y-%m-%d')}}</p>
15 </div>
16{% endfor %} {% endblock content %}
static/.well-known/security.txt → theme/static/.well-known/security.txt renamed
static/gpg.txt → theme/static/gpg.txt renamed
static/robots.txt → theme/static/robots.txt renamed
static/salary.csv → theme/static/salary.csv renamed
static/styles.css → theme/static/styles.css renamed
static/styles.min.css → theme/static/styles.min.css renamed
theme/templates/base.html added +39
@@ -0,0 +1,39 @@
1<!doctype html>
2<html lang="en-us">
3<head>
4 {% block head %}
5 <meta charset="utf-8">
6 <title>
7 {% block subtitle %}{% endblock %}
8 {% block title %}{{ site_name | default("weblorg website") }}{% endblock %}
9 </title>
10 <meta name="viewport" content="width=device-width, initial-scale=1">
11 {% if site_owner is defined %}<meta name="author" content="{{ site_owner }}" >{% endif %}
12 {% if site_description is defined %}<meta name="description" content="{{ site_description }}" >{% endif %}
13 {% if site_keywords is defined %}<meta name="keywords" content="{{ site_keywords }}" >{% endif %}
14 <link rel="stylesheet" href="{{ url_for("static", file="styles.min.css") }}" type="text/css">
15 <link rel="icon" href="data:,">
16 {% block meta %}{% endblock %}
17 {% endblock %}
18</head>
19<body>
20 <nav class="site-nav" aria-label="site-nav" role="navigation">
21 <ul>
22 <li><a href="/">Home</a></li>
23 <li><a href="/blog/">Blog</a></li>
24 <li><a href="/services/">Services</a></li>
25 <li><a href="/wiki/">Wiki</a></li>
26 </ul>
27 </nav>
28 <main>{% block main %}{% endblock %}</main>
29 <footer>
30 <p>Last build: &lt;{{ now() | strftime("%Y-%m-%d %a %H:%M:%S") }}&gt;</p>
31 <p>
32 <a href="https://git.sr.ht/~cyborg/cleberg.net"
33 target="_blank" rel="noopener">Source Code</a>
34 &middot;
35 <a href="/feed.xml">RSS Feed</a>
36 </p>
37 </footer>
38</body>
39</html>
theme/templates/blog.html added +15
@@ -0,0 +1,15 @@
1{% extends "base.html" %} {% block subtitle %}Blog | {% endblock %} {% block
2main %}
3<h1>Blog</h1>
4<p>Use <code>Ctrl + F</code> to search blog post titles for keywords.</p>
5<p>
6 You can also add the <a href="/feed.xml">RSS Feed</a> to your feed reader.
7</p>
8{% for post in posts %}
9<div class="post">
10 <time datetime='{{ post.date | strftime("%Y-%m-%d") }}'
11 >{{ post.date|strftime("%Y-%m-%d") }}</time
12 >
13 <a href='{{ url_for("blog", slug=post.slug) }}'>{{ post.title }}</a>
14</div>
15{% endfor %} {% endblock %}
theme/templates/feed.xml added +37
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="UTF-8" ?>
2<rss version="2.0"
3 xmlns:content="http://purl.org/rss/1.0/modules/content/"
4 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:atom="http://www.w3.org/2005/Atom"
7 xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
8 xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
9 xmlns:georss="http://www.georss.org/georss"
10 xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
11 xmlns:media="http://search.yahoo.com/mrss/">
12 <channel>
13 <title>{{ site_name | default("cleberg.net") }}</title>
14 <atom:link
15 href="{{ url_for("rss") }}"
16 rel="self" type="application/rss+xml" />
17 <link>{{ url_for("index") }}</link>
18 <description><![CDATA[]]></description>
19 <language>en</language>
20 <pubDate>{{ posts | first | getattr("date") | strftime("%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
21 <lastBuildDate>{{ now() | strftime("%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
22 <generator>{{ meta.generator }}</generator>
23 <webMaster>{{ site_owner | default("hello@cleberg.net") }}</webMaster>
24
25 {% for post in posts %}
26 <item>
27 <title>{{ post.title }}</title>
28 <link>{{ url_for("blog", slug=post.slug) }}</link>
29 <author>{{ post.author|default("hello@cleberg.net") }}</author>
30 <guid isPermaLink="false">{{ url_for("blog", slug=post.slug) }}</guid>
31 <pubDate>{{ post.date|strftime("%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
32 <description><![CDATA[{{ post.html|safe }}]]></description>
33 </item>
34 {% endfor %}
35
36 </channel>
37</rss>
theme/templates/index.html added +60
@@ -0,0 +1,60 @@
1{% extends "base.html" %} {% block main %}
2<section>
3 <h1>cleberg.net</h1>
4 <pre>pub rsa4096 2022-11-16 [SC]
5 <a href="/gpg.txt">3917 973F B159 BBB8 6194 5385 6945 1A51 7AC0 CB37</a>
6uid [ultimate] Christian Cleberg &lt;hello@cleberg.net&gt;</pre>
7</section>
8<section>
9 <h2>Recent Blog Posts</h2>
10
11 <div class="post">
12 <time datetime="2024-07-11 20:24:02">2024-07-11</time>
13 <a href="/blog/emacs-on-ipad/">Emacs on iPadOS</a>
14 </div>
15
16 <div class="post">
17 <time datetime="2024-06-19 08:00:00">2024-06-19</time>
18 <a href="/blog/deprecated-trusted-gpg-fix/"
19 >Fixing Ubuntu Error: 'Key is stored in legacy trusted.gpg keyring'</a
20 >
21 </div>
22
23 <div class="post">
24 <time datetime="2024-05-03 00:00:00">2024-05-03</time>
25 <a href="/blog/ubuntu-on-macos/"
26 >Running Ubuntu Linux on macOS with OrbStack</a
27 >
28 </div>
29
30 <br />
31 <a href="/blog/">All Posts →</a>
32</section>
33
34<section>
35 <h2>Everything Else</h2>
36 <ul>
37 <li><a href="https://cv.cleberg.net">Curriculum Vitae</a></li>
38 <li><a href="/salary/">Salary</a></li>
39 <li><a href="/services/">Services</a></li>
40 <li><a href="/wiki/">Wiki</a></li>
41 </ul>
42</section>
43
44<section>
45 <h2>Contact</h2>
46 <ul>
47 <li>
48 hello [at] cleberg.net (<a href="https://cleberg.net/gpg.txt">GPG</a>)
49 </li>
50 <li>cyb0rg on <a href="https://libera.chat/">libera.chat</a></li>
51 <li>
52 <a href="https://matrix.to/#/@cyborg:matrix.tchncs.de"
53 >@cyborg:@matrix.tchncs.de</a
54 >
55 </li>
56 <li><a href="https://git.sr.ht/~cyborg/">Sourcehut</a></li>
57 </ul>
58</section>
59
60{% endblock %}
theme/templates/page.html added +11
@@ -0,0 +1,11 @@
1{% extends "base.html" %}
2
3{% block subtitle %}
4 {{ post.title }}
5{% endblock %}
6{% block main %}
7<article role="article">
8 <h1>{{ post.title }}</h1>
9 <section>{{ post.html | safe }}</section>
10</article>
11{% endblock %}
theme/templates/post.html added +21
@@ -0,0 +1,21 @@
1{% extends "base.html" %} {% block subtitle %} {{ post.title }} | {% endblock %}
2{% block meta %}
3<meta property="og:title" content="{{ post.title }}" />
4<meta property="og:url" content="{{ post.url }}" />
5{% if post.description is defined %}
6<meta property="og:description" content="{{ post.description }}" />
7{% elif project_description is defined %}
8<meta property="og:description" content="{{ project_description }}" />
9{% endif %} {% endblock %} {% block main %}
10<article role="article">
11 <section class="post-metadata">
12 <h1 class="post__title">{{ post.title }}</h1>
13 {% if post.date is defined %}
14 <time datetime='{{ post.date|strftime("%Y-%m-%d") }}'
15 >&lt;{{ post.date|strftime("%Y-%m-%d %a %H:%M:%S") }}&gt;</time
16 >
17 {% endif %}
18 </section>
19 <section>{{ post.html | safe }}</section>
20</article>
21{% endblock %}
theme/templates/wiki.html added +12
@@ -0,0 +1,12 @@
1{% extends "base.html" %} {% block subtitle %}Wiki | {% endblock %} {% block
2main %}
3<h1>Wiki</h1>
4<p>Use <code>Ctrl + F</code> to search wiki pages for keywords.</p>
5{% for post in posts %}
6<div class="post">
7 <time datetime='{{ post.date | strftime("%Y-%m-%d") }}'
8 >{{ post.date|strftime("%Y-%m-%d") }}</time
9 >
10 <a href='{{ url_for("wiki", slug=post.slug) }}'>{{ post.title }}</a>
11</div>
12{% endfor %} {% endblock %}
utils/nginx-dev.conf added +17
@@ -0,0 +1,17 @@
1server {
2 server_name cleberg.net www.cleberg.net;
3
4 root /usr/share/nginx/html;
5 index index.html;
6 autoindex on;
7
8 access_log /var/log/nginx/cleberg.net.access.log;
9 error_log /var/log/nginx/cleberg.net.error.log;
10
11 location / {
12 try_files $uri $uri/ =404;
13 }
14
15 listen [::]:80;
16 listen 80;
17}
salary_visualization.py → utils/salary_visualization.py renamed