Commit 63a5afda03

63a5afda03daefb45e061a055685335207f21b60

parent: eac7b5d084

Unsigned

cmc <hello@cleberg.net> · 2024-04-27 22:01 UTC

test conversion back to markdown

Layout: unified · split

.gitignore +1 −1
@@ -1,3 +1,3 @@
1.DS_Store 1.DS_Store
2.build
3.vscode 2.vscode
3public\ No newline at end of file
README.md added +39
@@ -0,0 +1,39 @@
1# cleberg.net
2
3[cleberg.net](https://cleberg.net) is my personal webpage.
4
5This README is viewable on [git.cleberg.net](https://git.cleberg.net/?p=cleberg.net.git;a=blob;f=README.org;h=155f4696a09503411730afc422b7fb49acbef8f3;hb=HEAD).
6
7## Overview
8
9This website & blog uses [Markdown](https://spec.commonmark.org/current/), published with [Zola](https://www.getzola.org/).
10
11## Configuration
12
13All configuration options are available within the `config.toml` file. Refer to the Zola documentation for further configuration options.
14
15## Building
16
17To get running:
18
19```sh
20git clone https://git.cleberg.net/cleberg.net.git && \
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
31- [ ] Format all blog posts
32 - [ ] 2018
33 - [ ] 2019
34 - [ ] 2020
35 - [ ] 2021
36 - [ ] 2022
37 - [ ] 2023
38 - [ ] 2024
39- [ ] Figure out how to get file tags to show up
README.org deleted −61
@@ -1,61 +0,0 @@
1#+title: README
2#+description: Project README.
3
4* cleberg.net
5
6[[https://cleberg.net][cleberg.net]] is my personal webpage.
7
8This README is viewable on [[https://git.cleberg.net/?p=cleberg.net.git;a=blob;f=README.org;h=155f4696a09503411730afc422b7fb49acbef8f3;hb=HEAD][git.cleberg.net]].
9
10** Overview
11
12This website & blog uses [[https://orgmode.org/][Org-Mode]], published with [[https://github.com/emacs-love/weblorg][weblorg]].
13
14** Configuration
15
16All configuration options are available within the =publish.el= file. Refer to
17the weblorg documentation for further configuration options.
18
19** Building
20
21Local testing is available via [[https://www.gnu.org/software/emacs/][Emacs]] or through the command line.
22
23To get running:
24
25#+begin_src shell
26git clone https://git.cleberg.net/cleberg.net.git && \
27cd cleberg.net && \
28emacs -nw
29#+end_src
30
31Within Emacs, open any of the repository files. In Doom, I do this with =Spc f
32f= and selecting =README.org=. Make any changes necessary to customize the
33project.
34
35To publish, you can use the =build.sh= script (change the deployment target!) or
36you can run the following commands.
37
38Use the =ENV= environment variable to determine which base URL weblorg will use.
39If ENV is ommitted, it will default to =localhost:8000=. If =ENV=prod=, weblorg
40will look in the =publish.el= file for the production base URL.
41
42#+begin_src sh
43ENV=prod emacs --script publish.el
44#+end_src
45
46The files will be published to the =.build= directory. You can deploy these
47files to the target through any number of methods, such as =scp= or SFTP.
48
49** Tasks
50
51*** DONE Create RSS feed
52*** TODO Format all blog posts with =M q=
53**** DONE 2018
54**** DONE 2019
55**** DONE 2020
56**** TODO 2021
57**** TODO 2022
58**** TODO 2023
59**** TODO 2024
60*** DONE Create script to auto-generate the =/blog/= list and =/= most recent posts
61*** TODO Figure out how to get filetags to show up
build.sh +5 −4
@@ -1,12 +1,13 @@
1#!/bin/bash 1#!/bin/bash
2 2
3if [ "$ENV" == "prod" ]; then 3if [ "$ENV" == "prod" ]; then
4 echo "Environment = Production" && \ 4 echo "Environment: Production" && \
5 rm -rf .build/* && \ 5 rm -rf .build/* && \
6 emacs --script publish.el && \ 6 zola build && \
7 rsync -avzP .build/ ubuntu:/var/www/cleberg.net/ 7 rsync -avzP .build/ ubuntu:/var/www/cleberg.net/
8else 8else
9 echo "Environment = Development" && \ 9 echo "Environment: Development" && \
10 rm -rf .build/* && \ 10 rm -rf .build/* && \
11 emacs --script publish.el 11 zola build && \
12 zola serve
12fi 13fi
config.toml added +21
@@ -0,0 +1,21 @@
1# Site settings
2base_url = "https://cleberg.net"
3title = "cleberg.net"
4description = "The personal website of Christian Cleberg."
5author = "Christian Cleberg"
6
7# RSS settings
8generate_feed = true
9feed_filename = "atom.xml"
10
11[markdown]
12highlight_code = true
13highlight_theme = "css"
14highlight_themes_css = [
15 { theme = "ayu-dark", filename = "syntax-theme-dark.css" },
16 { theme = "inspired-github", filename = "syntax-theme-light.css" },
17]
18external_links_target_blank = true
19lazy_async_image = true
20
21[extra]
content/blog/2018-11-28-aes-encryption.md added +125
@@ -0,0 +1,125 @@
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
13the National Institute of Standards and Technology, sub-selected from
14the Rijndael family of ciphers (128, 192, and 256 bits) in 2001.
15Furthering its popularity and status, the US government chose AES as
16their default encryption method for top-secret data, removing the
17previous standard which had been in place since 1977.
18
19AES has proven to be an extremely safe encryption method, with 7-round
20and 8-round attacks making no material improvements since the release of
21this encryption standard almost two decades ago.
22
23> Though many papers have been published on the cryptanalysis of AES,
24> the fastest single-key attacks on round-reduced AES variants \[20,
25> 33\] so far are only slightly more powerful than those proposed 10
26> years ago \[23,24\].
27>
28> - [Bogdonav, et
29> al.](http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf)
30
31# How Secure is AES?
32
33In theory, AES-256 is non-crackable due to the massive number of
34combinations that can be produced. However, AES-128 is no longer
35recommended as a viable implementation to protect important data.
36
37A semi-short [comic
38strip](http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html)
39from Moserware quickly explains AES for the public to understand.
40Basically AES encrypts the data by obscuring the relationship between
41the data and the encrypted data. Additionally, this method spreads the
42message out. Lastly, the key produced by AES is the secret to decrypting
43it. Someone may know the method of AES, but without the key, they are
44powerless.
45
46To obscure and spread the data out, AES creates a
47substitution-permutation network. Wikipedia has a wonderful [example of
48an SP
49network](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png)
50available. This network sends the data through a set of S boxes (using
51the unique key) to substitute the bits with another block of bits. Then,
52a P box will permutate, or rearrange, the bits. This is done over and
53over, with the key being derived from the last round. For AES, the key
54size specifies the number of transformation rounds: 10, 12, and 14
55rounds for 128-bit, 192-bit, and 256-bit keys, respectively.
56
57# The Process
58
591. \*KeyExpansion=: Using [Rijndael\'s key
60 schedule](https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard),
61 the keys are dynamically generated.
622. **AddRoundKey**: Each byte of the data is combined with this key
63 using bitwise xor.
643. **SubBytes**: This is followed by the substitution of each byte of
65 data.
664. **ShiftRows**: Then, the final three rows are shifted a certain
67 number of steps, dictated by the cipher.
685. **MixColumns**: After the rows have been shifted, the columns are
69 mixed and combined.
70
71This process does not necessarily stop after one full round. Steps 2
72through 5 will repeat for the number of rounds specified by the key.
73However, the final round excludes the MixColumns step. As you can see,
74this is a fairly complex process. One must have a solid understanding of
75general mathematic principles to fully understand how the sequence works
76(and to even attempt to find a weakness).
77
78According to research done by Bogdanov et al., it would take billions of
79years to brute force a 126-bit key with current hardware. Additionally,
80this brute force attack would require storing 2^88^ bits of data!
81However, there are a few different attacks that have been used to show
82vulnerabilities with the use of this technology. Side-channel attacks
83use inadvertent leaks of data from the hardware or software, which can
84allow attackers to obtain the key or run programs on a user\'s hardware.
85
86Please note that this is not something you should run out and try to
87implement in your `Hello, World!` app after only a few hours
88of research. While AES (basically all encryption methods) is extremely
89efficient in what it does, it takes a lot of time and patience to
90understand. If you\'re looking for something which currently implements
91AES, check out the [Legion of the Bouncy
92Castle](https://www.bouncycastle.org/documentation.html) for Java
93implementations of cryptographic algorithms.
94
95# Why Does Encryption Matter?
96
97There are limitless reasons to enable encryption at-rest or in-transit
98for various aspects of your digital life. You can research specific
99examples, such as [Australia passes new law to thwart strong
100encryption](https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/).
101However, I will simply list a few basic reasons to always enable
102encryption, where feasible:
103
1041. Privacy is a human right and is recognized as a national right in
105 some countries (e.g., [US Fourth
106 Amendment](https://www.law.cornell.edu/wex/fourth_amendment)).
1072. \"Why not?\" Encryption rarely affects performance or speed, so
108 there\'s 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
113 lives.
1144. Wiping or factory-resetting does not actually wipe all data from the
115 storage device. There are methods to read data from the physical
116 disks/boards inside devices.
1175. Corporations, governments, and other nefarious groups/individuals
118 are actively looking for ways to collect personal information about
119 anyone they can. If someone\'s data is unencrypted, that person may
120 become a target due to the ease of data collection.
121
122​**Read More:**
123
124- [Federal Information Processing Standards Publication
125 197](http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf)
content/blog/2018-11-28-aes-encryption.org deleted −103
@@ -1,103 +0,0 @@
1#+title: AES Encryption
2#+description: Learn how the AES Encryption algorithm works.
3#+date: <2018-11-28 Wed>
4#+filetags: :security:
5
6* Basic AES
7If you're not familiar with encryption techniques, [[https://en.wikipedia.org/wiki/Advanced_Encryption_Standard][AES]] is the *Advanced
8Encryption Standard*. This specification was established by the National
9Institute of Standards and Technology, sub-selected from the Rijndael family of
10ciphers (128, 192, and 256 bits) in 2001. Furthering its popularity and status,
11the US government chose AES as their default encryption method for top-secret
12data, removing the previous standard which had been in place since 1977.
13
14AES has proven to be an extremely safe encryption method, with 7-round and
158-round attacks making no material improvements since the release of this
16encryption standard almost two decades ago.
17
18#+begin_quote
19Though many papers have been published on the cryptanalysis of AES, the fastest
20single-key attacks on round-reduced AES variants [20, 33] so far are only
21slightly more powerful than those proposed 10 years ago [23,24].
22
23- [[http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf][Bogdonav, et al.]]
24#+end_quote
25
26* How Secure is AES?
27In theory, AES-256 is non-crackable due to the massive number of combinations
28that can be produced. However, AES-128 is no longer recommended as a viable
29implementation to protect important data.
30
31A semi-short [[http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html][comic strip]] from Moserware quickly explains AES for the public to
32understand. Basically AES encrypts the data by obscuring the relationship
33between the data and the encrypted data. Additionally, this method spreads the
34message out. Lastly, the key produced by AES is the secret to decrypting it.
35Someone may know the method of AES, but without the key, they are powerless.
36
37To obscure and spread the data out, AES creates a substitution-permutation
38network. Wikipedia has a wonderful [[https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png][example of an SP network]] available. This
39network sends the data through a set of S boxes (using the unique key) to
40substitute the bits with another block of bits. Then, a P box will permutate, or
41rearrange, the bits. This is done over and over, with the key being derived from
42the last round. For AES, the key size specifies the number of transformation
43rounds: 10, 12, and 14 rounds for 128-bit, 192-bit, and 256-bit keys,
44respectively.
45
46* The Process
471. *KeyExpansion=: Using [[https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard][Rijndael's key schedule]], the keys are dynamically
48 generated.
492. *AddRoundKey*: Each byte of the data is combined with this key using bitwise
50 xor.
513. *SubBytes*: This is followed by the substitution of each byte of data.
524. *ShiftRows*: Then, the final three rows are shifted a certain number of
53 steps, dictated by the cipher.
545. *MixColumns*: After the rows have been shifted, the columns are mixed and
55 combined.
56
57This process does not necessarily stop after one full round. Steps 2 through 5
58will repeat for the number of rounds specified by the key. However, the final
59round excludes the MixColumns step. As you can see, this is a fairly complex
60process. One must have a solid understanding of general mathematic principles to
61fully understand how the sequence works (and to even attempt to find a
62weakness).
63
64According to research done by Bogdanov et al., it would take billions of years
65to brute force a 126-bit key with current hardware. Additionally, this brute
66force attack would require storing 2^{88} bits of data! However, there are a few
67different attacks that have been used to show vulnerabilities with the use of
68this technology. Side-channel attacks use inadvertent leaks of data from the
69hardware or software, which can allow attackers to obtain the key or run
70programs on a user's hardware.
71
72Please note that this is not something you should run out and try to implement
73in your =Hello, World!= app after only a few hours of research. While AES
74(basically all encryption methods) is extremely efficient in what it does, it
75takes a lot of time and patience to understand. If you're looking for something
76which currently implements AES, check out the [[https://www.bouncycastle.org/documentation.html][Legion of the Bouncy Castle]] for
77Java implementations of cryptographic algorithms.
78
79* Why Does Encryption Matter?
80There are limitless reasons to enable encryption at-rest or in-transit for
81various aspects of your digital life. You can research specific examples, such
82as [[https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/][Australia passes new law to thwart strong encryption]]. However, I will simply
83list a few basic reasons to always enable encryption, where feasible:
84
851. Privacy is a human right and is recognized as a national right in some
86 countries (e.g., [[https://www.law.cornell.edu/wex/fourth_amendment][US Fourth Amendment]]).
872. "Why not?" Encryption rarely affects performance or speed, so there's usually
88 not a reason to avoid it in the first place.
893. Your digital identity and activity (texts, emails, phone calls, online
90 accounts, etc.) are extremely valuable and can result in terrible
91 consequences, such as identity theft, if leaked to other parties. Encrypting
92 this data prevents such leaks from ruining lives.
934. Wiping or factory-resetting does not actually wipe all data from the storage
94 device. There are methods to read data from the physical disks/boards inside
95 devices.
965. Corporations, governments, and other nefarious groups/individuals are
97 actively looking for ways to collect personal information about anyone they
98 can. If someone's data is unencrypted, that person may become a target due to
99 the ease of data collection.
100
101​*Read More:*
102
103- [[http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf][Federal Information Processing Standards Publication 197]]
content/blog/2018-11-28-cpp-compiler.md added +140
@@ -0,0 +1,140 @@
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
11programming language with object-oriented, generic, and functional
12features in addition to facilities for low-level memory manipulation.
13
14The source code, shown in the snippet below, must be compiled before it
15can be executed. There are many steps and intricacies to the compilation
16process, and this post was a personal exercise to learn and remember as
17much information as I can.
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
33nonexistent errors keeping your program from successfully compiling can
34be annoying (especially since you know you wrote it perfectly the first
35time, right?).
36
37I\'m learning more and more about C++ these days and decided to write
38this concept down so that I can cement it even further in my own head.
39However, C++ is not the only compiled language. Check out [the Wikipedia
40entry for compiled
41languages](https://en.wikipedia.org/wiki/Compiled_language) for more
42examples of compiled languages.
43
44I\'ll start with a wonderful, graphical way to conceptualize the C++
45compiler. View [The C++ Compilation
46Process](https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html)
47by Kurt MacMahon, an NIU professor, to see the graphic and an
48explanation. The goal of the compilation process is to take the C++ code
49and produce a shared library, dynamic library, or an executable file.
50
51## Compilation Phases
52
53Let\'s break down the compilation process. There are four major steps to
54compiling C++ code.
55
56### Step 1
57
58The first step is to expand the source code file to meet all
59dependencies. The C++ preprocessor includes the code from all the header
60files, such as `#include
61<iostream>`. Now, what does that mean? The previous example
62includes the `iostream` header. This tells the computer that
63you want to use the `iostream` standard library, which
64contains classes and functions written in the core language. This
65specific header allows you to manipulate input/output streams. After all
66this, you\'ll end up which a temporary file that contains the expanded
67source code.
68
69In the example of the C++ code above, the `iostream` class
70would be included in the expanded code.
71
72### Step 2
73
74After the code is expanded, the compiler comes into play. The compiler
75takes the C++ code and converts this code into the assembly language,
76understood by the platform. You can see this in action if you head over
77to the [GodBolt Compiler Explorer](https://godbolt.org), which shows C++
78being converted into assembly dynamically.
79
80For example, the `Hello, world!` code snippet above compiles
81into the following assembly code:
82
83``` asm
84.LC0:
85 .string "Hello, world!\n"
86main:
87 push rbp
88 mov rbp, rsp
89 mov esi, OFFSET FLAT:.LC0
90 mov edi, OFFSET FLAT:_ZSt4cout
91 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*)
92 mov eax, 0
93 pop rbp
94 ret
95__static_initialization_and_destruction_0(int, int):
96 push rbp
97 mov rbp, rsp
98 sub rsp, 16
99 mov DWORD PTR [rbp-4], edi
100 mov DWORD PTR [rbp-8], esi
101 cmp DWORD PTR [rbp-4], 1
102 jne .L5
103 cmp DWORD PTR [rbp-8], 65535
104 jne .L5
105 mov edi, OFFSET FLAT:_ZStL8__ioinit
106 call std::ios_base::Init::Init() [complete object constructor]
107 mov edx, OFFSET FLAT:__dso_handle
108 mov esi, OFFSET FLAT:_ZStL8__ioinit
109 mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
110 call __cxa_atexit
111.L5:
112 nop
113 leave
114 ret
115_GLOBAL__sub_I_main:
116 push rbp
117 mov rbp, rsp
118 mov esi, 65535
119 mov edi, 1
120 call __static_initialization_and_destruction_0(int, int)
121 pop rbp
122 ret
123```
124
125### Step 3
126
127Third, the assembly code generated by the compiler is assembled into the
128object code for the platform. Essentially, this is when the compiler
129takes the assembly code and assembles it into machine code in a binary
130format. After researching this online, I figured out that a lot of
131compilers will allow you to stop compilation at this step. This would be
132useful for compiling each source code file separately. This saves time
133later if a single file changes; only that file needs to be recompiled.
134
135### Step 4
136
137Finally, the object code file generated by the assembler is linked
138together with the object code files for any library functions used to
139produce a shared library, dynamic library, or an executable file. It
140replaces all references to undefined symbols with the correct addresses.
content/blog/2018-11-28-cpp-compiler.org deleted −121
@@ -1,121 +0,0 @@
1#+title: The C++ Compiler
2#+date: 2018-11-28
3#+description: Learn basics about the C++ compilation process.
4#+filetags: :dev:
5
6* A Brief Introduction
7[[https://en.wikipedia.org/wiki/C%2B%2B][C++]] is a general-purpose programming language with object-oriented, generic, and
8functional features in addition to facilities for low-level memory manipulation.
9
10The source code, shown in the snippet below, must be compiled before it can be
11executed. There are many steps and intricacies to the compilation process, and
12this post was a personal exercise to learn and remember as much information as I
13can.
14
15#+begin_src cpp
16#include <iostream>
17
18int main()
19{
20 std::cout << "Hello, world!\n";
21}
22#+end_src
23
24** Compilation Process
25*** An Overview
26Compiling C++ projects is a frustrating task most days. Seemingly nonexistent
27errors keeping your program from successfully compiling can be annoying
28(especially since you know you wrote it perfectly the first time, right?).
29
30I'm learning more and more about C++ these days and decided to write this
31concept down so that I can cement it even further in my own head. However, C++
32is not the only compiled language. Check out [[https://en.wikipedia.org/wiki/Compiled_language][the Wikipedia entry for compiled
33languages]] for more examples of compiled languages.
34
35I'll start with a wonderful, graphical way to conceptualize the C++ compiler.
36View [[https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html][The C++ Compilation Process]] by Kurt MacMahon, an NIU professor, to see the
37graphic and an explanation. The goal of the compilation process is to take the
38C++ code and produce a shared library, dynamic library, or an executable file.
39
40** Compilation Phases
41Let's break down the compilation process. There are four major steps to
42compiling C++ code.
43
44*** Step 1
45The first step is to expand the source code file to meet all dependencies. The
46C++ preprocessor includes the code from all the header files, such as =#include
47<iostream>=. Now, what does that mean? The previous example includes the
48=iostream= header. This tells the computer that you want to use the =iostream=
49standard library, which contains classes and functions written in the core
50language. This specific header allows you to manipulate input/output streams.
51After all this, you'll end up which a temporary file that contains the expanded
52source code.
53
54In the example of the C++ code above, the =iostream= class would be included in
55the expanded code.
56
57*** Step 2
58After the code is expanded, the compiler comes into play. The compiler takes the
59C++ code and converts this code into the assembly language, understood by the
60platform. You can see this in action if you head over to the [[https://godbolt.org][GodBolt Compiler
61Explorer]], which shows C++ being converted into assembly dynamically.
62
63For example, the =Hello, world!= code snippet above compiles into the following
64assembly code:
65
66#+begin_src asm
67.LC0:
68 .string "Hello, world!\n"
69main:
70 push rbp
71 mov rbp, rsp
72 mov esi, OFFSET FLAT:.LC0
73 mov edi, OFFSET FLAT:_ZSt4cout
74 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*)
75 mov eax, 0
76 pop rbp
77 ret
78__static_initialization_and_destruction_0(int, int):
79 push rbp
80 mov rbp, rsp
81 sub rsp, 16
82 mov DWORD PTR [rbp-4], edi
83 mov DWORD PTR [rbp-8], esi
84 cmp DWORD PTR [rbp-4], 1
85 jne .L5
86 cmp DWORD PTR [rbp-8], 65535
87 jne .L5
88 mov edi, OFFSET FLAT:_ZStL8__ioinit
89 call std::ios_base::Init::Init() [complete object constructor]
90 mov edx, OFFSET FLAT:__dso_handle
91 mov esi, OFFSET FLAT:_ZStL8__ioinit
92 mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
93 call __cxa_atexit
94.L5:
95 nop
96 leave
97 ret
98_GLOBAL__sub_I_main:
99 push rbp
100 mov rbp, rsp
101 mov esi, 65535
102 mov edi, 1
103 call __static_initialization_and_destruction_0(int, int)
104 pop rbp
105 ret
106#+end_src
107
108*** Step 3
109Third, the assembly code generated by the compiler is assembled into the object
110code for the platform. Essentially, this is when the compiler takes the assembly
111code and assembles it into machine code in a binary format. After researching
112this online, I figured out that a lot of compilers will allow you to stop
113compilation at this step. This would be useful for compiling each source code
114file separately. This saves time later if a single file changes; only that file
115needs to be recompiled.
116
117*** Step 4
118Finally, the object code file generated by the assembler is linked together with
119the object code files for any library functions used to produce a shared
120library, dynamic library, or an executable file. It replaces all references to
121undefined symbols with the correct addresses.
content/blog/2019-01-07-useful-css.md added +200
@@ -0,0 +1,200 @@
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
11HTML code and make it \"pretty\", is one of the most effective ways to
12increase the attractiveness of a website. It can also lead to increased
13user engagement, retention, and satisfaction. In fact, there are whole
14career fields are dedicated to the improvement of user experiences,
15known as UI design and UX design.
16
17Some web developers are used to the common CSS properties, such as
18element sizing, fonts, colors, etc., but are not as well versed in
19less-used properties and values such as `flexbox`,
20`clip-path`, and `transform`. This article will
21provide some insight into the less-used and unique CSS properties.
22
23# CSS Variables
24
25The first topic today is CSS variables. Variables are not often used by
26smaller developers. CSS variables allow you to give your website a
27well-defined structure, where you can easily reuse CSS properties
28throughout the project.
29
30You can use variables to define things, such as color palettes. Then,
31you can use these colors for backgrounds anywhere else in the HTML. This
32could be extended, where extra variables could be defined for
33`primary-text`, `quoted-text`, etc. Variables can
34also be used to define spacing (e.g. `32px` or
35`2rem`), which can then be applied to margins, padding, font
36sizes, and more.
37
38For example, here are some variables defined at the root of the website,
39which allows for any subsequent CSS rules to use those variables:
40
41``` css
42:root {
43 --primary-color: black;
44 --secondary-color: white;
45}
46
47body {
48 background-color: var(--primary-color);
49 color: var(--secondary-color);
50}
51```
52
53# CSS Box Shadows
54
55Box shadows were once my mortal enemy. No matter how hard I tried, I
56just couldn\'t get them to work how I wanted. Because of this, my
57favorite discovery has been CSSMatic\'s [box shadow
58generator](https://www.cssmatic.com/box-shadow). It provides an
59excellent tool to generate box shadows using their simple sliders.
60Surprisingly, this is the reason I learned how box shadows work! You can
61use the sliders and watch how the CSS code changes in the image that is
62displayed. Through this, you should understand that the basic structure
63for box shadows is:
64
65``` css
66box-shadow: inset horizontal vertical blur spread color;
67```
68
69Now, let\'s look at some basic examples! You can copy and paste the
70following code into a site like CodePen or your own HTML files. Feel
71free to play around with the code, experiment, and learn.
72
73**Box Shadow #1**
74
75``` html
76<div class="shadow-examples">
77 <div class="box effect1">
78 <h3>Effect 1</h3>
79 </div>
80</div>
81```
82
83``` css
84.box h3 {
85 text-align: center;
86 position: relative;
87 top: 80px;
88}
89.box {
90 width: 70%;
91 height: 200px;
92 background: #fff;
93 margin: 40px auto;
94}
95.effect1 {
96 box-shadow: 0 10px 6px -6px #777;
97}
98```
99
100**Box Shadow #2**
101
102``` html
103<div class="shadow-examples">
104 <div class="box effect2">
105 <h3>Effect 2</h3>
106 </div>
107</div>
108```
109
110``` css
111.box h3 {
112 text-align: center;
113 position: relative;
114 top: 80px;
115}
116.box {
117 width: 70%;
118 height: 200px;
119 background: #fff;
120 margin: 40px auto;
121}
122.effect2 {
123 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
124}
125```
126
127Try these box shadows out on your own and see how changing each shadow
128value works.
129
130# CSS Flexbox
131
132Now, let\'s move on to the best part of this article: flexbox. The
133flexbox is by far my favorite new toy. I originally stumbled across this
134solution after looking for more efficient ways of centering content
135horizontally AND vertically. I had used a few hack-ish methods before,
136but flexbox throws those out the window. The best part of it all is that
137flexbox is *dead simple*.
138
139Flexbox pertains to the parent div of any element. You want the parent
140to be the flexbox in which items are arranged to use the flex methods.
141It\'s easier to see this in action that explained, so let\'s see an
142example.
143
144**Flexbox**
145
146``` html
147<div class="flex-examples">
148 <div class="sm-box">
149 <h3>1</h3>
150 </div>
151 <div class="sm-box">
152 <h3>2</h3>
153 </div>
154</div>
155```
156
157``` css
158.flex-examples {
159 display: flex;
160 flex-wrap: wrap;
161 justify-content: flex-start;
162 align-items: center;
163 padding: 10px;
164 background-color: #f2f2f2;
165}
166.sm-box {
167 display: flex;
168 justify-content: center;
169 align-items: center;
170 width: 20%;
171 height: 100px;
172 background: #fff;
173 margin: 40px 10px;
174}
175```
176
177You may notice that we no longer need to use the `top`
178property for the `h3` elements in our code. This is because
179we set the display box to be a flex container for the small boxes, AND
180we made the small boxes flex containers for their elements (the h3
181tags). Flex boxes can be nested like this to center content that is
182inside centered content.
183
184For the example above, we designated the `justify-content`
185property to be `flex-start` so that the boxes stack from the
186left side of the screen. This property can be changed to
187`center` to make the boxes appear in the center of the
188screen.
189
190For an interactive example, [check out this
191CodePen](https://codepen.io/LandonSchropp/pen/KpzzGo) from
192[LandonScropp](https://codepen.io/LandonSchropp/). Resize the window
193with dice to see how they collapse and re-align.
194
195# Even More CSS
196
197For more inspiration, you can visit [CodePen](https://www.codepen.io),
198[Dribbble](https://dribbble.com), or [UI
199Movement](https://uimovement.com) to browse the collections of many
200amazing web designers.
content/blog/2019-01-07-useful-css.org deleted −178
@@ -1,178 +0,0 @@
1#+title: Useful CSS Snippets
2#+date: 2019-01-07
3#+description: Explore some useful CSS snippets.
4#+filetags: :dev:
5
6* Introduction to CSS
7[[https://en.wikipedia.org/wiki/CSS][CSS]], the language used to markup HTML code and make it "pretty", is one of the
8most effective ways to increase the attractiveness of a website. It can also
9lead to increased user engagement, retention, and satisfaction. In fact, there
10are whole career fields are dedicated to the improvement of user experiences,
11known as UI design and UX design.
12
13Some web developers are used to the common CSS properties, such as element
14sizing, fonts, colors, etc., but are not as well versed in less-used properties
15and values such as =flexbox=, =clip-path=, and =transform=. This article will
16provide some insight into the less-used and unique CSS properties.
17
18* CSS Variables
19The first topic today is CSS variables. Variables are not often used by smaller
20developers. CSS variables allow you to give your website a well-defined
21structure, where you can easily reuse CSS properties throughout the project.
22
23You can use variables to define things, such as color palettes. Then, you can
24use these colors for backgrounds anywhere else in the HTML. This could be
25extended, where extra variables could be defined for =primary-text=,
26=quoted-text=, etc. Variables can also be used to define spacing (e.g. =32px= or
27=2rem=), which can then be applied to margins, padding, font sizes, and more.
28
29For example, here are some variables defined at the root of the website, which
30allows for any subsequent CSS rules to use those variables:
31
32#+begin_src css
33:root {
34 --primary-color: black;
35 --secondary-color: white;
36}
37
38body {
39 background-color: var(--primary-color);
40 color: var(--secondary-color);
41}
42#+end_src
43
44* CSS Box Shadows
45Box shadows were once my mortal enemy. No matter how hard I tried, I just
46couldn't get them to work how I wanted. Because of this, my favorite discovery
47has been CSSMatic's [[https://www.cssmatic.com/box-shadow][box shadow generator]]. It provides an excellent tool to
48generate box shadows using their simple sliders. Surprisingly, this is the
49reason I learned how box shadows work! You can use the sliders and watch how the
50CSS code changes in the image that is displayed. Through this, you should
51understand that the basic structure for box shadows is:
52
53#+begin_src css
54box-shadow: inset horizontal vertical blur spread color;
55#+end_src
56
57Now, let's look at some basic examples! You can copy and paste the following
58code into a site like CodePen or your own HTML files. Feel free to play around
59with the code, experiment, and learn.
60
61*Box Shadow #1*
62
63#+begin_src html
64<div class="shadow-examples">
65 <div class="box effect1">
66 <h3>Effect 1</h3>
67 </div>
68</div>
69#+end_src
70
71#+begin_src css
72.box h3 {
73 text-align: center;
74 position: relative;
75 top: 80px;
76}
77.box {
78 width: 70%;
79 height: 200px;
80 background: #fff;
81 margin: 40px auto;
82}
83.effect1 {
84 box-shadow: 0 10px 6px -6px #777;
85}
86#+end_src
87
88*Box Shadow #2*
89
90#+begin_src html
91<div class="shadow-examples">
92 <div class="box effect2">
93 <h3>Effect 2</h3>
94 </div>
95</div>
96#+end_src
97
98#+begin_src css
99.box h3 {
100 text-align: center;
101 position: relative;
102 top: 80px;
103}
104.box {
105 width: 70%;
106 height: 200px;
107 background: #fff;
108 margin: 40px auto;
109}
110.effect2 {
111 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
112}
113#+end_src
114
115Try these box shadows out on your own and see how changing each shadow value
116works.
117
118* CSS Flexbox
119Now, let's move on to the best part of this article: flexbox. The flexbox is by
120far my favorite new toy. I originally stumbled across this solution after
121looking for more efficient ways of centering content horizontally AND
122vertically. I had used a few hack-ish methods before, but flexbox throws those
123out the window. The best part of it all is that flexbox is /dead simple/.
124
125Flexbox pertains to the parent div of any element. You want the parent to be the
126flexbox in which items are arranged to use the flex methods. It's easier to see
127this in action that explained, so let's see an example.
128
129*Flexbox*
130
131#+begin_src html
132<div class="flex-examples">
133 <div class="sm-box">
134 <h3>1</h3>
135 </div>
136 <div class="sm-box">
137 <h3>2</h3>
138 </div>
139</div>
140#+end_src
141
142#+begin_src css
143.flex-examples {
144 display: flex;
145 flex-wrap: wrap;
146 justify-content: flex-start;
147 align-items: center;
148 padding: 10px;
149 background-color: #f2f2f2;
150}
151.sm-box {
152 display: flex;
153 justify-content: center;
154 align-items: center;
155 width: 20%;
156 height: 100px;
157 background: #fff;
158 margin: 40px 10px;
159}
160#+end_src
161
162You may notice that we no longer need to use the =top= property for the =h3=
163elements in our code. This is because we set the display box to be a flex
164container for the small boxes, AND we made the small boxes flex containers for
165their elements (the h3 tags). Flex boxes can be nested like this to center
166content that is inside centered content.
167
168For the example above, we designated the =justify-content= property to be
169=flex-start= so that the boxes stack from the left side of the screen. This
170property can be changed to =center= to make the boxes appear in the center of
171the screen.
172
173For an interactive example, [[https://codepen.io/LandonSchropp/pen/KpzzGo][check out this CodePen]] from [[https://codepen.io/LandonSchropp/][LandonScropp]]. Resize the
174window with dice to see how they collapse and re-align.
175
176* Even More CSS
177For more inspiration, you can visit [[https://www.codepen.io][CodePen]], [[https://dribbble.com][Dribbble]], or [[https://uimovement.com][UI Movement]] to browse
178the collections of many amazing web designers.
content/blog/2019-09-09-audit-analytics.md added +233
@@ -0,0 +1,233 @@
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`
11is a vague term that has become popular in recent years. Think of a
12`data analytic` as the output of any data analysis you
13perform. For example, a pivot table or a pie chart could be a data
14analytic.
15
16[Data analysis](https://en.wikipedia.org/wiki/Data_analysis) is a
17process that utilizes statistics and other mathematical methods to
18discover useful information within datasets. This involves examining,
19cleaning, transforming, and modeling data so that you can use the data
20to support an opinion, create more useful viewpoints, and gain knowledge
21to implement into audit planning or risk assessments.
22
23One of the common mistakes that managers (and anyone new to the process)
24make is assuming that everything involved with this process is \"data
25analytics\". In fact, data analytics are only a small part of the
26process.
27
28See **Figure 1\*** for a more accurate representation of where data
29analysis sits within the full process. This means that data analysis
30does not include querying or extracting data, selecting samples, or
31performing audit tests. These steps can be necessary for an audit (and
32may even be performed by the same associates), but they are not data
33analytics.
34
35![The Intelligence
36Cycle](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/intelligence_cycle-min.png)
37
38# Current Use of Analytics in Auditing
39
40While data analysis has been an integral part of most businesses and
41departments for the better part of the last century, only recently have
42internal audit functions been adopting this practice. The internal audit
43function works exclusively to provide assurance and consulting services
44to the business areas within the firm (except for internal auditing
45firms who are hired by different companies to perform their roles).
46
47> Internal Auditing helps an organization accomplish its objectives by
48> bringing a systematic, disciplined approach to evaluate and improve
49> the effectiveness of risk management, control and governance
50> processes.
51>
52> - The IIA\'s Definition of Internal Audit
53
54Part of the blame for the slow adoption of data analysis can be
55attributed to the fact that internal auditing is strongly based on
56tradition and following the precedents set by previous auditors.
57However, there can be no progress without auditors who are willing to
58break the mold and test new audit techniques. In fact, as of 2018, [only
5963% of internal audit departments currently utilize data
60analytics](https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics)
61in North America. This number should be as close as possible to 100%. I
62have never been part of an audit that would not have benefited from data
63analytics.
64
65So, how do internal audit functions remedy this situation? It\'s
66definitely not as easy as walking into work on Monday and telling your
67Chief Audit Executive that you\'re going to start implementing analytics
68in the next audit. You need a plan and a system to make the analysis
69process as effective as possible.
70
71# The DELTA Model
72
73One of the easiest ways to experiment with data analytics and gain an
74understanding of the processes is to implement them within your own
75department. But how do we do this if we\'ve never worked with analysis
76before? One of the most common places to start is to research some data
77analysis models currently available. For this post, we\'ll take a look
78at the DELTA model. You can take a look at **\*\*\*Figure 2\*\*\*\***
79for a quick overview of the model.
80
81The DELTA model sets a few guidelines for areas wanting to implement
82data analytics so that the results can be as comprehensive as possible:
83
84- **Data**: Must be clean, accessible, and (usually) unique.
85- **Enterprise-Wide Focus**: Key data systems and analytical resources
86 must be available for use (by the Internal Audit Function).
87- **Leaders**: Must promote a data analytics approach and show the
88 value of analytical results.
89- **Targets**: Must be set for key areas and risks that the analytics
90 can be compared against (KPIs).
91- **Analysts**: There must be auditors willing and able to perform
92 data analytics or else the system cannot be sustained.
93
94![The Delta
95Model](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/delta-min.png)
96
97# Finding the Proper KPIs
98
99Once the Internal Audit Function has decided that they want to start
100using data analytics internally and have ensured they\'re properly set
101up to do so, they need to figure out what they will be testing against.
102Key Performance Indicators (KPIs) are qualitative or quantitative
103factors that can be evaluated and assessed to determine if the
104department is performing well, usually compared to historical or
105industry benchmarks. Once KPIs have been agreed upon and set, auditors
106can use data analytics to assess and report on these KPIs. This allows
107the person performing the analytics the freedom to express opinions on
108the results, whereas the results are ambiguous if no KPIs exist.
109
110It should be noted that tracking KPIs in the department can help ensure
111you have a rigorous Quality Assurance and Improvement Program (QAIP) in
112accordance with some applicable standards, such as IPPF Standard 1300.
113
114> The chief audit executive must develop and maintain a quality
115> assurance and improvement program that covers all aspects of the
116> internal audit activity.
117>
118> - IPPF Standard 1300
119
120Additionally, IPPF Standard 2060 discusses reporting:
121
122> The chief audit executive must report periodically to senior
123> management and the board on the internal audit activity\'s purpose,
124> authority, responsibility, and performance relative to its plan and on
125> its conformance with the Code of Ethics and the Standards. Reporting
126> must also include significant risk and control issues, including fraud
127> risks, governance issues, and other matters that require the attention
128> of senior management and/or the board.
129>
130> - IPPF Standard 2060
131
132The hardest part of finding KPIs is to determine which KPIs are
133appropriate for your department. Since every department is different and
134has different goals, KPIs will vary drastically between companies. To
135give you an idea of where to look, here are some ideas I came up with
136when discussing the topic with a few colleagues.
137
138- Efficiency/Budgeting:
139 - Audit hours to staff utilization ratio (annual hours divided by
140 total annual work hours).
141 - Audit hours compared to the number of audits completed.
142 - Time between audit steps or to complete the whole audit. E.g.,
143 time from fieldwork completion to audit report issuance.
144- Reputation:
145 - The frequency that management has requested the services of the
146 IAF.
147 - Management, audit committee, or external audit satisfaction
148 survey results.
149 - Education, experience, certifications, tenure, and training of
150 the auditors on staff.
151- Quality:
152 - Number and frequency of audit findings. Assign monetary or
153 numerical values, if possible.
154 - Percentage of recommendations issued and implemented.
155- Planning:
156 - Percentage or number of key risks audited per year or per audit.
157 - Proportion of audit universe audited per year.
158
159# Data Analysis Tools
160
161Finally, to be able to analyze and report on the data analysis, auditors
162need to evaluate the tools at their disposal. There are many options
163available, but a few of the most common ones can easily get the job
164done. For example, almost every auditor already has access to Microsoft
165Excel. Excel is more powerful than most people give it credit for and
166can accomplish a lot of basic statistics without much work. If you
167don\'t know a lot about statistics but still want to see some of the
168more basic results, Excel is a great option.
169
170To perform more in-depth statistical analysis or to explore large
171datasets that Excel cannot handle, auditors will need to explore other
172options. The big three that have had a lot of success in recent years
173are Python, R, and ACL. ACL can be used as either a graphical tool
174(point and click) or as a scripting tool, where the auditor must write
175the scripts manually. Python and the R-language are solely scripting
176languages.
177
178The general trend in the data analytics environment is that if the tool
179allows you to do everything by clicking buttons or dragging elements,
180you won\'t be able to fully utilize the analytics you need. The most
181robust solutions are created by those who understand how to write the
182scripts manually. It should be noted that as the utility of a tool
183increases, it usually means that the learning curve for that tool will
184also be higher. It will take auditors longer to learn how to utilize
185Python, R, or ACL versus learning how to utilize Excel.
186
187# Visualization
188
189Once an auditor has finally found the right data, KPIs, and tools, they
190must report these results so that actions can be taken. Performing
191in-depth data analysis is only useful if the results are understood by
192the audiences of the data. The best way to create this understanding is
193to visualize the results of the data. Let\'s take a look at some of the
194best options to visualize and report the results you\'ve found.
195
196Some of the most popular commercial tools for visualization are
197Microsoft PowerBI and Tableau Desktop. However, other tools exist such
198as JMP, Plotly, Qlikview, Alteryx, or D3. Some require commercial
199licenses while others are simply free to use. For corporate data, you
200may want to make sure that the tool does not communicate any of the data
201outside the company (such as cloud storage). I won\'t be going into
202depth on any of these tools since visualization is largely a subjective
203and creative experience, but remember to constantly explore new options
204as you repeat the process.
205
206Lastly, let\'s take a look at an example of data visualization. This
207example comes from a [blog post written by Kushal
208Chakrabarti](https://talent.works/2018/03/28/the-science-of-the-job-search-part-iii-61-of-entry-level-jobs-require-3-years-of-experience/)
209in 2018 about the percent of entry-level US jobs that require
210experience. **Figure 3\*** shows us an easy-to-digest picture of the
211data. We can quickly tell that only about 12.5% of entry-level jobs
212don\'t require experience.
213
214This is the kind of result that easily describes the data for you.
215However, make sure to include an explanation of what the results mean.
216Don\'t let the reader assume what the data means, especially if it
217relates to a complex subject. *Tell a story* about the data and why the
218results matter. For example, **Figure 4\*** shows a part of the
219explanation the author gives to illustrate his point.
220
221![Entry-Level
222Visualization](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example-min.png)
223
224![Visualization
225Explanation](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example_explanation-min.png)
226
227# Wrap-Up
228
229While this is not an all-encompassing program that you can just adopt
230into your department, it should be enough to get anyone started on the
231process of understanding and implementing data analytics. Always
232remember to continue learning and exploring new options as your
233processes grow and evolve.
content/blog/2019-09-09-audit-analytics.org deleted −211
@@ -1,211 +0,0 @@
1#+title: Data Analysis in Auditing
2#+date: 2019-09-09
3#+description: Learn how to use data analysis in the world of auditing.
4#+filetags: :audit:
5
6* What Are Data Analytics?
7A quick aside before I dive into this post: =data analytics= is a vague term
8that has become popular in recent years. Think of a =data analytic= as the
9output of any data analysis you perform. For example, a pivot table or a pie
10chart could be a data analytic.
11
12[[https://en.wikipedia.org/wiki/Data_analysis][Data analysis]] is a process that utilizes statistics and other mathematical
13methods to discover useful information within datasets. This involves examining,
14cleaning, transforming, and modeling data so that you can use the data to
15support an opinion, create more useful viewpoints, and gain knowledge to
16implement into audit planning or risk assessments.
17
18One of the common mistakes that managers (and anyone new to the process) make is
19assuming that everything involved with this process is "data analytics". In
20fact, data analytics are only a small part of the process.
21
22See *Figure 1** for a more accurate representation of where data analysis sits
23within the full process. This means that data analysis does not include querying
24or extracting data, selecting samples, or performing audit tests. These steps
25can be necessary for an audit (and may even be performed by the same
26associates), but they are not data analytics.
27
28#+caption: The Intelligence Cycle
29[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/intelligence_cycle-min.png]]
30
31* Current Use of Analytics in Auditing
32While data analysis has been an integral part of most businesses and departments
33for the better part of the last century, only recently have internal audit
34functions been adopting this practice. The internal audit function works
35exclusively to provide assurance and consulting services to the business areas
36within the firm (except for internal auditing firms who are hired by different
37companies to perform their roles).
38
39#+begin_quote
40Internal Auditing helps an organization accomplish its objectives by bringing a
41systematic, disciplined approach to evaluate and improve the effectiveness of
42risk management, control and governance processes.
43
44- The IIA's Definition of Internal Audit
45
46#+end_quote
47
48Part of the blame for the slow adoption of data analysis can be attributed to
49the fact that internal auditing is strongly based on tradition and following the
50precedents set by previous auditors. However, there can be no progress without
51auditors who are willing to break the mold and test new audit techniques. In
52fact, as of 2018, [[https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics][only 63% of internal audit departments currently utilize data
53analytics]] in North America. This number should be as close as possible to 100%.
54I have never been part of an audit that would not have benefited from data
55analytics.
56
57So, how do internal audit functions remedy this situation? It's definitely not
58as easy as walking into work on Monday and telling your Chief Audit Executive
59that you're going to start implementing analytics in the next audit. You need a
60plan and a system to make the analysis process as effective as possible.
61
62* The DELTA Model
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 analytics
81 or else the system cannot be sustained.
82
83#+caption: The Delta Model
84[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/delta-min.png]]
85
86* Finding the Proper KPIs
87Once the Internal Audit Function has decided that they want to start using data
88analytics internally and have ensured they're properly set up to do so, they
89need to figure out what they will be testing against. Key Performance Indicators
90(KPIs) are qualitative or quantitative factors that can be evaluated and
91assessed to determine if the department is performing well, usually compared to
92historical or industry benchmarks. Once KPIs have been agreed upon and set,
93auditors can use data analytics to assess and report on these KPIs. This allows
94the person performing the analytics the freedom to express opinions on the
95results, whereas the results are ambiguous if no KPIs exist.
96
97It should be noted that tracking KPIs in the department can help ensure you have
98a rigorous Quality Assurance and Improvement Program (QAIP) in accordance with
99some applicable standards, such as IPPF Standard 1300.
100
101#+begin_quote
102The chief audit executive must develop and maintain a quality assurance and
103improvement program that covers all aspects of the internal audit activity.
104
105- IPPF Standard 1300
106
107#+end_quote
108
109Additionally, IPPF Standard 2060 discusses reporting:
110
111#+begin_quote
112The chief audit executive must report periodically to senior management and the
113board on the internal audit activity's purpose, authority, responsibility, and
114performance relative to its plan and on its conformance with the Code of Ethics
115and the Standards. Reporting must also include significant risk and control
116issues, including fraud risks, governance issues, and other matters that require
117the attention of senior management and/or the board.
118
119- IPPF Standard 2060
120
121#+end_quote
122
123The hardest part of finding KPIs is to determine which KPIs are appropriate for
124your department. Since every department is different and has different goals,
125KPIs will vary drastically between companies. To give you an idea of where to
126look, here are some ideas I came up with when discussing the topic with a few
127colleagues.
128
129- Efficiency/Budgeting:
130 - Audit hours to staff utilization ratio (annual hours divided by total annual
131 work hours).
132 - Audit hours compared to the number of audits completed.
133 - Time between audit steps or to complete the whole audit. E.g., time from
134 fieldwork completion to audit report issuance.
135- Reputation:
136 - The frequency that management has requested the services of the IAF.
137 - Management, audit committee, or external audit satisfaction survey results.
138 - Education, experience, certifications, tenure, and training of the auditors
139 on staff.
140- Quality:
141 - Number and frequency of audit findings. Assign monetary or numerical values,
142 if possible.
143 - Percentage of recommendations issued and implemented.
144- Planning:
145 - Percentage or number of key risks audited per year or per audit.
146 - Proportion of audit universe audited per year.
147
148* Data Analysis Tools
149Finally, to be able to analyze and report on the data analysis, auditors need to
150evaluate the tools at their disposal. There are many options available, but a
151few of the most common ones can easily get the job done. For example, almost
152every auditor already has access to Microsoft Excel. Excel is more powerful than
153most people give it credit for and can accomplish a lot of basic statistics
154without much work. If you don't know a lot about statistics but still want to
155see some of the more basic results, Excel is a great option.
156
157To perform more in-depth statistical analysis or to explore large datasets that
158Excel cannot handle, auditors will need to explore other options. The big three
159that have had a lot of success in recent years are Python, R, and ACL. ACL can
160be used as either a graphical tool (point and click) or as a scripting tool,
161where the auditor must write the scripts manually. Python and the R-language are
162solely scripting languages.
163
164The general trend in the data analytics environment is that if the tool allows
165you to do everything by clicking buttons or dragging elements, you won't be able
166to fully utilize the analytics you need. The most robust solutions are created
167by those who understand how to write the scripts manually. It should be noted
168that as the utility of a tool increases, it usually means that the learning
169curve for that tool will also be higher. It will take auditors longer to learn
170how to utilize Python, R, or ACL versus learning how to utilize Excel.
171
172* Visualization
173Once an auditor has finally found the right data, KPIs, and tools, they must
174report these results so that actions can be taken. Performing in-depth data
175analysis is only useful if the results are understood by the audiences of the
176data. The best way to create this understanding is to visualize the results of
177the data. Let's take a look at some of the best options to visualize and report
178the results you've found.
179
180Some of the most popular commercial tools for visualization are Microsoft
181PowerBI and Tableau Desktop. However, other tools exist such as JMP, Plotly,
182Qlikview, Alteryx, or D3. Some require commercial licenses while others are
183simply free to use. For corporate data, you may want to make sure that the tool
184does not communicate any of the data outside the company (such as cloud
185storage). I won't be going into depth on any of these tools since visualization
186is largely a subjective and creative experience, but remember to constantly
187explore new options as you repeat the process.
188
189Lastly, let's take a look at an example of data visualization. This example
190comes from a [[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 post written by Kushal Chakrabarti]] in 2018 about the percent
191of entry-level US jobs that require experience. *Figure 3** shows us an
192easy-to-digest picture of the data. We can quickly tell that only about 12.5% of
193entry-level jobs don't require experience.
194
195This is the kind of result that easily describes the data for you. However, make
196sure to include an explanation of what the results mean. Don't let the reader
197assume what the data means, especially if it relates to a complex subject. /Tell
198a story/ about the data and why the results matter. For example, *Figure 4**
199shows a part of the explanation the author gives to illustrate his point.
200
201#+caption: Entry-Level Visualization
202[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example-min.png]]
203
204#+caption: Visualization Explanation
205[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example_explanation-min.png]]
206
207* Wrap-Up
208While this is not an all-encompassing program that you can just adopt into your
209department, it should be enough to get anyone started on the process of
210understanding and implementing data analytics. Always remember to continue
211learning and exploring new options as your processes grow and evolve.
content/blog/2019-12-03-the-ansoff-matrix.md added +128
@@ -0,0 +1,128 @@
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
11planning and strategy with a contemporary mindset. According to Dess,
12McNamara, Eisner, and Lee, managers must be willing to adapt to the
13modern business environment by going beyond \"\'incremental
14management\', whereby they view their job as making a series of small,
15minor changes to improve the efficiency of the firm\'s
16operations\"(2019).
17
18One reason that strategic management is crucial is because most
19businesses that fail in the United States each year fail due to a lack
20of strategic focus or direction(2019). The rate of failure for
21businesses with poor strategies shows that strategic planning and
22management are crucial to a business\'s strength and longevity,
23injecting the critical factors of growth and direction into a company\'s
24business plan.
25
26One of the most significant strategic planning and management frameworks
27that companies can use is the [Ansoff
28Matrix](https://en.wikipedia.org/wiki/Ansoff_matrix). While this
29framework has unique purposes and use-cases, it can effectively help an
30organization grow and compete. Specifically, the Ansoff matrix is one of
31the most effective frameworks for companies who want to focus on
32increasing sales revenue or profitability(2019).
33
34This framework uses a two-by-two figure to show the four strategic
35options for companies to use in this framework: market penetration,
36market development, product development, and diversification (see
37**Figure 1**). The x-axis of the matrix focuses on the firm\'s markets
38and also determines if the firm is looking to enter new markets or
39innovate in its current markets. The y-axis of the matrix focuses on the
40firm\'s products and determines if the firm wants to pursue strategies
41around their existing products or explore new products.
42
43![The Ansoff Matrix by JaisonAbeySabu, Own work, CC BY-SA
443.0](https://img.cleberg.net/blog/20191203-the-ansoff-matrix/ansoff_matrix-min.png)
45
46# Strategic Options
47
48## Market Penetration
49
50The most straightforward strategy in the Ansoff matrix is to focus on
51existing products in existing markets, also known as market
52penetration(2019). Companies such as Coca-Cola have used market
53penetration successfully by investing a lot of money to get further
54value out of their current markets. Coca-Cola does this by introducing
55new features such as Christmas-themed bottles, personal names on the
56bottles, and other marketing schemes.
57
58## Market Development
59
60Market development extends existing products into new markets in an
61attempt to increase the number of buyers. One interesting way that
62Coca-Cola used this strategy comes from the stigma that Diet Coke is a
63woman\'s drink(2019). Coca-Cola introduced Coca-Cola Zero, which
64contained the same nutritional content as Diet Coke, but was packaged in
65a dark black can to appear more \"manly\"(2019).
66
67## Product Development
68
69Product development uses existing markets to introduce new products so
70that the firm can better meet customer needs(2019). The extreme end of
71diversification is home to companies such as Johnson & Johnson, a
72healthcare company that has developed a business portfolio of more than
7360,000 different products(2019). Johnson & Johnson\'s dedication to
74continuous diversification has led them to a balance sheet rating of
75\"AAA\", industry recognition for diversification, and increases in
76their investor dividends for 57 consecutive years(2019).
77
78## Related Diversification
79
80Diversification, the final strategy of the Ansoff Matrix, is more
81difficult than the others since it involves exploring both new markets
82and new products. Related diversification is a diversification strategy
83that closely relates to the firm\'s core business. Coca-Cola\'s best
84example of related diversification is its acquisition of Glaceau and
85Vitamin Water, which expanded their drinking lines of business(2019).
86
87## Unrelated Diversification
88
89Unrelated diversification is a diversification strategy that does not
90really relate to the firm\'s core business but still diversifies their
91business portfolio. A good example of this would be a coffee company who
92has decided to enter the market for bicycle sales. The main purpose of
93this strategy is to an extremely diverse company that will not go
94bankrupt if one market goes through difficult times. However, this
95requires a lot of independent skills and heavy investments since the
96company most likely cannot easily transfer knowledge between the markets
97they compete in.
98
99# Requirements for Success
100
101To use the Ansoff Matrix framework, managers need to formulate corporate
102goals and objectives. Without goals and direction, management frameworks
103do not present much practical utility. Further, the Ansoff Matrix
104requires the managers involved to make tactical decisions and create a
105path for the company to take toward their goals. Lastly, both the Ansoff
106Matrix needs to consider both internal and external perspectives
107throughout the strategy formulation process.
108
109One interesting probability is that companies will be using multiple
110strategic planning and management frameworks at the same time. While
111this may sound like it could crowd the management process, there are
112numerous reasons to do so. For example, the Ansoff Matrix and the
113Balanced Scorecard are relatively popular, and they cover entirely
114different parts of a company\'s strategy. Using the results from the
115Balanced Scorecard could inform a company of the potential product and
116market demands, such as from customer or supplier survey results, to
117help the company determine which Ansoff Matrix strategy to pursue.
118However, a combined approach at this level would require mature
119frameworks and focused managers who are able to strategize at a high
120level.
121
122Lastly, it should be noted that the author of the Ansoff matrix, Igor
123Ansoff, often used the term [paralysis by
124analysis](https://en.wikipedia.org/wiki/Analysis_paralysis) to explain
125the mistake of companies who overuse analysis and spend too much time
126planning. Companies need to understand the utility of a strategic
127management framework while ensuring that the company is poised to
128execute as efficiently as they have planned.
content/blog/2019-12-03-the-ansoff-matrix.org deleted −105
@@ -1,105 +0,0 @@
1#+title: The Ansoff Matrix
2#+date: 2019-12-03
3#+description: Learn about the Ansoff Matrix, a strategic management tool.
4#+filetags: :business:
5
6* Overview
7As the world of business evolves, managers must approach business planning and
8strategy with a contemporary mindset. According to Dess, McNamara, Eisner, and
9Lee, managers must be willing to adapt to the modern business environment by
10going beyond "'incremental management', whereby they view their job as making a
11series of small, minor changes to improve the efficiency of the firm's
12operations"(2019).
13
14One reason that strategic management is crucial is because most businesses that
15fail in the United States each year fail due to a lack of strategic focus or
16direction(2019). The rate of failure for businesses with poor strategies shows
17that strategic planning and management are crucial to a business's strength and
18longevity, injecting the critical factors of growth and direction into a
19company's business plan.
20
21One of the most significant strategic planning and management frameworks that
22companies can use is the [[https://en.wikipedia.org/wiki/Ansoff_matrix][Ansoff Matrix]]. While this framework has unique purposes
23and use-cases, it can effectively help an organization grow and compete.
24Specifically, the Ansoff matrix is one of the most effective frameworks for
25companies who want to focus on increasing sales revenue or profitability(2019).
26
27This framework uses a two-by-two figure to show the four strategic options for
28companies to use in this framework: market penetration, market development,
29product development, and diversification (see *Figure 1*). The x-axis of the
30matrix focuses on the firm's markets and also determines if the firm is looking
31to enter new markets or innovate in its current markets. The y-axis of the
32matrix focuses on the firm's products and determines if the firm wants to pursue
33strategies around their existing products or explore new products.
34
35#+caption: The Ansoff Matrix by JaisonAbeySabu, Own work, CC BY-SA 3.0
36[[https://img.cleberg.net/blog/20191203-the-ansoff-matrix/ansoff_matrix-min.png]]
37
38* Strategic Options
39** Market Penetration
40The most straightforward strategy in the Ansoff matrix is to focus on existing
41products in existing markets, also known as market penetration(2019). Companies
42such as Coca-Cola have used market penetration successfully by investing a lot
43of money to get further value out of their current markets. Coca-Cola does this
44by introducing new features such as Christmas-themed bottles, personal names on
45the bottles, and other marketing schemes.
46
47** Market Development
48Market development extends existing products into new markets in an attempt to
49increase the number of buyers. One interesting way that Coca-Cola used this
50strategy comes from the stigma that Diet Coke is a woman's drink(2019).
51Coca-Cola introduced Coca-Cola Zero, which contained the same nutritional
52content as Diet Coke, but was packaged in a dark black can to appear more
53"manly"(2019).
54
55** Product Development
56Product development uses existing markets to introduce new products so that the
57firm can better meet customer needs(2019). The extreme end of diversification is
58home to companies such as Johnson & Johnson, a healthcare company that has
59developed a business portfolio of more than 60,000 different products(2019).
60Johnson & Johnson's dedication to continuous diversification has led them to a
61balance sheet rating of "AAA", industry recognition for diversification, and
62increases in their investor dividends for 57 consecutive years(2019).
63
64** Related Diversification
65Diversification, the final strategy of the Ansoff Matrix, is more difficult than
66the others since it involves exploring both new markets and new products.
67Related diversification is a diversification strategy that closely relates to
68the firm's core business. Coca-Cola's best example of related diversification is
69its acquisition of Glaceau and Vitamin Water, which expanded their drinking
70lines of business(2019).
71
72** Unrelated Diversification
73Unrelated diversification is a diversification strategy that does not really
74relate to the firm's core business but still diversifies their business
75portfolio. A good example of this would be a coffee company who has decided to
76enter the market for bicycle sales. The main purpose of this strategy is to an
77extremely diverse company that will not go bankrupt if one market goes through
78difficult times. However, this requires a lot of independent skills and heavy
79investments since the company most likely cannot easily transfer knowledge
80between the markets they compete in.
81
82* Requirements for Success
83To use the Ansoff Matrix framework, managers need to formulate corporate goals
84and objectives. Without goals and direction, management frameworks do not
85present much practical utility. Further, the Ansoff Matrix requires the managers
86involved to make tactical decisions and create a path for the company to take
87toward their goals. Lastly, both the Ansoff Matrix needs to consider both
88internal and external perspectives throughout the strategy formulation process.
89
90One interesting probability is that companies will be using multiple strategic
91planning and management frameworks at the same time. While this may sound like
92it could crowd the management process, there are numerous reasons to do so. For
93example, the Ansoff Matrix and the Balanced Scorecard are relatively popular,
94and they cover entirely different parts of a company's strategy. Using the
95results from the Balanced Scorecard could inform a company of the potential
96product and market demands, such as from customer or supplier survey results, to
97help the company determine which Ansoff Matrix strategy to pursue. However, a
98combined approach at this level would require mature frameworks and focused
99managers who are able to strategize at a high level.
100
101Lastly, it should be noted that the author of the Ansoff matrix, Igor Ansoff,
102often used the term [[https://en.wikipedia.org/wiki/Analysis_paralysis][paralysis by analysis]] to explain the mistake of companies
103who overuse analysis and spend too much time planning. Companies need to
104understand the utility of a strategic management framework while ensuring that
105the company is poised to execute as efficiently as they have planned.
content/blog/2019-12-16-password-security.md added +127
@@ -0,0 +1,127 @@
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
13of the most important digital highlights of the last decade. With
14[billions of people affected 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
17systems. If you think you\'ve been part of a breach, or you want to
18check and see, you can use [Have I Been
19Pwned](https://haveibeenpwned.com/) to see if your email has been
20involved in any public breaches. Remember that there\'s a possibility
21that a company experienced a breach and did not report it to anyone.
22
23## How Do I Protect Myself?
24
25The first place to start with any personal security check-up is to
26gather a list of all the different websites, apps, or programs that
27require you to have login credentials. Optionally, once you know where
28your information is being stored, you can sort the list from the
29most-important items such as banks or government logins to less
30important items such as your favorite meme site. You will want to ensure
31that your critical logins are secure before getting to the others.
32
33Once you think you have a good idea of all your different authentication
34methods, I recommend using a password manager such as
35[Bitwarden](https://bitwarden.com/). Using a password manager allows you
36to automatically save your logins, create randomized passwords, and
37transfer passwords across devices. However, you\'ll need to memorize
38your \"vault password\" that allows you to open the password manager.
39It\'s important to make this something hard to guess since it would
40allow anyone who has it to access every password you\'ve stored in
41there.
42
43Personally, I recommend using a
44[passphrase](https://en.wikipedia.org/wiki/Passphrase) instead of a
45[password](https://en.wikipedia.org/wiki/Password) for your vault
46password. Instead of using a string of characters (whether random or
47simple), use a phrase and add in symbols and a number. For example, your
48vault password could be `Racing-Alphabet-Gourd-Parrot3`. Swap
49the symbols out for whichever symbol you want, move the number around,
50and fine-tune the passphrase until you are confident that you can
51remember it whenever necessary.
52
53Once you\'ve stored your passwords, make sure you continually check up
54on your account and make sure you aren\'t following bad password
55practices. Krebs on Security has a great [blog post on password
56recommendations](https://krebsonsecurity.com/password-dos-and-donts/).
57Any time that a data breach happens, make sure you check to see if you
58were included, and if you need to reset any account passwords.
59
60# Developers
61
62## What Are the Basic Requirements?
63
64When developing any password-protected application, there are a few
65basic rules that anyone should follow even if they do not follow any
66official guidelines such as NIST. The foremost practice is to require
67users to use passwords that are at least 8 characters and cannot easily
68be guessed. This sounds extremely simple, but it requires quite a few
69different strategies. First, the application should check the potential
70passwords against a dictionary of insecure passwords such
71`password`, `1234abc`, or
72`application_name`.
73
74Next, the application should offer guidance on the strength of passwords
75being entered during enrollment. Further, NIST officially recommends
76**not\*** implementing any composition rules that make passwords hard to
77remember (e.g. passwords with letters, numbers, and special characters)
78and instead encouraging the use of long pass phrases which can include
79spaces. It should be noted that to be able to keep spaces within
80passwords, all unicode characters should be supported, and passwords
81should not be truncated.
82
83## What Does NIST Recommend?
84
85The National Institute of Standards and Technology
86([NIST](https://www.nist.gov)) in the US Department of Commerce
87regularly publishes information around information security and digital
88identity guidelines. Recently, NIST published [Special Publication
89800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html): Digital
90Identity Guidelines and Authentication and Lifecycle Management.
91
92> A Memorized Secret authenticator - commonly referred to as a password
93> or, if numeric, a PIN - is a secret value intended to be chosen and
94> memorized by the user. Memorized secrets need to be of sufficient
95> complexity and secrecy that it would be impractical for an attacker to
96> guess or otherwise discover the correct secret value. A memorized
97> secret is something you know.
98>
99> - NIST Special Publication 800-63B
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
108 known 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 [NIST
123SP800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html) but NIST
124offers a lot of information on digital identities, enrollment, identity
125proofing, authentication, lifecycle management, federation, and
126assertions in the total [NIST SP800-63 Digital Identity
127Guidelines](https://pages.nist.gov/800-63-3/).
content/blog/2019-12-16-password-security.org deleted −103
@@ -1,103 +0,0 @@
1#+title: Password Security
2#+date: 2019-12-16
3#+description: Password security basics.
4#+filetags: :security:
5
6* Users
7** Why Does It Matter?
8Information security, including passwords and identities, has become one of the
9most important digital highlights of the last decade. With [[https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/][billions of people
10affected by data breaches each year]], there's a greater need to introduce strong
11information security systems. If you think you've been part of a breach, or you
12want to check and see, you can use [[https://haveibeenpwned.com/][Have I Been Pwned]] to see if your email has
13been involved in any public breaches. Remember that there's a possibility that a
14company experienced a breach and did not report it to anyone.
15
16** How Do I Protect Myself?
17The first place to start with any personal security check-up is to gather a list
18of all the different websites, apps, or programs that require you to have login
19credentials. Optionally, once you know where your information is being stored,
20you can sort the list from the most-important items such as banks or government
21logins to less important items such as your favorite meme site. You will want to
22ensure that your critical logins are secure before getting to the others.
23
24Once you think you have a good idea of all your different authentication
25methods, I recommend using a password manager such as [[https://bitwarden.com/][Bitwarden]]. Using a
26password manager allows you to automatically save your logins, create randomized
27passwords, and transfer passwords across devices. However, you'll need to
28memorize your "vault password" that allows you to open the password manager.
29It's important to make this something hard to guess since it would allow anyone
30who has it to access every password you've stored in there.
31
32Personally, I recommend using a [[https://en.wikipedia.org/wiki/Passphrase][passphrase]] instead of a [[https://en.wikipedia.org/wiki/Password][password]] for your vault
33password. Instead of using a string of characters (whether random or simple),
34use a phrase and add in symbols and a number. For example, your vault password
35could be =Racing-Alphabet-Gourd-Parrot3=. Swap the symbols out for whichever
36symbol you want, move the number around, and fine-tune the passphrase until you
37are confident that you can remember it whenever necessary.
38
39Once you've stored your passwords, make sure you continually check up on your
40account and make sure you aren't following bad password practices. Krebs on
41Security has a great [[https://krebsonsecurity.com/password-dos-and-donts/][blog post on password recommendations]]. Any time that a data
42breach happens, make sure you check to see if you were included, and if you need
43to reset any account passwords.
44
45* Developers
46** What Are the Basic Requirements?
47When developing any password-protected application, there are a few basic rules
48that anyone should follow even if they do not follow any official guidelines
49such as NIST. The foremost practice is to require users to use passwords that
50are at least 8 characters and cannot easily be guessed. This sounds extremely
51simple, but it requires quite a few different strategies. First, the application
52should check the potential passwords against a dictionary of insecure passwords
53such =password=, =1234abc=, or =application_name=.
54
55Next, the application should offer guidance on the strength of passwords being
56entered during enrollment. Further, NIST officially recommends *not**
57implementing any composition rules that make passwords hard to remember (e.g.
58passwords with letters, numbers, and special characters) and instead encouraging
59the use of long pass phrases which can include spaces. It should be noted that
60to be able to keep spaces within passwords, all unicode characters should be
61supported, and passwords should not be truncated.
62
63** What Does NIST Recommend?
64The National Institute of Standards and Technology ([[https://www.nist.gov][NIST]]) in the US Department
65of Commerce regularly publishes information around information security and
66digital identity guidelines. Recently, NIST published [[https://pages.nist.gov/800-63-3/sp800-63b.html][Special Publication
67800-63b]]: Digital Identity Guidelines and Authentication and Lifecycle
68Management.
69
70#+begin_quote
71A Memorized Secret authenticator - commonly referred to as a password or, if
72numeric, a PIN - is a secret value intended to be chosen and memorized by the
73user. Memorized secrets need to be of sufficient complexity and secrecy that it
74would be impractical for an attacker to guess or otherwise discover the correct
75secret value. A memorized secret is something you know.
76
77- NIST Special Publication 800-63B
78#+end_quote
79
80NIST offers a lot of guidance on passwords, but I'm going to highlight just a
81few of the important factors:
82
83- Require passwords to be a minimum of 8 characters (6 characters if randomly
84 generated and be generated using an approved random bit generator).
85- Compare potential passwords against a list that contains values known to be
86 commonly-used, expected, or compromised.
87- Offer guidance on password strength, such as a strength meter.
88- Implement a rate-limiting mechanism to limit the number of failed
89 authentication attempts for each user account.
90- Do not require composition rules for passwords and do not require passwords to
91 be changed periodically (unless compromised).
92- Allow pasting of user identification and passwords to facilitate the use of
93 password managers.
94- Allow users to view the password as it is being entered.
95- Use secure forms of communication and storage, including salting and hashing
96 passwords using a one-way key derivation function.
97
98NIST offers further guidance on other devices that require specific security
99policies, querying for passwords, and more. All the information discussed so far
100comes from [[https://pages.nist.gov/800-63-3/sp800-63b.html][NIST SP800-63b]] but NIST offers a lot of information on digital
101identities, enrollment, identity proofing, authentication, lifecycle management,
102federation, and assertions in the total [[https://pages.nist.gov/800-63-3/][NIST SP800-63 Digital Identity
103Guidelines]].
content/blog/2020-01-25-linux-software.md added +282
@@ -0,0 +1,282 @@
1+++
2date = 2020-01-25
3title = "My Favorite Linux Software"
4description = ""
5draft = false
6+++
7
8# GUI Applications
9
10## Etcher
11
12![Etcher
13Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/etcher.png)
14
15[Etcher](https://www.balena.io/etcher/) is a quick and easy way to burn
16ISO images to CDs and USB devices. There are two different ways you can
17install this program. First, you can navigate to the [official
18website](https://www.balena.io/etcher/) and download the AppImage file,
19which can run without installation.
20
21However, AppImage files are not executable by default, so you\'ll either
22need to right-click to open the properties of the file and click the
23\"Allow executing file as program\" box in the Permissions tab or use
24the following command:
25
26```sh
27chmod u+x FILE_NAME
28```
29
30If you don\'t like AppImage files or just prefer repositories, you can
31use the following commands to add the author\'s repository and install
32it through the command-line only.
33
34First, you\'ll have to echo the repo and write it to a list file:
35
36```sh
37echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
38```
39
40Next, add the application keys to Ubuntu\'s keyring:
41
42```sh
43sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
44```
45
46Finally, update the repositories and install the app.
47
48```sh
49sudo apt update && sudo apt install balena-etcher-electron
50```
51
52Using Arch, Manjaro, or another distro using the AUR? Use this command
53instead:
54
55```sh
56sudo pacman -S etcher
57```
58
59## Atom
60
61![Atom
62Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/atom.png)
63
64[Atom](https://atom.io) is the self-proclaimed \"hackable text editor
65for the 21st century\". This text editor is made by GitHub, [now owned
66by
67Microsoft](https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/),
68and has some of the best add-ons available to customize the layout and
69abilities of the app.
70
71First, add the Atom repository to your sources.
72
73```sh
74sudo add-apt-repository ppa:webupd8team/atom
75```
76
77Next, update your package listings and install atom.
78
79```sh
80sudo apt update && sudo apt install atom
81```
82
83If you have issues updating your packages with the Atom repository,
84you\'ll need to use the snap package described below instead of the
85repository. To remove the repository we just added, use this command:
86
87```sh
88sudo add-apt-repository -r ppa:webupd8team/atom
89```
90
91You can also install Atom as a snap package, but it must be installed
92with the `--classic` flag. A [full explanation is
93available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement)
94if you\'d like to read more about why you need the classic flag.
95
96```sh
97snap install atom --classic
98```
99
100Using Arch, Manjaro, or another distro using the AUR? Use this command
101instead:
102
103```sh
104sudo pacman -S atom
105```
106
107## Visual Studio Code
108
109![Visual Studio Code
110Code](https://img.cleberg.net/blog/20200125-the-best-linux-software/vscode.png)
111
112[Visual Studio Code](https://code.visualstudio.com) is yet another
113fantastic choice for programming on Linux, especially if you need those
114extra add-ons to spice up your late-night coding sessions. The theme
115used in the screenshot is
116[Mars](https://marketplace.visualstudio.com/items?itemName=EliverLara.mars)
117by theme creator [Eliver Lara](https://github.com/EliverLara), who makes
118a ton of great themes for VS Code, Atom, and various Linux desktop
119environments.
120
121To install VS Code, you\'ll need to download the `.deb` file
122from the official website. Once you\'ve downloaded the file, either
123double-click it to install through the Software Center or run the
124following command:
125
126```sh
127sudo dpkg -i FILE_NAME.deb
128```
129
130You can also install VS Code as a snap package, but it must be installed
131with the `--classic` flag. A [full explanation is
132available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement)
133if you\'d like to read more about why you need the classic flag.
134
135```sh
136snap install code --classic
137```
138
139Using Arch, Manjaro, or another distro using the AUR? Use these commands
140instead:
141
142```sh
143sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
144```
145
146## GNOME Tweaks
147
148![Gnome Tweaks
149Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/gnome-tweaks.png)
150
151[Gnome Tweaks](https://gitlab.gnome.org/GNOME/gnome-tweaks) is the
152ultimate tool to use if you want to customize your GNOME desktop
153environment. This is how you can switch application themes (GTK), shell
154themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you
155just need to install the official package.
156
157```sh
158sudo apt install gnome-tweaks
159```
160
161If you\'ve installed Manjaro or Arch with Gnome, you should have the
162tweak tool pre-installed. If you\'re on Fedora, this tool is available
163as an official package:
164
165```sh
166sudo dnf install gnome-tweaks
167```
168
169## Steam
170
171![Steam
172Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png)
173
174[Steam](https://steampowered.com) is one of the most popular gaming
175libraries for computers and is one of the main reasons that many people
176have been able to switch to Linux in recent years, thanks to Steam
177Proton, which makes it easier to play games not officially created for
178Linux platforms.
179
180To install Steam on Ubuntu, you just need to install the official
181package.
182
183```sh
184sudo apt install steam-installer
185```
186
187For Arch-based systems, you\'ll simply need to install the
188`steam` package. However, this requires that you enable the
189`multilib` source. To do so, use the following command:
190
191```sh
192sudo nano /etc/pacman.conf
193```
194
195Now, scroll down and uncomment the `multilib` section.
196
197``` config
198# Before:
199#[multilib]
200#Include = /etc/pacman.d/mirrorlist
201
202# After:
203[multilib]
204Include = /etc/pacman.d/mirrorlist
205```
206
207Finally, install the program:
208
209```sh
210sudo pacman -S steam
211```
212
213[Problem Launching Steam Games? Click
214Here.](./2020-01-26-steam-on-ntfs-drives.html)
215
216# Command-Line Packages
217
218## neofetch
219
220![Neofetch
221Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/neofetch.png)
222
223[Neofetch](https://github.com/dylanaraps/neofetch) is a customizable
224tool used in the command-line to show system information. This is
225exceptionally useful if you want to see your system\'s information
226quickly without the clutter of some resource-heavy GUI apps.
227
228This is an official package if you\'re running Ubuntu 17.04 or later, so
229simply use the following command:
230
231```sh
232sudo apt install neofetch
233```
234
235If you\'re running Ubuntu 16.10 or earlier, you\'ll have to use a series
236of commands:
237
238```sh
239sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
240```
241
242Using Arch, Manjaro, or another distro using the AUR? Use this command
243instead:
244
245```sh
246sudo pacman -S neofetch
247```
248
249## yt-dlp
250
251![yt-dlp
252Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/yt-dlp.png)
253
254[yt-dlp](https://github.com/yt-dlp/yt-dlp) is an extremely handy
255command-line tool that allows you to download video or audio files from
256various websites, such as YouTube. There are a ton of different options
257when running this package, so be sure to run `yt-dlp --help`
258first to look through everything you can do (or give up and search for
259the best config online).
260
261While this shouldn\'t be a problem for most users, yt-dlp requires
262Python 2.6, 2.7, or 3.2+ to work correctly, so install Python if you
263don\'t have it already. You can check to see if you have Python
264installed by running:
265
266```sh
267python -V
268```
269
270To get the youtube-dl package, simply curl the URL and output the
271results.
272
273```sh
274sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
275```
276
277Finally, make the file executable so that it can be run from the
278command-line.
279
280```sh
281sudo chmod a+rx /usr/local/bin/yt-dlp
282```
content/blog/2020-01-25-linux-software.org deleted −249
@@ -1,249 +0,0 @@
1#+title: Linux Software
2#+date: 2020-01-25
3#+description: A look at some useful Linux applications.
4#+filetags: :linux:
5
6* GUI Applications
7** Etcher
8#+caption: Etcher Screenshot
9[[https://img.cleberg.net/blog/20200125-the-best-linux-software/etcher.png]]
10
11[[https://www.balena.io/etcher/][Etcher]] is a quick and easy way to burn ISO images to CDs and USB devices. There
12are two different ways you can install this program. First, you can navigate to
13the [[https://www.balena.io/etcher/][official website]] and download the AppImage file, which can run without
14installation.
15
16However, AppImage files are not executable by default, so you'll either need to
17right-click to open the properties of the file and click the "Allow executing
18file as program" box in the Permissions tab or use the following command:
19
20#+begin_src sh
21chmod u+x FILE_NAME
22#+end_src
23
24If you don't like AppImage files or just prefer repositories, you can use the
25following commands to add the author's repository and install it through the
26command-line only.
27
28First, you'll have to echo the repo and write it to a list file:
29
30#+begin_src sh
31echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
32#+end_src
33
34Next, add the application keys to Ubuntu's keyring:
35
36#+begin_src sh
37sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
38#+end_src
39
40Finally, update the repositories and install the app.
41
42#+begin_src sh
43sudo apt update && sudo apt install balena-etcher-electron
44#+end_src
45
46Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
47
48#+begin_src sh
49sudo pacman -S etcher
50#+end_src
51
52** Atom
53#+caption: Atom Screenshot
54[[https://img.cleberg.net/blog/20200125-the-best-linux-software/atom.png]]
55
56[[https://atom.io][Atom]] is the self-proclaimed "hackable text editor for the 21st century". This
57text editor is made by GitHub, [[https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/][now owned by Microsoft]], and has some of the best
58add-ons available to customize the layout and abilities of the app.
59
60First, add the Atom repository to your sources.
61
62#+begin_src sh
63sudo add-apt-repository ppa:webupd8team/atom
64#+end_src
65
66Next, update your package listings and install atom.
67
68#+begin_src sh
69sudo apt update && sudo apt install atom
70#+end_src
71
72If you have issues updating your packages with the Atom repository, you'll need
73to use the snap package described below instead of the repository. To remove the
74repository we just added, use this command:
75
76#+begin_src sh
77sudo add-apt-repository -r ppa:webupd8team/atom
78#+end_src
79
80You can also install Atom as a snap package, but it must be installed with the
81=--classic= flag. A [[https://language-bash.com/blog/how-to-snap-introducing-classic-confinement][full explanation is available]] if you'd like to read more
82about why you need the classic flag.
83
84#+begin_src sh
85snap install atom --classic
86#+end_src
87
88Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
89
90#+begin_src sh
91sudo pacman -S atom
92#+end_src
93
94** Visual Studio Code
95#+caption: Visual Studio Code Code
96[[https://img.cleberg.net/blog/20200125-the-best-linux-software/vscode.png]]
97
98[[https://code.visualstudio.com][Visual Studio Code]] is yet another fantastic choice for programming on Linux,
99especially if you need those extra add-ons to spice up your late-night coding
100sessions. The theme used in the screenshot is [[https://marketplace.visualstudio.com/items?itemName=EliverLara.mars][Mars]] by theme creator [[https://github.com/EliverLara][Eliver Lara]],
101who makes a ton of great themes for VS Code, Atom, and various Linux desktop
102environments.
103
104To install VS Code, you'll need to download the =.deb= file from the official
105website. Once you've downloaded the file, either double-click it to install
106through the Software Center or run the following command:
107
108#+begin_src sh
109sudo dpkg -i FILE_NAME.deb
110#+end_src
111
112You can also install VS Code as a snap package, but it must be installed with
113the =--classic= flag. A [[https://language-bash.com/blog/how-to-snap-introducing-classic-confinement][full explanation is available]] if you'd like to read more
114about why you need the classic flag.
115
116#+begin_src sh
117snap install code --classic
118#+end_src
119
120Using Arch, Manjaro, or another distro using the AUR? Use these commands
121instead:
122
123#+begin_src sh
124sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
125#+end_src
126
127** GNOME Tweaks
128#+caption: Gnome Tweaks Screenshot
129[[https://img.cleberg.net/blog/20200125-the-best-linux-software/gnome-tweaks.png]]
130
131[[https://gitlab.gnome.org/GNOME/gnome-tweaks][Gnome Tweaks]] is the ultimate tool to use if you want to customize your GNOME
132desktop environment. This is how you can switch application themes (GTK), shell
133themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you just need
134to install the official package.
135
136#+begin_src sh
137sudo apt install gnome-tweaks
138#+end_src
139
140If you've installed Manjaro or Arch with Gnome, you should have the tweak tool
141pre-installed. If you're on Fedora, this tool is available as an official
142package:
143
144#+begin_src sh
145sudo dnf install gnome-tweaks
146#+end_src
147
148** Steam
149#+caption: Steam Screenshot
150[[https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png]]
151
152[[https://steampowered.com][Steam]] is one of the most popular gaming libraries for computers and is one of
153the main reasons that many people have been able to switch to Linux in recent
154years, thanks to Steam Proton, which makes it easier to play games not
155officially created for Linux platforms.
156
157To install Steam on Ubuntu, you just need to install the official package.
158
159#+begin_src sh
160sudo apt install steam-installer
161#+end_src
162
163For Arch-based systems, you'll simply need to install the =steam= package.
164However, this requires that you enable the =multilib= source. To do so, use the
165following command:
166
167#+begin_src sh
168sudo nano /etc/pacman.conf
169#+end_src
170
171Now, scroll down and uncomment the =multilib= section.
172
173#+begin_src config
174# Before:
175#[multilib]
176#Include = /etc/pacman.d/mirrorlist
177
178# After:
179[multilib]
180Include = /etc/pacman.d/mirrorlist
181#+end_src
182
183Finally, install the program:
184
185#+begin_src sh
186sudo pacman -S steam
187#+end_src
188
189[[./2020-01-26-steam-on-ntfs-drives.html][Problem Launching Steam Games?
190Click Here.]]
191
192* Command-Line Packages
193** neofetch
194#+caption: Neofetch Screenshot
195[[https://img.cleberg.net/blog/20200125-the-best-linux-software/neofetch.png]]
196
197[[https://github.com/dylanaraps/neofetch][Neofetch]] is a customizable tool used in the command-line to show system
198information. This is exceptionally useful if you want to see your system's
199information quickly without the clutter of some resource-heavy GUI apps.
200
201This is an official package if you're running Ubuntu 17.04 or later, so simply
202use the following command:
203
204#+begin_src sh
205sudo apt install neofetch
206#+end_src
207
208If you're running Ubuntu 16.10 or earlier, you'll have to use a series of
209commands:
210
211#+begin_src sh
212sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
213#+end_src
214
215Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
216
217#+begin_src sh
218sudo pacman -S neofetch
219#+end_src
220
221** yt-dlp
222#+caption: yt-dlp Screenshot
223[[https://img.cleberg.net/blog/20200125-the-best-linux-software/yt-dlp.png]]
224
225[[https://github.com/yt-dlp/yt-dlp][yt-dlp]] is an extremely handy command-line tool that allows you to download video
226or audio files from various websites, such as YouTube. There are a ton of
227different options when running this package, so be sure to run =yt-dlp --help=
228first to look through everything you can do (or give up and search for the best
229config online).
230
231While this shouldn't be a problem for most users, yt-dlp requires Python 2.6,
2322.7, or 3.2+ to work correctly, so install Python if you don't have it already.
233You can check to see if you have Python installed by running:
234
235#+begin_src sh
236python -V
237#+end_src
238
239To get the youtube-dl package, simply curl the URL and output the results.
240
241#+begin_src sh
242sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
243#+end_src
244
245Finally, make the file executable so that it can be run from the command-line.
246
247#+begin_src sh
248sudo chmod a+rx /usr/local/bin/yt-dlp
249#+end_src
content/blog/2020-01-26-steam-on-ntfs.md added +97
@@ -0,0 +1,97 @@
1+++
2date = 2020-01-26
3title = "Linux Gaming Tweak: Steam on NTFS Drives"
4description = ""
5draft = false
6+++
7
8# Auto-Mount Steam Drives
9
10![Steam
11Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png)
12
13If you want to see how to install Steam on Linux, see my other post:
14[Linux Software](../linux-software/).
15
16Are you having trouble launching games, even though they\'ve installed
17correctly? This may happen if you\'re storing your games on an
18NTFS-formatted drive. This shouldn\'t be an issue if you\'re storing
19your games on the same drive that Steam is on, but some gamers prefer to
20put Steam on their main drive and game files on another SSD or HDD.
21
22To fix this problem, you\'ll need to try a few things. First, you\'ll
23need to install the `ntfs-3g` package, which is meant for
24better interoperability with Linux.
25
26```sh
27sudo apt install ntfs-3g
28```
29
30Next, you should set up the `/etc/fstab` file to
31automatically mount your drives on boot. To automatically mount your
32drives when the computer boots up, you\'ll have to create the folders
33you want to mount your drive to first. I store mine in the
34`/mnt` folder using names that I\'ll recognize, but you can
35create your folders wherever you want.
36
37```sh
38mkdir /path/to/folder
39```
40
41For example:
42
43```sh
44mkdir /mnt/steam_library
45```
46
47To automatically mount drives upon system boot, you will need to collect
48a few items. The UUID is the identification number connected to
49whichever drive you\'re using to store Steam games.
50
51Drives are usually labeled similar to `/dev/nvme0n1p1` or
52`/dev/sda1`, so you\'ll need to find the line in the output
53of the command below that correlates to your drive and copy the UUID
54over to the `/etc/fstab` file.
55
56```sh
57sudo blkid | grep UUID=
58```
59
60Next, you\'ll need your `uid` and `gid`. To find
61these, run the following command:
62
63```sh
64id -u && id -g
65```
66
67Now that you have collected the necessary information, open the
68`/etc/fstab` file:
69
70```sh
71sudo nano /etc/fstab
72```
73
74Each drive you want to mount on boot should have its own line in the
75`/etc/fstab` file that looks similar to this:
76
77``` config
78UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
79```
80
81Now all you need to do is unmount your drive and re-mount it. You can
82unmount the drive by doing this (be sure to use the correct drive name
83here):
84
85```sh
86sudo umount /dev/sdxX
87```
88
89You can re-mount all your drives by executing the following:
90
91```sh
92sudo mount -a
93```
94
95If you don\'t know what your drive name is, or you\'re nervous about
96unmounting and re-mounting, simply reboot your computer, and it will be
97done for you automatically.
content/blog/2020-01-26-steam-on-ntfs.org deleted −91
@@ -1,91 +0,0 @@
1#+title: Linux Gaming Tweak: Steam on NTFS Drives
2#+date: 2020-01-26
3#+description: Learn how to fix Steam NTFS issues on Linux.
4#+filetags: :gaming:linux:
5
6* Auto-Mount Steam Drives
7#+caption: Steam Screenshot
8[[https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png]]
9
10If you want to see how to install Steam on Linux, see my other post: [[../linux-software/][Linux
11Software]].
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#+begin_src sh
24sudo apt install ntfs-3g
25#+end_src
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#+begin_src sh
34mkdir /path/to/folder
35#+end_src
36
37For example:
38
39#+begin_src sh
40mkdir /mnt/steam_library
41#+end_src
42
43To automatically mount drives upon system boot, you will need to collect 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#+begin_src sh
52sudo blkid | grep UUID=
53#+end_src
54
55Next, you'll need your =uid= and =gid=. To find these, run the following
56command:
57
58#+begin_src sh
59id -u && id -g
60#+end_src
61
62Now that you have collected the necessary information, open the =/etc/fstab=
63file:
64
65#+begin_src sh
66sudo nano /etc/fstab
67#+end_src
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#+begin_src config
73UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
74#+end_src
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#+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 unmounting
90and re-mounting, simply reboot your computer, and it will be done for you
91automatically.
content/blog/2020-02-09-cryptography-basics.md added +180
@@ -0,0 +1,180 @@
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
16a readable format into an unreadable format (and vice-versa) through a
17series of processes and secrets. More technically, this is the Internet
18Security Glossary\'s definition:
19
20> \[Cryptography is\] the mathematical science that deals with
21> transforming data to render its meaning unintelligible (i.e., to hide
22> its semantic content), prevent its undetected alteration, or prevent
23> its unauthorized use. If the transformation is reversible,
24> cryptography also deals with restoring encrypted data to an
25> intelligible form.
26>
27> - [Internet Security Glossary
28> (2000)](https://tools.ietf.org/html/rfc2828)
29
30Cryptography cannot offer protection against the loss of data; it simply
31offers encryption methods to protect data at-rest and data in-traffic.
32At a high-level, encrypted is when plaintext data is encrypted to
33ciphertext (a secure form of text that cannot be understood unless
34decrypted back to plaintext). The encryption process is completed
35through the use of a mathematical function that utilizes one or more
36values called keys to encrypt or decrypt the data.
37
38# Key Elements of Cryptographic Systems
39
40To create or evaluate a cryptographic system, you need to know the
41essential pieces to the system:
42
43- **Encryption Algorithm (Primitive):\*** A mathematical process that
44 encrypts and decrypts data.
45- **Encryption Key:\*** A string of bits used within the encryption
46 algorithm as the secret that allows successful encryption or
47 decryption of data.
48- **Key Length (Size):\*** The maximum number of bits within the
49 encryption key. It\'s important to remember that key size is
50 regulated in many countries.
51- **Message Digest:\*** A smaller, fixed-size bit string version of
52 the original message. This is practically infeasible to reverse,
53 which is why it\'s commonly used to verify integrity.
54
55# Symmetric Systems (Secret Key Cryptography)
56
57Symmetric cryptography utilizes a secret, bidirectional key to perform
58both encryption and decryption of the data. The most common
59implementation of symmetric cryptography is the Advanced Encryption
60Standard, which uses keys that are 128 bits to 256 bits in size. This
61standard came after the National Institute of Standards and Technology
62(NIST) decided to retire the Data Encryption Standard (DES) in 2001.
63
64Since brute force attacks strongly correlate with key length, the 56-bit
65key length of DES was considered insecure after it was publicly broken
66in under 24 hours. However, there is a modern implementation of DES
67called Triple DES where the DES method is applied three times to each
68data block.
69
70The main advantages to symmetric systems are the ease of use, since only
71one key is required for both encryption and decryption, and the
72simplicity of the algorithms. This helps with bulk data encryption that
73may unnecessarily waste time and power using asymmetric systems.
74
75However, symmetric systems have disadvantages to keep in mind. Since the
76key is private, it can be difficult to safely distribute keys to
77communication partners. Additionally, the key cannot be used to sign
78messages since it\'s necessary to keep the key private.
79
80# Asymmetric Systems (Public Key Cryptography)
81
82Asymmetric cryptography utilizes two keys within the system: a secret
83key that is privately-held and a public key that can be distributed
84freely. The interesting aspect of asymmetric cryptography is that either
85key can be used to encrypt the data, there\'s no rule that dictates
86which key must be used for encryption. Once one key is used to encrypt
87the data, only the other key can be used to decrypt the data. This means
88that if the private key encrypts the data, only the public key can
89decrypt the data.
90
91An advantage of this system is that if you successfully decrypt data
92using one of the keys, you can be sure of the sender since only the
93other key could have encrypted the data.
94
95One of the major implementations of an asymmetric system is a digital
96signature. A digital signature can be generated using the sender\'s
97private key, or a one-way hash function and is used to provide assurance
98for the integrity and authenticity of the message. A couple common
99message digest algorithms are SHA-256 and SHA-512, which securely
100compress data and produce a 128-bit message digest.
101
102It should be noted that man-in-the-middle attacks are one of the risks
103with digital signatures and public keys. To combat this, applications
104often use a public key infrastructure (PKI) to independently
105authenticate the validity of signatures and keys.
106
107Due to the large key size and [inefficient mathematical
108functions](https://crypto.stackexchange.com/a/591) of asymmetric
109encryption, elliptical curve cryptography (ECC) is often used to
110increase security while using fewer resources.
111
112# Applications of Cryptographic Systems
113
114There are quite a few implementations of cryptographic systems around
115the world. Here are a few popular examples:
116
117**Transport Layer Security (TLS):\*** One of the most famous
118cryptographic solutions created is TLS, a session-layered or
119connection-layered internet protocol that allows for secure
120communications between browsers and servers. Using handshakes, peer
121negotiation, and authentication allows TLS to prevent eavesdropping and
122malicious transformation of data. The major reason for TLS popularity is
123that a major vulnerability was found in the SSL protocol in 2014.
124Instead of SSL, TLS can be used with HTTP to form HTTPS and is the
125preferred method for modern web development due to its increased
126security.
127
128**Secure Hypertext Transfer Protocol (HTTPS):\*** An application layer
129protocol that allows for secure transport of data between servers and
130web clients. One of the unique parts of HTTPS is that it uses a secured
131port number instead of the default web port address.
132
133**Virtual Private Network (VPN):\*** VPNs are made to securely extend a
134private network across public networks by utilizing an encrypted layered
135tunneling protocol paired with an authentication method, such as
136usernames and passwords. This technology originally allowed remote
137employees to access their company\'s data but have evolved into one of
138the top choices for anyone who wishes to mask their sensitive personal
139data.
140
141**Internet Protocol Security (IPSec):\*** This protocol suite
142facilitates communication between two or more hosts or subnets by
143authenticating and encrypting the data packets. IPSec is used in a lot
144of VPNs to establish the VPN connection through the transport and tunnel
145mode encryption methods. IPSec encrypts just the data portion of packets
146in the transport methods, but it encrypts both the data and headers in
147the tunnel method (introducing an additional header for authentication).
148
149**Secure Shell (SSH):\*** SSH is another network protocol used to
150protect network services by authenticating users through a secure
151channel. This protocol is often used for command-line (shell) functions
152such as remote shell commands, logins, and file transfers.
153
154**Kerberos:\*** Developed by MIT, Kerberos is a computer-network
155authentication protocol that works on the basis of tickets to allow
156nodes communicating over a non-secure network to prove their identity to
157one another securely. This is most commonly used in business
158environments when used as the authentication and encryption method for
159Windows Active Directory (AD).
160
161# Cybersecurity Controls
162
163If you\'re someone who needs solutions on how to control risks
164associated with utilizing a crytograhpic system, start with a few basic
165controls:
166
167- **Policies:\*** A policy on the use of cryptographic controls for
168 protection of information is implemented and is in accordance with
169 organizational objectives.
170- **Key management:\*** A policy on the use, protection and lifetime
171 of cryptographic keys is implemented through the entire application
172 lifecycle.
173- **Key size:\*** The organization has researched the optimal key size
174 for their purposes, considering national laws, required processing
175 power, and longevity of the solution.
176- **Algorithm selection:\*** Implemented algorithms are sufficiently
177 appropriate for the business of the organization, robust, and align
178 with recommended guidelines.
179- **Protocol configuration:\*** Protocols have been reviewed and
180 configured suitable to the purpose of the business.
content/blog/2020-02-09-cryptography-basics.org deleted −158
@@ -1,158 +0,0 @@
1#+title: Cryptography Basics
2#+date: 2020-02-09
3#+description: Learn about the basics of cryptography.
4#+filetags: :security:
5
6* Similar Article Available
7If you haven't already, feel free to read my post on [[../aes-encryption/][AES Encryption]].
8
9* What is Cryptography?
10In layman's terms, cryptography is a process that can change data from a
11readable format into an unreadable format (and vice-versa) through a series of
12processes and secrets. More technically, this is the Internet Security
13Glossary's definition:
14
15#+begin_quote
16[Cryptography is] the mathematical science that deals with transforming data to
17render its meaning unintelligible (i.e., to hide its semantic content), prevent
18its undetected alteration, or prevent its unauthorized use. If the
19transformation is reversible, cryptography also deals with restoring encrypted
20data to an intelligible form.
21
22- [[https://tools.ietf.org/html/rfc2828][Internet Security Glossary (2000)]]
23#+end_quote
24
25Cryptography cannot offer protection against the loss of data; it simply offers
26encryption methods to protect data at-rest and data in-traffic. At a high-level,
27encrypted is when plaintext data is encrypted to ciphertext (a secure form of
28text that cannot be understood unless decrypted back to plaintext). The
29encryption process is completed through the use of a mathematical function that
30utilizes one or more values called keys to encrypt or decrypt the data.
31
32* Key Elements of Cryptographic Systems
33To create or evaluate a cryptographic system, you need to know the essential
34pieces to the system:
35
36- *Encryption Algorithm (Primitive):** A mathematical process that encrypts and
37 decrypts data.
38- *Encryption Key:** A string of bits used within the encryption algorithm as
39 the secret that allows successful encryption or decryption of data.
40- *Key Length (Size):** The maximum number of bits within the encryption key.
41 It's important to remember that key size is regulated in many countries.
42- *Message Digest:** A smaller, fixed-size bit string version of the original
43 message. This is practically infeasible to reverse, which is why it's commonly
44 used to verify integrity.
45
46* Symmetric Systems (Secret Key Cryptography)
47Symmetric cryptography utilizes a secret, bidirectional key to perform both
48encryption and decryption of the data. The most common implementation of
49symmetric cryptography is the Advanced Encryption Standard, which uses keys that
50are 128 bits to 256 bits in size. This standard came after the National
51Institute of Standards and Technology (NIST) decided to retire the Data
52Encryption Standard (DES) in 2001.
53
54Since brute force attacks strongly correlate with key length, the 56-bit key
55length of DES was considered insecure after it was publicly broken in under 24
56hours. However, there is a modern implementation of DES called Triple DES where
57the DES method is applied three times to each data block.
58
59The main advantages to symmetric systems are the ease of use, since only one key
60is required for both encryption and decryption, and the simplicity of the
61algorithms. This helps with bulk data encryption that may unnecessarily waste
62time and power using asymmetric systems.
63
64However, symmetric systems have disadvantages to keep in mind. Since the key is
65private, it can be difficult to safely distribute keys to communication
66partners. Additionally, the key cannot be used to sign messages since it's
67necessary to keep the key private.
68
69* Asymmetric Systems (Public Key Cryptography)
70Asymmetric cryptography utilizes two keys within the system: a secret key that
71is privately-held and a public key that can be distributed freely. The
72interesting aspect of asymmetric cryptography is that either key can be used to
73encrypt the data, there's no rule that dictates which key must be used for
74encryption. Once one key is used to encrypt the data, only the other key can be
75used to decrypt the data. This means that if the private key encrypts the data,
76only the public key can decrypt the data.
77
78An advantage of this system is that if you successfully decrypt data using one
79of the keys, you can be sure of the sender since only the other key could have
80encrypted the data.
81
82One of the major implementations of an asymmetric system is a digital signature.
83A digital signature can be generated using the sender's private key, or a
84one-way hash function and is used to provide assurance for the integrity and
85authenticity of the message. A couple common message digest algorithms are
86SHA-256 and SHA-512, which securely compress data and produce a 128-bit message
87digest.
88
89It should be noted that man-in-the-middle attacks are one of the risks with
90digital signatures and public keys. To combat this, applications often use a
91public key infrastructure (PKI) to independently authenticate the validity of
92signatures and keys.
93
94Due to the large key size and [[https://crypto.stackexchange.com/a/591][inefficient mathematical functions]] of asymmetric
95encryption, elliptical curve cryptography (ECC) is often used to increase
96security while using fewer resources.
97
98* Applications of Cryptographic Systems
99There are quite a few implementations of cryptographic systems around the world.
100Here are a few popular examples:
101
102*Transport Layer Security (TLS):** One of the most famous cryptographic
103solutions created is TLS, a session-layered or connection-layered internet
104protocol that allows for secure communications between browsers and servers.
105Using handshakes, peer negotiation, and authentication allows TLS to prevent
106eavesdropping and malicious transformation of data. The major reason for TLS
107popularity is that a major vulnerability was found in the SSL protocol in 2014.
108Instead of SSL, TLS can be used with HTTP to form HTTPS and is the preferred
109method for modern web development due to its increased security.
110
111*Secure Hypertext Transfer Protocol (HTTPS):** An application layer protocol
112that allows for secure transport of data between servers and web clients. One of
113the unique parts of HTTPS is that it uses a secured port number instead of the
114default web port address.
115
116*Virtual Private Network (VPN):** VPNs are made to securely extend a private
117network across public networks by utilizing an encrypted layered tunneling
118protocol paired with an authentication method, such as usernames and passwords.
119This technology originally allowed remote employees to access their company's
120data but have evolved into one of the top choices for anyone who wishes to mask
121their sensitive personal data.
122
123*Internet Protocol Security (IPSec):** This protocol suite facilitates
124communication between two or more hosts or subnets by authenticating and
125encrypting the data packets. IPSec is used in a lot of VPNs to establish the VPN
126connection through the transport and tunnel mode encryption methods. IPSec
127encrypts just the data portion of packets in the transport methods, but it
128encrypts both the data and headers in the tunnel method (introducing an
129additional header for authentication).
130
131*Secure Shell (SSH):** SSH is another network protocol used to protect network
132services by authenticating users through a secure channel. This protocol is
133often used for command-line (shell) functions such as remote shell commands,
134logins, and file transfers.
135
136*Kerberos:** Developed by MIT, Kerberos is a computer-network authentication
137protocol that works on the basis of tickets to allow nodes communicating over a
138non-secure network to prove their identity to one another securely. This is most
139commonly used in business environments when used as the authentication and
140encryption method for Windows Active Directory (AD).
141
142* Cybersecurity Controls
143If you're someone who needs solutions on how to control risks associated with
144utilizing a crytograhpic system, start with a few basic controls:
145
146- *Policies:** A policy on the use of cryptographic controls for protection of
147 information is implemented and is in accordance with organizational
148 objectives.
149- *Key management:** A policy on the use, protection and lifetime of
150 cryptographic keys is implemented through the entire application lifecycle.
151- *Key size:** The organization has researched the optimal key size for their
152 purposes, considering national laws, required processing power, and longevity
153 of the solution.
154- *Algorithm selection:** Implemented algorithms are sufficiently appropriate
155 for the business of the organization, robust, and align with recommended
156 guidelines.
157- *Protocol configuration:** Protocols have been reviewed and configured
158 suitable to the purpose of the business.
content/blog/2020-03-25-session-manager.org deleted −120
@@ -1,120 +0,0 @@
1#+title: Session Private Messenger
2#+date: 2020-03-25
3#+description: Exploring the Session Private Messenger application.
4#+filetags: :privacy:
5
6* Privacy Warning
7The company behind Session (Loki Foundation) is from Australia. If you didn't
8know, Australia has introduced [[https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf][legislation]] mandating companies comply with
9government requests to build backdoor access into applications. For more
10information, read my article on [[./2020-01-25-aes-encryption.html][AES Encryption]].
11
12* About Session
13[[https://getsession.org][Session]] is a private, cross-platform messaging app from the [[https://loki.foundation][Loki Foundation]]. As
14someone who has spent years looking for quality alternatives to major messaging
15apps, I was excited when I first heard about Session. Reading through [[https://arxiv.org/pdf/2002.04609.pdf][Session's
16white paper]], you can learn the technologies behind the Session app. Part of the
17security of Session comes from the Signal protocol, which was forked as the
18origin of Session.
19
20#+begin_quote
21Session is an end-to-end encrypted messenger that removes sensitive metadata
22collection, and is designed for people who want privacy and freedom from any
23forms of surveillance.
24#+end_quote
25
26In general, this app promises security through end-to-end encryption,
27decentralized onion routing, and private identities. The biggest change that the
28Loki Foundation has made to the Signal protocol is removing the need for a phone
29number. Instead, a random identification string is generated for any session you
30create. This means you can create a new session for each device if you want to,
31or link new devices with your ID.
32
33Since Session's website and white paper describe the details of Session's
34security, I'm going to focus on using the app in this post.
35
36* Features
37Since most people are looking for an alternative to a popular chat app, I am
38going to list out the features that Session has so that you are able to
39determine if the app would suit your needs:
40
41- Multiple device linking (via QR code or ID)
42- App locking via device screen lock, password, or fingerprint
43- Screenshot blocking
44- Incognito keyboard
45- Read receipts and typing indicators
46- Mobile notification customization
47- Old message deletion and conversation limit
48- Backups
49- Recovery phrase
50- Account deletion, including ID, messages, sessions, and contacts
51
52* Downloads
53I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and iOS 15.
54All apps have worked well without many issues.
55
56Below is a brief overview of the Session app on Linux. To get this app, you'll
57need to go to the [[https://getsession.org/download/][Downloads]] page and click to link to the operating system
58you're using.
59
60For Linux, it will download an AppImage that you'll need to enable with the
61following command:
62
63#+begin_src sh
64sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
65#+end_src
66
67#+caption: Session Download Options
68[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_downloads.png]]
69
70* Creating an Account
71Once you've installed the app, simply run the app and create your unique Session
72ID. It will look something like this:
73=05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608=.
74
75You'll need to set a display name and, optionally, a password. If you set a
76password, you will need to enter it every time you open the app.
77
78#+caption: Session Login (Linux)
79[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_linux_login.png]]
80
81#+caption: Session Login (macOS)
82[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_login.png]]
83
84#+caption: Password Authentication
85[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_password_authentication.png]]
86
87* Start Messaging
88Once you've created your account and set up your profile details, the next step
89is to start messaging other people. To do so, you'll need to share your Session
90ID with other people. From this point, it's fairly straightforward and acts like
91any other messaging app, so I won't dive into much detail here.
92
93** macOS
94#+caption: macOS Conversations
95[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_conversations.png]]
96
97One key feature to note is that the desktop application now provides a helpful
98pop-up box explaining the process that Session uses to hide your IP address:
99
100#+caption: IP Address Help Box
101[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_ip.png]]
102
103** iOS
104The mobile app is quite simple and effective, giving you all the standard mobile
105messaging options you'd expect.
106
107#+caption: iOS App
108[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_ios.png]]
109
110* Potential Issues
111I've discovered one annoying issue that would prevent from using this app
112regularly. On a mobile device, there have been issues with receiving messages on
113time. Even with battery optimization disabled and no network restrictions,
114Session notifications sometimes do not display until I open the app or the
115conversation itself and wait a few moments. This is actually one of the reasons
116I stopped using Signal (this seems fixed as of my updates in 2021/2022, so I
117wouldn't worry about this issue anymore).
118
119Looking for another messenger instead of Session? I recommend Signal, Matrix,
120and IRC.
content/blog/2020-03-25-session-messenger.md added +137
@@ -0,0 +1,137 @@
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
11didn\'t know, 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
14access into 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
20app from the [Loki Foundation](https://loki.foundation). As someone who
21has spent years looking for quality alternatives to major messaging
22apps, I was excited when I first heard about Session. Reading through
23[Session\'s white paper](https://arxiv.org/pdf/2002.04609.pdf), you can
24learn the technologies behind the Session app. Part of the security of
25Session comes from the Signal protocol, which was forked as the origin
26of Session.
27
28> Session is an end-to-end encrypted messenger that removes sensitive
29> metadata collection, and is designed for people who want privacy and
30> freedom from any forms of surveillance.
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 [Downloads](https://getsession.org/download/)
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```sh
73sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
74```
75
76![Session Download
77Options](https://img.cleberg.net/blog/20200325-session-private-messenger/session_downloads.png)
78
79# Creating an Account
80
81Once you\'ve installed the app, simply run the app and create your
82unique Session ID. It will look something like this:
83`05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608`.
84
85You\'ll need to set a display name and, optionally, a password. If you
86set a password, you will need to enter it every time you open the app.
87
88![Session Login
89(Linux)](https://img.cleberg.net/blog/20200325-session-private-messenger/session_linux_login.png)
90
91![Session Login
92(macOS)](https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_login.png)
93
94![Password
95Authentication](https://img.cleberg.net/blog/20200325-session-private-messenger/session_password_authentication.png)
96
97# Start Messaging
98
99Once you\'ve created your account and set up your profile details, the
100next step is to start messaging other people. To do so, you\'ll need to
101share your Session ID with other people. From this point, it\'s fairly
102straightforward and acts like any other messaging app, so I won\'t dive
103into much detail here.
104
105## macOS
106
107![macOS
108Conversations](https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_conversations.png)
109
110One key feature to note is that the desktop application now provides a
111helpful pop-up box explaining the process that Session uses to hide your
112IP address:
113
114![IP Address Help
115Box](https://img.cleberg.net/blog/20200325-session-private-messenger/session_ip.png)
116
117## iOS
118
119The mobile app is quite simple and effective, giving you all the
120standard mobile messaging options you\'d expect.
121
122![iOS
123App](https://img.cleberg.net/blog/20200325-session-private-messenger/session_ios.png)
124
125# Potential Issues
126
127I\'ve discovered one annoying issue that would prevent from using this
128app regularly. On a mobile device, there have been issues with receiving
129messages on time. Even with battery optimization disabled and no network
130restrictions, Session notifications sometimes do not display until I
131open the app or the conversation itself and wait a few moments. This is
132actually one of the reasons I stopped using Signal (this seems fixed as
133of my updates in 2021/2022, so I wouldn\'t worry about this issue
134anymore).
135
136Looking for another messenger instead of Session? I recommend Signal,
137Matrix, and IRC.
content/blog/2020-05-03-homelab.md added +166
@@ -0,0 +1,166 @@
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
11hobbies (other than building a gaming desktop). However, as the
12quarantine for COVID-19 stretches out further and further, I found
13myself bored and in search of new hobbies. After spending the last few
14months browsing the [r/homelab](https://www.reddit.com/r/homelab/)
15subreddit, I decided it was time to jump in and try things out for
16myself.
17
18Since I am a beginner and just recently graduated from college,
19everything I\'ve done so far in my homelab is fairly low-budget.
20
21# Hardware
22
23![HomeLab
24Diagram](https://img.cleberg.net/blog/20200503-homelab/homelab-min.png)
25
26### Raspberry Pi 4
27
28Luckily, I had actually purchased a [Raspberry Pi
294](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) before
30the quarantine started so that I could try to keep Plex Media Center
31running, even while my desktop computer was turned off. I started here,
32using the Pi to hold Plex and Pi-hole until I grew tired with the slow
33performance.
34
35Here are the specifications for the Pi 4:
36
37- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
38- 4GB LPDDR4-3200 SDRAM
39- Gigabit Ethernet
40- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
41- 64 GB MicroSD Card
42
43## Dell Optiplex 5040
44
45Since I wasn\'t happy with the Pi as my main server, I turned to
46Craigslist. I know a lot of other homelabbers use Ebay, but I can\'t
47seem to ever trust it enough to purchase items on there. So I used
48Craigslist and found a Dell Optiplex 5040 desktop computer on sale for
49\$90. While this computer might be underpowered, it was one of the few
50computers under \$100 that was available during quarantine.
51
52Here are the specifications for the Dell Optiplex 5040:
53
54- Intel Core i3 6100
55- 8GB RAM DDR3
56- Intel HD Graphics
57- Gigabit Ethernet
58- 500GB Hard Drive
59
60While this hardware would be awful for a work computer or a gaming rig,
61it turned out to be wonderful for my server purposes. The only
62limitation I have found so far is the CPU. The i3-6100 only has enough
63power for a single 4k video transcode at a time. I haven\'t tested more
64than three 1080p streams at a time, but the maximum amount of streams
65I\'ve ever actually used is two.
66
67## WD easystore 10TB & 8TB
68
69Application storage and temporary files are stored on the internal hard
70drive of the server, but all media files (movies, tv, games, books, etc)
71are stored externally on my WD easystore hard drive. Creating auto-boot
72configurations in the `/etc/fstab` file on my server allows
73the hard drives to automatically mount whenever I need to restart my
74server.
75
76> Update: In March 2022, I shucked the hard drives out of their external
77> cases, put some Kapton tape on the third power pin to prevent power
78> shutdowns, and stuck them inside my server tower using internal SATA
79> cables.
80
81## Netgear Unmanaged Switch
82
83To manage all the ethernet cords used by my homelab, my desktop, and my
84living room media center, I purchased an 8-port gigabit ethernet switch
85for \$50 at my local computer store. This is probably much more than I
86should have spent on an unmanaged switch, but I am comfortable with the
87choice.
88
89## TP-Link Managed Switch
90
91Since I use the unmanaged switch to group all living room devices
92together, I use the managed switch to configure VLANs and secure my
93network.
94
95## Arris TM1602A Modem & Sagecom Fast 5280 Router
96
97My default modem and router, provided by my ISP, are fairly standard.
98The Arris modem supports DOCSIS 3.0, which is something that I
99definitely wanted as a minimum. The Sagecom router is also standard, no
100fancy bells or whistles. However, it does support DHCP and DHCPv6, which
101is something you can use to route all household traffic through a
102pi-hole or firewall.
103
104## TP-Link EAP
105
106In order to gain better control over the network, I use my own wireless
107access point instead of the one included in the Sagecom router above.
108Now I can control and organize all of my ethernet connections through
109the VLANs on the managed switch and wireless connections through the
110VLANS on the EAP.
111
112## Generic Printer
113
114The last piece to my homelab is a standard wireless printer. Nothing
115special here.
116
117# Software
118
119## Ubuntu Server 20.04
120
121While the 20.04 version of Ubuntu was just released, I always like to
122experiment with new features (and I don\'t mind breaking my system - it
123just gives me more experience learning how to fix things). So, I have
124Ubuntu Server 20.04 installed on the Dell Optiplex server and Ubuntu
125Server 19.10 installed on the Raspberry Pi. Once I find an acceptable
126use for the Pi, I will most likely switch the operating system.
127
128## Docker
129
130I am *very* new to Docker, but I have had a lot of fun playing with it
131so far. Docker is used to create containers that can hold all the
132contents of a system without interfering with other software on the same
133system. So far, I have successfully installed pi-hole, GitLab, Gogs, and
134Nextcloud in containers. However, I opted to delete all of those so that
135I can reconfigure them more professionally at a later time.
136
137## Plex Media Server
138
139Plex is a media center software that allows you to organize your movies,
140TV shows, music, photos, and videos automatically. It will even download
141metadata for you so that you can easily browse these collections.
142
143## Pi-hole
144
145Pi-hole is an alternative ad-blocker that runs at the DNS level,
146allowing you to block traffic when it hits your network, so that you can
147reject any traffic you deem to be bad. Pi-hole uses blacklists and
148whitelists to decide which traffic block and, luckily, there are a lot
149of pre-made lists out there on Reddit, GitHub, etc.
150
151## Nextcloud
152
153While I had trouble with the Docker version of Nextcloud, I was very
154successful when setting up the snap version. Using this, I was able to
155map Nextcloud to a subdomain of a domain I own in Namecheap.
156Additionally, Nextcloud has an integration with Let\'s Encrypt that
157allows me to issue certificates automatically to any new domain I
158authorize.
159
160## Webmin
161
162To monitor my servers, and the processes running on them, I use the
163Webmin dashboard. This was fairly painless to set up, and I currently
164access it straight through the server\'s IP address. In the future, I
165will be looking to configure Webmin to use a custom domain just like
166Nextcloud.
content/blog/2020-05-03-homelab.org deleted −136
@@ -1,136 +0,0 @@
1#+title: An Inside Look at My Homelab
2#+date: 2020-05-03
3#+description: A retrospective on the first iteration of my home lab.
4#+filetags: :sysadmin:
5
6* What is a Homelab?
7Starting as a developer, I have largely stayed away from hardware-based hobbies
8(other than building a gaming desktop). However, as the quarantine for COVID-19
9stretches out further and further, I found myself bored and in search of new
10hobbies. After spending the last few months browsing the [[https://www.reddit.com/r/homelab/][r/homelab]] subreddit, I
11decided it was time to jump in and try things out for myself.
12
13Since I am a beginner and just recently graduated from college, everything I've
14done so far in my homelab is fairly low-budget.
15
16* Hardware
17#+caption: HomeLab Diagram
18[[https://img.cleberg.net/blog/20200503-homelab/homelab-min.png]]
19
20*** Raspberry Pi 4
21Luckily, I had actually purchased a [[https://www.raspberrypi.org/products/raspberry-pi-4-model-b/][Raspberry Pi 4]] before the quarantine started
22so that I could try to keep Plex Media Center running, even while my desktop
23computer was turned off. I started here, using the Pi to hold Plex and Pi-hole
24until I grew tired with the slow performance.
25
26Here are the specifications for the Pi 4:
27
28- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
29- 4GB LPDDR4-3200 SDRAM
30- Gigabit Ethernet
31- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
32- 64 GB MicroSD Card
33
34** Dell Optiplex 5040
35Since I wasn't happy with the Pi as my main server, I turned to Craigslist. I
36know a lot of other homelabbers use Ebay, but I can't seem to ever trust it
37enough to purchase items on there. So I used Craigslist and found a Dell
38Optiplex 5040 desktop computer on sale for $90. While this computer might be
39underpowered, it was one of the few computers under $100 that was available
40during quarantine.
41
42Here are the specifications for the Dell Optiplex 5040:
43
44- Intel Core i3 6100
45- 8GB RAM DDR3
46- Intel HD Graphics
47- Gigabit Ethernet
48- 500GB Hard Drive
49
50While this hardware would be awful for a work computer or a gaming rig, it
51turned out to be wonderful for my server purposes. The only limitation I have
52found so far is the CPU. The i3-6100 only has enough power for a single 4k video
53transcode at a time. I haven't tested more than three 1080p streams at a time,
54but the maximum amount of streams I've ever actually used is two.
55
56** WD easystore 10TB & 8TB
57Application storage and temporary files are stored on the internal hard drive of
58the server, but all media files (movies, tv, games, books, etc) are stored
59externally on my WD easystore hard drive. Creating auto-boot configurations in
60the =/etc/fstab= file on my server allows the hard drives to automatically mount
61whenever I need to restart my server.
62
63#+begin_quote
64Update: In March 2022, I shucked the hard drives out of their external cases,
65put some Kapton tape on the third power pin to prevent power shutdowns, and
66stuck them inside my server tower using internal SATA cables.
67#+end_quote
68
69** Netgear Unmanaged Switch
70To manage all the ethernet cords used by my homelab, my desktop, and my living
71room media center, I purchased an 8-port gigabit ethernet switch for $50 at my
72local computer store. This is probably much more than I should have spent on an
73unmanaged switch, but I am comfortable with the choice.
74
75** TP-Link Managed Switch
76Since I use the unmanaged switch to group all living room devices together, I
77use the managed switch to configure VLANs and secure my network.
78
79** Arris TM1602A Modem & Sagecom Fast 5280 Router
80My default modem and router, provided by my ISP, are fairly standard. The Arris
81modem supports DOCSIS 3.0, which is something that I definitely wanted as a
82minimum. The Sagecom router is also standard, no fancy bells or whistles.
83However, it does support DHCP and DHCPv6, which is something you can use to
84route all household traffic through a pi-hole or firewall.
85
86** TP-Link EAP
87In order to gain better control over the network, I use my own wireless access
88point instead of the one included in the Sagecom router above. Now I can control
89and organize all of my ethernet connections through the VLANs on the managed
90switch and wireless connections through the VLANS on the EAP.
91
92** Generic Printer
93The last piece to my homelab is a standard wireless printer. Nothing special
94here.
95
96* Software
97** Ubuntu Server 20.04
98While the 20.04 version of Ubuntu was just released, I always like to experiment
99with new features (and I don't mind breaking my system - it just gives me more
100experience learning how to fix things). So, I have Ubuntu Server 20.04 installed
101on the Dell Optiplex server and Ubuntu Server 19.10 installed on the Raspberry
102Pi. Once I find an acceptable use for the Pi, I will most likely switch the
103operating system.
104
105** Docker
106I am /very/ new to Docker, but I have had a lot of fun playing with it so far.
107Docker is used to create containers that can hold all the contents of a system
108without interfering with other software on the same system. So far, I have
109successfully installed pi-hole, GitLab, Gogs, and Nextcloud in containers.
110However, I opted to delete all of those so that I can reconfigure them more
111professionally at a later time.
112
113** Plex Media Server
114Plex is a media center software that allows you to organize your movies, TV
115shows, music, photos, and videos automatically. It will even download metadata
116for you so that you can easily browse these collections.
117
118** Pi-hole
119Pi-hole is an alternative ad-blocker that runs at the DNS level, allowing you to
120block traffic when it hits your network, so that you can reject any traffic you
121deem to be bad. Pi-hole uses blacklists and whitelists to decide which traffic
122block and, luckily, there are a lot of pre-made lists out there on Reddit,
123GitHub, etc.
124
125** Nextcloud
126While I had trouble with the Docker version of Nextcloud, I was very successful
127when setting up the snap version. Using this, I was able to map Nextcloud to a
128subdomain of a domain I own in Namecheap. Additionally, Nextcloud has an
129integration with Let's Encrypt that allows me to issue certificates
130automatically to any new domain I authorize.
131
132** Webmin
133To monitor my servers, and the processes running on them, I use the Webmin
134dashboard. This was fairly painless to set up, and I currently access it
135straight through the server's IP address. In the future, I will be looking to
136configure Webmin to use a custom domain just like Nextcloud.
content/blog/2020-05-19-customizing-ubuntu.md added +211
@@ -0,0 +1,211 @@
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
16the best perks of Linux is that it is **extremely\*** customizable. You
17can change the styles of the windows, shell (status bars/docks), icons,
18fonts, terminals, and more.
19
20In this post, I\'m going to go through customization on Ubuntu 20.04
21(GNOME) since most new users tend to choose Ubuntu-based distros. If
22you\'ve found a way to install Arch with i3-gaps, I\'m assuming you know
23how to find more advanced tutorials out there on customizations.
24
25## Required Tools
26
27![Gnome
28Tweaks](https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-tweaks-min.png)
29
30Ubuntu 20.04 ships with the default desktop environment
31[Gnome](https://www.gnome.org/), which includes the handy
32`gnome-tweaks` tool to quickly change designs. To install
33this, just open your terminal and enter the following command:
34
35```sh
36sudo apt install gnome-tweaks
37```
38
39After you\'ve finished installing the tool, simply launch the Tweaks
40application, and you\'ll be able to access the various customization
41options available by default on Ubuntu. You might even like some of the
42pre-installed options.
43
44## GNOME Application Themes
45
46To change the themes applied to applications in GNOME, you will need to
47change the Applications dropdown in the Appearance section of Tweaks. To
48add more themes, you will have to find your preferred theme online and
49follow the steps below to have it show up in the Tweaks tool. While you
50may find themes anywhere, one of the most popular sites for GNOME themes
51is [gnome-look.org](https://www.gnome-look.org/). This website contains
52themes for applications, shells, icons, and cursors.
53
54Steps to import themes into Tweaks:
55
561. Download the theme.
572. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you
58 will need to extract the contents. This is easiest when opening the
59 file explorer, right-clicking the compressed file, and choosing
60 \"Extract here.\"
613. Move the theme folder to `/usr/share/themes/`. You can do
62 so with the following command:
63 `sudo mv theme-folder/ /usr/share/themes/`.
64 - Icons and cursors will be moved to the
65 `/usr/share/icons/` folder.
66 - Fonts will be moved to the `/usr/share/fonts/` folder
67 Alternatively, you can move them to the
68 `/usr/share/fonts/opentype/` or
69 `/usr/share/fonts/opentype/` folders, if you have a
70 specific font type.
714. Close tweaks if it is open. Re-open Tweaks and your new theme will
72 be available in the Applications dropdown in the Appearance section
73 of Tweaks.
74
75If the theme is not showing up after you\'ve moved it into the themes
76folder, you may have uncompressed the folder into a sub-folder. You can
77check this by entering the theme folder and listing the contents:
78
79```sh
80cd /usr/share/themes/Mojave-Dark && ls -la
81```
82
83This is an example of what the contents of your theme folder should look
84like. If you just see another folder there, you should move that folder
85up into the `/usr/share/themes/` folder.
86
87```sh
88cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
89```
90
91## GNOME Shell Themes
92
93To change the appearance of the title bar, default dock, app menu, and
94other parts of the GNOME shell, you\'ll need to install the [user
95themes](https://extensions.gnome.org/extension/19/user-themes/)
96extension on [Gnome Extensions](https://extensions.gnome.org/). To be
97able to install extensions, you will first need to install the browser
98extension that the website instructs you to. See this screenshot for the
99blue box with a link to the extension.
100
101![Gnome
102Extensions](https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-extensions-min.png)
103
104After the browser extension is installed, you will need to install the
105native host connector:
106
107```sh
108sudo apt install chrome-gnome-shell
109```
110
111Finally, you can go the [user
112themes](https://extensions.gnome.org/extension/19/user-themes/)
113extension page and click the installation button. This will enable the
114Shell option in Tweaks. Now you can move shell themes to the
115`/usr/share/themes` directory, using the same steps mentioned
116in the previous section, and enable the new theme in Tweaks.
117
118## Icons & Cursors
119
120Icons and cursors are installed exactly the same way, so I\'m grouping
121these together in this post. Both of these items will need to follow the
122same process as installing themes, except you will want to move your
123font folders to the `/usr/share/icons/` directory instead.
124
125## Fonts
126
127Fonts are one of the overlooked parts of customization, but a good font
128can make the whole screen look different. For example, I have installed
129the [IBM Plex](https://github.com/IBM/plex/releases) fonts on my system.
130This follows the same process as installing themes, except you will want
131to move your font folders to the `/usr/share/fonts/`
132directory instead.
133
134## Terminal
135
136If you spend a lot of time typing commands, you know how important the
137style and functionality of the terminal is. After spending a lot of time
138using the default GNOME terminal with [unix
139shell](https://en.wikipedia.org/wiki/Bash_(Unix_shell)), I decided to
140try some different options. I ended up choosing
141[Terminator](https://terminator-gtk3.readthedocs.io/en/latest/) with
142[zsh](https://en.wikipedia.org/wiki/Z_shell).
143
144Terminator is great if you need to open multiple terminals at one time
145by simply right-clicking and splitting the screen into as many terminals
146as you want. While this project hasn\'t been updated in a while, [it is
147coming under new
148development](https://github.com/gnome-terminator/terminator/issues/1).
149However, this terminal is great and I haven\'t experienced any errors
150yet.
151
152For the shell choice, I decided to choose zsh after trying it out on a
153fresh Manjaro installation. Zsh is great if you like to change the
154themes of your terminal, include icons, or add plugins.
155
156The desktop uses the
157[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)
158to suggest past commands as you type. In addition, it suggests
159corrections if you misspell a command. Lastly, it uses the
160`af-magic` theme, which adds dashed lines between commands,
161moving the user@host tag to the right side of the terminal, and changes
162the colors. There are plenty of plugins and themes to choose from. Just
163figure out what you like and add it to your `~/.zshrc` file!
164
165### Steps to Replicate My Terminal
166
167To install zsh on Ubuntu, enter the following command into a terminal:
168
169```sh
170sudo apt install zsh
171```
172
173Then, enter the next command to activate zsh:
174
175```sh
176sudo chsh -s $(which zsh) $(whoami)
177```
178
179To install Terminator on Ubuntu:
180
181```sh
182sudo apt install terminator
183```
184
185To install Oh My Zsh on Ubuntu:
186
187```sh
188sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
189```
190
191To install zsh-autosuggestions via Oh My Zsh:
192
193```sh
194git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
195```
196
197Then, add the following plugin wording to your `~/.zshrc`
198file (the default config usually has the `git` plugin
199activated, so just add any other plugins to the parentheses separated by
200a space):
201
202```sh
203nano ~/.zshrc
204```
205
206```sh
207plugins=(git zsh-autosuggestions)
208```
209
210Finally, you need to log out of your computer and log back in so your
211user shell can refresh.
content/blog/2020-05-19-customizing-ubuntu.org deleted −178
@@ -1,178 +0,0 @@
1#+title: Beginner's Guide: Customizing Ubuntu
2#+date: 2020-05-19
3#+description: A beginner's guide to customizing the Ubuntu operating system.
4#+filetags: :linux:
5
6* More Information
7For inspiration on designing your *nix computer, check out the
8[[https://libredd.it/r/unixporn][r/unixporn]] subreddit!
9
10* Customizing Ubuntu
11New to Linux and want to add a personal touch to your machine? One of the best
12perks of Linux is that it is *extremely** customizable. You can change the
13styles of the windows, shell (status bars/docks), icons, fonts, terminals, and
14more.
15
16In this post, I'm going to go through customization on Ubuntu 20.04 (GNOME)
17since most new users tend to choose Ubuntu-based distros. If you've found a way
18to install Arch with i3-gaps, I'm assuming you know how to find more advanced
19tutorials out there on customizations.
20
21** Required Tools
22#+caption: Gnome Tweaks
23[[https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-tweaks-min.png]]
24
25Ubuntu 20.04 ships with the default desktop environment [[https://www.gnome.org/][Gnome]], which includes
26the handy =gnome-tweaks= tool to quickly change designs. To install this, just
27open your terminal and enter the following command:
28
29#+begin_src sh
30sudo apt install gnome-tweaks
31#+end_src
32
33After you've finished installing the tool, simply launch the Tweaks application,
34and you'll be able to access the various customization options available by
35default on Ubuntu. You might even like some of the pre-installed options.
36
37** GNOME Application Themes
38To change the themes applied to applications in GNOME, you will need to change
39the Applications dropdown in the Appearance section of Tweaks. To add more
40themes, you will have to find your preferred theme online and follow the steps
41below to have it show up in the Tweaks tool. While you may find themes anywhere,
42one of the most popular sites for GNOME themes is [[https://www.gnome-look.org/][gnome-look.org]]. This website
43contains themes for applications, shells, icons, and cursors.
44
45Steps to import themes into Tweaks:
46
471. Download the theme.
482. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you will need
49 to extract the contents. This is easiest when opening the file explorer,
50 right-clicking the compressed file, and choosing "Extract here."
513. Move the theme folder to =/usr/share/themes/=. You can do so with the
52 following command: =sudo mv theme-folder/ /usr/share/themes/=.
53 - Icons and cursors will be moved to the =/usr/share/icons/= folder.
54 - Fonts will be moved to the =/usr/share/fonts/= folder Alternatively, you
55 can move them to the =/usr/share/fonts/opentype/= or
56 =/usr/share/fonts/opentype/= folders, if you have a specific font type.
574. Close tweaks if it is open. Re-open Tweaks and your new theme will be
58 available in the Applications dropdown in the Appearance section of Tweaks.
59
60If the theme is not showing up after you've moved it into the themes folder, you
61may have uncompressed the folder into a sub-folder. You can check this by
62entering the theme folder and listing the contents:
63
64#+begin_src sh
65cd /usr/share/themes/Mojave-Dark && ls -la
66#+end_src
67
68This is an example of what the contents of your theme folder should look like.
69If you just see another folder there, you should move that folder up into the
70=/usr/share/themes/= folder.
71
72#+begin_src sh
73cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
74#+end_src
75
76** GNOME Shell Themes
77To change the appearance of the title bar, default dock, app menu, and other
78parts of the GNOME shell, you'll need to install the [[https://extensions.gnome.org/extension/19/user-themes/][user themes]] extension on
79[[https://extensions.gnome.org/][Gnome Extensions]]. To be able to install extensions, you will first need to
80install the browser extension that the website instructs you to. See this
81screenshot for the blue box with a link to the extension.
82
83#+caption: Gnome Extensions
84[[https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-extensions-min.png]]
85
86After the browser extension is installed, you will need to install the native
87host connector:
88
89#+begin_src sh
90sudo apt install chrome-gnome-shell
91#+end_src
92
93Finally, you can go the [[https://extensions.gnome.org/extension/19/user-themes/][user themes]] extension page and click the installation
94button. This will enable the Shell option in Tweaks. Now you can move shell
95themes to the =/usr/share/themes= directory, using the same steps mentioned in
96the previous section, and enable the new theme in Tweaks.
97
98** Icons & Cursors
99Icons and cursors are installed exactly the same way, so I'm grouping these
100together in this post. Both of these items will need to follow the same process
101as installing themes, except you will want to move your font folders to the
102=/usr/share/icons/= directory instead.
103
104** Fonts
105Fonts are one of the overlooked parts of customization, but a good font can make
106the whole screen look different. For example, I have installed the [[https://github.com/IBM/plex/releases][IBM Plex]]
107fonts on my system. This follows the same process as installing themes, except
108you will want to move your font folders to the =/usr/share/fonts/= directory
109instead.
110
111** Terminal
112If you spend a lot of time typing commands, you know how important the style and
113functionality of the terminal is. After spending a lot of time using the default
114GNOME terminal with [[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][unix shell]], I decided to try some different options. I ended
115up choosing [[https://terminator-gtk3.readthedocs.io/en/latest/][Terminator]] with [[https://en.wikipedia.org/wiki/Z_shell][zsh]].
116
117Terminator is great if you need to open multiple terminals at one time by simply
118right-clicking and splitting the screen into as many terminals as you want.
119While this project hasn't been updated in a while, [[https://github.com/gnome-terminator/terminator/issues/1][it is coming under new
120development]]. However, this terminal is great and I haven't experienced any
121errors yet.
122
123For the shell choice, I decided to choose zsh after trying it out on a fresh
124Manjaro installation. Zsh is great if you like to change the themes of your
125terminal, include icons, or add plugins.
126
127The desktop uses the [[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]] to suggest past commands as you type.
128In addition, it suggests corrections if you misspell a command. Lastly, it uses
129the =af-magic= theme, which adds dashed lines between commands, moving the
130user@host tag to the right side of the terminal, and changes the colors. There
131are plenty of plugins and themes to choose from. Just figure out what you like
132and add it to your =~/.zshrc= file!
133
134*** Steps to Replicate My Terminal
135To install zsh on Ubuntu, enter the following command into a terminal:
136
137#+begin_src sh
138sudo apt install zsh
139#+end_src
140
141Then, enter the next command to activate zsh:
142
143#+begin_src sh
144sudo chsh -s $(which zsh) $(whoami)
145#+end_src
146
147To install Terminator on Ubuntu:
148
149#+begin_src sh
150sudo apt install terminator
151#+end_src
152
153To install Oh My Zsh on Ubuntu:
154
155#+begin_src sh
156sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
157#+end_src
158
159To install zsh-autosuggestions via Oh My Zsh:
160
161#+begin_src sh
162git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
163#+end_src
164
165Then, add the following plugin wording to your =~/.zshrc= file (the default
166config usually has the =git= plugin activated, so just add any other plugins to
167the parentheses separated by a space):
168
169#+begin_src sh
170nano ~/.zshrc
171#+end_src
172
173#+begin_src sh
174plugins=(git zsh-autosuggestions)
175#+end_src
176
177Finally, you need to log out of your computer and log back in so your user shell
178can refresh.
content/blog/2020-07-20-video-game-sales.md added +183
@@ -0,0 +1,183 @@
1+++
2date = 2020-07-20
3title = "Data Exploration: Video Games Sales"
4description = ""
5draft = false
6+++
7
8# Background Information
9
10This dataset (obtained from
11[Kaggle](https://www.kaggle.com/gregorut/videogamesales/data)) contains
12a list of video games with sales greater than 100,000 copies. It was
13generated by a scrape of vgchartz.com.
14
15Fields include:
16
17- Rank: Ranking of overall sales
18- Name: The game name
19- Platform: Platform of the game release (i.e. PC,PS4, etc.)
20- Year: Year of the game\'s release
21- Genre: Genre of the game
22- Publisher: Publisher of the game
23- NA~Sales~: Sales in North America (in millions)
24- EU~Sales~: Sales in Europe (in millions)
25- JP~Sales~: Sales in Japan (in millions)
26- Other~Sales~: Sales in the rest of the world (in millions)
27- Global~Sales~: Total worldwide sales.
28
29There are 16,598 records. 2 records were dropped due to incomplete
30information.
31
32# Import the Data
33
34``` python
35# Import the Python libraries we will be using
36import pandas as pd
37import numpy as np
38import seaborn as sns; sns.set()
39import matplotlib.pyplot as plt
40
41# Load the file using the path to the downloaded file
42file = r'video_game_sales.csv'
43df = pd.read_csv(file)
44df
45```
46
47![Dataframe
48Results](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/01_dataframe-min.png)
49
50# Explore the Data
51
52``` python
53# With the description function, we can see the basic stats. For example, we can
54# also see that the 'Year' column has some incomplete values.
55df.describe()
56```
57
58![df.describe()](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/02_describe-min.png)
59
60``` python
61# This function shows the rows and columns of NaN values. For example, df[179,3] = nan
62np.where(pd.isnull(df))
63
64(array([179, ..., 16553], dtype=int64),
65 array([3, ..., 5], dtype=int64))
66```
67
68# Visualize the Data
69
70``` python
71# This function plots the global sales by platform
72sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
73```
74
75![Plot of Global Sales by
76Platform](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/03_plot-min.png)
77
78``` python
79# This function plots the global sales by genre
80sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
81```
82
83![Plot of Global Sales by
84Genre](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/04_plot-min.png)
85
86``` python
87# This function plots the global sales by year
88sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
89```
90
91![Plot of Global Sales by
92Year](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/05_plot-min.png)
93
94``` python
95# This function plots four different lines to show sales from different regions.
96# The global sales plot line is commented-out, but can be included for comparison
97df2 = df.groupby('Year').sum()
98years = range(1980,2019)
99
100a = df2['NA_Sales']
101b = df2['EU_Sales']
102c = df2['JP_Sales']
103d = df2['Other_Sales']
104# e = df2['Global_Sales']
105
106fig, ax = plt.subplots(figsize=(12,12))
107ax.set_ylabel('Region Sales (in Millions)')
108ax.set_xlabel('Year')
109
110ax.plot(years, a, label='NA_Sales')
111ax.plot(years, b, label='EU_Sales')
112ax.plot(years, c, label='JP_Sales')
113ax.plot(years, d, label='Other_Sales')
114# ax.plot(years, e, label='Global_Sales')
115
116ax.legend()
117plt.show()
118```
119
120![Plot of Regional Sales by
121Year](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/06_plot-min.png)
122
123## Investigate Outliers
124
125``` python
126# Find the game with the highest sales in North America
127df.loc[df['NA_Sales'].idxmax()]
128
129Rank 1
130Name Wii Sports
131Platform Wii
132Year 2006
133Genre Sports
134Publisher Nintendo
135NA_Sales 41.49
136EU_Sales 29.02
137JP_Sales 3.77
138Other_Sales 8.46
139Global_Sales 82.74
140Name: 0, dtype: object
141
142# Explore statistics in the year 2006 (highest selling year)
143df3 = df[(df['Year'] == 2006)]
144df3.describe()
145```
146
147![Descriptive Statistics of 2006
148Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/07_2006_stats-min.png)
149
150``` python
151# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
152sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
153```
154
155![Plot of 2006
156Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/08_plot-min.png)
157
158``` python
159# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
160# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
161df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
162```
163
164![Outliers of 2006
165Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/09_outliers-min.png)
166
167# Discussion
168
169The purpose of exploring datasets is to ask questions, answer questions,
170and discover intelligence that can be used to inform decision-making.
171So, what have we found in this dataset?
172
173Today we simply explored a publicly-available dataset to see what kind
174of information it contained. During that exploration, we found that
175video game sales peaked in 2006. That peak was largely due to Nintendo,
176who sold the top 5 games in 2006 and has a number of games in the top-10
177list for the years 1980-2020. Additionally, the top four platforms by
178global sales (Wii, NES, GB, DS) are owned by Nintendo.
179
180We didn\'t explore everything this dataset has to offer, but we can tell
181from a brief analysis that Nintendo seems to rule sales in the video
182gaming world. Further analysis could provide insight into which genres,
183regions, publishers, or world events are correlated with sales.
content/blog/2020-07-20-video-game-sales.org deleted −173
@@ -1,173 +0,0 @@
1#+title: Data Exploration: Video Game Sales
2#+date: 2020-07-20
3#+description: Exploring and visualizing data with Python.
4#+filetags: :data:
5
6* Background Information
7This dataset (obtained from [[https://www.kaggle.com/gregorut/videogamesales/data][Kaggle]]) contains a list of video games with sales
8greater than 100,000 copies. It was generated by a scrape of vgchartz.com.
9
10Fields include:
11
12- Rank: Ranking of overall sales
13- Name: The game name
14- Platform: Platform of the game release (i.e. PC,PS4, etc.)
15- Year: Year of the game's release
16- Genre: Genre of the game
17- Publisher: Publisher of the game
18- NA_{Sales}: Sales in North America (in millions)
19- EU_{Sales}: Sales in Europe (in millions)
20- JP_{Sales}: Sales in Japan (in millions)
21- Other_{Sales}: Sales in the rest of the world (in millions)
22- Global_{Sales}: Total worldwide sales.
23
24There are 16,598 records. 2 records were dropped due to incomplete information.
25
26* Import the Data
27#+begin_src python
28# Import the Python libraries we will be using
29import pandas as pd
30import numpy as np
31import seaborn as sns; sns.set()
32import matplotlib.pyplot as plt
33
34# Load the file using the path to the downloaded file
35file = r'video_game_sales.csv'
36df = pd.read_csv(file)
37df
38#+end_src
39
40#+caption: Dataframe Results
41[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/01_dataframe-min.png]]
42
43* Explore the Data
44#+begin_src python
45# With the description function, we can see the basic stats. For example, we can
46# also see that the 'Year' column has some incomplete values.
47df.describe()
48#+end_src
49
50#+caption: df.describe()
51[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/02_describe-min.png]]
52
53#+begin_src python
54# This function shows the rows and columns of NaN values. For example, df[179,3] = nan
55np.where(pd.isnull(df))
56
57(array([179, ..., 16553], dtype=int64),
58 array([3, ..., 5], dtype=int64))
59#+end_src
60
61* Visualize the Data
62#+begin_src python
63# This function plots the global sales by platform
64sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
65#+end_src
66
67#+caption: Plot of Global Sales by Platform
68[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/03_plot-min.png]]
69
70#+begin_src python
71# This function plots the global sales by genre
72sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
73#+end_src
74
75#+caption: Plot of Global Sales by Genre
76[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/04_plot-min.png]]
77
78#+begin_src python
79# This function plots the global sales by year
80sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
81#+end_src
82
83#+caption: Plot of Global Sales by Year
84[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/05_plot-min.png]]
85
86#+begin_src python
87# This function plots four different lines to show sales from different regions.
88# The global sales plot line is commented-out, but can be included for comparison
89df2 = df.groupby('Year').sum()
90years = range(1980,2019)
91
92a = df2['NA_Sales']
93b = df2['EU_Sales']
94c = df2['JP_Sales']
95d = df2['Other_Sales']
96# e = df2['Global_Sales']
97
98fig, ax = plt.subplots(figsize=(12,12))
99ax.set_ylabel('Region Sales (in Millions)')
100ax.set_xlabel('Year')
101
102ax.plot(years, a, label='NA_Sales')
103ax.plot(years, b, label='EU_Sales')
104ax.plot(years, c, label='JP_Sales')
105ax.plot(years, d, label='Other_Sales')
106# ax.plot(years, e, label='Global_Sales')
107
108ax.legend()
109plt.show()
110#+end_src
111
112#+caption: Plot of Regional Sales by Year
113[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/06_plot-min.png]]
114
115** Investigate Outliers
116#+begin_src python
117# Find the game with the highest sales in North America
118df.loc[df['NA_Sales'].idxmax()]
119
120Rank 1
121Name Wii Sports
122Platform Wii
123Year 2006
124Genre Sports
125Publisher Nintendo
126NA_Sales 41.49
127EU_Sales 29.02
128JP_Sales 3.77
129Other_Sales 8.46
130Global_Sales 82.74
131Name: 0, dtype: object
132
133# Explore statistics in the year 2006 (highest selling year)
134df3 = df[(df['Year'] == 2006)]
135df3.describe()
136#+end_src
137
138#+caption: Descriptive Statistics of 2006 Sales
139[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/07_2006_stats-min.png]]
140
141#+begin_src python
142# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
143sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
144#+end_src
145
146#+caption: Plot of 2006 Sales
147[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/08_plot-min.png]]
148
149#+begin_src python
150# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
151# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
152df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
153#+end_src
154
155#+caption: Outliers of 2006 Sales
156[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/09_outliers-min.png]]
157
158* Discussion
159The purpose of exploring datasets is to ask questions, answer questions, and
160discover intelligence that can be used to inform decision-making. So, what have
161we found in this dataset?
162
163Today we simply explored a publicly-available dataset to see what kind of
164information it contained. During that exploration, we found that video game
165sales peaked in 2006. That peak was largely due to Nintendo, who sold the top 5
166games in 2006 and has a number of games in the top-10 list for the years
1671980-2020. Additionally, the top four platforms by global sales (Wii, NES, GB,
168DS) are owned by Nintendo.
169
170We didn't explore everything this dataset has to offer, but we can tell from a
171brief analysis that Nintendo seems to rule sales in the video gaming world.
172Further analysis could provide insight into which genres, regions, publishers,
173or world events are correlated with sales.
content/blog/2020-07-26-business-analysis.org → content/blog/2020-07-26-business-analysis.md renamed +121 −106
@@ -1,28 +1,32 @@
1#+title: Algorithmically Analyzing Local Businesses 1+++
2#+date: 2020-07-26 2date = 2020-07-26
3#+description: Exploring and visualizing data with Python. 3title = "Algorithmically Analyzing Local Businesses "
4#+filetags: :data: 4description = ""
5draft = false
6+++
5 7
6* Background Information 8# Background Information
7This project aims to help investors learn more about a random city in order to 9
8determine optimal locations for business investments. The data used in this 10This project aims to help investors learn more about a random city in
9project was obtained using Foursquare's developer API. 11order to determine optimal locations for business investments. The data
12used in this project was obtained using Foursquare\'s developer API.
10 13
11Fields include: 14Fields include:
12 15
13- Venue Name 16- Venue Name
14- Venue Category 17- Venue Category
15- Venue Latitude 18- Venue Latitude
16- Venue Longitude 19- Venue Longitude
20
21There are 232 records found using the center of Lincoln as the area of
22interest with a radius of 10,000.
17 23
18There are 232 records found using the center of Lincoln as the area of interest 24# Import the Data
19with a radius of 10,000.
20 25
21* Import the Data 26The first step is the simplest: import the applicable libraries. We will
22The first step is the simplest: import the applicable libraries. We will be 27be using the libraries below for this project.
23using the libraries below for this project.
24 28
25#+begin_src python 29``` python
26# Import the Python libraries we will be using 30# Import the Python libraries we will be using
27import pandas as pd 31import pandas as pd
28import requests 32import requests
@@ -31,32 +35,32 @@ import math
31import json 35import json
32from pandas.io.json import json_normalize 36from pandas.io.json import json_normalize
33from sklearn.cluster import KMeans 37from sklearn.cluster import KMeans
34#+end_src 38```
35 39
36To begin our analysis, we need to import the data for this project. The data we 40To begin our analysis, we need to import the data for this project. The
37are using in this project comes directly from the Foursquare API. The first step 41data we are using in this project comes directly from the Foursquare
38is to get the latitude and longitude of the city being studied (Lincoln, NE) and 42API. The first step is to get the latitude and longitude of the city
39setting up the folium map. 43being studied (Lincoln, NE) and setting up the folium map.
40 44
41#+begin_src python 45``` python
42# Define the latitude and longitude, then map the results 46# Define the latitude and longitude, then map the results
43latitude = 40.806862 47latitude = 40.806862
44longitude = -96.681679 48longitude = -96.681679
45map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12) 49map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12)
46 50
47map_LNK 51map_LNK
48#+end_src 52```
49 53
50#+caption: Blank Map 54![Blank
51[[https://img.cleberg.net/blog/20200726-ibm-data-science/01_blank_map-min.png]] 55Map](https://img.cleberg.net/blog/20200726-ibm-data-science/01_blank_map-min.png)
52 56
53Now that we have defined our city and created the map, we need to go get the 57Now that we have defined our city and created the map, we need to go get
54business data. The Foursquare API will limit the results to 100 per API call, so 58the business data. The Foursquare API will limit the results to 100 per
55we use our first API call below to determine the total results that Foursquare 59API call, so we use our first API call below to determine the total
56has found. Since the total results are 232, we perform the API fetching process 60results that Foursquare has found. Since the total results are 232, we
57three times (100 + 100 + 32 = 232). 61perform the API fetching process three times (100 + 100 + 32 = 232).
58 62
59#+begin_src python 63``` python
60# Foursquare API credentials 64# Foursquare API credentials
61CLIENT_ID = 'your-client-id' 65CLIENT_ID = 'your-client-id'
62CLIENT_SECRET = 'your-client-secret' 66CLIENT_SECRET = 'your-client-secret'
@@ -114,17 +118,19 @@ url3 = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret
114 118
115# Fetch the final results (201 - 232) 119# Fetch the final results (201 - 232)
116results3 = requests.get(url3).json() 120results3 = requests.get(url3).json()
117#+end_src 121```
118 122
119* Clean the Data 123# Clean the Data
120Now that we have our data in three separate dataframes, we need to combine them
121into a single dataframe and make sure to reset the index so that we have a
122unique ID for each business. The =get~categorytype~= function below will pull
123the categories and name from each business's entry in the Foursquare data
124automatically. Once all the data has been labeled and combined, the results are
125stored in the =nearby_venues= dataframe.
126 124
127#+begin_src python 125Now that we have our data in three separate dataframes, we need to
126combine them into a single dataframe and make sure to reset the index so
127that we have a unique ID for each business. The
128`get~categorytype~` function below will pull the categories
129and name from each business\'s entry in the Foursquare data
130automatically. Once all the data has been labeled and combined, the
131results are stored in the `nearby_venues` dataframe.
132
133``` python
128# This function will extract the category of the venue from the API dictionary 134# This function will extract the category of the venue from the API dictionary
129def get_category_type(row): 135def get_category_type(row):
130 try: 136 try:
@@ -187,17 +193,18 @@ nearby_venues3.columns = [col.split(".")[-1] for col in nearby_venues3.columns]
187nearby_venues = nearby_venues.append(nearby_venues3) 193nearby_venues = nearby_venues.append(nearby_venues3)
188nearby_venues = nearby_venues.reset_index(drop=True) 194nearby_venues = nearby_venues.reset_index(drop=True)
189nearby_venues 195nearby_venues
190#+end_src 196```
197
198![Clean
199Data](https://img.cleberg.net/blog/20200726-ibm-data-science/02_clean_data-min.png)
191 200
192#+caption: Clean Data 201# Visualize the Data
193[[https://img.cleberg.net/blog/20200726-ibm-data-science/02_clean_data-min.png]]
194 202
195* Visualize the Data 203We now have a complete, clean data set. The next step is to visualize
196We now have a complete, clean data set. The next step is to visualize this data 204this data onto the map we created earlier. We will be using folium\'s
197onto the map we created earlier. We will be using folium's =CircleMarker()= 205`CircleMarker()` function to do this.
198function to do this.
199 206
200#+begin_src python 207``` python
201# add markers to map 208# add markers to map
202for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']): 209for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']):
203 label = '{} ({})'.format(name, categories) 210 label = '{} ({})'.format(name, categories)
@@ -213,22 +220,24 @@ for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng']
213 ).add_to(map_LNK) 220 ).add_to(map_LNK)
214 221
215map_LNK 222map_LNK
216#+end_src 223```
217 224
218![[https://img.cleberg.net/blog/20200726-ibm-data-science/03_data_map-min.png][Initial 225\![Initial data
219data map]] 226map](https://img.cleberg.net/blog/20200726-ibm-data-science/03_data_map-min.png)
220 227
221* Clustering: /k-means/ 228# Clustering: *k-means*
222To cluster the data, we will be using the /k-means/ algorithm. This algorithm is
223iterative and will automatically make sure that data points in each cluster are
224as close as possible to each other, while being as far as possible away from
225other clusters.
226 229
227However, we first have to figure out how many clusters to use (defined as the 230To cluster the data, we will be using the *k-means* algorithm. This
228variable /'k'/). To do so, we will use the next two functions to calculate the 231algorithm is iterative and will automatically make sure that data points
229sum of squares within clusters and then return the optimal number of clusters. 232in each cluster are as close as possible to each other, while being as
233far as possible away from other clusters.
230 234
231#+begin_src python 235However, we first have to figure out how many clusters to use (defined
236as the variable *\'k\'*). To do so, we will use the next two functions
237to calculate the sum of squares within clusters and then return the
238optimal number of clusters.
239
240``` python
232# This function will return the sum of squares found in the data 241# This function will return the sum of squares found in the data
233def calculate_wcss(data): 242def calculate_wcss(data):
234 wcss = [] 243 wcss = []
@@ -262,13 +271,13 @@ def optimal_number_of_clusters(wcss):
262 271
263# calculating the optimal number of clusters 272# calculating the optimal number of clusters
264n = optimal_number_of_clusters(sum_of_squares) 273n = optimal_number_of_clusters(sum_of_squares)
265#+end_src 274```
266 275
267Now that we have found that our optimal number of clusters is six, we need to 276Now that we have found that our optimal number of clusters is six, we
268perform k-means clustering. When this clustering occurs, each business is 277need to perform k-means clustering. When this clustering occurs, each
269assigned a cluster number from 0 to 5 in the dataframe. 278business is assigned a cluster number from 0 to 5 in the dataframe.
270 279
271#+begin_src python 280``` python
272# set number of clusters equal to the optimal number 281# set number of clusters equal to the optimal number
273kclusters = n 282kclusters = n
274 283
@@ -277,12 +286,13 @@ kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(cluster_df)
277 286
278# add clustering labels to dataframe 287# add clustering labels to dataframe
279nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_) 288nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_)
280#+end_src 289```
281 290
282Success! We now have a dataframe with clean business data, along with a cluster 291Success! We now have a dataframe with clean business data, along with a
283number for each business. Now let's map the data using six different colors. 292cluster number for each business. Now let\'s map the data using six
293different colors.
284 294
285#+begin_src python 295``` python
286# create map with clusters 296# create map with clusters
287map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12) 297map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12)
288colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717'] 298colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717']
@@ -301,35 +311,37 @@ for lat, lng, name, categories, cluster in zip(nearby_venues['lat'], nearby_venu
301 fill_opacity=0.7).add_to(map_clusters) 311 fill_opacity=0.7).add_to(map_clusters)
302 312
303map_clusters 313map_clusters
304#+end_src 314```
315
316![Clustered
317Map](https://img.cleberg.net/blog/20200726-ibm-data-science/04_clusters-min.png)
305 318
306#+caption: Clustered Map 319# Investigate Clusters
307[[https://img.cleberg.net/blog/20200726-ibm-data-science/04_clusters-min.png]]
308 320
309* Investigate Clusters 321Now that we have figured out our clusters, let\'s do a little more
310Now that we have figured out our clusters, let's do a little more analysis to 322analysis to provide more insight into the clusters. With the information
311provide more insight into the clusters. With the information below, we can see 323below, we can see which clusters are more popular for businesses and
312which clusters are more popular for businesses and which are less popular. The 324which are less popular. The results below show us that clusters 0
313results below show us that clusters 0 through 3 are popular, while clusters 4 325through 3 are popular, while clusters 4 and 5 are not very popular at
314and 5 are not very popular at all. 326all.
315 327
316#+begin_src python 328``` python
317# Show how many venues are in each cluster 329# Show how many venues are in each cluster
318color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black'] 330color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black']
319for x in range(0,6): 331for x in range(0,6):
320 print("Color of Cluster", x, ":", color_names[x]) 332 print("Color of Cluster", x, ":", color_names[x])
321 print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0]) 333 print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0])
322 print("---") 334 print("---")
323#+end_src 335```
324 336
325#+caption: Venues per Cluster 337![Venues per
326[[https://img.cleberg.net/blog/20200726-ibm-data-science/05_venues_per_cluster-min.png]] 338Cluster](https://img.cleberg.net/blog/20200726-ibm-data-science/05_venues_per_cluster-min.png)
327 339
328Our last piece of analysis is to summarize the categories of businesses within 340Our last piece of analysis is to summarize the categories of businesses
329each cluster. With these results, we can clearly see that restaurants, coffee 341within each cluster. With these results, we can clearly see that
330shops, and grocery stores are the most popular. 342restaurants, coffee shops, and grocery stores are the most popular.
331 343
332#+begin_src python 344``` python
333# Calculate how many venues there are in each category 345# Calculate how many venues there are in each category
334# Sort from largest to smallest 346# Sort from largest to smallest
335temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng']) 347temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng'])
@@ -349,26 +361,29 @@ with pd.option_context('display.max_rows', None, 'display.max_columns', None):
349 print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1]) 361 print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1])
350 print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1]) 362 print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1])
351 print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1]) 363 print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1])
352#+end_src 364```
365
366![Venues per Cluster, pt.
3671](https://img.cleberg.net/blog/20200726-ibm-data-science/06_categories_per_cluster_pt1-min.png)
353 368
354#+caption: Venues per Cluster, pt. 1 369![Venues per Cluster, pt.
355[[https://img.cleberg.net/blog/20200726-ibm-data-science/06_categories_per_cluster_pt1-min.png]] 3702](https://img.cleberg.net/blog/20200726-ibm-data-science/07_categories_per_cluster_pt2-min.png)
356 371
357#+caption: Venues per Cluster, pt. 2 372# Discussion
358[[https://img.cleberg.net/blog/20200726-ibm-data-science/07_categories_per_cluster_pt2-min.png]]
359 373
360* Discussion 374In this project, we gathered location data for Lincoln, Nebraska, USA
361In this project, we gathered location data for Lincoln, Nebraska, USA and 375and clustered the data using the k-means algorithm in order to identify
362clustered the data using the k-means algorithm in order to identify the unique 376the unique clusters of businesses in Lincoln. Through these actions, we
363clusters of businesses in Lincoln. Through these actions, we found that there 377found that there are six unique business clusters in Lincoln and that
364are six unique business clusters in Lincoln and that two of the clusters are 378two of the clusters are likely unsuitable for investors. The remaining
365likely unsuitable for investors. The remaining four clusters have a variety of 379four clusters have a variety of businesses, but are largely dominated by
366businesses, but are largely dominated by restaurants and grocery stores. 380restaurants and grocery stores.
367 381
368Using this project, investors can now make more informed decisions when deciding 382Using this project, investors can now make more informed decisions when
369the location and category of business in which to invest. 383deciding the location and category of business in which to invest.
370 384
371Further studies may involve other attributes for business locations, such as 385Further studies may involve other attributes for business locations,
372population density, average wealth across the city, or crime rates. In addition, 386such as population density, average wealth across the city, or crime
373further studies may include additional location data and businesses by utilizing 387rates. In addition, further studies may include additional location data
374multiple sources, such as Google Maps and OpenStreetMap. 388and businesses by utilizing multiple sources, such as Google Maps and
389OpenStreetMap.
content/blog/2020-08-22-redirect-github-pages.md added +137
@@ -0,0 +1,137 @@
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
13line: your 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
20your DNS configuration.
21
222.2: Remove a CNAME record for the second-level domain www if it is
23present.
24
25## Step 3
26
27Add these 5 entries to the very top of your DNS configuration:
28
29``` txt
30@ A 185.199.108.153
31@ A 185.199.109.153
32@ A 185.199.110.153
33@ A 185.199.111.153
34www CNAME your_github_username.github.io.
35```
36
37Replace `your_github_username` with your actual GitHub
38username.
39
40## Step 4
41
42Wait for your DNS changes to propagate. DNS changes aren\'t effective
43immediately. They can take up to a full day to propagate.
44
45# Long answer
46
47This issue has two sides. One is the DNS configuration itself. Another
48one is the way GitHub Pages will forward HTTP requests.
49
50We need to know a few things to understand what GitHub is trying to say
51in their documentation.
52
53## DNS Entry Types
54
55There are two types of DNS records which interest us: CNAME and A.
56
57`A` is also known as `Apex` or sometimes as
58`root entry`. It forwards requests to a specified fixed IP
59address. `CNAME` entry forwards requests to a specified URL
60(actual valid plain text URL, not an IP address).
61
62## DNS Load balancing
63
64GitHub has one central URL address which accepts all DNS requests for
65GitHub Pages: `http://username.github.io`. That URL is
66resolved to different IP addresses based on your geographical location.
67Website hosted on GitHub Pages is a simple collection of
68`HTML`, `CSS` and `JS` files. GitHub
69distributes these files to different servers across the globe. So that
70when your browser sends a request from Europe, it receives data from a
71server in Europe. The same is valid for the requests from Asia and the
72USA.
73
74## What GitHub is trying to say
75
76Since `A` records in DNS must contain IP addresses, and they
77must be either `185.199.108.153` or
78`185.199.109.153` or `185.199.110.153` or
79`185.199.111.153`, there is no way to forward requests to a
80server located somewhere in Europe or Asia. Your website hosted at
81GitHub Pages will be downloaded from a central GitHub Pages server.
82There is a minor risk that if GitHub Pages DNS servers
83(`x.x.x.153`) are down for some reason, all custom domains
84which use fixed GitHub Pages IP addresses will not be accessible (their
85DNS requests will not be resolvable).
86
87That is why GitHub strongly suggests to either use a second-level domain
88for your GitHub Pages (e.g. `blog.example.com`) or use a DNS
89service provider that supports a record type `ALIAS` that
90acts as `A` record but forwards request to a URL address
91(e.g. `username.github.io`) instead of a fixed IP address.
92
93## How GitHub Pages treats HTTP requests
94
95After a DNS request for `your_github_username.github.io` is
96resolved into an IP address, e.g. `185.199.108.153` your
97browser sends an HTTP request to that server with an HTTP header
98`Host`. Below are `curl` examples that load the
99same website (these examples might not work if you are behind a proxy
100server):
101
102```sh
103curl --header "Host: your_github_username.github.io" http://185.199.108.153/
104curl --header "Host: www.example.com" http://185.199.108.153/
105curl --header "Host: example.com" http://185.199.108.153/
106```
107
108This way GitHub Pages servers know which user website to serve.
109
110> GitHub Pages server will automatically redirect HTTP requests to the
111> top-level domain if your `CNAME` file contains
112> `example.com` but `www.example.com` is
113> requested.
114>
115> The same is valid if your `CNAME` file contains
116> `www.example.com` but the header `Host` in the
117> `HTTP` request contains `example.com`.
118
119## Why can\'t I add a `CNAME` record entry that accepts a top-level request (`@`) to my DNS configuration?
120
121Quote from the GitHub Pages documentation:
122
123> Warning: Do not create a CNAME record for your custom apex domain!
124> Doing so may cause issues with other services, such as email, on that
125> domain.
126
127## References:
128
1291. [Setting up a custom domain with GitHub
130 Pages](https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site)
1312. [My custom domain isn\'t
132 working](https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages)
1333. [Cannot access my GitHub Pages website by IP
134 Address](https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address)
1354. [How do I set up GitHub Pages to redirect DNS requests from a
136 subdomain (e.g. www) to the top-level domain (TLD, Apex
137 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 deleted −110
@@ -1,110 +0,0 @@
1#+title: Redirect GitHub Pages from Subdomain to the Top-Level Domain
2#+date: 2020-08-22
3#+description: Learn how to redirect Github pages to the TLD.
4#+filetags: :sysadmin:
5
6* Short answer
7** Step 1
8Add a new file CNAME to your GitHub Pages repository containing only one line:
9your top-level domain name. E.g.: =example.com=
10
11** Step 2
12[Optional] but highly recommended
13
142.1: Remove all other top-level records (prefixed with @) of type A from your
15DNS configuration.
16
172.2: Remove a CNAME record for the second-level domain www if it is present.
18
19** Step 3
20Add these 5 entries to the very top of your DNS configuration:
21
22#+begin_src txt
23@ A 185.199.108.153
24@ A 185.199.109.153
25@ A 185.199.110.153
26@ A 185.199.111.153
27www CNAME your_github_username.github.io.
28#+end_src
29
30Replace =your_github_username= with your actual GitHub username.
31
32** Step 4
33Wait for your DNS changes to propagate. DNS changes aren't effective
34immediately. They can take up to a full day to propagate.
35
36* Long answer
37This issue has two sides. One is the DNS configuration itself. Another one is
38the way GitHub Pages will forward HTTP requests.
39
40We need to know a few things to understand what GitHub is trying to say in their
41documentation.
42
43** DNS Entry Types
44There are two types of DNS records which interest us: CNAME and A.
45
46=A= is also known as =Apex= or sometimes as =root entry=. It forwards requests
47to a specified fixed IP address. =CNAME= entry forwards requests to a specified
48URL (actual valid plain text URL, not an IP address).
49
50** DNS Load balancing
51GitHub has one central URL address which accepts all DNS requests for GitHub
52Pages: =http://username.github.io=. That URL is resolved to different IP
53addresses based on your geographical location. Website hosted on GitHub Pages is
54a simple collection of =HTML=, =CSS= and =JS= files. GitHub distributes these
55files to different servers across the globe. So that when your browser sends a
56request from Europe, it receives data from a server in Europe. The same is valid
57for the requests from Asia and the USA.
58
59** What GitHub is trying to say
60Since =A= records in DNS must contain IP addresses, and they must be either
61=185.199.108.153= or =185.199.109.153= or =185.199.110.153= or
62=185.199.111.153=, there is no way to forward requests to a server located
63somewhere in Europe or Asia. Your website hosted at GitHub Pages will be
64downloaded from a central GitHub Pages server. There is a minor risk that if
65GitHub Pages DNS servers (=x.x.x.153=) are down for some reason, all custom
66domains which use fixed GitHub Pages IP addresses will not be accessible (their
67DNS requests will not be resolvable).
68
69That is why GitHub strongly suggests to either use a second-level domain for
70your GitHub Pages (e.g. =blog.example.com=) or use a DNS service provider that
71supports a record type =ALIAS= that acts as =A= record but forwards request to a
72URL address (e.g. =username.github.io=) instead of a fixed IP address.
73
74** How GitHub Pages treats HTTP requests
75After a DNS request for =your_github_username.github.io= is resolved into an IP
76address, e.g. =185.199.108.153= your browser sends an HTTP request to that
77server with an HTTP header =Host=. Below are =curl= examples that load the same
78website (these examples might not work if you are behind a proxy server):
79
80#+begin_src sh
81curl --header "Host: your_github_username.github.io" http://185.199.108.153/
82curl --header "Host: www.example.com" http://185.199.108.153/
83curl --header "Host: example.com" http://185.199.108.153/
84#+end_src
85
86This way GitHub Pages servers know which user website to serve.
87
88#+begin_quote
89GitHub Pages server will automatically redirect HTTP requests to the top-level
90domain if your =CNAME= file contains =example.com= but =www.example.com= is
91requested.
92
93The same is valid if your =CNAME= file contains =www.example.com= but the header
94=Host= in the =HTTP= request contains =example.com=.
95#+end_quote
96
97** Why can't I add a =CNAME= record entry that accepts a top-level request (=@=) to my DNS configuration?
98Quote from the GitHub Pages documentation:
99
100#+begin_quote
101Warning: Do not create a CNAME record for your custom apex domain! Doing so may
102cause issues with other services, such as email, on that domain.
103#+end_quote
104
105** References:
1061. [[https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site][Setting up a custom domain with GitHub Pages]]
1072. [[https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages][My custom domain isn't working]]
1083. [[https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address][Cannot access my GitHub Pages website by IP Address]]
1094. [[https://stackoverflow.com/questions/23375422/how-do-i-set-up-github-pages-to-redirect-dns-requests-from-a-subdomain-e-g-www][How do I set up GitHub Pages to redirect DNS requests from a subdomain (e.g.
110 www) to the top-level domain (TLD, Apex record)?]]
content/blog/2020-08-29-php-auth-flow.org → content/blog/2020-08-29-php-auth-flow.md renamed +81 −66
@@ -1,20 +1,24 @@
1#+title: PHP Authentication Flow 1+++
2#+date: 2020-08-29 2date = 2020-08-29
3#+description: Learn how to establish and maintain a basic user authentication flow in PHP. 3title = "PHP Authentication Flow"
4#+filetags: :dev: 4description = ""
5 5draft = false
6* Introduction 6+++
7When creating websites that will allow users to create accounts, the developer 7
8always needs to consider the proper authentication flow for their app. For 8# Introduction
9example, some developers will utilize an API for authentication, some will use 9
10OAuth, and some may just use their own simple database. 10When creating websites that will allow users to create accounts, the
11 11developer always needs to consider the proper authentication flow for
12For those using pre-built libraries, authentication may simply be a problem of 12their app. For example, some developers will utilize an API for
13copying and pasting the code from their library's documentation. For example, 13authentication, some will use OAuth, and some may just use their own
14here's the code I use to authenticate users with the Tumblr OAuth API for my 14simple database.
15Tumblr client, Vox Populi: 15
16 16For those using pre-built libraries, authentication may simply be a
17#+begin_src php 17problem of copying and pasting the code from their library\'s
18documentation. For example, here\'s the code I use to authenticate users
19with the Tumblr OAuth API for my Tumblr client, Vox Populi:
20
21``` php
18// Start the session 22// Start the session
19session_start(); 23session_start();
20 24
@@ -35,26 +39,28 @@ $client = new Tumblr\API\Client(
35 $token, 39 $token,
36 $token_secret 40 $token_secret
37); 41);
38#+end_src 42```
39 43
40However, developers creating authentication flows from scratch will need to 44However, developers creating authentication flows from scratch will need
41think carefully about when to make sure a web page will check the user's 45to think carefully about when to make sure a web page will check the
42authenticity. 46user\'s authenticity.
43 47
44In this article, we're going to look at a simple authentication flow using a 48In this article, we\'re going to look at a simple authentication flow
45MySQL database and PHP. 49using a MySQL database and PHP.
46 50
47* Creating User Accounts 51# Creating User Accounts
48The beginning to any type of user authentication is to create a user account.
49This process can take many formats, but the simplest is to accept user input
50from a form (e.g., username and password) and send it over to your database. For
51example, here's a snippet that shows how to get username and password parameters
52that would come when a user submits a form to your PHP script.
53 52
54*Note*: Ensure that your password column is large enough to hold the hashed 53The beginning to any type of user authentication is to create a user
55value (at least 60 characters or longer). 54account. This process can take many formats, but the simplest is to
55accept user input from a form (e.g., username and password) and send it
56over to your database. For example, here\'s a snippet that shows how to
57get username and password parameters that would come when a user submits
58a form to your PHP script.
56 59
57#+begin_src php 60**Note**: Ensure that your password column is large enough to hold the
61hashed value (at least 60 characters or longer).
62
63``` php
58// Get the values from the URL 64// Get the values from the URL
59$username = $_POST['username']; 65$username = $_POST['username'];
60$raw_password = $_POST['password']; 66$raw_password = $_POST['password'];
@@ -87,14 +93,15 @@ if ($conn->query($sql) === TRUE) {
87} 93}
88 94
89$conn->close(); 95$conn->close();
90#+end_src 96```
97
98## Validate Returning Users
91 99
92** Validate Returning Users 100To be able to verify that a returning user has a valid username and
93To be able to verify that a returning user has a valid username and password in 101password in your database is as simple as having users fill out a form
94your database is as simple as having users fill out a form and comparing their 102and comparing their inputs to your database.
95inputs to your database.
96 103
97#+begin_src php 104``` php
98// Query the database for username and password 105// Query the database for username and password
99// ... 106// ...
100 107
@@ -105,39 +112,42 @@ if(password_verify($password_input, $hashed_password)) {
105 112
106// Else, Redirect them back to the login page. 113// Else, Redirect them back to the login page.
107... 114...
108#+end_src 115```
116
117# Storing Authentication State
109 118
110* Storing Authentication State 119Once you\'ve created the user\'s account, now you\'re ready to
111Once you've created the user's account, now you're ready to initialize the 120initialize the user\'s session. **You will need to do this on every page
112user's session. *You will need to do this on every page you load while the user 121you load while the user is logged in.\*** To do so, simply enter the
113is logged in.** To do so, simply enter the following code snippet: 122following code snippet:
114 123
115#+begin_src php 124``` php
116session_start(); 125session_start();
117#+end_src 126```
118 127
119Once you've initialized the session, the next step is to store the session in a 128Once you\'ve initialized the session, the next step is to store the
120cookie so that you can access it later. 129session in a cookie so that you can access it later.
121 130
122#+begin_src php 131``` php
123setcookie(session_name()); 132setcookie(session_name());
124#+end_src 133```
125 134
126Now that the session name has been stored, you'll be able to check if there's an 135Now that the session name has been stored, you\'ll be able to check if
127active session whenever you load a page. 136there\'s an active session whenever you load a page.
128 137
129#+begin_src php 138``` php
130if(isset(session_name())) { 139if(isset(session_name())) {
131 // The session is active 140 // The session is active
132} 141}
133#+end_src 142```
134 143
135** Removing User Authentication 144## Removing User Authentication
136The next logical step is to give your users the option to log out once they are
137done using your application. This can be tricky in PHP since a few of the
138standard ways do not always work.
139 145
140#+begin_src php 146The next logical step is to give your users the option to log out once
147they are done using your application. This can be tricky in PHP since a
148few of the standard ways do not always work.
149
150``` php
141// Initialize the session. 151// Initialize the session.
142// If you are using session_name("something"), don't forget it now! 152// If you are using session_name("something"), don't forget it now!
143session_start(); 153session_start();
@@ -169,13 +179,18 @@ session_write_close();
169// Go back to sign-in page 179// Go back to sign-in page
170header('Location: https://example.com/logged-out/'); 180header('Location: https://example.com/logged-out/');
171die(); 181die();
172#+end_src 182```
183
184# Wrapping Up
173 185
174* Wrapping Up 186Now you should be ready to begin your authentication programming with
175Now you should be ready to begin your authentication programming with PHP. You 187PHP. You can create user accounts, create sessions for users across
176can create user accounts, create sessions for users across different pages of 188different pages of your site, and then destroy the user data when
177your site, and then destroy the user data when they're ready to leave. 189they\'re ready to leave.
178 190
179For more information on this subject, I recommend reading the [[https://www.php.net/][PHP Documentation]]. 191For more information on this subject, I recommend reading the [PHP
180Specifically, you may want to look at [[https://www.php.net/manual/en/features.http-auth.php][HTTP Authentication with PHP]], [[https://www.php.net/manual/en/book.session.php][session 192Documentation](https://www.php.net/). Specifically, you may want to look
181handling]], and [[https://www.php.net/manual/en/function.hash.php][hash]]. 193at [HTTP Authentication with
194PHP](https://www.php.net/manual/en/features.http-auth.php), [session
195handling](https://www.php.net/manual/en/book.session.php), and
196[hash](https://www.php.net/manual/en/function.hash.php).
content/blog/2020-09-01-visual-recognition.md added +205
@@ -0,0 +1,205 @@
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
11service is a suite of enterprise-ready AI services, applications, and
12tooling provided by IBM. Watson contains quite a few useful tools for
13data scientists and students, including the subject of this post today:
14visual recognition.
15
16If you\'d like to view the official documentation for the Visual
17Recognition API, visit the [API
18Docs](https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python).
19
20# Prerequisites
21
22To be able to use Watson Visual Recognition, you\'ll need the following:
23
241. Create a free account on [IBM Watson
25 Studio](https://www.ibm.com/cloud/watson-studio).
262. Add the [Watson Visual
27 Recognition](https://www.ibm.com/cloud/watson-visual-recognition)
28 service to your IBM Watson account.
293. Get your API key and URL. To do this, first go to the [profile
30 dashboard](https://dataplatform.cloud.ibm.com/home2?context=cpdaas)
31 for your IBM account and click on the Watson Visual Recognition
32 service you created. This will be listed in the section titled
33 **Your services**. Then click the **Credentials\*** tab and open the
34 **Auto-generated credentials\*** dropdown. Copy your API key and URL
35 so that you can use them in the Python script later.
364. **\[Optional\]\*** While not required, you can also create the
37 Jupyter Notebook for this project right inside [Watson
38 Studio](https://www.ibm.com/cloud/watson-studio). Watson Studio will
39 save your notebooks inside an organized project and allow you to use
40 their other integrated products, such as storage containers, AI
41 models, documentation, external sharing, etc.
42
43# Calling the IBM Watson Visual Recognition API
44
45Okay, now let\'s get started.
46
47To begin, we need to install the proper Python package for IBM Watson.
48
49```sh
50pip install --upgrade --user "ibm-watson>=4.5.0"
51```
52
53Next, we need to specify the API key, version, and URL given to us when
54we created the Watson Visual Recognition service.
55
56``` python
57apikey = "<your-apikey>"
58version = "2018-03-19"
59url = "<your-url>"
60```
61
62Now, let\'s import the necessary libraries and authenticate our service.
63
64``` python
65import json
66from ibm_watson import VisualRecognitionV3
67from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
68
69authenticator = IAMAuthenticator(apikey)
70visual_recognition = VisualRecognitionV3(
71 version=version,
72 authenticator=authenticator
73)
74
75visual_recognition.set_service_url(url)
76```
77
78**\[Optional\]** If you\'d like to tell the API not to use any data to
79improve their products, set the following header.
80
81``` python
82visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
83```
84
85Now we have our API all set and ready to go. For this example, I\'m
86going to include a `dict` of photos to load as we test out
87the API.
88
89``` python
90data = [
91 {
92 "title": "Grizzly Bear",
93 "url": "https://example.com/photos/image1.jpg"
94 },
95 {
96 "title": "Nature Lake",
97 "url": "https://example.com/photos/image2.jpg"
98 },
99 {
100 "title": "Welcome Sign",
101 "url": "https://example.com/photos/image3.jpg"
102 },
103 {
104 "title": "Honey Badger",
105 "url": "https://example.com/photos/image4.jpg"
106 },
107 {
108 "title": "Grand Canyon Lizard",
109 "url": "https://example.com/photos/image5.jpg"
110 },
111 {
112 "title": "Castle",
113 "url": "https://example.com/photos/image6.jpg"
114 }
115]
116```
117
118Now that we\'ve set up our libraries and have the photos ready, let\'s
119create a loop to call the API for each image. The code below shows a
120loop that calls the URL of each image and sends it to the API,
121requesting results with at least 60% confidence. The results are output
122to the console with dotted lines separating each section.
123
124In the case of an API error, the codes and explanations are output to
125the console.
126
127``` python
128from ibm_watson import ApiException
129
130for x in range(len(data)):
131try:
132 url = data[x]["url"]
133 images_filename = data[x]["title"]
134 classes = visual_recognition.classify(
135 url=url,
136 images_filename=images_filename,
137 threshold='0.6',
138 owners=["IBM"]).get_result()
139 print("-----------------------------------------------")
140 print("Image Title: ", data[x]["title"], "\n")
141 print("Image URL: ", data[x]["url"], "\n")
142 classification_results = classes["images"][0]["classifiers"][0]["classes"]
143 for result in classification_results:
144 print(result["class"], "(", result["score"], ")")
145 print("-----------------------------------------------")
146except ApiException as ex:
147 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
148```
149
150# The Results
151
152Here we can see the full result set of our function above. If you view
153each of the URLs that we sent to the API, you\'ll be able to see that it
154was remarkably accurate. To be fair, these are clear high-resolution,
155clear photos shot with a professional camera. In reality, you will most
156likely be processing images that are lower quality and may have a lot of
157noise in the photo.
158
159However, we can clearly see the benefit of being able to call this API
160instead of attempting to write our own image recognition function. Each
161of the classifications returned was a fair description of the image.
162
163If you wanted to restrict the results to those that are at least 90%
164confident or greater, you would simply adjust the `threshold`
165in the `visual_recognition.classify()` function.
166
167When your program runs, it should show the output below for each photo
168you provide.
169
170``` txt
171----------------------------------------------------------------
172Image Title: Grizzly Bear
173Image URL: https://example.com/photos/image1.jpg
174
175brown bear ( 0.944 )
176bear ( 1 )
177carnivore ( 1 )
178mammal ( 1 )
179animal ( 1 )
180Alaskan brown bear ( 0.759 )
181greenishness color ( 0.975 )
182----------------------------------------------------------------
183```
184
185# Discussion
186
187Now, this was a very minimal implementation of the API. We simply
188supplied some images and looked to see how accurate the results were.
189However, you could implement this type of API into many machine learning
190(ML) models.
191
192For example, you could be working for a company that scans their
193warehouses or inventory using drones. Would you want to pay employees to
194sit there and watch drone footage all day in order to identify or count
195things in the video? Probably not. Instead, you could use a
196classification system similar to this one in order to train your machine
197learning model to correctly identify items that the drones show through
198video. More specifically, you could have your machine learning model
199watch a drone fly over a field of sheep in order to count how many sheep
200are living in that field.
201
202There are many ways to implement machine learning functionality, but
203hopefully this post helped inspire some deeper thought about the tools
204that can help propel us further into the future of machine learning and
205AI.
content/blog/2020-09-01-visual-recognition.org deleted −186
@@ -1,186 +0,0 @@
1#+title: IBM Watson Visual Recognition
2#+date: 2020-09-01
3#+description: Exploring and visualizing data with Python.
4#+filetags: :dev:
5
6* What is IBM Watson?
7If you've never heard of [[https://www.ibm.com/watson][Watson]], this service is a suite of enterprise-ready AI
8services, applications, and tooling provided by IBM. Watson contains quite a few
9useful tools for data scientists and students, including the subject of this
10post today: visual recognition.
11
12If you'd like to view the official documentation for the Visual Recognition API,
13visit the [[https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python][API Docs]].
14
15* Prerequisites
16To be able to use Watson Visual Recognition, you'll need the following:
17
181. Create a free account on [[https://www.ibm.com/cloud/watson-studio][IBM Watson Studio]].
192. Add the [[https://www.ibm.com/cloud/watson-visual-recognition][Watson Visual Recognition]] service to your IBM Watson account.
203. Get your API key and URL. To do this, first go to the [[https://dataplatform.cloud.ibm.com/home2?context=cpdaas][profile dashboard]] for
21 your IBM account and click on the Watson Visual Recognition service you
22 created. This will be listed in the section titled *Your services*. Then
23 click the *Credentials** tab and open the *Auto-generated credentials**
24 dropdown. Copy your API key and URL so that you can use them in the Python
25 script later.
264. *[Optional]** While not required, you can also create the Jupyter Notebook
27 for this project right inside [[https://www.ibm.com/cloud/watson-studio][Watson Studio]]. Watson Studio will save your
28 notebooks inside an organized project and allow you to use their other
29 integrated products, such as storage containers, AI models, documentation,
30 external sharing, etc.
31
32* Calling the IBM Watson Visual Recognition API
33Okay, now let's get started.
34
35To begin, we need to install the proper Python package for IBM Watson.
36
37#+begin_src sh
38pip install --upgrade --user "ibm-watson>=4.5.0"
39#+end_src
40
41Next, we need to specify the API key, version, and URL given to us when we
42created the Watson Visual Recognition service.
43
44#+begin_src python
45apikey = "<your-apikey>"
46version = "2018-03-19"
47url = "<your-url>"
48#+end_src
49
50Now, let's import the necessary libraries and authenticate our service.
51
52#+begin_src python
53import json
54from ibm_watson import VisualRecognitionV3
55from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
56
57authenticator = IAMAuthenticator(apikey)
58visual_recognition = VisualRecognitionV3(
59 version=version,
60 authenticator=authenticator
61)
62
63visual_recognition.set_service_url(url)
64#+end_src
65
66*[Optional]* If you'd like to tell the API not to use any data to improve their
67products, set the following header.
68
69#+begin_src python
70visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
71#+end_src
72
73Now we have our API all set and ready to go. For this example, I'm going to
74include a =dict= of photos to load as we test out the API.
75
76#+begin_src python
77data = [
78 {
79 "title": "Grizzly Bear",
80 "url": "https://example.com/photos/image1.jpg"
81 },
82 {
83 "title": "Nature Lake",
84 "url": "https://example.com/photos/image2.jpg"
85 },
86 {
87 "title": "Welcome Sign",
88 "url": "https://example.com/photos/image3.jpg"
89 },
90 {
91 "title": "Honey Badger",
92 "url": "https://example.com/photos/image4.jpg"
93 },
94 {
95 "title": "Grand Canyon Lizard",
96 "url": "https://example.com/photos/image5.jpg"
97 },
98 {
99 "title": "Castle",
100 "url": "https://example.com/photos/image6.jpg"
101 }
102]
103#+end_src
104
105Now that we've set up our libraries and have the photos ready, let's create a
106loop to call the API for each image. The code below shows a loop that calls the
107URL of each image and sends it to the API, requesting results with at least 60%
108confidence. The results are output to the console with dotted lines separating
109each section.
110
111In the case of an API error, the codes and explanations are output to the
112console.
113
114#+begin_src python
115from ibm_watson import ApiException
116
117for x in range(len(data)):
118try:
119 url = data[x]["url"]
120 images_filename = data[x]["title"]
121 classes = visual_recognition.classify(
122 url=url,
123 images_filename=images_filename,
124 threshold='0.6',
125 owners=["IBM"]).get_result()
126 print("-----------------------------------------------")
127 print("Image Title: ", data[x]["title"], "\n")
128 print("Image URL: ", data[x]["url"], "\n")
129 classification_results = classes["images"][0]["classifiers"][0]["classes"]
130 for result in classification_results:
131 print(result["class"], "(", result["score"], ")")
132 print("-----------------------------------------------")
133except ApiException as ex:
134 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
135#+end_src
136
137* The Results
138Here we can see the full result set of our function above. If you view each of
139the URLs that we sent to the API, you'll be able to see that it was remarkably
140accurate. To be fair, these are clear high-resolution, clear photos shot with a
141professional camera. In reality, you will most likely be processing images that
142are lower quality and may have a lot of noise in the photo.
143
144However, we can clearly see the benefit of being able to call this API instead
145of attempting to write our own image recognition function. Each of the
146classifications returned was a fair description of the image.
147
148If you wanted to restrict the results to those that are at least 90% confident
149or greater, you would simply adjust the =threshold= in the
150=visual_recognition.classify()= function.
151
152When your program runs, it should show the output below for each photo you
153provide.
154
155#+begin_src txt
156----------------------------------------------------------------
157Image Title: Grizzly Bear
158Image URL: https://example.com/photos/image1.jpg
159
160brown bear ( 0.944 )
161bear ( 1 )
162carnivore ( 1 )
163mammal ( 1 )
164animal ( 1 )
165Alaskan brown bear ( 0.759 )
166greenishness color ( 0.975 )
167----------------------------------------------------------------
168#+end_src
169
170* Discussion
171Now, this was a very minimal implementation of the API. We simply supplied some
172images and looked to see how accurate the results were. However, you could
173implement this type of API into many machine learning (ML) models.
174
175For example, you could be working for a company that scans their warehouses or
176inventory using drones. Would you want to pay employees to sit there and watch
177drone footage all day in order to identify or count things in the video?
178Probably not. Instead, you could use a classification system similar to this one
179in order to train your machine learning model to correctly identify items that
180the drones show through video. More specifically, you could have your machine
181learning model watch a drone fly over a field of sheep in order to count how
182many sheep are living in that field.
183
184There are many ways to implement machine learning functionality, but hopefully
185this post helped inspire some deeper thought about the tools that can help
186propel us further into the future of machine learning and AI.
content/blog/2020-09-22-internal-audit.md added +264
@@ -0,0 +1,264 @@
1+++
2date = 2020-09-22
3title = "Who is Internal Audit?"
4description = ""
5draft = false
6+++
7
8![Internal Audit
9Overview](https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-overview.jpg)
10
11# Definitions
12
13One of the many reasons that Internal Audit needs such thorough
14explaining to non-auditors is that Internal Audit can serve many
15purposes, depending on the organization\'s size and needs. However, the
16Institute of Internal Auditors (IIA) defines Internal Auditing as:
17
18> Internal auditing is an independent, objective assurance and
19> consulting activity designed to add value and improve an
20> organization\'s operations. It helps an organization accomplish its
21> objectives by bringing a systematic, disciplined approach to evaluate
22> and improve the effectiveness of risk management, control, and
23> governance processes.
24
25However, this definition uses quite a few terms that aren\'t clear
26unless the reader already has a solid understanding of the auditing
27profession. To further explain, the following is a list of definitions
28that can help supplement understanding of internal auditing.
29
30## Independent
31
32Independence is the freedom from conditions that threaten the ability of
33the internal audit activity to carry out internal audit responsibilities
34in an unbiased manner. To achieve the degree of independence necessary
35to effectively carry out the responsibilities of the internal audit
36activity, the chief audit executive has direct and unrestricted access
37to senior management and the board. This can be achieved through a
38dual-reporting relationship. Threats to independence must be managed at
39the individual auditor, engagement, functional, and organizational
40levels.
41
42## Objective
43
44Objectivity is an unbiased mental attitude that allows internal auditors
45to perform engagements in such a manner that they believe in their work
46product and that no quality compromises are made. Objectivity requires
47that internal auditors do not subordinate their judgment on audit
48matters to others. Threats to objectivity must be managed at the
49individual auditor, engagement, functional, and organizational levels.
50
51## Assurance
52
53Assurance services involve the internal auditor\'s objective assessment
54of evidence to provide opinions or conclusions regarding an entity,
55operation, function, process, system, or other subject matters. The
56internal auditor determines the nature and scope of an assurance
57engagement. Generally, three parties are participants in assurance
58services: (1) the person or group directly involved with the entity,
59operation, function, process, system, or other subject - (the process
60owner), (2) the person or group making the assessment - (the internal
61auditor), and (3) the person or group using the assessment - (the user).
62
63## Consulting
64
65Consulting services are advisory in nature and are generally performed
66at the specific request of an engagement client. The nature and scope of
67the consulting engagement are subject to agreement with the engagement
68client. Consulting services generally involve two parties: (1) the
69person or group offering the advice (the internal auditor), and (2) the
70person or group seeking and receiving the advice (the engagement
71client). When performing consulting services, the internal auditor
72should maintain objectivity and not assume management responsibility.
73
74## Governance, Risk Management, & Compliance (GRC)
75
76The integrated collection of capabilities that enable an organization to
77reliably achieve objectives, address uncertainty and act with integrity.
78
79# Audit Charter & Standards
80
81First, it\'s important to note that not every organization needs
82internal auditors. In fact, it\'s unwise for an organization to hire
83internal auditors unless they have regulatory requirements for auditing
84and have the capital to support the department. Internal audit is a cost
85center that can only affect revenue indirectly.
86
87Once an organization determines the need for internal assurance
88services, they will hire a Chief Audit Executive and create the audit
89charter. This charter is a document, approved by the company\'s
90governing body, that will define internal audit\'s purpose, authority,
91responsibility, and position within the organization. Fortunately, the
92IIA has model charters available to IIA members for those developing or
93improving their charter.
94
95Beyond the charter and organizational documents, internal auditors
96follow a few different standards in order to perform their job. First is
97the International Professional Practices Framework (IPPF) by the IIA,
98which is the model of standards for internal auditing. In addition,
99ISACA\'s Information Technology Assurance Framework (ITAF) helps guide
100auditors in reference to information technology (IT) compliance and
101assurance. Finally, additional standards such as FASB, GAAP, and
102industry-specific standards are used when performing internal audit
103work.
104
105# Three Lines of Defense
106
107[The IIA](https://theiia.org) released the original Three Lines of
108Defense model in 2013, but have released an updated version in 2020.
109Here is what the Three Lines of Defense model has historically looked
110like:
111
112![2013 Three Lines of Defense
113Model](https://img.cleberg.net/blog/20200922-what-is-internal-audit/three_lines_model.png)
114
115I won\'t go into depth about the changes made to the model in this
116article. Instead, let\'s take a look at the most current model.
117
118![2020 Three Lines of Defense
119Model](https://img.cleberg.net/blog/20200922-what-is-internal-audit/updated_three_lines_model.png)
120
121The updated model forgets the strict idea of areas performing their own
122functions or line of defense. Instead of talking about management, risk,
123and internal audit as 1-2-3, the new model creates a more fluid and
124cooperative model.
125
126Looking at this model from an auditing perspective shows us that
127auditors will need to align, communicate, and collaborate with
128management, including business area managers and chief officers, as well
129as reporting to the governing body. The governing body will instruct
130internal audit *functionally* on their goals and track their progress
131periodically.
132
133However, the internal audit department will report *administratively* to
134a chief officer in the company for the purposes of collaboration,
135direction, and assistance with the business. Note that in most
136situations, the governing body is the audit committee on the company\'s
137board of directors.
138
139The result of this structure is that internal audit is an independent
140and objective function that can provide assurance over the topics they
141audit.
142
143# Audit Process
144
145A normal audit will generally follow the same process, regardless of the
146topic. However, certain special projects or abnormal business areas may
147call for changes to the audit process. The audit process is not set in
148stone, it\'s simply a set of best practices so that audits can be
149performed consistently.
150
151![The Internal Audit
152Process](https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-process.jpg)
153
154While different organizations may tweak the process, it will generally
155follow this flow:
156
157## 1. Risk Assessment
158
159The risk assessment part of the process has historically been performed
160annually, but many organizations have moved to performing this process
161much more frequently. In fact, some organizations are moving to an agile
162approach that can take new risks into the risk assessment and
163re-prioritize risk areas on-the-go. To perform a risk assessment,
164leaders in internal audit will research industry risks, consult with
165business leaders around the company, and perform analyses on company
166data.
167
168Once a risk assessment has been documented, the audit department has a
169prioritized list of risks that can be audited. This is usually in the
170form of auditable entities, such as business areas or departments.
171
172## 2. Planning
173
174During the planning phase of an audit, auditors will meet with the
175business area to discuss the various processes, controls, and risks
176applicable to the business. This helps the auditors determine the scope
177limits for the audit, as well as timing and subject-matter experts.
178Certain documents will be created in this phase that will be used to
179keep the audit on-track an in-scope as it goes forward.
180
181## 3. Testing
182
183The testing phase, also known as fieldwork or execution, is where
184internal auditors will take the information they\'ve discovered and test
185it against regulations, industry standards, company rules, best
186practices, as well as validating that any processes are complete and
187accurate. For example, an audit of HR would most likely examine
188processes such as employee on-boarding, employee termination, security
189of personally identifiable information (PII), or the IT systems involved
190in these processes. Company standards would be examined and compared
191against how the processes are actually being performed day-to-day, as
192well as compared against regulations such as the Equal Employment
193Opportunity (EEO), American with Disabilities Act, and National Labor
194Relations Act.
195
196## 4. Reporting
197
198Once all the tests have been completed, the audit will enter the
199reporting phase. This is when the audit team will conclude on the
200evidence they\'ve collected, interviews they\'ve held, and any opinions
201they\'ve formed on the controls in place. A summary of the audit
202findings, conclusions, and specific recommendations are officially
203communicated to the client through a draft report. Clients have the
204opportunity to respond to the report and submit an action plan and time
205frame. These responses become part of the final report which is
206distributed to the appropriate level of administration.
207
208## 5. Follow-Up
209
210After audits have been completed and management has formed action plans
211and time frames for audit issues, internal audit will follow up once
212that due date has arrived. In most cases, the follow-up will simply
213consist of a meeting to discuss how the action plan has been completed
214and to request documentation to prove it.
215
216# Audit Department Structure
217
218While an internal audit department is most often thought of as a team of
219full-time employees, there are actually many different ways in which a
220department can be structured. As the world becomes more digital and
221fast-paced, outsourcing has become a more attractive option for some
222organizations. Internal audit can be fully outsourced or partially
223outsourced, allowing for flexibility in cases where turnover is high.
224
225In addition, departments can implement a rotational model. This allows
226for interested employees around the organization to rotate into the
227internal audit department for a period of time, allowing them to obtain
228knowledge of risks and controls and allowing the internal audit team to
229obtain more business area knowledge. This program is popular in very
230large organizations, but organizations tend to rotate lower-level audit
231staff instead of managers. This helps prevent any significant knowledge
232loss as auditors rotate out to business areas.
233
234# Consulting
235
236Consulting is not an easy task at any organization, especially for a
237department that can have negative perceptions within the organization as
238the \"compliance police.\" However, once an internal audit department
239has delivered value to organization, adding consulting to their suite of
240services is a smart move. In most cases, Internal Audit can insert
241themselves into a consulting role without affecting the process of
242project management at the company. This means that internal audit can
243add objective assurance and opinions to business areas as they develop
244new processes, instead of coming in periodically to audit an area and
245file issues that could have been fixed at the beginning.
246
247# Data Science & Data Analytics
248
249![Data Science Skill
250Set](https://img.cleberg.net/blog/20200922-what-is-internal-audit/data-science-skillset.png)
251
252One major piece of the internal audit function in the modern world is
253data science. While the process is data science, most auditors will
254refer to anything in this realm as data analytics. Hot topics such as
255robotic process automation (RPA), machine learning (ML), and data mining
256have taken over the auditing world in recent years. These technologies
257have been immensely helpful with increasing the effectiveness and
258efficiency of auditors.
259
260For example, mundane and repetitive tasks can be automated in order for
261auditors to make more room in their schedules for labor-intensive work.
262Further, auditors will need to adapt technologies like machine learning
263in order to extract more value from the data they\'re using to form
264conclusions.
content/blog/2020-09-22-internal-audit.org deleted −232
@@ -1,232 +0,0 @@
1#+title: What is Internal Audit?
2#+date: 2020-09-22
3#+description: Learn about the Internal Audit function and their purpose.
4#+filetags: :audit:
5
6#+caption: Internal Audit Overview
7[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-overview.jpg]]
8
9* Definitions
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#+begin_quote
16Internal auditing is an independent, objective assurance and consulting activity
17designed to add value and improve an organization's operations. It helps an
18organization accomplish its objectives by bringing a systematic, disciplined
19approach to evaluate and improve the effectiveness of risk management, control,
20and governance processes.
21
22#+end_quote
23
24However, this definition uses quite a few terms that aren't clear unless the
25reader already has a solid understanding of the auditing profession. To further
26explain, the following is a list of definitions that can help supplement
27understanding of internal auditing.
28
29** Independent
30Independence is the freedom from conditions that threaten the ability of the
31internal audit activity to carry out internal audit responsibilities in an
32unbiased manner. To achieve the degree of independence necessary to effectively
33carry out the responsibilities of the internal audit activity, the chief audit
34executive has direct and unrestricted access to senior management and the board.
35This can be achieved through a dual-reporting relationship. Threats to
36independence must be managed at the individual auditor, engagement, functional,
37and organizational levels.
38
39** Objective
40Objectivity is an unbiased mental attitude that allows internal auditors to
41perform engagements in such a manner that they believe in their work product and
42that no quality compromises are made. Objectivity requires that internal
43auditors do not subordinate their judgment on audit matters to others. Threats
44to objectivity must be managed at the individual auditor, engagement,
45functional, and organizational levels.
46
47** Assurance
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
54subject - (the process owner), (2) the person or group making the assessment -
55(the internal auditor), and (3) the person or group using the assessment - (the
56user).
57
58** Consulting
59Consulting services are advisory in nature and are generally performed at the
60specific request of an engagement client. The nature and scope of the consulting
61engagement are subject to agreement with the engagement client. Consulting
62services generally involve two parties: (1) the person or group offering the
63advice (the internal auditor), and (2) the person or group seeking and receiving
64the advice (the engagement client). When performing consulting services, the
65internal auditor should maintain objectivity and not assume management
66responsibility.
67
68** Governance, Risk Management, & Compliance (GRC)
69The integrated collection of capabilities that enable an organization to
70reliably achieve objectives, address uncertainty and act with integrity.
71
72* Audit Charter & Standards
73First, it's important to note that not every organization needs internal
74auditors. In fact, it's unwise for an organization to hire internal auditors
75unless they have regulatory requirements for auditing and have the capital to
76support the department. Internal audit is a cost center that can only affect
77revenue indirectly.
78
79Once an organization determines the need for internal assurance services, they
80will hire a Chief Audit Executive and create the audit charter. This charter is
81a document, approved by the company's governing body, that will define internal
82audit's purpose, authority, responsibility, and position within the
83organization. Fortunately, the IIA has model charters available to IIA members
84for those developing or improving their charter.
85
86Beyond the charter and organizational documents, internal auditors follow a few
87different standards in order to perform their job. First is the International
88Professional Practices Framework (IPPF) by the IIA, which is the model of
89standards for internal auditing. In addition, ISACA's Information Technology
90Assurance Framework (ITAF) helps guide auditors in reference to information
91technology (IT) compliance and assurance. Finally, additional standards such as
92FASB, GAAP, and industry-specific standards are used when performing internal
93audit work.
94
95* Three Lines of Defense
96[[https://theiia.org][The IIA]] released the original Three Lines of Defense model in 2013, but have
97released an updated version in 2020. Here is what the Three Lines of Defense
98model has historically looked like:
99
100#+caption: 2013 Three Lines of Defense Model
101[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/three_lines_model.png]]
102
103I won't go into depth about the changes made to the model in this article.
104Instead, let's take a look at the most current model.
105
106#+caption: 2020 Three Lines of Defense Model
107[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/updated_three_lines_model.png]]
108
109The updated model forgets the strict idea of areas performing their own
110functions or line of defense. Instead of talking about management, risk, and
111internal audit as 1-2-3, the new model creates a more fluid and cooperative
112model.
113
114Looking at this model from an auditing perspective shows us that auditors will
115need to align, communicate, and collaborate with management, including business
116area managers and chief officers, as well as reporting to the governing body.
117The governing body will instruct internal audit /functionally/ on their goals
118and track their progress periodically.
119
120However, the internal audit department will report /administratively/ to a chief
121officer in the company for the purposes of collaboration, direction, and
122assistance with the business. Note that in most situations, the governing body
123is the audit committee on the company's board of directors.
124
125The result of this structure is that internal audit is an independent and
126objective function that can provide assurance over the topics they audit.
127
128* Audit Process
129A normal audit will generally follow the same process, regardless of the topic.
130However, certain special projects or abnormal business areas may call for
131changes to the audit process. The audit process is not set in stone, it's simply
132a set of best practices so that audits can be performed consistently.
133
134#+caption: The Internal Audit Process
135[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-process.jpg]]
136
137While different organizations may tweak the process, it will generally follow
138this flow:
139
140** 1. Risk Assessment
141The risk assessment part of the process has historically been performed
142annually, but many organizations have moved to performing this process much more
143frequently. In fact, some organizations are moving to an agile approach that can
144take new risks into the risk assessment and re-prioritize risk areas on-the-go.
145To perform a risk assessment, leaders in internal audit will research industry
146risks, consult with business leaders around the company, and perform analyses on
147company data.
148
149Once a risk assessment has been documented, the audit department has a
150prioritized list of risks that can be audited. This is usually in the form of
151auditable entities, such as business areas or departments.
152
153** 2. Planning
154During the planning phase of an audit, auditors will meet with the business area
155to discuss the various processes, controls, and risks applicable to the
156business. This helps the auditors determine the scope limits for the audit, as
157well as timing and subject-matter experts. Certain documents will be created in
158this phase that will be used to keep the audit on-track an in-scope as it goes
159forward.
160
161** 3. Testing
162The testing phase, also known as fieldwork or execution, is where internal
163auditors will take the information they've discovered and test it against
164regulations, industry standards, company rules, best practices, as well as
165validating that any processes are complete and accurate. For example, an audit
166of HR would most likely examine processes such as employee on-boarding, employee
167termination, security of personally identifiable information (PII), or the IT
168systems involved in these processes. Company standards would be examined and
169compared against how the processes are actually being performed day-to-day, as
170well as compared against regulations such as the Equal Employment Opportunity
171(EEO), American with Disabilities Act, and National Labor Relations Act.
172
173** 4. Reporting
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
184After audits have been completed and management has formed action plans and time
185frames for audit issues, internal audit will follow up once that due date has
186arrived. In most cases, the follow-up will simply consist of a meeting to
187discuss how the action plan has been completed and to request documentation to
188prove it.
189
190* Audit Department Structure
191While an internal audit department is most often thought of as a team of
192full-time employees, there are actually many different ways in which a
193department can be structured. As the world becomes more digital and fast-paced,
194outsourcing has become a more attractive option for some organizations. Internal
195audit can be fully outsourced or partially outsourced, allowing for flexibility
196in cases where turnover is high.
197
198In addition, departments can implement a rotational model. This allows for
199interested employees around the organization to rotate into the internal audit
200department for a period of time, allowing them to obtain knowledge of risks and
201controls and allowing the internal audit team to obtain more business area
202knowledge. This program is popular in very large organizations, but
203organizations tend to rotate lower-level audit staff instead of managers. This
204helps prevent any significant knowledge loss as auditors rotate out to business
205areas.
206
207* Consulting
208Consulting is not an easy task at any organization, especially for a department
209that can have negative perceptions within the organization as the "compliance
210police." However, once an internal audit department has delivered value to
211organization, adding consulting to their suite of services is a smart move. In
212most cases, Internal Audit can insert themselves into a consulting role without
213affecting the process of project management at the company. This means that
214internal audit can add objective assurance and opinions to business areas as
215they develop new processes, instead of coming in periodically to audit an area
216and file issues that could have been fixed at the beginning.
217
218* Data Science & Data Analytics
219#+caption: Data Science Skill Set
220[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/data-science-skillset.png]]
221
222One major piece of the internal audit function in the modern world is data
223science. While the process is data science, most auditors will refer to anything
224in this realm as data analytics. Hot topics such as robotic process automation
225(RPA), machine learning (ML), and data mining have taken over the auditing world
226in recent years. These technologies have been immensely helpful with increasing
227the effectiveness and efficiency of auditors.
228
229For example, mundane and repetitive tasks can be automated in order for auditors
230to make more room in their schedules for labor-intensive work. Further, auditors
231will need to adapt technologies like machine learning in order to extract more
232value from the data they're using to form conclusions.
content/blog/2020-09-25-happiness-map.org → content/blog/2020-09-25-happiness-map.md renamed +97 −83
@@ -1,86 +1,95 @@
1#+title: Data Visualization: World Choropleth Map of Happiness 1+++
2#+date: 2020-09-25 2date = 2020-09-25
3#+description: Exploring and visualizing data with Python. 3title = "Data Visualization: World Choropleth Map of Happiness"
4#+filetags: :data: 4description = ""
5draft = false
6+++
5 7
6* Background Information 8# Background Information
7The dataset (obtained from [[https://www.kaggle.com/unsdsn/world-happiness][Kaggle]]) used in this article contains a list of 9
8countries around the world, their happiness rankings and scores, as well as 10The dataset (obtained from
9other national scoring measures. 11[Kaggle](https://www.kaggle.com/unsdsn/world-happiness)) used in this
12article contains a list of countries around the world, their happiness
13rankings and scores, as well as other national scoring measures.
10 14
11Fields include: 15Fields include:
12 16
13- Overall rank 17- Overall rank
14- Country or region 18- Country or region
15- GDP per capita 19- GDP per capita
16- Social support 20- Social support
17- Healthy life expectancy 21- Healthy life expectancy
18- Freedom to make life choices 22- Freedom to make life choices
19- Generosity 23- Generosity
20- Perceptions of corruption 24- Perceptions of corruption
25
26There are 156 records. Since there are \~195 countries in the world, we
27can see that around 40 countries will be missing from this dataset.
21 28
22There are 156 records. Since there are ~195 countries in the world, we can see 29# Install Packages
23that around 40 countries will be missing from this dataset.
24 30
25* Install Packages 31As always, run the `install` command for all packages needed
26As always, run the =install= command for all packages needed to perform 32to perform analysis.
27analysis.
28 33
29#+begin_src python 34``` python
30!pip install folium geopandas matplotlib numpy pandas 35!pip install folium geopandas matplotlib numpy pandas
31#+end_src 36```
32 37
33* Import the Data 38# Import the Data
34We only need a couple packages to create a choropleth map. We will use [[https://python-visualization.github.io/folium/][Folium]],
35which provides map visualizations in Python. We will also use geopandas and
36pandas to wrangle our data before we put it on a map.
37 39
38#+begin_src python 40We only need a couple packages to create a choropleth map. We will use
41[Folium](https://python-visualization.github.io/folium/), which provides
42map visualizations in Python. We will also use geopandas and pandas to
43wrangle our data before we put it on a map.
44
45``` python
39# Import the necessary Python packages 46# Import the necessary Python packages
40import folium 47import folium
41import geopandas as gpd 48import geopandas as gpd
42import pandas as pd 49import pandas as pd
43#+end_src 50```
44 51
45To get anything to show up on a map, we need a file that will specify the 52To get anything to show up on a map, we need a file that will specify
46boundaries of each country. Luckily, GeoJSON files exist (for free!) on the 53the boundaries of each country. Luckily, GeoJSON files exist (for free!)
47internet. To get the boundaries of every country in the world, we will use the 54on the internet. To get the boundaries of every country in the world, we
48GeoJSON link shown below. 55will use the GeoJSON link shown below.
49 56
50GeoPandas will take this data and load it into a dataframe so that we can easily 57GeoPandas will take this data and load it into a dataframe so that we
51match it to the data we're trying to analyze. Let's look at the GeoJSON 58can easily match it to the data we\'re trying to analyze. Let\'s look at
52dataframe: 59the GeoJSON dataframe:
53 60
54#+begin_src python 61``` python
55# Load the GeoJSON data with geopandas 62# Load the GeoJSON data with geopandas
56geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson') 63geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson')
57geo_data.head() 64geo_data.head()
58#+end_src 65```
59 66
60#+caption: GeoJSON Dataframe 67![GeoJSON
61[[https://img.cleberg.net/blog/20200925-world-choropleth-map/geojson_df.png]] 68Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/geojson_df.png)
62 69
63Next, let's load the data from the Kaggle dataset. I've downloaded this file, so 70Next, let\'s load the data from the Kaggle dataset. I\'ve downloaded
64update the file path if you have it somewhere else. After loading, let's take a 71this file, so update the file path if you have it somewhere else. After
65look at this dataframe: 72loading, let\'s take a look at this dataframe:
66 73
67#+begin_src python 74``` python
68# Load the world happiness data with pandas 75# Load the world happiness data with pandas
69happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv') 76happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv')
70happy_data.head() 77happy_data.head()
71#+end_src 78```
79
80![Happiness
81Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/happiness_df.png)
72 82
73#+caption: Happiness Dataframe 83# Clean the Data
74[[https://img.cleberg.net/blog/20200925-world-choropleth-map/happiness_df.png]]
75 84
76* Clean the Data 85Some countries need to be renamed, or they will be lost when you merge
77Some countries need to be renamed, or they will be lost when you merge the 86the happiness and GeoJSON dataframes. This is something I discovered
78happiness and GeoJSON dataframes. This is something I discovered when the map 87when the map below showed empty countries. I searched both data frames
79below showed empty countries. I searched both data frames for the missing 88for the missing countries to see the naming differences. Any countries
80countries to see the naming differences. Any countries that do not have records 89that do not have records in the `happy_data` df will not show
81in the =happy_data= df will not show up on the map. 90up on the map.
82 91
83#+begin_src python 92``` python
84# Rename some countries to match our GeoJSON data 93# Rename some countries to match our GeoJSON data
85 94
86# Rename USA 95# Rename USA
@@ -98,35 +107,38 @@ happy_data.at[republic_congo_index, 'Country or region'] = 'Republic of Congo'
98# Rename the DRC 107# Rename the DRC
99democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)'] 108democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)']
100happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo' 109happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo'
101#+end_src 110```
111
112# Merge the Data
102 113
103* Merge the Data
104Now that we have clean data, we need to merge the GeoJSON data with the 114Now that we have clean data, we need to merge the GeoJSON data with the
105happiness data. Since we've stored them both in dataframes, we just need to call 115happiness data. Since we\'ve stored them both in dataframes, we just
106the =.merge()= function. 116need to call the `.merge()` function.
107 117
108We will also rename a couple columns, just so that they're a little easier to 118We will also rename a couple columns, just so that they\'re a little
109use when we create the map. 119easier to use when we create the map.
110 120
111#+begin_src python 121``` python
112# Merge the two previous dataframes into a single geopandas dataframe 122# Merge the two previous dataframes into a single geopandas dataframe
113merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region') 123merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region')
114 124
115# Rename columns for ease of use 125# Rename columns for ease of use
116merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'}) 126merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'})
117merged_df = merged_df.rename(columns = {'Country or region':'Country'}) 127merged_df = merged_df.rename(columns = {'Country or region':'Country'})
118#+end_src 128```
129
130![Merged
131Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/merged_df.png)
119 132
120#+caption: Merged Dataframe 133# Create the Map
121[[https://img.cleberg.net/blog/20200925-world-choropleth-map/merged_df.png]]
122 134
123* Create the Map
124The data is finally ready to be added to a map. The code below shows the 135The data is finally ready to be added to a map. The code below shows the
125simplest way to find the center of the map and create a Folium map object. The 136simplest way to find the center of the map and create a Folium map
126important part is to remember to reference the merged dataframe for our GeoJSON 137object. The important part is to remember to reference the merged
127data and value data. The columns specify which geo data and value data to use. 138dataframe for our GeoJSON data and value data. The columns specify which
139geo data and value data to use.
128 140
129#+begin_src python 141``` python
130# Assign centroids to map 142# Assign centroids to map
131x_map = merged_df.centroid.x.mean() 143x_map = merged_df.centroid.x.mean()
132y_map = merged_df.centroid.y.mean() 144y_map = merged_df.centroid.y.mean()
@@ -150,19 +162,21 @@ folium.Choropleth(
150 smooth_factor=0, 162 smooth_factor=0,
151 highlight=True 163 highlight=True
152).add_to(world_map) 164).add_to(world_map)
153#+end_src 165```
166
167Let\'s look at the resulting map.
154 168
155Let's look at the resulting map. 169![Choropleth
170Map](https://img.cleberg.net/blog/20200925-world-choropleth-map/map.png)
156 171
157#+caption: Choropleth Map 172# Create a Tooltip on Hover
158[[https://img.cleberg.net/blog/20200925-world-choropleth-map/map.png]]
159 173
160* Create a Tooltip on Hover 174Now that we have a map set up, we could stop. However, I want to add a
161Now that we have a map set up, we could stop. However, I want to add a tooltip 175tooltip so that I can see more information about each country. The
162so that I can see more information about each country. The =tooltip_data= code 176`tooltip_data` code below will show a popup on hover with all
163below will show a popup on hover with all the data fields shown. 177the data fields shown.
164 178
165#+begin_src python 179``` python
166 # Adding labels to map 180 # Adding labels to map
167 style_function = lambda x: {'fillColor': '#ffffff', 181 style_function = lambda x: {'fillColor': '#ffffff',
168 'color':'#000000', 182 'color':'#000000',
@@ -203,10 +217,10 @@ folium.LayerControl().add_to(world_map)
203 217
204# Display the map 218# Display the map
205world_map 219world_map
206#+end_src 220```
207 221
208The final image below will show you what the tooltip looks like whenever you 222The final image below will show you what the tooltip looks like whenever
209hover over a country. 223you hover over a country.
210 224
211#+caption: Choropleth Map Tooltip 225![Choropleth Map
212[[https://img.cleberg.net/blog/20200925-world-choropleth-map/tooltip_map.png]] 226Tooltip](https://img.cleberg.net/blog/20200925-world-choropleth-map/tooltip_map.png)
content/blog/2020-10-12-mediocrity.org → content/blog/2020-10-12-mediocrity.md renamed +56 −57
@@ -1,10 +1,13 @@
1#+title: On the Pursuit of Mediocrity 1+++
2#+date: 2020-10-12 2date = 2020-10-12
3#+description: Musings on mediocrity. 3title = "On the Pursuit of Mediocrity"
4#+filetags: :personal: 4description = ""
5draft = false
6+++
5 7
6* Perfect is the Enemy of Good 8# Perfect is the Enemy of Good
7As the saying goes, "the best is the enemy of the good." As we strive 9
10As the saying goes, \"the best is the enemy of the good.\" As we strive
8for perfection, we often fail to realize the implications of such an 11for perfection, we often fail to realize the implications of such an
9undertaking. Attempting to reach perfection is often unrealistic. Even 12undertaking. Attempting to reach perfection is often unrealistic. Even
10worse, it can get in the way of achieving a good outcome. In certain 13worse, it can get in the way of achieving a good outcome. In certain
@@ -13,37 +16,36 @@ burned the bridges that would have allowed us to reach a lesser yet
13still superb solution. 16still superb solution.
14 17
15Philosophers throughout history have inspected this plight from many 18Philosophers throughout history have inspected this plight from many
16viewpoints. Greek mythology speaks of the 19viewpoints. Greek mythology speaks of the [golden
17[[https://en.wikipedia.org/wiki/Golden_mean_(philosophy)][golden mean]], 20mean](https://en.wikipedia.org/wiki/Golden_mean_(philosophy)), which
18which uses the story of Icarus to illustrate that sometimes "the middle 21uses the story of Icarus to illustrate that sometimes \"the middle
19course" is the best solution. In this story, Daedalus, a famous artist 22course\" is the best solution. In this story, Daedalus, a famous artist
20of his time, built feathered wings for himself and his son so that they 23of his time, built feathered wings for himself and his son so that they
21might escape the clutches of King Minos. Daedalus warns his beloved son 24might escape the clutches of King Minos. Daedalus warns his beloved son
22whom he loved so much to "fly the middle course", between the sea spray 25whom he loved so much to \"fly the middle course\", between the sea
23and the sun's heat. Icarus did not heed his father; he flew up and up 26spray and the sun\'s heat. Icarus did not heed his father; he flew up
24until the sun melted the wax off his wings. For not heeding the middle 27and up until the sun melted the wax off his wings. For not heeding the
25course, he fell into the sea and drowned. 28middle course, he fell into the sea and drowned.
26 29
27More recently, management scholars have explored the 30More recently, management scholars have explored the [Pareto
28[[https://en.wikipedia.org/wiki/Pareto_principle][Pareto principle]] and 31principle](https://en.wikipedia.org/wiki/Pareto_principle) and found
29found that as we increase the frequency of something, or strive to 32that as we increase the frequency of something, or strive to perform
30perform actions to achieve some form of perfection, we run into 33actions to achieve some form of perfection, we run into [diminishing
31[[https://en.wikipedia.org/wiki/Diminishing_returns][diminishing 34returns](https://en.wikipedia.org/wiki/Diminishing_returns).
32returns]]. 35
33 36Even further, Harold Demsetz is noted as coining the term [the Nirvana
34Even further, Harold Demsetz is noted as coining the term 37fallacy](https://en.wikipedia.org/wiki/Nirvana_fallacy) in 1969, which
35[[https://en.wikipedia.org/wiki/Nirvana_fallacy][the Nirvana fallacy]] 38shows the fallacy of comparing actual things with unrealistic, idealized
36in 1969, which shows the fallacy of comparing actual things with 39alternatives. This is another trap that we may fall into, where we are
37unrealistic, idealized alternatives. This is another trap that we may 40constantly thinking of the ultimate solutions to problems, when
38fall into, where we are constantly thinking of the ultimate solutions to 41something more realistic needs to be considered.
39problems, when something more realistic needs to be considered. 42
40 43Over and over throughout history, we\'ve found that perfection is often
41Over and over throughout history, we've found that perfection is often
42unrealistic and unachievable. However, we push ourselves and our peers 44unrealistic and unachievable. However, we push ourselves and our peers
43to "give 100%" or "go the extra mile," while it may be that the better 45to \"give 100%\" or \"go the extra mile,\" while it may be that the
44course is to give a valuable level of effort while considering the 46better course is to give a valuable level of effort while considering
45effects of further effort on the outcome. Working harder does not always 47the effects of further effort on the outcome. Working harder does not
46help us achieve loftier goals. 48always help us achieve loftier goals.
47 49
48This has presented itself to me most recently during my time studying at 50This has presented itself to me most recently during my time studying at
49my university. I was anxious and feeling the stresses of my courses, 51my university. I was anxious and feeling the stresses of my courses,
@@ -52,44 +54,41 @@ how well I was doing at school and my level of effort at work. One day,
52I happened to be talking to my father when he said something simple that 54I happened to be talking to my father when he said something simple that
53hit home: 55hit home:
54 56
55#+begin_quote 57> All you can do is show up and do your best. Worrying about the
56All you can do is show up and do your best. Worrying about the outcomes 58> outcomes won\'t affect the outcome itself.
57won't affect the outcome itself.
58#+end_quote
59 59
60The thought was extremely straightforward and uncomplicated, yet it was 60The thought was extremely straightforward and uncomplicated, yet it was
61something that I had lost sight of during my stress-filled years at 61something that I had lost sight of during my stress-filled years at
62school. Ever since then, I've found myself pausing and remembering that 62school. Ever since then, I\'ve found myself pausing and remembering that
63quote every time I get anxious or stressed. It helps to stop and think 63quote every time I get anxious or stressed. It helps to stop and think
64"Can I do anything to affect the outcome, or am I simply worrying over 64\"Can I do anything to affect the outcome, or am I simply worrying over
65something I can't change?" 65something I can\'t change?\"
66
67# When Mediocrity Isn\'t Enough
66 68
67* When Mediocrity Isn't Enough
68One problem with the philosophies presented in this post is that they 69One problem with the philosophies presented in this post is that they
69are implemented far too often in situations where mediocrity simply 70are implemented far too often in situations where mediocrity simply
70isn't adequate. For example, let's take a look at digital user data, 71isn\'t adequate. For example, let\'s take a look at digital user data,
71specifically personally-identifiable information (PII). As a 72specifically personally-identifiable information (PII). As a
72cybersecurity auditor in the United States, I have found that most 73cybersecurity auditor in the United States, I have found that most
73companies are concerned more with compliance than any actual safeguards 74companies are concerned more with compliance than any actual safeguards
74over the privacy or protection of user data. Other than companies who 75over the privacy or protection of user data. Other than companies who
75have built their reputation on privacy and security, most companies will 76have built their reputation on privacy and security, most companies will
76use [[https://en.wikipedia.org/wiki/Satisficing][satisficing]] as their 77use [satisficing](https://en.wikipedia.org/wiki/Satisficing) as their
77primary decision-making strategy around user data. 78primary decision-making strategy around user data.
78 79
79#+begin_quote 80> Satisficing is a decision-making strategy or cognitive heuristic that
80Satisficing is a decision-making strategy or cognitive heuristic that 81> entails searching through the available alternatives until an
81entails searching through the available alternatives until an 82> acceptability threshold is met.
82acceptability threshold is met.
83#+end_quote
84 83
85This means that each decision will be met with certain possible 84This means that each decision will be met with certain possible
86solutions until one of the solutions meets their minimum acceptable 85solutions until one of the solutions meets their minimum acceptable
87standards. For companies that deal with user data, the 86standards. For companies that deal with user data, the
88minimum-acceptable standards come from three areas: 87minimum-acceptable standards come from three areas:
89 88
901. Laws and regulations 891. Laws and regulations
912. Competitive pressure 902. Competitive pressure
923. Risk of monetary or reputation loss 913. Risk of monetary or reputation loss
93 92
94Working with project management or auditing, the primary concern here is 93Working with project management or auditing, the primary concern here is
95the risk of legal ramifications. Since the primary risk comes from laws 94the risk of legal ramifications. Since the primary risk comes from laws
@@ -109,14 +108,14 @@ satisficing, which states that they have met the minimum acceptable
109standards and can now move on to other decisions. Modern business 108standards and can now move on to other decisions. Modern business
110culture in the United States dictates that profits are the golden 109culture in the United States dictates that profits are the golden
111measure of how well a company or manager is performing, so we often 110measure of how well a company or manager is performing, so we often
112don't think about our responsibilities beyond these basic standards. 111don\'t think about our responsibilities beyond these basic standards.
113 112
114Not all situations demand excellence, but I believe that applying any 113Not all situations demand excellence, but I believe that applying any
115philosophy as a broad stroke across one's life can be a mistake. We must 114philosophy as a broad stroke across one\'s life can be a mistake. We
116be able to think critically about what we are doing as we do it and ask 115must be able to think critically about what we are doing as we do it and
117ourselves a few questions. Have I done everything I can in this 116ask ourselves a few questions. Have I done everything I can in this
118situation? Is mediocrity an acceptable outcome, or should we strive for 117situation? Is mediocrity an acceptable outcome, or should we strive for
119perfection, even if we can't attain it? 118perfection, even if we can\'t attain it?
120 119
121Taking a few moments to think critically throughout our day, as we make 120Taking a few moments to think critically throughout our day, as we make
122decisions, can have a tremendous effect on the outcomes we create. 121decisions, can have a tremendous effect on the outcomes we create.
content/blog/2020-12-27-website-redesign.org → content/blog/2020-12-27-website-redesign.md renamed +51 −43
@@ -1,85 +1,93 @@
1#+title: Redesigning My Website: The 5 KB Result 1+++
2#+date: 2020-12-27 2date = 2020-12-27
3#+description: A retrospective on my recent website redesign. 3title = "Redesigning My Website: The 5 KB Result"
4#+filetags: :sysadmin: 4description = ""
5draft = false
6+++
5 7
6* A Brief History 8# A Brief History
7As a form of continuous learning and entertainment, I've been running a 9
10As a form of continuous learning and entertainment, I\'ve been running a
8handful of websites since 2016 when I took my first programming courses 11handful of websites since 2016 when I took my first programming courses
9in college. I maintain one main website, the place I consider the 12in college. I maintain one main website, the place I consider the
10official website to represent me. Under this site, I have a handful of 13official website to represent me. Under this site, I have a handful of
11subdirectories and subdomains. 14subdirectories and subdomains.
12 15
13One of the parts I've enjoyed the most about web development is the 16One of the parts I\'ve enjoyed the most about web development is the
14aspect of designing an identity for a web page and working to find 17aspect of designing an identity for a web page and working to find
15exciting ways to display the site's content. Inevitably, this means I've 18exciting ways to display the site\'s content. Inevitably, this means
16changed the designs for my websites more times than I could possibly 19I\'ve changed the designs for my websites more times than I could
17count. Since I don't really host anything on my main webpage that's 20possibly count. Since I don\'t really host anything on my main webpage
18vital, it allows me the freedom to change things as inspiration strikes. 21that\'s vital, it allows me the freedom to change things as inspiration
22strikes.
19 23
20Historically, I've relied on core utilities for spacing, components, and 24Historically, I\'ve relied on core utilities for spacing, components,
21layouts from [[https://getbootstrap.com][Bootstrap]] and added custom 25and layouts from [Bootstrap](https://getbootstrap.com) and added custom
22CSS for fonts, accents, colors, and other items. I also tend to create 26CSS for fonts, accents, colors, and other items. I also tend to create
23sites with no border radius on items, visible borders, and content that 27sites with no border radius on items, visible borders, and content that
24takes up the entire screen (using whitespace inside components instead 28takes up the entire screen (using whitespace inside components instead
25of whitespace around my components). 29of whitespace around my components).
26 30
27* The Redesign Process 31# The Redesign Process
32
28About a week ago, I found myself wishing for a new design yet again. The 33About a week ago, I found myself wishing for a new design yet again. The
29prior design was largely inspired by IBM's 34prior design was largely inspired by IBM\'s [Carbon Design
30[[https://www.carbondesignsystem.com][Carbon Design System]] and relied 35System](https://www.carbondesignsystem.com) and relied on jQuery,
31on jQuery, Bootstrap, along with some compressed 36Bootstrap, along with some compressed
32[[https://en.wikipedia.org/wiki/WebP][.webp]] images. 37[.webp](https://en.wikipedia.org/wiki/WebP) images.
33 38
34To anyone who knows my preferences toward web design - and even in my 39To anyone who knows my preferences toward web design - and even in my
35personal life - it should be no surprise that I immediately started 40personal life - it should be no surprise that I immediately started
36looking for inspiration on minimalism. While there are some decent 41looking for inspiration on minimalism. While there are some decent
37minimalistic designs on sites like 42minimalistic designs on sites like
38[[https://dribbble.com/search/shots/popular/web-design?q=minimalism][Dribbble]], 43[Dribbble](https://dribbble.com/search/shots/popular/web-design?q=minimalism),
39people seem to mostly discuss [[https://brutalist-web.design][brutalist 44people seem to mostly discuss [brutalist web
40web design]] when you ask about minimalism. While brutalist web design 45design](https://brutalist-web.design) when you ask about minimalism.
41doesn't have to be minimal, it often is. 46While brutalist web design doesn\'t have to be minimal, it often is.
42 47
43I suppose, in a way, I did create a brutalist website since my HTML is 48I suppose, in a way, I did create a brutalist website since my HTML is
44semantic and accessible, hyperlinks are colored and underlined, and all 49semantic and accessible, hyperlinks are colored and underlined, and all
45native browser functions like scrolling and the back button work as 50native browser functions like scrolling and the back button work as
46expected. However, I didn't think about brutalism while designing these 51expected. However, I didn\'t think about brutalism while designing these
47sites. 52sites.
48 53
49The new design followed a simple design process. I walked through the 54The new design followed a simple design process. I walked through the
50screens on my blog and asked myself: "Is this element necessary for a 55screens on my blog and asked myself: \"Is this element necessary for a
51user?" This allowed me to first start by removing all javascript, which 56user?\" This allowed me to first start by removing all javascript, which
52had the sole purpose of allowing users to open a collapsed navbar on 57had the sole purpose of allowing users to open a collapsed navbar on
53mobile. Replacing the collapsible navbar allowed me to remove both 58mobile. Replacing the collapsible navbar allowed me to remove both
54jQuery and Bootstrap's javascript. 59jQuery and Bootstrap\'s javascript.
55 60
56Next, I removed things like author names (since I'm literally the only 61Next, I removed things like author names (since I\'m literally the only
57person who will ever write on this site), multiple ways to click on a 62person who will ever write on this site), multiple ways to click on a
58blog post card, blog post descriptions, and the scroll-to-top button. It 63blog post card, blog post descriptions, and the scroll-to-top button. It
59also helped to move all categories to a single page, rather than have 64also helped to move all categories to a single page, rather than have
60each category on its own page. 65each category on its own page.
61 66
62The final big piece to finish the 67The final big piece to finish the
63"[[https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method][KonMari]]"-like 68\"[KonMari](https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method)\"-like
64part of my process was to remove Bootstrap CSS in its entirety. However, 69part of my process was to remove Bootstrap CSS in its entirety. However,
65this meant pulling out a few very useful classes, such as =.img-fluid= 70this meant pulling out a few very useful classes, such as
66and the default font stacks to keep in my custom CSS. 71`.img-fluid` and the default font stacks to keep in my custom
72CSS.
67 73
68After removing all the unnecessary pieces, I was finally able to 74After removing all the unnecessary pieces, I was finally able to
69reorganize my content and add a very small amount of custom CSS to make 75reorganize my content and add a very small amount of custom CSS to make
70everything pretty. This took a brief amount of time, effectively just 76everything pretty. This took a brief amount of time, effectively just
71consisting of me converting =<div>= tags into things like =<ul>= lists 77consisting of me converting `<div>` tags into things like
72and choosing accent colors. 78`<ul>` lists and choosing accent colors.
79
80# The Results
81
82## Reflection
73 83
74* The Results
75** Reflection
76So, what did all of this reorganizing do to my webpages? Well, first, my 84So, what did all of this reorganizing do to my webpages? Well, first, my
77websites are now *ridiculously fast*. Since the prior designs were also 85websites are now **ridiculously fast**. Since the prior designs were
78minimal and didn't have many images, they measured up in Firefox's 86also minimal and didn\'t have many images, they measured up in
79Network Monitor around 300 KB - 600KB. After making the changes, my main 87Firefox\'s Network Monitor around 300 KB - 600KB. After making the
80site is at 5 KB transferred (22 KB total), and my blog is at 6.5 KB 88changes, my main site is at 5 KB transferred (22 KB total), and my blog
81transferred (13 KB total). *That means the redesigned pages are less 89is at 6.5 KB transferred (13 KB total). **That means the redesigned
82than 2% the size of the old designs.* 90pages are less than 2% the size of the old designs.**
83 91
84Google Lighthouse ranks the new webpage as 100 in performance, 92Google Lighthouse ranks the new webpage as 100 in performance,
85accessibility, and best practices, with SEO at 92 since they think tap 93accessibility, and best practices, with SEO at 92 since they think tap
@@ -91,6 +99,6 @@ of blocking time, due to the fact that the CSS for my blog is within a
91separate CSS file, and the CSS for my main website is simply embedded in 99separate CSS file, and the CSS for my main website is simply embedded in
92the HTML file. 100the HTML file.
93 101
94Now that everything is complete, I can confidently say I'm happy with 102Now that everything is complete, I can confidently say I\'m happy with
95the result and proud to look at the fastest set of websites I've created 103the result and proud to look at the fastest set of websites I\'ve
96so far. 104created so far.
content/blog/2020-12-28-neon-drive.md added +95
@@ -0,0 +1,95 @@
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/)
11presents itself as a simple arcade-style game inspired by the arcade
12race games of the 1980s, yet it has managed to take up hours of my life
13without much effort. The game description, directly from the Steam page,
14is intriguing enough to entice anyone who\'s been looking for a good
15arcade racing game:
16
17> Neon Drive is a slick retro-futuristic arcade game that will make your
18> brain melt. You\'ve been warned. From beautiful cityscapes and ocean
19> roads to exploding enemy spaceships, Neon Drive has it all.
20
21# Gameplay
22
23The game holds true to the
24[retro-futurism](https://en.wikipedia.org/wiki/Retrofuturism) style,
25including chrome female robots, pixelated arcade machines, and
26[outrun](https://teddit.net/r/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
46![Neon Drive
47Menu](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_menu.png)
48
49Once you get into the first level, we see that the choice of car fits
50right in with the stereotypical cars of the 80s, like the
51[DeLorean](https://en.wikipedia.org/wiki/DMC_DeLorean) or the [Ferrari
52F40](https://en.wikipedia.org/wiki/Ferrari_F40). Each new level comes
53with new color schemes and cars, so you should never get tired of the
54aesthetic.
55
56![Neon Drive
57Race](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_race.png)
58
59Personally, I love the orange and blue colors used in level 2:
60
61![Level
622](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_level_2.png)
63
64If you\'re the competitive type and getting 100% on all arcade machines
65isn\'t enough, there are leaderboards for the regular part of the game,
66and the endurance game mode.
67
68![Leaderboard](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_leaderboard.png)
69
70# Other Suggestions
71
72Neon Drive sits nicely within the well-founded cult genre of Outrun.
73Other games that I\'ve enjoyed in this same spectrum are:
74
75- [Far Cry 3: Blood
76 Dragon](https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/)
77- [Retrowave](https://store.steampowered.com/app/1239690/Retrowave/)
78- [Slipstream](https://store.steampowered.com/app/732810/Slipstream/)
79
80Although these games aren\'t necessarily in the same genre, they do have
81aspects that place them close enough to interest gamers that enjoyed
82Neon Drive:
83
84- [Black Ice](https://store.steampowered.com/app/311800/Black_Ice/)
85- [Cloudpunk](https://store.steampowered.com/app/746850/Cloudpunk/)
86- [Need for Speed:
87 Heat](https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/)
88- [VirtuaVerse](https://store.steampowered.com/app/1019310/VirtuaVerse/)
89
90Of course, if all you really care about is the arcade aspect of these
91games, you can check out the [Atari
92Vault](https://store.steampowered.com/app/400020/Atari_Vault/) or any of
93the other classic games sold on Steam by companies like Namco, Atari.
94For something like Nintendo, you\'d have to settle for buying used
95classic consoles or delve into the world of emulation.
content/blog/2020-12-28-neon-drive.org deleted −83
@@ -1,83 +0,0 @@
1#+title: Neon Drive: A Nostalgic 80s Arcade Racing Game
2#+date: 2020-12-28
3#+description: A video game review for Neon Drive.
4#+filetags: :gaming:
5
6* Game Description
7[[https://store.steampowered.com/app/433910/Neon_Drive/][Neon Drive]] presents itself as a simple arcade-style game inspired by the arcade
8race games of the 1980s, yet it has managed to take up hours of my life without
9much effort. The game description, directly from the Steam page, is intriguing
10enough to entice anyone who's been looking for a good arcade racing game:
11
12#+begin_quote
13Neon Drive is a slick retro-futuristic arcade game that will make your brain
14melt. You've been warned. From beautiful cityscapes and ocean roads to exploding
15enemy spaceships, Neon Drive has it all.
16#+end_quote
17
18* Gameplay
19The game holds true to the [[https://en.wikipedia.org/wiki/Retrofuturism][retro-futurism]] style, including chrome female robots,
20pixelated arcade machines, and [[https://teddit.net/r/outrun/][outrun]] aesthetics.
21
22Each level of the game is shown as a separate arcade machine. Each arcade
23machine lets you play on Normal, Hard, Insane, Practice, and Free Run. To beat
24each arcade, you must reach the end of the level without crashing your car into
25the various obstacles on the course. Basic levels let you move left or right to
26avoid blocks in the road. Later levels put you through other tests, such as
27dodging traffic or blasting asteroids.
28
29The game uses synthwave music to keep you on track to make the correct moves by
30timing the beats of the songs to the correct moves on the screen. It reminds me
31of the early Guitar Hero games, as well as mobile apps like VOEZ - repetition
32and staying on-beat is the only way to win.
33
34* In-Game Screenshots
35Taking a look at the main menu, you can see that Neon Drive plays into every
36stereotype you can think of around retro-futuristic, synthwave arcades (in a
37good way).
38
39#+caption: Neon Drive Menu
40[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_menu.png]]
41
42Once you get into the first level, we see that the choice of car fits right in
43with the stereotypical cars of the 80s, like the [[https://en.wikipedia.org/wiki/DMC_DeLorean][DeLorean]] or the [[https://en.wikipedia.org/wiki/Ferrari_F40][Ferrari F40]].
44Each new level comes with new color schemes and cars, so you should never get
45tired of the aesthetic.
46
47#+caption: Neon Drive Race
48[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_race.png]]
49
50Personally, I love the orange and blue colors used in level 2:
51
52#+caption: Level 2
53[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_level_2.png]]
54
55If you're the competitive type and getting 100% on all arcade machines isn't
56enough, there are leaderboards for the regular part of the game, and the
57endurance game mode.
58
59#+caption: Leaderboard
60[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_leaderboard.png]]
61
62* Other Suggestions
63Neon Drive sits nicely within the well-founded cult genre of Outrun. Other games
64that I've enjoyed in this same spectrum are:
65
66- [[https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/][Far
67 Cry 3: Blood Dragon]]
68- [[https://store.steampowered.com/app/1239690/Retrowave/][Retrowave]]
69- [[https://store.steampowered.com/app/732810/Slipstream/][Slipstream]]
70
71Although these games aren't necessarily in the same genre, they do have aspects
72that place them close enough to interest gamers that enjoyed Neon Drive:
73
74- [[https://store.steampowered.com/app/311800/Black_Ice/][Black Ice]]
75- [[https://store.steampowered.com/app/746850/Cloudpunk/][Cloudpunk]]
76- [[https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/][Need
77 for Speed: Heat]]
78- [[https://store.steampowered.com/app/1019310/VirtuaVerse/][VirtuaVerse]]
79
80Of course, if all you really care about is the arcade aspect of these games, you
81can check out the [[https://store.steampowered.com/app/400020/Atari_Vault/][Atari Vault]] or any of the other classic games sold on Steam by
82companies like Namco, Atari. For something like Nintendo, you'd have to settle
83for buying used classic consoles or delve into the world of emulation.
content/blog/2020-12-29-zork.md added +96
@@ -0,0 +1,96 @@
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
11download Zork for free from Infocom\'s [download
12page](http://infocom-if.org/downloads/downloads.html). So feel free to
13boot it up 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
181980. This series, split into three separate games, introduced a robust
19and sophisticated text parser to gamers. People were largely used to the
20simple commands used in the popular game [Colossal Cave
21Adventure](https://en.wikipedia.org/wiki/Colossal_Cave_Adventure), but
22Zork allowed users to send more complex commands that included
23prepositions and conjunctions.
24
25Zork tracks your score as you explore the map, find tools, and collect
26trophy items (e.g., a jewel-encrusted egg). When you place your trophy
27items in the trophy case found in the Living Room area, you gain score
28points. Collecting the Twenty Treasures of Zork and placing them within
29the trophy case wins the game. However, you must explore the map, solve
30puzzles, and avoid being eaten by a grue 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
35explore and collect your trophies. However, if you want to play the game
36as it was truly intended, you should try to play it without using the
37map.
38
39![Zork Map](https://img.cleberg.net/blog/20201229-zork/zork_map.png)
40
41*[Map Source](https://www.filfre.net/2012/01/exploring-zork-part-1/)*
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 [Jimmy Maher\'s
50playthrough](https://www.filfre.net/2012/01/exploring-zork-part-1/), the
51original Zork games looked quite a bit different due to the older
52hardware of computers like the Apple II and multiple bug fixes that
53Infocom pushed out after the game\'s initial release. My play-through
54uses the [Zork
55Anthology](https://store.steampowered.com/app/570580/Zork_Anthology/)
56version, 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
60haven\'t played text adventures before, try to use simple commands like
61\"go west,\" \"look around,\" or \"hit troll with elvish sword.\"
62
63![Zork Screen, pt.
641](https://img.cleberg.net/blog/20201229-zork/zork_01.png)
65
66In this second screenshot, we see the player has entered the house and
67found the trophy case in the living room. The lantern and sword in this
68room allow the player to explore dark areas and attack enemies. If you
69don\'t use the lantern, you won\'t be able to see anything in dark
70areas, and you may be eaten by a grue.
71
72![Zork Screen, pt.
732](https://img.cleberg.net/blog/20201229-zork/zork_02.png)
74
75Finally, we see that the player has found the first treasure: a
76jewel-encrusted egg. These treasures can be taken back to the house and
77placed in the trophy case or carried until you feel like you want to put
78things away.
79
80![Zork Screen, pt
813.](https://img.cleberg.net/blog/20201229-zork/zork_03.png)
82
83# Conclusion
84
85It\'s been quite a few years since I first played Zork, but I clearly
86remember the late nights and bloodshot eyes that helped me find all the
87treasures. This game is well worth the time and effort, even though the
88text-based aspect may be off-putting to gamers who didn\'t have to grow
89up playing games without graphics. However, I believe that the strategy
90and skills learned in early video games like Zork can actually help you,
91even when playing newer games.
92
93If you do decide to play Zork, you can download Zork I, II, and III from
94Infocom\'s [download
95page](http://infocom-if.org/downloads/downloads.html) for free or search
96the internet for an online version.
content/blog/2020-12-29-zork.org deleted −77
@@ -1,77 +0,0 @@
1#+title: Zork: Let's Explore a Classic
2#+date: 2020-12-29
3#+description: A video game review for Zork.
4#+filetags: :gaming:
5
6* Download (Free)
7Before we get into the game itself, you should know that you can download Zork
8for free from Infocom's [[http://infocom-if.org/downloads/downloads.html][download page]]. So feel free to boot it up and take a
9ride back to the 1980s with this masterpiece.
10
11* Game Description
12Zork is an interactive, text-based computer game originally released in 1980.
13This series, split into three separate games, introduced a robust and
14sophisticated text parser to gamers. People were largely used to the simple
15commands used in the popular game [[https://en.wikipedia.org/wiki/Colossal_Cave_Adventure][Colossal Cave Adventure]], but Zork allowed
16users to send more complex commands that included prepositions and conjunctions.
17
18Zork tracks your score as you explore the map, find tools, and collect trophy
19items (e.g., a jewel-encrusted egg). When you place your trophy items in the
20trophy case found in the Living Room area, you gain score points. Collecting the
21Twenty Treasures of Zork and placing them within the trophy case wins the game.
22However, you must explore the map, solve puzzles, and avoid being eaten by a
23grue to collect these treasures.
24
25* The Map
26Since Zork is a vast and complex game, it helps to have a map as you explore and
27collect your trophies. However, if you want to play the game as it was truly
28intended, you should try to play it without using the map.
29
30#+caption: Zork Map
31[[https://img.cleberg.net/blog/20201229-zork/zork_map.png]]
32
33/[[https://www.filfre.net/2012/01/exploring-zork-part-1/][Map Source]]/
34
35* In-Game Screenshots
36After playing the game (for the first time ever) for several weeks around 2014,
37I was finally able to beat the game with some online help to find the last
38couple items. As I was writing this post, I installed the game again to grab
39some screenshots to show off the true glory of this game. As noted in [[https://www.filfre.net/2012/01/exploring-zork-part-1/][Jimmy
40Maher's playthrough]], the original Zork games looked quite a bit different due to
41the older hardware of computers like the Apple II and multiple bug fixes that
42Infocom pushed out after the game's initial release. My play-through uses the
43[[https://store.steampowered.com/app/570580/Zork_Anthology/][Zork Anthology]] version, which utilizes DOSBox on Windows.
44
45The first screenshot here shows the introductory information, which doesn't
46include instructions of any kind for the player. If you haven't played text
47adventures before, try to use simple commands like "go west," "look around," or
48"hit troll with elvish sword."
49
50#+caption: Zork Screen, pt. 1
51[[https://img.cleberg.net/blog/20201229-zork/zork_01.png]]
52
53In this second screenshot, we see the player has entered the house and found the
54trophy case in the living room. The lantern and sword in this room allow the
55player to explore dark areas and attack enemies. If you don't use the lantern,
56you won't be able to see anything in dark areas, and you may be eaten by a grue.
57
58#+caption: Zork Screen, pt. 2
59[[https://img.cleberg.net/blog/20201229-zork/zork_02.png]]
60
61Finally, we see that the player has found the first treasure: a jewel-encrusted
62egg. These treasures can be taken back to the house and placed in the trophy
63case or carried until you feel like you want to put things away.
64
65#+caption: Zork Screen, pt 3.
66[[https://img.cleberg.net/blog/20201229-zork/zork_03.png]]
67
68* Conclusion
69It's been quite a few years since I first played Zork, but I clearly remember
70the late nights and bloodshot eyes that helped me find all the treasures. This
71game is well worth the time and effort, even though the text-based aspect may be
72off-putting to gamers who didn't have to grow up playing games without graphics.
73However, I believe that the strategy and skills learned in early video games
74like Zork can actually help you, even when playing newer games.
75
76If you do decide to play Zork, you can download Zork I, II, and III from
77Infocom's [[http://infocom-if.org/downloads/downloads.html][download page]] for free or search the internet for an online version.
content/blog/2021-01-01-seum.md added +93
@@ -0,0 +1,93 @@
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/)
12is an incredibly fast-paced mashup of a puzzle game and a free-running
13game. Created by [Pine Studio](https://pinestudio.co) in early 2016 and
14first released as 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
17following on 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
22> from 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
27first time, playing an animated video in the form of comic book images.
28You see Marty, the protagonist, sitting around drinking his beer as
29Satan busts down the door, cuts his hand off, and steals all his beer
30and bacon. As Satan is leaving, Marty whips a vinyl record across the
31room and cuts off one of Satan\'s hands. This hand is what allows you to
32use all the powers in the game.
33
34Check out the screenshot below for one of the first panels of the
35storyline:
36
37![SEUM Story](https://img.cleberg.net/blog/20210101-seum/seum_story.png)
38
39# Gameplay
40
41To accomplish each level, you will need to get to the final blue portal
42under a certain time limit. You can beat a level by getting to the
43portal before the time meter runs out or \"Dominate\" a level by beating
44it within a shorter time limit (noted by a bright red/orange color in
45the time meter).
46
47The portal is usually set behind locked gates, protected by dangerous
48defenses, or in hard-to-reach places. To reach the portal, you\'ll need
49to go through any existing orange portals, light all yellow beacons,
50avoid things like fireballs and blades, or use any satanic power orbs
51lying around. These special abilities include:
52
53- Gravity
54- Teleport
55- Rewind
56- Spawn platform
57- Roar (DLC)
58- Rocket (DLC)
59- Shadow world (DLC)
60
61For the main storyline, there are nine floors to beat. Each floor
62contains nine regular levels, one boss level, and one bonus level;
63although you don\'t technically need to beat all levels to advance to
64the next floor.
65
66![SEUM Floor
67Menu](https://img.cleberg.net/blog/20210101-seum/seum_floor.png)
68
69# In-Game Screenshots
70
71The main menu gives you plenty of options for tuning your system,
72playing main levels, playing the DLC, or exploring the other game modes.
73
74![SEUM Main
75Menu](https://img.cleberg.net/blog/20210101-seum/seum_menu.png)
76
77Once you enter a level, you can look around and strategize before
78starting. Clicking any button will start the menu, and you\'ll have to
79restart if you die. One of the great things about SEUM is that it has
80great keyboard shortcuts. You can quickly smash `R` to
81restart the level or `M` to return to the level menu.
82
83![SEUM Level](https://img.cleberg.net/blog/20210101-seum/seum_level.png)
84
85Once you\'re able to win a level, you\'ll see the high scores and can
86watch replays of the best scores.
87
88![SEUM Win](https://img.cleberg.net/blog/20210101-seum/seum_win.png)
89
90Each regular level contains a beer in a disguised location that may take
91some out-of-the-box thinking.
92
93![SEUM Beer](https://img.cleberg.net/blog/20210101-seum/seum_beer.png)
content/blog/2021-01-01-seum.org deleted −92
@@ -1,92 +0,0 @@
1#+title: SEUM: Speedrunners from Hell
2#+date: 2021-01-01
3#+description: A video game review for SEUM: Speedrunners from Hell.
4#+filetags: :gaming:
5
6* Game Description
7[[https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/][SEUM:
8Speedrunners from Hell]] is an incredibly fast-paced mashup of a puzzle
9game and a free-running game. Created by [[https://pinestudio.co][Pine
10Studio]] in early 2016 and first released as a
11[[https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868][demo
12on GameJolt]], this game was successfully green-lit on Steam and has
13amassed a cult following on multiple consoles.
14
15Here's the game description straight from the developers:
16
17#+begin_quote
18Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners
19from Hell is truly hardcore and focuses on speed and fast reaction.
20#+end_quote
21
22* Story
23SEUM does a great job setting the stage when you open the game for the
24first time, playing an animated video in the form of comic book images.
25You see Marty, the protagonist, sitting around drinking his beer as
26Satan busts down the door, cuts his hand off, and steals all his beer
27and bacon. As Satan is leaving, Marty whips a vinyl record across the
28room and cuts off one of Satan's hands. This hand is what allows you to
29use all the powers in the game.
30
31Check out the screenshot below for one of the first panels of the
32storyline:
33
34#+caption: SEUM Story
35[[https://img.cleberg.net/blog/20210101-seum/seum_story.png]]
36
37* Gameplay
38To accomplish each level, you will need to get to the final blue portal
39under a certain time limit. You can beat a level by getting to the
40portal before the time meter runs out or "Dominate" a level by beating
41it within a shorter time limit (noted by a bright red/orange color in
42the time meter).
43
44The portal is usually set behind locked gates, protected by dangerous
45defenses, or in hard-to-reach places. To reach the portal, you'll need
46to go through any existing orange portals, light all yellow beacons,
47avoid things like fireballs and blades, or use any satanic power orbs
48lying around. These special abilities include:
49
50- Gravity
51- Teleport
52- Rewind
53- Spawn platform
54- Roar (DLC)
55- Rocket (DLC)
56- Shadow world (DLC)
57
58For the main storyline, there are nine floors to beat. Each floor
59contains nine regular levels, one boss level, and one bonus level;
60although you don't technically need to beat all levels to advance to the
61next floor.
62
63#+caption: SEUM Floor Menu
64[[https://img.cleberg.net/blog/20210101-seum/seum_floor.png]]
65
66* In-Game Screenshots
67The main menu gives you plenty of options for tuning your system,
68playing main levels, playing the DLC, or exploring the other game modes.
69
70#+caption: SEUM Main Menu
71[[https://img.cleberg.net/blog/20210101-seum/seum_menu.png]]
72
73Once you enter a level, you can look around and strategize before
74starting. Clicking any button will start the menu, and you'll have to
75restart if you die. One of the great things about SEUM is that it has
76great keyboard shortcuts. You can quickly smash =R= to restart the level
77or =M= to return to the level menu.
78
79#+caption: SEUM Level
80[[https://img.cleberg.net/blog/20210101-seum/seum_level.png]]
81
82Once you're able to win a level, you'll see the high scores and can
83watch replays of the best scores.
84
85#+caption: SEUM Win
86[[https://img.cleberg.net/blog/20210101-seum/seum_win.png]]
87
88Each regular level contains a beer in a disguised location that may take
89some out-of-the-box thinking.
90
91#+caption: SEUM Beer
92[[https://img.cleberg.net/blog/20210101-seum/seum_beer.png]]
content/blog/2021-01-04-fediverse.org → content/blog/2021-01-04-fediverse.md renamed +56 −41
@@ -1,9 +1,12 @@
1#+title: A Simple Guide to the Fediverse 1+++
2#+date: 2021-01-04 2date = 2021-01-04
3#+description: Learn about the basics of the Fediverse. 3title = "A Simple Guide to the Fediverse"
4#+filetags: :social: 4description = ""
5draft = false
6+++
7
8# What is the Fediverse?
5 9
6* What is the Fediverse?
7The fediverse is a federated universe of servers commonly used for 10The fediverse is a federated universe of servers commonly used for
8sharing content, like social media. So, instead of having to rely on a 11sharing content, like social media. So, instead of having to rely on a
9single organization to run the server (e.g. Facebook), the fediverse is 12single organization to run the server (e.g. Facebook), the fediverse is
@@ -20,26 +23,27 @@ and communicate back and forth without having to be on the same server.
20However, responsible email admins are there to set rules and control the 23However, responsible email admins are there to set rules and control the
21traffic going in/out of the server. 24traffic going in/out of the server.
22 25
23#+caption: Federated services diagram 26![Federated services
24[[https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg]] 27diagram](https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg)
25 28
26The main objective of this architecture is to decentralize the control 29The main objective of this architecture is to decentralize the control
27within the internet connections. For example, if you run your own 30within the internet connections. For example, if you run your own
28Mastodon instance, you and your users can't be censored or impacted in 31Mastodon instance, you and your users can\'t be censored or impacted in
29any way by authorities of another Mastodon instance. Some users have 32any way by authorities of another Mastodon instance. Some users have
30praised these features due to recent criticism of popular social media 33praised these features due to recent criticism of popular social media
31websites that may be over-censoring their users. 34websites that may be over-censoring their users.
32 35
33This strategy is great for making sure control of the social web isn't 36This strategy is great for making sure control of the social web isn\'t
34controlled by a single organization, but it also has some downsides. If 37controlled by a single organization, but it also has some downsides. If
35I create a Mastodon instance and get a ton of users to sign up, I can 38I create a Mastodon instance and get a ton of users to sign up, I can
36shut the server down at any time. That means you're at risk of losing 39shut the server down at any time. That means you\'re at risk of losing
37the content you've created unless you back it up, or the server backs it 40the content you\'ve created unless you back it up, or the server backs
38up for you. Also, depending on the software used (e.g. Mastodon, 41it up for you. Also, depending on the software used (e.g. Mastodon,
39Pixelfed, etc.), censorship may still be an issue if the server admins 42Pixelfed, etc.), censorship may still be an issue if the server admins
40decide they want to censor their users. Now, censorship isn't always a 43decide they want to censor their users. Now, censorship isn\'t always a
41bad thing and can even benefit the community as a whole, but you'll want 44bad thing and can even benefit the community as a whole, but you\'ll
42to determine which servers align with your idea of proper censorship. 45want to determine which servers align with your idea of proper
46censorship.
43 47
44However, these are risks that we take when we sign up for any online 48However, these are risks that we take when we sign up for any online
45platform. Whatever your reason is for trying out federated social 49platform. Whatever your reason is for trying out federated social
@@ -49,44 +53,55 @@ with the increased difficulty understanding and signing up for these
49platforms. Perhaps increased regulation and litigation against current 53platforms. Perhaps increased regulation and litigation against current
50social media sites will push more users into the fediverse. 54social media sites will push more users into the fediverse.
51 55
52* Federated Alternatives to Popular Sites 56# Federated Alternatives to Popular Sites
57
53The list below is a small guide that will show you federated 58The list below is a small guide that will show you federated
54alternatives to current popular websites. There are many more out there, 59alternatives to current popular websites. There are many more out there,
55so go and explore: you might just find the perfect home. 60so go and explore: you might just find the perfect home.
56 61
57** Reddit 62## Reddit
58- [[https://lemmy.ml/instances][Lemmy]] 63
64- [Lemmy](https://lemmy.ml/instances)
65
66## Twitter/Facebook/Tumblr
67
68- [Mastodon](https://joinmastodon.org)
69- [Diaspora](https://diasporafoundation.org)
70- [Friendica](https://friendi.ca)
71- [GNU Social](https://gnusocial.network)
72- [Pleroma](https://pleroma.social)
73
74## Instagram
75
76- [Pixelfed](https://pixelfed.org)
77
78## Slack/Discord
79
80- [Matrix](https://element.io)
81
82## Youtube/Vimeo
83
84- [Peertube](https://joinpeertube.org)
59 85
60** Twitter/Facebook/Tumblr 86## Spotify/Soundcloud
61- [[https://joinmastodon.org][Mastodon]]
62- [[https://diasporafoundation.org][Diaspora]]
63- [[https://friendi.ca][Friendica]]
64- [[https://gnusocial.network][GNU Social]]
65- [[https://pleroma.social][Pleroma]]
66 87
67** Instagram 88- [Funkwhale](https://funkwhale.audio)
68- [[https://pixelfed.org][Pixelfed]]
69 89
70** Slack/Discord 90## Podcasting
71- [[https://element.io][Matrix]]
72 91
73** Youtube/Vimeo 92- [Pubcast](https://pubcast.pub)
74- [[https://joinpeertube.org][Peertube]]
75 93
76** Spotify/Soundcloud 94## Medium/Blogger
77- [[https://funkwhale.audio][Funkwhale]]
78 95
79** Podcasting 96- [WriteFreely](https://writefreely.org)
80- [[https://pubcast.pub][Pubcast]]
81 97
82** Medium/Blogger 98# Get Started
83- [[https://writefreely.org][WriteFreely]]
84 99
85* Get Started
86The best way to get started is to simply sign up and learn as you go. If 100The best way to get started is to simply sign up and learn as you go. If
87you're comfortable signing up through a Mastodon, Pleroma, or Friendica 101you\'re comfortable signing up through a Mastodon, Pleroma, or Friendica
88server, here is [[https://fediverse.party/en/portal/servers][a list of 102server, here is [a list of themed
89themed servers]] to choose from. If you're looking for something else, 103servers](https://fediverse.party/en/portal/servers) to choose from. If
90try a web search for a federated alternative to your favorite sites. 104you\'re looking for something else, try a web search for a federated
105alternative to your favorite sites.
91 106
92Find a server that focuses on your passions and start there! 107Find a server that focuses on your passions and start there!
content/blog/2021-01-07-ufw.org → content/blog/2021-01-07-ufw.md renamed +96 −84
@@ -1,152 +1,163 @@
1#+title: Secure Your Network with the Uncomplicated Firewall (ufw) 1+++
2#+date: 2021-01-07 2date = 2021-01-07
3#+description: A simple guide to the UFW. 3title = "Secure Your Network with the Uncomplicated Firewall (ufw)"
4#+filetags: :sysadmin: 4description = ""
5draft = false
6+++
7
8# Uncomplicated Firewall
5 9
6* Uncomplicated Firewall
7Uncomplicated Firewall, also known as ufw, is a convenient and 10Uncomplicated Firewall, also known as ufw, is a convenient and
8beginner-friendly way to enforce OS-level firewall rules. For those who 11beginner-friendly way to enforce OS-level firewall rules. For those who
9are hosting servers or any device that is accessible to the world (i.e., 12are hosting servers or any device that is accessible to the world (i.e.,
10by public IP or domain name), it's critical that a firewall is properly 13by public IP or domain name), it\'s critical that a firewall is properly
11implemented and active. 14implemented and active.
12 15
13Ufw is available by default in all Ubuntu installations after 8.04 LTS. 16Ufw is available by default in all Ubuntu installations after 8.04 LTS.
14For other distributions, you can look to install ufw or check if there 17For other distributions, you can look to install ufw or check if there
15are alternative firewalls installed already. There are usually 18are alternative firewalls installed already. There are usually
16alternatives available, such as Fedora's =firewall= and the package 19alternatives available, such as Fedora\'s `firewall` and the
17available on most distributions: =iptables=. Ufw is considered a 20package available on most distributions: `iptables`. Ufw is
18beginner-friendly front-end to iptables. 21considered a beginner-friendly front-end to iptables.
19 22
20[[https://gufw.org][Gufw]] is available as a graphical user interface 23[Gufw](https://gufw.org) is available as a graphical user interface
21(GUI) application for users who are uncomfortable setting up a firewall 24(GUI) application for users who are uncomfortable setting up a firewall
22through a terminal. 25through a terminal.
23 26
24#+caption: Gufw Screenshot 27![Gufw
25[[https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png]] 28Screenshot](https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png)
29
30# Getting Help
26 31
27* Getting Help
28If you need help figuring out commands, remember that you can run the 32If you need help figuring out commands, remember that you can run the
29=--help= flag to get a list of options. 33`--help` flag to get a list of options.
30 34
31#+begin_src sh 35```sh
32sudo ufw --help 36sudo ufw --help
33#+end_src 37```
38
39# Set Default State
34 40
35* Set Default State
36The proper way to run a firewall is to set a strict default state and 41The 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 42slowly open up ports that you want to allow. This helps prevent anything
38malicious from slipping through the cracks. The following command 43malicious from slipping through the cracks. The following command
39prevents all incoming traffic (other than the rules we specify later), 44prevents all incoming traffic (other than the rules we specify later),
40but you can also set this for outgoing connections, if necessary. 45but you can also set this for outgoing connections, if necessary.
41 46
42#+begin_src sh 47```sh
43sudo ufw default deny incoming 48sudo ufw default deny incoming
44#+end_src 49```
45 50
46You should also allow outgoing traffic if you want to allow the device 51You should also allow outgoing traffic if you want to allow the device
47to communicate back to you or other parties. For example, media servers 52to 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 53like Plex need to be able to send out data related to streaming the
49media. 54media.
50 55
51#+begin_src sh 56```sh
52sudo ufw default allow outgoing 57sudo ufw default allow outgoing
53#+end_src 58```
54 59
55* Adding Port Rules 60# Adding Port Rules
56Now that we've disabled all incoming traffic by default, we need to open
57up some ports (or else no traffic would be able to come in). If you need
58to be able to =ssh= into the machine, you'll need to open up port 22.
59 61
60#+begin_src sh 62Now that we\'ve disabled all incoming traffic by default, we need to
63open up some ports (or else no traffic would be able to come in). If you
64need to be able to `ssh` into the machine, you\'ll need to
65open up port 22.
66
67```sh
61sudo ufw allow 22 68sudo ufw allow 22
62#+end_src 69```
63 70
64You can also issue more restrictive rules. The following rule will allow 71You can also issue more restrictive rules. The following rule will allow
65=ssh= connections only from machines on the local subnet. 72`ssh` connections only from machines on the local subnet.
66 73
67#+begin_src sh 74```sh
68sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22 75sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22
69#+end_src 76```
70 77
71If you need to set a rule that isn't tcp, just append your connection 78If you need to set a rule that isn\'t tcp, just append your connection
72type to the end of the rule. 79type to the end of the rule.
73 80
74#+begin_src sh 81```sh
75sudo ufw allow 1900/udp 82sudo ufw allow 1900/udp
76#+end_src 83```
84
85# Enable ufw
77 86
78* Enable ufw
79Now that the firewall is configured and ready to go, you can enable the 87Now that the firewall is configured and ready to go, you can enable the
80firewall. 88firewall.
81 89
82#+begin_src sh 90```sh
83sudo ufw enable 91sudo ufw enable
84#+end_src 92```
85 93
86A restart may be required for the firewall to begin operating. 94A restart may be required for the firewall to begin operating.
87 95
88#+begin_src sh 96```sh
89sudo reboot now 97sudo reboot now
90#+end_src 98```
91 99
92* Checking Status 100# Checking Status
93Now that the firewall is enabled, let's check and see what the rules 101
102Now that the firewall is enabled, let\'s check and see what the rules
94look like. 103look like.
95 104
96#+begin_src sh 105```sh
97sudo ufw status numbered 106sudo ufw status numbered
98#+end_src 107```
99 108
100#+begin_src txt 109``` txt
101Status: active 110Status: active
102 111
103 To Action From 112 To Action From
104 -- ------ ---- 113 -- ------ ----
105[ 1] 22 ALLOW IN Anywhere 114[ 1] 22 ALLOW IN Anywhere
106[ 2] 22 (v6) ALLOW IN Anywhere (v6) 115[ 2] 22 (v6) ALLOW IN Anywhere (v6)
107#+end_src 116```
117
118# Deleting Rules
108 119
109* Deleting Rules
110If you need to delete a rule, you need to know the number associated 120If you need to delete a rule, you need to know the number associated
111with that rule. Let's delete the first rule in the table above. You'll 121with that rule. Let\'s delete the first rule in the table above. You\'ll
112be asked to confirm the deletion as part of this process. 122be asked to confirm the deletion as part of this process.
113 123
114#+begin_src sh 124```sh
115sudo ufw delete 1 125sudo ufw delete 1
116#+end_src 126```
117 127
118* Managing App Rules 128# Managing App Rules
119Luckily, there's a convenient way for installed applications to create 129
120files that ufw can easily implement so that you don't have to search and 130Luckily, there\'s a convenient way for installed applications to create
121find which ports your application requires. To see if your device has 131files that ufw can easily implement so that you don\'t have to search
122any applications with pre-installed ufw rules, execute the following 132and find which ports your application requires. To see if your device
133has any applications with pre-installed ufw rules, execute the following
123command: 134command:
124 135
125#+begin_src sh 136```sh
126sudo ufw app list 137sudo ufw app list
127#+end_src 138```
128 139
129The results should look something like this: 140The results should look something like this:
130 141
131#+begin_src txt 142``` txt
132Available applications: 143Available applications:
133 OpenSSH 144 OpenSSH
134 Samba 145 Samba
135 plexmediaserver 146 plexmediaserver
136 plexmediaserver-all 147 plexmediaserver-all
137 plexmediaserver-dlna 148 plexmediaserver-dlna
138#+end_src 149```
139 150
140If you want to get more information on a specific app rule, use the 151If you want to get more information on a specific app rule, use the
141=info= command. 152`info` command.
142 153
143#+begin_src sh 154```sh
144sudo ufw app info plexmediaserver-dlna 155sudo ufw app info plexmediaserver-dlna
145#+end_src 156```
146 157
147You'll get a blurb of info back like this: 158You\'ll get a blurb of info back like this:
148 159
149#+begin_src txt 160``` txt
150Profile: plexmediaserver-dlna 161Profile: plexmediaserver-dlna
151Title: Plex Media Server (DLNA) 162Title: Plex Media Server (DLNA)
152Description: The Plex Media Server (additional DLNA capability only) 163Description: The Plex Media Server (additional DLNA capability only)
@@ -154,28 +165,29 @@ Description: The Plex Media Server (additional DLNA capability only)
154Ports: 165Ports:
155 1900/udp 166 1900/udp
156 32469/tcp 167 32469/tcp
157#+end_src 168```
158 169
159You can add or delete app rules the same way that you'd add or delete 170You can add or delete app rules the same way that you\'d add or delete
160specific port rules. 171specific port rules.
161 172
162#+begin_src sh 173```sh
163sudo ufw allow plexmediaserver-dlna 174sudo ufw allow plexmediaserver-dlna
164#+end_src 175```
165 176
166#+begin_src sh 177```sh
167sudo ufw delete RULE|NUM 178sudo ufw delete RULE|NUM
168#+end_src 179```
180
181# Creating App Rules
169 182
170* Creating App Rules 183If you\'d like to create you own app rule, you\'ll need to create a file
171If you'd like to create you own app rule, you'll need to create a file 184in the `/etc/ufw/applications.d` directory. Within the file
172in the =/etc/ufw/applications.d= directory. Within the file you create, 185you create, you need to make sure the content is properly formatted.
173you need to make sure the content is properly formatted.
174 186
175For example, here are the contents my =plexmediaserver= file, which 187For example, here are the contents my `plexmediaserver` file,
176creates three distinct app rules for ufw: 188which creates three distinct app rules for ufw:
177 189
178#+begin_src config 190``` config
179[plexmediaserver] 191[plexmediaserver]
180title=Plex Media Server (Standard) 192title=Plex Media Server (Standard)
181description=The Plex Media Server 193description=The Plex Media Server
@@ -190,24 +202,24 @@ ports=1900/udp|32469/tcp
190title=Plex Media Server (Standard + DLNA) 202title=Plex Media Server (Standard + DLNA)
191description=The Plex Media Server (with additional DLNA capability) 203description=The Plex Media Server (with additional DLNA capability)
192ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp 204ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp
193#+end_src 205```
194 206
195So, if I wanted to create a custom app rule called "mycustomrule," I'd 207So, if I wanted to create a custom app rule called \"mycustomrule,\"
196create a file and add my content like this: 208I\'d create a file and add my content like this:
197 209
198#+begin_src sh 210```sh
199sudo nano /etc/ufw/applications.d/mycustomrule 211sudo nano /etc/ufw/applications.d/mycustomrule
200#+end_src 212```
201 213
202#+begin_src config 214``` config
203[mycustomrule] 215[mycustomrule]
204title=My Custom Rule 216title=My Custom Rule
205description=This is a temporary ufw app rule. 217description=This is a temporary ufw app rule.
206ports=88/tcp|9100/udp 218ports=88/tcp|9100/udp
207#+end_src 219```
208 220
209Then, I would just enable this rule in ufw. 221Then, I would just enable this rule in ufw.
210 222
211#+begin_src sh 223```sh
212sudo ufw allow mycustomrule 224sudo ufw allow mycustomrule
213#+end_src 225```
content/blog/2021-02-19-macos.md added +215
@@ -0,0 +1,215 @@
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
11Linux, I have experienced macOS for the first time. By chance, my spouse
12happened to buy a new MacBook and gifted me their 2013 model. Of course,
13I still consider my Linux desktop to be my daily driver and keep Windows
14around for gaming needs, but over the past week I\'ve found myself using
15the MacBook more and more for things that don\'t require gaming specs or
16advanced dev tools.
17
18# Initial Thoughts
19
20Before I move on to the technical aspects of my set-up, I want to take
21some time and express my thoughts on the overall OS.
22
23![macOS
24Desktop](https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png)
25
26As expected, the initial computer setup is a breeze with Mac\'s guided
27GUI installer.
28
29The desktop itself reminds me of GNOME more than anything else I\'ve
30seen: even Pantheon from [ElementaryOS](https://elementary.io/), which
31people commonly refer to as the closest Linux distro to macOS. The
32desktop toolbar is great and far surpasses the utility of the GNOME
33toolbar due to the fact that the extensions and icons *actually work*. I
34launch macOS and immediately see my shortcuts for Tresorit, Bitwarden,
35and Mullvad pop up as the computer loads.
36
37Even further, the app dock is very useful and will be yet another
38familiarity for GNOME users. I know many people like panels instead of
39docks, but I\'ve always found docks to have a more pleasing UI. However,
40I had to disable the \"Show recent applications in Dock\" preference; I
41can\'t stand items taking up precious screen space if I\'m not currently
42using them. On that same note, it\'s taking me some time to get use to
43the fact that I have to manually quit an app or else it will still stay
44open/active in the dock, even if I\'ve closed out all windows for that
45app (e.g. Firefox).
46
47Overall, I\'m having a lot of fun and for users who spend a large
48majority of their time performing basic tasks like web browsing,
49writing, watching media, etc., macOS is a fantastic option.
50
51The rest of this post explains the technicalities of how I set up my CLI
52environment to make me feel more at-home, similar to the environments I
53set up on Fedora, Ubuntu, etc.
54
55# Making it Feel Like Home
56
57If you\'re someone who uses Linux primarily, no doubt your first thought
58when booting macOS will be the same as mine was: \"Where is the terminal
59and how do I set up my favorite utilities?\"
60
61Luckily, macOS hasn\'t completely hidden away the development tools from
62the average user. You can easily find the Terminal app in the Launchpad
63area, but it\'s probably not what you\'re used to. I was surprised (and
64happy) to see that the default shell is `zsh`, the shell I
65use on all of my Linux distros. However, the commands are not the same -
66even the ones you may think are native to the shell. Commands like
67`dir` do not exist, so other native commands like
68`ls -la` or `pwd` are more useful here.
69
70With only a few minutes of installing and tweaking a few packages, I was
71able to recreate a terminal environment that I feel very comfortable
72using. See the image below for a preview of the iTerm2 app with a split
73view between my macOS desktop shell and an SSH session into my server.
74
75![iTerm2](https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png)
76
77# Xcode
78
79My first step was to search the web for any hints on how to get
80`zsh` back up to the state I like, with extensions, themes,
81etc. My first step was to install the CLI tools for
82[Xcode](https://developer.apple.com/xcode/), Apple\'s suite of
83development tools.
84
85```sh
86sudo xcode-select -r
87```
88
89```sh
90sudo xcode-select --install
91```
92
93# Homebrew
94
95Next up is to install [Homebrew](https://brew.sh), a nifty package
96manager for macOS.
97
98```sh
99/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
100```
101
102I ran into a permission error when installing Homebrew:
103
104```sh
105Error: Failed to link all completions, docs and manpages:
106 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
107Failed during: /usr/local/bin/brew update --force --quiet
108```
109
110I found that the following permission modification worked like a charm.
111However, I noted that some users online discussed the fact that this
112solution may not work if your system has multiple users who use
113Homebrew.
114
115```sh
116sudo chown -R $(whoami) $(brew --prefix)/*
117```
118
119Next up is to ensure Homebrew is updated and cleaned.
120
121```sh
122brew update
123```
124
125```sh
126brew cleanup
127```
128
129# iTerm2
130
131Now that I\'ve installed the basic utilities for development, I moved
132onto installing iTerm2, a much better terminal than the default.
133
134```sh
135brew install --cask iterm2
136```
137
138I also used the `Make iTerm2 Default Term` and
139`Install Shell Integration` options in the iTerm2 application
140menu to make sure I don\'t run into any issues later on with different
141terminals.
142
143We will also install `zsh` so we can use it in iTerm2.
144
145```sh
146brew install zsh
147```
148
149# Oh-My-Zsh
150
151I\'ve shown the great aspects of [Oh My Zsh](https://ohmyz.sh) in other
152blog posts, so I\'ll skip over that speech for now. Simply install it
153and run an update.
154
155```sh
156sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
157```
158
159```sh
160omz update
161```
162
163Finally, restart the iTerm2 application to ensure all changes go into
164effect.
165
166# Oh-My-Zsh Themes
167
168Let\'s change the theme of the terminal to make it a little more
169friendly.
170
171```sh
172open ~/.zshrc
173```
174
175The third section of this file should contain a line like the code
176below. Change that theme to [any theme you
177want](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes), save the file,
178and exit.
179
180```sh
181ZSH_THEME="af-magic"
182```
183
184After changing the `.zshrc` file, you\'ll need to close your
185terminal and re-open it to see the changes. Optionally, just open a new
186tab if you\'re using iTerm2, and you\'ll see the new shell config.
187
188# Oh-My-Zsh Plugins
189
190Of course, my customization of `zsh` would not be complete
191without
192[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions).
193This will bring up commands you\'ve run in the past as you type them.
194For example, if you\'ve run `ssh user@192.168.1.99` before,
195the terminal will show this command as soon as you start typing it (e.g.
196`zsh u`), and you can hit the right arrow to autocomplete the
197command.
198
199```sh
200git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
201```
202
203```sh
204open ~/.zshrc
205```
206
207```sh
208# Scroll down the script and edit this line to add zsh-autosuggestions
209plugins=(git zsh-autosuggestions)
210```
211
212Remember: After changing the `.zshrc` file, you\'ll need to
213close your terminal and re-open it to see the changes. Optionally, just
214open a new tab if you\'re using iTerm2, and you\'ll see the new shell
215config.
content/blog/2021-02-19-macos.org deleted −200
@@ -1,200 +0,0 @@
1#+title: macOS: Testing Out A New OS
2#+date: 2021-02-19
3#+description: A retrospective on my migration from Linux to macOS.
4#+filetags: :apple:
5
6* Diving into macOS
7After spending nearly 15 years working with Windows and 8 years on
8Linux, I have experienced macOS for the first time. By chance, my spouse
9happened to buy a new MacBook and gifted me their 2013 model. Of course,
10I still consider my Linux desktop to be my daily driver and keep Windows
11around for gaming needs, but over the past week I've found myself using
12the MacBook more and more for things that don't require gaming specs or
13advanced dev tools.
14
15* Initial Thoughts
16Before I move on to the technical aspects of my set-up, I want to take
17some time and express my thoughts on the overall OS.
18
19#+caption: macOS Desktop
20[[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png]]
21
22As expected, the initial computer setup is a breeze with Mac's guided
23GUI installer.
24
25The desktop itself reminds me of GNOME more than anything else I've
26seen: even Pantheon from [[https://elementary.io/][ElementaryOS]], which
27people commonly refer to as the closest Linux distro to macOS. The
28desktop toolbar is great and far surpasses the utility of the GNOME
29toolbar due to the fact that the extensions and icons /actually work/. I
30launch macOS and immediately see my shortcuts for Tresorit, Bitwarden,
31and Mullvad pop up as the computer loads.
32
33Even further, the app dock is very useful and will be yet another
34familiarity for GNOME users. I know many people like panels instead of
35docks, but I've always found docks to have a more pleasing UI. However,
36I had to disable the "Show recent applications in Dock" preference; I
37can't stand items taking up precious screen space if I'm not currently
38using them. On that same note, it's taking me some time to get use to
39the fact that I have to manually quit an app or else it will still stay
40open/active in the dock, even if I've closed out all windows for that
41app (e.g. Firefox).
42
43Overall, I'm having a lot of fun and for users who spend a large
44majority of their time performing basic tasks like web browsing,
45writing, watching media, etc., macOS is a fantastic option.
46
47The rest of this post explains the technicalities of how I set up my CLI
48environment to make me feel more at-home, similar to the environments I
49set up on Fedora, Ubuntu, etc.
50
51* Making it Feel Like Home
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#+caption: iTerm2
70[[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png]]
71
72* Xcode
73My first step was to search the web for any hints on how to get =zsh=
74back up to the state I like, with extensions, themes, etc. My first step
75was to install the CLI tools for
76[[https://developer.apple.com/xcode/][Xcode]], Apple's suite of
77development tools.
78
79#+begin_src sh
80sudo xcode-select -r
81#+end_src
82
83#+begin_src sh
84sudo xcode-select --install
85#+end_src
86
87* Homebrew
88Next up is to install [[https://brew.sh][Homebrew]], a nifty package
89manager for macOS.
90
91#+begin_src sh
92/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
93#+end_src
94
95I ran into a permission error when installing Homebrew:
96
97#+begin_src sh
98Error: Failed to link all completions, docs and manpages:
99 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
100Failed during: /usr/local/bin/brew update --force --quiet
101#+end_src
102
103I found that the following permission modification worked like a charm.
104However, I noted that some users online discussed the fact that this
105solution may not work if your system has multiple users who use
106Homebrew.
107
108#+begin_src sh
109sudo chown -R $(whoami) $(brew --prefix)/*
110#+end_src
111
112Next up is to ensure Homebrew is updated and cleaned.
113
114#+begin_src sh
115brew update
116#+end_src
117
118#+begin_src sh
119brew cleanup
120#+end_src
121
122* iTerm2
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
141I've shown the great aspects of [[https://ohmyz.sh][Oh My Zsh]] in other
142blog posts, so I'll skip over that speech for now. Simply install it and
143run an update.
144
145#+begin_src sh
146sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
147#+end_src
148
149#+begin_src sh
150omz update
151#+end_src
152
153Finally, restart the iTerm2 application to ensure all changes go into
154effect.
155
156* Oh-My-Zsh Themes
157Let's change the theme of the terminal to make it a little more
158friendly.
159
160#+begin_src sh
161open ~/.zshrc
162#+end_src
163
164The third section of this file should contain a line like the code
165below. Change that theme to
166[[https://github.com/ohmyzsh/ohmyzsh/wiki/Themes][any theme you want]],
167save the file, and exit.
168
169#+begin_src sh
170ZSH_THEME="af-magic"
171#+end_src
172
173After changing the =.zshrc= file, you'll need to close your terminal and
174re-open it to see the changes. Optionally, just open a new tab if you're
175using iTerm2, and you'll see the new shell config.
176
177* Oh-My-Zsh Plugins
178Of course, my customization of =zsh= would not be complete without
179[[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]].
180This will bring up commands you've run in the past as you type them. For
181example, if you've run =ssh user@192.168.1.99= before, the terminal will
182show this command as soon as you start typing it (e.g. =zsh u=), and you
183can hit the right arrow to autocomplete the command.
184
185#+begin_src sh
186git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
187#+end_src
188
189#+begin_src sh
190open ~/.zshrc
191#+end_src
192
193#+begin_src sh
194# Scroll down the script and edit this line to add zsh-autosuggestions
195plugins=(git zsh-autosuggestions)
196#+end_src
197
198Remember: After changing the =.zshrc= file, you'll need to close your
199terminal and re-open it to see the changes. Optionally, just open a new
200tab if you're using iTerm2, and you'll see the new shell config.
content/blog/2021-03-19-clone-github-repos.org → content/blog/2021-03-19-clone-github-repos.md renamed +57 −49
@@ -1,55 +1,60 @@
1#+title: How to Clone All Repositories from a GitHub or Sourcehut Account 1+++
2#+date: 2021-03-19 2date = 2021-03-19
3#+description: Learn how to clone all GitHub or Sourcehut repositories. 3title = "How to Clonse All Repositories from a GitHub or Sourcehut Account"
4#+filetags: :dev: 4description = ""
5draft = false
6+++
5 7
6* Cloning from GitHub 8# Cloning from GitHub
7If you're like me and use a lot of different devices (and sometimes 9
10If you\'re like me and use a lot of different devices (and sometimes
8decide to just wipe your device and start with a new OS), you probably 11decide to just wipe your device and start with a new OS), you probably
9know the pain of cloning all your old code repositories down to your 12know the pain of cloning all your old code repositories down to your
10local file system. 13local file system.
11 14
12If you're using GitHub, you can easily clone all of your code back down 15If you\'re using GitHub, you can easily clone all of your code back down
13in just seconds. First, create a bash script. I do so by opening a new 16in just seconds. First, create a bash script. I do so by opening a new
14file in =nano=, but you can use =gedit=, =vim=, or something else: 17file in `nano`, but you can use `gedit`,
18`vim`, or something else:
15 19
16#+begin_src sh 20```sh
17nano clone_github_repos.sh 21nano clone_github_repos.sh
18#+end_src 22```
19 23
20Next, paste in the following information. Note that you can replace the 24Next, paste in the following information. Note that you can replace the
21word =users= in the first line with =orgs= and type an organization's 25word `users` in the first line with `orgs` and
22name instead of a user's name. 26type an organization\'s name instead of a user\'s name.
23 27
24#+begin_src sh 28```sh
25CNTX=users; NAME=YOUR-USERNAME; PAGE=1 29CNTX=users; NAME=YOUR-USERNAME; PAGE=1
26curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" | 30curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" |
27 grep -e 'git_url*' | 31 grep -e 'git_url*' |
28 cut -d \" -f 4 | 32 cut -d \" -f 4 |
29 xargs -L1 git clone 33 xargs -L1 git clone
30#+end_src 34```
31 35
32Finally, save the bash script and make it executable. 36Finally, save the bash script and make it executable.
33 37
34#+begin_src sh 38```sh
35chmod a+x clone_github_repos.sh 39chmod a+x clone_github_repos.sh
36#+end_src 40```
37 41
38Now you can run the script and should see the cloning process begin. 42Now you can run the script and should see the cloning process begin.
39 43
40#+begin_src sh 44```sh
41./clone_github_repos.sh 45./clone_github_repos.sh
42#+end_src 46```
47
48# Cloning from Sourcehut
43 49
44* Cloning from Sourcehut 50I haven\'t fully figured out how to directly incorporate Sourcehut\'s
45I haven't fully figured out how to directly incorporate Sourcehut's
46GraphQL API into a bash script yet, so this one will take two steps. 51GraphQL API into a bash script yet, so this one will take two steps.
47 52
48First, log-in to Sourcehut and go to their 53First, log-in to Sourcehut and go to their [GraphQL playground for
49[[https://git.sr.ht/graphql][GraphQL playground for Git]]. Next, paste 54Git](https://git.sr.ht/graphql). Next, paste the following query into
50the following query into the left box: 55the left box:
51 56
52#+begin_src sh 57```sh
53query { 58query {
54 me { 59 me {
55 canonicalName 60 canonicalName
@@ -61,27 +66,28 @@ query {
61 } 66 }
62 } 67 }
63} 68}
64#+end_src 69```
65 70
66The output on the right side will give you an object of all your 71The output on the right side will give you an object of all your
67repositories. Just grab that text and remove all the characters such as 72repositories. Just grab that text and remove all the characters such as
68quotation marks and curly brackets. You will need a single-line list of 73quotation marks and curly brackets. You will need a single-line list of
69space-separated values for the next step. 74space-separated values for the next step.
70 75
71Now let's create the bash script: 76Now let\'s create the bash script:
72 77
73#+begin_src sh 78```sh
74nano clone_sourcehut_repos.sh 79nano clone_sourcehut_repos.sh
75#+end_src 80```
76 81
77Next, paste the following bash script in with the list of repositories 82Next, paste the following bash script in with the list of repositories
78you obtained above and replace =your-username= with your username. 83you obtained above and replace `your-username` with your
84username.
79 85
80Note that this uses the SSH-based Git cloning method 86Note that this uses the SSH-based Git cloning method (e.g.
81(e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut 87`git@git...`), so you\'ll need to ensure you have set up
82with your SSH key. 88Sourcehut with your SSH key.
83 89
84#+begin_src sh 90```sh
85repos=(repo1 repo2 repo3) 91repos=(repo1 repo2 repo3)
86 92
87# List all sub-directories in the current directory 93# List all sub-directories in the current directory
@@ -90,35 +96,37 @@ do
90 # Clone 96 # Clone
91 git clone git@git.sr.ht:~your-username/$repo 97 git clone git@git.sr.ht:~your-username/$repo
92done 98done
93#+end_src 99```
94 100
95Finally, save the bash script and make it executable. 101Finally, save the bash script and make it executable.
96 102
97#+begin_src sh 103```sh
98chmod a+x clone_sourcehut_repos.sh 104chmod a+x clone_sourcehut_repos.sh
99#+end_src 105```
100 106
101Now you can run the script and should see the cloning process begin. 107Now you can run the script and should see the cloning process begin.
102 108
103#+begin_src sh 109```sh
104./clone_sourcehut_repos.sh 110./clone_sourcehut_repos.sh
105#+end_src 111```
112
113# Moving Repositories to a New Host
106 114
107* Moving Repositories to a New Host
108Now that you have all of your code repositories cloned to your local 115Now that you have all of your code repositories cloned to your local
109computer, you may want to change the remote host (e.g., moving from 116computer, you may want to change the remote host (e.g., moving from
110GitHub to GitLab). To do this, let's create another bash script: 117GitHub to GitLab). To do this, let\'s create another bash script:
111 118
112#+begin_src sh 119```sh
113nano change_remote_urls.sh 120nano change_remote_urls.sh
114#+end_src 121```
115 122
116Past the following information and be sure to change the URL information 123Past the following information and be sure to change the URL information
117to whichever host you are moving to. For this example, I am looping 124to whichever host you are moving to. For this example, I am looping
118through all of my cloned GitHub directories and changing them to 125through all of my cloned GitHub directories and changing them to
119Sourcehut (e.g. =<YOUR_NEW_REMOTE_URL>= -> =git@git.sr.ht:~myusername=). 126Sourcehut (e.g. `<YOUR_NEW_REMOTE_URL>` -\>
127`git@git.sr.ht:~myusername`).
120 128
121#+begin_src sh 129```sh
122# List all sub-directories in the current directory 130# List all sub-directories in the current directory
123for dir in */ 131for dir in */
124do 132do
@@ -133,16 +141,16 @@ do
133 # Go back to main directory 141 # Go back to main directory
134 cd .. 142 cd ..
135done 143done
136#+end_src 144```
137 145
138Finally, save the bash script and make it executable. 146Finally, save the bash script and make it executable.
139 147
140#+begin_src sh 148```sh
141chmod a+x change_remote_urls.sh 149chmod a+x change_remote_urls.sh
142#+end_src 150```
143 151
144Now you can run the script and should see the cloning process begin. 152Now you can run the script and should see the cloning process begin.
145 153
146#+begin_src sh 154```sh
147./change_remote_urls.sh 155./change_remote_urls.sh
148#+end_src 156```
content/blog/2021-03-28-gemini-capsule.md added +186
@@ -0,0 +1,186 @@
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
11introduced in June 2019 as an alternative to HTTP(S) or Gopher. In
12layman\'s terms, it\'s an alternative way to browse sites (called
13capsules) that requires a special browser. Since Gemini is not
14standardized as an internet standard, normal web browsers won\'t be able
15to load a Gemini capsule. Instead, you\'ll need to use [a
16Gemini-specific
17browser](https://gemini.%20circumlunar.space/clients.html).
18
19The content found within a Gemini page is called
20[Gemtext](https://gemini.circumlunar.space/docs/cheatsheet.gmi) and is
21*extremely* basic (on purpose). Gemini only processes the text, no media
22content like images. However, you\'re able to style 3 levels of
23headings, regular text, links (which will display on their own line),
24quotes, and an unordered list.
25
26Here\'s a complete listing of valid Gemtext:
27
28``` txt
29# Heading 1
30## Heading 2
31### Heading 3
32
33Regular text! Lorem ipsum dolor sit amet.
34
35=> https://example.com My Website
36=> gemini://example.com My Gemini Capsule
37
38> "If life were predictable it would cease to be life, and be without flavor." - Eleanor Roosevelt
39
40My List:
41** Item
42** Item
43
44```Anything between three backticks will be rendered as code.```
45```
46
47### Free Option
48
49There are probably numerous websites that allow you to create your
50personal Gemini capsule, but I\'m going to focus on the two sites that I
51have personally tested. The first option below, Midnight Pub, allows you
52to create/edit any Gemini files you want in your account. This is
53essentially a GUI option with a built-in text box for editing. The
54second option below, Sourcehut, allows you to use a Git repository and
55automatic build process to deploy your personal Gemini capsule every
56time you push a commit.
57
58## Midnight Pub - Beginner Friendly
59
60[Midnight Pub](https://midnight.pub/) is a small, virtual community
61meant to reflect the atmosphere of wandering into a small alley pub. The
62site is built in Gemtext and has a server-side process to convert
63Gemtext to HTML if someone loads the site in an HTTP(S) browser.
64
65To create an account, you\'ll need to email the owner of the website to
66obtain a key. You can find their email on the Midnight Pub homepage.
67Once registered, head to [your account](https://midnight.pub/account)
68and select [manage site](https://midnight.pub/site). This is the screen
69where you can upload or create any files to be displayed on the
70internet.
71
72For example, I\'ve created both an HTML file and a Gemini file. Remember
73that Gemini is automatically converted to HTML on the Pub, so you don\'t
74need an HTML version. For example, I created an HTML version to add in
75some extra styling.
76
77All you need to do is create a page like `index.gmi` and use
78your Gemini browser to head over to your-username.midnight.pub to see
79the result.
80
81That\'s all there is to it! Easy enough, right? Let\'s check out a more
82advanced version in the next section.
83
84# Paid Option
85
86As of 2021, Sourcehut has decided to require users to have a paid
87account in order to utilize their automated build system. For now, paid
88accounts can be as low as \$2/month.
89
90## Sourcehut
91
92[Sourcehut](https://sourcehut.org/) is a collection of software
93development tools, but mostly surrounds their hosted Git repository
94service. Simply put, it\'s a minimal and more private alternative to
95services like GitHub.
96
97This walkthrough is more advanced and involves things like Git, SSH, the
98command line. If you don\'t think you know enough to do this, check out
99my walkthrough on creating a Gemini capsule for the Midnight Pub
100instead.
101
102The first thing you\'ll need to do is create an SSH key pair, if you
103don\'t already have one on your system. Once created, grab the contents
104of `id_rsa.pub` and add it to your Sourcehut account
105settings - this will allow you to push and pull code changes without
106using a username/password.
107
108```sh
109ssh keygen
110```
111
112Next up, let\'s create a repository with the proper name so that the
113Sourcehut build system will know we want them to host a website for us.
114Use the following format exactly:
115
116```sh
117mkdir your-username.srht.site && cd your-username.srht.site
118```
119
120Now that we\'ve created the repo, let\'s initialize Git and add the
121proper remote URL.
122
123```sh
124git init
125```
126
127```sh
128git remote add origin git@git.sr.ht:~your-username/your-username.srht.site
129```
130
131Now that our repository is set up and configured, we will need to create
132at least two files:
133
134- `index.gmi`
135- `.build.yml`
136
137For your `.build.yml` file, use the following content and be
138sure to update the `site` line with your username!
139
140``` yaml
141image: alpine/latest
142oauth: pages.sr.ht/PAGES:RW
143environment:
144 site: your-username.srht.site
145tasks:
146 - package: |
147 cd $site
148 tar -cvz . > ../site.tar.gz
149 - upload: |
150 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
151```
152
153For the `index.gmi` file, put whatever you want in there and
154save it. You could even just copy and paste the Gemtext cheatsheet.
155
156If you want to serve both HTML and Gemini files from this repository,
157just add a second command to the `upload` section:
158
159``` yaml
160- upload: |
161 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
162 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
163```
164
165Lastly, commit your changes and push them to the remote repo.
166
167```sh
168git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD
169```
170
171If you\'ve successfully created the files with the proper format,
172you\'ll see the terminal print a message that lets you know where the
173automatic build is taking place. For example, here\'s what the terminal
174tells me:
175
176```sh
177remote: Build started:
178remote: https://builds.sr.ht/~user/job/689803 [.build.yml]
179```
180
181Now that you\'ve properly built your Sourcehut page, you can browse to
182your-username.srht.site in a Gemini browser and view the final results.
183Take a look at the image below for my Sourcehut Gemini capsule.
184
185![Gemini page on the amfora
186browser](https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png)
content/blog/2021-03-28-gemini-capsule.org deleted −177
@@ -1,177 +0,0 @@
1#+title: Launching a Gemini Capsule
2#+date: 2021-03-28
3#+description: A guide to self-hosting a Gemini capsule on your own server.
4#+filetags: :dev:
5
6* What is Gemini?
7[[https://gemini.circumlunar.space/][Gemini]] is an internet protocol
8introduced in June 2019 as an alternative to HTTP(S) or Gopher. In
9layman's terms, it's an alternative way to browse sites (called
10capsules) that requires a special browser. Since Gemini is not
11standardized as an internet standard, normal web browsers won't be able
12to load a Gemini capsule. Instead, you'll need to use
13[[https://gemini.%20circumlunar.space/clients.html][a Gemini-specific
14browser]].
15
16The content found within a Gemini page is called
17[[https://gemini.circumlunar.space/docs/cheatsheet.gmi][Gemtext]] and is
18/extremely/ basic (on purpose). Gemini only processes the text, no media
19content like images. However, you're able to style 3 levels of headings,
20regular text, links (which will display on their own line), quotes, and
21an unordered list.
22
23Here's a complete listing of valid Gemtext:
24
25#+begin_src 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#+end_src
43
44*** Free Option
45There are probably numerous websites that allow you to create your
46personal Gemini capsule, but I'm going to focus on the two sites that I
47have personally tested. The first option below, Midnight Pub, allows you
48to create/edit any Gemini files you want in your account. This is
49essentially a GUI option with a built-in text box for editing. The
50second option below, Sourcehut, allows you to use a Git repository and
51automatic build process to deploy your personal Gemini capsule every
52time you push a commit.
53
54** Midnight Pub - Beginner Friendly
55[[https://midnight.pub/][Midnight Pub]] is a small, virtual community
56meant to reflect the atmosphere of wandering into a small alley pub. The
57site is built in Gemtext and has a server-side process to convert
58Gemtext to HTML if someone loads the site in an HTTP(S) browser.
59
60To create an account, you'll need to email the owner of the website to
61obtain a key. You can find their email on the Midnight Pub homepage.
62Once registered, head to [[https://midnight.pub/account][your account]]
63and select [[https://midnight.pub/site][manage site]]. This is the
64screen where you can upload or create any files to be displayed on the
65internet.
66
67For example, I've created both an HTML file and a Gemini file. Remember
68that Gemini is automatically converted to HTML on the Pub, so you don't
69need an HTML version. For example, I created an HTML version to add in
70some extra styling.
71
72All you need to do is create a page like =index.gmi= and use your Gemini
73browser to head over to your-username.midnight.pub to see the result.
74
75That's all there is to it! Easy enough, right? Let's check out a more
76advanced version in the next section.
77
78* Paid Option
79As of 2021, Sourcehut has decided to require users to have a paid
80account in order to utilize their automated build system. For now, paid
81accounts can be as low as $2/month.
82
83** Sourcehut
84[[https://sourcehut.org/][Sourcehut]] is a collection of software
85development tools, but mostly surrounds their hosted Git repository
86service. Simply put, it's a minimal and more private alternative to
87services like GitHub.
88
89This walkthrough is more advanced and involves things like Git, SSH, the
90command line. If you don't think you know enough to do this, check out
91my walkthrough on creating a Gemini capsule for the Midnight Pub
92instead.
93
94The first thing you'll need to do is create an SSH key pair, if you
95don't already have one on your system. Once created, grab the contents
96of =id_rsa.pub= and add it to your Sourcehut account settings - this
97will allow you to push and pull code changes without using a
98username/password.
99
100#+begin_src sh
101ssh keygen
102#+end_src
103
104Next up, let's create a repository with the proper name so that the
105Sourcehut build system will know we want them to host a website for us.
106Use the following format exactly:
107
108#+begin_src sh
109mkdir your-username.srht.site && cd your-username.srht.site
110#+end_src
111
112Now that we've created the repo, let's initialize Git and add the proper
113remote URL.
114
115#+begin_src sh
116git init
117#+end_src
118
119#+begin_src sh
120git remote add origin git@git.sr.ht:~your-username/your-username.srht.site
121#+end_src
122
123Now that our repository is set up and configured, we will need to create
124at least two files:
125
126- =index.gmi=
127- =.build.yml=
128
129For your =.build.yml= file, use the following content and be sure to
130update the =site= line with your username!
131
132#+begin_src yaml
133image: alpine/latest
134oauth: pages.sr.ht/PAGES:RW
135environment:
136 site: your-username.srht.site
137tasks:
138 - package: |
139 cd $site
140 tar -cvz . > ../site.tar.gz
141 - upload: |
142 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
143#+end_src
144
145For the =index.gmi= file, put whatever you want in there and save it.
146You could even just copy and paste the Gemtext cheatsheet.
147
148If you want to serve both HTML and Gemini files from this repository,
149just add a second command to the =upload= section:
150
151#+begin_src yaml
152- upload: |
153 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
154 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
155#+end_src
156
157Lastly, commit your changes and push them to the remote repo.
158
159#+begin_src sh
160git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD
161#+end_src
162
163If you've successfully created the files with the proper format, you'll
164see the terminal print a message that lets you know where the automatic
165build is taking place. For example, here's what the terminal tells me:
166
167#+begin_src sh
168remote: Build started:
169remote: https://builds.sr.ht/~user/job/689803 [.build.yml]
170#+end_src
171
172Now that you've properly built your Sourcehut page, you can browse to
173your-username.srht.site in a Gemini browser and view the final results.
174Take a look at the image below for my Sourcehut Gemini capsule.
175
176#+caption: Gemini page on the amfora browser
177[[https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png]]
content/blog/2021-03-28-vaporwave-vs-outrun.md added +133
@@ -0,0 +1,133 @@
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
11tends to be a lot of confusion around what falls into each specific
12genre. This post discusses Vaporwave and Outrun, which have become
13almost synonymous in the minds of those online who aren\'t hardcore into
14these genres of media. More specifically, Outrun is largely an unknown
15term while all aesthetics in these two genres are usually attributed to
16Vaporwave. For example, take a look at the image search results for
17Vaporwave: the results include **a lot\*** of Outrun-themed images.
18You\'ll find a similar trend almost everywhere.
19
20![Vaporwave Search
21Results](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png)
22
23# Vaporwave
24
25![Macintosh Plus Album
26Cover](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png)
27
28Okay, so what is Vaporwave? I\'m going to just copy-and-paste some
29general info from the Wikipedia article on
30[Vaporwave](https://en.wikipedia.org/wiki/Vaporwave), so that I\'m not
31repeating everything you can already search for online:
32
33> Vaporwave is a microgenre of electronic music, a visual art style, and
34> an Internet meme that emerged in the early 2010s. It is defined partly
35> by its slowed-down, chopped and screwed samples of smooth jazz,
36> elevator, R&B, and lounge music from the 1980s and 1990s. The
37> surrounding subculture is sometimes associated with an ambiguous or
38> satirical take on consumer capitalism and pop culture, and tends to be
39> characterized by a nostalgic or surrealist engagement with the popular
40> entertainment, technology and advertising of previous decades.
41> Visually, it incorporates early Internet imagery, late 1990s web
42> design, glitch art, anime, 3D-rendered objects, and cyberpunk tropes
43> in its cover artwork and music videos.
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 [Macintosh
66Plus](https://en.wikipedia.org/wiki/Vektroid), who released the album
67[Floral
68Shoppe](https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete)
69in 2011. Another more recent example is the artist
70[sadbert](https://sadbert.bandcamp.com/), 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![Outrun](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png)
83
84Now, let\'s get to Outrun. This one is a little trickier since the genre
85has largely been lumped under the Vaporwave title for so long. However,
86it stems from the [Synthwave](https://en.wikipedia.org/wiki/Synthwave)
87music genre and is likely named after the 1986 racer game, [Out
88Run](https://en.wikipedia.org/wiki/Out_Run).
89
90## Time Frame
91
92Outrun can be thought of as a retro-futuristic aesthetic born from the
931980s.
94
95## Art
96
97The color scheme uses a very dark color palette with the main colors
98being deep oranges, blues, and purples. Red edges are common around
99objects in Outrun art. The background of the Outrun aesthetic is almost
100always a neon grid like you\'d expect to see in Tron or a 1980s arcade
101machine.
102
103Classic sports cars, chrome robots, computer generated graphics and
104fonts, and the occasional use of rain or palm trees can be found in
105Outrun art.
106
107## Music
108
109This aesthetic has a more aggressive and fast-paced style of music,
110which tends to match the subject of the art in this aesthetic.
111
112Outrun enthusiasts love what people in the 1980s thought the future
113would look like. Take a look at a common video game discussed in Outrun
114circles, Far Cry 3: Blood Dragon:
115
116![Fry Cry 3: Blood
117Dragon](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png)
118
119Another example that doesn\'t force the color scheme as hard as some
120online art does is Kung Fury:
121
122![Kung
123Fury](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png)
124
125![Kung Fury Hacker
126Scene](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png)
127
128# Conclusion
129
130While Vaporwave and Outrun share similarities, they are two distinct
131aesthetics with many important distinctions. Someone who enjoys one may
132not necessarily enjoy the other, so it\'s important to make sure we
133properly describe the aesthetic we\'re looking for.
content/blog/2021-03-28-vaporwave-vs-outrun.org deleted −124
@@ -1,124 +0,0 @@
1#+title: Vaporwave vs Outrun
2#+date: 2021-03-28
3#+description: Learn the differences between the vaporwave and Outrun aesthetics.
4#+filetags: :personal:
5
6* Overview
7When it comes to an aesthetic that originated primarily online, there
8tends to be a lot of confusion around what falls into each specific
9genre. This post discusses Vaporwave and Outrun, which have become
10almost synonymous in the minds of those online who aren't hardcore into
11these genres of media. More specifically, Outrun is largely an unknown
12term while all aesthetics in these two genres are usually attributed to
13Vaporwave. For example, take a look at the image search results for
14Vaporwave: the results include *a lot** of Outrun-themed images. You'll
15find a similar trend almost everywhere.
16
17#+caption: Vaporwave Search Results
18[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png]]
19
20* Vaporwave
21#+caption: Macintosh Plus Album Cover
22[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png]]
23
24Okay, so what is Vaporwave? I'm going to just copy-and-paste some
25general info from the Wikipedia article on
26[[https://en.wikipedia.org/wiki/Vaporwave][Vaporwave]], so that I'm not
27repeating everything you can already search for online:
28
29#+begin_quote
30Vaporwave is a microgenre of electronic music, a visual art style, and
31an Internet meme that emerged in the early 2010s. It is defined partly
32by its slowed-down, chopped and screwed samples of smooth jazz,
33elevator, R&B, and lounge music from the 1980s and 1990s. The
34surrounding subculture is sometimes associated with an ambiguous or
35satirical take on consumer capitalism and pop culture, and tends to be
36characterized by a nostalgic or surrealist engagement with the popular
37entertainment, technology and advertising of previous decades. Visually,
38it incorporates early Internet imagery, late 1990s web design, glitch
39art, anime, 3D-rendered objects, and cyberpunk tropes in its cover
40artwork and music videos.
41#+end_quote
42
43This is an excellent summary, and it helps address my point here: there
44are specific aspects that make Vaporwave unique:
45
46** Time Frame
47The time frame for references, logos, etc. focuses mostly on the 1990s
48in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens,
49tropical plants, classic marble sculptures, and many references from
50Japan's influence in the 90s.
51
52** Art
53The color scheme is generally a soft, light palette that uses pastel
54colors. The backdrop will often be in a light-pink or teal blue.
55
56** Music
57The musical genre of Vaporwave incorporates soft tunes, lounge music,
58and sound effects that will make the listener reminisce of the 90s. The
59sounds of the genre are generally slower-paced and calming. The major
60breakthrough artist for Vaporwave was
61[[https://en.wikipedia.org/wiki/Vektroid][Macintosh Plus]], who released
62the album
63[[https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete][Floral
64Shoppe]] in 2011. Another more recent example is the artist
65[[https://sadbert.bandcamp.com/][sadbert]], whose latest album
66incorporates the upbeat tones of the 1999 Dilbert TV series.
67
68Notice that Vaporwave doesn't include things like racing cars,
69futuristic technology, chrome, or the deep orange/purple color scheme.
70Vaporwave is a focus on the idyllic state of the world when technology
71was becoming common in households, a reality that we have already
72experienced. Focus on the most aesthetically-pleasing parts of that past
73is a large part of Vaporwave.
74
75* Outrun
76#+caption: Outrun
77[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png]]
78
79Now, let's get to Outrun. This one is a little trickier since the genre
80has largely been lumped under the Vaporwave title for so long. However,
81it stems from the [[https://en.wikipedia.org/wiki/Synthwave][Synthwave]]
82music genre and is likely named after the 1986 racer game,
83[[https://en.wikipedia.org/wiki/Out_Run][Out Run]].
84
85** Time Frame
86Outrun can be thought of as a retro-futuristic aesthetic born from the
871980s.
88
89** Art
90The color scheme uses a very dark color palette with the main colors
91being deep oranges, blues, and purples. Red edges are common around
92objects in Outrun art. The background of the Outrun aesthetic is almost
93always a neon grid like you'd expect to see in Tron or a 1980s arcade
94machine.
95
96Classic sports cars, chrome robots, computer generated graphics and
97fonts, and the occasional use of rain or palm trees can be found in
98Outrun art.
99
100** Music
101This aesthetic has a more aggressive and fast-paced style of music,
102which tends to match the subject of the art in this aesthetic.
103
104Outrun enthusiasts love what people in the 1980s thought the future
105would look like. Take a look at a common video game discussed in Outrun
106circles, Far Cry 3: Blood Dragon:
107
108#+caption: Fry Cry 3: Blood Dragon
109[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png]]
110
111Another example that doesn't force the color scheme as hard as some
112online art does is Kung Fury:
113
114#+caption: Kung Fury
115[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png]]
116
117#+caption: Kung Fury Hacker Scene
118[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png]]
119
120* Conclusion
121While Vaporwave and Outrun share similarities, they are two distinct
122aesthetics with many important distinctions. Someone who enjoys one may
123not necessarily enjoy the other, so it's important to make sure we
124properly describe the aesthetic we're looking for.
content/blog/2021-03-30-vps-web-server.md added +418
@@ -0,0 +1,418 @@
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
11decisions for beginner web developers. Even for experienced web devs,
12choosing between different forms of web hosting can be a daunting
13choice.
14
15First, let\'s take a look at [shared web
16hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service).
17Shared web hosting is a product where you are purchasing a small piece
18of a web server that is being shared between many websites. As a result,
19the cost of shared hosting is extremely low. You won\'t have access to
20the server itself, which means you can\'t install your own software on
21the server, such as Docker. Usually, you are simply allowed to connect
22your domains to the server, set up domain security, and other small
23utilities.
24
25In contrast, a [virtual private
26server](https://en.wikipedia.org/wiki/Virtual_private_server) (VPS) is a
27virtual machine that replicates the environment of having a dedicated
28server to yourself. You are able to control the virtual server\'s host
29names, base file system, package manager, etc. Another great upside of a
30VPS is that since it\'s virtual, the company providing the VPS can
31dynamically increase the disk size, RAM size, or number of CPUs at any
32time. However, the virtual server is still physically located on a
33server that is shared between multiple virtual servers.
34
35The choice between shared hosting and VPS mostly depends on your skill
36level with system administration. If you\'re comforting working on a
37server that is mostly left up to you (or you\'re willing to learn), then
38a VPS is usually a better option. However, shared hosting is a fantastic
39option for people who don\'t want to have to learn how to manage their
40server.
41
42# My Situation
43
44I had used shared hosting for approximately 5 years before trying my
45first VPS. I manage a homelab and have had success running a server and
46performing typical sysadmin duties, but I was still hesitant to get a
47VPS. One fear was that I always struggled to properly set up the
48networking part of a server - DNS and hostname configurations were not
49my friend.
50
51As a little bit of background, I originally used
52[Siteground](https://www.siteground.com) for my initially shared hosting
53and stayed on that platform for at least a year. However, the UI was
54clunky, and I didn\'t like how they handled certain technical aspects,
55so I switched to [Namecheap](https://www.namecheap.com). Namecheap was
56great because it is the service I primarily use for purchasing domain
57names, which made it incredibly easy to link them to my hosting service.
58However, it was still mediocre shared hosting, and Namecheap is
59notorious for not letting you use [Let\'s
60Encrypt](https://letsencrypt.org) to obtain free SSL/TLS certificates;
61Namecheap wants to make you purchase certificates through their store.
62
63Finally, I settled down with [iWebFusion](https://www.iwebfusion.net)
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[Postmill](https://postmill.xyz) or [Matrix](https://matrix.org). This
70is possible with a VPS, so I decided to grab a new domain name to try it
71out.
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 [1984](https://1984hosting.com) and
77prefer their services much more than any alternative, due to their
78location (Iceland), their [privacy
79policy](https://1984hosting.com/GDPR/), their respect for GDPR, and the
80ability to remain anonymous if you pay in Bitcoin or Monero.
81
82[Njalla](https://njal.la) 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
103need to first move that DNS over to your new VPS provider. For me, I
104route my DNS through [Gandi](https://www.gandi.net).
105
106Once you know where your DNS settings are, go ahead and update the
107`A` records to match the public IP address of your VPS. For
108example:
109
110``` txt
111A example.com xxx.xxx.xxx.xxx
112A subdomain xxx.xxx.xxx.xxx
113CNAME www example.com.
114```
115
116If you have any other records that require updates, such as MX or TXT
117records for a mail server, be sure to update those accordingly.
118Personally, I don\'t host my own mail server. I route all mail on my
119custom domains to [Migadu](https://www.migadu.com). Hosting your own
120email server can become complex quickly and is not for beginners.
121
122DNS changes can take up to 48 hours to propagate, so be sure to give it
123some time before assuming you\'ve made an error.
124
125# Server Updates and Packages
126
127Now that the DNS settings have been changed, let\'s set up our server
128while we wait for the DNS to propagate. First up is to ssh into your
129server. If you\'ve signed up with a service like DigitalOcean, you can
130add your SSH key to your account and to your VPS droplet so that you
131don\'t need a password in order to SSH.
132
133```sh
134ssh root@xxx.xxx.xxx.xxx
135```
136
137The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache
138web server. If you\'re working on a different operating system (OS) or
139want a different web server, such as Nginx, you\'ll have to use
140different commands to set it up.
141
142First, let\'s update and upgrade our server.
143
144**NOTE:** Since we have logged in to the server as `root` for
145now, we don\'t need to use the `sudo` modifier before our
146commands.
147
148```sh
149apt update && apt upgrade -y
150```
151
152# Create A User Account
153
154While being able to use `root` can be beneficial at times,
155you shouldn\'t use `root` unless you have to.
156
157So let\'s set up a new user in our system. The `-m` option
158below tells the OS to create a home directory for the new user.
159
160```sh
161adduser USERNAME
162```
163
164Now, create a password for that user.
165
166```sh
167passwd USERNAME
168```
169
170Finally, add the user to the sudoers file, so they can perform
171priveleged commands.
172
173```sh
174usermod -a -G sudo USERNAME
175```
176
177If you are using SSH keys and not passwords, you\'ll need to copy your
178SSH key from your local machine to the VPS. If you haven\'t disabled
179password-based SSH yet, the easiest way to do this is
180`ssh-copy-id` from your local computer (not from the VPS):
181
182```sh
183ssh-copy-id testuser@xxx.xxx.xxx.xxx
184```
185
186If you\'ve disabled password-based SSH, you\'ll need to manually copy
187your SSH key into the `~/.ssh/authorized_keys` file.
188
189# Install Software
190
191Our goal here is to host a web server, so the next step is to install
192the Apache web server and any other packages we need.
193
194From this point on, I will be logged in as a user (not
195`root`) and will need to use the `sudo` modifier
196for most commands.
197
198```sh
199sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
200sudo apt install apache2
201```
202
203If you need other language support, such as PHP, you\'ll need to install
204that too.
205
206```sh
207sudo apt install libapache2-mod-php php-dom
208sudo a2enmod php
209sudo systemctl restart apache2
210```
211
212# Website Files & Folders
213
214Next up is to create the directories for the domain(s) we want to be
215hosted on this web server.
216
217```sh
218cd /var/www
219sudo mkdir example.com
220```
221
222We have a folder for `example.com` now, so let\'s add an
223`index.html` file and put it within a specific
224`public_html` folder. You don\'t need this
225`public_html` if you don\'t want it, but it helps with
226organizing items related to `example.com` that you don\'t
227want to publish to the internet.
228
229```sh
230cd example.com
231sudo mkdir public_html && cd public_html
232sudo nano index.html
233```
234
235You can put anything you want in this `index.html` file. If
236you can\'t think of anything, paste this in there:
237
238``` html
239<!DOCTYPE html>
240<html lang="en">
241 <head>
242 <meta charset="utf-8" />
243 <meta name="viewport" content="width=device-width, initial-scale=1" />
244 <title>Hello, world!</title>
245 </head>
246 <body>
247 <h1>Hello, world!</h1>
248 </body>
249</html>
250```
251
252If you want something to be served at
253`example.com/page01/file.txt`, you\'ll have to create the
254`page01` directory under the `example.com`
255directory. For example:
256
257```sh
258cd /var/www/example.com/public_html
259sudo mkdir page01
260sudo nano file.txt
261```
262
263# Apache Configuration
264
265Now, let\'s set up the files that will tell the server where to find the
266files for `example.com`. We will copy the default
267configuration file and create our own.
268
269```sh
270cd /etc/apache2/sites-available
271sudo cp 000-default.conf example.com.conf
272sudo nano example.com.conf
273```
274
275This configuration file will have a few default lines, but you\'ll need
276to edit it to look similar to this (settings may change based on your
277personal needs):
278
279``` config
280<VirtualHost *:80>
281 ServerAdmin your-email@email-provider.com
282 ServerName example.com
283 ServerAlias www.example.com
284 DocumentRoot /var/www/example.com/public_html
285 ErrorLog ${APACHE_LOG_DIR}/error.log
286 CustomLog ${APACHE_LOG_DIR}/access.log combined
287</VirtualHost>
288```
289
290Now, enable the configuration for your new site, disable the default
291configuration, and reload the web server.
292
293```sh
294sudo a2ensite example.com.conf
295sudo a2dissite 000-default.conf
296sudo systemctl reload apache2
297```
298
299You can always run a test to make sure no errors or warnings are found
300in your configuration files.
301
302```sh
303sudo apache2ctl configtest
304```
305
306Now, restart the web server entirely. After this, you should be able to
307browse to `http://example.com` and see the HTML content you
308provided earlier. Note that SSL/TLS has not been enabled yet, so you
309won\'t be able to use the secure version yet
310(`https://example.com`).
311
312```sh
313sudo systemctl restart apache2
314```
315
316You can repeat this for as many websites as you need. Just create the
317domain folders in `/var/www/`, add the configuration file,
318enable the configuration, and restart `apache2`.
319
320# SSL/TLS Certificates: Serve Websites Over HTTPS
321
322In order to serve secure content, you\'ll need to obtain SSL/TLS
323certificates. Luckily, there\'s a free tool called
324[Certbot](https://certbot.eff.org) that helps us with the process.
325
326The first step is to install `snapd` and `core`
327for Ubuntu.
328
329```sh
330sudo apt install snapd
331sudo snap install core
332sudo snap refresh core
333```
334
335Next, install the `certbot` snap package.
336
337```sh
338sudo snap install --classic certbot
339```
340
341Execute the following command to ensure that the `certbot`
342command can be run.
343
344```sh
345sudo ln -s /snap/bin/certbot /usr/bin/certbot
346```
347
348Finally, you can run `certbot` one of two ways:
349
3501. run it and let it alter your Apache configuration files
351 automatically to enable HTTPS redirects.
3522. run it and only allow it to create certificates. You\'ll need to
353 manually alter the config files to enable HTTPS redirects.
354
355Run certbot and allow automatic config changes:
356
357```sh
358sudo certbot --apache
359```
360
361Run certbot for certificates only and don\'t allow it to alter config
362files:
363
364```sh
365sudo certbot certonly --apache
366```
367
368The Certbot packages on your system come with a cron job or systemd
369timer that will renew your certificates automatically before they
370expire. You will not need to run Certbot again unless you change your
371configuration. You can test automatic renewal for your certificates by
372running this command:
373
374```sh
375sudo certbot renew --dry-run
376```
377
378Now, test your domains by going to `https://example.com`.
379
380# Firewall Security
381
382To enable better security on your server, you\'ll need to enable a basic
383firewall. For Ubuntu, we\'ll use [the uncomplicated
384firewall](../secure-your-network-with-the-uncomplicated-firewall/).
385
386Now, add the following rules to the firewall allow SSH, Apache, and
387HTTP(S) connections. If you need to, you can enable different ports for
388specifics applications, SFTP, etc.
389
390```sh
391sudo ufw default deny incoming
392sudo ufw default allow outgoing
393sudo ufw allow OpenSSH
394sudo ufw allow Apache
395sudo ufw allow proto tcp from any to any port 80,443
396```
397
398Once you\'ve added all the rules you need, enable the firewall.
399
400```sh
401sudo ufw enable
402```
403
404# Troubleshooting
405
406If you run into any issues during your VPS set-up, be sure to walk back
407through your actions and make sure you didn\'t miss any steps.
408
409Many websites have fantastic guides to setting up various web servers.
410This is one of the areas [where DigitalOcean
411shines](https://www.digitalocean.%20com/community/tutorials). For
412simpler or more Linux-oriented questions, I suggest using \[Linuxize\]
413(<https://linuxize.com>).
414
415If you\'re getting certain errors (e.g.
416`500 Internal Server Error`) and need to debug locally, you
417can view the `access.log` and `error.log` files in
418the `/var/log/apache/` directory.
content/blog/2021-03-30-vps-web-server.org deleted −399
@@ -1,399 +0,0 @@
1#+title: How to Set Up a VPS Web Server
2#+date: 2021-03-30
3#+description: A beginner's guide to setting up a virtual private server (VPS).
4#+filetags: :sysadmin:
5
6* Shared Hosting vs. VPS
7Choosing a place to host a website is one of the most confusing
8decisions for beginner web developers. Even for experienced web devs,
9choosing between different forms of web hosting can be a daunting
10choice.
11
12First, let's take a look at
13[[https://en.wikipedia.org/wiki/Shared_web_hosting_service][shared web
14hosting]]. Shared web hosting is a product where you are purchasing a
15small piece of a web server that is being shared between many websites.
16As a result, the cost of shared hosting is extremely low. You won't have
17access to the server itself, which means you can't install your own
18software on the server, such as Docker. Usually, you are simply allowed
19to connect your domains to the server, set up domain security, and other
20small utilities.
21
22In contrast, a
23[[https://en.wikipedia.org/wiki/Virtual_private_server][virtual private
24server]] (VPS) is a virtual machine that replicates the environment of
25having a dedicated server to yourself. You are able to control the
26virtual server's host names, base file system, package manager, etc.
27Another great upside of a VPS is that since it's virtual, the company
28providing the VPS can dynamically increase the disk size, RAM size, or
29number of CPUs at any time. However, the virtual server is still
30physically located on a server that is shared between multiple virtual
31servers.
32
33The choice between shared hosting and VPS mostly depends on your skill
34level with system administration. If you're comforting working on a
35server that is mostly left up to you (or you're willing to learn), then
36a VPS is usually a better option. However, shared hosting is a fantastic
37option for people who don't want to have to learn how to manage their
38server.
39
40* My Situation
41I had used shared hosting for approximately 5 years before trying my
42first VPS. I manage a homelab and have had success running a server and
43performing typical sysadmin duties, but I was still hesitant to get a
44VPS. One fear was that I always struggled to properly set up the
45networking part of a server - DNS and hostname configurations were not
46my friend.
47
48As a little bit of background, I originally used
49[[https://www.siteground.com][Siteground]] for my initially shared
50hosting and stayed on that platform for at least a year. However, the UI
51was clunky, and I didn't like how they handled certain technical
52aspects, so I switched to [[https://www.namecheap.com][Namecheap]].
53Namecheap was great because it is the service I primarily use for
54purchasing domain names, which made it incredibly easy to link them to
55my hosting service. However, it was still mediocre shared hosting, and
56Namecheap is notorious for not letting you use
57[[https://letsencrypt.org][Let's Encrypt]] to obtain free SSL/TLS
58certificates; Namecheap wants to make you purchase certificates through
59their store.
60
61Finally, I settled down with [[https://www.iwebfusion.net][iWebFusion]]
62for about the last year of my shared hosting. This service was pretty
63great, came with free SSL/TLS, and I never had any complaints.
64
65However, I finally grew tired of not being able to install software on
66my own web server. I wanted to be able to try out things like
67[[https://postmill.xyz][Postmill]] or [[https://matrix.org][Matrix]].
68This is possible with a VPS, so I decided to grab a new domain name to
69try it out.
70
71* Getting Started: Buying a VPS
72The first step to moving over to a VPS is (you guessed it): finding a
73VPS provider. For my VPSs, I use [[https://1984hosting.com][1984]] and
74prefer their services much more than any alternative, due to their
75location (Iceland), their [[https://1984hosting.com/GDPR/][privacy
76policy]], their respect for GDPR, and the ability to remain anonymous if
77you pay in Bitcoin or Monero.
78
79[[https://njal.la][Njalla]] is another good, privacy-oriented option for
80VPS services.
81
82You'll have to decide what specifications you want on your VPS. For me,
83I only build and deploy low-resource HTML, PHP, and Python websites.
84This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and
8525GB SSD for $5.00 per month.
86
87As noted above, the great thing about a VPS is you can request your
88provider to increase the resources at any time.
89
90* Configuring DNS Settings
91Okay, so now let's get into some actual work that has to be done to get
92content moved from a shared host to a VPS. At this point, I'm assuming
93you have a shared host with website content that you can still access,
94and you've purchased a new VPS and can SSH into that server.
95
96The first change is minor, but it should be done immediately in order to
97get things moving: DNS settings. Go to wherever your DNS settings are
98handled. If your shared host also managed your DNS settings, you'll need
99to first move that DNS over to your new VPS provider. For me, I route my
100DNS through [[https://www.gandi.net][Gandi]].
101
102Once you know where your DNS settings are, go ahead and update the =A=
103records to match the public IP address of your VPS. For example:
104
105#+begin_src txt
106A example.com xxx.xxx.xxx.xxx
107A subdomain xxx.xxx.xxx.xxx
108CNAME www example.com.
109#+end_src
110
111If you have any other records that require updates, such as MX or TXT
112records for a mail server, be sure to update those accordingly.
113Personally, I don't host my own mail server. I route all mail on my
114custom domains to [[https://www.migadu.com][Migadu]]. Hosting your own
115email server can become complex quickly and is not for beginners.
116
117DNS changes can take up to 48 hours to propagate, so be sure to give it
118some time before assuming you've made an error.
119
120* Server Updates and Packages
121Now that the DNS settings have been changed, let's set up our server
122while we wait for the DNS to propagate. First up is to ssh into your
123server. If you've signed up with a service like DigitalOcean, you can
124add your SSH key to your account and to your VPS droplet so that you
125don't need a password in order to SSH.
126
127#+begin_src sh
128ssh root@xxx.xxx.xxx.xxx
129#+end_src
130
131The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache
132web server. If you're working on a different operating system (OS) or
133want a different web server, such as Nginx, you'll have to use different
134commands to set it up.
135
136First, let's update and upgrade our server.
137
138*NOTE:* Since we have logged in to the server as =root= for now, we
139don't need to use the =sudo= modifier before our commands.
140
141#+begin_src sh
142apt update && apt upgrade -y
143#+end_src
144
145* Create A User Account
146While being able to use =root= can be beneficial at times, you shouldn't
147use =root= unless you have to.
148
149So let's set up a new user in our system. The =-m= option below tells
150the OS to create a home directory for the new user.
151
152#+begin_src sh
153adduser USERNAME
154#+end_src
155
156Now, create a password for that user.
157
158#+begin_src sh
159passwd USERNAME
160#+end_src
161
162Finally, add the user to the sudoers file, so they can perform
163priveleged commands.
164
165#+begin_src sh
166usermod -a -G sudo USERNAME
167#+end_src
168
169If you are using SSH keys and not passwords, you'll need to copy your
170SSH key from your local machine to the VPS. If you haven't disabled
171password-based SSH yet, the easiest way to do this is =ssh-copy-id= from
172your local computer (not from the VPS):
173
174#+begin_src sh
175ssh-copy-id testuser@xxx.xxx.xxx.xxx
176#+end_src
177
178If you've disabled password-based SSH, you'll need to manually copy your
179SSH key into the =~/.ssh/authorized_keys= file.
180
181* Install Software
182Our goal here is to host a web server, so the next step is to install
183the Apache web server and any other packages we need.
184
185From this point on, I will be logged in as a user (not =root=) and will
186need to use the =sudo= modifier for most commands.
187
188#+begin_src sh
189sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
190sudo apt install apache2
191#+end_src
192
193If you need other language support, such as PHP, you'll need to install
194that too.
195
196#+begin_src sh
197sudo apt install libapache2-mod-php php-dom
198sudo a2enmod php
199sudo systemctl restart apache2
200#+end_src
201
202* Website Files & Folders
203Next up is to create the directories for the domain(s) we want to be
204hosted on this web server.
205
206#+begin_src sh
207cd /var/www
208sudo mkdir example.com
209#+end_src
210
211We have a folder for =example.com= now, so let's add an =index.html=
212file and put it within a specific =public_html= folder. You don't need
213this =public_html= if you don't want it, but it helps with organizing
214items related to =example.com= that you don't want to publish to the
215internet.
216
217#+begin_src sh
218cd example.com
219sudo mkdir public_html && cd public_html
220sudo nano index.html
221#+end_src
222
223You can put anything you want in this =index.html= file. If you can't
224think of anything, paste this in there:
225
226#+begin_src html
227<!DOCTYPE html>
228<html lang="en">
229 <head>
230 <meta charset="utf-8" />
231 <meta name="viewport" content="width=device-width, initial-scale=1" />
232 <title>Hello, world!</title>
233 </head>
234 <body>
235 <h1>Hello, world!</h1>
236 </body>
237</html>
238#+end_src
239
240If you want something to be served at =example.com/page01/file.txt=,
241you'll have to create the =page01= directory under the =example.com=
242directory. For example:
243
244#+begin_src sh
245cd /var/www/example.com/public_html
246sudo mkdir page01
247sudo nano file.txt
248#+end_src
249
250* Apache Configuration
251Now, let's set up the files that will tell the server where to find the
252files for =example.com=. We will copy the default configuration file and
253create our own.
254
255#+begin_src sh
256cd /etc/apache2/sites-available
257sudo cp 000-default.conf example.com.conf
258sudo nano example.com.conf
259#+end_src
260
261This configuration file will have a few default lines, but you'll need
262to edit it to look similar to this (settings may change based on your
263personal needs):
264
265#+begin_src config
266<VirtualHost *:80>
267 ServerAdmin your-email@email-provider.com
268 ServerName example.com
269 ServerAlias www.example.com
270 DocumentRoot /var/www/example.com/public_html
271 ErrorLog ${APACHE_LOG_DIR}/error.log
272 CustomLog ${APACHE_LOG_DIR}/access.log combined
273</VirtualHost>
274#+end_src
275
276Now, enable the configuration for your new site, disable the default
277configuration, and reload the web server.
278
279#+begin_src sh
280sudo a2ensite example.com.conf
281sudo a2dissite 000-default.conf
282sudo systemctl reload apache2
283#+end_src
284
285You can always run a test to make sure no errors or warnings are found
286in your configuration files.
287
288#+begin_src sh
289sudo apache2ctl configtest
290#+end_src
291
292Now, restart the web server entirely. After this, you should be able to
293browse to =http://example.com= and see the HTML content you provided
294earlier. Note that SSL/TLS has not been enabled yet, so you won't be
295able to use the secure version yet (=https://example.com=).
296
297#+begin_src sh
298sudo systemctl restart apache2
299#+end_src
300
301You can repeat this for as many websites as you need. Just create the
302domain folders in =/var/www/=, add the configuration file, enable the
303configuration, and restart =apache2=.
304
305* SSL/TLS Certificates: Serve Websites Over HTTPS
306In order to serve secure content, you'll need to obtain SSL/TLS
307certificates. Luckily, there's a free tool called
308[[https://certbot.eff.org][Certbot]] that helps us with the process.
309
310The first step is to install =snapd= and =core= for Ubuntu.
311
312#+begin_src sh
313sudo apt install snapd
314sudo snap install core
315sudo snap refresh core
316#+end_src
317
318Next, install the =certbot= snap package.
319
320#+begin_src sh
321sudo snap install --classic certbot
322#+end_src
323
324Execute the following command to ensure that the =certbot= command can
325be run.
326
327#+begin_src sh
328sudo ln -s /snap/bin/certbot /usr/bin/certbot
329#+end_src
330
331Finally, you can run =certbot= one of two ways:
332
3331. run it and let it alter your Apache configuration files automatically
334 to enable HTTPS redirects.
3352. run it and only allow it to create certificates. You'll need to
336 manually alter the config files to enable HTTPS redirects.
337
338Run certbot and allow automatic config changes:
339
340#+begin_src sh
341sudo certbot --apache
342#+end_src
343
344Run certbot for certificates only and don't allow it to alter config
345files:
346
347#+begin_src sh
348sudo certbot certonly --apache
349#+end_src
350
351The Certbot packages on your system come with a cron job or systemd
352timer that will renew your certificates automatically before they
353expire. You will not need to run Certbot again unless you change your
354configuration. You can test automatic renewal for your certificates by
355running this command:
356
357#+begin_src sh
358sudo certbot renew --dry-run
359#+end_src
360
361Now, test your domains by going to =https://example.com=.
362
363* Firewall Security
364To enable better security on your server, you'll need to enable a basic
365firewall. For Ubuntu, we'll use
366[[../secure-your-network-with-the-uncomplicated-firewall/][the
367uncomplicated firewall]].
368
369Now, add the following rules to the firewall allow SSH, Apache, and
370HTTP(S) connections. If you need to, you can enable different ports for
371specifics applications, SFTP, etc.
372
373#+begin_src sh
374sudo ufw default deny incoming
375sudo ufw default allow outgoing
376sudo ufw allow OpenSSH
377sudo ufw allow Apache
378sudo ufw allow proto tcp from any to any port 80,443
379#+end_src
380
381Once you've added all the rules you need, enable the firewall.
382
383#+begin_src sh
384sudo ufw enable
385#+end_src
386
387* Troubleshooting
388If you run into any issues during your VPS set-up, be sure to walk back
389through your actions and make sure you didn't miss any steps.
390
391Many websites have fantastic guides to setting up various web servers.
392This is one of the areas
393[[https://www.digitalocean.%20com/community/tutorials][where
394DigitalOcean shines]]. For simpler or more Linux-oriented questions, I
395suggest using [Linuxize] ([[https://linuxize.com]]).
396
397If you're getting certain errors (e.g. =500 Internal Server Error=) and
398need to debug locally, you can view the =access.log= and =error.log=
399files in the =/var/log/apache/= directory.
content/blog/2021-04-17-gemini-server.md added +165
@@ -0,0 +1,165 @@
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
11your own 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
17through a public IP address and that you own a domain name used for this
18Gemini capsule.
19
20# Getting Started with Agate
21
22We are going to use [Agate](https://github.com/mbrubeck/agate) for this
23tutorial. This is a basic Gemini server written in Rust. It takes very
24little time and maintenance to get it running.
25
26# Install Dependencies
27
28First, you will need to install the Rust package for your system. On
29Ubuntu, use the following commands (remember to use `sudo` if
30you are not the root user). The Rust installation will give you options
31to customize the installation; I used the default installation options.
32
33```sh
34sudo apt update && sudo apt upgrade -y
35curl https://sh.rustup.rs -sSf | sh
36```
37
38Remember to configure your shell with the new configuration:
39
40```sh
41source $HOME/.cargo/env
42```
43
44Before we install agate, make sure you have the `gcc` package
45installed:
46
47```sh
48sudo apt install gcc
49```
50
51Next, you\'ll need to install the agate executable with Rust\'s Cargo
52package maintainer:
53
54```sh
55cargo install agate
56```
57
58# Create Symlinks
59
60Once Cargo has finished installing all the required packages, symlink
61the executable to your \$PATH.
62
63```sh
64sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate
65```
66
67# Using Agate\'s Built-In Installation Tool
68
69If you\'re running Ubuntu or Debian, use the Debian installation script
70found in Agate\'s GitHub repository, under the `tools/debian`
71folder.
72
73```sh
74git clone https://github.com/mbrubeck/agate
75cd agate/tools/debian
76sudo ./install.sh
77```
78
79# Configure the Gemini Service
80
81We have a little more to do, but since this script tries to immediately
82run the service, it will likely fail with an exit code. Let\'s add our
83finishing touches. Edit the following file and replace the hostname with
84your desired URL. You can also change the directory where content will
85be served.
86
87```sh
88sudo nano /etc/systemd/system/gemini.service
89```
90
91```sh
92# Edit these lines to whatever you want - see the next code block for my personal configuration.
93WorkingDirectory=/srv/gemini
94ExecStart=agate --hostname $(uname -n) --lang en
95```
96
97This is my personal config:
98
99```sh
100WorkingDirectory=/var/gemini/
101ExecStart=agate --hostname gemini.example.com --lang en
102```
103
104Since we\'ve altered the systemd configuration files, we have to reload
105the daemon. Let\'s do that, restart our service, and check its status.
106
107```sh
108sudo systemctl daemon-reload
109sudo systemctl restart gemini.service
110sudo systemctl status gemini.service
111```
112
113# Fixing Systemd Errors
114
115If you\'re still getting errors, the installation process may not have
116properly enabled the gemini service. Fix it with the following commands.
117
118```sh
119sudo systemctl enable gemini.service
120sudo systemctl restart gemini.service
121sudo systemctl status gemini.service
122```
123
124# Firewall Rules
125
126Great! Our server is now functional and running. The first consideration
127now is that you need to be able to access port 1965 on the server. If
128you have a firewall enabled, you\'ll need to open that port up.
129
130```sh
131sudo ufw allow 1965
132sudo ufw reload
133```
134
135# Creating Content
136
137Let\'s create the Gemini capsule. Note that wherever you set the
138WorkingDirectory variable to earlier, Agate will expect you to put your
139Gemini capsule contents in a sub-folder called \"content.\" So, I place
140my files in \"/var/gmi/content.\" I\'m going to create that folder now
141and put a file in there.
142
143```sh
144sudo mkdir /var/gemini/content
145sudo nano /var/gemini/content/index.gmi
146```
147
148You can put whatever you want in the \"index.gmi\" file, just make sure
149it\'s valid Gemtext.
150
151# The Results
152
153Here are some screenshots of the Gemini page I just created in the
154[Lagrange](https://gmi.skyjake.fi/lagrange/) browser and the
155[amfora](https://github.com/makeworld-the-better-one/amfora) browser.
156
157![GUI Gemini
158browser](https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png)
159
160*Lagrange*
161
162![CLI Gemini
163browser](https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png)
164
165*Amfora*
content/blog/2021-04-17-gemini-server.org deleted −150
@@ -1,150 +0,0 @@
1#+title: Hosting a Gemini Server
2#+date: 2021-04-17
3#+description: A guide to self-hosting a Gemini web server on your own server.
4#+filetags: :sysadmin:
5
6* Similar Article Available
7To read more about Gemini and ways to test out this new protocol without
8your own server, see my previous post
9[[../launching-a-gemini-capsule/][Launching a Gemini Capsule]].
10
11* Preparation
12This guide assumes you have access to a server accessible to the world
13through a public IP address and that you own a domain name used for this
14Gemini capsule.
15
16* Getting Started with Agate
17We are going to use [[https://github.com/mbrubeck/agate][Agate]] for
18this tutorial. This is a basic Gemini server written in Rust. It takes
19very little time and maintenance to get it running.
20
21* Install Dependencies
22First, you will need to install the Rust package for your system. On
23Ubuntu, use the following commands (remember to use =sudo= if you are
24not the root user). The Rust installation will give you options to
25customize the installation; I used the default installation options.
26
27#+begin_src sh
28sudo apt update && sudo apt upgrade -y
29curl https://sh.rustup.rs -sSf | sh
30#+end_src
31
32Remember to configure your shell with the new configuration:
33
34#+begin_src sh
35source $HOME/.cargo/env
36#+end_src
37
38Before we install agate, make sure you have the =gcc= package installed:
39
40#+begin_src sh
41sudo apt install gcc
42#+end_src
43
44Next, you'll need to install the agate executable with Rust's Cargo
45package maintainer:
46
47#+begin_src sh
48cargo install agate
49#+end_src
50
51* Create Symlinks
52Once Cargo has finished installing all the required packages, symlink
53the executable to your $PATH.
54
55#+begin_src sh
56sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate
57#+end_src
58
59* Using Agate's Built-In Installation Tool
60If you're running Ubuntu or Debian, use the Debian installation script
61found in Agate's GitHub repository, under the =tools/debian= folder.
62
63#+begin_src sh
64git clone https://github.com/mbrubeck/agate
65cd agate/tools/debian
66sudo ./install.sh
67#+end_src
68
69* Configure the Gemini Service
70We have a little more to do, but since this script tries to immediately
71run the service, it will likely fail with an exit code. Let's add our
72finishing touches. Edit the following file and replace the hostname with
73your desired URL. You can also change the directory where content will
74be served.
75
76#+begin_src sh
77sudo nano /etc/systemd/system/gemini.service
78#+end_src
79
80#+begin_src sh
81# Edit these lines to whatever you want - see the next code block for my personal configuration.
82WorkingDirectory=/srv/gemini
83ExecStart=agate --hostname $(uname -n) --lang en
84#+end_src
85
86This is my personal config:
87
88#+begin_src sh
89WorkingDirectory=/var/gemini/
90ExecStart=agate --hostname gemini.example.com --lang en
91#+end_src
92
93Since we've altered the systemd configuration files, we have to reload
94the daemon. Let's do that, restart our service, and check its status.
95
96#+begin_src sh
97sudo systemctl daemon-reload
98sudo systemctl restart gemini.service
99sudo systemctl status gemini.service
100#+end_src
101
102* Fixing Systemd Errors
103If you're still getting errors, the installation process may not have
104properly enabled the gemini service. Fix it with the following commands.
105
106#+begin_src sh
107sudo systemctl enable gemini.service
108sudo systemctl restart gemini.service
109sudo systemctl status gemini.service
110#+end_src
111
112* Firewall Rules
113Great! Our server is now functional and running. The first consideration
114now is that you need to be able to access port 1965 on the server. If
115you have a firewall enabled, you'll need to open that port up.
116
117#+begin_src sh
118sudo ufw allow 1965
119sudo ufw reload
120#+end_src
121
122* Creating Content
123Let's create the Gemini capsule. Note that wherever you set the
124WorkingDirectory variable to earlier, Agate will expect you to put your
125Gemini capsule contents in a sub-folder called "content." So, I place my
126files in "/var/gmi/content." I'm going to create that folder now and put
127a file in there.
128
129#+begin_src sh
130sudo mkdir /var/gemini/content
131sudo nano /var/gemini/content/index.gmi
132#+end_src
133
134You can put whatever you want in the "index.gmi" file, just make sure
135it's valid Gemtext.
136
137* The Results
138Here are some screenshots of the Gemini page I just created in the
139[[https://gmi.skyjake.fi/lagrange/][Lagrange]] browser and the
140[[https://github.com/makeworld-the-better-one/amfora][amfora]] browser.
141
142#+caption: GUI Gemini browser
143[[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png]]
144
145/Lagrange/
146
147#+caption: CLI Gemini browser
148[[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png]]
149
150/Amfora/
content/blog/2021-04-23-php-comment-system.org → content/blog/2021-04-23-php-comment-system.md renamed +103 −91
@@ -1,11 +1,14 @@
1#+title: Roll Your Own Static Commenting System in PHP 1+++
2#+date: 2021-04-23 2date = 2021-04-23
3#+description: A simple guide to creating a commenting system in PHP. 3title = "Roll Your Own Static Commenting System in PHP"
4#+filetags: :dev: 4description = ""
5draft = false
6+++
7
8# The Terrible-ness of Commenting Systems
5 9
6* The Terrible-ness of Commenting Systems
7The current state of affairs regarding interactive comment systems is, 10The current state of affairs regarding interactive comment systems is,
8well, terrible. It is especially awful if you're a privacy conscious 11well, terrible. It is especially awful if you\'re a privacy conscious
9person who does not generally load third-party scripts or frames on the 12person who does not generally load third-party scripts or frames on the
10websites you visit. 13websites you visit.
11 14
@@ -14,75 +17,78 @@ something that should be standard.
14 17
15Of course, there are some really terrible options: 18Of course, there are some really terrible options:
16 19
17- Facebook Comments 20- Facebook Comments
18- Discourse 21- Discourse
19 22
20There are some options that are better but still use too many scripts, 23There are some options that are better but still use too many scripts,
21frames, or social integrations on your web page that could impact some 24frames, or social integrations on your web page that could impact some
22users: 25users:
23 26
24- Disqus 27- Disqus
25- Isso 28- Isso
26- Remark42 29- Remark42
27 30
28Lastly, I looked into a few unique ways of generating blog comments, 31Lastly, I looked into a few unique ways of generating blog comments,
29such as using Twitter threads or GitHub issues to automatically post 32such as using Twitter threads or GitHub issues to automatically post
30issues. However, these both rely on external third-party sites that I 33issues. However, these both rely on external third-party sites that I
31don't currently use. 34don\'t currently use.
35
36# Stay Static with Server-Side Comments
32 37
33* Stay Static with Server-Side Comments
34The main issue for my personal use-case is that my blog is completely, 38The main issue for my personal use-case is that my blog is completely,
35100% static. I use PHP on the back-end but website visitors only see 39100% static. I use PHP on the back-end but website visitors only see
36HTML and a single CSS file. No external javascript and no embedded 40HTML and a single CSS file. No external javascript and no embedded
37frames. 41frames.
38 42
39So, how do we keep a site static and still allow users to interact with 43So, how do we keep a site static and still allow users to interact with
40blog posts? The key actually pretty simple - I'm already using PHP, so 44blog posts? The key actually pretty simple - I\'m already using PHP, so
41why not rely on the classic HTML =<form>= and a PHP script to save the 45why not rely on the classic HTML `<form>` and a PHP script to
42comments somewhere? As it turns out, this was a perfect solution for me. 46save the comments somewhere? As it turns out, this was a perfect
47solution for me.
43 48
44The second issue for my personal use-case is that I am trying to keep 49The second issue for my personal use-case is that I am trying to keep
45the contents of my website accessible over time, as described by 50the contents of my website accessible over time, as described by
46[cite/t:@brandur], in his post entitled 51@brandur, in his post entitled [Blog with Markdown + Git, and degrade
47[[https://brandur.org/fragments/graceful-degradation-time][Blog with 52gracefully through
48Markdown + Git, and degrade gracefully through time]] . 53time](https://brandur.org/fragments/graceful-degradation-time) .
49 54
50This means I cannot rely on a database for comments, since I do not rely 55This means I cannot rely on a database for comments, since I do not rely
51on a database for any other part of my websites. 56on a database for any other part of my websites.
52 57
53I blog in plain Markdown files, commit all articles to Git, and ensure 58I blog in plain Markdown files, commit all articles to Git, and ensure
54that future readers will be able to see the source data long after I'm 59that future readers will be able to see the source data long after I\'m
55gone, or the website has gone offline. However, I still haven't 60gone, or the website has gone offline. However, I still haven\'t
56committed any images served on my blog to Git, as I'm not entirely sold 61committed any images served on my blog to Git, as I\'m not entirely sold
57on Git LFS yet - for now, images can be found at 62on Git LFS yet - for now, images can be found at
58[[https://img.cleberg.net][img.cleberg.net]]. 63[img.cleberg.net](https://img.cleberg.net).
59 64
60Saving my comments back to the Git repository ensures that another 65Saving my comments back to the Git repository ensures that another
61aspect of my site will degrade gracefully. 66aspect of my site will degrade gracefully.
62 67
63* Create a Comment Form 68# Create a Comment Form
64Okay, let's get started. The first step is to create an HTML form that 69
70Okay, let\'s get started. The first step is to create an HTML form that
65users can see and utilize to submit comments. This is fairly easy and 71users can see and utilize to submit comments. This is fairly easy and
66can be changed depending on your personal preferences. 72can be changed depending on your personal preferences.
67 73
68Take a look at the code block below for the form I currently use. Note 74Take a look at the code block below for the form I currently use. Note
69that =<current-url>= is replaced automatically in PHP with the current 75that `<current-url>` is replaced automatically in PHP with
70post's URL, so that my PHP script used later will know which blog post 76the current post\'s URL, so that my PHP script used later will know
71the comment is related to. 77which blog post the comment is related to.
72 78
73The form contains the following structure: 79The form contains the following structure:
74 80
751. =<form>= - This is the form and will determine which PHP script to 811. `<form>` - This is the form and will determine which PHP
76 send the comment to. 82 script to send the comment to.
772. =<section hidden>= - This section is hidden from the user and is used 832. `<section hidden>` - This section is hidden from the user
78 to ensure that we know which blog post sent the comment. 84 and is used to ensure that we know which blog post sent the comment.
793. =<section>= Display Name (Optional) - Used to accept a display name, 853. `<section>` Display Name (Optional) - Used to accept a
80 if entered. 86 display name, if entered.
814. =<section>= Comment (Required) - Used to accept the user's full 874. `<section>` Comment (Required) - Used to accept the
82 comment. Markdown is allowed. 88 user\'s full comment. Markdown is allowed.
835. =<button>= - A button to submit the form. 895. `<button>` - A button to submit the form.
84 90
85#+begin_src html 91``` html
86<form action="/comment.php" method="POST"> 92<form action="/comment.php" method="POST">
87 <h3>Leave a Comment</h3> 93 <h3>Leave a Comment</h3>
88 <section hidden> 94 <section hidden>
@@ -122,30 +128,32 @@ The form contains the following structure:
122 </section> 128 </section>
123 <button type="submit">Submit</button> 129 <button type="submit">Submit</button>
124</form> 130</form>
125#+end_src 131```
132
133# Handle Comments via POST
126 134
127* Handle Comments via POST
128Now that we have a form and can submit comments, we need to create a PHP 135Now that we have a form and can submit comments, we need to create a PHP
129script so that the server can fetch the comment data and save it. Make 136script so that the server can fetch the comment data and save it. Make
130sure your PHP script name matches the name you entered in the =action= 137sure your PHP script name matches the name you entered in the
131field in your form. 138`action` field in your form.
132 139
133See the code block below for the contents of my =comment.php= script. We 140See the code block below for the contents of my `comment.php`
134perform the following tasks in this script: 141script. We perform the following tasks in this script:
135 142
1361. Grab the POST data from the HTML form. 1431. Grab the POST data from the HTML form.
1372. Sanitize the comment data with =htmlentities=. 1442. Sanitize the comment data with `htmlentities`.
1383. Set the display name to =Anonymous= if it was left blank. 1453. Set the display name to `Anonymous` if it was left blank.
1394. Create a PHP object that combines all of this data. 1464. Create a PHP object that combines all of this data.
1405. Check if our destination file =comments.json= exists. 1475. Check if our destination file `comments.json` exists.
1416. If so, convert the PHP object to JSON and save it to the file. 1486. If so, convert the PHP object to JSON and save it to the file.
1427. If the =comments.json= file does not exist, the script will exit with 1497. If the `comments.json` file does not exist, the script
143 an error. You can alter this to ensure it creates the script, but my 150 will exit with an error. You can alter this to ensure it creates the
144 source code includes the file by default, so it will always exist. 151 script, but my source code includes the file by default, so it will
1458. Finally, send the user back to the =#comments= section of the blog 152 always exist.
146 post they just read. 1538. Finally, send the user back to the `#comments` section of
147 154 the blog post they just read.
148#+begin_src php 155
156``` php
149// Get the content sent from the comment form 157// Get the content sent from the comment form
150$comment = htmlentities($_POST['userContent']); 158$comment = htmlentities($_POST['userContent']);
151$post_url = $_POST['postURL']; 159$post_url = $_POST['postURL'];
@@ -180,32 +188,33 @@ if (file_exists($file_name)) {
180 188
181// Send the user back 189// Send the user back
182header('Location: ' . $post_url . '#comments'); 190header('Location: ' . $post_url . '#comments');
183#+end_src 191```
184 192
185If you're using Apache, make sure the =www-data= user on your server has 193If you\'re using Apache, make sure the `www-data` user on
186the correct permissions to your website directory or else it will not be 194your server has the correct permissions to your website directory or
187able to write to =comments.json=. 195else it will not be able to write to `comments.json`.
188 196
189#+begin_src sh 197```sh
190chgrp -R www-data /path/to/website/ 198chgrp -R www-data /path/to/website/
191chmod -R g+w comments.json 199chmod -R g+w comments.json
192#+end_src 200```
201
202# Display User Comments
193 203
194* Display User Comments
195Now that we can submit comments, and they are saved to the 204Now that we can submit comments, and they are saved to the
196=comments.json= file, let's make sure we can show those comments in each 205`comments.json` file, let\'s make sure we can show those
197blog post. 206comments in each blog post.
198 207
199The code block below shows the function I use to decode my 208The code block below shows the function I use to decode my
200=comments.json= file, check if the comments apply to the current post, 209`comments.json` file, check if the comments apply to the
201and then display them. 210current post, and then display them.
202 211
203This piece of code should *really** be inside a function (or at least in 212This piece of code should **really\*** be inside a function (or at least
204an organized PHP workflow). Don't just copy-and-paste and expect it to 213in an organized PHP workflow). Don\'t just copy-and-paste and expect it
205work. You need to at least supply a =$query= variable depending on the 214to work. You need to at least supply a `$query` variable
206page visited. 215depending on the page visited.
207 216
208#+begin_src php 217``` php
209$query = 'your-blog-post.html'; 218$query = 'your-blog-post.html';
210 219
211// Load saved comments 220// Load saved comments
@@ -237,29 +246,32 @@ foreach ($comments as $comment) {
237} 246}
238 247
239echo $comment_section; 248echo $comment_section;
240#+end_src 249```
250
251# Bonus: Create a \'Recent Comments\' Page
241 252
242* Bonus: Create a 'Recent Comments' Page
243Finally, the last part of my current system is to create a Recent 253Finally, the last part of my current system is to create a Recent
244Comments page so that I can easily check-in on my blog and moderate any 254Comments page so that I can easily check-in on my blog and moderate any
245spam. As an alternative, you could use PHP's =mail()= function to email 255spam. As an alternative, you could use PHP\'s `mail()`
246you for each blog comment. 256function to email you for each blog comment.
247 257
248The code to do this is literally the same code as the previous section, 258The code to do this is literally the same code as the previous section,
249I just make sure it is printed when someone visits 259I just make sure it is printed when someone visits
250=https://example.com/comments/=. 260`https://example.com/comments/`.
261
262# Possible Enhancements
251 263
252* Possible Enhancements
253This comment system is by no means a fully-developed system. I have 264This comment system is by no means a fully-developed system. I have
254noted a few possible enhancements here that I may implement in the 265noted a few possible enhancements here that I may implement in the
255future: 266future:
256 267
257- Create a secure moderator page with user authentication at 268- Create a secure moderator page with user authentication at
258 =https://blog.example.com/mod/=. This page could have the option to 269 `https://blog.example.com/mod/`. This page could have the
259 edit or delete any comment found in =comments.json=. 270 option to edit or delete any comment found in
260- Create a temporary file, such as =pending_comments.json=, that will 271 `comments.json`.
261 store newly-submitted comments and won't display on blog posts until 272- Create a temporary file, such as `pending_comments.json`,
262 approved by a moderator. 273 that will store newly-submitted comments and won\'t display on blog
263- Create a =/modlog/= page with a chronological log, showing which 274 posts until approved by a moderator.
264 moderator approved which comments and why certain comments were 275- Create a `/modlog/` page with a chronological log,
265 rejected. 276 showing which moderator approved which comments and why certain
277 comments were rejected.
content/blog/2021-04-28-photography.md added +73
@@ -0,0 +1,73 @@
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
11see billions of people around the world taking photographs every single
12moment of the day. New technology often boasts about their photographic
13capabilities, especially smartphones. I would even assume that we live
14in a world where there is never a moment in which a photograph is not
15being taken somewhere on Earth.
16
17As for myself, I would simply say that I enjoy preserving a memory in
18physical (or digital) form. I\'ve never had the best memory when it
19comes to recalling details of places and people gone by, so it helps to
20have a frame of reference lying around.
21
22Regardless of the reason, I think most people would agree that you
23simply cannot have too many hobbies.
24
25# Older Cameras
26
27I started playing around with the idea of photography when my family
28purchased a Fujifilm camera for family-specific events. I don\'t recall
29the specific model, but I do recall it was a point-and-shoot camera
30without an interchangeable lens. However, it was of great value to
31someone, like myself, who couldn\'t afford any other camera. I took
32about 10,000 shots with that camera over a 3-5 year span. Most notably,
33all of my trips to California were documented through this camera.
34
35When possible, I would borrow my sister\'s camera, which is a Sony
36SLT-A58. This camera was great and allowed for some of my best early
37shots, especially those taken in Utah\'s and Nevada\'s parks.
38
39# My Current Kit
40
41I\'ve finally come to a point in my life where I have the disposable
42income to invest in a solid photography kit. I played around with the
43idea of a lot of different cameras, different types, new vs used, etc.
44Finally, I settled on the [Sony α7
45III](https://en.wikipedia.org/wiki/Sony_%CE%B17_III). This camera is
46mirror-less and uses a full-frame image sensor at 24 megapixels. I
47don\'t create large prints, and I am mostly focused on preserving
48memories in high quality for the next 5-10 years with this camera, so
49the specifications here are just perfect for me.
50
51For lenses, I decided to buy two lenses that could carry me through most
52situations:
53
54- [Vario-Tessar T\*\* FE 24-70 mm F4 ZA
55 OSS](https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z)
56- [Tamron 70-300mm f4.5-6.3 Di III
57 RXD](https://www.tamron-usa.com/product/lenses/a047.html)
58
59In addition, I grabbed a couple [HGX Prime
6067mm](https://www.promaster.com/Product/6725) protection filters for the
61lenses.
62
63As I delve further into photography and pick up more skills, I will most
64likely go back and grab a lens with a higher f-stop value, such as
65f/1.8. I toyed with the idea of grabbing a 50 mm at `f/1.8`,
66but decided to keep things in a reasonable price range instead.
67
68Finally, I made sure to buy a photography-specific backpack with a rain
69guard, and the zipper on the back panel, to protect the equipment while
70wearing the bag. If you\'ve ever had to haul around a DSLR (or camera of
71similar heft) in a bag that only has a shoulder strap, you\'ll know the
72pain it can cause. Putting all my equipment in a backpack was an easy
73decision.
content/blog/2021-04-28-photography.org deleted −68
@@ -1,68 +0,0 @@
1#+title: Jumping Back Into Photography
2#+date: 2021-04-28
3#+description: Some thoughts on photography.
4#+filetags: :personal:
5
6* Why Photography?
7I've often wondered why photography is as enticing as it is. You can see
8billions of people around the world taking photographs every single
9moment of the day. New technology often boasts about their photographic
10capabilities, especially smartphones. I would even assume that we live
11in a world where there is never a moment in which a photograph is not
12being taken somewhere on Earth.
13
14As for myself, I would simply say that I enjoy preserving a memory in
15physical (or digital) form. I've never had the best memory when it comes
16to recalling details of places and people gone by, so it helps to have a
17frame of reference lying around.
18
19Regardless of the reason, I think most people would agree that you
20simply cannot have too many hobbies.
21
22* Older Cameras
23I started playing around with the idea of photography when my family
24purchased a Fujifilm camera for family-specific events. I don't recall
25the specific model, but I do recall it was a point-and-shoot camera
26without an interchangeable lens. However, it was of great value to
27someone, like myself, who couldn't afford any other camera. I took about
2810,000 shots with that camera over a 3-5 year span. Most notably, all of
29my trips to California were documented through this camera.
30
31When possible, I would borrow my sister's camera, which is a Sony
32SLT-A58. This camera was great and allowed for some of my best early
33shots, especially those taken in Utah's and Nevada's parks.
34
35* My Current Kit
36I've finally come to a point in my life where I have the disposable
37income to invest in a solid photography kit. I played around with the
38idea of a lot of different cameras, different types, new vs used, etc.
39Finally, I settled on the
40[[https://en.wikipedia.org/wiki/Sony_%CE%B17_III][Sony α7 III]]. This
41camera is mirror-less and uses a full-frame image sensor at 24
42megapixels. I don't create large prints, and I am mostly focused on
43preserving memories in high quality for the next 5-10 years with this
44camera, so the specifications here are just perfect for me.
45
46For lenses, I decided to buy two lenses that could carry me through most
47situations:
48
49- [[https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z][Vario-Tessar
50 T** FE 24-70 mm F4 ZA OSS]]
51- [[https://www.tamron-usa.com/product/lenses/a047.html][Tamron 70-300mm
52 f4.5-6.3 Di III RXD]]
53
54In addition, I grabbed a couple
55[[https://www.promaster.com/Product/6725][HGX Prime 67mm]] protection
56filters for the lenses.
57
58As I delve further into photography and pick up more skills, I will most
59likely go back and grab a lens with a higher f-stop value, such as
60f/1.8. I toyed with the idea of grabbing a 50 mm at =f/1.8=, but decided
61to keep things in a reasonable price range instead.
62
63Finally, I made sure to buy a photography-specific backpack with a rain
64guard, and the zipper on the back panel, to protect the equipment while
65wearing the bag. If you've ever had to haul around a DSLR (or camera of
66similar heft) in a bag that only has a shoulder strap, you'll know the
67pain it can cause. Putting all my equipment in a backpack was an easy
68decision.
content/blog/2021-05-30-changing-git-authors.org → content/blog/2021-05-30-changing-git-authors.md renamed +23 −20
@@ -1,30 +1,33 @@
1#+title: Changing Git Authors 1+++
2#+date: 2021-05-30 2date = 2021-05-30
3#+description: A guide to change Git author names and emails in old commits. 3title = "Changing Git Authors"
4#+filetags: :dev: 4description = ""
5draft = false
6+++
5 7
6* Changing Git Author/Email Based on Previously Committed Email 8# Changing Git Author/Email Based on Previously Committed Email
7Here's the dilemma: You've been committing changes to your git 9
10Here\'s the dilemma: You\'ve been committing changes to your git
8repository with an incorrect name or email (or multiple repositories), 11repository with an incorrect name or email (or multiple repositories),
9and now you want to fix it. Luckily, there's a semi-reliable way to fix 12and now you want to fix it. Luckily, there\'s a semi-reliable way to fix
10that. While I have never experienced issues with this method, some 13that. While I have never experienced issues with this method, some
11people have warned that it can mess with historical hashes and integrity 14people have warned that it can mess with historical hashes and integrity
12of commits, so use this method only if you're okay accepting that risk. 15of commits, so use this method only if you\'re okay accepting that risk.
13 16
14Okay, let's create the bash script: 17Okay, let\'s create the bash script:
15 18
16#+begin_src sh 19```sh
17nano change_git_authors.sh 20nano change_git_authors.sh
18#+end_src 21```
19 22
20The following information can be pasted directly into your bash script. 23The following information can be pasted directly into your bash script.
21The only changes you need to make are to the following variables: 24The only changes you need to make are to the following variables:
22 25
23- =OLD_EMAIL= 26- `OLD_EMAIL`
24- =CORRECT_NAME= 27- `CORRECT_NAME`
25- =CORRECT_EMAIL= 28- `CORRECT_EMAIL`
26 29
27#+begin_src sh 30```sh
28#!/bin/sh 31#!/bin/sh
29 32
30# List all sub-directories in the current directory 33# List all sub-directories in the current directory
@@ -57,16 +60,16 @@ do
57 60
58 cd .. 61 cd ..
59done 62done
60#+end_src 63```
61 64
62Finally, save the bash script and make it executable. 65Finally, save the bash script and make it executable.
63 66
64#+begin_src sh 67```sh
65chmod a+x change_git_authors.sh 68chmod a+x change_git_authors.sh
66#+end_src 69```
67 70
68Now you can run the script and should see the process begin. 71Now you can run the script and should see the process begin.
69 72
70#+begin_src sh 73```sh
71./change_git_authors.sh 74./change_git_authors.sh
72#+end_src 75```
content/blog/2021-07-15-delete-gitlab-repos.org → content/blog/2021-07-15-delete-gitlab-repos.md renamed +35 −29
@@ -1,47 +1,53 @@
1#+title: How to Delete All GitLab Repositories 1+++
2#+date: 2021-07-15 2date = 2021-07-15
3#+description: Learn how to delete all GitLab repositories in your account. 3title = "How t oDelete All GitLab Repositories"
4#+filetags: :dev: 4description = ""
5draft = false
6+++
7
8# Background
5 9
6* Background
7Have you ever used GitLab to host your source code, moved to a different 10Have you ever used GitLab to host your source code, moved to a different
8host, and wanted to delete everything from your GitLab account? Well, 11host, and wanted to delete everything from your GitLab account? Well,
9this post covers any scenario where you would want to delete all 12this post covers any scenario where you would want to delete all
10repositories from your GitLab account. 13repositories from your GitLab account.
11 14
12For me, I currently maintain around 30 repositories and don't like to 15For me, I currently maintain around 30 repositories and don\'t like to
13manually delete them whenever I switch host. GitHub has a few different 16manually delete them whenever I switch host. GitHub has a few different
14tools online to delete all repositories for you, but I have not found 17tools online to delete all repositories for you, but I have not found
15anything similar for GitLab, so I needed an alternative solution. 18anything similar for GitLab, so I needed an alternative solution.
16 19
17* Use a Python Script 20# Use a Python Script
18** Requirements 21
22## Requirements
23
19Before we look at the script, make sure you know your GitLab username. 24Before we look at the script, make sure you know your GitLab username.
20Next, [[https://gitlab.com/-/profile/personal_access_tokens][create an 25Next, [create an authorization
21authorization token]] so that the Python script can delete your 26token](https://gitlab.com/-/profile/personal_access_tokens) so that the
22repositories. Don't lose this token or else you'll need to create a new 27Python script can delete your repositories. Don\'t lose this token or
23one. 28else you\'ll need to create a new one.
29
30## Create the Script
24 31
25** Create the Script
26To run a Python script, you must first create it. Open a terminal and 32To run a Python script, you must first create it. Open a terminal and
27enter the following commands in whichever directory you prefer to store 33enter the following commands in whichever directory you prefer to store
28the script. You can do the same things in a file manager if you prefer. 34the script. You can do the same things in a file manager if you prefer.
29 35
30#+begin_src sh 36```sh
31mkdir delete-gitlab 37mkdir delete-gitlab
32#+end_src 38```
33 39
34#+begin_src sh 40```sh
35cd delete-gitlab 41cd delete-gitlab
36#+end_src 42```
37 43
38#+begin_src sh 44```sh
39nano main.py 45nano main.py
40#+end_src 46```
41 47
42Enter the following code into your =main.py= script. 48Enter the following code into your `main.py` script.
43 49
44#+begin_src python 50``` python
45import request 51import request
46import json 52import json
47 53
@@ -95,16 +101,16 @@ def main():
95 101
96if __name__ == "__main__": 102if __name__ == "__main__":
97 main() 103 main()
98#+end_src 104```
99 105
100Now that you have the proper information, replace ={user-id}= with your 106Now that you have the proper information, replace `{user-id}`
101GitLab username and ={auth-token}= with the authorization token you 107with your GitLab username and `{auth-token}` with the
102created earlier. 108authorization token you created earlier.
103 109
104Finally, simply run the script and watch the output. You can also use 110Finally, simply run the script and watch the output. You can also use
105PyCharm Community Edition to edit and run the Python script if you don't 111PyCharm Community Edition to edit and run the Python script if you
106want to work in a terminal. 112don\'t want to work in a terminal.
107 113
108#+begin_src sh 114```sh
109python3 main.py 115python3 main.py
110#+end_src 116```
content/blog/2021-08-25-audit-sampling.org → content/blog/2021-08-25-audit-sampling.md renamed +94 −80
@@ -1,9 +1,12 @@
1#+title: Audit Sampling with Python 1+++
2#+date: 2021-08-25 2date = 2021-08-25
3#+description: Learn how to sample populations with Python. 3title = "Audit Sampling with Python"
4#+filetags: :audit: 4description = ""
5draft = false
6+++
7
8# Introduction
5 9
6* Introduction
7For anyone who is familiar with internal auditing, external auditing, or 10For anyone who is familiar with internal auditing, external auditing, or
8consulting, you will understand how tedious audit testing can become 11consulting, you will understand how tedious audit testing can become
9when you are required to test large swaths of data. When we cannot 12when you are required to test large swaths of data. When we cannot
@@ -16,33 +19,35 @@ However, sampling data within the world of audit still seems to confuse
16quite a lot of people. While some audit-focused tools have introduced 19quite a lot of people. While some audit-focused tools have introduced
17sampling functionality (e.g. Wdesk), many audit departments and firms 20sampling functionality (e.g. Wdesk), many audit departments and firms
18cannot use software like this due to certain constraints, such as the 21cannot use software like this due to certain constraints, such as the
19team's budget or knowledge. Here is where this article comes in: we're 22team\'s budget or knowledge. Here is where this article comes in: we\'re
20going to use [[https://www.python.org][Python]], a free and open-source 23going to use [Python](https://www.python.org), a free and open-source
21programming language, to generate random samples from a dataset in order 24programming language, to generate random samples from a dataset in order
22to suffice numerous audit situations. 25to suffice numerous audit situations.
23 26
24* Audit Requirements for Sampling 27# Audit Requirements for Sampling
28
25Before we get into the details of how to sample with Python, I want to 29Before we get into the details of how to sample with Python, I want to
26make sure I discuss the different requirements that auditors may have of 30make sure I discuss the different requirements that auditors may have of
27samples used within their projects. 31samples used within their projects.
28 32
29** Randomness 33## Randomness
30First, let's discuss randomness. When testing out new technology to help 34
31assist with audit sampling, you need to understand exactly how your 35First, let\'s discuss randomness. When testing out new technology to
36help assist with audit sampling, you need to understand exactly how your
32samples are being generated. For example, if the underlying function is 37samples are being generated. For example, if the underlying function is
33just picking every 57th element from a list, that's not truly random; 38just picking every 57th element from a list, that\'s not truly random;
34it's a systematic form of sampling. Luckily, since Python is 39it\'s a systematic form of sampling. Luckily, since Python is
35open-source, we have access to its codebase. Through this blog post, I 40open-source, we have access to its codebase. Through this blog post, I
36will be using the [[https://pandas.pydata.org][pandas]] module in order 41will be using the [pandas](https://pandas.pydata.org) module in order to
37to generate the random samples. More specifically, I will be using the 42generate the random samples. More specifically, I will be using the
38[[https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html][pandas.DataFrame.sample]] 43[pandas.DataFrame.sample](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html)
39function provided by Pandas. 44function provided by Pandas.
40 45
41Now that you know what you're using, you can always check out the code 46Now that you know what you\'re using, you can always check out the code
42behind =pandas.DataFrame.sample=. This function does a lot of work, but 47behind `pandas.DataFrame.sample`. This function does a lot of
43we really only care about the following snippets of code: 48work, but we really only care about the following snippets of code:
44 49
45#+begin_src python 50``` python
46# Process random_state argument 51# Process random_state argument
47rs = com.random_state(random_state) 52rs = com.random_state(random_state)
48 53
@@ -54,22 +59,24 @@ if ignore_index:
54result.index = ibase.default_index(len(result)) 59result.index = ibase.default_index(len(result))
55 60
56return result 61return result
57#+end_src 62```
58 63
59The block of code above shows you that if you assign a =random_state= 64The block of code above shows you that if you assign a
60argument when you run the function, that will be used as a seed number 65`random_state` argument when you run the function, that will
61in the random generation and will allow you to reproduce a sample, given 66be used as a seed number in the random generation and will allow you to
62that nothing else changes. This is critical to the posterity of audit 67reproduce a sample, given that nothing else changes. This is critical to
63work. After all, how can you say your audit process is adequately 68the posterity of audit work. After all, how can you say your audit
64documented if the next person can't run the code and get the same 69process is adequately documented if the next person can\'t run the code
65sample? The final piece here on randomness is to look at the 70and get the same sample? The final piece here on randomness is to look
66[[https://docs.%20python.org/3/library/random.html#random.choice][choice]] 71at the
72[choice](https://docs.%20python.org/3/library/random.html#random.choice)
67function used above. This is the crux of the generation and can also be 73function used above. This is the crux of the generation and can also be
68examined for more detailed analysis on its reliability. As far as 74examined for more detailed analysis on its reliability. As far as
69auditing goes, we will trust that these functions are mathematically 75auditing goes, we will trust that these functions are mathematically
70random. 76random.
71 77
72** Sample Sizes 78## Sample Sizes
79
73As mentioned in the intro, sampling is only an effective method of 80As mentioned in the intro, sampling is only an effective method of
74auditing when it truly represents the entire population. While some 81auditing when it truly represents the entire population. While some
75audit departments or firms may consider certain judgmental sample sizes 82audit departments or firms may consider certain judgmental sample sizes
@@ -88,32 +95,35 @@ proprietary, table that will instruct auditors which sample sizes to
88choose. This allows for uniform testing and reduces overall workload. 95choose. This allows for uniform testing and reduces overall workload.
89See the table below for a common implementation of sample sizes: 96See the table below for a common implementation of sample sizes:
90 97
91| Control Frequency | Sample Size - High Risk | Sample Size - Low Risk | 98 Control Frequency Sample Size - High Risk Sample Size - Low Risk
92|-------------------+-------------------------+------------------------| 99 ------------------- ------------------------- ------------------------
93| More Than Daily | 40 | 25 | 100 More Than Daily 40 25
94| Daily | 40 | 25 | 101 Daily 40 25
95| Weekly | 12 | 5 | 102 Weekly 12 5
96| Monthly | 5 | 3 | 103 Monthly 5 3
97| Quarterly | 2 | 2 | 104 Quarterly 2 2
98| Semi-Annually | 1 | 1 | 105 Semi-Annually 1 1
99| Annually | 1 | 1 | 106 Annually 1 1
100| Ad-hoc | 1 | 1 | 107 Ad-hoc 1 1
101 108
102*** Sampling with Python & Pandas 109### Sampling with Python & Pandas
110
103In this section, I am going to cover a few basic audit situations that 111In this section, I am going to cover a few basic audit situations that
104require sampling. While some situations may require more effort, the 112require sampling. While some situations may require more effort, the
105syntax, organization, and intellect used remain largely the same. If 113syntax, organization, and intellect used remain largely the same. If
106you've never used Python before, note that lines starting with a '=#=' 114you\'ve never used Python before, note that lines starting with a
107symbol are called comments, and they will be skipped by Python. I highly 115\'`#`\' symbol are called comments, and they will be skipped
108recommend taking a quick tutorial online to understand the basics of 116by Python. I highly recommend taking a quick tutorial online to
109Python if any of the code below is confusing to you. 117understand the basics of Python if any of the code below is confusing to
118you.
119
120## Simple Random Sample
110 121
111** Simple Random Sample 122First, let\'s look at a simple, random sample. The code block below will
112First, let's look at a simple, random sample. The code block below will 123import the `pandas` module, load a data file, sample the
113import the =pandas= module, load a data file, sample the data, and 124data, and export the sample to a file.
114export the sample to a file.
115 125
116#+begin_src python 126``` python
117# Import the Pandas module 127# Import the Pandas module
118import pandas 128import pandas
119 129
@@ -131,13 +141,14 @@ sample = df.sample(n=25, random_state=0)
131 141
132# Save the sample to Excel 142# Save the sample to Excel
133sample.to_excel(file_output) 143sample.to_excel(file_output)
134#+end_src 144```
145
146## Simple Random Sample: Using Multiple Input Files
135 147
136** Simple Random Sample: Using Multiple Input Files 148Now that we\'ve created a simple sample, let\'s create a sample from
137Now that we've created a simple sample, let's create a sample from
138multiple files. 149multiple files.
139 150
140#+begin_src python 151``` python
141# Import the Pandas module 152# Import the Pandas module
142import pandas 153import pandas
143 154
@@ -164,14 +175,15 @@ sample = pandas.concat([sample_01, sample_02, sample_03], ignore_index=True)
164 175
165# Save the sample to Excel 176# Save the sample to Excel
166sample.to_excel(file_output) 177sample.to_excel(file_output)
167#+end_src 178```
179
180## Stratified Random Sample
168 181
169** Stratified Random Sample
170Well, what if you need to sample distinct parts of a single file? For 182Well, what if you need to sample distinct parts of a single file? For
171example, let's write some code to separate our data by "Region" and 183example, let\'s write some code to separate our data by \"Region\" and
172sample those regions independently. 184sample those regions independently.
173 185
174#+begin_src python 186``` python
175# Import the Pandas module 187# Import the Pandas module
176import pandas 188import pandas
177 189
@@ -197,15 +209,16 @@ sample = pandas.concat([sample_east, sample_west], ignore_index=True)
197 209
198# Save the sample to Excel 210# Save the sample to Excel
199sample.to_excel(file_output) 211sample.to_excel(file_output)
200#+end_src 212```
213
214## Stratified Systematic Sample
201 215
202** Stratified Systematic Sample
203This next example is quite useful if you need audit coverage over a 216This next example is quite useful if you need audit coverage over a
204certain time period. This code will generate samples for each month in 217certain time period. This code will generate samples for each month in
205the data and combine them all together at the end. Obviously, this code 218the data and combine them all together at the end. Obviously, this code
206can be modified to stratify by something other than months, if needed. 219can be modified to stratify by something other than months, if needed.
207 220
208#+begin_src python 221``` python
209# Import the Pandas module 222# Import the Pandas module
210import pandas 223import pandas
211 224
@@ -245,20 +258,21 @@ def monthly_stratified_sample(df: pandas.DataFrame, date_column: str, num_select
245sample_size = 3 258sample_size = 3
246sample = monthly_stratified_sample(df, 'Date of Sale', sample_size) 259sample = monthly_stratified_sample(df, 'Date of Sale', sample_size)
247sample.to_excel(file_output) 260sample.to_excel(file_output)
248#+end_src 261```
249 262
250*** Documenting the Results 263### Documenting the Results
251Once you've generated a proper sample, there are a few things left to do 264
252in order to properly ensure your process is reproducible. 265Once you\'ve generated a proper sample, there are a few things left to
253 266do in order to properly ensure your process is reproducible.
2541. Document the sample. Make sure the resulting file is readable and 267
255 includes the documentation listed in the next bullet. 2681. Document the sample. Make sure the resulting file is readable and
2562. Include documentation around the data source, extraction techniques, 269 includes the documentation listed in the next bullet.
257 any modifications made to the data, and be sure to include a copy of 2702. Include documentation around the data source, extraction techniques,
258 the script itself. 271 any modifications made to the data, and be sure to include a copy of
2593. Whenever possible, perform a completeness and accuracy test to ensure 272 the script itself.
260 your sample is coming from a complete and accurate population. To 2733. Whenever possible, perform a completeness and accuracy test to
261 ensure completeness, compare the record count from the data source to 274 ensure your sample is coming from a complete and accurate
262 the record count loaded into Python. To ensure accuracy, test a small 275 population. To ensure completeness, compare the record count from
263 sample against the source data (e.g., test 5 sales against the 276 the data source to the record count loaded into Python. To ensure
264 database to see if the details are accurate). 277 accuracy, test a small sample against the source data (e.g., test 5
278 sales against the database to see if the details are accurate).
content/blog/2021-10-09-apache-redirect.md added +47
@@ -0,0 +1,47 @@
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
11changed with no option to preserve the classic `.html`
12extension at the end of my blog post URLs.
13
14I really disliked using my old SSG ([Jekyll](https://jekyllrb.com)) and
15prefer my new tool ([Zola](https://www.getzola.org)) much more, so I was
16determined to figure out a way to get the proper redirect set up so that
17people who find my posts 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
24 `/blog/some-post.html` to `/blog/some-post/`.
252. Ensure that no other `.html` files are redirected, such
26 as `index.html`.
27
28After *a lot* of tweaking and testing, I believe I have finally found
29the solution. The solution is shown below.
30
31``` conf
32RewriteEngine On
33RewriteCond %{REQUEST_URI} !\index.html$ [NC]
34RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
35```
36
37This piece of code in the Apache `.conf` or
38`.htaccess` file will do the following:
39
401. Turn on the RewriteEngine so that we can modify URLs.
412. Ignore any `index.html` files from the rule we are about
42 to specify.
433. Find any `.html` files within the website directory and
44 redirect it to exclude the file extension.
454. The final piece is adding the trailing slash (`/`) at the
46 end of the URL - you\'ll notice that I don\'t have an Apache rule
47 for that since Apache handles that automatically.
content/blog/2021-10-09-apache-redirect.org deleted −43
@@ -1,43 +0,0 @@
1#+title: Apache Redirect HTML Files to a Directory
2#+date: 2021-10-09
3#+description: A guide on redirecting HTML files to directory in Apache.
4#+filetags: :apache:
5
6* The Problem
7After recently switching static site generators (SSG), my blog URLs
8changed with no option to preserve the classic =.html= extension at the
9end of my blog post URLs.
10
11I really disliked using my old SSG ([[https://jekyllrb.com][Jekyll]])
12and prefer my new tool ([[https://www.getzola.org][Zola]]) much more, so
13I was determined to figure out a way to get the proper redirect set up
14so that people who find my posts online aren't constantly met by 404
15errors.
16
17* The Solution
18To solve this problem, I really needed to solve two pieces:
19
201. Redirect all blog post URL requests from =/blog/some-post.html= to
21 =/blog/some-post/=.
222. Ensure that no other =.html= files are redirected, such as
23 =index.html=.
24
25After /a lot/ of tweaking and testing, I believe I have finally found
26the solution. The solution is shown below.
27
28#+begin_src conf
29RewriteEngine On
30RewriteCond %{REQUEST_URI} !\index.html$ [NC]
31RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
32#+end_src
33
34This piece of code in the Apache =.conf= or =.htaccess= file will do the
35following:
36
371. Turn on the RewriteEngine so that we can modify URLs.
382. Ignore any =index.html= files from the rule we are about to specify.
393. Find any =.html= files within the website directory and redirect it
40 to exclude the file extension.
414. The final piece is adding the trailing slash (=/=) at the end of the
42 URL - you'll notice that I don't have an Apache rule for that since
43 Apache handles that automatically.
content/blog/2021-12-04-cisa.md added +215
@@ -0,0 +1,215 @@
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
12Certified Information Systems Auditor. This certification and exam are
13part of ISACA\'s suite of certifications. As I often explain it to
14people like my family, it basically means you\'re employed to use your
15knowledge of information systems, regulations, common threats, risks,
16etc. in order to assess an organization\'s current control of their
17risk. If a risk isn\'t controlled (and the company doesn\'t want to
18accept the risk), an IS auditor will suggest implementing a control to
19address that risk.
20
21Now, the CISA certification itself is, in my opinion, the main
22certification for this career. While certifications such as the CPA or
23CISSP are beneficial, nothing matches the power of the CISA for an IS
24auditor when it comes to getting hired, getting a raise/bonus, or
25earning respect in the field.
26
27However, to be honest, I am a skeptic of most certifications. I
28understand the value they hold in terms of how much you need to commit
29to studying or learning on the job, as well as the market value for
30certifications such as the CISA. But I also have known some very
31~~incompetent~~ *less than stellar* auditors who have CPAs, CISAs, CIAs,
32etc.
33
34The same goes for most industries: if a person is good at studying, they
35can earn the certification. However, that knowledge means nothing unless
36you\'re actually able to use it in real life and perform as expected of
37a certification holder. The challenge comes when people are hired or
38connected strictly because of their certifications or resume; you need
39to see a person work before you can assume them having a CISA means
40they\'re better than someone without the CISA.
41
42Okay, rant over. Certifications are generally accepted as a measuring
43stick of commitment and quality of an employee, so I am accepting it
44too.
45
46# Exam Content
47
48The CISA is broken down into five sections, each weighted with a
49percentage of test questions that may appear.
50
51![CISA exam
52sections](https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png)
53
54Since the exam contains 150 questions, here\'s how those sections break
55down:
56
57 Exam Section Percentage of Exam Questions
58 ----------------- -------------------- -----------
59 1 21% 32
60 2 17% 26
61 3 12% 18
62 4 23% 34
63 5 27% 40
64 **Grand Total** **100%** **150**
65
66# My Studying Habits
67
68This part is a little hard for me to break down into specific detail due
69to the craziness of the last year. While I officially purchased my
70studying materials in December 2020 and opened them to \"start
71studying\" in January 2021, I really wasn\'t able to study much due to
72the demands of my job and personal life.
73
74Let me approach this from a few different viewpoints.
75
76## Study Materials
77
78Let\'s start by discussing the study materials I purchased. I\'ll be
79referring to #1 as the CRM and #2 as the QAE.
80
811. [CISA Review Manual, 27th Edition \|
82 Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK)
832. \[\[<https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK>\]\[CISA
84 Review Questions, Answers & Explanations Manual, 12th Edition \|
85 Print\]\]
86
87The CRM is an excellent source of information and could honestly be used
88as a reference for most IS auditors as a learning reference during their
89daily audit responsibilities. However, it is **full\*** of information
90and can be overloading if you\'re not good at filtering out useless
91information while studying.
92
93The QAE is the real star of the show here. This book contains 1000
94questions, separated by exam section, and a practice exam. My only
95complaint about the QAE is that each question is immediately followed
96with the correct answer and explanations below it, which means I had to
97use something to constantly cover the answers while I was studying.
98
99I didn\'t use the online database version of the QAE, but I\'ve heard
100that it\'s easier to use than the printed book. However, it is more
101expensive (\$299 database vs \$129 book) which might be important if
102you\'re paying for materials yourself.
103
104In terms of question difficulty, I felt that the QAE was a good
105representation of the actual exam. I\'ve seen a lot of people online say
106it wasn\'t accurate to the exam or that it was much easier/harder, but I
107disagree with all of those. The exam was fairly similar to the QAE, just
108focusing on whichever topics they chose for my version of the exam.
109
110If you understand the concepts, skim the CRM (and read in-depth on
111topics you struggle with), and use the QAE to continue practicing
112exam-like questions, you should be fine. I didn\'t use any online
113courses, videos, etc. - the ISACA materials are more than enough.
114
115## Studying Process
116
117While I was able to briefly read through sections 1 and 2 in early 2021,
118I had to stop and take a break from February/March to September. I
119switched jobs in September, which allowed me a lot more free time to
120study.
121
122In September, I studied sections 3-5, took notes, and did a quick review
123of the section topics. Once I felt comfortable with my notes, I took a
124practice exam from the QAE manual and scored 70% (105/150).
125
126Here\'s a breakdown of my initial practice exam:
127
128 Exam Section Incorrect Correct Grand Total Percent
129 ------------------- ----------- ----------- ------------- -----------
130 1 8 25 33 76%
131 2 5 20 25 80%
132 3 6 12 18 67%
133 4 10 23 33 70%
134 5 16 25 41 61%
135 **Grand Total\*** **45\*** **105\*** **150\*** **70%\***
136
137As I expected, my toughest sections were related to project management,
138development, implementation, and security.
139
140This just leaves October and November. For these months, I tried to
141practice every few days, doing 10 questions for each section, until the
142exam. This came out to 13 practice sessions, \~140 questions per
143section, and \~700 questions total.
144
145While some practice sessions were worse and some were better, the final
146results were similar to my practice exam results. As you can see below,
147my averages were slightly worse than my practice exam. However, I got in
148over 700 questions of practice and, most importantly, \*I read through
149the explanations every time I answered incorrectly and learned from my
150mistakes\*.
151
152 Exam Section Incorrect Correct Grand Total Percent
153 ------------------- ----------- ----------- ------------- -----------
154 1 33 108 141 77%
155 2 33 109 142 77%
156 3 55 89 144 62%
157 4 52 88 140 63%
158 5 55 85 140 61%
159 **Grand Total\*** **228\*** **479\*** **707\*** **68%\***
160
161![CISA practice question
162results](https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png)
163
164# Results
165
166Now, how do the practice scores reflect my actual results? After all,
167it\'s hard to tell how good a practice regimen is unless you see how it
168turns out.
169
170 Exam Section Section Name Score
171 -------------- ------------------------------------------------------------------ ---------
172 1 Information Systems Auditing Process 678
173 2 Governance and Management of IT 590
174 3 Information Systems Acquisition, Development, and Implementation 721
175 4 Information Systems Operations and Business Resilience 643
176 5 Protection of Information Assets 511
177 **TOTAL** **616**
178
179Now, in order to pass the CISA, you need at least 450 on a sliding scale
180of 200-800. Personally, I really have no clue what an average CISA score
181is. After a *very* brief look online, I can see that the high end is
182usually in the low 700s. In addition, only about 50-60% of people pass
183the exam.
184
185Given this information, I feel great about my scores. 616 may not be
186phenomenal, and I wish I had done better on sections 2 & 5, but my
187practicing seems to have worked very well overall.
188
189However, the practice results do not conform to the actual results.
190Section 2 was one of my highest practice sections and was my
191second-lowest score in the exam. Conversely, section 3 was my
192second-lowest practice section and turned out to be my highest actual
193score!
194
195After reflecting, it is obvious that if you have any background on the
196CISA topics at all, the most important part of studying is doing
197practice questions. You really need to understand how to read the
198questions critically and pick the best answer.
199
200# Looking Forward
201
202I am extremely happy that I was finally able to pass the CISA. Looking
203to the future, I\'m not sure what\'s next in terms of professional
204learning. My current company offers internal learning courses, so I will
205most likely focus on that if I need to gain more knowledge in certain
206areas.
207
208To be fair, even if you pass the CISA, it\'s hard to become an expert on
209any specific topic found within. My career may take me in a different
210direction, and I might need to focus more on security or networking
211certifications (or possibly building a better analysis/visualization
212portfolio if I want to go into data analysis/science).
213
214All I know is that I am content at the moment and extremely proud of my
215accomplishment.
content/blog/2021-12-04-cisa.org deleted −205
@@ -1,205 +0,0 @@
1#+title: I Passed the CISA!
2#+date: 2021-12-04
3#+description: A recap of the CISA certification exam and my results.
4#+filetags: :audit:
5
6* What is the CISA?
7For those of you lucky enough not to be knee-deep in the world of IT/IS
8Auditing, [[https://www.isaca.org/credentialing/cisa][CISA]] stands for
9Certified Information Systems Auditor. This certification and exam are
10part of ISACA's suite of certifications. As I often explain it to people
11like my family, it basically means you're employed to use your knowledge
12of information systems, regulations, common threats, risks, etc. in
13order to assess an organization's current control of their risk. If a
14risk isn't controlled (and the company doesn't want to accept the risk),
15an IS auditor will suggest implementing a control to address that risk.
16
17Now, the CISA certification itself is, in my opinion, the main
18certification for this career. While certifications such as the CPA or
19CISSP are beneficial, nothing matches the power of the CISA for an IS
20auditor when it comes to getting hired, getting a raise/bonus, or
21earning respect in the field.
22
23However, to be honest, I am a skeptic of most certifications. I
24understand the value they hold in terms of how much you need to commit
25to studying or learning on the job, as well as the market value for
26certifications such as the CISA. But I also have known some very
27+incompetent+ /less than stellar/ auditors who have CPAs, CISAs, CIAs,
28etc.
29
30The same goes for most industries: if a person is good at studying, they
31can earn the certification. However, that knowledge means nothing unless
32you're actually able to use it in real life and perform as expected of a
33certification holder. The challenge comes when people are hired or
34connected strictly because of their certifications or resume; you need
35to see a person work before you can assume them having a CISA means
36they're better than someone without the CISA.
37
38Okay, rant over. Certifications are generally accepted as a measuring
39stick of commitment and quality of an employee, so I am accepting it
40too.
41
42* Exam Content
43The CISA is broken down into five sections, each weighted with a
44percentage of test questions that may appear.
45
46#+caption: CISA exam sections
47[[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png]]
48
49Since the exam contains 150 questions, here's how those sections break
50down:
51
52| Exam Section | Percentage of Exam | Questions |
53|---------------+--------------------+-----------|
54| 1 | 21% | 32 |
55| 2 | 17% | 26 |
56| 3 | 12% | 18 |
57| 4 | 23% | 34 |
58| 5 | 27% | 40 |
59| *Grand Total* | *100%* | *150* |
60
61* My Studying Habits
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
71Let's start by discussing the study materials I purchased. I'll be
72referring to #1 as the CRM and #2 as the QAE.
73
741. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK][CISA
75 Review Manual, 27th Edition | Print]]
762. [[[[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK]]][CISA
77 Review Questions, Answers & Explanations Manual, 12th Edition |
78 Print]]
79
80The CRM is an excellent source of information and could honestly be used
81as a reference for most IS auditors as a learning reference during their
82daily audit responsibilities. However, it is *full** of information and
83can be overloading if you're not good at filtering out useless
84information while studying.
85
86The QAE is the real star of the show here. This book contains 1000
87questions, separated by exam section, and a practice exam. My only
88complaint about the QAE is that each question is immediately followed
89with the correct answer and explanations below it, which means I had to
90use something to constantly cover the answers while I was studying.
91
92I didn't use the online database version of the QAE, but I've heard that
93it's easier to use than the printed book. However, it is more expensive
94($299 database vs $129 book) which might be important if you're paying
95for materials yourself.
96
97In terms of question difficulty, I felt that the QAE was a good
98representation of the actual exam. I've seen a lot of people online say
99it wasn't accurate to the exam or that it was much easier/harder, but I
100disagree with all of those. The exam was fairly similar to the QAE, just
101focusing on whichever topics they chose for my version of the exam.
102
103If you understand the concepts, skim the CRM (and read in-depth on
104topics you struggle with), and use the QAE to continue practicing
105exam-like questions, you should be fine. I didn't use any online
106courses, videos, etc. - the ISACA materials are more than enough.
107
108** Studying Process
109While I was able to briefly read through sections 1 and 2 in early 2021,
110I had to stop and take a break from February/March to September. I
111switched jobs in September, which allowed me a lot more free time to
112study.
113
114In September, I studied sections 3-5, took notes, and did a quick review
115of the section topics. Once I felt comfortable with my notes, I took a
116practice exam from the QAE manual and scored 70% (105/150).
117
118Here's a breakdown of my initial practice exam:
119
120| Exam Section | Incorrect | Correct | Grand Total | Percent |
121|---------------+-----------+---------+-------------+---------|
122| 1 | 8 | 25 | 33 | 76% |
123| 2 | 5 | 20 | 25 | 80% |
124| 3 | 6 | 12 | 18 | 67% |
125| 4 | 10 | 23 | 33 | 70% |
126| 5 | 16 | 25 | 41 | 61% |
127| *Grand Total** | *45** | *105** | *150** | *70%** |
128
129As I expected, my toughest sections were related to project management,
130development, implementation, and security.
131
132This just leaves October and November. For these months, I tried to
133practice every few days, doing 10 questions for each section, until the
134exam. This came out to 13 practice sessions, ~140 questions per section,
135and ~700 questions total.
136
137While some practice sessions were worse and some were better, the final
138results were similar to my practice exam results. As you can see below,
139my averages were slightly worse than my practice exam. However, I got in
140over 700 questions of practice and, most importantly, *I read through
141the explanations every time I answered incorrectly and learned from my
142mistakes*.
143
144| Exam Section | Incorrect | Correct | Grand Total | Percent |
145|---------------+-----------+---------+-------------+---------|
146| 1 | 33 | 108 | 141 | 77% |
147| 2 | 33 | 109 | 142 | 77% |
148| 3 | 55 | 89 | 144 | 62% |
149| 4 | 52 | 88 | 140 | 63% |
150| 5 | 55 | 85 | 140 | 61% |
151| *Grand Total** | *228** | *479** | *707** | *68%** |
152
153#+caption: CISA practice question results
154[[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png]]
155
156* Results
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| *TOTAL* | | *616* |
169
170Now, in order to pass the CISA, you need at least 450 on a sliding scale
171of 200-800. Personally, I really have no clue what an average CISA score
172is. After a /very/ brief look online, I can see that the high end is
173usually in the low 700s. In addition, only about 50-60% of people pass
174the exam.
175
176Given this information, I feel great about my scores. 616 may not be
177phenomenal, and I wish I had done better on sections 2 & 5, but my
178practicing seems to have worked very well overall.
179
180However, the practice results do not conform to the actual results.
181Section 2 was one of my highest practice sections and was my
182second-lowest score in the exam. Conversely, section 3 was my
183second-lowest practice section and turned out to be my highest actual
184score!
185
186After reflecting, it is obvious that if you have any background on the
187CISA topics at all, the most important part of studying is doing
188practice questions. You really need to understand how to read the
189questions critically and pick the best answer.
190
191* Looking Forward
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.org → content/blog/2022-02-10-leaving-the-office.md renamed +83 −80
@@ -1,9 +1,12 @@
1#+title: Leaving Office-Based Work in the Past 1+++
2#+date: 2022-02-10 2date = 2022-02-10
3#+description: My thoughts on the current surge of remote work and what that means for full-time office-based roles. 3title = "Leaving Office-Based Work in the Past"
4#+filetags: :audit: 4description = ""
5draft = false
6+++
7
8# The Working World is Changing
5 9
6* The Working World is Changing
7There has been a trend for the past few years of companies slowly 10There has been a trend for the past few years of companies slowly
8realizing that the pandemic is not just a temporary state that will go 11realizing that the pandemic is not just a temporary state that will go
9away eventually and let everything return to the way it was before. In 12away eventually and let everything return to the way it was before. In
@@ -19,28 +22,31 @@ operate in select states).
19 22
20I decided to take a look back at my relatively short career so far and 23I decided to take a look back at my relatively short career so far and
21compare the positive and negative effects of the different work 24compare the positive and negative effects of the different work
22environments I've been in. 25environments I\'ve been in.
26
27# In-Person Offices
28
29## Retail Internship
23 30
24* In-Person Offices
25** Retail Internship
26I started my first job as a management intern at a busy retail pharmacy, 31I started my first job as a management intern at a busy retail pharmacy,
27working my 40-hour weeks on my feet. As these retail stores don't 32working my 40-hour weeks on my feet. As these retail stores don\'t
28believe in resting or sitting down, you can guarantee that you will 33believe in resting or sitting down, you can guarantee that you will
29spend entire shifts standing, walking, or running around the store. 34spend entire shifts standing, walking, or running around the store.
30Unfortunately, I worked at a time when our store didn't have enough 35Unfortunately, I worked at a time when our store didn\'t have enough
31managers, so I spent the majority of my tenure at the store running and 36managers, so I spent the majority of my tenure at the store running and
32breaking a sweat. 37breaking a sweat.
33 38
34Now, things aren't all bad in retail stores like this. It is definitely 39Now, things aren\'t all bad in retail stores like this. It is definitely
35tiring and inefficient to force employees to work constantly, or pretend 40tiring and inefficient to force employees to work constantly, or pretend
36to work if there's nothing to do, and not allow anyone to sit down. 41to work if there\'s nothing to do, and not allow anyone to sit down.
37However, if you are able to operate a retail store with a limited crew 42However, if you are able to operate a retail store with a limited crew
38and provide enough comfort and support, I believe these jobs could be 43and provide enough comfort and support, I believe these jobs could be
39both comfortable and efficient. 44both comfortable and efficient.
40 45
41** Semi-Private Cubicles 46## Semi-Private Cubicles
42#+caption: Semi-Private Cubicles 47
43[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png]] 48![Semi-Private
49Cubicles](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png)
44 50
45After about a year, I was able to find another internship - this time, 51After about a year, I was able to find another internship - this time,
46it was in my field of interest: internal auditing. This was for a life 52it was in my field of interest: internal auditing. This was for a life
@@ -51,22 +57,23 @@ Likewise, they owned a large headquarters in a nice area of town with
51plenty of space, parking, etc. 57plenty of space, parking, etc.
52 58
53One upside is that each person gets their own large L-shaped desk, 59One upside is that each person gets their own large L-shaped desk,
54formed into cubicles that house 4 desks/employees. These "pods" of 60formed into cubicles that house 4 desks/employees. These \"pods\" of
554-person cubicles are linked throughout each floor of the headquarters 614-person cubicles are linked throughout each floor of the headquarters
56(except the sales people, who had that open-floor concept going on). The 62(except the sales people, who had that open-floor concept going on). The
57walls of the cubicle were tall and provided a lot of privacy and 63walls of the cubicle were tall and provided a lot of privacy and
58sound-proofing, except when I used the standing desk feature (I'm over 6 64sound-proofing, except when I used the standing desk feature (I\'m over
59feet tall, so probably not an issue for most people). 656 feet tall, so probably not an issue for most people).
60 66
61I loved this environment, it allowed me to focus on my work with minimal 67I loved this environment, it allowed me to focus on my work with minimal
62distractions, but also allowed easy access, so I could spin around in my 68distractions, but also allowed easy access, so I could spin around in my
63chair and chat with my friends without leaving my chair. This is the 69chair and chat with my friends without leaving my chair. This is the
64closest I've been to a home office environment (which is my personal 70closest I\'ve been to a home office environment (which is my personal
65favorite, as I'll get to later in this post). 71favorite, as I\'ll get to later in this post).
72
73## Semi-Open Floor Concept
66 74
67** Semi-Open Floor Concept 75![Semi-Open Floor
68#+caption: Semi-Open Floor Concept 76Concept](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png)
69[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png]]
70 77
71When I shifted to my first full-time internal audit job out of college, 78When I shifted to my first full-time internal audit job out of college,
72I was working at a company that was headquartered on a floor in a 79I was working at a company that was headquartered on a floor in a
@@ -74,42 +81,44 @@ downtown high-rise building. The company was only about 20 years old
74when I worked there and were trying a lot of new things to attract young 81when I worked there and were trying a lot of new things to attract young
75talent, one of which was a semi-open floor concept for the office. My 82talent, one of which was a semi-open floor concept for the office. My
76department worked just around the hallway corner from the executive 83department worked just around the hallway corner from the executive
77offices and used that "modern" layout young tech companies started using 84offices and used that \"modern\" layout young tech companies started
78in the 2000s/2010s. 85using in the 2000s/2010s.
79 86
80Each desk was brief, and you could look most coworkers in the face 87Each desk was brief, and you could look most coworkers in the face
81without moving from your chair, I hated this so much. Directly to my 88without moving from your chair, I hated this so much. Directly to my
82left was the Chief Audit Executive (our department's leading boss), and 89left was the Chief Audit Executive (our department\'s leading boss), and
83his desk was pointed so that his face would stare straight at my desk 90his desk was pointed so that his face would stare straight at my desk
84all day. I spent more time thinking about who was looking at me or 91all day. I spent more time thinking about who was looking at me or
85checking on me than actually working. 92checking on me than actually working.
86 93
87The other annoying part of the open concept they used was that the 94The other annoying part of the open concept they used was that the
88kitchen area and pathways were too close to everyone's desks (since the 95kitchen area and pathways were too close to everyone\'s desks (since the
89desks were spread out, to provide space or something), so noise and 96desks were spread out, to provide space or something), so noise and
90conversation would be constant throughout the day while you try to work. 97conversation would be constant throughout the day while you try to work.
91For someone like me, who needs silence to get work done, that was a 98For someone like me, who needs silence to get work done, that was a
92non-starter. 99non-starter.
93 100
94** Hotel Office Concept 101## Hotel Office Concept
95#+caption: Hotel Office Concept 102
96[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png]] 103![Hotel Office
104Concept](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png)
97 105
98I currently work for a company remotely (for now) and travel to the 106I currently work for a company remotely (for now) and travel to the
99office every once in a while for events and to help coach the staff 107office every once in a while for events and to help coach the staff
100underneath me. The office I visit uses the hotel desk concept, where you 108underneath me. The office I visit uses the hotel desk concept, where you
101need to check in at a touch screen when you enter the office and "rent" 109need to check in at a touch screen when you enter the office and
102a desk for the day. The same goes for offices and meeting rooms. 110\"rent\" a desk for the day. The same goes for offices and meeting
111rooms.
103 112
104These desks are flat-top only and do not have any walls at all. In 113These desks are flat-top only and do not have any walls at all. In
105addition, they're stacked with one row of 4 desks facing another row of 114addition, they\'re stacked with one row of 4 desks facing another row of
1064 desks. These pairs of desk rows are repeated through the office. 1154 desks. These pairs of desk rows are repeated through the office.
107 116
108This means that when I go, I need to rent a random desk or try to 117This means that when I go, I need to rent a random desk or try to
109remember the unique ID numbers on desks I like. Once I rent it, I have 118remember the unique ID numbers on desks I like. Once I rent it, I have
110to make sure no one sat down in that desk without renting it. Then, I 119to make sure no one sat down in that desk without renting it. Then, I
111can sit down and work, but will probably need to adjust the monitors so 120can sit down and work, but will probably need to adjust the monitors so
112that I'm not staring in the face of the person across from me all day. 121that I\'m not staring in the face of the person across from me all day.
113Finally, I need to wear headphones as this environment does nothing to 122Finally, I need to wear headphones as this environment does nothing to
114provide you with peace or quiet. 123provide you with peace or quiet.
115 124
@@ -117,27 +126,26 @@ Luckily, you can rent offices with doors that offer quiet and privacy,
117which can be very nice if you have a lot of meetings or webinars on a 126which can be very nice if you have a lot of meetings or webinars on a
118certain day. 127certain day.
119 128
120* Home Office 129# Home Office
121#+caption: Home Office
122[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png]]
123 130
124Okay, now let's finally get to the home office concept. I have worked 131![Home
132Office](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png)
133
134Okay, now let\'s finally get to the home office concept. I have worked
125from home for a little over two years at this point, across three 135from home for a little over two years at this point, across three
126different jobs/employers. Over this time, I have experimented with a 136different jobs/employers. Over this time, I have experimented with a
127plethora of different organizational ideas, desks, and room layouts to 137plethora of different organizational ideas, desks, and room layouts to
128find what works best for me. 138find what works best for me.
129 139
130These things might not apply to you, and that's fine. Everyone has a 140These things might not apply to you, and that\'s fine. Everyone has a
131different situation, and I really don't think you'll know what works 141different situation, and I really don\'t think you\'ll know what works
132until you try. 142until you try.
133 143
134** Tip #1 144## Tip #1
135Let's start with my top rule for a home office:
136 145
137#+begin_quote 146Let\'s start with my top rule for a home office:
138If you live with others, working in a shared space is not effective.
139 147
140#+end_quote 148> If you live with others, working in a shared space is not effective.
141 149
142It just does not work. If you have another person sleeping in your 150It just does not work. If you have another person sleeping in your
143bedroom, it is difficult to manage your work schedule with their 151bedroom, it is difficult to manage your work schedule with their
@@ -146,18 +154,16 @@ work, you might wake them up or have to suffer the agony of staring at
146bright screens in a dark room. 154bright screens in a dark room.
147 155
148In a similar vein, working from a location such as the living room 156In a similar vein, working from a location such as the living room
149likely won't work either. Distractions will come far more frequently: 157likely won\'t work either. Distractions will come far more frequently:
150televisions, cooking, cleaning, deliveries, etc. If you're like me, 158televisions, cooking, cleaning, deliveries, etc. If you\'re like me,
151you'll end up playing a game instead of actually doing any work. 159you\'ll end up playing a game instead of actually doing any work.
152 160
153** Tip #2 161## Tip #2
154Okay, the second thing I've discovered that works for me:
155 162
156#+begin_quote 163Okay, the second thing I\'ve discovered that works for me:
157Use the pomodoro method (or something similar) to balance work tasks
158with personal tasks.
159 164
160#+end_quote 165> Use the pomodoro method (or something similar) to balance work tasks
166> with personal tasks.
161 167
162I use a very casual version of the pomodoro method where I will work for 168I use a very casual version of the pomodoro method where I will work for
1631-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and 1691-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and
@@ -165,26 +171,24 @@ then will allow myself 30-60 minutes for personal tasks. This schedule
165works for me, since my work schedule really only comes to 3-6 hours of 171works for me, since my work schedule really only comes to 3-6 hours of
166work per day. 172work per day.
167 173
168In this case, I'll work through my list of tasks for an hour or two and 174In this case, I\'ll work through my list of tasks for an hour or two and
169then give myself personal time to get drinks and food, wash dishes, put 175then give myself personal time to get drinks and food, wash dishes, put
170clothes in the washer, get the mail, etc. If you're in a convenient 176clothes in the washer, get the mail, etc. If you\'re in a convenient
171location, this usually gives time for things like getting groceries (as 177location, this usually gives time for things like getting groceries (as
172long as you're not a slow shopper). 178long as you\'re not a slow shopper).
173 179
174** Tip #3 180## Tip #3
175While I listed this one as number three, I don't think I'd accomplish
176anything without it:
177 181
178#+begin_quote 182While I listed this one as number three, I don\'t think I\'d accomplish
179Document everything: even things you didn't before - such as task lists 183anything without it:
180and notes from casual calls or meetings.
181 184
182#+end_quote 185> Document everything: even things you didn\'t before - such as task
186> lists and notes from casual calls or meetings.
183 187
184I've noticed that staying in an office gave me more constant reminders 188I\'ve noticed that staying in an office gave me more constant reminders
185of outstanding tasks or facts I had learned in a conversation. 189of outstanding tasks or facts I had learned in a conversation.
186Translating everything to a digital world has made me lose a bit of that 190Translating everything to a digital world has made me lose a bit of that
187focus (perhaps since I don't have visual reminders?). 191focus (perhaps since I don\'t have visual reminders?).
188 192
189Keeping a running task list of all things I have to do - even potential 193Keeping a running task list of all things I have to do - even potential
190tasks! - has helped me keep up without missing anything small. Likewise, 194tasks! - has helped me keep up without missing anything small. Likewise,
@@ -193,13 +197,11 @@ has improved my retention immensely. Beyond helping my mental
193recollection, it has saved me numerous times when I need to do a keyword 197recollection, it has saved me numerous times when I need to do a keyword
194search for some topic that was discussed 6+ months ago. 198search for some topic that was discussed 6+ months ago.
195 199
196** Tip #4 200## Tip #4
197Okay, last one for now.
198 201
199#+begin_quote 202Okay, last one for now.
200Keep your work area clean.
201 203
202#+end_quote 204> Keep your work area clean.
203 205
204This one is straightforward, but I know some people struggle with 206This one is straightforward, but I know some people struggle with
205cleanliness or may not believe it makes a difference. Trust me, keeping 207cleanliness or may not believe it makes a difference. Trust me, keeping
@@ -210,20 +212,21 @@ Just think about it, you walk into your home office and see a clean desk
210with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen 212with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen
211on top. 213on top.
212 214
213Now imagine the opposite, there's an office with the same equipment, but 215Now imagine the opposite, there\'s an office with the same equipment,
214there are clothes hanging on the chair, empty drink bottles, candy 216but there are clothes hanging on the chair, empty drink bottles, candy
215wrappers and dirty plates. This can take both a mental and emotional 217wrappers and dirty plates. This can take both a mental and emotional
216toll by bringing constant disarray and stress into your working 218toll by bringing constant disarray and stress into your working
217environment. 219environment.
218 220
219Just keep things clean each day, and you won't need to do any big 221Just keep things clean each day, and you won\'t need to do any big
220cleaning days to recover. 222cleaning days to recover.
221 223
222* My Preferences 224# My Preferences
223I've talked about the different environments I've worked in and 225
226I\'ve talked about the different environments I\'ve worked in and
224expressed some honest thoughts on pros or cons to each, but what do I 227expressed some honest thoughts on pros or cons to each, but what do I
225prefer? Well, if you're reading along, you should be able to tell that I 228prefer? Well, if you\'re reading along, you should be able to tell that
226much prefer a home office above all else. 229I much prefer a home office above all else.
227 230
228Being able to control my own day and allot my time as needed has brought 231Being able to control my own day and allot my time as needed has brought
229a calmness to my life and has allowed me to maximize each day. I feel 232a calmness to my life and has allowed me to maximize each day. I feel
@@ -236,5 +239,5 @@ my work done.
236 239
237Cubicles are good! I agree with Alice (from the comic Dilbert): 240Cubicles are good! I agree with Alice (from the comic Dilbert):
238 241
239#+caption: Dilbert comic strip 242![Dilbert comic
240[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png]] 243strip](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png)
content/blog/2022-02-10-njalla-dns-api.org → content/blog/2022-02-10-njalla-dns-api.md renamed +75 −62
@@ -1,13 +1,16 @@
1#+title: Dynamic DNS with Njalla API 1+++
2#+date: 2022-02-10 2date = 2022-02-10
3#+description: Learn how to dynamically update DNS records for changing IPs with Njalla. 3title = "Dynamic DNS with Njalla API"
4#+filetags: :sysadmin: 4description = ""
5draft = false
6+++
5 7
6* Njalla's API 8# Njalla\'s API
7As noted in my recent post about [[/blog/ditching-cloudflare/][switching 9
8to Njalla from Cloudflare]], I was searching for a way to replace my 10As noted in my recent post about [switching to Njalla from
9very easy-to-use bash script to [[/blog/cloudflare-dns-api/][update 11Cloudflare](file:///blog/ditching-cloudflare/), I was searching for a
10Cloudflare's DNS via their API]]. 12way to replace my very easy-to-use bash script to [update Cloudflare\'s
13DNS via their API](file:///blog/cloudflare-dns-api/).
11 14
12To reiterate what I said in those posts, this is a common necessity for 15To reiterate what I said in those posts, this is a common necessity for
13those of us who have non-static IP addresses that can change at any 16those of us who have non-static IP addresses that can change at any
@@ -15,86 +18,93 @@ moment due to ISP policy.
15 18
16In order to keep a home server running smoothly, the server admin needs 19In order to keep a home server running smoothly, the server admin needs
17to have a process to constantly monitor their public IP address and 20to have a process to constantly monitor their public IP address and
18update their domain's DNS records if it changes. 21update their domain\'s DNS records if it changes.
22
23This post explains how to use Python to update Njalla\'s DNS records
24whenever a machine\'s public IP address changes.
19 25
20This post explains how to use Python to update Njalla's DNS records 26## Creating a Token
21whenever a machine's public IP address changes.
22 27
23** Creating a Token 28To use Njalla\'s API, you will first need to create a token that will be
24To use Njalla's API, you will first need to create a token that will be
25used to authenticate you every time you call the API. Luckily, this is 29used to authenticate you every time you call the API. Luckily, this is
26very easy to do if you have an account with Njalla. 30very easy to do if you have an account with Njalla.
27 31
28Simply go the [[https://njal.la/settings/api/][API Settings]] page and 32Simply go the [API Settings](https://njal.la/settings/api/) page and
29click the =Add Token= button. Next, enter a name for the token and click 33click the `Add Token` button. Next, enter a name for the
30=Add=. 34token and click `Add`.
31 35
32Finally, click the =Manage= button next to your newly created token and 36Finally, click the `Manage` button next to your newly created
33copy the =API Token= field. 37token and copy the `API Token` field.
34 38
35** Finding the Correct API Request 39## Finding the Correct API Request
36Once you have a token, you're ready to call the Njalla API for any 40
41Once you have a token, you\'re ready to call the Njalla API for any
37number of requests. For a full listing of available requests, see the 42number of requests. For a full listing of available requests, see the
38[[https://njal.la/api/][Njalla API Documentation]]. 43[Njalla API Documentation](https://njal.la/api/).
39 44
40For this demo, we are using the =list-records= and =edit-record= 45For this demo, we are using the `list-records` and
41requests. 46`edit-record` requests.
42 47
43The =list-records= request requires the following payload to be sent 48The `list-records` request requires the following payload to
44when calling the API: 49be sent when calling the API:
45 50
46#+begin_src txt 51``` txt
47params: { 52params: {
48 domain: string 53 domain: string
49} 54}
50#+end_src 55```
51 56
52The =edit-record= request requires the following payload to be sent when 57The `edit-record` request requires the following payload to
53calling the API: 58be sent when calling the API:
54 59
55#+begin_src txt 60``` txt
56params: { 61params: {
57 domain: string 62 domain: string
58 id: int 63 id: int
59 content: string 64 content: string
60} 65}
61#+end_src 66```
67
68# Server Set-Up
62 69
63* Server Set-Up
64To create this script, we will be using Python. By default, I use Python 70To create this script, we will be using Python. By default, I use Python
653 on my servers, so please note that I did not test this in Python 2, 713 on my servers, so please note that I did not test this in Python 2,
66and I do not know if Python 2 will work for this. 72and I do not know if Python 2 will work for this.
67 73
68** Creating the Script 74## Creating the Script
75
69First, find a suitable place to create your script. Personally, I just 76First, find a suitable place to create your script. Personally, I just
70create a directory called =ddns= in my home directory: 77create a directory called `ddns` in my home directory:
71 78
72#+begin_src sh 79```sh
73mkdir ~/ddns 80mkdir ~/ddns
74#+end_src 81```
75 82
76Next, create a Python script file: 83Next, create a Python script file:
77 84
78#+begin_src sh 85```sh
79nano ~/ddns/ddns.py 86nano ~/ddns/ddns.py
80#+end_src 87```
81 88
82The following code snippet is quite long, so I won't go into depth on 89The following code snippet is quite long, so I won\'t go into depth on
83each part. However, I suggest you read through the entire script before 90each part. However, I suggest you read through the entire script before
84running it; it is quite simple and contains comments to help explain 91running it; it is quite simple and contains comments to help explain
85each code block. 92each code block.
86 93
87:warning: *Note*: You will need to update the following variables for 94:warning: **Note**: You will need to update the following variables for
88this to work: 95this to work:
89 96
90- =token=: This is the Njalla API token you created earlier. 97- `token`: This is the Njalla API token you created
91- =user_domain=: This is the top-level domain you want to modify. 98 earlier.
92- =include_subdomains=: Set this to =True= if you also want to modify 99- `user_domain`: This is the top-level domain you want to
93 subdomains found under the TLD. 100 modify.
94- =subdomains=: If =include_subdomains= = =True=, you can include your 101- `include_subdomains`: Set this to `True` if
95 list of subdomains to be modified here. 102 you also want to modify subdomains found under the TLD.
96 103- `subdomains`: If `include_subdomains` =
97#+begin_src python 104 `True`, you can include your list of subdomains to be
105 modified here.
106
107``` python
98#!/usr/bin/python 108#!/usr/bin/python
99# -*- coding: utf-8 -*- 109# -*- coding: utf-8 -*-
100# Import Python modules 110# Import Python modules
@@ -173,27 +183,30 @@ for record in data['records']:
173 'does not match Njalla's value of', 183 'does not match Njalla's value of',
174 record['content'], '. Updating...') 184 record['content'], '. Updating...')
175 update_record(user_domain, record['id'], ipv6) 185 update_record(user_domain, record['id'], ipv6)
176#+end_src 186```
177 187
178** Running the Script 188## Running the Script
179Once you've created the script and are ready to test it, run the 189
190Once you\'ve created the script and are ready to test it, run the
180following command: 191following command:
181 192
182#+begin_src sh 193```sh
183python3 ~/ddns/ddns.py 194python3 ~/ddns/ddns.py
184#+end_src 195```
196
197## Setting the Script to Run Automatically
185 198
186** Setting the Script to Run Automatically 199To make sure the scripts run automatically, add it to the
187To make sure the scripts run automatically, add it to the =cron= file so 200`cron` file so that it will run on a schedule. To do this,
188that it will run on a schedule. To do this, open the =cron= file: 201open the `cron` file:
189 202
190#+begin_src sh 203```sh
191crontab -e 204crontab -e
192#+end_src 205```
193 206
194In the cron file, paste the following at the bottom of the editor in 207In the cron file, paste the following at the bottom of the editor in
195order to check the IP every five minutes: 208order to check the IP every five minutes:
196 209
197#+begin_src sh 210```sh
198,*/5 ** ** ** ** python3 /home/<your_username>/ddns/ddns.py 211*/5 ** ** ** ** python3 /home/<your_username>/ddns/ddns.py
199#+end_src 212```
content/blog/2022-02-16-debian-and-nginx.org → content/blog/2022-02-16-debian-and-nginx.md renamed +79 −70
@@ -1,74 +1,78 @@
1#+title: Migrating to a New Web Server Setup with Debian, Nginx, and Agate 1+++
2#+date: 2022-02-16 2date = 2022-02-16
3#+description: A retrospective on my recent server migration. 3title = "Migrating to a New Web Server Setup with Debian, Nginx, and Agate"
4#+filetags: :sysadmin: 4description = ""
5draft = false
6+++
5 7
6* Server OS: Debian 8# Server OS: Debian
7#+caption: Debian + neofetch
8[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png]]
9 9
10I've used various Linux distributions throughout the years, but I've 10![Debian +
11neofetch](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png)
12
13I\'ve used various Linux distributions throughout the years, but I\'ve
11never used anything except Ubuntu for my servers. Why? I really have no 14never used anything except Ubuntu for my servers. Why? I really have no
12idea, mostly just comfort around the commands and software availability. 15idea, mostly just comfort around the commands and software availability.
13 16
14However, I have always wanted to try Debian as a server OS after testing 17However, I have always wanted to try Debian as a server OS after testing
15it out in a VM a few years ago (side-note: I'd love to try Alpine too, 18it out in a VM a few years ago (side-note: I\'d love to try Alpine too,
16but I always struggle with compatibility). So, I decided to launch a new 19but I always struggle with compatibility). So, I decided to launch a new
17VPS and use [[https://www.debian.org][Debian]] 11 as the OS. Spoiler 20VPS and use [Debian](https://www.debian.org) 11 as the OS. Spoiler
18alert: it feels identical to Ubuntu for my purposes. 21alert: it feels identical to Ubuntu for my purposes.
19 22
20I did the normal things when first launching the VPS, such as adding a 23I did the normal things when first launching the VPS, such as adding a
21new user, locking down SSH, etc. If you want to see that level of 24new user, locking down SSH, etc. If you want to see that level of
22detail, read my other post about 25detail, read my other post about [How to Set Up a VPS Web
23[[https://cleberg.net/blog/how-to-set-up-a-vps-web-server/][How to Set 26Server](https://cleberg.net/blog/how-to-set-up-a-vps-web-server/).
24Up a VPS Web Server]].
25 27
26All of this has been similar, apart from small things such as the 28All of this has been similar, apart from small things such as the
27location of users' home folders. No complaints at all from me - Debian 29location of users\' home folders. No complaints at all from me - Debian
28seems great. 30seems great.
29 31
30* Web Server: Nginx 32# Web Server: Nginx
31#+caption: Nginx status 33
32[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png]] 34![Nginx
35status](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png)
33 36
34Once I had the baseline server configuration set-up for Debian, I moved 37Once I had the baseline server configuration set-up for Debian, I moved
35on to trying out [[https://nginx.org][Nginx]] as my web server software. 38on to trying out [Nginx](https://nginx.org) as my web server software.
36This required me to install the =nginx= and =ufw= packages, as well as 39This required me to install the `nginx` and `ufw`
37setting up the initial UFW config: 40packages, as well as setting up the initial UFW config:
38 41
39#+begin_src sh 42```sh
40sudo apt install nginx ufw 43sudo apt install nginx ufw
41sudo ufw allow 'Nginx Full' 44sudo ufw allow 'Nginx Full'
42sudo ufw allow SSH 45sudo ufw allow SSH
43sudo ufw enable 46sudo ufw enable
44sudo ufw status 47sudo ufw status
45sudo systemctl status nginx 48sudo systemctl status nginx
46#+end_src 49```
47 50
48Once I had the firewall set, I moved on to creating the directories and 51Once I had the firewall set, I moved on to creating the directories and
49files for my website. This is very easy and is basically the same as 52files for my website. This is very easy and is basically the same as
50setting up an Apache server, so no struggles here. 53setting up an Apache server, so no struggles here.
51 54
52#+begin_src sh 55```sh
53sudo mkdir -p /var/www/your_domain/html 56sudo mkdir -p /var/www/your_domain/html
54sudo chown -R $USER:$USER /var/www/your_domain/html 57sudo chown -R $USER:$USER /var/www/your_domain/html
55sudo chmod -R 755 /var/www/your_domain 58sudo chmod -R 755 /var/www/your_domain
56nano /var/www/your_domain/html/index.html 59nano /var/www/your_domain/html/index.html
57#+end_src 60```
58 61
59The next part, creating the Nginx configuration files, is quite a bit 62The next part, creating the Nginx configuration files, is quite a bit
60different from Apache. First, you need to create the files in the 63different from Apache. First, you need to create the files in the
61=sites-available= folder and symlink it the =sites-enabled= folder. 64`sites-available` folder and symlink it the
65`sites-enabled` folder.
62 66
63Creating the config file for your domain: 67Creating the config file for your domain:
64 68
65#+begin_src sh 69```sh
66sudo nano /etc/nginx/sites-available/your_domain 70sudo nano /etc/nginx/sites-available/your_domain
67#+end_src 71```
68 72
69Default content for an Nginx config file: 73Default content for an Nginx config file:
70 74
71#+begin_src sh 75```sh
72server { 76server {
73 listen 80; 77 listen 80;
74 listen [::]:80; 78 listen [::]:80;
@@ -82,43 +86,45 @@ server {
82 try_files $uri $uri/ =404; 86 try_files $uri $uri/ =404;
83 } 87 }
84} 88}
85#+end_src 89```
86 90
87Finally, symlink it together: 91Finally, symlink it together:
88 92
89#+begin_src sh 93```sh
90sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/ 94sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
91#+end_src 95```
92 96
93This will make your site available to the public (as long as you have 97This will make your site available to the public (as long as you have
94=your_domain= DNS records pointed at the server's IP address)! 98`your_domain` DNS records pointed at the server\'s IP
99address)!
95 100
96Next, I used [[https://certbot.eff.org/][certbot]] to issue an HTTPS 101Next, I used [certbot](https://certbot.eff.org/) to issue an HTTPS
97certificate for my domains using the following commands: 102certificate for my domains using the following commands:
98 103
99#+begin_src sh 104```sh
100sudo apt install snapd; sudo snap install core; sudo snap refresh core 105sudo apt install snapd; sudo snap install core; sudo snap refresh core
101sudo snap install --classic certbot 106sudo snap install --classic certbot
102sudo ln -s /snap/bin/certbot /usr/bin/certbot 107sudo ln -s /snap/bin/certbot /usr/bin/certbot
103sudo certbot --nginx 108sudo certbot --nginx
104#+end_src 109```
105 110
106Now that certbot ran successfully and updated my Nginx config files to 111Now that certbot ran successfully and updated my Nginx config files to
107include a =443= server block of code, I went back in and edited the 112include a `443` server block of code, I went back in and
108config file to include security HTTP headers. This part is optional, but 113edited the config file to include security HTTP headers. This part is
109is recommended for security purposes; you can even test a website's HTTP 114optional, but is recommended for security purposes; you can even test a
110header security at [[https://securityheaders.com/][Security Headers]]. 115website\'s HTTP header security at [Security
116Headers](https://securityheaders.com/).
111 117
112The configuration below shows a set-up where you only want your website 118The configuration below shows a set-up where you only want your website
113to serve content from its own domain, except for images and scripts, 119to serve content from its own domain, except for images and scripts,
114which may come from =nullitics.com=. All other content would be blocked 120which may come from `nullitics.com`. All other content would
115from loading in a browser. 121be blocked from loading in a browser.
116 122
117#+begin_src sh 123```sh
118sudo nano /etc/nginx/sites-available/your_domain 124sudo nano /etc/nginx/sites-available/your_domain
119#+end_src 125```
120 126
121#+begin_src sh 127```sh
122server { 128server {
123 ... 129 ...
124 add_header Content-Security-Policy "default-src 'none'; img-src 'self' https://nullitics.com; script-src 'self' https://nullitics.com; style-src 'self'; font-src 'self'"; 130 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'";
@@ -129,44 +135,47 @@ server {
129 add_header Referrer-Policy "no-referrer"; 135 add_header Referrer-Policy "no-referrer";
130 ... 136 ...
131} 137}
132#+end_src 138```
133 139
134#+begin_src sh 140```sh
135sudo systemctl restart nginx 141sudo systemctl restart nginx
136#+end_src 142```
143
144## Nginx vs. Apache
137 145
138** Nginx vs. Apache
139As I stated at the beginning, my historical hesitation with trying Nginx 146As I stated at the beginning, my historical hesitation with trying Nginx
140was that the differences in configuration formats scared me away from 147was that the differences in configuration formats scared me away from
141leaving Apache. However, I prefer Nginx to Apache for a few reasons: 148leaving Apache. However, I prefer Nginx to Apache for a few reasons:
142 149
1431. Nginx uses only one config file (=your_domain=) vs. Apache's two-file 1501. Nginx uses only one config file (`your_domain`) vs.
144 approach for HTTP vs. HTTPS (=your_domain.conf= and 151 Apache\'s two-file approach for HTTP vs. HTTPS
145 =your_domain-le-ssl.conf=). 152 (`your_domain.conf` and
1462. Symlinking new configurations files and reloading Nginx are way 153 `your_domain-le-ssl.conf`).
147 easier than Apache's process of having to enable headers with 1542. Symlinking new configurations files and reloading Nginx are way
148 =a2enmod mod_headers=, enable PHP with =a2enmod php= (plus any other 155 easier than Apache\'s process of having to enable headers with
149 mods you need), and then enabling sites with =a2ensite=, and THEN 156 `a2enmod mod_headers`, enable PHP with
150 reloading Apache. 157 `a2enmod php` (plus any other mods you need), and then
1513. The contents of the Nginx config files seem more organized and 158 enabling sites with `a2ensite`, and THEN reloading
152 logical with the curly-bracket approach. This is a minor reason, but 159 Apache.
153 everything just felt cleaner while I was installing my sites and that 1603. The contents of the Nginx config files seem more organized and
154 had a big quality of life impact on the installation for me. 161 logical with the curly-bracket approach. This is a minor reason, but
155 162 everything just felt cleaner while I was installing my sites and
156They're both great software packages, but Nginx just seems more 163 that had a big quality of life impact on the installation for me.
164
165They\'re both great software packages, but Nginx just seems more
157organized and easier to use these days. I will certainly be exploring 166organized and easier to use these days. I will certainly be exploring
158the Nginx docs to see what other fun things I can do with all of this. 167the Nginx docs to see what other fun things I can do with all of this.
159 168
160* Gemini Server: Agate 169# Gemini Server: Agate
161#+caption: Agate status 170
162[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png]] 171![Agate
172status](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png)
163 173
164Finally, I set up the Agate software on this server again to host my 174Finally, I set up the Agate software on this server again to host my
165Gemini server content, using Rust as I have before. You can read my 175Gemini server content, using Rust as I have before. You can read my
166other post for more information on installing Agate: 176other post for more information on installing Agate: [Hosting a Gemini
167[[https://cleberg.net/blog/hosting-a-gemini-server/][Hosting a Gemini 177Server](https://cleberg.net/blog/hosting-a-gemini-server/).
168Server]].
169 178
170All in all, Debian + Nginx is very slick and I prefer it over my old 179All in all, Debian + Nginx is very slick and I prefer it over my old
171combination of Ubuntu + Apache (although it's really just Nginx > Apache 180combination of Ubuntu + Apache (although it\'s really just Nginx \>
172for me, since Debian seems mostly the same as Ubuntu is so far). 181Apache for me, since Debian seems mostly the same as Ubuntu is so far).
content/blog/2022-02-17-exiftool.md added +68
@@ -0,0 +1,68 @@
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
11pictures of very rare birds, and the location metadata is a gold mine
12for poachers, or perhaps you\'re just privacy-oriented like me and
13prefer to strip metadata from publicly-available images.
14
15There are various components of image metadata that you may want to
16delete before releasing a photo to the public. Here\'s an incomplete
17list of things I could easily see just by inspecting a photo on my
18laptop:
19
20- Location (Latitude & Longitude)
21- Dimensions
22- Device Make & Model
23- Color Space
24- Color Profile
25- Focal Length
26- Alpha Channel
27- Red Eye
28- Metering Mode
29- F Number
30
31Regardless of your reasoning, I\'m going to explain how I used the
32`exiftool` package in Linux to automatically strip metadata
33from all images in a directory (+ subdirectories).
34
35## Installing `exiftool`
36
37First things first: we need to install the tool. I\'m running Debian 11
38on my server (Ubuntu will work the same), so the command is as simple
39as:
40
41```sh
42sudo apt install exiftool
43```
44
45There are different tools that can accomplish the same thing across
46distributions, but I really only care to test out this one package.
47
48## Recursively Strip Data
49
50I actually use this tool extensively to strip any photos uploaded to the
51website that serves all the images for my blog
52(`img.cleberg.net`).
53
54The following command is incredibly useful and can be modified to
55include any image extensions that `exiftool` supports:
56
57```sh
58exiftool -r -all= -ext jpg -ext png /path/to/directory/
59```
60
61See below for the results of my most recent usage of
62`exiftool` after I uploaded the image for this blog post. You
63can see that the command will let you know how many directories were
64scanned, how many images were updated, and how many images were
65unchanged.
66
67![exiftool
68results](https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png)
content/blog/2022-02-17-exiftool.org deleted −60
@@ -1,60 +0,0 @@
1#+title: Stripping Image Metadata with exiftool
2#+date: 2022-02-17
3#+description: A simple guide to remove exif data with exiftool.
4#+filetags: :privacy:
5
6** Why Strip Metadata?
7Okay, so you want to strip metadata from your photos. Perhaps you take
8pictures of very rare birds, and the location metadata is a gold mine
9for poachers, or perhaps you're just privacy-oriented like me and prefer
10to strip metadata from publicly-available images.
11
12There are various components of image metadata that you may want to
13delete before releasing a photo to the public. Here's an incomplete list
14of things I could easily see just by inspecting a photo on my laptop:
15
16- Location (Latitude & Longitude)
17- Dimensions
18- Device Make & Model
19- Color Space
20- Color Profile
21- Focal Length
22- Alpha Channel
23- Red Eye
24- Metering Mode
25- F Number
26
27Regardless of your reasoning, I'm going to explain how I used the
28=exiftool= package in Linux to automatically strip metadata from all
29images in a directory (+ subdirectories).
30
31** Installing =exiftool=
32First things first: we need to install the tool. I'm running Debian 11
33on my server (Ubuntu will work the same), so the command is as simple
34as:
35
36#+begin_src sh
37sudo apt install exiftool
38#+end_src
39
40There are different tools that can accomplish the same thing across
41distributions, but I really only care to test out this one package.
42
43** Recursively Strip Data
44I actually use this tool extensively to strip any photos uploaded to the
45website that serves all the images for my blog (=img.cleberg.net=).
46
47The following command is incredibly useful and can be modified to
48include any image extensions that =exiftool= supports:
49
50#+begin_src sh
51exiftool -r -all= -ext jpg -ext png /path/to/directory/
52#+end_src
53
54See below for the results of my most recent usage of =exiftool= after I
55uploaded the image for this blog post. You can see that the command will
56let you know how many directories were scanned, how many images were
57updated, and how many images were unchanged.
58
59#+caption: exiftool results
60[[https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png]]
content/blog/2022-02-20-nginx-caching.md added +73
@@ -0,0 +1,73 @@
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
11that is not dynamic and changing with interactions from the user), you
12would likely benefit from caching that content on the client-side. If
13you\'re used to Apache and looking for the Nginx equivalent, this post
14should help.
15
16Luckily, setting up the cache is as easy as identifying the file types
17you want to cache and determining the expiration length. To include more
18file types, simply use the bar separator (`|`) and type the
19new file extension you want to include.
20
21``` config
22server {
23 ...
24
25 location ~** .(css|js|jpg|jpeg|gif|png|ico)$ {
26 expires 30d;
27 }
28
29 ...
30}
31```
32
33I have seen some people who prefer to set `expires` as
34`365d` or even `max`, but that is only for stable,
35infrequently changing websites. As my site often changes (i.e., I\'m
36never content with my website), I need to know that my readers are
37seeing the new content without waiting too long.
38
39So, I went ahead and set the expiration date at `30d`, which
40is short enough to refresh for readers but long enough that
41clients/browsers won\'t be re-requesting the static files too often,
42hopefully resulting in faster loading times, as images should be the
43only thing slowing down my site.
44
45# Testing Results
46
47To test my changes to the Nginx configuration, I used the [HTTP Header
48Live](https://addons.mozilla.org/en-US/firefox/addon/http-header-live/)
49extension on my Gecko browser and used the sidebar to inspect the
50headers of a recent image from my blog.
51
52In the image below, you can see that the `Cache-Control`
53header is now present and set to 2592000, which is 30 days represented
54in seconds (30 days \_ 24 hours/day \_ 60 minutes/hour \*\* 60
55seconds/minute = 2,592,000 seconds).
56
57The `Expires` field is now showing 22 March 2022, which is 30
58days from the day of this post, 20 February 2022.
59
60![Image
61Headers](https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png)
62
63# Caveats
64
65Remember that this caching system is **client-side**, which means that
66content is only cached for as long as a client allows it. For example,
67my browser purges all caches, data, etc. upon exit, so this caching
68policy will only work as long as my browser remains open and running.
69
70If you need to test updates to your site, you\'ll need to clear the
71cache to see updates for any file extension you configured. This can
72often be done with the `Shift + F5` or `Ctrl + F5`
73key combinations in most browsers.
content/blog/2022-02-20-nginx-caching.org deleted −68
@@ -1,68 +0,0 @@
1#+title: Caching Static Content with Nginx
2#+date: 2022-02-20
3#+description: Learn how to enable the static content cache in Nginx.
4#+filetags: :nginx:
5
6* Update Your Nginx Config to Cache Static Files
7If you run a website on Nginx that serves static content (i.e., content
8that is not dynamic and changing with interactions from the user), you
9would likely benefit from caching that content on the client-side. If
10you're used to Apache and looking for the Nginx equivalent, this post
11should help.
12
13Luckily, setting up the cache is as easy as identifying the file types
14you want to cache and determining the expiration length. To include more
15file types, simply use the bar separator (=|=) and type the new file
16extension you want to include.
17
18#+begin_src config
19server {
20 ...
21
22 location ~** .(css|js|jpg|jpeg|gif|png|ico)$ {
23 expires 30d;
24 }
25
26 ...
27}
28#+end_src
29
30I have seen some people who prefer to set =expires= as =365d= or even
31=max=, but that is only for stable, infrequently changing websites. As
32my site often changes (i.e., I'm never content with my website), I need
33to know that my readers are seeing the new content without waiting too
34long.
35
36So, I went ahead and set the expiration date at =30d=, which is short
37enough to refresh for readers but long enough that clients/browsers
38won't be re-requesting the static files too often, hopefully resulting
39in faster loading times, as images should be the only thing slowing down
40my site.
41
42* Testing Results
43To test my changes to the Nginx configuration, I used the
44[[https://addons.mozilla.org/en-US/firefox/addon/http-header-live/][HTTP
45Header Live]] extension on my Gecko browser and used the sidebar to
46inspect the headers of a recent image from my blog.
47
48In the image below, you can see that the =Cache-Control= header is now
49present and set to 2592000, which is 30 days represented in seconds (30
50days _ 24 hours/day _ 60 minutes/hour ** 60 seconds/minute = 2,592,000
51seconds).
52
53The =Expires= field is now showing 22 March 2022, which is 30 days from
54the day of this post, 20 February 2022.
55
56#+caption: Image Headers
57[[https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png]]
58
59* Caveats
60Remember that this caching system is *client-side*, which means that
61content is only cached for as long as a client allows it. For example,
62my browser purges all caches, data, etc. upon exit, so this caching
63policy will only work as long as my browser remains open and running.
64
65If you need to test updates to your site, you'll need to clear the cache
66to see updates for any file extension you configured. This can often be
67done with the =Shift + F5= or =Ctrl + F5= key combinations in most
68browsers.
content/blog/2022-02-22-tuesday.md added +42
@@ -0,0 +1,42 @@
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
11about something a little more enjoyable today.
12
13Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
14Twosday.
15
16Let\'s look at everything that fell in place today:
17
181. Written in the `m-dd-yy` or `dd-m-yy` formats,
19 today is 2-22-22 or 22-2-22, which is a neat little palindrome in
20 either format. (The last ubiquitous six-digit palindrome was
21 1-11-11.)
222. Today is Tuesday, which is why everyone is using the nickname
23 Twosday to call out these similarities.
243. Falling on Tuesday means today is the 2nd day of the week (for most
25 cultures. For the US, it\'s the 3rd day of the week since we start
26 on Sunday).
274. The only culture I could find with a connection to a `2`
28 is that some Slavic languages derived their version of Tuesday from
29 the Old Church Slavonic word `въторъ`, meaning \"the
30 second.\"
315. Written in the classic monospaced, digital font (think of digital
32 clocks from the 80s/90s), there is nice symmetry to the numbers
33 ([view the image
34 here](https://img.cleberg.net/blog/20220222-tuesday/digital_font.png)!).
356. This one isn\'t naturally-occurring, but it seems people around the
36 world are celebrating the day. For example, a group is putting
37 together
38 \[\[<https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597>\]\[a
39 wedding of 222 couples at the California State Capitol in
40 Sacramento\]\], concluding at exactly 2:22 PM. These couples will
41 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
42 for \$222.22.
content/blog/2022-02-22-tuesday.org deleted −37
@@ -1,37 +0,0 @@
1#+title: Tuesday
2#+date: 2022-02-22
3#+description: 2-22-22 || 22-2-22
4#+filetags: :personal:
5
6* Tuesday, Twosday
7I'm taking a break from my usual technology-related posts and writing
8about something a little more enjoyable today.
9
10Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
11Twosday.
12
13Let's look at everything that fell in place today:
14
151. Written in the =m-dd-yy= or =dd-m-yy= formats, today is 2-22-22 or
16 22-2-22, which is a neat little palindrome in either format. (The
17 last ubiquitous six-digit palindrome was 1-11-11.)
182. Today is Tuesday, which is why everyone is using the nickname Twosday
19 to call out these similarities.
203. Falling on Tuesday means today is the 2nd day of the week (for most
21 cultures. For the US, it's the 3rd day of the week since we start on
22 Sunday).
234. The only culture I could find with a connection to a =2= is that some
24 Slavic languages derived their version of Tuesday from the Old Church
25 Slavonic word =въторъ=, meaning "the second."
265. Written in the classic monospaced, digital font (think of digital
27 clocks from the 80s/90s), there is nice symmetry to the numbers
28 ([[https://img.cleberg.net/blog/20220222-tuesday/digital_font.png][view
29 the image here]]!).
306. This one isn't naturally-occurring, but it seems people around the
31 world are celebrating the day. For example, a group is putting
32 together
33 [[[[https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597]]][a
34 wedding of 222 couples at the California State Capitol in
35 Sacramento]], concluding at exactly 2:22 PM. These couples will
36 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
37 for $222.22.
content/blog/2022-03-02-reliable-notes.org → content/blog/2022-03-02-reliable-notes.md renamed +56 −55
@@ -1,17 +1,17 @@
1#+title: Easy, Reliable Note-Taking 1+++
2#+date: 2022-03-02 2date = 2022-03-02
3#+description: My personal preferences to ensure notes are clear, portable, and long-lasting. 3title = "Easy, Reliable Note-Taking"
4#+filetags: :personal: 4description = ""
5draft = false
6+++
5 7
6* Choosing Durable File Formats 8# Choosing Durable File Formats
7 9
8#+begin_quote 10> TL;DR: Write in a format that can be easily rendered and read in
9TL;DR: Write in a format that can be easily rendered and read in 11> plain-text mode (e.g., `.txt`, `.md`, etc.).
10plain-text mode (e.g., =.txt=, =.md=, etc.).
11#+end_quote
12 12
13As I've written more and more over the years, I've found that my love of 13As I\'ve written more and more over the years, I\'ve found that my love
14note-taking is always growing. Everything I learn or need to remember 14of note-taking is always growing. Everything I learn or need to remember
15can be written down in a note and saved digitally, with no cost to 15can be written down in a note and saved digitally, with no cost to
16myself. Unlike paper copies that need physical storage space, digital 16myself. Unlike paper copies that need physical storage space, digital
17files simply need space on your local disk or cloud storage, which is 17files simply need space on your local disk or cloud storage, which is
@@ -23,8 +23,8 @@ styling. This meant that I had to go through each note during migration
23and edit the file to look presentable again. 23and edit the file to look presentable again.
24 24
25For the last year or two, I have written everything exclusively in 25For the last year or two, I have written everything exclusively in
26[[https://en.wikipedia.org/wiki/Markdown][Markdown]] format. Small 26[Markdown](https://en.wikipedia.org/wiki/Markdown) format. Small notes,
27notes, long-form writings, and even these blog posts are all written in 27long-form writings, and even these blog posts are all written in
28Markdown. 28Markdown.
29 29
30Why Markdown? While I do appreciate the simplicity of plain-text files 30Why Markdown? While I do appreciate the simplicity of plain-text files
@@ -32,18 +32,19 @@ without any formatting, I often need visual cues such as heading and
32code blocks to keep my thoughts straight. Markdown provides a minimal 32code blocks to keep my thoughts straight. Markdown provides a minimal
33set of styling indicators for me to style my notes without adding any 33set of styling indicators for me to style my notes without adding any
34proprietary, embedded data into the files. If I want a top-level 34proprietary, embedded data into the files. If I want a top-level
35heading, I simply add a hash (=#=) before the line. An added bonus is 35heading, I simply add a hash (`#`) before the line. An added
36that even if a system doesn't understand Markdown, it will render it as 36bonus is that even if a system doesn\'t understand Markdown, it will
37plain-text and I can read it just as easily. 37render it as plain-text and I can read it just as easily.
38 38
39For example, here's how TextEdit on macOS will open and display a 39For example, here\'s how TextEdit on macOS will open and display a
40Markdown file in plain-text, since it does contain any features to 40Markdown file in plain-text, since it does contain any features to
41preview Markdown as HTML: 41preview Markdown as HTML:
42 42
43#+caption: Plain Text Markdown 43![Plain Text
44[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png]] 44Markdown](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png)
45
46## Saving & Syncing Files
45 47
46** Saving & Syncing Files
47In order to read and edit my notes across platforms, I use my personal 48In order to read and edit my notes across platforms, I use my personal
48cloud storage through Tresorit due to its native integration with macOS 49cloud storage through Tresorit due to its native integration with macOS
49and iOS file managers. In addition, Tresorit works well on Debian-based 50and iOS file managers. In addition, Tresorit works well on Debian-based
@@ -59,17 +60,19 @@ me to sync anything manually or kick-off a sync job to update my files.
59This means that I can edit on mobile, and it takes about 5-10 seconds to 60This means that I can edit on mobile, and it takes about 5-10 seconds to
60see the changes on desktop. 61see the changes on desktop.
61 62
62*** Version Control with Git 63### Version Control with Git
63A different approach I've contemplated is storing my notes and 64
65A different approach I\'ve contemplated is storing my notes and
64attachments is using a hosted Git repository to track changes to the 66attachments is using a hosted Git repository to track changes to the
65files. However, I don't want to rely on an external service that could 67files. However, I don\'t want to rely on an external service that could
66potentially see into my data, even if the repository is private. 68potentially see into my data, even if the repository is private.
67 69
68I might just do =git init= locally and then commit my changes each time 70I might just do `git init` locally and then commit my changes
69I write or update a note, but that seems to be a lot of work just for 71each time I write or update a note, but that seems to be a lot of work
70tracking changes - which I don't necessarily care to know. 72just for tracking changes - which I don\'t necessarily care to know.
73
74### Backups!
71 75
72*** Backups!
73One small addition to the storage/sync conversation is the idea of 76One small addition to the storage/sync conversation is the idea of
74backups. Personally, I manually create periodic backups of my entire 77backups. Personally, I manually create periodic backups of my entire
75cloud storage, compress it into an archive, and store it on my home 78cloud storage, compress it into an archive, and store it on my home
@@ -79,46 +82,44 @@ To improve my workflow, I am going to be exploring options to
79automatically compress the mounted cloud directory and send it over to 82automatically compress the mounted cloud directory and send it over to
80my server on a set schedule. 83my server on a set schedule.
81 84
82** Writing on Desktop 85## Writing on Desktop
83 86
84#+begin_quote 87> **Update (06.14.22)**: Since writing this post, I have reverted to
85*Update (06.14.22)*: Since writing this post, I have reverted to simply 88> simply keeping my `notes` folder open and opening notes
86keeping my =notes= folder open and opening notes individually in 89> individually in TextEdit for a more minimal and relaxing writing
87TextEdit for a more minimal and relaxing writing experience on the 90> experience on the desktop.
88desktop.
89
90#+end_quote
91 91
92The bulk of my writing occurs in a desktop environment, with a full 92The bulk of my writing occurs in a desktop environment, with a full
93keyboard layout and wide screen. I don't illustrate with a smart pen, I 93keyboard layout and wide screen. I don\'t illustrate with a smart pen, I
94rarely use embedded images, and I love being able to see all of my 94rarely use embedded images, and I love being able to see all of my
95notes/directories in a sidebar. 95notes/directories in a sidebar.
96 96
97With this simple set of requirements, I chose 97With this simple set of requirements, I chose
98[[https://obsidian.md][Obsidian]] as my desktop text editor. Obsidian 98[Obsidian](https://obsidian.md) as my desktop text editor. Obsidian has
99has some in-depth tools like a graph view, command palette, mentions, 99some in-depth tools like a graph view, command palette, mentions, etc.,
100etc., but I've found that using it as a simple Markdown editor is 100but I\'ve found that using it as a simple Markdown editor is incredibly
101incredibly easy and straightforward. 101easy and straightforward.
102 102
103Here's an example of how my Markdown notes look when opened in 103Here\'s an example of how my Markdown notes look when opened in
104plain-text mode: 104plain-text mode:
105 105
106#+caption: Obsidian Markdown Source Mode 106![Obsidian Markdown Source
107[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png]] 107Mode](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png)
108
109Here\'s the \"live preview\" version, where the Markdown is rendered
110into its HTML format:
108 111
109Here's the "live preview" version, where the Markdown is rendered into 112![Obsidian Markdown Live
110its HTML format: 113Preview](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png)
111 114
112#+caption: Obsidian Markdown Live Preview 115### Programming on Desktop
113[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png]]
114 116
115*** Programming on Desktop 117While I was writing this, I realized I should specify that I don\'t use
116While I was writing this, I realized I should specify that I don't use
117the same editor for writing notes and for writing code. For programming 118the same editor for writing notes and for writing code. For programming
118purposes, I use [[https://vscodium.com][VSCodium]] as my development 119purposes, I use [VSCodium](https://vscodium.com) as my development IDE.
119IDE. 120
121## Writing on Mobile
120 122
121** Writing on Mobile
122Personally, I write very little on mobile, except when needing to take 123Personally, I write very little on mobile, except when needing to take
123important notes on-the-go. Any long-form writing, journals, etc. are 124important notes on-the-go. Any long-form writing, journals, etc. are
124done at home, where I always have my laptop available. 125done at home, where I always have my laptop available.
@@ -126,9 +127,9 @@ done at home, where I always have my laptop available.
126I wanted a simple and foolproof editor for iOS, preferably open-source. 127I wanted a simple and foolproof editor for iOS, preferably open-source.
127After a long journey of testing the few (& terrible) open-source iOS 128After a long journey of testing the few (& terrible) open-source iOS
128note-taking apps, I finally found a phenomenal one: 129note-taking apps, I finally found a phenomenal one:
129[[https://github.com/simonbs/runestone][Runestone]]. This app is 130[Runestone](https://github.com/simonbs/runestone). This app is fantastic
130fantastic for note-taking, has plenty of optional features, and 131for note-taking, has plenty of optional features, and integrates
131integrates natively with the iOS file manager. 132natively with the iOS file manager.
132 133
133This app opens the iOS file manager and allows you to click any file you 134This app opens the iOS file manager and allows you to click any file you
134want, opens it up in an editor, and lets me save and close out of that 135want, opens it up in an editor, and lets me save and close out of that
content/blog/2022-03-03-financial-database.md added +272
@@ -0,0 +1,272 @@
1+++
2date = 2022-03-03
3title = "Maintaining a Personal Financial Database"
4description = ""
5draft = false
6+++
7
8# Personal Financial Tracking
9
10For the last 6-ish years, I\'ve tracked my finances in a spreadsheet.
11This is common practice in the business world, but any good dev will
12cringe at the thought of storing long-term data in a spreadsheet. A
13spreadsheet is not for long-term storage or as a source of data to pull
14data/reports.
15
16As I wanted to expand the functionality of my financial data (e.g.,
17adding more reports), I decided to migrate the data into a database. To
18run reports, I would query the database and use a language like Python
19or Javascript to process the data, perform calculations, and visualize
20the data.
21
22# SQLite
23
24When choosing the type of database I wanted to use for this project, I
25was split between three options:
26
271. MySQL: The database I have the most experience with and have used
28 for years.
292. PostgreSQL: A database I\'m new to, but want to learn.
303. SQLite: A database that I\'ve used for a couple projects and have
31 moderate experience.
32
33I ended up choosing SQLite since it can be maintained within a single
34`.sqlite` file, which allows me more flexibility for storage
35and backup. I keep this file in my cloud storage and pull it up whenever
36needed.
37
38## GUI Editing
39
40Since I didn\'t want to try and import 1000--1500 records into my new
41database via the command line, I opted to use [DB Browser for SQLite
42(DB4S)](https://sqlitebrowser.org/) as a GUI tool. This application is
43excellent, and I don\'t see myself going back to the CLI when working in
44this database.
45
46DB4S allows you to copy a range of cells from a spreadsheet and paste it
47straight into the SQL table. I used this process for all 36 accounts,
481290 account statements, and 126 pay statements. Overall, I\'m guessing
49this took anywhere between 4--8 hours. In comparison, it probably took
50me 2-3 days to initially create the spreadsheet.
51
52![DB4S](https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png)
53
54## Schema
55
56The schema for this database is actually extremely simple and involves
57only three tables (for now):
58
591. Accounts
602. Statements
613. Payroll
62
63**Accounts**
64
65The Accounts table contains summary information about an account, such
66as a car loan or a credit card. By viewing this table, you can find
67high-level data, such as interest rate, credit line, or owner.
68
69``` sql
70CREATE TABLE "Accounts" (
71 "AccountID" INTEGER NOT NULL UNIQUE,
72 "AccountType" TEXT,
73 "AccountName" TEXT,
74 "InterestRate" NUMERIC,
75 "CreditLine" NUMERIC,
76 "State" TEXT,
77 "Owner" TEXT,
78 "Co-Owner" TEXT,
79 PRIMARY KEY("AccountID" AUTOINCREMENT)
80)
81```
82
83**Statements**
84
85The Statements table uses the same unique identifier as the Accounts
86table, meaning you can join the tables to find a monthly statement for
87any of the accounts listed in the Accounts table. Each statement has an
88account ID, statement date, and total balance.
89
90``` sql
91CREATE TABLE "Statements" (
92 "StatementID" INTEGER NOT NULL UNIQUE,
93 "AccountID" INTEGER,
94 "StatementDate" INTEGER,
95 "Balance" NUMERIC,
96 PRIMARY KEY("StatementID" AUTOINCREMENT),
97 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
98)
99```
100
101**Payroll**
102
103The Payroll table is a separate entity, unrelated to the Accounts or
104Statements tables. This table contains all information you would find on
105a pay statement from an employer. As you change employers or obtain new
106perks/benefits, just add new columns to adapt to the new data.
107
108``` sql
109CREATE TABLE "Payroll" (
110 "PaycheckID" INTEGER NOT NULL UNIQUE,
111 "PayDate" TEXT,
112 "Payee" TEXT,
113 "Employer" TEXT,
114 "JobTitle" TEXT,
115 "IncomeRegular" NUMERIC,
116 "IncomePTO" NUMERIC,
117 "IncomeHoliday" NUMERIC,
118 "IncomeBonus" NUMERIC,
119 "IncomePTOPayout" NUMERIC,
120 "IncomeReimbursements" NUMERIC,
121 "FringeHSA" NUMERIC,
122 "FringeStudentLoan" NUMERIC,
123 "Fringe401k" NUMERIC,
124 "PreTaxMedical" NUMERIC,
125 "PreTaxDental" NUMERIC,
126 "PreTaxVision" NUMERIC,
127 "PreTaxLifeInsurance" NUMERIC,
128 "PreTax401k" NUMERIC,
129 "PreTaxParking" NUMERIC,
130 "PreTaxStudentLoan" NUMERIC,
131 "PreTaxOther" NUMERIC,
132 "TaxFederal" NUMERIC,
133 "TaxSocial" NUMERIC,
134 "TaxMedicare" NUMERIC,
135 "TaxState" NUMERIC,
136 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
137)
138```
139
140## Python Reporting
141
142Once I created the database tables and imported all my data, the only
143step left was to create a process to report and visualize on various
144aspects of the data.
145
146In order to explore and create the reports I\'m interested in, I
147utilized a two-part process involving Jupyter Notebooks and Python
148scripts.
149
1501. Step 1: Jupyter Notebooks
151
152 When I need to explore data, try different things, and re-run my
153 code cell-by-cell, I use Jupyter Notebooks. For example, I explored
154 the `Accounts` table until I found the following useful
155 information:
156
157 ``` python
158 import sqlite3
159 import pandas as pd
160 import matplotlib
161
162 # Set up database filename and connect
163 db = "finances.sqlite"
164 connection = sqlite3.connect(db)
165 df = pd.read_sql_query("SELECT ** FROM Accounts", connection)
166
167 # Set global matplotlib variables
168 %matplotlib inline
169 matplotlib.rcParams['text.color'] = 'white'
170 matplotlib.rcParams['axes.labelcolor'] = 'white'
171 matplotlib.rcParams['xtick.color'] = 'white'
172 matplotlib.rcParams['ytick.color'] = 'white'
173 matplotlib.rcParams['legend.labelcolor'] = 'black'
174
175 # Display graph
176 df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
177 ```
178
1792. Step 2: Python Scripts
180
181 Once I explored enough through the notebooks and had a list of
182 reports I wanted, I moved on to create a Python project with the
183 following structure:
184
185 ``` txt
186 finance/
187 ├── notebooks/
188 │ │ ├── account_summary.ipynb
189 │ │ ├── account_details.ipynb
190 │ │ └── payroll.ipynb
191 ├── public/
192 │ │ ├── image-01.png
193 │ │ └── image-0X.png
194 ├── src/
195 │ └── finance.sqlite
196 ├── venv/
197 ├── _init.py
198 ├── database.py
199 ├── process.py
200 ├── requirements.txt
201 └── README.md
202 ```
203
204 This structure allows me to:
205
206 1. Compile all required python packages into
207 `requirements.txt` for easy installation if I move to
208 a new machine.
209 2. Activate a virtual environment in `venv/` so I don\'t
210 need to maintain a system-wide Python environment just for this
211 project.
212 3. Keep my `notebooks/` folder to continuously explore
213 the data as I see fit.
214 4. Maintain a local copy of the database in `src/` for
215 easy access.
216 5. Export reports, images, HTML files, etc. to
217 `public/`.
218
219 Now, onto the differences between the code in a Jupyter Notebook and
220 the actual Python files. To create the report in the Notebook
221 snippet above, I created the following function inside
222 `process.py`:
223
224 ``` python
225 # Create summary pie chart
226 def summary_data(accounts: pandas.DataFrame) -> None:
227 accounts_01 = accounts[accounts["Owner"] == "Person01"]
228 accounts_02 = accounts[accounts["Owner"] == "Person02"]
229 for x in range(1, 4):
230 if x == 1:
231 df = accounts
232 account_string = "All Accounts"
233 elif x == 2:
234 df = accounts_01
235 account_string = "Person01's Accounts"
236 elif x == 3:
237 df = accounts_02
238 account_string = "Person02's Accounts"
239 print(f"Generating pie chart summary image for {account_string}...")
240 summary_chart = (
241 df.groupby(["AccountType"])
242 .sum()
243 .plot.pie(
244 title=f"Credit Line by Type for {account_string}",
245 y="CreditLine",
246 autopct="%1.1f%%",
247 )
248 )
249 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
250 ```
251
252 The result? A high-quality pie chart that is read directly by the
253 `public/index.html` template I use.
254
255 ![Summary Pie
256 Chart](https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png)
257
258 Other charts generated by this project include:
259
260 - Charts of account balances over time.
261 - Line chart of effective tax rate (taxes divided by taxable
262 income).
263 - Salary projections and error limits using past income and
264 inflation rates.
265 - Multi-line chart of gross income, taxable income, and net
266 income.
267
268 The best thing about this project? I can improve it at any given
269 time, shaping it into whatever helps me the most for that time. I
270 imagine that I will be introducing an asset tracking table soon to
271 track the depreciating value of cars, houses, etc. Who knows what\'s
272 next?
content/blog/2022-03-03-financial-database.org deleted −256
@@ -1,256 +0,0 @@
1#+title: Maintaining a Personal Financial Database
2#+date: 2022-03-03
3#+description: An example project showing to build and maintain a simple financial database.
4#+filetags: :personal:
5
6* Personal Financial Tracking
7For the last 6-ish years, I've tracked my finances in a spreadsheet.
8This is common practice in the business world, but any good dev will
9cringe at the thought of storing long-term data in a spreadsheet. A
10spreadsheet is not for long-term storage or as a source of data to pull
11data/reports.
12
13As I wanted to expand the functionality of my financial data (e.g.,
14adding more reports), I decided to migrate the data into a database. To
15run reports, I would query the database and use a language like Python
16or Javascript to process the data, perform calculations, and visualize
17the data.
18
19* SQLite
20When choosing the type of database I wanted to use for this project, I
21was split between three options:
22
231. MySQL: The database I have the most experience with and have used for
24 years.
252. PostgreSQL: A database I'm new to, but want to learn.
263. SQLite: A database that I've used for a couple projects and have
27 moderate experience.
28
29I ended up choosing SQLite since it can be maintained within a single
30=.sqlite= file, which allows me more flexibility for storage and backup.
31I keep this file in my cloud storage and pull it up whenever needed.
32
33** GUI Editing
34Since I didn't want to try and import 1000--1500 records into my new
35database via the command line, I opted to use
36[[https://sqlitebrowser.org/][DB Browser for SQLite (DB4S)]] as a GUI
37tool. This application is excellent, and I don't see myself going back
38to the CLI when working in this database.
39
40DB4S allows you to copy a range of cells from a spreadsheet and paste it
41straight into the SQL table. I used this process for all 36 accounts,
421290 account statements, and 126 pay statements. Overall, I'm guessing
43this took anywhere between 4--8 hours. In comparison, it probably took
44me 2-3 days to initially create the spreadsheet.
45
46#+caption: DB4S
47[[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png]]
48
49** Schema
50The schema for this database is actually extremely simple and involves
51only three tables (for now):
52
531. Accounts
542. Statements
553. Payroll
56
57*Accounts*
58
59The Accounts table contains summary information about an account, such
60as a car loan or a credit card. By viewing this table, you can find
61high-level data, such as interest rate, credit line, or owner.
62
63#+begin_src sql
64CREATE TABLE "Accounts" (
65 "AccountID" INTEGER NOT NULL UNIQUE,
66 "AccountType" TEXT,
67 "AccountName" TEXT,
68 "InterestRate" NUMERIC,
69 "CreditLine" NUMERIC,
70 "State" TEXT,
71 "Owner" TEXT,
72 "Co-Owner" TEXT,
73 PRIMARY KEY("AccountID" AUTOINCREMENT)
74)
75#+end_src
76
77*Statements*
78
79The Statements table uses the same unique identifier as the Accounts
80table, meaning you can join the tables to find a monthly statement for
81any of the accounts listed in the Accounts table. Each statement has an
82account ID, statement date, and total balance.
83
84#+begin_src sql
85CREATE TABLE "Statements" (
86 "StatementID" INTEGER NOT NULL UNIQUE,
87 "AccountID" INTEGER,
88 "StatementDate" INTEGER,
89 "Balance" NUMERIC,
90 PRIMARY KEY("StatementID" AUTOINCREMENT),
91 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
92)
93#+end_src
94
95*Payroll*
96
97The Payroll table is a separate entity, unrelated to the Accounts or
98Statements tables. This table contains all information you would find on
99a pay statement from an employer. As you change employers or obtain new
100perks/benefits, just add new columns to adapt to the new data.
101
102#+begin_src sql
103CREATE TABLE "Payroll" (
104 "PaycheckID" INTEGER NOT NULL UNIQUE,
105 "PayDate" TEXT,
106 "Payee" TEXT,
107 "Employer" TEXT,
108 "JobTitle" TEXT,
109 "IncomeRegular" NUMERIC,
110 "IncomePTO" NUMERIC,
111 "IncomeHoliday" NUMERIC,
112 "IncomeBonus" NUMERIC,
113 "IncomePTOPayout" NUMERIC,
114 "IncomeReimbursements" NUMERIC,
115 "FringeHSA" NUMERIC,
116 "FringeStudentLoan" NUMERIC,
117 "Fringe401k" NUMERIC,
118 "PreTaxMedical" NUMERIC,
119 "PreTaxDental" NUMERIC,
120 "PreTaxVision" NUMERIC,
121 "PreTaxLifeInsurance" NUMERIC,
122 "PreTax401k" NUMERIC,
123 "PreTaxParking" NUMERIC,
124 "PreTaxStudentLoan" NUMERIC,
125 "PreTaxOther" NUMERIC,
126 "TaxFederal" NUMERIC,
127 "TaxSocial" NUMERIC,
128 "TaxMedicare" NUMERIC,
129 "TaxState" NUMERIC,
130 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
131)
132#+end_src
133
134** Python Reporting
135Once I created the database tables and imported all my data, the only
136step left was to create a process to report and visualize on various
137aspects of the data.
138
139In order to explore and create the reports I'm interested in, I utilized
140a two-part process involving Jupyter Notebooks and Python scripts.
141
1421. Step 1: Jupyter Notebooks
143
144 When I need to explore data, try different things, and re-run my code
145 cell-by-cell, I use Jupyter Notebooks. For example, I explored the
146 =Accounts= table until I found the following useful information:
147
148 #+begin_src python
149 import sqlite3
150 import pandas as pd
151 import matplotlib
152
153 # Set up database filename and connect
154 db = "finances.sqlite"
155 connection = sqlite3.connect(db)
156 df = pd.read_sql_query("SELECT ** FROM Accounts", connection)
157
158 # Set global matplotlib variables
159 %matplotlib inline
160 matplotlib.rcParams['text.color'] = 'white'
161 matplotlib.rcParams['axes.labelcolor'] = 'white'
162 matplotlib.rcParams['xtick.color'] = 'white'
163 matplotlib.rcParams['ytick.color'] = 'white'
164 matplotlib.rcParams['legend.labelcolor'] = 'black'
165
166 # Display graph
167 df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
168 #+end_src
169
1702. Step 2: Python Scripts
171
172 Once I explored enough through the notebooks and had a list of
173 reports I wanted, I moved on to create a Python project with the
174 following structure:
175
176 #+begin_src txt
177 finance/
178 ├── notebooks/
179 │ │ ├── account_summary.ipynb
180 │ │ ├── account_details.ipynb
181 │ │ └── payroll.ipynb
182 ├── public/
183 │ │ ├── image-01.png
184 │ │ └── image-0X.png
185 ├── src/
186 │ └── finance.sqlite
187 ├── venv/
188 ├── _init.py
189 ├── database.py
190 ├── process.py
191 ├── requirements.txt
192 └── README.md
193 #+end_src
194
195 This structure allows me to:
196
197 1. Compile all required python packages into =requirements.txt= for
198 easy installation if I move to a new machine.
199 2. Activate a virtual environment in =venv/= so I don't need to
200 maintain a system-wide Python environment just for this project.
201 3. Keep my =notebooks/= folder to continuously explore the data as I
202 see fit.
203 4. Maintain a local copy of the database in =src/= for easy access.
204 5. Export reports, images, HTML files, etc. to =public/=.
205
206 Now, onto the differences between the code in a Jupyter Notebook and
207 the actual Python files. To create the report in the Notebook snippet
208 above, I created the following function inside =process.py=:
209
210 #+begin_src python
211 # Create summary pie chart
212 def summary_data(accounts: pandas.DataFrame) -> None:
213 accounts_01 = accounts[accounts["Owner"] == "Person01"]
214 accounts_02 = accounts[accounts["Owner"] == "Person02"]
215 for x in range(1, 4):
216 if x == 1:
217 df = accounts
218 account_string = "All Accounts"
219 elif x == 2:
220 df = accounts_01
221 account_string = "Person01's Accounts"
222 elif x == 3:
223 df = accounts_02
224 account_string = "Person02's Accounts"
225 print(f"Generating pie chart summary image for {account_string}...")
226 summary_chart = (
227 df.groupby(["AccountType"])
228 .sum()
229 .plot.pie(
230 title=f"Credit Line by Type for {account_string}",
231 y="CreditLine",
232 autopct="%1.1f%%",
233 )
234 )
235 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
236 #+end_src
237
238 The result? A high-quality pie chart that is read directly by the
239 =public/index.html= template I use.
240
241 #+caption: Summary Pie Chart
242 [[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png]]
243
244 Other charts generated by this project include:
245
246 - Charts of account balances over time.
247 - Line chart of effective tax rate (taxes divided by taxable income).
248 - Salary projections and error limits using past income and inflation
249 rates.
250 - Multi-line chart of gross income, taxable income, and net income.
251
252 The best thing about this project? I can improve it at any given
253 time, shaping it into whatever helps me the most for that time. I
254 imagine that I will be introducing an asset tracking table soon to
255 track the depreciating value of cars, houses, etc. Who knows what's
256 next?
content/blog/2022-03-08-plex-migration.md added +248
@@ -0,0 +1,248 @@
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
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
28## Choosing an OS
29
30In order to migrate Plex to my new server, I first needed to choose an
31appropriate operating system (OS) and install it on the machine. Given
32that I have encountered numerous issues installing other Linux
33distributions properly with Nvidia graphics, I chose [Ubuntu
34Server](https://ubuntu.com/download/server).
35
36The first step is to create a bootable USB with Ubuntu Server. This is
37easy with [Etcher](https://www.balena.io/etcher/), an app that runs on
38many different platforms. Just download the Ubuntu Server
39`.iso` image, launch Etcher, and install the
40`.iso` on the USB.
41
42Once the USB is created, insert it into my server, reboot, and click
43`Esc` (or any of the `F1-12` keys) until the BIOS
44menu appears. Finally, launch the USB boot drive.
45
46## Booting with Nvidia
47
48In order to install Ubuntu Server with an Nvidia Graphics card (and no
49integrated graphics on this device for some reason), you\'ll have to
50configure the boot menu to allow different graphics drivers to be
51loaded.
52
53When booting from the USB, the machine will launch the initial
54installation menu. From this menu, type `e` to view the
55default command options that come with the device - it\'s a good idea to
56take a photo of this screen, so you can enter these commands on the next
57screen (along with adding support for Nvidia).
58
59Finally, type `Ctrl + C` to enter the command line. From this
60command line, enter the commands found on the `e` screen.
61\*Remember to add `nomodeset` to the `linux ...`
62line so that your Nvidia device will display the installation screens
63properly!\*
64
65Here\'s an example of the commands I pulled from the `e`
66screen and entered on the command line.
67
68```sh
69setparams 'Install Ubuntu Server'
70setgfxpayload=keep
71linux /casper/vmlinuz quiet nomodeset ---
72initrd /casper/initrd
73boot
74```
75
76![Ubuntu Server Installation
7701](https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png)
78
79![Ubuntu Server Installation
8002](https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png)
81
82Once the machine is rebooted, enter the `e` screen again and
83add `nomodeset` to the `linux ...` line again and
84press `Ctrl + X` to save the boot options.
85
86The machine is now fully installed and can properly display on an
87external display using the Nvidia GPU.
88
89Always remember to update and upgrade on a new installation:
90
91```sh
92sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
93```
94
95# Phase 2: Migrate Plex Data & Devices
96
97This phase uses the great Plex article on migrations ([Move an
98Installation to Another
99System](https://support.plex.tv/articles/201370363-move-an-install-to-another-system/))
100and adds a bit more information to help with commands and context.
101
102## Terminology
103
104**Source:\*** The original server that is being replaced.\
105**Destination:\*** The new server.\
106**Client:\*** Any application that can be used to modify settings for
107both source/destination.
108
109## Step 01: \[Client\] Update Settings
110
111Open up a Plex app and *disable* the `Account` \>
112`Library` \>
113`Empty trash automatically after every scan` preference for
114the source server.
115
116## Step 02: \[Destination\] Install Plex
117
118Open up the [Plex Media Server download
119page](https://www.plex.tv/media-server-downloads/) and copy the link for
120the appropriate platform.
121
122Execute the following commands on the destination server to install
123Plex:
124
125```sh
126wget <url>
127sudo dpkg -i <filename>
128sudo systemctl stop plexmediaserver.service
129```
130
131## Step 03: \[Source\] Stop Plex & Migrate Data
132
133First, stop the Plex service so that no data is created or modified
134during the migration.
135
136```sh
137sudo systemctl stop plexmediaserver.service
138```
139
140Next, copy the data to the new server. To find where the Plex data
141directory is located, Plex has another excellent article available:
142[Where is the Plex Media Server data directory
143located?](https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/).
144
145There are many ways to copy the data to the new server and will largely
146depend on the size of the folder being copied. Personally, my data
147folder was \~23GB and I opted to simply use the `scp` command
148to copy the files over SSH.
149
150This process was throttled by the old server\'s slow HDD and ports and
151took approximately 90 minutes to complete. In comparison, moving the
152data from the new server\'s `home/user/` directory to the
153`/var/.../Plex Media Server` directory took 2-3 minutes.
154
155```sh
156scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
157```
158
159## Step 04: \[Destination\] Update File Permissions
160
161In case you move the data directory to a common area on the new server,
162it will have to be moved to the proper location before Plex can function
163properly:
164
165```sh
166mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
167```
168
169To ensure permissions were retained properly, the server will need to
170show that all files and folders in the data directory are owned by
171`plex:plex` (or whichever user is running the Plex
172application).
173
174```sh
175sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
176```
177
178Finally, start the service and check the status.
179
180```sh
181sudo systemctl start plexmediaserver.service
182sudo systemctl status plexmediaserver.service
183```
184
185## Step 05: \[Client\] Update Libraries & Metadata
186
187The first step - now that the new server is up and running - is to sign
188out of the client and sign back in. Once this is done, update any
189library locations, if necessary. This was unnecessary in my case since I
190simply moved my storage drives from the source server to the destination
191server.
192
193Next, perform the following actions in the client:
194
1951. On the left sidebar, click `More` \> Three-Dot Menu \>
196 `Scan Library Files`
1972. *Enable* the `Account` \> `Library` \>
198 `Empty trash automatically after every scan` preference
199 for the source server.
2003. On the left sidebar, click `More` \> Three-Dot Menu \>
201 `Manage Server` \> `Empty Trash`
2024. On the left sidebar, click `More` \> Three-Dot Menu \>
203 `Manage Server` \> `Clean Bundles`
2045. On the left sidebar, click `More` \> Three-Dot Menu \>
205 `Manage Server` \> `Optimize Database`
206
207Finally, double-check the Remote Access settings to make sure no changes
208have caused issues with accessing the server from outside the network.
209
210In my case, I use a single port forwarding rule in my router and needed
211to update the Local LAN IP Address to the new server IP address.
212
213![Router port
214forwarding](https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png)
215
216# Phase 3: Configure GPU Transcoding
217
218The final piece to the migration is enabling hardware transcoding so
219that Plex can fully utilize the new Nvidia GPU available in the server.
220The first step is to install Nvidia graphics drivers. This process may
221take a few minutes, but the commands are pretty simple:
222
223```sh
224sudo add-apt-repository ppa:graphics-drivers/ppa
225sudo apt update
226sudo apt-get install ubuntu-drivers-common
227sudo ubuntu-drivers autoinstall
228```
229
230Finally, reboot so that the changes are loaded:
231
232```sh
233sudo reboot now
234```
235
236To ensure that the Nvidia graphics drivers are working properly, run the
237following command to view the available GPUs, statistics, and processes:
238
239```sh
240sudo nvidia-smi
241```
242
243![nvidia-smi](https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png)
244
245Finally, enable hardware transcoding settings in the Plex application:
246
247![Plex transcoding
248settings](https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png)
content/blog/2022-03-08-plex-migration.org deleted −230
@@ -1,230 +0,0 @@
1#+title: Migrating Plex to a New Server (& Nvidia Transcoding)
2#+date: 2022-03-08
3#+description: A retrospective on migrating Plex between servers and enabling Nvidia GPU transcoding.
4#+filetags: :selfhosting:
5
6* Migration Phases
7I recently decided to migrate my server from an old OptiPlex desktop
8machine to a custom-built tower with better hardware in every category.
9In order to do this, I would need to properly migrate a full Plex
10installation.
11
12The second part of this migration is that the new server uses an Nvidia
13GPU and does not have any integrated graphics, which requires extra work
14for installation, but provides much better hardware transcoding options
15for Plex.
16
17Therefore, I have broken this migration down into three phases:
18
191. Configure the New Server
202. Migrate Plex Data & Devices
213. Configure GPU Transcoding
22
23* Phase 1: Configure the New Server
24** Choosing an OS
25In order to migrate Plex to my new server, I first needed to choose an
26appropriate operating system (OS) and install it on the machine. Given
27that I have encountered numerous issues installing other Linux
28distributions properly with Nvidia graphics, I chose
29[[https://ubuntu.com/download/server][Ubuntu Server]].
30
31The first step is to create a bootable USB with Ubuntu Server. This is
32easy with [[https://www.balena.io/etcher/][Etcher]], an app that runs on
33many different platforms. Just download the Ubuntu Server =.iso= image,
34launch Etcher, and install the =.iso= on the USB.
35
36Once the USB is created, insert it into my server, reboot, and click
37=Esc= (or any of the =F1-12= keys) until the BIOS menu appears. Finally,
38launch the USB boot drive.
39
40** Booting with Nvidia
41In order to install Ubuntu Server with an Nvidia Graphics card (and no
42integrated graphics on this device for some reason), you'll have to
43configure the boot menu to allow different graphics drivers to be
44loaded.
45
46When booting from the USB, the machine will launch the initial
47installation menu. From this menu, type =e= to view the default command
48options that come with the device - it's a good idea to take a photo of
49this screen, so you can enter these commands on the next screen (along
50with adding support for Nvidia).
51
52Finally, type =Ctrl + C= to enter the command line. From this command
53line, enter the commands found on the =e= screen. *Remember to add
54=nomodeset= to the =linux ...= line so that your Nvidia device will
55display the installation screens properly!*
56
57Here's an example of the commands I pulled from the =e= screen and
58entered on the command line.
59
60#+begin_src sh
61setparams 'Install Ubuntu Server'
62setgfxpayload=keep
63linux /casper/vmlinuz quiet nomodeset ---
64initrd /casper/initrd
65boot
66#+end_src
67
68#+caption: Ubuntu Server Installation 01
69[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png]]
70
71#+caption: Ubuntu Server Installation 02
72[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png]]
73
74Once the machine is rebooted, enter the =e= screen again and add
75=nomodeset= to the =linux ...= line again and press =Ctrl + X= to save
76the boot options.
77
78The machine is now fully installed and can properly display on an
79external display using the Nvidia GPU.
80
81Always remember to update and upgrade on a new installation:
82
83#+begin_src sh
84sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
85#+end_src
86
87* Phase 2: Migrate Plex Data & Devices
88This phase uses the great Plex article on migrations
89([[https://support.plex.tv/articles/201370363-move-an-install-to-another-system/][Move
90an Installation to Another System]]) and adds a bit more information to
91help with commands and context.
92
93** Terminology
94*Source:** The original server that is being replaced.\\
95*Destination:** The new server.\\
96*Client:** Any application that can be used to modify settings for both
97source/destination.
98
99** Step 01: [Client] Update Settings
100Open up a Plex app and /disable/ the =Account= > =Library= >
101=Empty trash automatically after every scan= preference for the source
102server.
103
104** Step 02: [Destination] Install Plex
105Open up the [[https://www.plex.tv/media-server-downloads/][Plex Media
106Server download page]] and copy the link for the appropriate platform.
107
108Execute the following commands on the destination server to install
109Plex:
110
111#+begin_src sh
112wget <url>
113sudo dpkg -i <filename>
114sudo systemctl stop plexmediaserver.service
115#+end_src
116
117** Step 03: [Source] Stop Plex & Migrate Data
118First, stop the Plex service so that no data is created or modified
119during the migration.
120
121#+begin_src sh
122sudo systemctl stop plexmediaserver.service
123#+end_src
124
125Next, copy the data to the new server. To find where the Plex data
126directory is located, Plex has another excellent article available:
127[[https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/][Where
128is the Plex Media Server data directory located?]].
129
130There are many ways to copy the data to the new server and will largely
131depend on the size of the folder being copied. Personally, my data
132folder was ~23GB and I opted to simply use the =scp= command to copy the
133files over SSH.
134
135This process was throttled by the old server's slow HDD and ports and
136took approximately 90 minutes to complete. In comparison, moving the
137data from the new server's =home/user/= directory to the
138=/var/.../Plex Media Server= directory took 2-3 minutes.
139
140#+begin_src sh
141scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
142#+end_src
143
144** Step 04: [Destination] Update File Permissions
145In case you move the data directory to a common area on the new server,
146it will have to be moved to the proper location before Plex can function
147properly:
148
149#+begin_src sh
150mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
151#+end_src
152
153To ensure permissions were retained properly, the server will need to
154show that all files and folders in the data directory are owned by
155=plex:plex= (or whichever user is running the Plex application).
156
157#+begin_src sh
158sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
159#+end_src
160
161Finally, start the service and check the status.
162
163#+begin_src sh
164sudo systemctl start plexmediaserver.service
165sudo systemctl status plexmediaserver.service
166#+end_src
167
168** Step 05: [Client] Update Libraries & Metadata
169The first step - now that the new server is up and running - is to sign
170out of the client and sign back in. Once this is done, update any
171library locations, if necessary. This was unnecessary in my case since I
172simply moved my storage drives from the source server to the destination
173server.
174
175Next, perform the following actions in the client:
176
1771. On the left sidebar, click =More= > Three-Dot Menu >
178 =Scan Library Files=
1792. /Enable/ the =Account= > =Library= >
180 =Empty trash automatically after every scan= preference for the
181 source server.
1823. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
183 > =Empty Trash=
1844. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
185 > =Clean Bundles=
1865. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
187 > =Optimize Database=
188
189Finally, double-check the Remote Access settings to make sure no changes
190have caused issues with accessing the server from outside the network.
191
192In my case, I use a single port forwarding rule in my router and needed
193to update the Local LAN IP Address to the new server IP address.
194
195#+caption: Router port forwarding
196[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png]]
197
198* Phase 3: Configure GPU Transcoding
199The final piece to the migration is enabling hardware transcoding so
200that Plex can fully utilize the new Nvidia GPU available in the server.
201The first step is to install Nvidia graphics drivers. This process may
202take a few minutes, but the commands are pretty simple:
203
204#+begin_src sh
205sudo add-apt-repository ppa:graphics-drivers/ppa
206sudo apt update
207sudo apt-get install ubuntu-drivers-common
208sudo ubuntu-drivers autoinstall
209#+end_src
210
211Finally, reboot so that the changes are loaded:
212
213#+begin_src sh
214sudo reboot now
215#+end_src
216
217To ensure that the Nvidia graphics drivers are working properly, run the
218following command to view the available GPUs, statistics, and processes:
219
220#+begin_src sh
221sudo nvidia-smi
222#+end_src
223
224#+caption: nvidia-smi
225[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png]]
226
227Finally, enable hardware transcoding settings in the Plex application:
228
229#+caption: Plex transcoding settings
230[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png]]
content/blog/2022-03-23-cloudflare-dns-api.org → content/blog/2022-03-23-cloudflare-dns-api.md renamed +49 −44
@@ -1,39 +1,42 @@
1#+title: Dynamic DNS with Cloudflare API 1+++
2#+date: 2022-03-23 2date = 2022-03-23
3#+description: Learn how to dynamically update DNS records for changing IPs with Cloudflare. 3title = "Dynamic DNS with Cloudflare API"
4#+filetags: :sysadmin: 4description = ""
5 5draft = false
6* DDNS: Dynamic DNS 6+++
7If you're hosting a service from a location with dynamic DNS (where your 7
8IP may change at any time), you must have a solution to update the DNS 8# DDNS: Dynamic DNS
9so that you can access your service even when the IP of the server 9
10If you\'re hosting a service from a location with dynamic DNS (where
11your IP may change at any time), you must have a solution to update the
12DNS so that you can access your service even when the IP of the server
10changes. 13changes.
11 14
12The process below uses the [[https://api.cloudflare.com/][Cloudflare 15The process below uses the [Cloudflare API](https://api.cloudflare.com/)
13API]] to update DNS =A= and =AAAA= records with the server's current IP. 16to update DNS `A` and `AAAA` records with the
14If you use another DNS provider, you will have to find a way to update 17server\'s current IP. If you use another DNS provider, you will have to
15your DNS (or find a way to get a static IP). 18find a way to update your DNS (or find a way to get a static IP).
16 19
17First, install =jq= since we will use it in the next script: 20First, install `jq` since we will use it in the next script:
18 21
19#+begin_src sh 22```sh
20sudo apt install jq 23sudo apt install jq
21#+end_src 24```
22 25
23Next, create a location for your DDNS update scripts and open the first 26Next, create a location for your DDNS update scripts and open the first
24script: 27script:
25 28
26#+begin_src sh 29```sh
27mkdir ~/ddns 30mkdir ~/ddns
28nano ~/ddns/update.sh 31nano ~/ddns/update.sh
29#+end_src 32```
30 33
31The following =update.sh= script will take all of your domains and 34The following `update.sh` script will take all of your
32subdomains and check Cloudflare to see if the current =A= and =AAAA= 35domains and subdomains and check Cloudflare to see if the current
33records match your server's IP address. If not, it will update the 36`A` and `AAAA` records match your server\'s IP
34records. 37address. If not, it will update the records.
35 38
36#+begin_src sh 39```sh
37# file: update.sh 40# file: update.sh
38#!/bin/bash 41#!/bin/bash
39 42
@@ -55,20 +58,21 @@ 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#+end_src 61```
59 62
60Next, open up the =ddns.sh= script. Paste the following into the script 63Next, open up the `ddns.sh` script. Paste the following into
61and update the =api_token= and =email= variables. 64the script and update the `api_token` and `email`
65variables.
62 66
63#+begin_src sh 67```sh
64nano ~/ddns/ddns.sh 68nano ~/ddns/ddns.sh
65#+end_src 69```
66 70
67*Note*: If you want your DNS records to be proxied through 71**Note**: If you want your DNS records to be proxied through Cloudflare,
68Cloudflare, find and update the following snippet: ="proxied":false}"= 72find and update the following snippet: `"proxied":false}"` to
69to say =true= instead of =false=. 73say `true` instead of `false`.
70 74
71#+begin_src sh 75```sh
72# file: ddns.sh 76# file: ddns.sh
73#!/bin/bash 77#!/bin/bash
74# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a 78# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
@@ -161,30 +165,31 @@ then
161else 165else
162 echo "There is a problem with either the email or the password" 166 echo "There is a problem with either the email or the password"
163fi 167fi
164#+end_src 168```
165 169
166Once the script is saved and closed, make the scripts executable: 170Once the script is saved and closed, make the scripts executable:
167 171
168#+begin_src sh 172```sh
169chmod +x ~/ddns/ddns.sh 173chmod +x ~/ddns/ddns.sh
170chmod +x ~/ddns/update.sh 174chmod +x ~/ddns/update.sh
171#+end_src 175```
172 176
173You can test the script by running it manually: 177You can test the script by running it manually:
174 178
175#+begin_src sh 179```sh
176./update.sh 180./update.sh
177#+end_src 181```
178 182
179To make sure the scripts run automatically, add it to the =cron= file so 183To make sure the scripts run automatically, add it to the
180that it will run on a schedule. To do this, open the cron file: 184`cron` file so that it will run on a schedule. To do this,
185open the cron file:
181 186
182#+begin_src sh 187```sh
183crontab -e 188crontab -e
184#+end_src 189```
185 190
186In the cron file, paste the following at the bottom of the editor: 191In the cron file, paste the following at the bottom of the editor:
187 192
188#+begin_src sh 193```sh
189,*/5 ** ** ** ** bash /home/<your_username>/ddns/update.sh 194*/5 ** ** ** ** bash /home/<your_username>/ddns/update.sh
190#+end_src 195```
content/blog/2022-03-23-nextcloud-on-ubuntu.org → content/blog/2022-03-23-nextcloud-on-ubuntu.md renamed +71 −60
@@ -1,77 +1,84 @@
1#+title: Nextcloud on Ubuntu 1+++
2#+date: 2022-03-23 2date = 2022-03-23
3#+description: A guide to self-hosting the NextCloud application on your own server. 3title = "Self-Hosting Nextcloud"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* What is Nextcloud? 8# What is Nextcloud?
7[[https://nextcloud.com/][Nextcloud]] is a self-hosted solution for 9
10[Nextcloud](https://nextcloud.com/) is a self-hosted solution for
8storage, communications, editing, calendar, contacts, and more. 11storage, communications, editing, calendar, contacts, and more.
9 12
10This 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
11configured to point toward the server. 14configured to point toward the server.
12 15
13* Install Dependencies 16# Install Dependencies
17
14To start, you will need to install the packages that Nextcloud requires: 18To start, you will need to install the packages that Nextcloud requires:
15 19
16#+begin_src sh 20```sh
17sudo apt install apache2 mariadb-server libapache2-mod-php7.4 21sudo apt install apache2 mariadb-server libapache2-mod-php7.4
18sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl 22sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl
19sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip 23sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip
20#+end_src 24```
21 25
22* Set Up MySQL 26# Set Up MySQL
23Next, you will need to log in to MySQL as the =root= user of the
24machine.
25 27
26#+begin_src sh 28Next, you will need to log in to MySQL as the `root` user of
29the machine.
30
31```sh
27sudo mysql -uroot -p 32sudo mysql -uroot -p
28#+end_src 33```
29 34
30Once you've logged in, you must create a new user so that Nextcloud can 35Once you\'ve logged in, you must create a new user so that Nextcloud can
31manage the database. You will also create a =nextcloud= database and 36manage the database. You will also create a `nextcloud`
32assign privileges: 37database and assign privileges:
33 38
34#+begin_src sql 39``` sql
35CREATE USER 'username'@'localhost' IDENTIFIED BY 'password'; 40CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
36CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 41CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
37GRANT ALL PRIVILEGES ON nextcloud.** TO 'username'@'localhost'; 42GRANT ALL PRIVILEGES ON nextcloud.** TO 'username'@'localhost';
38FLUSH PRIVILEGES; 43FLUSH PRIVILEGES;
39quit; 44quit;
40#+end_src 45```
46
47# Download & Install Nextcloud
41 48
42* Download & Install Nextcloud 49To download Nextcloud, go the [Nextcloud downloads
43To download Nextcloud, go the 50page](https://nextcloud.com/install/#instructions-server), click on
44[[https://nextcloud.com/install/#instructions-server][Nextcloud 51`Archive File` and right-click the big blue button to copy
45downloads page]], click on =Archive File= and right-click the big blue 52the link.
46button to copy the link.
47 53
48Then, go to your server and enter the following commands to download, 54Then, go to your server and enter the following commands to download,
49unzip, and move the files to your destination directory. This example 55unzip, and move the files to your destination directory. This example
50uses =example.com= as the destination, but you can put it wherever you 56uses `example.com` as the destination, but you can put it
51want to server your files from. 57wherever you want to server your files from.
52 58
53#+begin_src sh 59```sh
54wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip 60wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip
55sudo apt install unzip 61sudo apt install unzip
56unzip nextcloud-23.0.3.zip 62unzip nextcloud-23.0.3.zip
57sudo cp -r nextcloud /var/www/example.com 63sudo cp -r nextcloud /var/www/example.com
58#+end_src 64```
65
66# Configure the Apache Web Server
59 67
60* Configure the Apache Web Server
61Now that the database is set up and Nextcloud is installed, you need to 68Now that the database is set up and Nextcloud is installed, you need to
62set up the Apache configuration files to tell the server how to handle 69set up the Apache configuration files to tell the server how to handle
63requests for =example.com/nextcloud=. 70requests for `example.com/nextcloud`.
64 71
65First, open the following file in the editor: 72First, open the following file in the editor:
66 73
67#+begin_src sh 74```sh
68sudo nano /etc/apache2/sites-available/nextcloud.conf 75sudo nano /etc/apache2/sites-available/nextcloud.conf
69#+end_src 76```
70 77
71Once the editor is open, paste the following information in. Then, save 78Once the editor is open, paste the following information in. Then, save
72and close the file. 79and close the file.
73 80
74#+begin_src config 81``` config
75<VirtualHost *:80> 82<VirtualHost *:80>
76 DocumentRoot /var/www/example.com 83 DocumentRoot /var/www/example.com
77 ServerName example.com 84 ServerName example.com
@@ -90,70 +97,74 @@ and close the file.
90 </IfModule> 97 </IfModule>
91 </Directory> 98 </Directory>
92</VirtualHost> 99</VirtualHost>
93#+end_src 100```
94 101
95Once the file is saved, enable it with Apache: 102Once the file is saved, enable it with Apache:
96 103
97#+begin_src sh 104```sh
98sudo a2ensite nextcloud.conf 105sudo a2ensite nextcloud.conf
99#+end_src 106```
100 107
101Next, enable the Apache mods required by Nextcloud: 108Next, enable the Apache mods required by Nextcloud:
102 109
103#+begin_src sh 110```sh
104sudo a2enmod rewrite headers env dir mime 111sudo a2enmod rewrite headers env dir mime
105#+end_src 112```
106 113
107Finally, restart Apache. If any errors arise, you must solve those 114Finally, restart Apache. If any errors arise, you must solve those
108before continuing. 115before continuing.
109 116
110#+begin_src sh 117```sh
111sudo systemctl restart apache2 118sudo systemctl restart apache2
112#+end_src 119```
113 120
114For the app to work, you must have the correct file permissions on your 121For the app to work, you must have the correct file permissions on your
115=nextcloud= directory. Set the owner to be =www-data=: 122`nextcloud` directory. Set the owner to be
123`www-data`:
116 124
117#+begin_src sh 125```sh
118sudo chown -R www-data:www-data /var/www/example.com/nextcloud/ 126sudo chown -R www-data:www-data /var/www/example.com/nextcloud/
119#+end_src 127```
128
129# DNS
120 130
121* DNS
122If you do not have a static IP address, you will need to update your DNS 131If you do not have a static IP address, you will need to update your DNS
123settings (at your DNS provider) whenever your dynamic IP address 132settings (at your DNS provider) whenever your dynamic IP address
124changes. 133changes.
125 134
126For an example on how I do that with Cloudflare, see my other post: 135For an example on how I do that with Cloudflare, see my other post:
127[[../updating-dynamic-dns-with-cloudflare-api/][Updating Dynamic DNS 136[Updating Dynamic DNS with Cloudflare
128with Cloudflare API]] 137API](../updating-dynamic-dns-with-cloudflare-api/)
138
139# Certbot
129 140
130* Certbot
131If you want to serve Nextcloud from HTTPS rather than plain HTTP, use 141If you want to serve Nextcloud from HTTPS rather than plain HTTP, use
132the following commands to issue Let's Encrypt SSL certificates: 142the following commands to issue Let\'s Encrypt SSL certificates:
133 143
134#+begin_src sh 144```sh
135sudo apt install snapd 145sudo apt install snapd
136sudo snap install core 146sudo snap install core
137sudo snap refresh core 147sudo snap refresh core
138sudo snap install --classic certbot 148sudo snap install --classic certbot
139sudo ln -s /snap/bin/certbot /usr/bin/certbot 149sudo ln -s /snap/bin/certbot /usr/bin/certbot
140sudo certbot --apache 150sudo certbot --apache
141#+end_src 151```
152
153# Results
142 154
143* Results 155Voilà! You\'re all done and should be able to access Nextcloud from your
144Voilà! You're all done and should be able to access Nextcloud from your
145domain or IP address. 156domain or IP address.
146 157
147See the screenshots below for the dashboard and a settings page on my 158See the screenshots below for the dashboard and a settings page on my
148instance of Nextcloud, using the =Breeze Dark= theme I installed from 159instance of Nextcloud, using the `Breeze Dark` theme I
149the Apps page. 160installed from the Apps page.
150 161
151#+caption: Nextcloud Dashboard 162![Nextcloud
152[[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png]] 163Dashboard](https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png)
153 164
154/Figure 01: Nextcloud Dashboard/ 165*Figure 01: Nextcloud Dashboard*
155 166
156#+caption: Nextcloud Settings 167![Nextcloud
157[[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png]] 168Settings](https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png)
158 169
159/Figure 02: Nextcloud Security Settings/ 170*Figure 02: Nextcloud Security Settings*
content/blog/2022-03-24-server-hardening.md added +353
@@ -0,0 +1,353 @@
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
11> gaps in my write-up and wanted to add a few things, even though this
12> blog is really just a retrospective and knowledge dump for myself. I
13> left things intact and simply crossed them out (~~like this~~) for
14> posterity.
15
16# Planning Data Flows & Security
17
18## My Personal Data Flow
19
20``` txt
21 ┌───────┐ ┌─────────────────┐
22 ┌──► VLAN1 ├───► Private Devices │
23 │ └───────┘ └─────────────────┘
24┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
25│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
26└──────────┘ └────────┘ └──────────┘ └────────┘ │
27 │ ┌───────┐ ┌───────────────┐
28 └──► VLAN2 ├───► Public Server │
29 └───────┘ └───────────────┘
30```
31
32## Thought Process
33
34To serve content from your home server and harden your security posture,
35you have to think about the transport of data from `server`
36to `client`.
37
38Let\'s start with the actual server itself. Think about the following:
39
40- Do I have a firewall enabled? Do I need to update this to allow new
41 ports or IPs?
42- Do I have an IPS/IDS that may prevent outside traffic?
43- Do I have any other security software installed?
44- Are the services hosted inside Docker containers, behind a reverse
45 proxy, or virtualized? If so, are they configured to allow outside
46 traffic?
47
48Once the data leaves the server, where does it go? In my case, it goes
49to a managed switch. In this case, I asked the following:
50
51- What configurations is the switch using?
52- Am I using VLANs?
53 - Yes, I am using 802.1Q VLANs.
54- Are the VLANs configured properly?
55 - Yes, as shown in the Switch section below, I have a separate
56 VLAN to allow outside traffic to and from the server alone. No
57 other devices, except for a service port, and in that VLAN.
58
59At this point, the data has been processed through the switch. Where
60does it go next? In my case, it\'s pretty simple: it goes to the
61router/modem device.
62
63- Does my ISP block any ports that I need?
64 - This is an important step that a lot of people run into when
65 self-hosting at home. Use an online port-checker tool for your
66 IP or call your ISP if you think ports are blocked.
67- Is there a router firewall?
68 - Yes, I checked that it\'s configured to allow the ports I need
69 to run my services publicly. Common web servers and reverse
70 proxies require ports 80 and 443, but other services like media
71 servers or games can require unique ports, so be sure to check
72 the documentation for your service(s).
73- Are there any other settings affecting inbound/outbound traffic?
74 - Schedules or access blocks
75 - Static Routing
76 - QoS
77 - Port Forwarding
78 - DMZ Hosting
79 - Remote Management (this can sometimes mess with services that
80 also require the use of ports 80 and 443)
81
82Once the data leaves my router, it goes to the upstream ISP and can be
83accessed publicly.
84
85### Server
86
87+The services I run on my server are installed straight into the OS,
88without any use of Docker or VMs, so I don\'t need any extra application
89configuration to make them accessible to the outside world.+
90
91As of 2022-10-04, the paragraph above is no longer true as I now run a
92reverse proxy with Nginx and host many services inside Docker. However,
93it doesn\'t change anything regarding this post as I still just need to
94open ports 80 & 443 and create the necessary website configuration
95files.
96
97When creating new services - either installed directly on bare metal or
98within something like Docker - I ensure that I read through the
99documentation thoroughly to understand a few key things: - What network
100activities should this app perform (if any)? Using which ports and
101protocols? - Does this app require any commands/services to be run as
102`root`? - Does this app log errors, authentication
103failures/successes, or anything else that would be useful for an
104investigation?
105
106For extra security, I use limit all incoming connections to SSH
107connections through my server firewall (`ufw`) and disable
108common SSH settings. After all of that, I use `fail2ban` as a
109preventative measure against brute-force login attempts.
110
111As another piece of security, you can randomize your SSH port to ensure
112that random scanners or attackers can\'t easily try to force their way
113into your network. For example, you can edit the port rules in your
114server to block all connection requests to port `22` but
115forward all remote connections from port `12345` to your
116server\'s port `22`. Then you just need to SSH to your
117network via your randomized port.
118
119## `ufw`
120
121To see how to configure `ufw`, see my other post: [Secure
122Your Network with the Uncomplicated
123Firewall](../secure-your-network-with-the-uncomplicated-firewall/).
124
125The general notion with an on-device firewall is that you want to deny
126all incoming connections by default and then selectively open certain
127ports for services or users that you know need access.
128
129If you know that you will only be logging into this server from a
130certain set or list of IPs, you can always set the firewall to only
131allow connections to port 22 from those IPs.
132
133For a quick start to only allow SSH connections to the server, use this:
134
135```sh
136sudo ufw default deny incoming
137sudo ufw default allow outgoing
138sudo ufw allow 22
139sudo ufw enable
140```
141
142![ufw](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png)
143
144## `ssh`
145
1461. Using SSH Keys
147
148 First, make sure you have an SSH keypair generated on the device(s)
149 that you\'ll be using to log in to the server. If you don\'t have an
150 SSH key, run this command:
151
152 ```sh
153 ssh-keygen
154 ```
155
156 Now that we have an SSH key, copy it to the server with the
157 following command, which will ask for the user\'s password before
158 accepting the key:
159
160 ```sh
161 ssh-copy-id my_user@my_server
162 ```
163
164 If you have multiple keys, you\'ll need to specify which to use.
165 After it\'s complete, `ssh` back into the server as that
166 user and make sure it doesn\'t ask for a password.
167
1682. Disable Password & Root Authentication
169
170 Now that we can access the server without a password, we will
171 disable password authentication and disable anyone from using
172 `ssh` to login as `root`.
173
174 To do this, open the `sshd_config` file:
175
176 ```sh
177 sudo nano /etc/ssh/sshd_config
178 ```
179
180 You\'ll need to update the parameters to the values below. If one of
181 these rules is commented-out or doesn\'t exist, create the rule at
182 the bottom of the file.
183
184 ``` config
185 PermitRootLogin no
186 PasswordAuthentication no
187 PubkeyAuthentication yes
188 ```
189
190 Finally, restart the `ssh` service:
191
192 ```sh
193 sudo systemctl restart sshd.service
194 ```
195
196 To test that everything\'s working so far, open ANOTHER terminal and
197 try logging in as `root` over SSH. It is very important
198 that you keep your current SSH session open and test with an
199 additional session, or you will lock yourself out at some point and
200 will need to use a recovery method (e.g., hooking monitor up to home
201 server) to get yourself back in.
202
2033. Enable MFA for `ssh`
204
205 This part is optional, but I highly recommend it. So far, we\'ve
206 ensured that no one can log into our user on the server without
207 using our secret key, and we\'ve ensured that no one can log in
208 remotely as `root`. Next, you can enable MFA
209 authentication for `ssh` connections.
210
211 This process involves editing a couple files and installing an MFA
212 package, so I will not include all the details in this post. To see
213 how to configure MFA for `ssh`, see my other post:
214 [Enabling MFA for SSH](../enable-totp-mfa-for-ssh/).
215
216 ![SSH
217 MFA](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png)
218
219## `fail2ban`
220
221I haven\'t written a post on how I use `fail2ban`, but it\'s
222quite simple. I use the default `sshd` jail, but you can
223always create new jails for respective applications or ports. For
224example, if you use Nginx as your web server, you can use the
225`nginx-http-auth` jail.
226
227In order to get it up and running, use the following commands:
228
229```sh
230sudo apt install fail2ban
231sudo fail2ban-client start sshd
232sudo fail2ban-client status sshd
233```
234
235This should be used as a last-resort defense and shouldn\'t be a
236replacement for the security measures mentioned above.
237
238![fail2ban](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png)
239
240# Switch
241
242Between the router and any local devices is my managed switch, which is
243used to create VLANs. The example below shows how I would isolate the
244VLANs if I were starting to host a single service at home.
245
246## 802.1Q VLAN Configuration
247
248In this configuration, port 8 is the public server that needs to be
249accessed from the outside. Port 23 is my \'dedicated service port\' for
250this server. In order to SSH to this server, I need to plug my laptop
251into port 23 or else I cannot SSH. Otherwise, I\'d need to hook up a
252monitor and keyboard directly to the server to manage it.
253
254 VLAN ID VLAN Name Member Ports Tagged Ports Untagged Ports
255 --------- ----------- -------------- -------------- ----------------
256 1 Default 1-24 1-24
257 2 Server 1,8,23 1,8,23
258
259## 802.1Q VLAN PVID Setting
260
261Once the VLAN is created, I simply add the `VLAN ID` of
262`2` as the `PVID` for any related ports (in this
263case, see that ports `8` and `23` have a PVID of
264`2`).
265
266 Port PVID
267 ------ ------
268 1 1
269 2 1
270 3 1
271 4 1
272 5 1
273 6 1
274 7 1
275 8 2
276 9 1
277 10 1
278 11 1
279 12 1
280 13 1
281 14 1
282 15 1
283 16 1
284 17 1
285 18 1
286 19 1
287 20 1
288 21 1
289 22 1
290 23 2
291 24 1
292
293# Router
294
295On my router, the configuration was as easy as opening the firewall
296settings and unblocking the ports I needed for my services (e.g.,
297HTTP/S, Plex, SSH, MySQL, etc.).
298
299+Since I\'m relying on an ISP-provided modem/router combo for now (not
300by choice), I do not use any other advanced settings on my router that
301would inhibit any valid traffic to these services.+
302
303The paragraph above regarding the ISP-owned router is no longer accurate
304as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
305this router, I enabled port forwarding/firewall rules, segregate the
306network based on the device, and enable traffic restrictions (e.g.,
307silently drop traffic from certain countries and threat categories).
308
309If you have the option with your ISP, I recommend using a personal
310router with software that you are familiar with so that you can explore
311all the options available to you.
312
313# Physical Security
314
315One large piece of self-hosting that people generally don\'t discuss
316online is physical security. However, physical security is very
317important for everyone who hosts a server like this. Exactly *how*
318important it is depends on the server use/purpose.
319
320If you self-host customer applications that hold protected data (HIPAA,
321GDPR, COPPA, etc.), then physical security is extremely important and
322cannot be ignored. If you simply host a blog and some hobby sites, then
323it\'s a relatively minor consideration, but one you still need to think
324about.
325
326## Location
327
328The first consideration is quite simple: location. - Is the server
329within a property you own or housed on someone else\'s property? - Is it
330nearby (in your house, in your work office, in your neighbor\'s garage,
331in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
332there climate considerations, such as humidity, fires, tornadoes,
333monsoons? - Do you have emergency equipment nearby in case of emergency?
334
335## Hardware Ownership
336
337Secondly, consider the hardware itself: - Do you own the server in its
338entirety? - Are any other users able to access the server, even if your
339data/space is segregated? - If you\'re utilizing a third party, do they
340have any documentation to show responsibility? This could be a SOC 1/2/3
341report, ISO compliance report, internal security/safety documentation.
342
343## Physical Controls
344
345Regardless of who owns the hardware, ensure that there are adequate
346safeguards in place, if necessary. These usually don\'t apply to small
347home servers and are usually covered already if you\'re utilizing a
348third party.
349
350These can include: - Server bezel locks - Server room locks - physical,
351digital, or biometric authentication - Security cameras - Raised
352floors/lowered ceilings with proper guards/gates in-place within the
353floors or ceilings - Security personnel - Log sheets and/or guest badges
content/blog/2022-03-24-server-hardening.org deleted −334
@@ -1,334 +0,0 @@
1#+title: Hardening a Public-Facing Home Server
2#+date: 2022-03-24
3#+description: Learn some basic techniques to harden a home server and network.
4#+filetags: :security:
5
6* Post Updates
7
8#+begin_quote
9After reviewing this post today (2022-10-04), I noticed quite a few gaps
10in my write-up and wanted to add a few things, even though this blog is
11really just a retrospective and knowledge dump for myself. I left things
12intact and simply crossed them out (+like this+) for posterity.
13#+end_quote
14
15* Planning Data Flows & Security
16** My Personal Data Flow
17#+begin_src txt
18 ┌───────┐ ┌─────────────────┐
19 ┌──► VLAN1 ├───► Private Devices │
20 │ └───────┘ └─────────────────┘
21┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
22│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
23└──────────┘ └────────┘ └──────────┘ └────────┘ │
24 │ ┌───────┐ ┌───────────────┐
25 └──► VLAN2 ├───► Public Server │
26 └───────┘ └───────────────┘
27#+end_src
28
29** Thought Process
30To serve content from your home server and harden your security posture,
31you have to think about the transport of data from =server= to =client=.
32
33Let's start with the actual server itself. Think about the following:
34
35- Do I have a firewall enabled? Do I need to update this to allow new
36 ports or IPs?
37- Do I have an IPS/IDS that may prevent outside traffic?
38- Do I have any other security software installed?
39- Are the services hosted inside Docker containers, behind a reverse
40 proxy, or virtualized? If so, are they configured to allow outside
41 traffic?
42
43Once the data leaves the server, where does it go? In my case, it goes
44to a managed switch. In this case, I asked the following:
45
46- What configurations is the switch using?
47- Am I using VLANs?
48 - Yes, I am using 802.1Q VLANs.
49- Are the VLANs configured properly?
50 - Yes, as shown in the Switch section below, I have a
51 separate VLAN to allow outside traffic to and from the server alone.
52 No other devices, except for a service port, and in that VLAN.
53
54At this point, the data has been processed through the switch. Where
55does it go next? In my case, it's pretty simple: it goes to the
56router/modem device.
57
58- Does my ISP block any ports that I need?
59 - This is an important step that a lot of people run into when
60 self-hosting at home. Use an online port-checker tool for your IP or
61 call your ISP if you think ports are blocked.
62- Is there a router firewall?
63 - Yes, I checked that it's configured to allow the ports I need to run
64 my services publicly. Common web servers and reverse proxies require
65 ports 80 and 443, but other services like media servers or games can
66 require unique ports, so be sure to check the documentation for your
67 service(s).
68- Are there any other settings affecting inbound/outbound traffic?
69 - Schedules or access blocks
70 - Static Routing
71 - QoS
72 - Port Forwarding
73 - DMZ Hosting
74 - Remote Management (this can sometimes mess with services that also
75 require the use of ports 80 and 443)
76
77Once the data leaves my router, it goes to the upstream ISP and can be
78accessed publicly.
79
80*** Server
81+The services I run on my server are installed straight into the OS,
82without any use of Docker or VMs, so I don't need any extra application
83configuration to make them accessible to the outside world.+
84
85As of 2022-10-04, the paragraph above is no longer true as I now run a
86reverse proxy with Nginx and host many services inside Docker. However,
87it doesn't change anything regarding this post as I still just need to
88open ports 80 & 443 and create the necessary website configuration
89files.
90
91When creating new services - either installed directly on bare metal or
92within something like Docker - I ensure that I read through the
93documentation thoroughly to understand a few key things: - What network
94activities should this app perform (if any)? Using which ports and
95protocols? - Does this app require any commands/services to be run as
96=root=? - Does this app log errors, authentication failures/successes,
97or anything else that would be useful for an investigation?
98
99For extra security, I use limit all incoming connections to SSH
100connections through my server firewall (=ufw=) and disable common SSH
101settings. After all of that, I use =fail2ban= as a preventative measure
102against brute-force login attempts.
103
104As another piece of security, you can randomize your SSH port to ensure
105that random scanners or attackers can't easily try to force their way
106into your network. For example, you can edit the port rules in your
107server to block all connection requests to port =22= but forward all
108remote connections from port =12345= to your server's port =22=. Then
109you just need to SSH to your network via your randomized port.
110
111** =ufw=
112To see how to configure =ufw=, see my other post:
113[[../secure-your-network-with-the-uncomplicated-firewall/][Secure
114Your Network with the Uncomplicated Firewall]].
115
116The general notion with an on-device firewall is that you want to deny
117all incoming connections by default and then selectively open certain
118ports for services or users that you know need access.
119
120If you know that you will only be logging into this server from a
121certain set or list of IPs, you can always set the firewall to only
122allow connections to port 22 from those IPs.
123
124For a quick start to only allow SSH connections to the server, use this:
125
126#+begin_src sh
127sudo ufw default deny incoming
128sudo ufw default allow outgoing
129sudo ufw allow 22
130sudo ufw enable
131#+end_src
132
133#+caption: ufw
134[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png]]
135
136** =ssh=
1371. Using SSH Keys
138
139 First, make sure you have an SSH keypair generated on the device(s)
140 that you'll be using to log in to the server. If you don't have an
141 SSH key, run this command:
142
143 #+begin_src sh
144 ssh-keygen
145 #+end_src
146
147 Now that we have an SSH key, copy it to the server with the following
148 command, which will ask for the user's password before accepting the
149 key:
150
151 #+begin_src sh
152 ssh-copy-id my_user@my_server
153 #+end_src
154
155 If you have multiple keys, you'll need to specify which to use. After
156 it's complete, =ssh= back into the server as that user and make sure
157 it doesn't ask for a password.
158
1592. Disable Password & Root Authentication
160
161 Now that we can access the server without a password, we will disable
162 password authentication and disable anyone from using =ssh= to login
163 as =root=.
164
165 To do this, open the =sshd_config= file:
166
167 #+begin_src sh
168 sudo nano /etc/ssh/sshd_config
169 #+end_src
170
171 You'll need to update the parameters to the values below. If one of
172 these rules is commented-out or doesn't exist, create the rule at the
173 bottom of the file.
174
175 #+begin_src config
176 PermitRootLogin no
177 PasswordAuthentication no
178 PubkeyAuthentication yes
179 #+end_src
180
181 Finally, restart the =ssh= service:
182
183 #+begin_src sh
184 sudo systemctl restart sshd.service
185 #+end_src
186
187 To test that everything's working so far, open ANOTHER terminal and
188 try logging in as =root= over SSH. It is very important that you keep
189 your current SSH session open and test with an additional session, or
190 you will lock yourself out at some point and will need to use a
191 recovery method (e.g., hooking monitor up to home server) to get
192 yourself back in.
193
1943. Enable MFA for =ssh=
195
196 This part is optional, but I highly recommend it. So far, we've
197 ensured that no one can log into our user on the server without using
198 our secret key, and we've ensured that no one can log in remotely as
199 =root=. Next, you can enable MFA authentication for =ssh=
200 connections.
201
202 This process involves editing a couple files and installing an MFA
203 package, so I will not include all the details in this post. To see
204 how to configure MFA for =ssh=, see my other post:
205 [[../enable-totp-mfa-for-ssh/][Enabling MFA for SSH]].
206
207 #+caption: SSH MFA
208 [[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png]]
209
210** =fail2ban=
211I haven't written a post on how I use =fail2ban=, but it's quite simple.
212I use the default =sshd= jail, but you can always create new jails for
213respective applications or ports. For example, if you use Nginx as your
214web server, you can use the =nginx-http-auth= jail.
215
216In order to get it up and running, use the following commands:
217
218#+begin_src sh
219sudo apt install fail2ban
220sudo fail2ban-client start sshd
221sudo fail2ban-client status sshd
222#+end_src
223
224This should be used as a last-resort defense and shouldn't be a
225replacement for the security measures mentioned above.
226
227#+caption: fail2ban
228[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png]]
229
230* Switch
231Between the router and any local devices is my managed switch, which is
232used to create VLANs. The example below shows how I would isolate the
233VLANs if I were starting to host a single service at home.
234
235** 802.1Q VLAN Configuration
236In this configuration, port 8 is the public server that needs to be
237accessed from the outside. Port 23 is my 'dedicated service port' for
238this server. In order to SSH to this server, I need to plug my laptop
239into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a
240monitor and keyboard directly to the server to manage it.
241
242| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
243|---------+-----------+--------------+--------------+----------------|
244| 1 | Default | 1-24 | | 1-24 |
245| 2 | Server | 1,8,23 | | 1,8,23 |
246
247** 802.1Q VLAN PVID Setting
248Once the VLAN is created, I simply add the =VLAN ID= of =2= as the
249=PVID= for any related ports (in this case, see that ports =8= and =23=
250have a PVID of =2=).
251
252| Port | PVID |
253|------+------|
254| 1 | 1 |
255| 2 | 1 |
256| 3 | 1 |
257| 4 | 1 |
258| 5 | 1 |
259| 6 | 1 |
260| 7 | 1 |
261| 8 | 2 |
262| 9 | 1 |
263| 10 | 1 |
264| 11 | 1 |
265| 12 | 1 |
266| 13 | 1 |
267| 14 | 1 |
268| 15 | 1 |
269| 16 | 1 |
270| 17 | 1 |
271| 18 | 1 |
272| 19 | 1 |
273| 20 | 1 |
274| 21 | 1 |
275| 22 | 1 |
276| 23 | 2 |
277| 24 | 1 |
278
279* Router
280On my router, the configuration was as easy as opening the firewall
281settings and unblocking the ports I needed for my services (e.g.,
282HTTP/S, Plex, SSH, MySQL, etc.).
283
284+Since I'm relying on an ISP-provided modem/router combo for now (not by
285choice), I do not use any other advanced settings on my router that
286would inhibit any valid traffic to these services.+
287
288The paragraph above regarding the ISP-owned router is no longer accurate
289as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
290this router, I enabled port forwarding/firewall rules, segregate the
291network based on the device, and enable traffic restrictions (e.g.,
292silently drop traffic from certain countries and threat categories).
293
294If you have the option with your ISP, I recommend using a personal
295router with software that you are familiar with so that you can explore
296all the options available to you.
297
298* Physical Security
299One large piece of self-hosting that people generally don't discuss
300online is physical security. However, physical security is very
301important for everyone who hosts a server like this. Exactly /how/
302important it is depends on the server use/purpose.
303
304If you self-host customer applications that hold protected data (HIPAA,
305GDPR, COPPA, etc.), then physical security is extremely important and
306cannot be ignored. If you simply host a blog and some hobby sites, then
307it's a relatively minor consideration, but one you still need to think
308about.
309
310** Location
311The first consideration is quite simple: location. - Is the server
312within a property you own or housed on someone else's property? - Is it
313nearby (in your house, in your work office, in your neighbor's garage,
314in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
315there climate considerations, such as humidity, fires, tornadoes,
316monsoons? - Do you have emergency equipment nearby in case of emergency?
317
318** Hardware Ownership
319Secondly, consider the hardware itself: - Do you own the server in its
320entirety? - Are any other users able to access the server, even if your
321data/space is segregated? - If you're utilizing a third party, do they
322have any documentation to show responsibility? This could be a SOC 1/2/3
323report, ISO compliance report, internal security/safety documentation.
324
325** Physical Controls
326Regardless of who owns the hardware, ensure that there are adequate
327safeguards in place, if necessary. These usually don't apply to small
328home servers and are usually covered already if you're utilizing a third
329party.
330
331These can include: - Server bezel locks - Server room locks - physical,
332digital, or biometric authentication - Security cameras - Raised
333floors/lowered ceilings with proper guards/gates in-place within the
334floors or ceilings - Security personnel - Log sheets and/or guest badges
content/blog/2022-03-26-ssh-mfa.org → content/blog/2022-03-26-ssh-mfa.md renamed +73 −63
@@ -1,78 +1,84 @@
1#+title: Enable TOTP MFA for SSH 1+++
2#+date: 2022-03-26 2date = 2022-03-26
3#+description: Learn how to enable timed one-time passcodes for SSH. 3title = "Enablt TOTP MFA for SSH"
4#+filetags: :security: 4description = ""
5draft = false
6+++
7
8# Why Do I Need MFA for SSH?
5 9
6* Why Do I Need MFA for SSH?
7If you are a sysadmin of a server anywhere (that includes at home!), you 10If you are a sysadmin of a server anywhere (that includes at home!), you
8may want an added layer of protection against intruders. This is not a 11may want an added layer of protection against intruders. This is not a
9replacement for other security measures, such as: 12replacement for other security measures, such as:
10 13
11- Disable root SSH 14- Disable root SSH
12- Disable SSH password authentication 15- Disable SSH password authentication
13- Allow only certain users to login via SSH 16- Allow only certain users to login via SSH
14- Allow SSH only from certain IPs 17- Allow SSH only from certain IPs
15 18
16However, MFA can be added as an additional security measure to ensure 19However, MFA can be added as an additional security measure to ensure
17that your server is protected. This is especially important if you need 20that your server is protected. This is especially important if you need
18to allow password authentication for SSH. 21to allow password authentication for SSH.
19 22
20For more guidance on server security measures, see my other post: 23For more guidance on server security measures, see my other post:
21[[../hardening-a-public-facing-home-server/][Hardening a 24[Hardening a Public-Facing Home
22Public-Facing Home Server]]. 25Server](../hardening-a-public-facing-home-server/).
26
27# Install MFA PAM Module
23 28
24* Install MFA PAM Module
25PAM, which stands for Pluggable Authentication Module, is an 29PAM, which stands for Pluggable Authentication Module, is an
26authentication infrastructure used on Linux systems to authenticate a 30authentication infrastructure used on Linux systems to authenticate a
27user. In order to use this technology, let's install the 31user. In order to use this technology, let\'s install the
28=libpam-google-authenticator= package: 32`libpam-google-authenticator` package:
29 33
30#+begin_src sh 34```sh
31sudo apt-get update 35sudo apt-get update
32#+end_src 36```
33 37
34#+begin_src sh 38```sh
35sudo apt-get install libpam-google-authenticator 39sudo apt-get install libpam-google-authenticator
36#+end_src 40```
41
42# Initialize the PAM Module
43
44## Interactive Method
37 45
38* Initialize the PAM Module
39** Interactive Method
40Once the package is installed, initialize it and following the 46Once the package is installed, initialize it and following the
41interactive prompts to generate your OTP or TOTP: 47interactive prompts to generate your OTP or TOTP:
42 48
43#+begin_src sh 49```sh
44google-authenticator 50google-authenticator
45#+end_src 51```
46 52
47If you are not sure how to answer, read the prompts carefully and think 53If you are not sure how to answer, read the prompts carefully and think
48about having to how each situation would affect your normal login 54about having to how each situation would affect your normal login
49attempts. If you are still not sure, use my default responses below. 55attempts. If you are still not sure, use my default responses below.
50 56
51#+begin_src txt 57``` txt
52OUTPUT 58OUTPUT
53 59
54Do you want authentication tokens to be time-based (y/n) y 60Do you want authentication tokens to be time-based (y/n) y
55#+end_src 61```
56 62
57At this point, use an authenticator app somewhere one of your devices to 63At this point, use an authenticator app somewhere one of your devices to
58scan the QR code. Any future login attempts after our upcoming 64scan the QR code. Any future login attempts after our upcoming
59configuration changes will require that TOTP. 65configuration changes will require that TOTP.
60 66
61#+begin_src txt 67``` txt
62OUTPUT 68OUTPUT
63 69
64Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y 70Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y
65#+end_src 71```
66 72
67#+begin_src txt 73``` txt
68OUTPUT 74OUTPUT
69 75
70Do you want to disallow multiple uses of the same authentication 76Do you want to disallow multiple uses of the same authentication
71token? This restricts you to one login about every 30s, but it increases 77token? This restricts you to one login about every 30s, but it increases
72your chances to notice or even prevent man-in-the-middle attacks (y/n) y 78your chances to notice or even prevent man-in-the-middle attacks (y/n) y
73#+end_src 79```
74 80
75#+begin_src txt 81``` txt
76OUTPUT 82OUTPUT
77 83
78By default, a new token is generated every 30 seconds by the mobile app. 84By default, a new token is generated every 30 seconds by the mobile app.
@@ -85,29 +91,30 @@ code, the next code) to 17 permitted codes (the 8 previous codes, the current
85code, and the 8 next codes). This will permit for a time skew of up to 4 minutes 91code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
86between client and server. 92between client and server.
87Do you want to do so? (y/n) n 93Do you want to do so? (y/n) n
88#+end_src 94```
89 95
90#+begin_src txt 96``` txt
91OUTPUT 97OUTPUT
92 98
93If the computer that you are logging into isn't hardened against brute-force 99If the computer that you are logging into isn't hardened against brute-force
94login attempts, you can enable rate-limiting for the authentication module. 100login attempts, you can enable rate-limiting for the authentication module.
95By default, this limits attackers to no more than 3 login attempts every 30s. 101By default, this limits attackers to no more than 3 login attempts every 30s.
96Do you want to enable rate-limiting? (y/n) y 102Do you want to enable rate-limiting? (y/n) y
97#+end_src 103```
104
105## Non-Interactive Method
98 106
99** Non-Interactive Method
100If you need to do this quickly, know your responses to the prompts, or 107If you need to do this quickly, know your responses to the prompts, or
101are setting this up for numerous users, the non-interactive method can 108are setting this up for numerous users, the non-interactive method can
102be much faster: 109be much faster:
103 110
104#+begin_src sh 111```sh
105google-authenticator -t -d -f -r 3 -R 30 -w 3 112google-authenticator -t -d -f -r 3 -R 30 -w 3
106#+end_src 113```
107 114
108The options referenced above are as follows: 115The options referenced above are as follows:
109 116
110#+begin_src txt 117``` txt
111google-authenticator [<options>] 118google-authenticator [<options>]
112 -h, --help Print this message 119 -h, --help Print this message
113 -c, --counter-based Set up counter-based (HOTP) verification 120 -c, --counter-based Set up counter-based (HOTP) verification
@@ -127,63 +134,66 @@ google-authenticator [<options>]
127 -w, --window-size=W Set window of concurrently valid codes 134 -w, --window-size=W Set window of concurrently valid codes
128 -W, --minimal-window Disable window of concurrently valid codes 135 -W, --minimal-window Disable window of concurrently valid codes
129 -e, --emergency-codes=N Number of emergency codes to generate 136 -e, --emergency-codes=N Number of emergency codes to generate
130#+end_src 137```
131 138
132This fully configures the authenticator, saves it to a file, and then 139This fully configures the authenticator, saves it to a file, and then
133outputs the secret key, QR code, and recovery codes. (If you add the 140outputs the secret key, QR code, and recovery codes. (If you add the
134flag =-q=, then there won't be any output). If you use this command in 141flag `-q`, then there won\'t be any output). If you use this
135an automated fashion, make sure your script captures the secret key 142command in an automated fashion, make sure your script captures the
136and/or recovery codes and makes them available to the user. 143secret key and/or recovery codes and makes them available to the user.
144
145# PAM Configuration Settings
137 146
138* PAM Configuration Settings 147Once you\'ve enabled the T/OTP and have it saved to an MFA app on your
139Once you've enabled the T/OTP and have it saved to an MFA app on your 148phone or other device, open the PAM `sshd` file:
140phone or other device, open the PAM =sshd= file:
141 149
142#+begin_src sh 150```sh
143sudo nano /etc/pam.d/sshd 151sudo nano /etc/pam.d/sshd
144#+end_src 152```
145 153
146You need to do two things in this file. First, add the following lines 154You need to do two things in this file. First, add the following lines
147to the bottom of the file: 155to the bottom of the file:
148 156
149#+begin_src config 157``` config
150auth required pam_google_authenticator.so nullok 158auth required pam_google_authenticator.so nullok
151auth required pam_permit.so 159auth required pam_permit.so
152#+end_src 160```
153 161
154Second, comment-out the following line near the top of the file. 162Second, comment-out the following line near the top of the file.
155 163
156If you leave this line uncommented, every SSH login attempt will ask for 164If you leave this line uncommented, every SSH login attempt will ask for
157the following three authentication factors: 165the following three authentication factors:
158 166
1591. Publickey 1671. Publickey
1602. Password 1682. Password
1613. T/OTP code 1693. T/OTP code
162 170
163#+begin_src config 171``` config
164#@include common-auth 172#@include common-auth
165#+end_src 173```
174
175# SSH Configuration Settings
166 176
167* SSH Configuration Settings 177Finally, edit the `sshd_config` file again:
168Finally, edit the =sshd_config= file again:
169 178
170#+begin_src sh 179```sh
171sudo nano /etc/ssh/sshd_config 180sudo nano /etc/ssh/sshd_config
172#+end_src 181```
173 182
174You'll need to change =ChallengeResponseAuthentication= to yes and add 183You\'ll need to change `ChallengeResponseAuthentication` to
175the =AuthenticationMethods= line to the bottom of the file. 184yes and add the `AuthenticationMethods` line to the bottom of
185the file.
176 186
177#+begin_src config 187``` config
178ChallengeResponseAuthentication yes 188ChallengeResponseAuthentication yes
179AuthenticationMethods publickey,password publickey,keyboard-interactive 189AuthenticationMethods publickey,password publickey,keyboard-interactive
180#+end_src 190```
181 191
182Finally, restart the =ssh= service: 192Finally, restart the `ssh` service:
183 193
184#+begin_src sh 194```sh
185sudo systemctl restart sshd.service 195sudo systemctl restart sshd.service
186#+end_src 196```
187 197
188The next time you log in, you should be greeted with a verification code 198The next time you log in, you should be greeted with a verification code
189request! 199request!
content/blog/2022-04-02-nginx-reverse-proxy.md added +235
@@ -0,0 +1,235 @@
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
11services and clients/users (e.g., the internet). The reverse proxy
12intercepts all requests from clients at the network edge and uses its
13configuration files to determine where 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- Server~01~ (`example.com`)
20- Server~02~ (`service01.example.com`)
21- Server~03~ (`service02.example.com`)
22
23I also run a reverse proxy in my home that intercepts all public
24traffic:
25
26- Reverse Proxy
27
28Assume that I have a domain name (`example.com`) that allows
29clients to request websites or services from my home servers.
30
31In this case, the reverse proxy will intercept all traffic from
32`example.com` that enters my network and determine if the
33client is requesting valid data, based on my configuration.
34
35If the user is requesting `example.com` and my configuration
36files say that Server~01~ holds that data, Nginx will send the user to
37Server~01~. If I were to change the configuration so that
38`example.com` is routed to Server~02~, that same user would
39be sent to Server~02~ instead.
40
41``` txt
42┌──────┐ ┌───────────┐
43│ User │─┐ ┌──► Server_01 │
44└──────┘ │ │ └───────────┘
45 │ ┌──────────┐ ┌───────────────┐ │ ┌───────────┐
46 ├────► Internet ├───► Reverse Proxy ├─────├──► Server_02 │
47 │ └──────────┘ └───────────────┘ │ └───────────┘
48┌──────┐ │ │ ┌───────────┐
49│ User │─┘ └──► Server_03 │
50└──────┘ └───────────┘
51```
52
53# Reverse Proxy Options
54
55There are a lot of options when it comes to reverse proxy servers, so
56I\'m just going to list a few of the options I\'ve heard recommended
57over the last few years:
58
59- [Nginx](https://nginx.com)
60- [Caddy](https://caddyserver.com)
61- [Traefik](https://traefik.io/)
62- [HAProxy](https://www.haproxy.org/)
63- [Squid](https://ubuntu.com/server/docs/proxy-servers-squid)
64
65In this post, we will be using Nginx as our reverse proxy, running on
66Ubuntu Server 20.04.4 LTS.
67
68# Nginx Reverse Proxy Example
69
70## Local Applications
71
72You may be like me and have a lot of applications running on your local
73network that you\'d like to expose publicly with a domain.
74
75In my case, I have services running in multiple Docker containers within
76a single server and want a way to visit those services from anywhere
77with a URL. For example, on my local network, [Dashy](https://dashy.to)
78runs through port 4000 (`localhost:4000`) and [Uptime
79Kuma](https://github.com/louislam/uptime-kuma) runs through port 3001
80(`localhost:3001`).
81
82In order to expose these services to the public, I will need to do the
83following:
84
851. Set up DNS records for a domain or subdomain (one per service) to
86 point toward the IP address of the server.
872. Open up the server network\'s HTTP and HTTPS ports (80 & 443) so
88 that the reverse proxy can accept traffic and determine where to
89 send it.
903. Install the reverse proxy software.
914. Configure the reverse proxy to recognize which service should get
92 traffic from any of the domains or subdomains.
93
94## Step 1: DNS Configuration
95
96To start, update your DNS configuration so that you have an
97`A` record for each domain or subdomain.
98
99The `A` records should point toward the public IP address of
100the server. If you don\'t know the public IP address, log in to the
101server and run the following command:
102
103```sh
104curl ifconfig.co
105```
106
107In the DNS example below, `xxx.xxx.xxx.xxx` is the public IP
108address of the server.
109
110``` config
111example.com A xxx.xxx.xxx.xxx
112uptime.example.com A xxx.xxx.xxx.xxx
113dashy.example.com A xxx.xxx.xxx.xxx
114www CNAME example.com
115```
116
117Finally, ensure the DNS has propagated correctly with [DNS
118Checker](https://dnschecker.org) by entering your domains or subdomains
119in the search box and ensuring the results are showing the correct IP
120address.
121
122## Step 2: Open Network Ports
123
124This step will be different depending on which router you have in your
125home. If you\'re not sure, try to visit
126[192.168.1.1](http://192.168.1.1) in your browser. Login credentials are
127usually written on a sticker somewhere on your modem/router.
128
129Once you\'re able to log in to your router, find the Port Forwarding
130settings. You will need to forward ports `80` and
131`443` to whichever machine is running the reverse proxy.
132
133In my case, the table below shows the port-forwarding rules I\'ve
134created. In this table, `xxx.xxx.xxx.xxx` is the local device
135IP of the reverse proxy server, it will probably be an IP between
136`192.168.1.1` and `192.168.1.255`.
137
138 NAME FROM PORT DEST PORT/IP ENABLED
139 ------- ------ ------ ----------------- ---------
140 HTTP ​\*\* 80 xxx.xxx.xxx.xxx TRUE
141 HTTPS ​\*\* 443 xxx.xxx.xxx.xxx TRUE
142
143Once configured, these rules will direct all web traffic to your reverse
144proxy.
145
146## Step 3: Nginx Installation
147
148To install Nginx, simply run the following command:
149
150```sh
151sudo apt install nginx
152```
153
154If you have a firewall enabled, open up ports `80` and
155`443` on your server so that Nginx can accept web traffic
156from the router.
157
158For example, if you want to use `ufw` for web traffic and
159SSH, run the following commands:
160
161```sh
162sudo ufw allow 'Nginx Full'
163sudo ufw allow SSH
164sudo ufw enable
165```
166
167## Step 4: Nginx Configuration
168
169Now that we have domains pointing toward the server, the only step left
170is to configure the reverse proxy to direct traffic from domains to
171local services.
172
173To start, you\'ll need to create a configuration file for each domain in
174`/etc/nginx/sites-available/`. They will look identical
175except for the `server_name` variable and the
176`proxy_pass` port.
177
178Dashy:
179
180```sh
181nano /etc/nginx/sites-available/dashy.example.com
182```
183
184``` config
185server {
186 listen 80;
187 server_name dashy.example.com;
188
189 location / {
190 proxy_pass http://localhost:4000;
191 }
192}
193```
194
195Uptime:
196
197```sh
198nano /etc/nginx/sites-available/uptime.example.com
199```
200
201``` config
202server {
203 listen 80;
204 server_name uptime.example.com;
205
206 location / {
207 proxy_pass http://localhost:3001;
208 }
209}
210```
211
212Once the configuration files are created, you will need to enable them
213with the `symlink` command:
214
215```sh
216sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
217```
218
219Voilà! Your local services should now be available through their URLs.
220
221# HTTPS with Certbot
222
223If you\'ve followed along, you\'ll notice that your services are only
224available via HTTP (not HTTPS).
225
226If you want to enable HTTPS for your new domains, you will need to
227generate SSL/TLS certificates for them. The easiest way to generate
228certificates on Nginx is [Certbot](https://certbot.eff.org):
229
230```sh
231sudo apt install snapd; sudo snap install core; sudo snap refresh core
232sudo snap install --classic certbot
233sudo ln -s /snap/bin/certbot /usr/bin/certbot
234sudo certbot --nginx
235```
content/blog/2022-04-02-nginx-reverse-proxy.org deleted −220
@@ -1,220 +0,0 @@
1#+title: Set-Up a Reverse Proxy with Nginx
2#+date: 2022-04-02
3#+description: Learn how to set-up an Nginx reverse proxy from scratch.
4#+filetags: :nginx:
5
6* What is a Reverse Proxy?
7A reverse proxy is a server that is placed between local servers or
8services and clients/users (e.g., the internet). The reverse proxy
9intercepts all requests from clients at the network edge and uses its
10configuration files to determine where each request should be sent.
11
12** A Brief Example
13For example, let's say that I run three servers in my home:
14
15- Server_{01} (=example.com=)
16- Server_{02} (=service01.example.com=)
17- Server_{03} (=service02.example.com=)
18
19I also run a reverse proxy in my home that intercepts all public
20traffic:
21
22- Reverse Proxy
23
24Assume that I have a domain name (=example.com=) that allows clients to
25request websites or services from my home servers.
26
27In this case, the reverse proxy will intercept all traffic from
28=example.com= that enters my network and determine if the client is
29requesting valid data, based on my configuration.
30
31If the user is requesting =example.com= and my configuration files say
32that Server_{01} holds that data, Nginx will send the user to
33Server_{01}. If I were to change the configuration so that =example.com=
34is routed to Server_{02}, that same user would be sent to Server_{02}
35instead.
36
37#+begin_src txt
38┌──────┐ ┌───────────┐
39│ User │─┐ ┌──► Server_01 │
40└──────┘ │ │ └───────────┘
41 │ ┌──────────┐ ┌───────────────┐ │ ┌───────────┐
42 ├────► Internet ├───► Reverse Proxy ├─────├──► Server_02 │
43 │ └──────────┘ └───────────────┘ │ └───────────┘
44┌──────┐ │ │ ┌───────────┐
45│ User │─┘ └──► Server_03 │
46└──────┘ └───────────┘
47#+end_src
48
49* Reverse Proxy Options
50There are a lot of options when it comes to reverse proxy servers, so
51I'm just going to list a few of the options I've heard recommended over
52the last few years:
53
54- [[https://nginx.com][Nginx]]
55- [[https://caddyserver.com][Caddy]]
56- [[https://traefik.io/][Traefik]]
57- [[https://www.haproxy.org/][HAProxy]]
58- [[https://ubuntu.com/server/docs/proxy-servers-squid][Squid]]
59
60In this post, we will be using Nginx as our reverse proxy, running on
61Ubuntu Server 20.04.4 LTS.
62
63* Nginx Reverse Proxy Example
64** Local Applications
65You may be like me and have a lot of applications running on your local
66network that you'd like to expose publicly with a domain.
67
68In my case, I have services running in multiple Docker containers within
69a single server and want a way to visit those services from anywhere
70with a URL. For example, on my local network,
71[[https://dashy.to][Dashy]] runs through port 4000 (=localhost:4000=)
72and [[https://github.com/louislam/uptime-kuma][Uptime Kuma]] runs
73through port 3001 (=localhost:3001=).
74
75In order to expose these services to the public, I will need to do the
76following:
77
781. Set up DNS records for a domain or subdomain (one per service) to
79 point toward the IP address of the server.
802. Open up the server network's HTTP and HTTPS ports (80 & 443) so that
81 the reverse proxy can accept traffic and determine where to send it.
823. Install the reverse proxy software.
834. Configure the reverse proxy to recognize which service should get
84 traffic from any of the domains or subdomains.
85
86** Step 1: DNS Configuration
87To start, update your DNS configuration so that you have an =A= record
88for each domain or subdomain.
89
90The =A= records should point toward the public IP address of the server.
91If you don't know the public IP address, log in to the server and run
92the following command:
93
94#+begin_src sh
95curl ifconfig.co
96#+end_src
97
98In the DNS example below, =xxx.xxx.xxx.xxx= is the public IP address of
99the server.
100
101#+begin_src config
102example.com A xxx.xxx.xxx.xxx
103uptime.example.com A xxx.xxx.xxx.xxx
104dashy.example.com A xxx.xxx.xxx.xxx
105www CNAME example.com
106#+end_src
107
108Finally, ensure the DNS has propagated correctly with
109[[https://dnschecker.org][DNS Checker]] by entering your domains or
110subdomains in the search box and ensuring the results are showing the
111correct IP address.
112
113** Step 2: Open Network Ports
114This step will be different depending on which router you have in your
115home. If you're not sure, try to visit
116[[http://192.168.1.1][192.168.1.1]] in your browser. Login credentials
117are usually written on a sticker somewhere on your modem/router.
118
119Once you're able to log in to your router, find the Port Forwarding
120settings. You will need to forward ports =80= and =443= to whichever
121machine is running the reverse proxy.
122
123In my case, the table below shows the port-forwarding rules I've
124created. In this table, =xxx.xxx.xxx.xxx= is the local device IP of the
125reverse proxy server, it will probably be an IP between =192.168.1.1=
126and =192.168.1.255=.
127
128| NAME | FROM | PORT | DEST PORT/IP | ENABLED |
129|-------+------+------+-----------------+---------|
130| HTTP | ​** | 80 | xxx.xxx.xxx.xxx | TRUE |
131| HTTPS | ​** | 443 | xxx.xxx.xxx.xxx | TRUE |
132
133Once configured, these rules will direct all web traffic to your reverse
134proxy.
135
136** Step 3: Nginx Installation
137To install Nginx, simply run the following command:
138
139#+begin_src sh
140sudo apt install nginx
141#+end_src
142
143If you have a firewall enabled, open up ports =80= and =443= on your
144server so that Nginx can accept web traffic from the router.
145
146For example, if you want to use =ufw= for web traffic and SSH, run the
147following commands:
148
149#+begin_src sh
150sudo ufw allow 'Nginx Full'
151sudo ufw allow SSH
152sudo ufw enable
153#+end_src
154
155** Step 4: Nginx Configuration
156Now that we have domains pointing toward the server, the only step left
157is to configure the reverse proxy to direct traffic from domains to
158local services.
159
160To start, you'll need to create a configuration file for each domain in
161=/etc/nginx/sites-available/=. They will look identical except for the
162=server_name= variable and the =proxy_pass= port.
163
164Dashy:
165
166#+begin_src sh
167nano /etc/nginx/sites-available/dashy.example.com
168#+end_src
169
170#+begin_src config
171server {
172 listen 80;
173 server_name dashy.example.com;
174
175 location / {
176 proxy_pass http://localhost:4000;
177 }
178}
179#+end_src
180
181Uptime:
182
183#+begin_src sh
184nano /etc/nginx/sites-available/uptime.example.com
185#+end_src
186
187#+begin_src config
188server {
189 listen 80;
190 server_name uptime.example.com;
191
192 location / {
193 proxy_pass http://localhost:3001;
194 }
195}
196#+end_src
197
198Once the configuration files are created, you will need to enable them
199with the =symlink= command:
200
201#+begin_src sh
202sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
203#+end_src
204
205Voilà! Your local services should now be available through their URLs.
206
207* HTTPS with Certbot
208If you've followed along, you'll notice that your services are only
209available via HTTP (not HTTPS).
210
211If you want to enable HTTPS for your new domains, you will need to
212generate SSL/TLS certificates for them. The easiest way to generate
213certificates on Nginx is [[https://certbot.eff.org][Certbot]]:
214
215#+begin_src sh
216sudo apt install snapd; sudo snap install core; sudo snap refresh core
217sudo snap install --classic certbot
218sudo ln -s /snap/bin/certbot /usr/bin/certbot
219sudo certbot --nginx
220#+end_src
content/blog/2022-04-09-pinetime.org → content/blog/2022-04-09-pinetime.md renamed +75 −63
@@ -1,39 +1,45 @@
1#+title: PineTime: An Open-Source Smart Watch 1+++
2#+date: 2022-04-09 2date = 2022-04-09
3#+description: Playing with the PineTime smart watch. 3title = "PineTime: An Open-Source Smart Watch"
4#+filetags: :hardware: 4description = ""
5 5draft = false
6* PineTime Product Information 6+++
7** Overview 7
8The [[https://www.pine64.org/pinetime/][PineTime]] is an open-source 8# PineTime Product Information
9smartwatch, created by [[https://www.pine64.org][PINE64]]. Originally 9
10## Overview
11
12The [PineTime](https://www.pine64.org/pinetime/) is an open-source
13smartwatch, created by [PINE64](https://www.pine64.org). Originally
10announced in September 2019, this ARM-based watch is a fantastic option 14announced in September 2019, this ARM-based watch is a fantastic option
11for users who want the benefits of a modern smartwatch with the backing 15for users who want the benefits of a modern smartwatch with the backing
12of open-source components and software. 16of open-source components and software.
13 17
14** Product Specifications 18## Product Specifications
15I won't dive into too many details that you can find on
16[[https://www.pine64.org/pinetime/][the product page]], but I wanted to
17point out the prices for each watch and the primary functions:
18 19
191. Price: 20I won\'t dive into too many details that you can find on [the product
21page](https://www.pine64.org/pinetime/), but I wanted to point out the
22prices for each watch and the primary functions:
20 23
21 - $26.99 (Sealed) 241. Price:
22 - $24.99 (Dev Kit)
23 - $51.98 (One Sealed + One Dev Kit)
24 25
252. Primary Functionality: 26 - \$26.99 (Sealed)
27 - \$24.99 (Dev Kit)
28 - \$51.98 (One Sealed + One Dev Kit)
26 29
27 - Clock (+ Smartphone Sync) 302. Primary Functionality:
28 - Pedometer 31
29 - Heart Rate Monitor 32 - Clock (+ Smartphone Sync)
30 - Sleep Monitor 33 - Pedometer
31 - Calories burned 34 - Heart Rate Monitor
32 - Messaging 35 - Sleep Monitor
33 - Smartphone Notifications 36 - Calories burned
34 - Media Controls 37 - Messaging
38 - Smartphone Notifications
39 - Media Controls
40
41# Unboxing
35 42
36* Unboxing
37Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and 43Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and
38was delivered on 2022-03-23. With the current delays on shipping times 44was delivered on 2022-03-23. With the current delays on shipping times
39around the world (and the semiconductor shortage), a month for delivery 45around the world (and the semiconductor shortage), a month for delivery
@@ -46,76 +52,81 @@ USB wall adapter).
46The watch itself was able to turn on immediately when I pulled it out of 52The watch itself was able to turn on immediately when I pulled it out of
47the box, but the battery was depleted and required charging right away. 53the box, but the battery was depleted and required charging right away.
48 54
49#+caption: PineTime Box Contents 55![PineTime Box
50[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png]] 56Contents](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png)
57
58# Software
59
60## Watch OS: InfiniTime
51 61
52* Software
53** Watch OS: InfiniTime
54While turning on the watch for the first time, some of the main design 62While turning on the watch for the first time, some of the main design
55choices you can see in the watch OS, 63choices you can see in the watch OS,
56[[https://wiki.pine64.org/wiki/InfiniTime][InfiniTime]], are: 64[InfiniTime](https://wiki.pine64.org/wiki/InfiniTime), are:
57 65
58- A square bezel, not too thin against the sides of the watch. 66- A square bezel, not too thin against the sides of the watch.
59- A simple, rubber band. 67- A simple, rubber band.
60- Basic font and screen pixel design. 68- Basic font and screen pixel design.
61- Swipe gestures to access other screens. 69- Swipe gestures to access other screens.
62 70
63#+caption: PineTime Screens 71![PineTime
64[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png]] 72Screens](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png)
65 73
66The OS itself is fantastic in terms of functionality for me. It does 74The OS itself is fantastic in terms of functionality for me. It does
67exactly what a smartwatch should do - track time, steps, heart rates, 75exactly what a smartwatch should do - track time, steps, heart rates,
68and connect to another smart device, without being overly burdensome to 76and connect to another smart device, without being overly burdensome to
69the user. 77the user.
70 78
71My only gripe so far is that it's /really/ difficult to swipe to 79My 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 80different 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 81sure if this is an OS or hardware issue, but it makes it quite hard to
74quickly move around the screens. 82quickly move around the screens.
75 83
76However, my absolute favorite design choice is that the button the side 84However, my absolute favorite design choice is that the button the side
77turns the screen on and off and tilting/waving my wrist doesn't 85turns the screen on and off and tilting/waving my wrist doesn\'t
78accidentally turn on the screen. With other watches, I absolutely hated 86accidentally 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., 87not 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). 88blinding myself while wearing a watch at night because I moved my arm).
81 89
82** iOS App: InfiniLink 90## iOS App: InfiniLink
91
83Since I am using iOS as my primary mobile device OS, I am using the 92Since I am using iOS as my primary mobile device OS, I am using the
84[[https://github.com/xan-m/InfiniLink][InfiniLink]] app to connect my 93[InfiniLink](https://github.com/xan-m/InfiniLink) app to connect my
85watch. 94watch.
86 95
87This app provides the following for PineTime owners: 96This app provides the following for PineTime owners:
88 97
89- Firmware updates 98- Firmware updates
90- Steps 99- Steps
91- Charts 100- Charts
92- Notifications 101- Notifications
93 102
94I mashed up a few screenshots to show off the home page, menu, firmware 103I mashed up a few screenshots to show off the home page, menu, firmware
95update, and notification test screens: 104update, and notification test screens:
96 105
97#+caption: InfiniLink Home 106![InfiniLink
98[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png]] 107Home](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png)
99 108
100Another big feature of InfiniLink is the ability to track pedometer 109Another big feature of InfiniLink is the ability to track pedometer
101steps in a collection of beautiful graphs, with the option to change 110steps in a collection of beautiful graphs, with the option to change
102your step goal and add in manual steps. 111your step goal and add in manual steps.
103 112
104#+caption: InfiniLink Steps 113![InfiniLink
105[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png]] 114Steps](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png)
106 115
107Finally, there are charts to display the battery percentage and heart 116Finally, there are charts to display the battery percentage and heart
108rates over time. This area also comes with an option to clear data. 117rates over time. This area also comes with an option to clear data.
109 118
110#+caption: InfiniLink Charts 119![InfiniLink
111[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png]] 120Charts](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png)
121
122# Final Thoughts
123
124## Pros
112 125
113* Final Thoughts
114** Pros
115After wearing my watch for a few weeks, I have mostly positive thoughts 126After wearing my watch for a few weeks, I have mostly positive thoughts
116about the watch so far. In the past, I have owned smartwatches by 127about the watch so far. In the past, I have owned smartwatches by
117FitBit, Fossil, Apple, etc. - *but I prefer the PineTime over all of 128FitBit, Fossil, Apple, etc. - **but I prefer the PineTime over all of
118those watches*. 129those watches**.
119 130
120The PineTime strips out all the unnecessary features and performs the 131The PineTime strips out all the unnecessary features and performs the
121functions that it provides effectively and efficiently. 132functions that it provides effectively and efficiently.
@@ -123,11 +134,12 @@ functions that it provides effectively and efficiently.
123The battery life is amazing on this device. By default, the watch seems 134The battery life is amazing on this device. By default, the watch seems
124to last anywhere from a few days to a week before dying. 135to last anywhere from a few days to a week before dying.
125 136
126And of course, it's open source and backed by some of the most dedicated 137And of course, it\'s open source and backed by some of the most
127enthusiasts and developers I've seen. Watching the Matrix channel, 138dedicated enthusiasts and developers I\'ve seen. Watching the Matrix
128forums, and website have been exciting to see. 139channel, forums, and website have been exciting to see.
140
141## Cons
129 142
130** Cons
131If I had to complain about anything, it would simply be the small bugs 143If I had to complain about anything, it would simply be the small bugs
132in some features that can be contributed to the companion apps more than 144in some features that can be contributed to the companion apps more than
133the watch itself. 145the watch itself.
@@ -136,8 +148,8 @@ A regular user would want native notification support out-of-the-box,
136which is the biggest item not working for me at the moment. 148which is the biggest item not working for me at the moment.
137 149
138My only other complaint is that the battery indicator on the watch 150My only other complaint is that the battery indicator on the watch
139doesn't seem accurate when it's nearing depletion - it seems that 151doesn\'t seem accurate when it\'s nearing depletion - it seems that
140there's a bit of battery life left and then my watch is dead very 152there\'s a bit of battery life left and then my watch is dead very
141suddenly after. This could just be me misinterpreting the battery level 153suddenly after. This could just be me misinterpreting the battery level
142icons, but it has fooled me a few times into thinking I had more battery 154icons, but it has fooled me a few times into thinking I had more battery
143left than I actually did. 155left than I actually did.
content/blog/2022-06-01-ditching-cloudflare.md added +95
@@ -0,0 +1,95 @@
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
11or applications - I spent the last few months using Cloudflare Tunnels
12and Cloudflare Access to protect my self-hosted websites and
13applications via their proxy traffic model.
14
15However, I have never liked using Cloudflare due to their increasingly
16large share of control over web traffic, as well as their business model
17of being a MITM 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
22propagate.
23
24+I do still have two domains sitting at Cloudflare for the moment while
25I decide if they\'re worth the higher rates (one domain is 30€ and the
26other is 45€).+
27
28> **Update (2022.06.03)**: I ended up transferring my final two domains
29> over to Njalla, clearing my Cloudflare account of personal data, and
30> deleting the Cloudflare account entirely. *I actually feel relieved to
31> have moved on to a provider I trust.*
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 [Gandi.net](https://gandi.net/)?) and it made life a lot
47easier.
48
49## Dynamic DNS Updates
50
51I have built an easy Python script to run (or set-up in
52`cron` to run automatically) that will check my server\'s
53IPv4 and IPv6, compare it to Njalla, and update the DNS records if they
54don\'t match. You can see the full script and process in my other post:
55[Updating Dynamic DNS with Njalla API](../njalla-dns-api/).
56
57I haven\'t used this other method, but I do know that you can create
58`Dynamic` DNS records with Njalla that [work for updating
59dynamic subdomains](https://njal.la/docs/ddns/).
60
61## Njalla\'s DNS Tool
62
63One neat upside to Njalla is that they have a [DNS lookup
64tool](https://check.njal.la/dns/) that provides a lot of great
65information for those of you (AKA: me) who hate using the
66`dig` command.
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 [my own reverse proxy with
81Nginx](file:///blog/set-up-nginx-reverse-proxy/). With the recent
82additions of Unifi hardware in my server/network rack, I am much more
83protected against spam and malicious attacks at the network edge than I
84was before I switched to Cloudflare.
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-01-ditching-cloudflare.org deleted −89
@@ -1,89 +0,0 @@
1#+title: Ditching Cloudflare for Njalla
2#+date: 2022-06-01
3#+description: A retrospective on my decision to leave Cloudflare and move to Njalla for domain registration and DNS.
4#+filetags: :sysadmin:
5
6* Registrar
7After spending a year or so using Cloudflare for DNS only - no proxying
8or applications - I spent the last few months using Cloudflare Tunnels
9and Cloudflare Access to protect my self-hosted websites and
10applications via their proxy traffic model.
11
12However, I have never liked using Cloudflare due to their increasingly
13large share of control over web traffic, as well as their business model
14of being a MITM for all of your traffic.
15
16So, as of today, I have switched over to [[https://njal.la][Njalla]] as
17my registrar and DNS manager. I was able to easily transfer my domains
18over rapidly, with only one domain taking more than 15-30 minutes to
19propagate.
20
21+I do still have two domains sitting at Cloudflare for the moment while
22I decide if they're worth the higher rates (one domain is 30€ and the
23other is 45€).+
24
25#+begin_quote
26*Update (2022.06.03)*: I ended up transferring my final two domains over
27to Njalla, clearing my Cloudflare account of personal data, and deleting
28the Cloudflare account entirely. /I actually feel relieved to have moved
29on to a provider I trust./
30
31#+end_quote
32
33* DNS
34As noted above, I'm using Njalla exclusively for DNS configurations on
35my domains.
36
37However, the transfer process was not ideal. As soon as the domains
38transferred over, I switched the nameservers from Cloudflare to Njalla
39and lost most of the associated DNS records. So, the majority of the
40time spent during the migration was simply re-typing all the DNS records
41back in one-by-one.
42
43This would be much simpler if I were able to edit the plain-text format
44of the DNS configuration. I was able to do that at a past registrar
45(perhaps it was [[https://gandi.net/][Gandi.net]]?) and it made life a
46lot easier.
47
48** Dynamic DNS Updates
49I have built an easy Python script to run (or set-up in =cron= to run
50automatically) that will check my server's IPv4 and IPv6, compare it to
51Njalla, and update the DNS records if they don't match. You can see the
52full script and process in my other post:
53[[../njalla-dns-api/][Updating Dynamic DNS with Njalla API]].
54
55I haven't used this other method, but I do know that you can create
56=Dynamic= DNS records with Njalla that
57[[https://njal.la/docs/ddns/][work for updating dynamic subdomains]].
58
59** Njalla's DNS Tool
60One neat upside to Njalla is that they have a
61[[https://check.njal.la/dns/][DNS lookup tool]] that provides a lot of
62great information for those of you (AKA: me) who hate using the =dig=
63command.
64
65This was very useful for monitoring a couple of my transferred domains
66to see when the changes in nameservers, records, and DNSSEC went into
67effect.
68
69* Tunnel
70Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on
71Cloudflare's servers) and allowed me to mask the private IP address of
72the server hosting my various websites and apps.
73
74However, as I was moving away from Cloudflare, I was not able to find a
75suitable replacement that was both inexpensive and simple. So, I simply
76went back to hosting [[/blog/set-up-nginx-reverse-proxy/][my own reverse
77proxy with Nginx]]. With the recent additions of Unifi hardware in my
78server/network rack, I am much more protected against spam and malicious
79attacks at the network edge than I was before I switched to Cloudflare.
80
81* Access
82Cloudflare Access, another app I used in combination with Cloudflare
83Tunnel, provided an authentication screen that required you to enter
84valid credentials before Cloudflare would forward you to the actual
85website or app (if the website/app has their own authentication, you'd
86then have to authenticate a second time).
87
88I did not replace this service with anything since I only host a handful
89of non-sensitive apps that don't require duplicate authentication.
content/blog/2022-06-07-self-hosting-freshrss.org → content/blog/2022-06-07-self-hosting-freshrss.md renamed +117 −94
@@ -1,9 +1,12 @@
1#+title: Self-Hosting FreshRSS 1+++
2#+date: 2022-06-07 2date = 2022-06-07
3#+description: A guide to self-hosting the FreshRSS application on your own server. 3title = "Self-Hosting FreshRSS"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
7
8# Why RSS?
5 9
6* Why RSS?
7After noticing that I have collected 50+ blogs as bookmarks, I decided 10After noticing that I have collected 50+ blogs as bookmarks, I decided
8to migrate back to using RSS feeds to stay up-to-date with my favorite 11to migrate back to using RSS feeds to stay up-to-date with my favorite
9websites. Using RSS allows me to read all of these posts in a single app 12websites. Using RSS allows me to read all of these posts in a single app
@@ -14,49 +17,56 @@ However, I ran into one issue: syncing subscriptions and read/unread
14posts across devices. Since I want to be able to easily read on both 17posts across devices. Since I want to be able to easily read on both
15mobile and desktop, I decided to look for a self-hosted RSS solution. 18mobile and desktop, I decided to look for a self-hosted RSS solution.
16 19
17Thus, I found [[https://www.freshrss.org/][FreshRSS]] and was able to 20Thus, I found [FreshRSS](https://www.freshrss.org/) and was able to
18successfully install it on my server in about 30 minutes. 21successfully install it on my server in about 30 minutes.
19 22
20* Documentation 23# Documentation
21While it's certainly not robust, the 24
22[[https://freshrss.github.io/FreshRSS/][FreshRSS documentation]] is 25While it\'s certainly not robust, the [FreshRSS
23helpful for figuring out basic information about the service. 26documentation](https://freshrss.github.io/FreshRSS/) is helpful for
27figuring out basic information about the service.
24 28
25However, I wanted to install this service as a Docker container and 29However, I wanted to install this service as a Docker container and
26stumbled across the 30stumbled across the [Docker
27[[https://github.com/FreshRSS/FreshRSS/tree/edge/Docker][Docker README]] 31README](https://github.com/FreshRSS/FreshRSS/tree/edge/Docker) within
28within the GitHub repository. 32the GitHub repository.
33
34This README was the documentation I actually needed. However, as you\'ll
35see below, I still had to manually edit one file
36(`config.php`) to access the API externally via my RSS apps.
29 37
30This README was the documentation I actually needed. However, as you'll 38# Installation
31see below, I still had to manually edit one file (=config.php=) to 39
32access the API externally via my RSS apps. 40## DNS
33 41
34* Installation
35** DNS
36The first step, as required by any external web service, was assigning a 42The first step, as required by any external web service, was assigning a
37domain name to use. I chose to use a subdomain, like =rss.example.com=. 43domain name to use. I chose to use a subdomain, like
44`rss.example.com`.
38 45
39To assign this, I created an =A= record in my DNS settings with the IPv4 46To assign this, I created an `A` record in my DNS settings
40address of the server and an =AAAA= record with the IPv6 address of the 47with the IPv4 address of the server and an `AAAA` record with
41server. Note: assigning an IPv6 (=AAAA=) record is optional, but I like 48the IPv6 address of the server. Note: assigning an IPv6
42to enable IPV6 for my services. 49(`AAAA`) record is optional, but I like to enable IPV6 for my
50services.
43 51
44#+begin_src config 52``` config
45rss.example.com A xxx.xxx.xxx.xxx 53rss.example.com A xxx.xxx.xxx.xxx
46rss.example.com AAAA xxxx:xxxx: ... :xxxx 54rss.example.com AAAA xxxx:xxxx: ... :xxxx
47#+end_src 55```
56
57## Docker
48 58
49** Docker 59I initially tried to set up a `docker-compose.yml` file with
50I initially tried to set up a =docker-compose.yml= file with a =.env= 60a `.env` file because I prefer to have a file I can look back
51file because I prefer to have a file I can look back at later to see how 61at later to see how I initially started the container, but it simply
52I initially started the container, but it simply wouldn't work for me. 62wouldn\'t work for me. I\'m not sure why, but I assume I wasn\'t telling
53I'm not sure why, but I assume I wasn't telling =docker-compose= where 63`docker-compose` where the `.env` file was.
54the =.env= file was.
55 64
56Regardless, I chose to simply run the service with =docker run=. See the 65Regardless, I chose to simply run the service with
57following command for my =docker run= configuration: 66`docker run`. See the following command for my
67`docker run` configuration:
58 68
59#+begin_src sh 69```sh
60sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ 70sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
61 -p 8080:80 \ 71 -p 8080:80 \
62 -e TZ=America/Chicago \ 72 -e TZ=America/Chicago \
@@ -65,40 +75,44 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
65 -v freshrss_extensions:/var/www/FreshRSS/extensions \ 75 -v freshrss_extensions:/var/www/FreshRSS/extensions \
66 --name freshrss \ 76 --name freshrss \
67 freshrss/freshrss 77 freshrss/freshrss
68#+end_src 78```
69 79
70This started the container successfully and allowed me to visit the 80This started the container successfully and allowed me to visit the
71FreshRSS instance at =localhost:8080=. 81FreshRSS instance at `localhost:8080`.
82
83## Fresh RSS Set-Up
72 84
73** Fresh RSS Set-Up 85I **HIGHLY\*** suggest that you set up your user account prior to
74I *HIGHLY** suggest that you set up your user account prior to exposing 86exposing this service to the public. It\'s unlikely that someone is
75this service to the public. It's unlikely that someone is trying to 87trying to access the exact domain or IP/port you\'re assigning here, but
76access the exact domain or IP/port you're assigning here, but as soon as 88as soon as you expose this service, the first person to open the URL
77you expose this service, the first person to open the URL will be able 89will be able to create the admin user.
78to create the admin user.
79 90
80In order to set up your FreshRSS service, open the =localhost:8080= URL 91In order to set up your FreshRSS service, open the
81in your browser (you may need to use a local IP instead of =localhost= 92`localhost:8080` URL in your browser (you may need to use a
82if you're accessing the page from a different machine on the network - 93local IP instead of `localhost` if you\'re accessing the page
83e.g., =192.168.1.20:8080=). 94from a different machine on the network - e.g.,
95`192.168.1.20:8080`).
84 96
85Once the page loads, set up your default user with a strong username and 97Once the page loads, set up your default user with a strong username and
86password. You may also choose to configure other settings prior to 98password. You may also choose to configure other settings prior to
87exposing this service. 99exposing this service.
88 100
89** Nginx Reverse-Proxy 101## Nginx Reverse-Proxy
102
90In order to access this service outside my home, I needed to set up a 103In order to access this service outside my home, I needed to set up a
91reverse-proxy to connect =localhost:8080= to =rss.example.com=. 104reverse-proxy to connect `localhost:8080` to
105`rss.example.com`.
92 106
93First, I created a new Nginx configuration file: 107First, I created a new Nginx configuration file:
94 108
95#+begin_src sh 109```sh
96sudo nano /etc/nginx/sites-available/rss.example.com 110sudo nano /etc/nginx/sites-available/rss.example.com
97#+end_src 111```
98 112
99Within the config file, I pasted the following code: 113Within the config file, I pasted the following code:
100 114
101#+begin_src config 115``` config
102upstream freshrss { 116upstream freshrss {
103 server 127.0.0.1:8080; 117 server 127.0.0.1:8080;
104 keepalive 64; 118 keepalive 64;
@@ -127,79 +141,87 @@ server {
127 proxy_pass_header Authorization; 141 proxy_pass_header Authorization;
128 } 142 }
129} 143}
130#+end_src 144```
131 145
132Finally, restart Nginx and you will be able to access your service via 146Finally, restart Nginx and you will be able to access your service via
133HTTP: 147HTTP:
134 148
135#+begin_src sh 149```sh
136sudo systemctl restart nginx.service 150sudo systemctl restart nginx.service
137#+end_src 151```
152
153## HTTPS
138 154
139** HTTPS 155However, I don\'t want to access my RSS feeds via HTTP. I want it
140However, I don't want to access my RSS feeds via HTTP. I want it
141available only via HTTPS. In order to do this, I ran the 156available only via HTTPS. In order to do this, I ran the
142[[https://certbot.eff.org/][certbot]] program to generate SSL 157[certbot](https://certbot.eff.org/) program to generate SSL certificates
143certificates for me: 158for me:
144 159
145#+begin_src sh 160```sh
146sudo certbot --nginx 161sudo certbot --nginx
147#+end_src 162```
148 163
149This process will automatically generate an SSL certificate for you and 164This process will automatically generate an SSL certificate for you and
150modify the Nginx configuration file to include a redirect from HTTP to 165modify the Nginx configuration file to include a redirect from HTTP to
151HTTPS. 166HTTPS.
152 167
153* Post-Installation Fixes 168# Post-Installation Fixes
169
154At this point, we have a functional FreshRSS website, available from 170At this point, we have a functional FreshRSS website, available from
155anywhere and secured with HTTPS. However, attempting to connect this 171anywhere and secured with HTTPS. However, attempting to connect this
156service to an RSS app resulted in many errors regarding unavailable URLs 172service to an RSS app resulted in many errors regarding unavailable URLs
157and incorrect credentials. 173and incorrect credentials.
158 174
159** API Set-Up 175## API Set-Up
160First, you need to open your user profile in FreshRSS (=Settings= > 176
161=Profile=) and set an API password in the field at the bottom. This is 177First, you need to open your user profile in FreshRSS
162the password you will need to provide to your RSS apps. 178(`Settings` \> `Profile`) and set an API password
179in the field at the bottom. This is the password you will need to
180provide to your RSS apps.
163 181
164Once that is set and saved, click the link below the API password field 182Once that is set and saved, click the link below the API password field
165to open the API check tool. It should look something like 183to open the API check tool. It should look something like
166=https://localhost:8080/api/= or =https://rss.example.com/api/=. 184`https://localhost:8080/api/` or
185`https://rss.example.com/api/`.
167 186
168Within this page, you /should/ see your correct external URL and "PASS" 187Within this page, you *should* see your correct external URL and
169at the bottom of each API type. This would mean everything is set up 188\"PASS\" at the bottom of each API type. This would mean everything is
170correctly, and you can now move on and login to any RSS apps that 189set up correctly, and you can now move on and login to any RSS apps that
171support self-hosted options. 190support self-hosted options.
172 191
173In my case, the URL showed an internal URL and I had a warning that the 192In my case, the URL showed an internal URL and I had a warning that the
174=base_url= variable may be misconfigured. If this is the case, see the 193`base_url` variable may be misconfigured. If this is the
175next section for a fix. 194case, see the next section for a fix.
195
196## Base URL Fix
176 197
177** Base URL Fix 198In order to fix the `base_url` for the API, I opened up my
178In order to fix the =base_url= for the API, I opened up my docker 199docker container with the following command:
179container with the following command:
180 200
181#+begin_src sh 201```sh
182sudo docker exec -it freshrss bash 202sudo docker exec -it freshrss bash
183#+end_src 203```
184 204
185Within this container, update the packages and install an editor: 205Within this container, update the packages and install an editor:
186 206
187#+begin_src sh 207```sh
188apt-get update 208apt-get update
189apt-get install nano 209apt-get install nano
190#+end_src 210```
191 211
192Finally, open up =config.php= in the =data= directory: 212Finally, open up `config.php` in the `data`
213directory:
193 214
194#+begin_src sh 215```sh
195nano data/config.php 216nano data/config.php
196#+end_src 217```
197 218
198Within =config.php=, you will need to update the =base_url= variable and 219Within `config.php`, you will need to update the
199update it to match your external URL. In my case, I simply commented-out 220`base_url` variable and update it to match your external URL.
200the incorrect URL with =//= and added the correct one on a new line: 221In my case, I simply commented-out the incorrect URL with
222`//` and added the correct one on a new line:
201 223
202#+begin_src php 224``` php
203<?php 225<?php
204 return array ( 226 return array (
205 ... 227 ...
@@ -208,25 +230,26 @@ the incorrect URL with =//= and added the correct one on a new line:
208 ... 230 ...
209 ) 231 )
210> 232>
211#+end_src 233```
212 234
213You can now exit the file with =Ctrl + x=, press =y= to save the file, 235You can now exit the file with `Ctrl + x`, press
214and then click =Enter= to keep the same file name. 236`y` to save the file, and then click `Enter` to
237keep the same file name.
215 238
216Finally, just exit out of the docker container: 239Finally, just exit out of the docker container:
217 240
218#+begin_src sh 241```sh
219exit 242exit
220#+end_src 243```
221 244
222Next, just restart the container: 245Next, just restart the container:
223 246
224#+begin_src sh 247```sh
225sudo docker restart freshrss 248sudo docker restart freshrss
226#+end_src 249```
227 250
228Voilà! Your API check should now "PASS" and you should be able to use 251Voilà! Your API check should now \"PASS\" and you should be able to use
229one of the API URLs in your RSS apps. 252one of the API URLs in your RSS apps.
230 253
231In my case, I use [[https://netnewswire.com][NetNewsWire]] on my desktop 254In my case, I use [NetNewsWire](https://netnewswire.com) on my desktop
232and phone. 255and phone.
content/blog/2022-06-16-terminal-lifestyle.org → content/blog/2022-06-16-terminal-lifestyle.md renamed +96 −91
@@ -1,38 +1,42 @@
1#+title: A Terminal Lifestyle 1+++
2#+date: 2022-06-16 2date = 2022-06-16
3#+description: Explaining how I live my digital life mostly in the console/terminal. 3title = "A Terminal Lifestyle"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
5 7
6* Text-Based Simplicity 8# Text-Based Simplicity
7I've detailed my views on web-based minimalism and related topics in 9
10I\'ve detailed my views on web-based minimalism and related topics in
8other posts throughout the years; e.g., JavaScript/CSS bloat slowing 11other posts throughout the years; e.g., JavaScript/CSS bloat slowing
9down websites that are essentially a text document. However, I have 12down websites that are essentially a text document. However, I have
10never really expanded beyond talking about the web and describing how I 13never really expanded beyond talking about the web and describing how I
11focus on minimizing distractions in other digital environments. 14focus on minimizing distractions in other digital environments.
12 15
13This post is going to set the baseline for how I /try/ to live my 16This post is going to set the baseline for how I *try* to live my
14digital life. It does not necessarily get into my physical life, which 17digital life. It does not necessarily get into my physical life, which
15is often harder to control and contain all the noise in our modern 18is often harder to control and contain all the noise in our modern
16world. 19world.
17 20
18While there are new things to do every day in our digital world, I find 21While there are new things to do every day in our digital world, I find
19that keeping a core set of values and interests can ground you and keep 22that keeping a core set of values and interests can ground you and keep
20you mindful of /why/ you are participating in the digital world. For 23you mindful of *why* you are participating in the digital world. For
21example, if - at your core - you have no interest in what strangers 24example, if - at your core - you have no interest in what strangers
22think about random topics, it would be unwise to start participating in 25think about random topics, it would be unwise to start participating in
23social media. However, I am someone who has been dragged in by effective 26social media. However, I am someone who has been dragged in by effective
24advertising to participate in communities that I realize I do not care 27advertising to participate in communities that I realize I do not care
25for. 28for.
26 29
27I won't dive much further into explaining the philosophy of all this, 30I won\'t dive much further into explaining the philosophy of all this,
28but I will link a few helpful articles that may pique your interest if 31but I will link a few helpful articles that may pique your interest if
29you're in search of more meaningful experiences: 32you\'re in search of more meaningful experiences:
33
34- [Mindfulness](https://en.wikipedia.org/wiki/Mindfulness)
35- [Minimalism](https://en.wikipedia.org/wiki/Minimalism)
36- [Stoicism](https://en.wikipedia.org/wiki/Stoicism)
30 37
31- [[https://en.wikipedia.org/wiki/Mindfulness][Mindfulness]] 38# Living Life in the Terminal
32- [[https://en.wikipedia.org/wiki/Minimalism][Minimalism]]
33- [[https://en.wikipedia.org/wiki/Stoicism][Stoicism]]
34 39
35* Living Life in the Terminal
36My personal approach to reducing digital distractions and increasing my 40My personal approach to reducing digital distractions and increasing my
37focus on the task at hand is to use a terminal for as much as I possibly 41focus on the task at hand is to use a terminal for as much as I possibly
38can. 42can.
@@ -40,12 +44,12 @@ can.
40Most days, this means that I have a few tabs open constantly in my 44Most days, this means that I have a few tabs open constantly in my
41terminal: 45terminal:
42 46
431. A web browser 471. A web browser
442. A chat client 482. A chat client
453. An email client 493. An email client
464. An RSS feed reader 504. An RSS feed reader
475. A local shell for navigating my computer's files 515. A local shell for navigating my computer\'s files
486. A remote shell for managing servers and other machines 526. A remote shell for managing servers and other machines
49 53
50Beyond this, I rarely open other tabs or GUI applications, unless 54Beyond this, I rarely open other tabs or GUI applications, unless
51absolutely necessary. If you look, you may be surprised what can be 55absolutely necessary. If you look, you may be surprised what can be
@@ -54,55 +58,56 @@ accomplished in the terminal.
54For example, I have moved my music and entertainment downloads to the 58For example, I have moved my music and entertainment downloads to the
55terminal, along with my device VPN connections. I am exploring options 59terminal, along with my device VPN connections. I am exploring options
56for moving my RSS subscriptions to something like 60for moving my RSS subscriptions to something like
57[[https://newsboat.org/][Newsboat]], so that I can read my daily 61[Newsboat](https://newsboat.org/), so that I can read my daily articles
58articles without all the fuss. 62without all the fuss.
59 63
60Now that we have some examples out of the way, let's dive into the 64Now that we have some examples out of the way, let\'s dive into the
61specifics. 65specifics.
62 66
63** Browsing the Web 67## Browsing the Web
64I'm going to start off with a hard topic for those who prefer to live in 68
65the terminal: web browsing. This task is made hard mostly by websites 69I\'m going to start off with a hard topic for those who prefer to live
70in the terminal: web browsing. This task is made hard mostly by websites
66and web apps that require JavaScript to run. The other difficult part is 71and web apps that require JavaScript to run. The other difficult part is
67that if you're using a text-based browser, that means images won't load 72that if you\'re using a text-based browser, that means images won\'t
68(hopefully that's obvious). 73load (hopefully that\'s obvious).
69 74
70I am using [[https://lynx.invisible-island.net][Lynx]], a text-based 75I am using [Lynx](https://lynx.invisible-island.net), a text-based
71browser that runs quickly and easily in the terminal. Lynx allows me to 76browser that runs quickly and easily in the terminal. Lynx allows me to
72browser most websites by simply typing =g= and then typing in the URL I 77browser most websites by simply typing `g` and then typing in
73want. 78the URL I want.
74 79
75#+caption: Lynx 80![Lynx](https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png)
76[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png]]
77 81
78If you need a search engine while in Lynx, I recommend 82If you need a search engine while in Lynx, I recommend [DuckDuckGo
79[[https://lite.duckduckgo.com/lite/][DuckDuckGo (Lite)]], which allows 83(Lite)](https://lite.duckduckgo.com/lite/), which allows you to search
80you to search the web using their text-only interface. 84the web using their text-only interface.
81 85
82#+caption: DuckDuckGo !Lite 86![DuckDuckGo
83[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png]] 87!Lite](https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png)
84 88
85Eventually, you will run into websites that don't work (or are just too 89Eventually, you will run into websites that don\'t work (or are just too
86ugly and messy) in a text-only mode, and you'll be forced to switch over 90ugly and messy) in a text-only mode, and you\'ll be forced to switch
87to a GUI browser to look at that site. Personally, I don't mind this as 91over to a GUI browser to look at that site. Personally, I don\'t mind
88it doesn't happen as often as I thought it would. 92this as it doesn\'t happen as often as I thought it would.
89 93
90The only time I need to do this is when I want to browse an 94The only time I need to do this is when I want to browse an
91image/video-focused webpage or if I need to log in to a site, and it 95image/video-focused webpage or if I need to log in to a site, and it
92doesn't support a text-only login page. For example, I am able to easily 96doesn\'t support a text-only login page. For example, I am able to
93log in to [[https://sr.ht][Sourcehut]] in lynx. 97easily log in to [Sourcehut](https://sr.ht) in lynx.
98
99## Chatting with Friends
94 100
95** Chatting with Friends
96After web browsing activities, my main form of terminal communication is 101After web browsing activities, my main form of terminal communication is
97Matrix. I use the [[https://docs.mau.fi/gomuks/][gomuks]] client 102Matrix. I use the [gomuks](https://docs.mau.fi/gomuks/) client
98currently. 103currently.
99 104
100This was incredibly easy to install on macOS (but I will need to see if 105This was incredibly easy to install on macOS (but I will need to see if
101it'll be just as easy on Linux when my new laptop arrives): 106it\'ll be just as easy on Linux when my new laptop arrives):
102 107
103#+begin_src sh 108```sh
104brew install gomuks 109brew install gomuks
105#+end_src 110```
106 111
107Once you launch gomuks, it will sync and require your username and 112Once you launch gomuks, it will sync and require your username and
108password to login. After doing so, the only problem I ran into was 113password to login. After doing so, the only problem I ran into was
@@ -110,102 +115,102 @@ verifying my gomuks client so that I could participate in rooms with
110E2EE. 115E2EE.
111 116
112Finally, I was able to verify the session by opening the Element desktop 117Finally, I was able to verify the session by opening the Element desktop
113app (I assume you can do this in the browser and mobile app too, but I'm 118app (I assume you can do this in the browser and mobile app too, but
114not sure) and manually verifying myself with this process: 119I\'m not sure) and manually verifying myself with this process:
115 120
1161. Open the Element desktop app 1211. Open the Element desktop app
1172. Open a room I was a member of 1222. Open a room I was a member of
1183. Open the =Room Info= pane 1233. Open the `Room Info` pane
1194. Open the =People= menu and search for myself 1244. Open the `People` menu and search for myself
1205. Click on my profile name 1255. Click on my profile name
1216. Click on the session link under the =Security= section and follow the 1266. Click on the session link under the `Security` section
122 prompts to manually verify the session 127 and follow the prompts to manually verify the session
123 128
124Overall, I like gomuks and am able to enjoy all the features I was using 129Overall, I like gomuks and am able to enjoy all the features I was using
125in Element. The only hiccup I have occurred is manually downloading 130in Element. The only hiccup I have occurred is manually downloading
126images to view them, which can be annoying. 131images to view them, which can be annoying.
127 132
128#+caption: gomuks 133![gomuks](https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png)
129[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png]] 134
135## Email
130 136
131** Email
132Moving email to the terminal has been the hardest of the tasks for me. 137Moving email to the terminal has been the hardest of the tasks for me.
133Unlike web browsing, where I can simply decide to not look at a website 138Unlike web browsing, where I can simply decide to not look at a website
134that does not work in the terminal, I cannot simply ignore emails sent 139that does not work in the terminal, I cannot simply ignore emails sent
135to me. 140to me.
136 141
137Personally, I am experimenting with [[https://neomutt.org/][neomutt]] as 142Personally, I am experimenting with [neomutt](https://neomutt.org/) as a
138a potential email client. 143potential email client.
139 144
140However, this requires a *TON** of configuration and tweaking to get 145However, this requires a **TON\*** of configuration and tweaking to get
141right. Even when I was able to set up neomutt, configure my email 146right. Even when I was able to set up neomutt, configure my email
142account, and customize a few personal preferences, a lot of emails still 147account, and customize a few personal preferences, a lot of emails still
143do not display correctly (mostly due to HTML and images). 148do not display correctly (mostly due to HTML and images).
144 149
145I won't get into the details of configuring =neomutt=; I mostly followed 150I won\'t get into the details of configuring `neomutt`; I
146this blog post: 151mostly followed this blog post: [Email in the Terminal: Configuring
147[[https://gideonwolfe.com/posts/workflow/neomutt/intro/][Email in the 152Neomutt](https://gideonwolfe.com/posts/workflow/neomutt/intro/).
148Terminal: Configuring Neomutt]].
149 153
150Finally, I have yet to figure out how to connect my GPG keys to 154Finally, I have yet to figure out how to connect my GPG keys to
151=neomutt=, but that's a problem for another day. 155`neomutt`, but that\'s a problem for another day.
156
157## RSS Feed Reader
152 158
153** RSS Feed Reader 159I have just started using [Newsboat](https://newsboat.org/) to read
154I have just started using [[https://newsboat.org/][Newsboat]] to read
155articles in my terminal and have found quick success with it. 160articles in my terminal and have found quick success with it.
156 161
157I'll show you a quick screenshot first: 162I\'ll show you a quick screenshot first:
158 163
159#+caption: Newsboat 164![Newsboat](https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png)
160[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png]]
161 165
162The configuration was super easy for this app; I simply installed the 166The configuration was super easy for this app; I simply installed the
163app, created a file for URLs, and imported my OPML subscriptions that I 167app, created a file for URLs, and imported my OPML subscriptions that I
164had exported out of my old feed reader: 168had exported out of my old feed reader:
165 169
166#+begin_src sh 170```sh
167brew install newsboat 171brew install newsboat
168#+end_src 172```
169 173
170#+begin_src sh 174```sh
171touch ~/.newsboat/urls 175touch ~/.newsboat/urls
172#+end_src 176```
173 177
174#+begin_src sh 178```sh
175newsboat -i=my_subscriptions.opml 179newsboat -i=my_subscriptions.opml
176#+end_src 180```
181
182## Writing & Programming
177 183
178** Writing & Programming
179Unfortunately, the weak link in my terminal-based environment right now 184Unfortunately, the weak link in my terminal-based environment right now
180is my grasp of the possibilities of editing files within a shell. 185is my grasp of the possibilities of editing files within a shell.
181 186
182I am used to the easy extensions found in VSCodium and Kate, so I am 187I am used to the easy extensions found in VSCodium and Kate, so I am
183slowly learning how to mold the default editing tools to my needs. 188slowly learning how to mold the default editing tools to my needs.
184Currently, this means I am using =nano= with the following 189Currently, this means I am using `nano` with the following
185configuration: 190configuration:
186 191
187#+begin_src config 192``` config
188set breaklonglines 193set breaklonglines
189set autoindent 194set autoindent
190set linenumbers 195set linenumbers
191set tabstospaces 196set tabstospaces
192set tabsize 2 197set tabsize 2
193set fill 80 198set fill 80
194#+end_src 199```
195 200
196This configuration allows nano to automatically hard-wrap lines at 80 201This configuration allows nano to automatically hard-wrap lines at 80
197characters, autoindent the wrapped lines (if the previous line was 202characters, autoindent the wrapped lines (if the previous line was
198indented), use 2 spaces per tab, and display line numbers within each 203indented), use 2 spaces per tab, and display line numbers within each
199file I open. 204file I open.
200 205
201I am currently looking to see if =vim= or =emacs= would be more useful 206I am currently looking to see if `vim` or `emacs`
202for my current needs, but I'm not in any rush, so I don't expect to find 207would be more useful for my current needs, but I\'m not in any rush, so
203an answer anytime soon. 208I don\'t expect to find an answer anytime soon.
204 209
205With my current life demands, I am not programming at the moment and 210With my current life demands, I am not programming at the moment and
206have not explored the best terminal set-up for programming. However, I 211have not explored the best terminal set-up for programming. However, I
207have seen many peers find success configuring =vim= and =emacs=, so 212have seen many peers find success configuring `vim` and
208that's where I will start when I pick my projects back up. 213`emacs`, so that\'s where I will start when I pick my
214projects back up.
209 215
210#+caption: nano 216![nano](https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png)
211[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png]]
content/blog/2022-06-22-daily-poetry.org → content/blog/2022-06-22-daily-poetry.md renamed +86 −73
@@ -1,64 +1,73 @@
1#+title: Daily Plaintext Poetry via Email 1+++
2#+date: 2022-06-22 2date = 2022-06-22
3#+description: A small project to automatically deliver poetry to your inbox daily. 3title = "Daily Plaintext Poetry via Email"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* Source Code 8# Source Code
7I don't want to bury the lede here, so if you'd like to see the full 9
10I don\'t want to bury the lede here, so if you\'d like to see the full
8source code I use to email myself plaintext poems daily, visit the 11source code I use to email myself plaintext poems daily, visit the
9repository: [[https://git.cleberg.net/?p=daily-poem.git;a=summary][daily-poem.git]]. 12repository:
13[daily-poem.git](https://git.cleberg.net/?p=daily-poem.git;a=summary).
14
15# My Daily Dose of Poetry
10 16
11* My Daily Dose of Poetry
12Most of my programming projects are small, random projects that are made 17Most of my programming projects are small, random projects that are made
13strictly to fix some small problem I have or enhance my quality of life. 18strictly to fix some small problem I have or enhance my quality of life.
14 19
15In this case, I was looking for a simply and easy way to get a daily 20In this case, I was looking for a simply and easy way to get a daily
16dose of literature or poetry to read in the mornings. 21dose of literature or poetry to read in the mornings.
17 22
18However, I don't want to sign up for a random mailing list on just any 23However, I don\'t want to sign up for a random mailing list on just any
19website. I also don't want to have to work to find the reading content 24website. I also don\'t want to have to work to find the reading content
20each morning, as I know I would simply give up and stop reading daily. 25each morning, as I know I would simply give up and stop reading daily.
21 26
22Thus, I found a way to deliver poetry to myself in plain-text format, on 27Thus, I found a way to deliver poetry to myself in plain-text format, on
23a daily basis, and scheduled to deliver automatically. 28a daily basis, and scheduled to deliver automatically.
24 29
25* Prerequisites 30# Prerequisites
31
26This solution uses Python and email, so the following process requires 32This solution uses Python and email, so the following process requires
27the following to be installed: 33the following to be installed:
28 34
291. An SMTP server, which can be as easy as installing =mailutils= if 351. An SMTP server, which can be as easy as installing
30 you're on a Debian-based distro. 36 `mailutils` if you\'re on a Debian-based distro.
312. Python (& pip!) 372. Python (& pip!)
323. The following Python packages: =email=, =smtplib=, =json=, and 383. The following Python packages: `email`,
33 =requests= 39 `smtplib`, `json`, and `requests`
40
41# Breaking Down the Logic
34 42
35* Breaking Down the Logic 43I want to break down the logic for this program, as it\'s quite simple
36I want to break down the logic for this program, as it's quite simple
37and informational. 44and informational.
38 45
39** Required Packages 46## Required Packages
47
40This program starts with a simple import of the required packages, so I 48This program starts with a simple import of the required packages, so I
41wanted to explain why each package is used: 49wanted to explain why each package is used:
42 50
43#+begin_src python 51``` python
44from email.mime.text import MIMEText # Required for translating MIMEText 52from email.mime.text import MIMEText # Required for translating MIMEText
45import smtplib # Required to process the SMTP mail delivery 53import smtplib # Required to process the SMTP mail delivery
46import json # Required to parse the poetry API results 54import json # Required to parse the poetry API results
47import requests # Required to send out a request to the API 55import requests # Required to send out a request to the API
48#+end_src 56```
57
58## Sending the API Request
49 59
50** Sending the API Request 60Next, we need to actually send the API request. In my case, I\'m calling
51Next, we need to actually send the API request. In my case, I'm calling
52a random poem from the entire API. If you want, you can call specific 61a random poem from the entire API. If you want, you can call specific
53poems or authors from this API. 62poems or authors from this API.
54 63
55#+begin_src python 64``` python
56json_data = requests.get('https://poetrydb.org/random').json() 65json_data = requests.get('https://poetrydb.org/random').json()
57#+end_src 66```
58 67
59This gives us the following result in JSON: 68This gives us the following result in JSON:
60 69
61#+begin_src json 70``` json
62[ 71[
63 { 72 {
64 "title": "Sonnet XXII: With Fools and Children", 73 "title": "Sonnet XXII: With Fools and Children",
@@ -84,40 +93,40 @@ This gives us the following result in JSON:
84 "linecount": "15" 93 "linecount": "15"
85 } 94 }
86] 95]
87#+end_src 96```
88 97
89** Parsing the API Results 98## Parsing the API Results
90In order to parse this into a readable format, we need to use the =json=
91package and extract the fields we want. In the example below, I am
92grabbing every field presented by the API.
93
94For the actual poem content, we need to loop over each line in the
95=lines= variable since each line is a separate string by default.
96 99
97#+begin_quote 100In order to parse this into a readable format, we need to use the
98You /could/ also extract the title or author and make another call out 101`json` package and extract the fields we want. In the example
99to the API to avoid having to build the plaintext poem with a loop, but 102below, I am grabbing every field presented by the API.
100it just doesn't make sense to me to send multiple requests when we can
101create a simple loop on our local machine to work with the data we
102already have.
103 103
104For 104For the actual poem content, we need to loop over each line in the
105[[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]], 105`lines` variable since each line is a separate string by
106look at the raw data response of this link to see the poem's lines 106default.
107returned in plaintext. 107
108 108> You *could* also extract the title or author and make another call out
109#+end_quote 109> to the API to avoid having to build the plaintext poem with a loop,
110 110> but it just doesn\'t make sense to me to send multiple requests when
111#+begin_src python 111> we can create a simple loop on our local machine to work with the data
112> we already have.
113>
114> For
115> [example](https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text),
116> look at the raw data response of this link to see the poem\'s lines
117> returned in plaintext.
118
119``` python
112title = json_data[0]['title'] 120title = json_data[0]['title']
113author = json_data[0]['author'] 121author = json_data[0]['author']
114line_count = json_data[0]['linecount'] 122line_count = json_data[0]['linecount']
115lines = '' 123lines = ''
116for line in json_data[0]['lines']: 124for line in json_data[0]['lines']:
117 lines = lines + line + "\n" 125 lines = lines + line + "\n"
118#+end_src 126```
127
128## Composing the Email
119 129
120** Composing the Email
121Now that I have all the data I need, I just need to compose it into a 130Now that I have all the data I need, I just need to compose it into a
122message and prepare the message metadata. 131message and prepare the message metadata.
123 132
@@ -126,41 +135,43 @@ by the author, then a blank line, and finally the full poem. This code
126snippet combines that data and packages it into a MIMEText container, 135snippet combines that data and packages it into a MIMEText container,
127ready to be emailed. 136ready to be emailed.
128 137
129#+begin_src python 138``` python
130msg_body = title + "\n" + author + "\n\n" + lines 139msg_body = title + "\n" + author + "\n\n" + lines
131msg = MIMEText(msg_body) 140msg = MIMEText(msg_body)
132#+end_src 141```
133 142
134Before we send the email, we need to prepare the metadata (subject, 143Before we send the email, we need to prepare the metadata (subject,
135from, to, etc.): 144from, to, etc.):
136 145
137#+begin_src python 146``` python
138sender_email = 'example@server.local' 147sender_email = 'example@server.local'
139recipient_emails = ['user@example.com'] 148recipient_emails = ['user@example.com']
140msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)' 149msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
141msg['From'] = sender_email 150msg['From'] = sender_email
142msg['To'] = recipient_email 151msg['To'] = recipient_email
143#+end_src 152```
153
154## Sending the Email
144 155
145** Sending the Email
146Now that I have everything ready to be emailed, the last step is to 156Now that I have everything ready to be emailed, the last step is to
147simply connect to an SMTP server and send the email out to the 157simply connect to an SMTP server and send the email out to the
148recipients. In my case, I installed =mailutils= on Ubuntu and let my 158recipients. In my case, I installed `mailutils` on Ubuntu and
149SMTP server be =localhost=. 159let my SMTP server be `localhost`.
150 160
151#+begin_src python 161``` python
152smtp_server = 'localhost' 162smtp_server = 'localhost'
153s = smtplib.SMTP(smtp_server) 163s = smtplib.SMTP(smtp_server)
154s.sendmail(sender_email, recipient_emails, msg.as_string()) 164s.sendmail(sender_email, recipient_emails, msg.as_string())
155s.quit() 165s.quit()
156#+end_src 166```
167
168# The Result!
157 169
158* The Result! 170Instead of including a screenshot, I\'ve copied the contents of the
159Instead of including a screenshot, I've copied the contents of the email 171email that was delivered to my inbox below since I set this process up
160that was delivered to my inbox below since I set this process up in 172in plaintext format.
161plaintext format.
162 173
163#+begin_src txt 174``` txt
164Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC) 175Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
165From: REDACTED 176From: REDACTED
166To: REDACTED 177To: REDACTED
@@ -186,23 +197,25 @@ You that behold us, laugh us not to scorn;
186Give Nature thanks you are not such as we. 197Give Nature thanks you are not such as we.
187Yet fools and children sometimes tell in play 198Yet fools and children sometimes tell in play
188Some, wise in show, more fools indeed than they. 199Some, wise in show, more fools indeed than they.
189#+end_src 200```
201
202# Scheduling the Daily Email
190 203
191* Scheduling the Daily Email 204Last, but not least, is scheduling this Python script with
192Last, but not least, is scheduling this Python script with =crontab=. To 205`crontab`. To schedule a script to run daily, you can add it
193schedule a script to run daily, you can add it to the =crontab= file. To 206to the `crontab` file. To do this, open `crontab`
194do this, open =crontab= in editing mode: 207in editing mode:
195 208
196#+begin_src sh 209```sh
197crontab -e 210crontab -e
198#+end_src 211```
199 212
200In the file, simply paste the following snippet at the bottom of the 213In the file, simply paste the following snippet at the bottom of the
201file and ensure that the file path is correctly pointing to wherever you 214file and ensure that the file path is correctly pointing to wherever you
202saved your Python script: 215saved your Python script:
203 216
204#+begin_src config 217``` config
2050 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py 2180 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py
206#+end_src 219```
207 220
208We have now set up the script and scheduled it to run daily at 08:00! 221We have now set up the script and scheduled it to run daily at 08:00!
content/blog/2022-06-24-fedora-i3.md added +159
@@ -0,0 +1,159 @@
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
11on migrating from macOS back to a Linux-based OS. I am happy to say that
12I have finally completed my migration and am now stuck in the wonderful
13world of Linux again.
14
15My decision to leave macOS really came down to just a few important
16things:
17
18- Apple Security (Gatekeeper) restricting me from running any software
19 I want. Even if you disable Gatekeeper and allow software to bypass
20 the rest of the device installation security, you still have to
21 repeat that process every time the allowed software is updated.
22- macOS sends out nearly constant connections, pings, telemetry, etc.
23 to a myriad of mysterious Apple services. I\'m not even going to
24 dive into how many macOS apps have constant telemetry on, as well.
25- Lastly, I just *really* missed the customization and freedom that
26 comes with Linux. Being able to switch to entirely new kernel, OS,
27 or desktop within minutes is a freedom I took for granted when I
28 switched to macOS.
29
30Now that I\'ve covered macOS, I\'m going to move on to more exciting
31topics: my personal choice of OS, DE, and various customizations I\'m
32using.
33
34# Fedora
35
36After trying a ton of distros (I think I booted and tested around 20-25
37distros), I finally landed on [Fedora Linux](https://getfedora.org/). I
38have quite a bit of experience with Fedora and enjoy the
39`dnf` package manager. Fedora allows me to keep up-to-date
40with recent software (I\'m looking at you, Debian), but still provides a
41level of stability you don\'t find in every distro.
42
43In a very close second place was Arch Linux, as well as its spin-off:
44Garuda Linux (Garuda w/ sway is *beautiful*). Arch is great for
45compatibility and the massive community it has, but I have just never
46had the time to properly sit down and learn the methodology behind their
47packaging systems.
48
49Basically, everything else I tested was unacceptable in at least one way
50or another. Void (`glibc`) was great, but doesn\'t support
51all the software I need. Slackware worked well as a tui, but I wasn\'t
52skilled enough to get a tiling window manager (WM) working on it.
53
54## i3
55
56One of the reasons I settled on Fedora is that it comes with an official
57i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
58the biggest things I wanted to do as soon as I adopted Linux again.
59
60I will probably set up a dotfile repository soon, so that I don\'t lose
61any of my configurations, but nothing big has been configured thus far.
62
63The two main things I have updated in i3wm are natural scrolling and
64binding my brightness keys to the `brightnessctl` program.
65
661. Natural Scrolling
67
68 You can enable natural scrolling by opening the following file:
69
70 ```sh
71 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
72 ```
73
74 Within the `40-libinput.conf` file, find the following
75 input sections and enable the natural scrolling option.
76
77 This is the `pointer` section:
78
79 ``` conf
80 Section "InputClass"
81 Identifier "libinput pointer catchall"
82 MatchIsPointer "on"
83 MatchDevicePath "/dev/input/event*"
84 Driver "libinput"
85 Option "NaturalScrolling" "True"
86 EndSection
87 ```
88
89 This is the `touchpad` section:
90
91 ``` conf
92 Section "InputClass"
93 Identifier "libinput touchpad catchall"
94 MatchIsTouchpad "on"
95 MatchDevicePath "/dev/input/event*"
96 Driver "libinput"
97 Option "NaturalScrolling" "True"
98 EndSection
99 ```
100
1012. Enabling Brightness Keys
102
103 Likewise, enabling brightness key functionality is as simple as
104 binding the keys to the `brightnessctl` program.
105
106 To do this, open up your i3 config file. Mine is located here:
107
108 ```sh
109 nano /home/<my-user>/.config/i3/config
110 ```
111
112 ``` conf
113 # Use brightnessctl to adjust brightness.
114 bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
115 bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
116 ```
117
1183. `polybar`
119
120 Instead of using the default `i3status` bar, I have opted
121 to use `polybar` instead (as you can also see in the
122 screenshot above).
123
124 My config for this menu bar is basically just the default settings
125 with modified colors and an added battery block to quickly show me
126 the machine\'s battery info.
127
1284. `alacritty`
129
130 Not much to say on this part yet, as I haven\'t configured it much,
131 but I installed `alacritty` as my default terminal, and I
132 am using `zsh` and the shell.
133
134# Software Choices
135
136Again, I\'m not going to say much that I haven\'t said yet in other blog
137posts, so I\'ll just do a quick rundown of the apps I installed
138immediately after I set up the environment.
139
140Flatpak Apps:
141
142- Cryptomator
143- pCloud
144- Signal
145
146Fedora Packages:
147
148- gomuks
149- neomutt
150- neofetch
151- Firefox
152 - uBlock Origin
153 - Bitwarden
154 - Stylus
155 - Privacy Redirect
156
157Other:
158
159- exiftool
content/blog/2022-06-24-fedora-i3.org deleted −152
@@ -1,152 +0,0 @@
1#+title: Rebooting My Love Affair with Linux
2#+date: 2022-06-24
3#+description: A retrospective on moving from macOS to Linux.
4#+filetags: :linux:
5
6* Leaving macOS
7As I noted [[../foss-macos-apps][in a recent post]], I have been
8planning on migrating from macOS back to a Linux-based OS. I am happy to
9say that I have finally completed my migration and am now stuck in the
10wonderful world of Linux again.
11
12My decision to leave macOS really came down to just a few important
13things:
14
15- Apple Security (Gatekeeper) restricting me from running any software I
16 want. Even if you disable Gatekeeper and allow software to bypass the
17 rest of the device installation security, you still have to repeat
18 that process every time the allowed software is updated.
19- macOS sends out nearly constant connections, pings, telemetry, etc. to
20 a myriad of mysterious Apple services. I'm not even going to dive into
21 how many macOS apps have constant telemetry on, as well.
22- Lastly, I just /really/ missed the customization and freedom that
23 comes with Linux. Being able to switch to entirely new kernel, OS, or
24 desktop within minutes is a freedom I took for granted when I switched
25 to macOS.
26
27Now that I've covered macOS, I'm going to move on to more exciting
28topics: my personal choice of OS, DE, and various customizations I'm
29using.
30
31* Fedora
32After trying a ton of distros (I think I booted and tested around 20-25
33distros), I finally landed on [[https://getfedora.org/][Fedora Linux]].
34I have quite a bit of experience with Fedora and enjoy the =dnf= package
35manager. Fedora allows me to keep up-to-date with recent software (I'm
36looking at you, Debian), but still provides a level of stability you
37don't find in every distro.
38
39In a very close second place was Arch Linux, as well as its spin-off:
40Garuda Linux (Garuda w/ sway is /beautiful/). Arch is great for
41compatibility and the massive community it has, but I have just never
42had the time to properly sit down and learn the methodology behind their
43packaging systems.
44
45Basically, everything else I tested was unacceptable in at least one way
46or another. Void (=glibc=) was great, but doesn't support all the
47software I need. Slackware worked well as a tui, but I wasn't skilled
48enough to get a tiling window manager (WM) working on it.
49
50** i3
51One of the reasons I settled on Fedora is that it comes with an official
52i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
53the biggest things I wanted to do as soon as I adopted Linux again.
54
55I will probably set up a dotfile repository soon, so that I don't lose
56any of my configurations, but nothing big has been configured thus far.
57
58The two main things I have updated in i3wm are natural scrolling and
59binding my brightness keys to the =brightnessctl= program.
60
611. Natural Scrolling
62
63 You can enable natural scrolling by opening the following file:
64
65 #+begin_src sh
66 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
67 #+end_src
68
69 Within the =40-libinput.conf= file, find the following input sections
70 and enable the natural scrolling option.
71
72 This is the =pointer= section:
73
74 #+begin_src conf
75 Section "InputClass"
76 Identifier "libinput pointer catchall"
77 MatchIsPointer "on"
78 MatchDevicePath "/dev/input/event*"
79 Driver "libinput"
80 Option "NaturalScrolling" "True"
81 EndSection
82 #+end_src
83
84 This is the =touchpad= section:
85
86 #+begin_src conf
87 Section "InputClass"
88 Identifier "libinput touchpad catchall"
89 MatchIsTouchpad "on"
90 MatchDevicePath "/dev/input/event*"
91 Driver "libinput"
92 Option "NaturalScrolling" "True"
93 EndSection
94 #+end_src
95
962. Enabling Brightness Keys
97
98 Likewise, enabling brightness key functionality is as simple as
99 binding the keys to the =brightnessctl= program.
100
101 To do this, open up your i3 config file. Mine is located here:
102
103 #+begin_src sh
104 nano /home/<my-user>/.config/i3/config
105 #+end_src
106
107 #+begin_src conf
108 # Use brightnessctl to adjust brightness.
109 bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
110 bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
111 #+end_src
112
1133. =polybar=
114
115 Instead of using the default =i3status= bar, I have opted to use
116 =polybar= instead (as you can also see in the screenshot above).
117
118 My config for this menu bar is basically just the default settings
119 with modified colors and an added battery block to quickly show me
120 the machine's battery info.
121
1224. =alacritty=
123
124 Not much to say on this part yet, as I haven't configured it much,
125 but I installed =alacritty= as my default terminal, and I am using
126 =zsh= and the shell.
127
128* Software Choices
129Again, I'm not going to say much that I haven't said yet in other blog
130posts, so I'll just do a quick rundown of the apps I installed
131immediately after I set up the environment.
132
133Flatpak Apps:
134
135- Cryptomator
136- pCloud
137- Signal
138
139Fedora Packages:
140
141- gomuks
142- neomutt
143- neofetch
144- Firefox
145 - uBlock Origin
146 - Bitwarden
147 - Stylus
148 - Privacy Redirect
149
150Other:
151
152- exiftool
content/blog/2022-07-01-git-server.md added +655
@@ -0,0 +1,655 @@
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
11fallen short when I tried to find a suitable web interface to show on
12the front-end.
13
14After a few years, I have finally found a combination of methods that
15allow me to easily self-host my projects, view them on the web, and
16access them from anywhere.
17
18Before I dive into the details, I want to state a high-level summary of
19my self-hosted Git approach:
20
21- This method uses the `ssh://` (read & write) and
22 `git://` (read-only) protocols for push and pull access.
23 - For the `git://` protocol, I create a
24 `git-daemon-export-ok` file in any repository that I
25 want to be cloneable by anyone.
26 - The web interface I am using (`cgit`) allows simple
27 HTTP cloning by default. I do not disable this setting as I want
28 beginners to be able to clone one of my repositories even if
29 they don\'t know the proper method.
30- I am not enabling Smart HTTPS for any repositories. Updates to
31 repositories must be pushed via SSH.
32- Beyond the actual repository management, I am using
33 `cgit` for the front-end web interface.
34 - If you use the `scan-path=<path>` configuration in
35 the `cgitrc` configuration file to automatically find
36 repositories, you can\'t exclude a repository from
37 `cgit` if it\'s stored within the path that
38 `cgit` reads. To host private repositories, you\'d
39 need to set up another directory that `cgit` can\'t
40 read.
41
42# Assumptions
43
44For the purposes of this walkthrough, I am assuming you have a URL
45(`git.example.com`) or IP address
46(`207.84.26.991`) addressed to the server that you will be
47using to host your git repositories.
48
49# Adding a Git User
50
51In order to use the SSH method associated with git, we will need to add
52a user named `git`. If you have used the SSH method for other
53git hosting sites, you are probably used to the following syntax:
54
55```sh
56git clone [user@]server:project.git
57```
58
59The syntax above is an `scp`-like syntax for using SSH on the
60`git` user on the server to access your repository.
61
62Let\'s delete any remnants of an old `git` user, if any, and
63create the new user account:
64
65```sh
66sudo deluser --remove-home git
67sudo adduser git
68```
69
70## Import Your SSH Keys to the Git User
71
72Once the `git` user is created, you will need to copy your
73public SSH key on your local development machine to the `git`
74user on the server.
75
76If you don\'t have an SSH key yet, create one with this command:
77
78```sh
79ssh-keygen
80```
81
82Once you create the key pair, the public should be saved to
83`~/.ssh/id_rsa.pub`.
84
85If your server still has password-based authentication available, you
86can copy it over to your user\'s home directory like this:
87
88```sh
89ssh-copy-id git@server
90```
91
92Otherwise, copy it over to any user that you can access.
93
94```sh
95scp ~/.ssh/id_rsa.pub your_user@your_server:
96```
97
98Once on the server, you will need to copy the contents into the
99`git` user\'s `authorized_keys` file:
100
101```sh
102cat id_rsa.pub > /home/git/.ssh/authorized_keys
103```
104
105## (Optional) Disable Password-Based SSH
106
107If you want to lock down your server and ensure that no one can
108authenticate in via SSH with a password, you will need to edit your SSH
109configuration.
110
111```sh
112sudo nano /etc/ssh/sshd_config
113```
114
115Within this file, find the following settings and set them to the values
116I am showing below:
117
118``` conf
119PermitRootLogin no
120PasswordAuthentication no
121AuthenticationMethods publickey
122```
123
124You may have other Authentication Methods required in your personal
125set-up, so the key here is just to ensure that
126`AuthenticationMethods` does not allow passwords.
127
128### Setting up the Base Directory
129
130Now that we have set up a `git` user to handle all transport
131methods, we need to set up the directory that we will be using as our
132base of all repositories.
133
134In my case, I am using `/git` as my source folder. To create
135this folder and assign it to the user we created, execute the following
136commands:
137
138```sh
139sudo mkdir /git
140sudo chown -R git:git /git
141```
142
143### Creating a Test Repository
144
145On your server, switch over to the `git` user in order to
146start managing git files.
147
148```sh
149su git
150```
151
152Once logged-in as the `git` user, go to your base directory
153and create a test repository.
154
155```sh
156cd /git
157mkdir test.git && cd test.git
158git init --bare
159```
160
161If you want to make this repo viewable/cloneable to the public via the
162`git://` protocol, you need to create a
163`git-daemon-export-ok` file inside the repository.
164
165```sh
166touch git-daemon-export-ok
167```
168
169# Change the Login Shell for `git`
170
171To make sure that the `git` user is only used for git
172operations and nothing else, you need to change the user\'s login shell.
173To do this, simply use the `chsh` command:
174
175```sh
176sudo chsh git
177```
178
179The interactive prompt will ask which shell you want the
180`git` user to use. You must use the following value:
181
182```sh
183/usr/bin/git-shell
184```
185
186Once done, no one will be able to SSH to the `git` user or
187execute commands other than the standard git commands.
188
189# Opening the Firewall
190
191Don\'t forget to open up ports on the device firewall and network
192firewall if you want to access these repositories publicly. If you\'re
193using default ports, forward ports `22` (ssh) and
194`9418` (git) from your router to your server\'s IP address.
195
196If your server also has a firewall, ensure that the firewall allows the
197same ports that are forwarded from the router. For example, if you use
198`ufw`:
199
200```sh
201sudo ufw allow 22
202sudo ufw allow 9418
203```
204
205## Non-Standard SSH Ports
206
207If you use a non-standard port for SSH, such as `9876`, you
208will need to create an SSH configuration file on your local development
209machine in order to connect to your server\'s git repositories.
210
211To do this, you\'ll need to define your custom port on your client
212machine in your `~/.ssh/config` file:
213
214```sh
215nano ~/.ssh/config
216```
217
218``` conf
219Host git.example.com
220 # HostName can be a URL or an IP address
221 HostName git.example.com
222 Port 9876
223 User git
224```
225
226## Testing SSH
227
228There are two main syntaxes you can use to manage git over SSH:
229
230- `git clone [user@]server:project.git`
231- `git clone ssh://[user@]server/project.git`
232
233I prefer the first, which is an `scp`-like syntax. To test
234it, try to clone the test repository you set up on the server:
235
236```sh
237git clone git@git.example.com:/git/test.git
238```
239
240# Enabling Read-Only Access
241
242If you want people to be able to clone any repository where you\'ve
243placed a `git-daemon-export-ok` file, you will need to start
244the git daemon.
245
246To do this on a system with `systemd`, create a service file:
247
248```sh
249sudo nano /etc/systemd/system/git-daemon.service
250```
251
252Inside the `git-daemon.service` file, paste the following:
253
254``` conf
255[Unit]
256Description=Start Git Daemon
257
258[Service]
259ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
260
261Restart=always
262RestartSec=500ms
263
264StandardOutput=syslog
265StandardError=syslog
266SyslogIdentifier=git-daemon
267
268User=git
269Group=git
270
271[Install]
272WantedBy=multi-user.target
273```
274
275Once created, enable and start the service:
276
277```sh
278sudo systemctl enable git-daemon.service
279sudo systemctl start git-daemon.service
280```
281
282To clone read-only via the `git://` protocol, you can use the
283following syntax:
284
285```sh
286git clone git://git.example.com/test.git
287```
288
289# Migrating Repositories
290
291At this point, we have a working git server that works with both SSH and
292read-only access.
293
294For each of the repositories I had hosted a different provider, I
295executed the following commands in order to place a copy on my server as
296my new source of truth:
297
298Server:
299
300```sh
301su git
302mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
303git init --bare
304
305# If you want to make this repo viewable/cloneable to the public
306touch git-daemon-export-ok
307```
308
309Client:
310
311```sh
312git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
313git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
314git push
315```
316
317# Optional Web View: `cgit`
318
319If you want a web viewer for your repositories, you can use various
320tools, such as `gitweb`, `cgit`, or
321`klaus`. I chose `cgit` due to its simple
322interface and fairly easy set-up (compared to others). Not to mention
323that the [Linux kernel uses `cgit`](https://git.kernel.org/).
324
325## Docker Compose
326
327Instead of using my previous method of using a `docker run`
328command, I\'ve updated this section to use `docker-compose`
329instead for an easier installation and simpler management and
330configuration.
331
332In order to use Docker Compose, you will set up a
333`docker-compose.yml` file to automatically connect resources
334like the repositories, `cgitrc`, and various files or folders
335to the `cgit` container you\'re creating:
336
337```sh
338mkdir ~/cgit && cd ~/cgit
339nano docker-compose.yml
340```
341
342``` conf
343# docker-compose.yml
344version: '3'
345
346services:
347 cgit:
348 image: invokr/cgit
349 volumes:
350 - /git:/git
351 - ./cgitrc:/etc/cgitrc
352 - ./logo.png:/var/www/htdocs/cgit/logo.png
353 - ./favicon.png:/var/www/htdocs/cgit/favicon.png
354 - ./filters:/var/www/htdocs/cgit/filters
355 ports:
356 - "8763:80"
357 restart: always
358```
359
360Then, just start the container:
361
362```sh
363sudo docker-compose up -d
364```
365
366Once it\'s finished installing, you can access the site at
367`<SERVER_IP>:8763` or use a reverse-proxy service to forward
368`cgit` to a URL, such as `git.example.com`. See
369the next section for more details on reverse proxying a URL to a local
370port.
371
372## Nginx Reverse Proxy
373
374I am using Nginx as my reverse proxy so that the `cgit`
375Docker container can use `git.example.com` as its URL. To do
376so, I simply created the following configuration file:
377
378```sh
379sudo nano /etc/nginx/sites-available/git.example.com
380```
381
382``` conf
383server {
384 listen 80;
385 server_name git.example.com;
386
387 if ($host = git.example.com) {
388 return 301 https://$host$request_uri;
389 }
390
391 return 404;
392}
393
394server {
395 server_name git.example.com;
396 listen 443 ssl http2;
397
398 location / {
399 # The final `/` is important.
400 proxy_pass http://localhost:8763/;
401 add_header X-Frame-Options SAMEORIGIN;
402 add_header X-XSS-Protection "1; mode=block";
403 proxy_redirect off;
404 proxy_buffering off;
405 proxy_set_header Host $host;
406 proxy_set_header X-Real-IP $remote_addr;
407 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
408 proxy_set_header X-Forwarded-Proto $scheme;
409 proxy_set_header X-Forwarded-Port $server_port;
410 }
411
412 # INCLUDE ANY SSL CERTS HERE
413 include /etc/letsencrypt/options-ssl-nginx.conf;
414 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
415}
416```
417
418Once created, symlink it and restart the web server.
419
420```sh
421sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
422sudo systemctl restart nginx.service
423```
424
425As we can see below, my site at `git.example.com` is
426available and running:
427
428## Settings Up Git Details
429
430Once you have `cgit` running, you can add some small details,
431such as repository owners and descriptions by editing the following
432files within each repository.
433
434Alternatively, you can use the `cgitrc` file to edit these
435details if you only care to edit them for the purpose of seeing them on
436your website.
437
438The `description` file within the repository on your server
439will display the description online.
440
441```sh
442cd /git/example.git
443nano description
444```
445
446You can add a `[gitweb]` block to the `config`
447file in order to display the owner of the repository.
448
449```sh
450cd /git/example.git
451nano config
452```
453
454``` conf
455[gitweb]
456 owner = "YourName"
457```
458
459Note that you can ignore the configuration within each repository and
460simply set up this information in the `cgitrc` file, if you
461want to do it that way.
462
463## Editing `cgit`
464
465In order to edit certain items within `cgit`, you need to
466edit the `cgitrc` file.
467
468```sh
469nano ~/cgit/cgitrc
470```
471
472Below is an example configuration for `cgitrc`. You can find
473all the configuration options within the \[configuration manual\]
474(<https://git.zx2c4.com/cgit/plain/cgitrc.5.txt>).
475
476``` conf
477css=/cgit.css
478logo=/logo.png
479favicon=/favicon.png
480robots=noindex, nofollow
481
482enable-index-links=1
483enable-commit-graph=1
484enable-blame=1
485enable-log-filecount=1
486enable-log-linecount=1
487enable-git-config=1
488
489clone-url=git://git.example.com/$CGIT_REPO_URL ssh://git@git.example.com:/git/$CGIT_REPO_URL
490
491root-title=My Git Website
492root-desc=My personal git repositories.
493
494# Allow download of tar.gz, tar.bz2 and zip-files
495snapshots=tar.gz tar.bz2 zip
496
497##
498## List of common mimetypes
499##
500mimetype.gif=image/gif
501mimetype.html=text/html
502mimetype.jpg=image/jpeg
503mimetype.jpeg=image/jpeg
504mimetype.pdf=application/pdf
505mimetype.png=image/png
506mimetype.svg=image/svg+xml
507
508# Highlight source code
509# source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.sh
510source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
511
512# Format markdown, restructuredtext, manpages, text files, and html files
513# through the right converters
514about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
515
516##
517## Search for these files in the root of the default branch of repositories
518## for coming up with the about page:
519##
520readme=:README.md
521readme=:readme.md
522readme=:README.mkd
523readme=:readme.mkd
524readme=:README.rst
525readme=:readme.rst
526readme=:README.html
527readme=:readme.html
528readme=:README.htm
529readme=:readme.htm
530readme=:README.txt
531readme=:readme.txt
532readme=:README
533readme=:readme
534
535# Repositories
536
537# Uncomment the following line to scan a path instead of adding repositories manually
538# scan-path=/git
539
540## Test Section
541section=git/test-section
542
543repo.url=test.git
544repo.path=/git/test.git
545repo.readme=:README.md
546repo.owner=John Doe
547repo.desc=An example repository!
548```
549
550## Final Fixes: Syntax Highlighting & README Rendering
551
552After completing my initial install and playing around with it for a few
553days, I noticed two issues:
554
5551. Syntax highlighting did not work when viewing the source code within
556 a file.
5572. The `about` tab within a repository was not rendered to
558 HTML.
559
560The following process fixes these issues. To start, let\'s go to the
561`cgit` directory where we were editing our configuration file
562earlier.
563
564```sh
565cd ~/cgit
566```
567
568In here, create two folders that will hold our syntax files:
569
570```sh
571mkdir filters && mkdir filters/html-converters && cd filters
572```
573
574Next, download the default filters:
575
576```sh
577curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
578chmod 755 about-formatting.sh
579curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
580chmod 755 syntax-highlighting.py
581```
582
583Finally, download the HTML conversion files you need. The example below
584downloads the Markdown converter:
585
586```sh
587cd html-converters
588curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
589chmod 755 md2html
590```
591
592If you need other filters or html-converters found within [the cgit
593project files](https://git.zx2c4.com/cgit/tree/filters), repeat the
594`curl` and `chmod` process above for whichever
595files you need.
596
597However, formatting will not work quite yet since the Docker cgit
598container we\'re using doesn\'t have the formatting package installed.
599You can install this easily by install Python 3+ and the
600`pygments` package:
601
602```sh
603# Enter the container's command line
604sudo docker exec -it cgit bash
605```
606
607```sh
608# Install the necessary packages and then exit
609yum update -y && \
610yum upgrade -y && \
611yum install python3 python3-pip -y && \
612pip3 install markdown pygments && \
613exit
614```
615
616**You will need to enter the cgit docker container and re-run these
617`yum` commands every time you kill and restart the
618container!**
619
620If not done already, we need to add the following variables to our
621`cgitrc` file in order for `cgit` to know where
622our filtering files are:
623
624``` conf
625# Highlight source code with python pygments-based highlighter
626source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
627
628# Format markdown, restructuredtext, manpages, text files, and html files
629# through the right converters
630about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
631```
632
633Now you should see that syntax highlighting and README rendering to the
634`about` tab is fixed.
635
636## Theming
637
638I won\'t go into much detail in this section, but you can fully theme
639your installation of `cgit` since you have access to the
640`cgit.css` file in your web root. This is another file you
641can add as a volume to the `docker-compose.yml` file if you
642want to edit this without entering the container\'s command line.
643
644### :warning: Remember to Back Up Your Data!
645
646The last thing to note is that running services on your own equipment
647means that you\'re assuming a level of risk that exists regarding data
648loss, catastrophes, etc. In order to reduce the impact of any such
649occurrence, I suggest backing up your data regularly.
650
651Backups can be automated via `cron`, by hooking your base
652directory up to a cloud provider, or even setting up hooks to push all
653repository info to git mirrors on other git hosts. Whatever the method,
654make sure that your data doesn\'t vanish in the event that your drives
655or servers fail.
content/blog/2022-07-01-git-server.org deleted −617
@@ -1,617 +0,0 @@
1#+title: Self-Hosting a Personal Git Server
2#+date: 2022-07-01
3#+description: A guide to self-hosting a Git server on your own server.
4#+filetags: :selfhosting:
5
6* My Approach to Self-Hosting Git
7I have often tried to self-host my Git repositories, but have always
8fallen short when I tried to find a suitable web interface to show on
9the front-end.
10
11After a few years, I have finally found a combination of methods that
12allow me to easily self-host my projects, view them on the web, and
13access them from anywhere.
14
15Before I dive into the details, I want to state a high-level summary of
16my self-hosted Git approach:
17
18- This method uses the =ssh://= (read & write) and =git://= (read-only)
19 protocols for push and pull access.
20 - For the =git://= protocol, I create a =git-daemon-export-ok= file in
21 any repository that I want to be cloneable by anyone.
22 - The web interface I am using (=cgit=) allows simple HTTP cloning by
23 default. I do not disable this setting as I want beginners to be
24 able to clone one of my repositories even if they don't know the
25 proper method.
26- I am not enabling Smart HTTPS for any repositories. Updates to
27 repositories must be pushed via SSH.
28- Beyond the actual repository management, I am using =cgit= for the
29 front-end web interface.
30 - If you use the =scan-path=<path>= configuration in the =cgitrc=
31 configuration file to automatically find repositories, you can't
32 exclude a repository from =cgit= if it's stored within the path that
33 =cgit= reads. To host private repositories, you'd need to set up
34 another directory that =cgit= can't read.
35
36* Assumptions
37For the purposes of this walkthrough, I am assuming you have a URL
38(=git.example.com=) or IP address (=207.84.26.991=) addressed to the
39server that you will be using to host your git repositories.
40
41* Adding a Git User
42In order to use the SSH method associated with git, we will need to add
43a user named =git=. If you have used the SSH method for other git
44hosting sites, you are probably used to the following syntax:
45
46#+begin_src sh
47git clone [user@]server:project.git
48#+end_src
49
50The syntax above is an =scp=-like syntax for using SSH on the =git= user
51on the server to access your repository.
52
53Let's delete any remnants of an old =git= user, if any, and create the
54new user account:
55
56#+begin_src sh
57sudo deluser --remove-home git
58sudo adduser git
59#+end_src
60
61** Import Your SSH Keys to the Git User
62Once the =git= user is created, you will need to copy your public SSH
63key on your local development machine to the =git= user on the server.
64
65If you don't have an SSH key yet, create one with this command:
66
67#+begin_src sh
68ssh-keygen
69#+end_src
70
71Once you create the key pair, the public should be saved to
72=~/.ssh/id_rsa.pub=.
73
74If your server still has password-based authentication available, you
75can copy it over to your user's home directory like this:
76
77#+begin_src sh
78ssh-copy-id git@server
79#+end_src
80
81Otherwise, copy it over to any user that you can access.
82
83#+begin_src sh
84scp ~/.ssh/id_rsa.pub your_user@your_server:
85#+end_src
86
87Once on the server, you will need to copy the contents into the =git=
88user's =authorized_keys= file:
89
90#+begin_src sh
91cat id_rsa.pub > /home/git/.ssh/authorized_keys
92#+end_src
93
94** (Optional) Disable Password-Based SSH
95If you want to lock down your server and ensure that no one can
96authenticate in via SSH with a password, you will need to edit your SSH
97configuration.
98
99#+begin_src sh
100sudo nano /etc/ssh/sshd_config
101#+end_src
102
103Within this file, find the following settings and set them to the values
104I am showing below:
105
106#+begin_src conf
107PermitRootLogin no
108PasswordAuthentication no
109AuthenticationMethods publickey
110#+end_src
111
112You may have other Authentication Methods required in your personal
113set-up, so the key here is just to ensure that =AuthenticationMethods=
114does not allow passwords.
115
116*** Setting up the Base Directory
117Now that we have set up a =git= user to handle all transport methods, we
118need to set up the directory that we will be using as our base of all
119repositories.
120
121In my case, I am using =/git= as my source folder. To create this folder
122and assign it to the user we created, execute the following commands:
123
124#+begin_src sh
125sudo mkdir /git
126sudo chown -R git:git /git
127#+end_src
128
129*** Creating a Test Repository
130On your server, switch over to the =git= user in order to start managing
131git files.
132
133#+begin_src sh
134su git
135#+end_src
136
137Once logged-in as the =git= user, go to your base directory and create a
138test repository.
139
140#+begin_src sh
141cd /git
142mkdir test.git && cd test.git
143git init --bare
144#+end_src
145
146If you want to make this repo viewable/cloneable to the public via the
147=git://= protocol, you need to create a =git-daemon-export-ok= file
148inside the repository.
149
150#+begin_src sh
151touch git-daemon-export-ok
152#+end_src
153
154* Change the Login Shell for =git=
155To make sure that the =git= user is only used for git operations and
156nothing else, you need to change the user's login shell. To do this,
157simply use the =chsh= command:
158
159#+begin_src sh
160sudo chsh git
161#+end_src
162
163The interactive prompt will ask which shell you want the =git= user to
164use. You must use the following value:
165
166#+begin_src sh
167/usr/bin/git-shell
168#+end_src
169
170Once done, no one will be able to SSH to the =git= user or execute
171commands other than the standard git commands.
172
173* Opening the Firewall
174Don't forget to open up ports on the device firewall and network
175firewall if you want to access these repositories publicly. If you're
176using default ports, forward ports =22= (ssh) and =9418= (git) from your
177router to your server's IP address.
178
179If your server also has a firewall, ensure that the firewall allows the
180same ports that are forwarded from the router. For example, if you use
181=ufw=:
182
183#+begin_src sh
184sudo ufw allow 22
185sudo ufw allow 9418
186#+end_src
187
188** Non-Standard SSH Ports
189If you use a non-standard port for SSH, such as =9876=, you will need to
190create an SSH configuration file on your local development machine in
191order to connect to your server's git repositories.
192
193To do this, you'll need to define your custom port on your client
194machine in your =~/.ssh/config= file:
195
196#+begin_src sh
197nano ~/.ssh/config
198#+end_src
199
200#+begin_src conf
201Host git.example.com
202 # HostName can be a URL or an IP address
203 HostName git.example.com
204 Port 9876
205 User git
206#+end_src
207
208** Testing SSH
209There are two main syntaxes you can use to manage git over SSH:
210
211- =git clone [user@]server:project.git=
212- =git clone ssh://[user@]server/project.git=
213
214I prefer the first, which is an =scp=-like syntax. To test it, try to
215clone the test repository you set up on the server:
216
217#+begin_src sh
218git clone git@git.example.com:/git/test.git
219#+end_src
220
221* Enabling Read-Only Access
222If you want people to be able to clone any repository where you've
223placed a =git-daemon-export-ok= file, you will need to start the git
224daemon.
225
226To do this on a system with =systemd=, create a service file:
227
228#+begin_src sh
229sudo nano /etc/systemd/system/git-daemon.service
230#+end_src
231
232Inside the =git-daemon.service= file, paste the following:
233
234#+begin_src conf
235[Unit]
236Description=Start Git Daemon
237
238[Service]
239ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
240
241Restart=always
242RestartSec=500ms
243
244StandardOutput=syslog
245StandardError=syslog
246SyslogIdentifier=git-daemon
247
248User=git
249Group=git
250
251[Install]
252WantedBy=multi-user.target
253#+end_src
254
255Once created, enable and start the service:
256
257#+begin_src sh
258sudo systemctl enable git-daemon.service
259sudo systemctl start git-daemon.service
260#+end_src
261
262To clone read-only via the =git://= protocol, you can use the following
263syntax:
264
265#+begin_src sh
266git clone git://git.example.com/test.git
267#+end_src
268
269* Migrating Repositories
270At this point, we have a working git server that works with both SSH and
271read-only access.
272
273For each of the repositories I had hosted a different provider, I
274executed the following commands in order to place a copy on my server as
275my new source of truth:
276
277Server:
278
279#+begin_src sh
280su git
281mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
282git init --bare
283
284# If you want to make this repo viewable/cloneable to the public
285touch git-daemon-export-ok
286#+end_src
287
288Client:
289
290#+begin_src sh
291git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
292git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
293git push
294#+end_src
295
296* Optional Web View: =cgit=
297If you want a web viewer for your repositories, you can use various
298tools, such as =gitweb=, =cgit=, or =klaus=. I chose =cgit= due to its
299simple interface and fairly easy set-up (compared to others). Not to
300mention that the [[https://git.kernel.org/][Linux kernel uses =cgit=]].
301
302** Docker Compose
303Instead of using my previous method of using a =docker run= command,
304I've updated this section to use =docker-compose= instead for an easier
305installation and simpler management and configuration.
306
307In order to use Docker Compose, you will set up a =docker-compose.yml=
308file to automatically connect resources like the repositories, =cgitrc=,
309and various files or folders to the =cgit= container you're creating:
310
311#+begin_src sh
312mkdir ~/cgit && cd ~/cgit
313nano docker-compose.yml
314#+end_src
315
316#+begin_src conf
317# docker-compose.yml
318version: '3'
319
320services:
321 cgit:
322 image: invokr/cgit
323 volumes:
324 - /git:/git
325 - ./cgitrc:/etc/cgitrc
326 - ./logo.png:/var/www/htdocs/cgit/logo.png
327 - ./favicon.png:/var/www/htdocs/cgit/favicon.png
328 - ./filters:/var/www/htdocs/cgit/filters
329 ports:
330 - "8763:80"
331 restart: always
332#+end_src
333
334Then, just start the container:
335
336#+begin_src sh
337sudo docker-compose up -d
338#+end_src
339
340Once it's finished installing, you can access the site at
341=<SERVER_IP>:8763= or use a reverse-proxy service to forward =cgit= to a
342URL, such as =git.example.com=. See the next section for more details on
343reverse proxying a URL to a local port.
344
345** Nginx Reverse Proxy
346I am using Nginx as my reverse proxy so that the =cgit= Docker container
347can use =git.example.com= as its URL. To do so, I simply created the
348following configuration file:
349
350#+begin_src sh
351sudo nano /etc/nginx/sites-available/git.example.com
352#+end_src
353
354#+begin_src conf
355server {
356 listen 80;
357 server_name git.example.com;
358
359 if ($host = git.example.com) {
360 return 301 https://$host$request_uri;
361 }
362
363 return 404;
364}
365
366server {
367 server_name git.example.com;
368 listen 443 ssl http2;
369
370 location / {
371 # The final `/` is important.
372 proxy_pass http://localhost:8763/;
373 add_header X-Frame-Options SAMEORIGIN;
374 add_header X-XSS-Protection "1; mode=block";
375 proxy_redirect off;
376 proxy_buffering off;
377 proxy_set_header Host $host;
378 proxy_set_header X-Real-IP $remote_addr;
379 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
380 proxy_set_header X-Forwarded-Proto $scheme;
381 proxy_set_header X-Forwarded-Port $server_port;
382 }
383
384 # INCLUDE ANY SSL CERTS HERE
385 include /etc/letsencrypt/options-ssl-nginx.conf;
386 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
387}
388#+end_src
389
390Once created, symlink it and restart the web server.
391
392#+begin_src sh
393sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
394sudo systemctl restart nginx.service
395#+end_src
396
397As we can see below, my site at =git.example.com= is available and
398running:
399
400** Settings Up Git Details
401Once you have =cgit= running, you can add some small details, such as
402repository owners and descriptions by editing the following files within
403each repository.
404
405Alternatively, you can use the =cgitrc= file to edit these details if
406you only care to edit them for the purpose of seeing them on your
407website.
408
409The =description= file within the repository on your server will display
410the description online.
411
412#+begin_src sh
413cd /git/example.git
414nano description
415#+end_src
416
417You can add a =[gitweb]= block to the =config= file in order to display
418the owner of the repository.
419
420#+begin_src sh
421cd /git/example.git
422nano config
423#+end_src
424
425#+begin_src conf
426[gitweb]
427 owner = "YourName"
428#+end_src
429
430Note that you can ignore the configuration within each repository and
431simply set up this information in the =cgitrc= file, if you want to do
432it that way.
433
434** Editing =cgit=
435In order to edit certain items within =cgit=, you need to edit the
436=cgitrc= file.
437
438#+begin_src sh
439nano ~/cgit/cgitrc
440#+end_src
441
442Below is an example configuration for =cgitrc=. You can find all the
443configuration options within the [configuration manual]
444([[https://git.zx2c4.com/cgit/plain/cgitrc.5.txt]]).
445
446#+begin_src conf
447css=/cgit.css
448logo=/logo.png
449favicon=/favicon.png
450robots=noindex, nofollow
451
452enable-index-links=1
453enable-commit-graph=1
454enable-blame=1
455enable-log-filecount=1
456enable-log-linecount=1
457enable-git-config=1
458
459clone-url=git://git.example.com/$CGIT_REPO_URL ssh://git@git.example.com:/git/$CGIT_REPO_URL
460
461root-title=My Git Website
462root-desc=My personal git repositories.
463
464# Allow download of tar.gz, tar.bz2 and zip-files
465snapshots=tar.gz tar.bz2 zip
466
467##
468## List of common mimetypes
469##
470mimetype.gif=image/gif
471mimetype.html=text/html
472mimetype.jpg=image/jpeg
473mimetype.jpeg=image/jpeg
474mimetype.pdf=application/pdf
475mimetype.png=image/png
476mimetype.svg=image/svg+xml
477
478# Highlight source code
479# source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.sh
480source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
481
482# Format markdown, restructuredtext, manpages, text files, and html files
483# through the right converters
484about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
485
486##
487## Search for these files in the root of the default branch of repositories
488## for coming up with the about page:
489##
490readme=:README.md
491readme=:readme.md
492readme=:README.mkd
493readme=:readme.mkd
494readme=:README.rst
495readme=:readme.rst
496readme=:README.html
497readme=:readme.html
498readme=:README.htm
499readme=:readme.htm
500readme=:README.txt
501readme=:readme.txt
502readme=:README
503readme=:readme
504
505# Repositories
506
507# Uncomment the following line to scan a path instead of adding repositories manually
508# scan-path=/git
509
510## Test Section
511section=git/test-section
512
513repo.url=test.git
514repo.path=/git/test.git
515repo.readme=:README.md
516repo.owner=John Doe
517repo.desc=An example repository!
518#+end_src
519
520** Final Fixes: Syntax Highlighting & README Rendering
521After completing my initial install and playing around with it for a few
522days, I noticed two issues:
523
5241. Syntax highlighting did not work when viewing the source code within
525 a file.
5262. The =about= tab within a repository was not rendered to HTML.
527
528The following process fixes these issues. To start, let's go to the
529=cgit= directory where we were editing our configuration file earlier.
530
531#+begin_src sh
532cd ~/cgit
533#+end_src
534
535In here, create two folders that will hold our syntax files:
536
537#+begin_src sh
538mkdir filters && mkdir filters/html-converters && cd filters
539#+end_src
540
541Next, download the default filters:
542
543#+begin_src sh
544curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
545chmod 755 about-formatting.sh
546curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
547chmod 755 syntax-highlighting.py
548#+end_src
549
550Finally, download the HTML conversion files you need. The example below
551downloads the Markdown converter:
552
553#+begin_src sh
554cd html-converters
555curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
556chmod 755 md2html
557#+end_src
558
559If you need other filters or html-converters found within
560[[https://git.zx2c4.com/cgit/tree/filters][the cgit project files]],
561repeat the =curl= and =chmod= process above for whichever files you
562need.
563
564However, formatting will not work quite yet since the Docker cgit
565container we're using doesn't have the formatting package installed. You
566can install this easily by install Python 3+ and the =pygments= package:
567
568#+begin_src sh
569# Enter the container's command line
570sudo docker exec -it cgit bash
571#+end_src
572
573#+begin_src sh
574# Install the necessary packages and then exit
575yum update -y && \
576yum upgrade -y && \
577yum install python3 python3-pip -y && \
578pip3 install markdown pygments && \
579exit
580#+end_src
581
582*You will need to enter the cgit docker container and re-run these =yum=
583commands every time you kill and restart the container!*
584
585If not done already, we need to add the following variables to our
586=cgitrc= file in order for =cgit= to know where our filtering files are:
587
588#+begin_src conf
589# Highlight source code with python pygments-based highlighter
590source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
591
592# Format markdown, restructuredtext, manpages, text files, and html files
593# through the right converters
594about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
595#+end_src
596
597Now you should see that syntax highlighting and README rendering to the
598=about= tab is fixed.
599
600** Theming
601I won't go into much detail in this section, but you can fully theme
602your installation of =cgit= since you have access to the =cgit.css= file
603in your web root. This is another file you can add as a volume to the
604=docker-compose.yml= file if you want to edit this without entering the
605container's command line.
606
607*** :warning: Remember to Back Up Your Data!
608The last thing to note is that running services on your own equipment
609means that you're assuming a level of risk that exists regarding data
610loss, catastrophes, etc. In order to reduce the impact of any such
611occurrence, I suggest backing up your data regularly.
612
613Backups can be automated via =cron=, by hooking your base directory up
614to a cloud provider, or even setting up hooks to push all repository
615info to git mirrors on other git hosts. Whatever the method, make sure
616that your data doesn't vanish in the event that your drives or servers
617fail.
content/blog/2022-07-14-gnupg.org → content/blog/2022-07-14-gnupg.md renamed +124 −107
@@ -1,27 +1,31 @@
1#+title: GNU Privacy Guard (GPG) 1+++
2#+date: 2022-07-14 2date = 2022-07-14
3#+description: Learn how to create a PGP key with GNU Privacy Guard (GPG). 3title = "GNU Privacy Guard (GPG)"
4#+filetags: :privacy: 4description = ""
5 5draft = false
6* The History of GPG 6+++
7[[https://gnupg.org/][GNU Privacy Guard]], also known as GnuPG and GPG, 7
8is a free ("free" as in both speech and beer) software that fully 8# The History of GPG
9implements the OpenPGP Message Format documented in 9
10[[https://www.rfc-editor.org/rfc/rfc4880][RFC 4880]]. 10[GNU Privacy Guard](https://gnupg.org/), also known as GnuPG and GPG, is
11 11a free (\"free\" as in both speech and beer) software that fully
12I won't go in-depth on the full history of the software in this post, 12implements the OpenPGP Message Format documented in [RFC
134880](https://www.rfc-editor.org/rfc/rfc4880).
14
15I won\'t go in-depth on the full history of the software in this post,
13but it is important to understand that GPG is not the same as PGP 16but it is important to understand that GPG is not the same as PGP
14(Pretty Good Privacy), which is a different implementation of RFC 4880. 17(Pretty Good Privacy), which is a different implementation of RFC 4880.
15However, GPG was designed to interoperate with PGP. 18However, GPG was designed to interoperate with PGP.
16 19
17GPG was originally developed in the late 1990s by 20GPG was originally developed in the late 1990s by [Werner
18[[https://en.wikipedia.org/wiki/Werner_Koch][Werner Koch]] and has 21Koch](https://en.wikipedia.org/wiki/Werner_Koch) and has historically
19historically been funded generously by the German government. 22been funded generously by the German government.
20 23
21Now that we have all the high-level info out of the way, let's dive into 24Now that we have all the high-level info out of the way, let\'s dive
22the different aspects of GPG and its uses. 25into the different aspects of GPG and its uses.
26
27# Encryption Algorithms
23 28
24* Encryption Algorithms
25GPG supports a wide range of different encryption algorithms, including 29GPG supports a wide range of different encryption algorithms, including
26public-key, cipher, hash, and compression algorithms. The support for 30public-key, cipher, hash, and compression algorithms. The support for
27these algorithms has grown since the adoption of the Libgcrypt library 31these algorithms has grown since the adoption of the Libgcrypt library
@@ -31,7 +35,7 @@ As you will be able to see below in an example of a full key generation
31with the GPG command line tool, GPG recommends the following algorithms 35with the GPG command line tool, GPG recommends the following algorithms
32to new users: 36to new users:
33 37
34#+begin_src sh 38```sh
35Please select what kind of key you want: 39Please select what kind of key you want:
36 (1) RSA and RSA 40 (1) RSA and RSA
37 (2) DSA and Elgamal 41 (2) DSA and Elgamal
@@ -39,66 +43,72 @@ Please select what kind of key you want:
39 (4) RSA (sign only) 43 (4) RSA (sign only)
40 (9) ECC (sign and encrypt) *default* 44 (9) ECC (sign and encrypt) *default*
41 (10) ECC (sign only) 45 (10) ECC (sign only)
42#+end_src 46```
43 47
44I am not doing an in-depth explanation here in order to keep the focus 48I am not doing an in-depth explanation here in order to keep the focus
45on GPG and not encryption algorithms. If you want a deep dive into 49on GPG and not encryption algorithms. If you want a deep dive into
46cryptography or encryption algorithms, please read my other posts: 50cryptography or encryption algorithms, please read my other posts:
47 51
48- [[../aes-encryption/][AES Encryption]] (2018) 52- [AES Encryption](../aes-encryption/) (2018)
49- [[../cryptography-basics/][Cryptography Basics]] (2020) 53- [Cryptography Basics](../cryptography-basics/) (2020)
54
55## Vulnerabilities
50 56
51** Vulnerabilities
52As of 2022-07-14, there are a few different vulnerabilities associated 57As of 2022-07-14, there are a few different vulnerabilities associated
53with GPG or the libraries it uses: 58with GPG or the libraries it uses:
54 59
55- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one 60- GPG versions 1.0.2--1.2.3 contains a bug where \"as soon as one
56 (GPG-generated) ElGamal signature of an arbitrary message is released, 61 (GPG-generated) ElGamal signature of an arbitrary message is
57 one can recover the signer's private key in less than a second on a 62 released, one can recover the signer\'s private key in less than a
58 PC." ([[https://www.di.ens.fr/~pnguyen/pub_Ng04.htm][Source]]) 63 second on a PC.\"
59- GPG versions prior to 1.4.2.1 contain a false positive signature 64 ([Source](https://www.di.ens.fr/~pnguyen/pub_Ng04.htm))
60 verification bug. 65- GPG versions prior to 1.4.2.1 contain a false positive signature
61 ([[https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html][Source]]) 66 verification bug.
62- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned 67 ([Source](https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html))
63 data. ( 68- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned
64 [[https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html][Source]]) 69 data. (
65- Libgcrypt, a library used by GPG, contained a bug which enabled full 70 [Source](https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html))
66 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved in 71- Libgcrypt, a library used by GPG, contained a bug which enabled full
67 a GPG update in 2017. ([[https://lwn.net/Articles/727179/][Source]]) 72 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved
68- The [[https://en.wikipedia.org/wiki/ROCA_vulnerability][ROCA 73 in a GPG update in 2017.
69 Vulnerability]] affects RSA keys generated by YubiKey 4 tokens. 74 ([Source](https://lwn.net/Articles/727179/))
70 ([[https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf][Source]]) 75- The [ROCA
71- The [[https://en.wikipedia.org/wiki/SigSpoof][SigSpoof Attack]] allows 76 Vulnerability](https://en.wikipedia.org/wiki/ROCA_vulnerability)
72 an attacker to spoof digital signatures. 77 affects RSA keys generated by YubiKey 4 tokens.
73 ([[https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/][Source]]) 78 ([Source](https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf))
74- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer 79- The [SigSpoof Attack](https://en.wikipedia.org/wiki/SigSpoof) allows
75 overflow, fixed in Libgcrypt 1.9.1 80 an attacker to spoof digital signatures.
76 ([[https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/][Source]]) 81 ([Source](https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/))
77 82- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer
78*** Platforms 83 overflow, fixed in Libgcrypt 1.9.1
79Originally developed as a command-line program for *nix systems, GPG now 84 ([Source](https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/))
80has a wealth of front-end applications and libraries available for 85
86### Platforms
87
88Originally developed as a command-line program for \*nix systems, GPG
89now has a wealth of front-end applications and libraries available for
81end-users. However, the most recommended programs remain the same: 90end-users. However, the most recommended programs remain the same:
82 91
83- [[https://gnupg.org][GnuPG]] for Linux (depending on distro) 92- [GnuPG](https://gnupg.org) for Linux (depending on distro)
84- [[https://gpg4win.org][Gpg4win]] for Windows 93- [Gpg4win](https://gpg4win.org) for Windows
85- [[https://gpgtools.org][GPGTools]] for macOS 94- [GPGTools](https://gpgtools.org) for macOS
95
96# Creating a Key Pair
86 97
87* Creating a Key Pair
88In order to create a GPG key pair, a user would first need to install 98In order to create a GPG key pair, a user would first need to install
89GPG on their system. If we're assuming that the user is on Fedora Linux, 99GPG on their system. If we\'re assuming that the user is on Fedora
90they would execute the following: 100Linux, they would execute the following:
91 101
92#+begin_src sh 102```sh
93sudo dnf install gpg 103sudo dnf install gpg
94#+end_src 104```
95 105
96Once installed, a user can create a new key pair with the following 106Once installed, a user can create a new key pair with the following
97command(s): 107command(s):
98 108
99#+begin_src sh 109```sh
100gpg --full-generate-key 110gpg --full-generate-key
101#+end_src 111```
102 112
103GPG will walk the user through an interactive setup that asks for an 113GPG will walk the user through an interactive setup that asks for an
104algorithm preference, expiration date, name, and email to associate with 114algorithm preference, expiration date, name, and email to associate with
@@ -107,7 +117,7 @@ this key.
107See the following example key set-up for a default key generation using 117See the following example key set-up for a default key generation using
108the GnuPG command-line interface: 118the GnuPG command-line interface:
109 119
110#+begin_src sh 120```sh
111gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc. 121gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc.
112This is free software: you are free to change and redistribute it. 122This is free software: you are free to change and redistribute it.
113There is NO WARRANTY, to the extent permitted by law. 123There is NO WARRANTY, to the extent permitted by law.
@@ -159,28 +169,30 @@ pub ed25519 2022-07-14 [SC]
159 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C 169 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C
160uid John Doe (test key) <johndoe@example.com> 170uid John Doe (test key) <johndoe@example.com>
161sub cv25519 2022-07-14 [E] 171sub cv25519 2022-07-14 [E]
162#+end_src 172```
163 173
164Please note that GUI apps may differ slightly from the GPG command-line 174Please note that GUI apps may differ slightly from the GPG command-line
165interface. 175interface.
166 176
167* Common Usage 177# Common Usage
178
168As noted in RFC 4880, the general functions of OpenPGP are as follows: 179As noted in RFC 4880, the general functions of OpenPGP are as follows:
169 180
170- digital signatures 181- digital signatures
171- encryption 182- encryption
172- compression 183- compression
173- Radix-64 conversion 184- Radix-64 conversion
174- key management and certificate services 185- key management and certificate services
175 186
176From this, you can probably gather that the main use of GPG is for 187From this, you can probably gather that the main use of GPG is for
177encrypting data and/or signing the data with a key. The purpose of 188encrypting data and/or signing the data with a key. The purpose of
178encrypting data with GPG is to ensure that no one except the intended 189encrypting data with GPG is to ensure that no one except the intended
179recipient(s) can access the data. 190recipient(s) can access the data.
180 191
181Let's explore some specific GPG use-cases. 192Let\'s explore some specific GPG use-cases.
193
194## Email
182 195
183** Email
184One of the more popular uses of GPG is to sign and/or encrypt emails. 196One of the more popular uses of GPG is to sign and/or encrypt emails.
185With the use of a GPG keypair, you can encrypt a message, its subject, 197With the use of a GPG keypair, you can encrypt a message, its subject,
186and even the attachments within. 198and even the attachments within.
@@ -188,16 +200,16 @@ and even the attachments within.
188The first process, regarding the signing of a message without any 200The first process, regarding the signing of a message without any
189encryption, is generally used to provide assurance that an email is 201encryption, is generally used to provide assurance that an email is
190truly coming from the sender that the message claims. When I send an 202truly coming from the sender that the message claims. When I send an
191email, and it's signed with my public key, the recipient(s) of the 203email, and it\'s signed with my public key, the recipient(s) of the
192message can verify that the message was signed with my personal key. 204message can verify that the message was signed with my personal key.
193 205
194The second process, regarding the actual encryption of the message and 206The second process, regarding the actual encryption of the message and
195its contents, works by using a combination of the sender's keys and the 207its contents, works by using a combination of the sender\'s keys and the
196recipient's keys. This process may vary slightly by implementation, but 208recipient\'s keys. This process may vary slightly by implementation, but
197it most commonly uses asymmetric cryptography, also known as public-key 209it most commonly uses asymmetric cryptography, also known as public-key
198cryptography. In this version of encryption, the sender's private key to 210cryptography. In this version of encryption, the sender\'s private key
199sign the message and a combination of the sender's keys and the 211to sign the message and a combination of the sender\'s keys and the
200recipient's public key to encrypt the message. 212recipient\'s public key to encrypt the message.
201 213
202If two people each have their own private keys and exchange their public 214If two people each have their own private keys and exchange their public
203keys, they can send encrypted messages back and forth with GPG. This is 215keys, they can send encrypted messages back and forth with GPG. This is
@@ -205,60 +217,64 @@ also possible with symmetric cryptography, but the process differs since
205there are no key pairs. 217there are no key pairs.
206 218
207Implementation of email encryption varies greatly between email clients, 219Implementation of email encryption varies greatly between email clients,
208so you will need to reference your email client's documentation to 220so you will need to reference your email client\'s documentation to
209ensure you are setting it up correctly for that specific client. 221ensure you are setting it up correctly for that specific client.
210 222
211** File Encryption 223## File Encryption
224
212As noted in the section above regarding emails, GPG enables users to be 225As noted in the section above regarding emails, GPG enables users to be
213able to send a message to each other if they are both set-up with GPG 226able to send a message to each other if they are both set-up with GPG
214keys. In this example, I am going to show how a user could send a file 227keys. In this example, I am going to show how a user could send a file
215called =example_file.txt= to another user via the recipient's email. 228called `example_file.txt` to another user via the
229recipient\'s email.
216 230
217The sender would find the file they want to send and execute the 231The sender would find the file they want to send and execute the
218following command: 232following command:
219 233
220#+begin_src sh 234```sh
221gpg --encrypt --output example_file.txt.gpg --recipient \ 235gpg --encrypt --output example_file.txt.gpg --recipient \
222recipient@example.com example_file.txt 236recipient@example.com example_file.txt
223#+end_src 237```
224 238
225Once received, the recipient can decrypt the file with the following 239Once received, the recipient can decrypt the file with the following
226command: 240command:
227 241
228#+begin_src sh 242```sh
229gpg --decrypt --output example_file.txt example_file.txt.gpg 243gpg --decrypt --output example_file.txt example_file.txt.gpg
230#+end_src 244```
245
246## Ownership Signatures
231 247
232** Ownership Signatures
233One important aspect of GPG, especially for developers, is the ability 248One important aspect of GPG, especially for developers, is the ability
234to sign data without encrypting it. For example, developers often sign 249to sign data without encrypting it. For example, developers often sign
235code changes when they commit the changes back to a central repository, 250code changes when they commit the changes back to a central repository,
236in order to display ownership of who made the changes. This allows other 251in order to display ownership of who made the changes. This allows other
237users to look at a code change and determine that the change was valid. 252users to look at a code change and determine that the change was valid.
238 253
239In order to do this using [[https://git-scm.com][Git]], the developer 254In order to do this using [Git](https://git-scm.com), the developer
240simply needs to alter the =git commit= command to include the =-S= flag. 255simply needs to alter the `git commit` command to include the
241Here's an example: 256`-S` flag. Here\'s an example:
242 257
243#+begin_src sh 258```sh
244git commit -S -m "my commit message" 259git commit -S -m "my commit message"
245#+end_src 260```
246 261
247As an expansion of the example above, Git users can configure their 262As an expansion of the example above, Git users can configure their
248environment with a default key to use by adding their GPG signature: 263environment with a default key to use by adding their GPG signature:
249 264
250#+begin_src sh 265```sh
251git config --global user.signingkey XXXXXXXXXXXXXXXX 266git config --global user.signingkey XXXXXXXXXXXXXXXX
252#+end_src 267```
253 268
254If you're not sure what your signature is, you can find it titled =sig= 269If you\'re not sure what your signature is, you can find it titled
255in the output of this command: 270`sig` in the output of this command:
256 271
257#+begin_src sh 272```sh
258gpg --list-signatures 273gpg --list-signatures
259#+end_src 274```
275
276## File Integrity
260 277
261** File Integrity
262When a person generates a signature for data, they are allowing users 278When a person generates a signature for data, they are allowing users
263the ability to verify the signature on that data in the future to ensure 279the ability to verify the signature on that data in the future to ensure
264the data has not been corrupted. This is most common with software 280the data has not been corrupted. This is most common with software
@@ -268,30 +284,31 @@ replaced with dangerous software.
268 284
269In order to verify signed data, a user needs to have: 285In order to verify signed data, a user needs to have:
270 286
2711. The signed data 2871. The signed data
2722. A signature file 2882. A signature file
2733. The public GPG key of the signer 2893. The public GPG key of the signer
274 290
275Once the signer's public key is imported on the user's system, and they 291Once the signer\'s public key is imported on the user\'s system, and
276have the data and signature, they can verify the data with the following 292they have the data and signature, they can verify the data with the
277commands: 293following commands:
278 294
279#+begin_src sh 295```sh
280# If the signature is attached to the data 296# If the signature is attached to the data
281gpg --verify [signature-file] 297gpg --verify [signature-file]
282 298
283# If the signature is detached as a separate file from the data 299# If the signature is detached as a separate file from the data
284gpg --verify [signature-file] [original-file] 300gpg --verify [signature-file] [original-file]
285#+end_src 301```
302
303### Finding Public Keys
286 304
287*** Finding Public Keys
288In order to use GPG with others, a user needs to know the other user(s) 305In order to use GPG with others, a user needs to know the other user(s)
289keys. This is easy to do if the user knows the other user(s) in person, 306keys. This is easy to do if the user knows the other user(s) in person,
290but may be hard if the relationship is strictly digital. Luckily, there 307but may be hard if the relationship is strictly digital. Luckily, there
291are a few options. The first option is to look at a user's web page or 308are a few options. The first option is to look at a user\'s web page or
292social pages if they have them. 309social pages if they have them.
293 310
294Otherwise, the best option is to use a keyserver, such as: 311Otherwise, the best option is to use a keyserver, such as:
295 312
296- [[https://pgp.mit.edu][pgp.mit.edu]] 313- [pgp.mit.edu](https://pgp.mit.edu)
297- [[https://keys.openpgp.org][keys.openpgp.org]] 314- [keys.openpgp.org](https://keys.openpgp.org)
content/blog/2022-07-25-curseradio.md added +107
@@ -0,0 +1,107 @@
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
12radio player based on Python.
13
14This application is fantastic and incredibly easy to install, so I
15wanted to dedicate a post today to this app. Let\'s look at the features
16within the app and then walk through the installation process I took to
17get `curseradio` working.
18
19# Features
20
21![curseradio](https://img.cleberg.net/blog/20220725-curseradio/curseradio.png)
22
23The radio player itself is quite minimal. As you can see in the
24screenshot above, it contains a simple plaintext list of all available
25categories, which can be broken down further and further. In addition,
26radio shows are available for listening, alongside regular radio
27stations.
28
29For example, the `Sports` \> `Pro Basketball` \>
30`Shows` category contains a number of specific shows related
31to Professional Basketball.
32
33Aside from being able to play any of the listed stations/shows, you can
34make a channel your favorite by pressing `f`. It will now
35show up at the top of the radio player in the `Favourites`
36category.
37
38## Commands/Shortcuts
39
40 Key(s) Command
41 ------------ ---------------------------------
42 ↑, ↓ navigate
43 PgUp, PgDn navigate quickly
44 Home, End to top/bottom
45 Enter open/close folders, play stream
46 k stop playing stream
47 q quit
48 f toggle favourite
49
50# Installation
51
52## Dependencies
53
54Before installing `curseradio`, a handful of system and
55Python packages are required. To get started, install
56`python3`, `pip3`, and `mpv` on your
57system. In this example, I\'m using Fedora Linux, which uses the
58`dnf` package manager. You may need to adjust this if you\'re
59using a different system.
60
61```sh
62sudo dnf install python3 pip3 mpv
63```
64
65Next, use `pip3` to install `requests`,
66`xdg`, and `lxml`:
67
68```sh
69pip3 install requests xdg lxml
70```
71
72## Repository Source Installation
73
74Once all the dependencies are installed, we can clone the source code
75and enter that directory:
76
77```sh
78git clone https://github.com/chronitis/curseradio && cd curseradio
79```
80
81Once you\'re within the `curseradio` directory, you can
82install the application with the provided `setup.py` script.
83
84```sh
85sudo python3 setup.py install
86```
87
88In my case, I ran into a few errors and needed to create the folders
89that curseradio wanted to use for its installation. If you don\'t get
90any errors, you can skip this and run the app.
91
92```sh
93sudo mkdir /usr/local/lib/python3.10/
94sudo mkdir /usr/local/lib/python3.10/site-packages/
95```
96
97```sh
98sudo chown -R $USER:$USER /usr/local/lib/python3.10/
99```
100
101# Run the Application
102
103Once fully installed without errors, you can run the application!
104
105```sh
106python3 /usr/local/bin/curseradio
107```
content/blog/2022-07-25-curseradio.org deleted −95
@@ -1,95 +0,0 @@
1#+title: CurseRadio: Listening to the Radio on the Command Line
2#+date: 2022-07-25
3#+description: Use Curse Radio to listen to radio on the command line.
4#+filetags: :linux:
5
6* Overview
7While exploring some interesting Linux applications, I stumbled across
8[[https://github.com/chronitis/curseradio][curseradio]], a command-line
9radio player based on Python.
10
11This application is fantastic and incredibly easy to install, so I
12wanted to dedicate a post today to this app. Let's look at the features
13within the app and then walk through the installation process I took to
14get =curseradio= working.
15
16* Features
17#+caption: curseradio
18[[https://img.cleberg.net/blog/20220725-curseradio/curseradio.png]]
19
20The radio player itself is quite minimal. As you can see in the
21screenshot above, it contains a simple plaintext list of all available
22categories, which can be broken down further and further. In addition,
23radio shows are available for listening, alongside regular radio
24stations.
25
26For example, the =Sports= > =Pro Basketball= > =Shows= category contains
27a number of specific shows related to Professional Basketball.
28
29Aside from being able to play any of the listed stations/shows, you can
30make a channel your favorite by pressing =f=. It will now show up at the
31top of the radio player in the =Favourites= category.
32
33** Commands/Shortcuts
34| Key(s) | Command |
35|------------+---------------------------------|
36| ↑, ↓ | navigate |
37| PgUp, PgDn | navigate quickly |
38| Home, End | to top/bottom |
39| Enter | open/close folders, play stream |
40| k | stop playing stream |
41| q | quit |
42| f | toggle favourite |
43
44* Installation
45** Dependencies
46Before installing =curseradio=, a handful of system and Python packages
47are required. To get started, install =python3=, =pip3=, and =mpv= on
48your system. In this example, I'm using Fedora Linux, which uses the
49=dnf= package manager. You may need to adjust this if you're using a
50different system.
51
52#+begin_src sh
53sudo dnf install python3 pip3 mpv
54#+end_src
55
56Next, use =pip3= to install =requests=, =xdg=, and =lxml=:
57
58#+begin_src sh
59pip3 install requests xdg lxml
60#+end_src
61
62** Repository Source Installation
63Once all the dependencies are installed, we can clone the source code
64and enter that directory:
65
66#+begin_src sh
67git clone https://github.com/chronitis/curseradio && cd curseradio
68#+end_src
69
70Once you're within the =curseradio= directory, you can install the
71application with the provided =setup.py= script.
72
73#+begin_src sh
74sudo python3 setup.py install
75#+end_src
76
77In my case, I ran into a few errors and needed to create the folders
78that curseradio wanted to use for its installation. If you don't get any
79errors, you can skip this and run the app.
80
81#+begin_src sh
82sudo mkdir /usr/local/lib/python3.10/
83sudo mkdir /usr/local/lib/python3.10/site-packages/
84#+end_src
85
86#+begin_src sh
87sudo chown -R $USER:$USER /usr/local/lib/python3.10/
88#+end_src
89
90* Run the Application
91Once fully installed without errors, you can run the application!
92
93#+begin_src sh
94python3 /usr/local/bin/curseradio
95#+end_src
content/blog/2022-07-30-flac-to-opus.org → content/blog/2022-07-30-flac-to-opus.md renamed +67 −58
@@ -1,65 +1,71 @@
1#+title: Recursive Command-Line FLAC to Opus Conversion 1+++
2#+date: 2022-07-30 2date = 2022-07-30
3#+description: Learn how to convert all FLAC files to Opus, including recursive files in subdirectories. 3title = "Recursive Command-Line FLAC to Opus Conversion"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
7
8# Converting FLAC to OPUS
5 9
6* Converting FLAC to OPUS
7I am currently rebuilding my music library from scratch so that I can 10I am currently rebuilding my music library from scratch so that I can
8effectively archive all the music I own in the 11effectively archive all the music I own in the [FLAC file
9[[https://en.wikipedia.org/wiki/FLAC][FLAC file format]], a lossless 12format](https://en.wikipedia.org/wiki/FLAC), a lossless audio codec.
10audio codec.
11 13
12However, streaming FLAC files outside the home can be difficult due to 14However, streaming FLAC files outside the home can be difficult due to
13the size of the files, especially if you're using a weak connection. 15the size of the files, especially if you\'re using a weak connection.
14 16
15So, in order to archive the music in a lossless format and still be able 17So, in order to archive the music in a lossless format and still be able
16to stream it easily, I opted to create a copy of my FLAC files in the 18to stream it easily, I opted to create a copy of my FLAC files in the
17[[https://en.wikipedia.org/wiki/Opus_(audio_format)][Opus audio codec]]. 19[Opus audio codec](https://en.wikipedia.org/wiki/Opus_(audio_format)).
18This allows me to archive a quality, lossless version of the music and 20This allows me to archive a quality, lossless version of the music and
19then point my streaming service to the smaller, stream-ready version. 21then point my streaming service to the smaller, stream-ready version.
20 22
21** Dependencies 23## Dependencies
22The process I follow utilizes the =opus-tools= package in Ubuntu. Before 24
23proceeding, install the package: 25The process I follow utilizes the `opus-tools` package in
26Ubuntu. Before proceeding, install the package:
24 27
25#+begin_src sh 28```sh
26sudo apt install opus-tools 29sudo apt install opus-tools
27#+end_src 30```
31
32If you want to use a different conversion method, such as
33`ffmpeg` or `avconv`, simply install that package
34instead.
28 35
29If you want to use a different conversion method, such as =ffmpeg= or 36## Conversion Process
30=avconv=, simply install that package instead.
31 37
32** Conversion Process 38The script I\'m using is stored in my home directory, but feel free to
33The script I'm using is stored in my home directory, but feel free to
34create it wherever you want. It does not need to be in the same 39create it wherever you want. It does not need to be in the same
35directory as your music files. 40directory as your music files.
36 41
37#+begin_src sh 42```sh
38cd ~ && nano transform.sh 43cd ~ && nano transform.sh
39#+end_src 44```
40 45
41Once you have your new bash script opened in an editor, go ahead and 46Once you have your new bash script opened in an editor, go ahead and
42paste the following logic into the script. 47paste the following logic into the script.
43 48
44You *MUST* edit the following variables in order for it to work: 49You **MUST** edit the following variables in order for it to work:
45 50
46- =source=: The source directory where your FLAC files are stored. 51- `source`: The source directory where your FLAC files are
47- =dest=: The destination directory where you want the resulting Opus 52 stored.
48 files to be stored. 53- `dest`: The destination directory where you want the
54 resulting Opus files to be stored.
49 55
50You *MAY* want to edit the following variables to suit your needs: 56You **MAY** want to edit the following variables to suit your needs:
51 57
52- =filename=: If you are converting to a file format other than Opus, 58- `filename`: If you are converting to a file format other
53 you'll need to edit this so that your resulting files have the correct 59 than Opus, you\'ll need to edit this so that your resulting files
54 filename extension. 60 have the correct filename extension.
55- =reldir=: This variable can be edited to strip out more leading 61- `reldir`: This variable can be edited to strip out more
56 directories in the file path. As you'll see later, I ignore this for 62 leading directories in the file path. As you\'ll see later, I ignore
57 now and simply clean it up afterward. 63 this for now and simply clean it up afterward.
58- =opusenc=: This is the actual conversion process. You may want to edit 64- `opusenc`: This is the actual conversion process. You may
59 the bitrate to suit your needs. I set mine at 128 but some prefer 160 65 want to edit the bitrate to suit your needs. I set mine at 128 but
60 or higher. 66 some prefer 160 or higher.
61 67
62#+begin_src sh 68```sh
63#!/bin/bash 69#!/bin/bash
64## - The IFS takes care of spaces in file and dirnames 70## - The IFS takes care of spaces in file and dirnames
65## - your folders may vary 71## - your folders may vary
@@ -121,49 +127,52 @@ opusenc --vbr --bitrate 128 --date "$DATE" \
121## just for testing 127## just for testing
122# sleep 1 128# sleep 1
123done 129done
124#+end_src 130```
125 131
126Once you're done, simply save the file and exit your editor. Don't 132Once you\'re done, simply save the file and exit your editor. Don\'t
127forget to enable execution of the script: 133forget to enable execution of the script:
128 134
129#+begin_src sh 135```sh
130chmod +x transform.sh 136chmod +x transform.sh
131#+end_src 137```
132 138
133Finally, you may now run the script: 139Finally, you may now run the script:
134 140
135#+begin_src sh 141```sh
136./transform.sh 142./transform.sh
137#+end_src 143```
138 144
139If you used =opusenc=, you'll see the conversions happen within the 145If you used `opusenc`, you\'ll see the conversions happen
140terminal as it progresses. You will also see variables printed if you 146within the terminal as it progresses. You will also see variables
141uncommented any of the bash script's comments. 147printed if you uncommented any of the bash script\'s comments.
142 148
143** Cleanup 149## Cleanup
144As I noted above, I didn't customize my =reldir= variable in the script, 150
145which caused my output directory to be =/mnt/music/library/archives= 151As I noted above, I didn\'t customize my `reldir` variable in
146instead of =/mnt/music/library=. So, I moved the output up one level and 152the script, which caused my output directory to be
153`/mnt/music/library/archives` instead of
154`/mnt/music/library`. So, I moved the output up one level and
147deleted the accidental directory. 155deleted the accidental directory.
148 156
149#+begin_src sh 157```sh
150cd /mnt/music/library 158cd /mnt/music/library
151mv archives/** . 159mv archives/** .
152rm -rf archives 160rm -rf archives
153#+end_src 161```
162
163## Check the Resulting Size
154 164
155** Check the Resulting Size 165If you want to see what kind of file size savings you\'ve gained, you
156If you want to see what kind of file size savings you've gained, you can 166can always use the `du` command to check:
157always use the =du= command to check:
158 167
159#+begin_src sh 168```sh
160cd /mnt/music 169cd /mnt/music
161du -h --max-depth=1 . 170du -h --max-depth=1 .
162#+end_src 171```
163 172
164In my case, my small library went from 78GB to 6.3GB! 173In my case, my small library went from 78GB to 6.3GB!
165 174
166#+begin_src txt 175``` txt
16778G ./archives 17678G ./archives
1686.3G ./library 1776.3G ./library
169#+end_src 178```
content/blog/2022-07-31-bash-it.org → content/blog/2022-07-31-bash-it.md renamed +112 −102
@@ -1,113 +1,118 @@
1#+title: Upgrade Bash with Bash-It & Ble.sh 1+++
2#+date: 2022-07-31 2date = 2022-07-31
3#+description: Learn how to increase the power of bash with Bash-It and Ble.sh. 3title = "Upgrade Bash with Bash-It and Ble.sh"
4#+filetags: :sysadmin: 4description = ""
5draft = false
6+++
7
8# Bash
5 9
6* Bash
7For those who are not familiar, 10For those who are not familiar,
8[[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][Bash]] is a Unix 11[Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) is a Unix shell
9shell that is used as the default login shell for most Linux 12that is used as the default login shell for most Linux distributions.
10distributions. This shell and command processor should be familiar if 13This shell and command processor should be familiar if you\'ve used
11you've used Linux (or older version of macOS) before. 14Linux (or older version of macOS) before.
12 15
13However, bash is not the only option. There are numerous other shells 16However, bash is not the only option. There are numerous other shells
14that exist. Here are some popular examples: 17that exist. Here are some popular examples:
15 18
16- [[https://en.wikipedia.org/wiki/Z_shell][zsh]] 19- [zsh](https://en.wikipedia.org/wiki/Z_shell)
17- [[https://en.wikipedia.org/wiki/Fish_(Unix_shell)][fish]] 20- [fish](https://en.wikipedia.org/wiki/Fish_(Unix_shell))
18- [[https://github.com/ibara/oksh][oksh]] 21- [oksh](https://github.com/ibara/oksh)
19- [[https://wiki.gentoo.org/wiki/Mksh][mksh]] 22- [mksh](https://wiki.gentoo.org/wiki/Mksh)
20- [[https://en.wikipedia.org/wiki/Debian_Almquist_shell][dash]] 23- [dash](https://en.wikipedia.org/wiki/Debian_Almquist_shell)
21 24
22While each shell has its differences, bash is POSIX compliant and the 25While each shell has its differences, bash is POSIX compliant and the
23default for many Linux users. Because of this, I am going to explore a 26default for many Linux users. Because of this, I am going to explore a
24program called =bash-it= below that helps bash users increase the 27program called `bash-it` below that helps bash users increase
25utility of their shell without installing a completely new shell. 28the utility of their shell without installing a completely new shell.
29
30## Installation
26 31
27** Installation
28First, if bash is not already installed on your system, you can 32First, if bash is not already installed on your system, you can
29[[https://www.gnu.org/software/bash/][download bash from GNU]] or use 33[download bash from GNU](https://www.gnu.org/software/bash/) or use your
30your package manager to install it. 34package manager to install it.
31 35
32For example, this is how you can install bash on Fedora Linux: 36For example, this is how you can install bash on Fedora Linux:
33 37
34#+begin_src sh 38```sh
35sudo dnf install bash 39sudo dnf install bash
36#+end_src 40```
37 41
38If you are not using bash as your default shell, use the =chsh= command 42If you are not using bash as your default shell, use the
39to change your shell: 43`chsh` command to change your shell:
40 44
41#+begin_src sh 45```sh
42chsh 46chsh
43#+end_src 47```
44 48
45You should see a prompt like the one below. If the brackets (=[]=) 49You should see a prompt like the one below. If the brackets
46contain =bash= already, you're done, and you can simply continue by 50(`[]`) contain `bash` already, you\'re done, and
47hitting the Enter key. 51you can simply continue by hitting the Enter key.
48 52
49If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter 53If the brackets contain another shell path (e.g.
50the path to the bash program on your system (it's most likely located at 54`/usr/bin/zsh`), enter the path to the bash program on your
51=/usr/bin/bash=). 55system (it\'s most likely located at `/usr/bin/bash`).
52 56
53#+begin_src sh 57```sh
54Changing shell for <user>. 58Changing shell for <user>.
55New shell [/usr/bin/bash]: 59New shell [/usr/bin/bash]:
56#+end_src 60```
57 61
58You must log out or restart the machine in order for the login shell to 62You must log out or restart the machine in order for the login shell to
59be refreshed. You can do it now or wait until you're finished 63be refreshed. You can do it now or wait until you\'re finished
60customizing the shell. 64customizing the shell.
61 65
62#+begin_src sh 66```sh
63sudo reboot now 67sudo reboot now
64#+end_src 68```
65 69
66* Bash-it 70# Bash-it
67As noted on the [[https://github.com/Bash-it/bash-it][Bash-it]]
68repository:
69 71
70#+begin_quote 72As noted on the [Bash-it](https://github.com/Bash-it/bash-it)
71Bash-it is a collection of community Bash commands and scripts for Bash 73repository:
723.2+. (And a shameless ripoff of oh-my-zsh 😃)
73 74
74#+end_quote 75> Bash-it is a collection of community Bash commands and scripts for
76> Bash 3.2+. (And a shameless ripoff of oh-my-zsh 😃)
75 77
76Bash-it makes it easy to install plugins, set up aliases for common 78Bash-it makes it easy to install plugins, set up aliases for common
77commands, and easily change the visual theme of your shell. 79commands, and easily change the visual theme of your shell.
78 80
79** Installation 81## Installation
82
80To install the framework, simply copy the repository files and use the 83To install the framework, simply copy the repository files and use the
81=install.sh= script provided. If you want, you can (and should!) inspect 84`install.sh` script provided. If you want, you can (and
82the contents of the installation script before you run it. 85should!) inspect the contents of the installation script before you run
86it.
83 87
84#+begin_src sh 88```sh
85git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it 89git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it
86~/.bash_it/install.sh 90~/.bash_it/install.sh
87#+end_src 91```
88 92
89If you didn't restart your session after making bash the default, and 93If you didn\'t restart your session after making bash the default, and
90are currently working within another shell, be sure to enter a bash 94are currently working within another shell, be sure to enter a bash
91session before using =bash-it=: 95session before using `bash-it`:
92 96
93#+begin_src sh 97```sh
94bash 98bash
95#+end_src 99```
100
101## Aliases
96 102
97** Aliases
98Bash-it contains a number of aliases for common commands to help improve 103Bash-it contains a number of aliases for common commands to help improve
99efficiency in the terminal. To list all available options, use the 104efficiency in the terminal. To list all available options, use the
100following command: 105following command:
101 106
102#+begin_src sh 107```sh
103bash-it show aliases 108bash-it show aliases
104#+end_src 109```
105 110
106This will provide you a list that looks like the following text block. 111This will provide you a list that looks like the following text block.
107Within this screen, you will be able to see all available options and 112Within this screen, you will be able to see all available options and
108which ones are currently enabled. 113which ones are currently enabled.
109 114
110#+begin_src txt 115``` txt
111Alias Enabled? Description 116Alias Enabled? Description
112ag [ ] the silver searcher (ag) aliases 117ag [ ] the silver searcher (ag) aliases
113ansible [ ] ansible abbreviations 118ansible [ ] ansible abbreviations
@@ -120,32 +125,33 @@ clipboard [ ] xclip shortcuts
120composer [ ] common composer abbreviations 125composer [ ] common composer abbreviations
121curl [x] Curl aliases for convenience. 126curl [x] Curl aliases for convenience.
122... 127...
123#+end_src 128```
124 129
125To enable an alias, do: 130To enable an alias, do:
126 131
127#+begin_src sh 132```sh
128bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all 133bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all
129#+end_src 134```
130 135
131To disable an alias, do: 136To disable an alias, do:
132 137
133#+begin_src sh 138```sh
134bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all 139bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all
135#+end_src 140```
141
142## Plugins
136 143
137** Plugins
138Similar to aliases, plugins are available with bash-it. You can find a 144Similar to aliases, plugins are available with bash-it. You can find a
139complete list of plugins in the same way as aliases. Simply execute the 145complete list of plugins in the same way as aliases. Simply execute the
140following: 146following:
141 147
142#+begin_src sh 148```sh
143bash-it show plugins 149bash-it show plugins
144#+end_src 150```
145 151
146You will see the following output showing enabled and disabled plugins: 152You will see the following output showing enabled and disabled plugins:
147 153
148#+begin_src txt 154``` txt
149Plugin Enabled? Description 155Plugin Enabled? Description
150alias-completion [ ] 156alias-completion [ ]
151autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details 157autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details
@@ -156,78 +162,82 @@ battery [x] display info about your battery charge level
156blesh [ ] load ble.sh, the Bash line editor! 162blesh [ ] load ble.sh, the Bash line editor!
157boot2docker [ ] Helpers to get Docker setup correctly for boot2docker 163boot2docker [ ] Helpers to get Docker setup correctly for boot2docker
158browser [ ] render commandline output in your browser 164browser [ ] render commandline output in your browser
159#+end_src 165```
160 166
161To enable a plugin, do: 167To enable a plugin, do:
162 168
163#+begin_src sh 169```sh
164bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all 170bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all
165#+end_src 171```
166 172
167To disable a plugin, do: 173To disable a plugin, do:
168 174
169#+begin_src sh 175```sh
170bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all 176bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all
171#+end_src 177```
178
179## Themes
172 180
173** Themes
174There are quite a few pre-defined 181There are quite a few pre-defined
175[[https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes][themes]] 182[themes](https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes)
176available with bash-it. 183available with bash-it.
177 184
178To list all themes: 185To list all themes:
179 186
180#+begin_src sh 187```sh
181ls ~/.bash_it/themes/ 188ls ~/.bash_it/themes/
182#+end_src 189```
183 190
184To use a new theme, you'll need to edit =.bashrc= and alter the 191To use a new theme, you\'ll need to edit `.bashrc` and alter
185=BASH_IT_THEME= variable to your desired theme. For example, I am using 192the `BASH_IT_THEME` variable to your desired theme. For
186the =zork= theme. 193example, I am using the `zork` theme.
187 194
188#+begin_src sh 195```sh
189nano ~/.bashrc 196nano ~/.bashrc
190#+end_src 197```
191 198
192#+begin_src sh 199```sh
193export BASH_IT_THEME='zork' 200export BASH_IT_THEME='zork'
194#+end_src 201```
195 202
196Once you save your changes, you just need to exit your terminal and 203Once you save your changes, you just need to exit your terminal and
197create a new one in order to see your changes to the =.bashrc= file. You 204create a new one in order to see your changes to the
198can also =source= the file to see changes, but I recommend starting a 205`.bashrc` file. You can also `source` the file to
199completely new shell instead. 206see changes, but I recommend starting a completely new shell instead.
200 207
201*** ble.sh 208### ble.sh
202One big feature I was missing in Bash that both =zsh= and =fish= have is 209
203an autosuggestion feature. To explain: as you type, an autosuggestion 210One big feature I was missing in Bash that both `zsh` and
204feature in the shell will offer suggestions in a lighter font color 211`fish` have is an autosuggestion feature. To explain: as you
205beyond the characters already typed. Once you see the command you want, 212type, an autosuggestion feature in the shell will offer suggestions in a
206you can click the right arrow and have the shell auto-complete that line 213lighter font color beyond the characters already typed. Once you see the
207for you. 214command you want, you can click the right arrow and have the shell
208 215auto-complete that line for you.
209Luckily, the [[https://github.com/akinomyoga/ble.sh][Bash Line Editor]] 216
217Luckily, the [Bash Line Editor](https://github.com/akinomyoga/ble.sh)
210(ble.sh) exists! This program provides a wonderful autosuggestions 218(ble.sh) exists! This program provides a wonderful autosuggestions
211feature perfectly, among other features that I haven't tested yet. 219feature perfectly, among other features that I haven\'t tested yet.
212 220
213In order to install ble.sh, execute the following: 221In order to install ble.sh, execute the following:
214 222
215#+begin_src sh 223```sh
216git clone --recursive https://github.com/akinomyoga/ble.sh.git 224git clone --recursive https://github.com/akinomyoga/ble.sh.git
217make -C ble.sh install PREFIX=~/.local 225make -C ble.sh install PREFIX=~/.local
218echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc 226echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc
219#+end_src 227```
220 228
221Again, exit the terminal and open a new one in order to see the 229Again, exit the terminal and open a new one in order to see the
222newly-configured shell. 230newly-configured shell.
223 231
224* Restart the Session 232# Restart the Session
225Finally, as mentioned above, you'll need to restart the session to 233
234Finally, as mentioned above, you\'ll need to restart the session to
226ensure that your user is using bash by default. 235ensure that your user is using bash by default.
227 236
228You will also need to exit and re-open a shell (e.g., terminal or 237You will also need to exit and re-open a shell (e.g., terminal or
229terminal tab) any time you make changes to the =.bashrc= file. 238terminal tab) any time you make changes to the `.bashrc`
239file.
230 240
231#+begin_src sh 241```sh
232sudo reboot now 242sudo reboot now
233#+end_src 243```
content/blog/2022-08-31-privacy-com-changes.org → content/blog/2022-08-31-privacy-com-changes.md renamed +41 −36
@@ -1,25 +1,29 @@
1#+title: Concerning Changes on Privacy.com 1+++
2#+date: 2022-08-31 2date = 2022-08-31
3#+description: My thoughts on Privacy.com changing from a prepaid model to a credit charge model. 3title = "Concerning Changes on Privacy.com"
4#+filetags: :privacy: 4description = ""
5draft = false
6+++
7
8# Privacy.com Changes Their Terms
5 9
6* Privacy.com Changes Their Terms
7Recently, Privacy.com reached out to their customers regarding a change 10Recently, Privacy.com reached out to their customers regarding a change
8in their terms of use. Further, all customers are required to agree to 11in their terms of use. Further, all customers are required to agree to
9the changes in order to continue using their accounts. 12the changes in order to continue using their accounts.
10 13
11[[https://privacy.com/commercial-cardholder-agreement][You can view the 14[You can view the new cardholder agreement
12new cardholder agreement here]]. 15here](https://privacy.com/commercial-cardholder-agreement).
13 16
14When you log in, you'll be greeted with a pop-up window asking you to 17When you log in, you\'ll be greeted with a pop-up window asking you to
15review and agree to the new terms of use. You will also not be able to 18review and agree to the new terms of use. You will also not be able to
16open any new cards until the terms are agreed to. 19open any new cards until the terms are agreed to.
17 20
18** Changing from a "Prepaid Debit" Model to a "Charge Card" Model 21## Changing from a \"Prepaid Debit\" Model to a \"Charge Card\" Model
22
19The actual content of the changes is interesting. While the historical 23The actual content of the changes is interesting. While the historical
20model of using Privacy.com was akin to prepaid debit cards, the new 24model of using Privacy.com was akin to prepaid debit cards, the new
21model is very similar to a credit card (they use the term "charge 25model is very similar to a credit card (they use the term \"charge
22card"). 26card\").
23 27
24I have used Privacy.com for 1-2 years, and the process was always that 28I have used Privacy.com for 1-2 years, and the process was always that
25you would create a single-use or merchant-locked card. This card could 29you would create a single-use or merchant-locked card. This card could
@@ -31,17 +35,18 @@ The benefit this service provides with a premium account is masking the
31merchant names from your funding source. If you have a concern that your 35merchant names from your funding source. If you have a concern that your
32bank account uses merchant data from your account, you could direct all 36bank account uses merchant data from your account, you could direct all
33charges through Privacy.com and set the merchant as one of their pre-set 37charges through Privacy.com and set the merchant as one of their pre-set
34options, such as "Smiley's Corner Store" or "NSA Gift Shop." 38options, such as \"Smiley\'s Corner Store\" or \"NSA Gift Shop.\"
35 39
36The new model still works with a bank account as a funding source, but 40The new model still works with a bank account as a funding source, but
37the model is changed so that you get a "line of credit" set according to 41the model is changed so that you get a \"line of credit\" set according
38a 14-day billing cycle. It seems that Privacy.com will now allow charges 42to a 14-day billing cycle. It seems that Privacy.com will now allow
39to be incurred without being immediately paid. 43charges to be incurred without being immediately paid.
40 44
41** Daily Payments and Available Credit 45## Daily Payments and Available Credit
42Instead of paying as charges are incurred, you must make a "Daily 46
43Payment" and your "Available Credit" will be locked until you make that 47Instead of paying as charges are incurred, you must make a \"Daily
44payment. There are also "End of Billing Cycle Payments" that are 48Payment\" and your \"Available Credit\" will be locked until you make
49that payment. There are also \"End of Billing Cycle Payments\" that are
45assigned a due date. 50assigned a due date.
46 51
47Further, Privacy.com will decline charges that would cause you to exceed 52Further, Privacy.com will decline charges that would cause you to exceed
@@ -49,20 +54,18 @@ your Available Credit or Credit Limit.
49 54
50One particular interesting section states the following: 55One particular interesting section states the following:
51 56
52#+begin_quote 57> YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER
53YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER 58> OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT
54OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT 59> PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE
55PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE 60> WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL
56WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL 61> LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY
57LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT 62> AGREEMENT FOR MORE INFORMATION.
58FOR MORE INFORMATION.
59 63
60#+end_quote 64## Personal Information
61 65
62** Personal Information
63Now that Privacy.com is more of a financial institution, they are 66Now that Privacy.com is more of a financial institution, they are
64obligated to comply with the 67obligated to comply with the [know your
65[[https://en.wikipedia.org/wiki/Know_your_customer][know your customer]] 68customer](https://en.wikipedia.org/wiki/Know_your_customer)
66guidelines/laws. 69guidelines/laws.
67 70
68I did not proceed with the change to my Privacy.com account, but I have 71I did not proceed with the change to my Privacy.com account, but I have
@@ -71,11 +74,13 @@ to be submitted, such as SSN. I am not aware of all new personal
71information required or if the funding source is now required to only be 74information required or if the funding source is now required to only be
72a bank account. 75a bank account.
73 76
74** Fees 77## Fees
78
75Luckily, the fees section did not change much. The subscription fees for 79Luckily, the fees section did not change much. The subscription fees for
76a premium account are still the only fees. 80a premium account are still the only fees.
77 81
78* My Thoughts 82# My Thoughts
83
79Personally, I wiped my personal information from my account and then 84Personally, I wiped my personal information from my account and then
80permanently deleted it when I heard about these changes. I have no 85permanently deleted it when I heard about these changes. I have no
81interest in yet another method of credit lending offered by private 86interest in yet another method of credit lending offered by private
@@ -85,10 +90,10 @@ payments, I have no interest in incurring charges that will need to be
85paid back at a later date. I also have no interest in submitting 90paid back at a later date. I also have no interest in submitting
86personal information to Privacy.com. 91personal information to Privacy.com.
87 92
88This type of change toward a "buy it now, pay us later" model is 93This type of change toward a \"buy it now, pay us later\" model is
89concerning, and I will be watching Privacy.com to see if they further 94concerning, and I will be watching Privacy.com to see if they further
90their interests in the credit model as time goes on. 95their interests in the credit model as time goes on.
91 96
92Could we see them start charging interest, fees, etc.? I'm not sure, but 97Could we see them start charging interest, fees, etc.? I\'m not sure,
93this change does not inspire confidence in their mission as a 98but this change does not inspire confidence in their mission as a
94privacy-focused company. 99privacy-focused company.
content/blog/2022-09-17-serenity-os.md added +122
@@ -0,0 +1,122 @@
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)
11that I have seen pop up in my news feed a few times over the last few
12years, but I have never 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
16to my youth.
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
23> Unix-like core. It flatters with sincerity by stealing beautiful ideas
24> from various other systems.
25>
26> Roughly speaking, the goal is a marriage between the aesthetic of
27> late-1990s productivity software and the power-user accessibility of
28> late-2000s \*nix.
29>
30> This is a system by us, for us, based on the things we like.
31
32# Building
33
34Your first question may be \"Where\'s the iso?\" and the answer is...
35there are none. SerenityOS does not provide pre-built images for
36testing. You must build the images yourself. This seems intentionally to
37limit participation to only those who are truly interested enough to
38learn how to build the OS.
39
40## Clone
41
42In order to get started, you\'ll need to clone the source repository:
43
44```sh
45git clone https://github.com/SerenityOS/serenity && cd serenity
46```
47
48## Build
49
50Note that I followed the [Build
51Instructions](https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md)
52in the SerenityOS repository as of commit
53`660d2b53b1206e868d5470eee80b5e62d7e30da7`. Things may have
54changed since my installation, and you should double-check the
55instructions first.
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```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```
66
67Next, make sure you\'re inside the `serenity` directory
68created earlier during the git cloning process and process to build the
69toolchain:
70
71```sh
72Meta/serenity.sh rebuild-toolchain
73```
74
75Once the toolchain is built, you can Build and run the OS!
76
77```sh
78Meta/serenity.sh run
79```
80
81After this process is completed, the image should run automatically and
82launch.
83
84# Issues
85
86I played around in SerenityOS for an hour or two in order to see what I
87could do and had a lot of fun with it. The only issue I ran into was a
88lack of working internet. I didn\'t try very hard, but I could tell that
89the main network link wasn\'t connecting to my Fedora host properly.
90
91# Screenshots
92
93The initial launch of the image displays the SerenityOS desktop, with a
94simple terminal already launched:
95
96![SerenityOS
97Terminal](https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png)
98
99Here you can see the Fire application (literally just shows fire
100burning), a browser with the local Serenity Browser page loaded, and a
101text editor.
102
103![SerenityOS
104Fire](https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png)
105
106I also poked around the system utilities and found most tools you\'d
107expect to find within a standard desktop.
108
109![SerenityOS System
110Monitor](https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png)
111
112Lastly, I noted that the default desktop contains numerous pre-defined
113themes to choose from. This is a small piece, but it\'s actually
114wonderful to see desktop developers consider theming directly out of the
115box rather than using an addon-based mentality.
116
117![SerenityOS
118Themes](https://img.cleberg.net/blog/20220917-serenityos/themes.png)
119
120I didn\'t take a screenshot of the other pre-installed games, but I did
121spend nearly 30 minutes playing Solitaire before remembering that I was
122supposed to be writing a post about the OS.
content/blog/2022-09-17-serenity-os.org deleted −116
@@ -1,116 +0,0 @@
1#+title: Serenity OS: Testing Out a Unique System
2#+date: 2022-09-17
3#+description: A quick look at the Serenity operating system.
4#+filetags: :dev:
5
6* Overview
7[[https://serenityos.org][SerenityOS]] is a unique operating system (OS)
8that I have seen pop up in my news feed a few times over the last few
9years, but I have never had time to test it out until now.
10
11Testing out this system brought back fond memories of yellowed,
12modem-screeching, 100-pound computers that brought so many fond memories
13to my youth.
14
15Per their website:
16
17#+begin_quote
18A graphical Unix-like operating system for desktop computers!
19
20SerenityOS is a love letter to '90s user interfaces with a custom
21Unix-like core. It flatters with sincerity by stealing beautiful ideas
22from various other systems.
23
24Roughly speaking, the goal is a marriage between the aesthetic of
25late-1990s productivity software and the power-user accessibility of
26late-2000s *nix.
27
28This is a system by us, for us, based on the things we like.
29
30#+end_quote
31
32* Building
33Your first question may be "Where's the iso?" and the answer is... there
34are none. SerenityOS does not provide pre-built images for testing. You
35must build the images yourself. This seems intentionally to limit
36participation to only those who are truly interested enough to learn how
37to build the OS.
38
39** Clone
40In order to get started, you'll need to clone the source repository:
41
42#+begin_src sh
43git clone https://github.com/SerenityOS/serenity && cd serenity
44#+end_src
45
46** Build
47Note that I followed the
48[[https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md][Build
49Instructions]] in the SerenityOS repository as of commit
50=660d2b53b1206e868d5470eee80b5e62d7e30da7=. Things may have changed
51since my installation, and you should double-check the instructions
52first.
53
54Regardless, I want to repeat my steps here to illustrate any errors or
55differing commands 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
58to build the OS images:
59
60#+begin_src 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#+end_src
63
64Next, make sure you're inside the =serenity= directory created earlier
65during the git cloning process and process to build the toolchain:
66
67#+begin_src sh
68Meta/serenity.sh rebuild-toolchain
69#+end_src
70
71Once the toolchain is built, you can Build and run the OS!
72
73#+begin_src sh
74Meta/serenity.sh run
75#+end_src
76
77After this process is completed, the image should run automatically and
78launch.
79
80* Issues
81I played around in SerenityOS for an hour or two in order to see what I
82could do and had a lot of fun with it. The only issue I ran into was a
83lack of working internet. I didn't try very hard, but I could tell that
84the main network link wasn't connecting to my Fedora host properly.
85
86* Screenshots
87The initial launch of the image displays the SerenityOS desktop, with a
88simple terminal already launched:
89
90#+caption: SerenityOS Terminal
91[[https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png]]
92
93Here you can see the Fire application (literally just shows fire
94burning), a browser with the local Serenity Browser page loaded, and a
95text editor.
96
97#+caption: SerenityOS Fire
98[[https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png]]
99
100I also poked around the system utilities and found most tools you'd
101expect to find within a standard desktop.
102
103#+caption: SerenityOS System Monitor
104[[https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png]]
105
106Lastly, I noted that the default desktop contains numerous pre-defined
107themes to choose from. This is a small piece, but it's actually
108wonderful to see desktop developers consider theming directly out of the
109box rather than using an addon-based mentality.
110
111#+caption: SerenityOS Themes
112[[https://img.cleberg.net/blog/20220917-serenityos/themes.png]]
113
114I didn't take a screenshot of the other pre-installed games, but I did
115spend nearly 30 minutes playing Solitaire before remembering that I was
116supposed to be writing a post about the OS.
content/blog/2022-09-21-graphene-os.md added +168
@@ -0,0 +1,168 @@
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
11purchased a Pixel 6 Pro in order to test and use \[GrapheneOS\]
12(<https://grapheneos.org>).
13
14The installation process was rather quick once you have the tools and
15files you need. Overall, it can be done in just a few minutes.
16
17# Gathering Tools & Files
18
19## Android Tools
20
21First, in order to interact with the device, we will need the [Android
22platform
23tools](https://developer.android.com/studio/releases/platform-tools.html).
24Find the Linux download and save the ZIP folder to your preferred
25location.
26
27Once we\'ve downloaded the files, we will need to unzip them, enter the
28directory, and move the necessary executables to a central location,
29such as `/usr/bin/`. For this installation, we only need the
30`fastboot` and `adb` executables.
31
32```sh
33cd ~/Downloads
34```
35
36```sh
37unzip platform-tools_r33.0.3-linux.zip
38cd platform-tools
39sudo mv fastboot /usr/bin/
40sudo mv adb /usr/bin
41```
42
43## GrapheneOS Files
44
45Next, we need the [GrapheneOS files](https://grapheneos.org/releases)
46for our device and model. For example, the Pixel 6 Pro is codenamed
47`raven` on the release page.
48
49Once we have the links, let\'s download them to our working directory:
50
51```sh
52curl -O https://releases.grapheneos.org/factory.pub
53curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
54curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
55```
56
571. Validate Integrity
58
59 In order to validate the integrity of the downloaded files, we will
60 need the `signify` package and Graphene\'s
61 `factory.pub` file.
62
63 ```sh
64 sudo dnf install signify
65 ```
66
67 ```sh
68 curl -O https://releases.grapheneos.org/factory.pub
69 ```
70
71 Then we can validate the files and ensure that no data was corrupted
72 or modified before it was saved to our device.
73
74 ```sh
75 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
76 ```
77
782. Unzip Files
79
80 Once the files are verified, we can unzip the Graphene image and
81 enter the directory:
82
83 ```sh
84 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
85 ```
86
87# Installation Process
88
89## Enable Developer Debugging & OEM Unlock
90
91Before we can actually flash anything to the phone, we will need to
92enable OEM Unlocking, as well as either USB Debugging or Wireless
93Debugging, depending on which method we will be using.
94
95To start, enable developer mode by going to `Settings` \>
96`About` and tapping `Build Number` seven (7)
97times. You may need to enter your PIN to enable this mode.
98
99Once developer mode is enabled, go to `Settings` \>
100`System` \> `Devloper Options` and enable OEM
101Unlocking, as well as USB or Wireless Debugging. In my case, I chose USB
102Debugging and performed all actions via USB cable.
103
104Once these options are enabled, plug the phone into the computer and
105execute the following command:
106
107```sh
108adb devices
109```
110
111If an unauthorized error occurs, make sure the USB mode on the phone is
112changed from charging to something like \"File Transfer\" or \"PTP.\"
113You can find the USB mode in the notification tray.
114
115## Reboot Device
116
117Once we have found the device via `adb`, we can either boot
118into the bootloader interface by holding the volume down button while
119the phone reboots or by executing the following command:
120
121```sh
122adb reboot bootloader
123```
124
125## Unlock the Bootloader
126
127The phone will reboot and load the bootloader screen upon startup. At
128this point, we are ready to start the actual flashing of GrapheneOS onto
129the device.
130
131**NOTE**: In my situation, I needed to use `sudo` with every
132`fastboot` command, but not with `adb` commands. I
133am not sure if this is standard or a Fedora quirk, but I\'m documenting
134my commands verbatim in this post.
135
136First, we start by unlocking the bootloader so that we can load other
137ROMs:
138
139```sh
140sudo fastboot flashing unlock
141```
142
143## Flashing Factory Images
144
145Once the phone is unlocked, we can flash it with the
146`flash-all.sh` script found inside the
147`raven-factory-2022091400` folder we entered earlier:
148
149```sh
150sudo ./flash-all.sh
151```
152
153This process should take a few minutes and will print informational
154messages as things progress. Avoid doing anything on the phone while
155this process is operating.
156
157## Lock the Bootloader
158
159If everything was successful, the phone should reboot a few times and
160finally land back on the bootloader screen. At this point, we can
161re-lock the bootloader to enable full verified boot and protect the
162device from unwanted flashing or erasure of data.
163
164```sh
165sudo fastboot flashing lock
166```
167
168Once done, the device will be wiped and ready for a fresh set-up!
content/blog/2022-09-21-graphene-os.org deleted −154
@@ -1,154 +0,0 @@
1#+title: Installing Graphene OS on the Pixel 6 Pro
2#+date: 2022-09-21
3#+description: A retrospective on the successful command-line installation of Graphene OS on a Pixel 6 Pro.
4#+filetags: :privacy:
5
6* Introduction
7After using iOS for a couple of years, I finally took the plunge and
8purchased a Pixel 6 Pro in order to test and use [GrapheneOS]
9([[https://grapheneos.org]]).
10
11The installation process was rather quick once you have the tools and
12files you need. Overall, it can be done in just a few minutes.
13
14* Gathering Tools & Files
15** Android Tools
16First, in order to interact with the device, we will need the
17[[https://developer.android.com/studio/releases/platform-tools.html][Android
18platform tools]]. Find the Linux download and save the ZIP folder to
19your preferred location.
20
21Once we've downloaded the files, we will need to unzip them, enter the
22directory, and move the necessary executables to a central location,
23such as =/usr/bin/=. For this installation, we only need the =fastboot=
24and =adb= executables.
25
26#+begin_src sh
27cd ~/Downloads
28#+end_src
29
30#+begin_src sh
31unzip platform-tools_r33.0.3-linux.zip
32cd platform-tools
33sudo mv fastboot /usr/bin/
34sudo mv adb /usr/bin
35#+end_src
36
37** GrapheneOS Files
38Next, we need the [[https://grapheneos.org/releases][GrapheneOS files]]
39for our device and model. For example, the Pixel 6 Pro is codenamed
40=raven= on the release page.
41
42Once we have the links, let's download them to our working directory:
43
44#+begin_src sh
45curl -O https://releases.grapheneos.org/factory.pub
46curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
47curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
48#+end_src
49
501. Validate Integrity
51
52 In order to validate the integrity of the downloaded files, we will
53 need the =signify= package and Graphene's =factory.pub= file.
54
55 #+begin_src sh
56 sudo dnf install signify
57 #+end_src
58
59 #+begin_src sh
60 curl -O https://releases.grapheneos.org/factory.pub
61 #+end_src
62
63 Then we can validate the files and ensure that no data was corrupted
64 or modified before it was saved to our device.
65
66 #+begin_src sh
67 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
68 #+end_src
69
702. Unzip Files
71
72 Once the files are verified, we can unzip the Graphene image and
73 enter the directory:
74
75 #+begin_src sh
76 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
77 #+end_src
78
79* Installation Process
80** Enable Developer Debugging & OEM Unlock
81Before we can actually flash anything to the phone, we will need to
82enable OEM Unlocking, as well as either USB Debugging or Wireless
83Debugging, depending on which method we will be using.
84
85To start, enable developer mode by going to =Settings= > =About= and
86tapping =Build Number= seven (7) times. You may need to enter your PIN
87to enable this mode.
88
89Once developer mode is enabled, go to =Settings= > =System= >
90=Devloper Options= and enable OEM Unlocking, as well as USB or Wireless
91Debugging. In my case, I chose USB Debugging and performed all actions
92via USB cable.
93
94Once these options are enabled, plug the phone into the computer and
95execute the following command:
96
97#+begin_src sh
98adb devices
99#+end_src
100
101If an unauthorized error occurs, make sure the USB mode on the phone is
102changed from charging to something like "File Transfer" or "PTP." You
103can find the USB mode in the notification tray.
104
105** Reboot Device
106Once we have found the device via =adb=, we can either boot into the
107bootloader interface by holding the volume down button while the phone
108reboots or by executing the following command:
109
110#+begin_src sh
111adb reboot bootloader
112#+end_src
113
114** Unlock the Bootloader
115The phone will reboot and load the bootloader screen upon startup. At
116this point, we are ready to start the actual flashing of GrapheneOS onto
117the device.
118
119*NOTE*: In my situation, I needed to use =sudo= with every =fastboot=
120command, but not with =adb= commands. I am not sure if this is standard
121or a Fedora quirk, but I'm documenting my commands verbatim in this
122post.
123
124First, we start by unlocking the bootloader so that we can load other
125ROMs:
126
127#+begin_src sh
128sudo fastboot flashing unlock
129#+end_src
130
131** Flashing Factory Images
132Once the phone is unlocked, we can flash it with the =flash-all.sh=
133script found inside the =raven-factory-2022091400= folder we entered
134earlier:
135
136#+begin_src sh
137sudo ./flash-all.sh
138#+end_src
139
140This process should take a few minutes and will print informational
141messages as things progress. Avoid doing anything on the phone while
142this process is operating.
143
144** Lock the Bootloader
145If everything was successful, the phone should reboot a few times and
146finally land back on the bootloader screen. At this point, we can
147re-lock the bootloader to enable full verified boot and protect the
148device from unwanted flashing or erasure of data.
149
150#+begin_src sh
151sudo fastboot flashing lock
152#+end_src
153
154Once done, the device will be wiped and ready for a fresh set-up!
content/blog/2022-10-04-mtp-linux.org → content/blog/2022-10-04-mtp-linux.md renamed +40 −32
@@ -1,73 +1,81 @@
1#+title: How to Mount an MTP Mobile Device on Fedora Linux 1+++
2#+date: 2022-10-04 2date = 2022-10-04
3#+description: Learn how to mount an MTP mobile device on Fedora Linux. 3title = "How to Mount an MTP Mobile Device on Fedora Linux"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
5 7
6I recently ran into trouble attempting to mount my GrapheneOS phone to 8I recently ran into trouble attempting to mount my GrapheneOS phone to
7my laptop running Fedora Linux via the 9my laptop running Fedora Linux via the [Media Transfer
8[[https://en.wikipedia.org/wiki/Media_transfer_protocol][Media Transfer 10Protocol](https://en.wikipedia.org/wiki/Media_transfer_protocol) (MTP)
9Protocol]] (MTP) and discovered a simple and effective solution. 11and discovered a simple and effective solution.
12
13# Use a USB 3.0 Port
10 14
11* Use a USB 3.0 Port
12First, ensure that the device was plugged in to the laptop through a USB 15First, ensure that the device was plugged in to the laptop through a USB
133.0 port, if possible. From a brief glance online, it seems that USB 2.0 163.0 port, if possible. From a brief glance online, it seems that USB 2.0
14ports may cause issues with dropped connections over MTP. This is purely 17ports may cause issues with dropped connections over MTP. This is purely
15anecdotal since I don't have any evidence to link showing that USB 2.0 18anecdotal since I don\'t have any evidence to link showing that USB 2.0
16causes issues, but I can confirm that switching to a USB 3.0 port seemed 19causes issues, but I can confirm that switching to a USB 3.0 port seemed
17to cut out most of my issues. 20to cut out most of my issues.
18 21
19* Switch USB Preferences to MTP 22# Switch USB Preferences to MTP
20Secondly, you need to ensure that the phone's USB preferences/mode is 23
24Secondly, you need to ensure that the phone\'s USB preferences/mode is
21changed to MTP or File Transfer once the phone is plugged in. Other 25changed to MTP or File Transfer once the phone is plugged in. Other
22modes will not allow you to access the phone's file system. 26modes will not allow you to access the phone\'s file system.
27
28# Install `jmtpfs`
23 29
24* Install =jmtpfs= 30Next, I used the `jmtpfs` package to mount my phone to my
25Next, I used the =jmtpfs= package to mount my phone to my laptop. There 31laptop. There are other packages that exist, but this one worked
26are other packages that exist, but this one worked perfectly for me. On 32perfectly for me. On Fedora Linux, you can install it like this:
27Fedora Linux, you can install it like this:
28 33
29#+begin_src sh 34```sh
30sudo dnf install jmtpfs -y 35sudo dnf install jmtpfs -y
31#+end_src 36```
37
38# Create a Mount Point
32 39
33* Create a Mount Point
34Once you have the package installed, you just need to create a folder 40Once you have the package installed, you just need to create a folder
35for the device to use as a mount point. In my case, I used =/mnt/pixel=: 41for the device to use as a mount point. In my case, I used
42`/mnt/pixel`:
36 43
37#+begin_src sh 44```sh
38sudo mkdir /mnt/pixel 45sudo mkdir /mnt/pixel
39sudo chown -R $USER:$USER /mnt/pixel 46sudo chown -R $USER:$USER /mnt/pixel
40#+end_src 47```
48
49# Mount & Access the Phone\'s File System
41 50
42* Mount & Access the Phone's File System
43Finally, plug-in and mount the device, and you should be able to see all 51Finally, plug-in and mount the device, and you should be able to see all
44storage (internal and external) inside your new folder! 52storage (internal and external) inside your new folder!
45 53
46#+begin_src sh 54```sh
47jmtpfs /mnt/pixel 55jmtpfs /mnt/pixel
48#+end_src 56```
49 57
50The output should look something like this: 58The output should look something like this:
51 59
52#+begin_src sh 60```sh
53Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP). 61Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP).
54Android device detected, assigning default bug flags 62Android device detected, assigning default bug flags
55#+end_src 63```
56 64
57Now you are mounted and can do anything you'd like with the device's 65Now you are mounted and can do anything you\'d like with the device\'s
58files: 66files:
59 67
60#+begin_src sh 68```sh
61cd /mnt/pixel 69cd /mnt/pixel
62ls -lha 70ls -lha
63#+end_src 71```
64 72
65From here, you will be able to see any internal or external storage 73From here, you will be able to see any internal or external storage
66available on the device: 74available on the device:
67 75
68#+begin_src sh 76```sh
69total 0 77total 0
70drwxr-xr-x. 3 user user 0 Jan 1 1970 . 78drwxr-xr-x. 3 user user 0 Jan 1 1970 .
71drwxr-xr-x. 1 root root 10 Oct 4 13:29 .. 79drwxr-xr-x. 1 root root 10 Oct 4 13:29 ..
72drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage' 80drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage'
73#+end_src 81```
content/blog/2022-10-04-syncthing.md added +179
@@ -0,0 +1,179 @@
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
11while, you\'ve undoubtedly run into someone suggesting
12[Syncthing](https://syncthing.net) as an option. However, it is an
13unusual alternative for those users out there who are used to having a
14centralized cloud server that serves as the \"controller\" of the data
15and interacts with clients on devices to fetch files.
16
17This post is a walkthrough of the Syncthing software, how I set up my
18personal storage, and some pros and cons of using the software.
19
20# Installing Syncthing
21
22To install Syncthing, visit the
23[Downloads](https://syncthing.net/downloads/) page or install via your
24device\'s package manager.
25
26## Server & Desktop
27
28You can install Syncthing on servers and desktops via the Downloads page
29linked above or via the command-line.
30
31For Debian-based distros:
32
33```sh
34sudo apt install syncthing
35```
36
37For Fedora-based distros:
38
39```sh
40sudo dnf install syncthing
41```
42
43## Mobile
44
45Syncthing for Android is available on
46[F-Droid](https://f-droid.org/packages/com.nutomic.syncthingandroid/)
47and [Google
48Play](https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid).
49Syncthing does not have an official iOS client, but there is a
50third-party client called [Möbius
51Sync](https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216).
52
53# How Does Syncthing Work?
54
55To start, I wanted to include the main marketing blurb from their
56website:
57
58> Syncthing is a continuous file synchronization program. It
59> synchronizes files between two or more computers in real time, safely
60> protected from prying eyes. Your data is your data alone, and you
61> deserve to choose where it is stored, whether it is shared with some
62> third party, and how it\'s transmitted over the internet.
63
64Let\'s break this apart and add in some other details to help explain
65what exactly Syncthing does in order to sync files between devices.
66
67## Local Syncthing Server(s)
68
69Syncthing syncs files between multiple devices by creating a local
70server on each device. These local servers handle a few different
71things, such as watching files and directories for changes, hosting an
72administrative GUI website, and authenticating with connected devices.
73
74You can also start, stop, and restart the Syncthing server via the
75command-line or web dashboard. If you\'re running Syncthing on a device
76with `systemd`, you can use the following commands:
77
78```sh
79sudo systemctl start syncthing@username.service
80sudo systemctl restart syncthing@username.service
81sudo systemctl stop syncthing@username.service
82```
83
84## Syncthing Dashboard
85
86This biggest part of Syncthing is the admin GUI website that runs on
87each device (note that mobile devices will use the Syncthing app rather
88than the web GUI). The admin GUI is available through the web browser on
89the local device that is running Syncthing - simply go to
90`http://localhost:8384` or
91`http://127.0.0.1:8384`. This web page is the place where you
92will change settings, add/modify synced files, and add/modify connected
93devices.
94
95Here\'s an example web GUI dashboard:
96
97![Syncthing
98Dashboard](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
109code for 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
134complete, Syncthing validated that all files were there and not missing,
135and it did 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
143thought about my experiences with Nextcloud, WebDAV, proprietary
144services (Google Drive, iCloud, etc.), and privacy-focused cloud
145solutions (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
152 leave 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
166 or re-connecting after sleeping.
167- Multiple personal devices are required and require the user to own
168 or rent them as no third-party servers are involved in the storage
169 of data.
170
171Overall, I\'ve had a great experience with Syncthing so far. I\'ve had
172no data 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 [source code](https://github.com/syncthing) and continue
178development/support if the Syncthing Foundation decides to stop
179developing the software or sells the business.
content/blog/2022-10-04-syncthing.org deleted −169
@@ -1,169 +0,0 @@
1#+title: Syncthing: A Minimal Self-Hosted Cloud Storage Solution
2#+date: 2022-10-04
3#+description: An overview of how you can use Syncthing as a minimal alternative to cloud storage.
4#+filetags: :selfhosting:
5
6* An Overview of Syncthing
7If you've been looking around the self-hosted cloud storage space for a
8while, you've undoubtedly run into someone suggesting
9[[https://syncthing.net][Syncthing]] as an option. However, it is an
10unusual alternative for those users out there who are used to having a
11centralized cloud server that serves as the "controller" of the data and
12interacts with clients on devices to fetch files.
13
14This post is a walkthrough of the Syncthing software, how I set up my
15personal storage, and some pros and cons of using the software.
16
17* Installing Syncthing
18To install Syncthing, visit the
19[[https://syncthing.net/downloads/][Downloads]] page or install via your
20device's package manager.
21
22** Server & Desktop
23You can install Syncthing on servers and desktops via the Downloads page
24linked above or via the command-line.
25
26For Debian-based distros:
27
28#+begin_src sh
29sudo apt install syncthing
30#+end_src
31
32For Fedora-based distros:
33
34#+begin_src sh
35sudo dnf install syncthing
36#+end_src
37
38** Mobile
39Syncthing for Android is available on
40[[https://f-droid.org/packages/com.nutomic.syncthingandroid/][F-Droid]]
41and
42[[https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid][Google
43Play]]. Syncthing does not have an official iOS client, but there is a
44third-party client called
45[[https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216][Möbius
46Sync]].
47
48* How Does Syncthing Work?
49To start, I wanted to include the main marketing blurb from their
50website:
51
52#+begin_quote
53Syncthing is a continuous file synchronization program. It synchronizes
54files between two or more computers in real time, safely protected from
55prying eyes. Your data is your data alone, and you deserve to choose
56where it is stored, whether it is shared with some third party, and how
57it's transmitted over the internet.
58#+end_quote
59
60Let's break this apart and add in some other details to help explain
61what exactly Syncthing does in order to sync files between devices.
62
63** Local Syncthing Server(s)
64Syncthing syncs files between multiple devices by creating a local
65server on each device. These local servers handle a few different
66things, such as watching files and directories for changes, hosting an
67administrative GUI website, and authenticating with connected devices.
68
69You can also start, stop, and restart the Syncthing server via the
70command-line or web dashboard. If you're running Syncthing on a device
71with =systemd=, you can use the following commands:
72
73#+begin_src sh
74sudo systemctl start syncthing@username.service
75sudo systemctl restart syncthing@username.service
76sudo systemctl stop syncthing@username.service
77#+end_src
78
79** Syncthing Dashboard
80This biggest part of Syncthing is the admin GUI website that runs on
81each device (note that mobile devices will use the Syncthing app rather
82than the web GUI). The admin GUI is available through the web browser on
83the local device that is running Syncthing - simply go to
84=http://localhost:8384= or =http://127.0.0.1:8384=. This web page is the
85place where you will change settings, add/modify synced files, and
86add/modify connected devices.
87
88Here's an example web GUI dashboard:
89
90#+caption: Syncthing Dashboard
91[[https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png]]
92
93** Remote Devices
94A cloud storage solution wouldn't be very useful if you aren't able to
95share data among various devices. Syncthing does this by sharing Device
96IDs to connect servers, and then by manually sharing Folders with
97devices that have been connected.
98
99For instance, if you have a laptop running Syncthing and then install
100the Syncthing mobile app on a phone, you could scan the laptop's QR code
101for Device ID and then accept the authentication on the laptop's
102dashboard. Next, you can use either device to select a folder for
103sharing and dictating which device should send, receive, or both.
104
105When you connect devices, you can set one device as an "Introducer,"
106which can add devices from the introducer to the device list, for
107mutually shared folders. You can also configure Auto Accept,
108compression, rate limits, and more settings per device.
109
110* My Personal Cloud Storage Set-up
111Personally, I use a model similar to a traditional cloud storage
112service. I have a "centralized" server running 24/7 that acts as an
113Introducer for my Syncthing network. I think of this as my main storage
114and all other devices as tertiary client devices. I will likely add
115additional servers as backups as time goes on so that I don't have to
116rely on my laptop or phone as the only backups.
117
118Currently, I have one desktop and one mobile device connected to the
119network, both running intermittently as they are not powered-on 24/7.
120
121The initial set-up of the software was easy enough, but data transfer
122rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged
123my laptop into the ethernet network that my server is on and manually
124copied my folders over to the server with =scp=. Once complete,
125Syncthing validated that all files were there and not missing, and it
126did not need to transfer any data through the WAN.
127
128As slow as the transfer was going, this probably saved me a few days of
129waiting for my ~100GB sync.
130
131* Pros & Cons
132I've put together a short list of pros and cons for Syncthing. I thought
133about my experiences with Nextcloud, WebDAV, proprietary services
134(Google Drive, iCloud, etc.), and privacy-focused cloud solutions
135(pCloud, Tresorit, etc.).
136
137*Pros:*
138
139- I've faced no data loss at all through my two-month trial run.
140- No third-parties store your data on their servers.
141- You have full control over your data and can take your data and leave
142 at any time.
143- It's possible to encrypt client-side easily with software like
144 Cryptomator.
145- No proprietary clients or mounted volumes, just plain files and
146 folders.
147
148*Cons:*
149
150- The learning curve is steeper than traditional cloud services and is
151 focused on a technical audience.
152- If a device needs to modify files in a Folder, the devices will need
153 to sync ALL files from the folder, which may be large. To avoid size
154 restraints, split large folders into smaller folders for syncing.
155- Syncing can be slow due to the clients/servers initially connecting or
156 re-connecting after sleeping.
157- Multiple personal devices are required and require the user to own or
158 rent them as no third-party servers are involved in the storage of
159 data.
160
161Overall, I've had a great experience with Syncthing so far. I've had no
162data loss, syncing has been quick and easy when changes are made to
163files, device connections are reliable, and I love the freedom of
164controlling the clients and servers as I choose.
165
166Not to mention that I appreciate that I - or someone else - could pull
167the Syncthing [[https://github.com/syncthing][source code]] and continue
168development/support if the Syncthing Foundation decides to stop
169developing the software or sells the business.
content/blog/2022-10-22-alpine-linux.md added +281
@@ -0,0 +1,281 @@
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
11musl libc and busybox. It uses ash as the default shell, OpenRC as the
12init system, and apk as the package manager. According to their website,
13an Alpine container \"requires no more than 8 MB and a minimal
14installation to disk requires around 130 MB of storage.\" An actual bare
15metal machine is recommended to have 100 MB of RAM and 0-700 MB of
16storage space.
17
18Historically, I\'ve used Ubuntu\'s minimal installation image as my
19server OS for the last five years. Ubuntu worked well and helped as my
20original server contained an nVidia GPU and no onboard graphics, so
21quite a few distros won\'t boot or install without a lot of tinkering.
22
23Alpine has given me a huge increase in performance across my Docker apps
24and Nginx websites. CPU load for the new server I\'m using to test
25Alpine hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100
26CPU @ 3.70GHz.
27
28The only services I haven\'t moved over to Alpine are Plex Media Server
29and Syncthing, which may increase CPU load quite a bit depending on how
30many streams are running.
31
32## Installation
33
34In terms of installation, Alpine has an incredibly useful
35[wiki](https://wiki.alpinelinux.org/wiki/Installation) that will guide a
36user throughout the installation and post-installation processes, as
37well as various other articles and guides.
38
39To install Alpine, find an appropriate [image to
40download](https://alpinelinux.org/downloads/) and flash it to a USB
41using software such as Rufus or Etcher. I opted to use the Standard
42image for my x86~64~ architecture.
43
44Once the USB is ready, plug it into the machine and reboot. Note that
45you may have to use a key such as `Esc` or `F1-12`
46to access the boot menu. The Alpine Linux terminal will load quickly and
47for a login.
48
49To log in to the installation image, use the `root` account;
50there is no password. Once logged-in, execute the setup command:
51
52```sh
53setup-alpine
54```
55
56The setup script will ask a series of questions to configure the system.
57Be sure to answer carefully or else you may have to re-configure the
58system after boot.
59
60- Keyboard Layout (Local keyboard language and usage mode, e.g., us
61 and variant of us-nodeadkeys.)
62- Hostname (The name for the computer.)
63- Network (For example, automatic IP address discovery with the
64 \"DHCP\" protocol.)
65- DNS Servers (Domain Name Servers to query. For privacy reasons, it
66 is NOT recommended to route every local request to servers like
67 Google\'s 8.8.8.8 .)
68- Timezone
69- Proxy (Proxy server to use for accessing the web. Use \"none\" for
70 direct connections to the internet.)
71- Mirror (From where to download packages. Choose the organization you
72 trust giving your usage patterns to.)
73- SSH (Secure SHell remote access server. \"Openssh\" is part of the
74 default install image. Use \"none\" to disable remote login, e.g. on
75 laptops.)
76- NTP (Network Time Protocol client used for keeping the system clock
77 in sync with a time-server. Package \"chrony\" is part of the
78 default install image.)
79- Disk Mode (Select between diskless (disk=\"none\"), \"data\" or
80 \"sys\", as described above.)
81
82Once the setup script is finished, be sure to reboot the machine and
83remove the USB device.
84
85```sh
86reboot
87```
88
89## Post-Installation
90
91There are many things you can do once your Alpine Linux system is up and
92running, and it largely depends on what you\'ll use the machine for.
93I\'m going to walk through my personal post-installation setup for my
94web server.
95
961. Upgrade the System
97
98 First, login as `root` in order to update and upgrade the
99 system:
100
101 ```sh
102 apk -U upgrade
103 ```
104
1052. Adding a User
106
107 I needed to add a user so that I don\'t need to log in as root. Note
108 that if you\'re used to using the `sudo` command, you
109 will now need to use the `doas` command on Alpine Linux.
110
111 ```sh
112 apk add doas
113 adduser <username>
114 adduser <username> wheel
115 ```
116
117 You can now log out and log back in using the newly-created user:
118
119 ```sh
120 exit
121 ```
122
1233. Enable Community Packages
124
125 In order to install more common packages that aren\'t found in the
126 `main` repository, you will need to enable the
127 `community` repository:
128
129 ```sh
130 doas nano /etc/apk/repositories
131 ```
132
133 Uncomment the community line for whichever version of Alpine you\'re
134 running:
135
136 ```sh
137 /media/usb/apks
138 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
139 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
140 #http://dl-cdn.alpinelinux.org/alpine/edge/main
141 #http://dl-cdn.alpinelinux.org/alpine/edge/community
142 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
143 ```
144
1454. Install Required Packages
146
147 Now that the community packages are available, you can install any
148 packages you need. In my case, I installed the web server packages I
149 need for my services:
150
151 ```sh
152 doas apk add nano nginx docker docker-compose ufw
153 ```
154
1555. SSH
156
157 If you didn\'t install OpenSSH as part of the installation, you can
158 do so now:
159
160 ```sh
161 doas apk add openssh
162 ```
163
164 Next, either create a new key or copy your SSH key to the server
165 from your current machines:
166
167 ```sh
168 # Create a new key
169 ssh-keygen
170 ```
171
172 If you need to copy an existing SSH key from a current machine:
173
174 ```sh
175 # Copy key from existing machines
176 ssh-copy-id <username>@<ip_address>
177 ```
178
1796. Firewall
180
181 Lastly, I installed `ufw` above as my firewall. To set
182 up, default to deny incoming and allow outgoing connections. Then
183 selectively allow other ports or apps as needed.
184
185 ```sh
186 doas ufw default deny incoming
187 doas ufw default allow outgoing
188 doas ufw allow SSH
189 doas ufw allow "WWW Full"
190 doas ufw allow 9418 # Git server port
191 ```
192
1937. Change Hostname
194
195 If you don\'t like the hostname set during installation, you just
196 need to edit two files. First, edit the simple hostname file:
197
198 ```sh
199 doas nano /etc/hostname
200 ```
201
202 ```sh
203 <hostname>
204 ```
205
206 Next, edit the `hosts` file:
207
208 ```sh
209 doas nano /etc/hosts
210 ```
211
212 ```sh
213 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
214 ::1 <hostname> <hostname>.local
215 ```
216
217# Nginx Web Server
218
219To set up my web server, I simply created the `www` user and
220created the necessary files.
221
222```sh
223doas adduser -D -g 'www' www
224mkdir /www
225doas mkdir /www
226doas chown -R www:www /var/lib/nginx/
227doas chown -R www:www /www
228```
229
230If you\'re running a simple webroot, you can alter the main
231`nginx.conf` file. Otherwise, you can drop configuration
232files in the following directory. You don\'t need to enable or symlink
233the configuration file like you do in other systems.
234
235```sh
236doas nano /etc/nginx/http.d/example_website.conf
237```
238
239Once the configuration is set and pointed at the `/www`
240directory to serve files, enable the Nginx service:
241
242```sh
243# Note that 'default' must be included or Nginx will not start on boot
244doas rc-update add nginx default
245```
246
247# Docker Containers
248
249Docker works exactly the same as other systems. Either execute a
250`docker run` command or create a
251`docker-compose.yml` file and do
252`docker-compose up -d`.
253
254# Git Server
255
256I went in-depth on how to self-host a git server in another post:
257[Self-Hosting a Personal Git Server](../git-server/).
258
259However, there are a few differences with Alpine. First note that in
260order to change the `git` user\'s shell, you must do a few
261things a little different:
262
263```sh
264doas apk add libuser
265doas touch /etc/login.defs
266doas mkdir /etc/default
267doas touch /etc/default/useradd
268doas lchsh git
269```
270
271# Thoughts on Alpine
272
273So far, I love Alpine Linux. I have no complaints about anything at this
274point, but I\'m not completely finished with the migration yet. Once
275I\'m able to upgrade my hardware to a rack-mounted server, I will
276migrate Plex and Syncthing over to Alpine as well - possibly putting
277Plex into a container or VM.
278
279The performance is stellar, the `apk` package manager is
280seamless, and system administration tasks are effortless. My only regret
281is that I didn\'t install Alpine sooner.
content/blog/2022-10-22-alpine-linux.org deleted −269
@@ -1,269 +0,0 @@
1#+title: Alpine Linux: My New Server OS
2#+date: 2022-10-22
3#+description: A retrospective on installing and configuring Alpine Linux as my new server operating system.
4#+filetags: :linux:
5
6* Alpine Linux
7[[https://alpinelinux.org][Alpine Linux]] is a very small distro, built
8on musl libc and busybox. It uses ash as the default shell, OpenRC as
9the init system, and apk as the package manager. According to their
10website, an Alpine container "requires no more than 8 MB and a minimal
11installation to disk requires around 130 MB of storage." An actual bare
12metal machine is recommended to have 100 MB of RAM and 0-700 MB of
13storage space.
14
15Historically, I've used Ubuntu's minimal installation image as my server
16OS for the last five years. Ubuntu worked well and helped as my original
17server contained an nVidia GPU and no onboard graphics, so quite a few
18distros won't boot or install without a lot of tinkering.
19
20Alpine has given me a huge increase in performance across my Docker apps
21and Nginx websites. CPU load for the new server I'm using to test Alpine
22hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @
233.70GHz.
24
25The only services I haven't moved over to Alpine are Plex Media Server
26and Syncthing, which may increase CPU load quite a bit depending on how
27many streams are running.
28
29** Installation
30In terms of installation, Alpine has an incredibly useful
31[[https://wiki.alpinelinux.org/wiki/Installation][wiki]] that will guide
32a user throughout the installation and post-installation processes, as
33well as various other articles and guides.
34
35To install Alpine, find an appropriate
36[[https://alpinelinux.org/downloads/][image to download]] and flash it
37to a USB using software such as Rufus or Etcher. I opted to use the
38Standard image for my x86_{64} architecture.
39
40Once the USB is ready, plug it into the machine and reboot. Note that
41you may have to use a key such as =Esc= or =F1-12= to access the boot
42menu. The Alpine Linux terminal will load quickly and for a login.
43
44To log in to the installation image, use the =root= account; there is no
45password. Once logged-in, execute the setup command:
46
47#+begin_src sh
48setup-alpine
49#+end_src
50
51The setup script will ask a series of questions to configure the system.
52Be sure to answer carefully or else you may have to re-configure the
53system after boot.
54
55- Keyboard Layout (Local keyboard language and usage mode, e.g., us and
56 variant of us-nodeadkeys.)
57- Hostname (The name for the computer.)
58- Network (For example, automatic IP address discovery with the "DHCP"
59 protocol.)
60- DNS Servers (Domain Name Servers to query. For privacy reasons, it is
61 NOT recommended to route every local request to servers like Google's
62 8.8.8.8 .)
63- Timezone
64- Proxy (Proxy server to use for accessing the web. Use "none" for
65 direct connections to the internet.)
66- Mirror (From where to download packages. Choose the organization you
67 trust giving your usage patterns to.)
68- SSH (Secure SHell remote access server. "Openssh" is part of the
69 default install image. Use "none" to disable remote login, e.g. on
70 laptops.)
71- NTP (Network Time Protocol client used for keeping the system clock in
72 sync with a time-server. Package "chrony" is part of the default
73 install image.)
74- Disk Mode (Select between diskless (disk="none"), "data" or "sys", as
75 described above.)
76
77Once the setup script is finished, be sure to reboot the machine and
78remove the USB device.
79
80#+begin_src sh
81reboot
82#+end_src
83
84** Post-Installation
85There are many things you can do once your Alpine Linux system is up and
86running, and it largely depends on what you'll use the machine for. I'm
87going to walk through my personal post-installation setup for my web
88server.
89
901. Upgrade the System
91
92 First, login as =root= in order to update and upgrade the system:
93
94 #+begin_src sh
95 apk -U upgrade
96 #+end_src
97
982. Adding a User
99
100 I needed to add a user so that I don't need to log in as root. Note
101 that if you're used to using the =sudo= command, you will now need to
102 use the =doas= command on Alpine Linux.
103
104 #+begin_src sh
105 apk add doas
106 adduser <username>
107 adduser <username> wheel
108 #+end_src
109
110 You can now log out and log back in using the newly-created user:
111
112 #+begin_src sh
113 exit
114 #+end_src
115
1163. Enable Community Packages
117
118 In order to install more common packages that aren't found in the
119 =main= repository, you will need to enable the =community=
120 repository:
121
122 #+begin_src sh
123 doas nano /etc/apk/repositories
124 #+end_src
125
126 Uncomment the community line for whichever version of Alpine you're
127 running:
128
129 #+begin_src sh
130 /media/usb/apks
131 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
132 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
133 #http://dl-cdn.alpinelinux.org/alpine/edge/main
134 #http://dl-cdn.alpinelinux.org/alpine/edge/community
135 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
136 #+end_src
137
1384. Install Required Packages
139
140 Now that the community packages are available, you can install any
141 packages you need. In my case, I installed the web server packages I
142 need for my services:
143
144 #+begin_src sh
145 doas apk add nano nginx docker docker-compose ufw
146 #+end_src
147
1485. SSH
149
150 If you didn't install OpenSSH as part of the installation, you can do
151 so now:
152
153 #+begin_src sh
154 doas apk add openssh
155 #+end_src
156
157 Next, either create a new key or copy your SSH key to the server from
158 your current machines:
159
160 #+begin_src sh
161 # Create a new key
162 ssh-keygen
163 #+end_src
164
165 If you need to copy an existing SSH key from a current machine:
166
167 #+begin_src sh
168 # Copy key from existing machines
169 ssh-copy-id <username>@<ip_address>
170 #+end_src
171
1726. Firewall
173
174 Lastly, I installed =ufw= above as my firewall. To set up, default to
175 deny incoming and allow outgoing connections. Then selectively allow
176 other ports or apps as needed.
177
178 #+begin_src sh
179 doas ufw default deny incoming
180 doas ufw default allow outgoing
181 doas ufw allow SSH
182 doas ufw allow "WWW Full"
183 doas ufw allow 9418 # Git server port
184 #+end_src
185
1867. Change Hostname
187
188 If you don't like the hostname set during installation, you just need
189 to edit two files. First, edit the simple hostname file:
190
191 #+begin_src sh
192 doas nano /etc/hostname
193 #+end_src
194
195 #+begin_src sh
196 <hostname>
197 #+end_src
198
199 Next, edit the =hosts= file:
200
201 #+begin_src sh
202 doas nano /etc/hosts
203 #+end_src
204
205 #+begin_src sh
206 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
207 ::1 <hostname> <hostname>.local
208 #+end_src
209
210* Nginx Web Server
211To set up my web server, I simply created the =www= user and created the
212necessary files.
213
214#+begin_src sh
215doas adduser -D -g 'www' www
216mkdir /www
217doas mkdir /www
218doas chown -R www:www /var/lib/nginx/
219doas chown -R www:www /www
220#+end_src
221
222If you're running a simple webroot, you can alter the main =nginx.conf=
223file. Otherwise, you can drop configuration files in the following
224directory. You don't need to enable or symlink the configuration file
225like you do in other systems.
226
227#+begin_src sh
228doas nano /etc/nginx/http.d/example_website.conf
229#+end_src
230
231Once the configuration is set and pointed at the =/www= directory to
232serve files, enable the Nginx service:
233
234#+begin_src sh
235# Note that 'default' must be included or Nginx will not start on boot
236doas rc-update add nginx default
237#+end_src
238
239* Docker Containers
240Docker works exactly the same as other systems. Either execute a
241=docker run= command or create a =docker-compose.yml= file and do
242=docker-compose up -d=.
243
244* Git Server
245I went in-depth on how to self-host a git server in another post:
246[[../git-server/][Self-Hosting a Personal Git Server]].
247
248However, there are a few differences with Alpine. First note that in
249order to change the =git= user's shell, you must do a few things a
250little different:
251
252#+begin_src sh
253doas apk add libuser
254doas touch /etc/login.defs
255doas mkdir /etc/default
256doas touch /etc/default/useradd
257doas lchsh git
258#+end_src
259
260* Thoughts on Alpine
261So far, I love Alpine Linux. I have no complaints about anything at this
262point, but I'm not completely finished with the migration yet. Once I'm
263able to upgrade my hardware to a rack-mounted server, I will migrate
264Plex and Syncthing over to Alpine as well - possibly putting Plex into a
265container or VM.
266
267The performance is stellar, the =apk= package manager is seamless, and
268system administration tasks are effortless. My only regret is that I
269didn't install Alpine sooner.
content/blog/2022-10-30-linux-display-manager.md added +82
@@ -0,0 +1,82 @@
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 -
12or any other Linux distro - you need to identify the currently enabled
13display manager.
14
15## Disabling the Current Display Manager
16
17Void Linux only has one ISO available for download with a pre-built
18display manager at the time of this post: the XFCE ISO. If you\'ve
19installed this version, the pre-assigned display manager is
20`lxdm`. If you installed another display manager, replace
21`lxdm` in the following command with the display manager you
22have installed.
23
24To disable `lxdm`, simply remove the service symlink:
25
26```sh
27sudo rm /var/service/lxdm
28```
29
30## Enabling a New Display Manager
31
32If you want to enable a new display manager, you can do so after
33`lxdm` is disabled. Make sure to replace
34`<new_display_manager>` with your new DM, such as
35`gdm`, `xdm`, etc.
36
37```sh
38sudo ln -s /etc/sv/<new_display_manager> /var/service
39```
40
41# Set Up `.xinitrc`
42
43Depending on your setup, you may need to create a few X files, such as
44`~/.xinitrc`. For my personal set-up, I created this file to
45launch the i3wm as my desktop.
46
47```sh
48nano ~/.xinitrc
49```
50
51```sh
52#!/bin/sh
53
54exec i3
55```
56
57If you run a desktop other than i3, simply replace `i3` with
58the shell command that launches that desktop.
59
60# Set Up Your Shell Profile
61
62Finally, in order to automatically launch an X session upon login, you
63will need to edit the `.bash_profile` (bash) or
64`.zprofile` (zsh) files for your shell:
65
66```sh
67nano ~/.zprofile
68```
69
70Add the following snippet to the end of the shell profile file. This
71will execute the `startx` command upon login.
72
73```sh
74if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
75 exec startx
76fi
77```
78
79Alternatively, you can ignore this step and simply choose to manually
80execute `startx` upon login. This can be useful if you have
81issues with your desktop or like to manually launch different desktops
82by choice.
content/blog/2022-10-30-linux-display-manager.org deleted −72
@@ -1,72 +0,0 @@
1#+title: How to Disable or Change the Display Manager on Void Linux
2#+date: 2022-10-30
3#+description: Learn how to remove or modify the display manager on Void Linux.
4#+filetags: :linux:
5
6* Display Manager Services
7In order to change the
8[[https://en.wikipedia.org/wiki/Display_manager][display manager]] on
9Void Linux - or any other Linux distro - you need to identify the
10currently enabled display manager.
11
12** Disabling the Current Display Manager
13Void Linux only has one ISO available for download with a pre-built
14display manager at the time of this post: the XFCE ISO. If you've
15installed this version, the pre-assigned display manager is =lxdm=. If
16you installed another display manager, replace =lxdm= in the following
17command with the display manager you have installed.
18
19To disable =lxdm=, simply remove the service symlink:
20
21#+begin_src sh
22sudo rm /var/service/lxdm
23#+end_src
24
25** Enabling a New Display Manager
26If you want to enable a new display manager, you can do so after =lxdm=
27is disabled. Make sure to replace =<new_display_manager>= with your new
28DM, such as =gdm=, =xdm=, etc.
29
30#+begin_src sh
31sudo ln -s /etc/sv/<new_display_manager> /var/service
32#+end_src
33
34* Set Up =.xinitrc=
35Depending on your setup, you may need to create a few X files, such as
36=~/.xinitrc=. For my personal set-up, I created this file to launch the
37i3wm as my desktop.
38
39#+begin_src sh
40nano ~/.xinitrc
41#+end_src
42
43#+begin_src sh
44#!/bin/sh
45
46exec i3
47#+end_src
48
49If you run a desktop other than i3, simply replace =i3= with the shell
50command that launches that desktop.
51
52* Set Up Your Shell Profile
53Finally, in order to automatically launch an X session upon login, you
54will need to edit the =.bash_profile= (bash) or =.zprofile= (zsh) files
55for your shell:
56
57#+begin_src sh
58nano ~/.zprofile
59#+end_src
60
61Add the following snippet to the end of the shell profile file. This
62will execute the =startx= command upon login.
63
64#+begin_src sh
65if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
66 exec startx
67fi
68#+end_src
69
70Alternatively, you can ignore this step and simply choose to manually
71execute =startx= upon login. This can be useful if you have issues with
72your desktop or like to manually launch different desktops by choice.
content/blog/2022-11-07-self-hosting-matrix.org → content/blog/2022-11-07-self-hosting-matrix.md renamed +87 −73
@@ -1,98 +1,107 @@
1#+title: Self-Hosting Matrix Synapse on Alpine Linux 1+++
2#+date: 2022-11-07 2date = 2022-11-07
3#+description: A guide to self-hosting the Matrix Synapse application on your own server. 3title = "Self-Hosting Matrix Synapse on Alpine Linux"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* Synpase 8# Synpase
7If you're reading this, you likely know that 9
8[[https://github.com/matrix-org/synapse/][Synapse]] is a popular 10If you\'re reading this, you likely know that
9[[https://matrix.org/][Matrix]] home server software that allows users 11[Synapse](https://github.com/matrix-org/synapse/) is a popular
10to run their own Matrix home server. 12[Matrix](https://matrix.org/) home server software that allows users to
13run their own Matrix home server.
11 14
12This post is a short guide describing how I was able to get Synapse 15This post is a short guide describing how I was able to get Synapse
13working in a minimally-usable state on Alpine Linux. 16working in a minimally-usable state on Alpine Linux.
14 17
15* Installation Process 18# Installation Process
16** Dependencies 19
20## Dependencies
21
17First, since there is no Alpine-specific package for Synapse, we need to 22First, since there is no Alpine-specific package for Synapse, we need to
18ensure that Alpine has the required dependencies for the Python-based 23ensure that Alpine has the required dependencies for the Python-based
19installation method. 24installation method.
20 25
21#+begin_src sh 26```sh
22doas apk -U update 27doas apk -U update
23doas apk add python3 py3-virtualenv 28doas apk add python3 py3-virtualenv
24#+end_src 29```
25 30
26Next, we need to set up a Python virtual environment for Synapse: 31Next, we need to set up a Python virtual environment for Synapse:
27 32
28#+begin_src sh 33```sh
29mkdir -p ~/synapse && cd ~/synapse 34mkdir -p ~/synapse && cd ~/synapse
30virtualenv -p python3 ~/synapse/env 35virtualenv -p python3 ~/synapse/env
31source ~/synapse/env/bin/activate 36source ~/synapse/env/bin/activate
32pip install --upgrade pip 37pip install --upgrade pip
33pip install --upgrade setuptools 38pip install --upgrade setuptools
34pip install matrix-synapse 39pip install matrix-synapse
35#+end_src 40```
41
42## Running Synapse
36 43
37** Running Synapse
38Once installed, running Synapse is easy. Simply execute the following 44Once installed, running Synapse is easy. Simply execute the following
39command, replacing =example.com= with the domain name that will be used 45command, replacing `example.com` with the domain name that
40with this home server. This will generate the configuration files needed 46will be used with this home server. This will generate the configuration
41to run the server. 47files needed to run the server.
42 48
43#+begin_src sh 49```sh
44python -m synapse.app.homeserver \ 50python -m synapse.app.homeserver \
45 --server-name example.com \ 51 --server-name example.com \
46 --config-path homeserver.yaml \ 52 --config-path homeserver.yaml \
47 --generate-config \ 53 --generate-config \
48 --report-stats=no 54 --report-stats=no
49#+end_src 55```
50 56
51Once the configuration is generated, we can start up the Synapse server: 57Once the configuration is generated, we can start up the Synapse server:
52 58
53#+begin_src sh 59```sh
54synctl start 60synctl start
55#+end_src 61```
56 62
57** Configuring Synapse 63## Configuring Synapse
58To make any change to Synapse, we need to edit the =YAML= configuration
59file:
60 64
61#+begin_src sh 65To make any change to Synapse, we need to edit the `YAML`
66configuration file:
67
68```sh
62nano ~/synapse/homeserver.yaml 69nano ~/synapse/homeserver.yaml
63#+end_src 70```
64 71
65For now, we just need to ensure the =server_name= is accurate. However, 72For now, we just need to ensure the `server_name` is
66there are a lot of other configuration options found in the 73accurate. However, there are a lot of other configuration options found
67[[https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html][Configuring 74in the [Configuring
68Synapse]] documentation that can be enabled/disabled at any point. 75Synapse](https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html)
76documentation that can be enabled/disabled at any point.
69 77
70#+begin_src yaml 78``` yaml
71server_name: "example.com" 79server_name: "example.com"
72#+end_src 80```
73 81
74Make sure to restart Synapse when you make changes to the configuration: 82Make sure to restart Synapse when you make changes to the configuration:
75 83
76#+begin_src sh 84```sh
77synctl restart 85synctl restart
78#+end_src 86```
87
88## Nginx Reverse-Proxy
79 89
80** Nginx Reverse-Proxy
81To ensure that Synapse is reachable from the public, we need to connect 90To ensure that Synapse is reachable from the public, we need to connect
82our domain to the Synapse server. In my case, I use a Nginx 91our domain to the Synapse server. In my case, I use a Nginx
83reverse-proxy for this purpose. 92reverse-proxy for this purpose.
84 93
85To use Nginx, we need to create a reverse-proxy configuration file: 94To use Nginx, we need to create a reverse-proxy configuration file:
86 95
87#+begin_src sh 96```sh
88doas nano /etc/nginx/http.d/example.com.conf 97doas nano /etc/nginx/http.d/example.com.conf
89#+end_src 98```
90 99
91If you already have TLS certificates for this domain (=example.com=), 100If you already have TLS certificates for this domain
92you can simply use the SSL configuration and point toward your TLS 101(`example.com`), you can simply use the SSL configuration and
93certificates. 102point toward your TLS certificates.
94 103
95#+begin_src conf 104``` conf
96server { 105server {
97 listen 443 ssl http2; 106 listen 443 ssl http2;
98 listen [::]:443 ssl http2; 107 listen [::]:443 ssl http2;
@@ -131,15 +140,15 @@ server {
131 listen 80; 140 listen 80;
132 return 404; 141 return 404;
133} 142}
134#+end_src 143```
135 144
136If you need to generate TLS certificates (I recommend 145If you need to generate TLS certificates (I recommend
137[[https://certbot.eff.org/][Certbot]]), you'll need a more minimal Nginx 146[Certbot](https://certbot.eff.org/)), you\'ll need a more minimal Nginx
138conf file before you can use the TLS-enabled example above. Instead, use 147conf file before you can use the TLS-enabled example above. Instead, use
139this configuration file during the Certbot certificate generation 148this configuration file during the Certbot certificate generation
140process: 149process:
141 150
142#+begin_src conf 151``` conf
143server { 152server {
144 server_name example.com; 153 server_name example.com;
145 location / { 154 location / {
@@ -147,60 +156,65 @@ server {
147 } 156 }
148 listen 80; 157 listen 80;
149} 158}
150#+end_src 159```
151 160
152Once you're done editing the Nginx conf file, restart Nginx: 161Once you\'re done editing the Nginx conf file, restart Nginx:
153 162
154#+begin_src sh 163```sh
155doas rc-service nginx restart 164doas rc-service nginx restart
156#+end_src 165```
157 166
158If you still need to generate TLS certificates, run =certbot= now and 167If you still need to generate TLS certificates, run `certbot`
159obtain the certificates. Certbot will ask if you want to use a webroot 168now and obtain the certificates. Certbot will ask if you want to use a
160or spin up a temporary web server. I *highly** recommend using the 169webroot or spin up a temporary web server. I **highly\*** recommend
161temporary web server due to the many issues with using a webroot. 170using the temporary web server due to the many issues with using a
171webroot.
162 172
163You will need to stop Nginx in order to user the temporary web server 173You will need to stop Nginx in order to user the temporary web server
164option with Certbot: 174option with Certbot:
165 175
166#+begin_src sh 176```sh
167# Stop Nginx so certbot can spin up a temp webserver for cert generation 177# Stop Nginx so certbot can spin up a temp webserver for cert generation
168doas rc-service nginx stop 178doas rc-service nginx stop
169doas certbot certonly -v 179doas certbot certonly -v
170doas rc-service nginx start 180doas rc-service nginx start
171#+end_src 181```
172 182
173** Open Firewall & Router Ports 183## Open Firewall & Router Ports
174If you use a firewall on the server, open the =8448= port for discovery
175and federation, as well as the normal web server ports if you're using a
176reverse proxy. If you want additional services, such as voice calls, you
177will need to read the Synapse documentation to see which ports need to
178be opened for those features.
179 184
180Here's an example of the Universal Firewall (UFW) software: 185If you use a firewall on the server, open the `8448` port for
186discovery and federation, as well as the normal web server ports if
187you\'re using a reverse proxy. If you want additional services, such as
188voice calls, you will need to read the Synapse documentation to see
189which ports need to be opened for those features.
181 190
182#+begin_src sh 191Here\'s an example of the Universal Firewall (UFW) software:
192
193```sh
183# Matrix port 194# Matrix port
184doas ufw allow 8448 195doas ufw allow 8448
185# Standard web server ports 196# Standard web server ports
186doas ufw allow "Nginx Full" 197doas ufw allow "Nginx Full"
187#+end_src 198```
199
200Remember to forward any Synapse ports, such as `8448`,
201`80`, and `443`, in your Router from the internet
202to your server\'s IP address.
188 203
189Remember to forward any Synapse ports, such as =8448=, =80=, and =443=, 204## Adding Matrix Users
190in your Router from the internet to your server's IP address.
191 205
192** Adding Matrix Users 206Finally, if you didn\'t enable public registration in the
193Finally, if you didn't enable public registration in the 207`homeserver.yaml` file, you can manually create users via the
194=homeserver.yaml= file, you can manually create users via the
195command-line: 208command-line:
196 209
197#+begin_src sh 210```sh
198cd ~/synapse 211cd ~/synapse
199register_new_matrix_user -c homeserver.yaml 212register_new_matrix_user -c homeserver.yaml
200#+end_src 213```
201 214
202Remember that the format for federated Matrix usernames is 215Remember that the format for federated Matrix usernames is
203=@username:example.com= when logging in to client applications. 216`@username:example.com` when logging in to client
217applications.
204 218
205Once Synapse is running, and you have a username, you are ready to log 219Once Synapse is running, and you have a username, you are ready to log
206in to a Matrix client and start sending messages, joining rooms, and 220in to a Matrix client and start sending messages, joining rooms, and
content/blog/2022-11-11-nginx-tmp-errors.md added +80
@@ -0,0 +1,80 @@
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
9has occurred multiple times for me.*
10
11# The Problem
12
13After migrating to a new server OS, I started receiving quite a few
14permission errors like the one below. These popped up for various
15different websites I\'m serving via Nginx on this server, but did not
16prevent the website from loading.
17
18I found the errors in the standard log file:
19
20```sh
21cat /var/log/nginx/error.log
22```
23
24```sh
252022/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/"
26```
27
28You can see that the error is `13: Permission denied` and it
29occurs in the `/var/lib/nginx/tmp/` directory. In my case, I
30had thousands of errors where Nginx was denied permission to read/write
31files in this directory.
32
33So how do I fix it?
34
35# The Solution
36
37In order to resolve the issue, I had to ensure the
38`/var/lib/nginx` directory is owned by Nginx. Mine was owned
39by the `www` user and Nginx was not able to read or write
40files within that directory. This prevented Nginx from caching temporary
41files.
42
43```sh
44# Alpine Linux
45doas chown -R nginx:nginx /var/lib/nginx
46
47# Other Distros
48sudo chown -R nginx:nginx /var/lib/nginx
49```
50
51You *may* also be able to change the `proxy_temp_path` in
52your Nginx config, but I did not try this. Here\'s a suggestion I found
53online that may work if the above solution does not:
54
55```sh
56nano /etc/nginx/http.d/example.com.conf
57```
58
59``` conf
60server {
61 ...
62
63 # Set the proxy_temp_path to your preference, make sure it's owned by the
64 # `nginx` user
65 proxy_temp_path /tmp;
66
67 ...
68}
69```
70
71Finally, restart Nginx and your server should be able to cache temporary
72files again.
73
74```sh
75# Alpine Linux (OpenRC)
76doas rc-service nginx restart
77
78# Other Distros (systemd)
79sudo systemctl restart nginx
80```
content/blog/2022-11-11-nginx-tmp-errors.org deleted −75
@@ -1,75 +0,0 @@
1#+title: Fixing Permission Errors in /var/lib/nginx
2#+date: 2022-11-11
3#+description: Learn how to fix permission errors related to the Nginx temporary file storage.
4#+filetags: :nginx:
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
10After migrating to a new server OS, I started receiving quite a few
11permission errors like the one below. These popped up for various
12different websites I'm serving via Nginx on this server, but did not
13prevent the website from loading.
14
15I found the errors in the standard log file:
16
17#+begin_src sh
18cat /var/log/nginx/error.log
19#+end_src
20
21#+begin_src sh
222022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/"
23#+end_src
24
25You can see that the error is =13: Permission denied= and it occurs in
26the =/var/lib/nginx/tmp/= directory. In my case, I had thousands of
27errors where Nginx was denied permission to read/write files in this
28directory.
29
30So how do I fix it?
31
32* The Solution
33In order to resolve the issue, I had to ensure the =/var/lib/nginx=
34directory is owned by Nginx. Mine was owned by the =www= user and Nginx
35was not able to read or write files within that directory. This
36prevented Nginx from caching temporary files.
37
38#+begin_src sh
39# Alpine Linux
40doas chown -R nginx:nginx /var/lib/nginx
41
42# Other Distros
43sudo chown -R nginx:nginx /var/lib/nginx
44#+end_src
45
46You /may/ also be able to change the =proxy_temp_path= in your Nginx
47config, but I did not try this. Here's a suggestion I found online that
48may work if the above solution does not:
49
50#+begin_src sh
51nano /etc/nginx/http.d/example.com.conf
52#+end_src
53
54#+begin_src conf
55server {
56 ...
57
58 # Set the proxy_temp_path to your preference, make sure it's owned by the
59 # `nginx` user
60 proxy_temp_path /tmp;
61
62 ...
63}
64#+end_src
65
66Finally, restart Nginx and your server should be able to cache temporary
67files again.
68
69#+begin_src sh
70# Alpine Linux (OpenRC)
71doas rc-service nginx restart
72
73# Other Distros (systemd)
74sudo systemctl restart nginx
75#+end_src
content/blog/2022-11-27-server-build.org → content/blog/2022-11-27-server-build.md renamed +63 −56
@@ -1,9 +1,12 @@
1#+title: Building a Custom Rack-Mounted Server 1+++
2#+date: 2022-11-27 2date = 2022-11-27
3#+description: A retrospective on building my own rack-mounted server. 3title = "Building a Custom Rack-Mounted Server"
4#+filetags: :hardware: 4description = ""
5draft = false
6+++
7
8# The Dilemma
5 9
6* The Dilemma
7For years, I have been using desktops and a Raspberry Pi as the backbone 10For years, I have been using desktops and a Raspberry Pi as the backbone
8of my homelab. I have always wanted to move toward a single dedicated 11of my homelab. I have always wanted to move toward a single dedicated
9server that could handle all of my tasks, but was often put off by the 12server that could handle all of my tasks, but was often put off by the
@@ -19,59 +22,62 @@ After returning the R720XD, I decided that I wanted to build my own
19server with modern, consumer-grade PC components. This time, I am very 22server with modern, consumer-grade PC components. This time, I am very
20happy with the results of my server. 23happy with the results of my server.
21 24
22* Components 25# Components
23I'll start by listing all the components I used for this server build: 26
24 27I\'ll start by listing all the components I used for this server build:
25- *Case*: 28
26 [[https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825][Rosewill 29- **Case**: [Rosewill RSV-R4100U 4U Server Chassis Rackmount
27 RSV-R4100U 4U Server Chassis Rackmount Case]] 30 Case](https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825)
28- *Motherboard*: [[https://nzxt.com/product/n7-b550][NZXT B550]] 31- **Motherboard**: [NZXT B550](https://nzxt.com/product/n7-b550)
29- *CPU*: AMD Ryzen 7 5700G with Radeon Graphics 32- **CPU**: AMD Ryzen 7 5700G with Radeon Graphics
30- *GPU*: N/A - I specifically chose one of the few AMD CPUs that support 33- **GPU**: N/A - I specifically chose one of the few AMD CPUs that
31 onboard graphics. 34 support onboard graphics.
32- *RAM*: 64GB RAM (2x32GB) /Max of 128GB RAM on this motherboard/ 35- **RAM**: 64GB RAM (2x32GB) *Max of 128GB RAM on this motherboard*
33- *Boot Drive*: Western Digital 500GB M.2 NVME SSD 36- **Boot Drive**: Western Digital 500GB M.2 NVME SSD
34- *HDD Bay*: 37- **HDD Bay**:
35 - 10TB WD White /(shucked, moved from previous server)/ 38 - 10TB WD White *(shucked, moved from previous server)*
36 - 8TB WD White /(shucked, moved from previous server)/ 39 - 8TB WD White *(shucked, moved from previous server)*
37 - 2 x 8TB WD Red Plus /(Black Friday lined up perfectly with this 40 - 2 x 8TB WD Red Plus *(Black Friday lined up perfectly with this
38 build, so I grabbed two of these)/ 41 build, so I grabbed two of these)*
39- *PSU*: Corsair RM850 PSU 42- **PSU**: Corsair RM850 PSU
40- *Extras*: 43- **Extras**:
41 - Corsair TM3Q Thermal Paste 44 - Corsair TM3Q Thermal Paste
42 - Noctua 120mm fan /(replacement for front case fan)/ 45 - Noctua 120mm fan *(replacement for front case fan)*
43 - 2 x Noctua 80mm fans /(replacement for rear case fans)/ 46 - 2 x Noctua 80mm fans *(replacement for rear case fans)*
44 - CableMatters 6Gbps SATA Cables 47 - CableMatters 6Gbps SATA Cables
45 48
46* Building the Server 49# Building the Server
50
47This took quite a while for me to build (in my opinion of time), 51This took quite a while for me to build (in my opinion of time),
48totaling around 3 hours from start to finish. The case has some peculiar 52totaling around 3 hours from start to finish. The case has some peculiar
49construction, so you have to completely remove the ODD & HDD cages to 53construction, so you have to completely remove the ODD & HDD cages to
50install the motherboard and other components first. 54install the motherboard and other components first.
51 55
52Now, I've never built a computer of any kind before, so I was quite 56Now, I\'ve never built a computer of any kind before, so I was quite
53nervous. Personally, the only challenging part was getting the CPU 57nervous. Personally, the only challenging part was getting the CPU
54cooler to screw into the motherboard without sliding the thermal paste 58cooler to screw into the motherboard without sliding the thermal paste
55around too much underneath. I'm still not entirely sure if I did a great 59around too much underneath. I\'m still not entirely sure if I did a
56job of it, but nothing's broken yet. 60great job of it, but nothing\'s broken yet.
57 61
58The main components were all fine and simple. However, installing the 62The main components were all fine and simple. However, installing the
59hard drives is slightly tedious as I need to power off the server and 63hard drives is slightly tedious as I need to power off the server and
60completely unscrew the HDD cage to install or remove any drives. 64completely unscrew the HDD cage to install or remove any drives.
61Additionally, the drives are screwed directly into the metal cage with 65Additionally, the drives are screwed directly into the metal cage with
62small screws, which are quite a bit different from the HDD trays I'm 66small screws, which are quite a bit different from the HDD trays I\'m
63used to in other machines. 67used to in other machines.
64 68
65Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay 69Seeing that the cases with hot-swap bays were 3-4x the price, I\'m okay
66dealing with the tedium of removing the cage to install new drives. 70dealing with the tedium of removing the cage to install new drives.
67 71
68* Software 72# Software
69I'm not going to dive into the software as I have done so in other 73
74I\'m not going to dive into the software as I have done so in other
70recent posts. However, I wanted to note that I am using Alpine Linux on 75recent posts. However, I wanted to note that I am using Alpine Linux on
71this server and hosting most services inside Docker. No virtual machines 76this server and hosting most services inside Docker. No virtual machines
72(VMs) and very few bare-metal services. 77(VMs) and very few bare-metal services.
73 78
74* The Results 79# The Results
80
75How did my build turn out? Well, after migrating my other servers and 81How did my build turn out? Well, after migrating my other servers and
76their services over, I found that my server is blazing fast. The 82their services over, I found that my server is blazing fast. The
77heaviest of my applications, Plex, is handled with ease. Even 4k 83heaviest of my applications, Plex, is handled with ease. Even 4k
@@ -81,28 +87,29 @@ I am very happy with the results and will likely continue to improve on
81this server as the years go by rather than buying another used server 87this server as the years go by rather than buying another used server
82online. 88online.
83 89
84** Mistakes I Made 90## Mistakes I Made
85This post wouldn't be complete unless I wrote about the mistakes I made 91
86while building. The only real mistake I made beyond a "whoops I dropped 92This post wouldn\'t be complete unless I wrote about the mistakes I made
87a screw" related to airflow and fan direction. 93while building. The only real mistake I made beyond a \"whoops I dropped
94a screw\" related to airflow and fan direction.
88 95
89While installing the two new hard drives that showed up on 2022-11-30 96While installing the two new hard drives that showed up on 2022-11-30
90and getting ready to install the case in my rack, I noticed that the 97and getting ready to install the case in my rack, I noticed that the
91hard drive temperatures were quite high. 98hard drive temperatures were quite high.
92 99
93I used the =smartctl= command for each of my drives (=/dev/sda= through 100I used the `smartctl` command for each of my drives
94=/dev/sdd=): 101(`/dev/sda` through `/dev/sdd`):
95 102
96#+begin_src sh 103```sh
97doas smartctl -a /dev/sda | grep Temperature_Celsius 104doas smartctl -a /dev/sda | grep Temperature_Celsius
98#+end_src 105```
99 106
100The results were unusual - all four drives were idling at ~44-46 degrees 107The results were unusual - all four drives were idling at \~44-46
101Celsius. The only drive that was cooler was my 10TB drive, which was at 108degrees Celsius. The only drive that was cooler was my 10TB drive, which
10238 degrees Celsius. I noted that this 10TB drive was also closest to the 109was at 38 degrees Celsius. I noted that this 10TB drive was also closest
103case fan. 110to the case fan.
104 111
105#+begin_src sh 112```sh
106ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE 113ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
107# /dev/sda 114# /dev/sda
108194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46) 115194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46)
@@ -112,7 +119,7 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_
112194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61) 119194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61)
113# /dev/sdd 120# /dev/sdd
114194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56) 121194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56)
115#+end_src 122```
116 123
117After looking to see if I could fit more fans into the case, I noticed 124After looking to see if I could fit more fans into the case, I noticed
118that the 120mm fan used for intake from the front of the case was 125that the 120mm fan used for intake from the front of the case was
@@ -120,11 +127,11 @@ actually pushing air out of the case by mistake. This fan sits right in
120front of the hard drive bay. 127front of the hard drive bay.
121 128
122Once I flipped the fan around to act as an intake fan, the temperatures 129Once I flipped the fan around to act as an intake fan, the temperatures
123dropped immediately! They are now idling at ~31-33 degrees Celsius. A 130dropped immediately! They are now idling at \~31-33 degrees Celsius. A
124single fan spinning the wrong way caused my drives to idle 10-15 degrees 131single fan spinning the wrong way caused my drives to idle 10-15 degrees
125higher than they should have. 132higher than they should have.
126 133
127#+begin_src sh 134```sh
128ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE 135ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
129# /dev/sda 136# /dev/sda
130194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56) 137194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56)
@@ -134,8 +141,8 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_
134194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48) 141194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48)
135# /dev/sdd 142# /dev/sdd
136194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46) 143194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46)
137#+end_src 144```
138 145
139This was a silly error to make, but I'm glad I found it today before I 146This was a silly error to make, but I\'m glad I found it today before I
140screwed the case into the rack and made things a lot more tedious to 147screwed the case into the rack and made things a lot more tedious to
141fix. 148fix.
content/blog/2022-11-29-nginx-referrer-ban-list.md added +134
@@ -0,0 +1,134 @@
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
11to create a ban list file. The file below will accept regexes for
12different domains or websites 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
21whichever domains 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
39to handle 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
46file location to the 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
62so, edit the configuration file for your website. For example, I have
63all website configuration files in the `http.d` directory.
64You may have them in the `sites-available` directory on some
65distributions.
66
67```sh
68doas nano /etc/nginx/http.d/example.com.conf
69```
70
71Within each website\'s configuration file, edit the `server`
72blocks that are listening to ports 80 and 443 and create a check for the
73`$bad_referrer` variable we created in the ban list file.
74
75If a matching site is found, you can return any [HTTP Status
76Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) you want.
77Code 403 (Forbidden) is logical in this case since you are preventing a
78client connection due to a banned domain.
79
80``` conf
81server {
82 ...
83
84 # If a referral site is banned, return an error
85 if ($bad_referer) {
86 return 403;
87 }
88
89 ...
90}
91```
92
93# Restart Nginx
94
95Lastly, restart Nginx to enable all changes made.
96
97```sh
98doas rc-service nginx restart
99```
100
101# Testing Results
102
103In order to test the results, let\'s curl the contents of our site. To
104start, I\'ll curl the site normally:
105
106```sh
107curl https://cleberg.net
108```
109
110The HTML contents of the page come back successfully:
111
112``` html
113<!doctype html>...</html>
114```
115
116Next, let\'s include a banned referrer:
117
118```sh
119curl --referer https://news.ycombinator.com https://cleberg.net
120```
121
122This time, I\'m met with a 403 Forbidden response page. That means we
123are successful and any clients being referred from a banned domain will
124be met with this same response code.
125
126``` html
127<html>
128<head><title>403 Forbidden</title></head>
129<body>
130<center><h1>403 Forbidden</h1></center>
131<hr><center>nginx</center>
132</body>
133</html>
134```
content/blog/2022-11-29-nginx-referrer-ban-list.org deleted −126
@@ -1,126 +0,0 @@
1#+title: Creating a Referrer Ban List in Nginx
2#+date: 2022-11-29
3#+description: Learn how to create a ban list for referring sites in Nginx.
4#+filetags: :nginx:
5
6* Creating the Ban List
7In order to ban list referral domains or websites with Nginx, you need
8to create a ban list file. The file below will accept regexes for
9different domains or websites you wish to block.
10
11First, create the file in your nginx directory:
12
13#+begin_src sh
14doas nano /etc/nginx/banlist.conf
15#+end_src
16
17Next, paste the following contents in and fill out the regexes with
18whichever domains you're blocking.
19
20#+begin_src conf
21# /etc/nginx/banlist.conf
22
23map $http_referer $bad_referer {
24 hostnames;
25
26 default 0;
27
28 # Put regexes for undesired referrers here
29 "~news.ycombinator.com" 1;
30}
31#+end_src
32
33* Configuring Nginx
34In order for the ban list to work, Nginx needs to know it exists and how
35to handle it. For this, edit the =nginx.conf= file.
36
37#+begin_src sh
38doas nano /etc/nginx/nginx.conf
39#+end_src
40
41Within this file, find the =http= block and add your ban list file
42location to the end of the block.
43
44#+begin_src conf
45# /etc/nginx/nginx.conf
46
47http {
48 ...
49
50 # Include ban list
51 include /etc/nginx/banlist.conf;
52}
53#+end_src
54
55* Enabling the Ban List
56Finally, we need to take action when a bad referral site is found. To do
57so, edit the configuration file for your website. For example, I have
58all website configuration files in the =http.d= directory. You may have
59them in the =sites-available= directory on some distributions.
60
61#+begin_src sh
62doas nano /etc/nginx/http.d/example.com.conf
63#+end_src
64
65Within each website's configuration file, edit the =server= blocks that
66are listening to ports 80 and 443 and create a check for the
67=$bad_referrer= variable we created in the ban list file.
68
69If a matching site is found, you can return any
70[[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes][HTTP Status
71Code]] you want. Code 403 (Forbidden) is logical in this case since you
72are preventing a client connection due to a banned domain.
73
74#+begin_src conf
75server {
76 ...
77
78 # If a referral site is banned, return an error
79 if ($bad_referer) {
80 return 403;
81 }
82
83 ...
84}
85#+end_src
86
87* Restart Nginx
88Lastly, restart Nginx to enable all changes made.
89
90#+begin_src sh
91doas rc-service nginx restart
92#+end_src
93
94* Testing Results
95In order to test the results, let's curl the contents of our site. To
96start, I'll curl the site normally:
97
98#+begin_src sh
99curl https://cleberg.net
100#+end_src
101
102The HTML contents of the page come back successfully:
103
104#+begin_src html
105<!doctype html>...</html>
106#+end_src
107
108Next, let's include a banned referrer:
109
110#+begin_src sh
111curl --referer https://news.ycombinator.com https://cleberg.net
112#+end_src
113
114This time, I'm met with a 403 Forbidden response page. That means we are
115successful and any clients being referred from a banned domain will be
116met with this same response code.
117
118#+begin_src html
119<html>
120<head><title>403 Forbidden</title></head>
121<body>
122<center><h1>403 Forbidden</h1></center>
123<hr><center>nginx</center>
124</body>
125</html>
126#+end_src
content/blog/2022-12-01-nginx-compression.md added +80
@@ -0,0 +1,80 @@
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
11faster than uncompressed data. This can speed up things like First
12Contentful Paint, Tie to Interactive, and Speed Index.
13
14# Enable Nginx Compression with gzip
15
16In order to enable text compression on Nginx, we need to enable it
17within the configuration file:
18
19```sh
20nano /etc/nginx/nginx.conf
21```
22
23Within the `http` block, find the section that shows
24something like the block below. This is the default gzip configuration I
25found in my `nginx.conf` file on Alpine Linux 3.17. Yours may
26look slightly different, just make sure that you\'re not creating any
27duplicate gzip options.
28
29``` conf
30# Enable gzipping of responses.
31#gzip on;
32
33# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
34gzip_vary on;
35```
36
37Remove the default gzip lines and replace them with the following:
38
39``` conf
40# Enable gzipping of responses.
41gzip on;
42gzip_vary on;
43gzip_min_length 10240;
44gzip_proxied expired no-cache no-store private auth;
45gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
46gzip_disable "MSIE [1-6]";
47```
48
49# Explanations of ngx~httpgzipmodule~ Options
50
51Each of the lines above enables a different aspect of the gzip response
52for Nginx. Here are the full explanations:
53
54- `gzip` -- Enables or disables gzipping of responses.
55- `gzip_vary` -- Enables or disables inserting the \"Vary:
56 Accept-Encoding\" response header field if the directives gzip,
57 gzip~static~, or gunzip are active.
58- `gzip_min_length` -- Sets the minimum length of a
59 response that will be gzipped. The length is determined only from
60 the \"Content-Length\" response header field.
61- `gzip_proxied` -- Enables or disables gzipping of
62 responses for proxied requests depending on the request and
63 response. The fact that the request is proxied is determined by the
64 presence of the \"Via\" request header field.
65- `gzip_types` -- Enables gzipping of responses for the
66 specified MIME types in addition to \"text/html\". The special value
67 "\*" matches any MIME type (0.8.29). Responses with the
68 \"text/html\" type are always compressed.
69- `gzip_disable` -- Disables gzipping of responses for
70 requests with \"User-Agent\" header fields matching any of the
71 specified regular expressions.
72 - The special mask \"msie6\" (0.7.12) corresponds to the regular
73 expression \"MSIE \[4-6\].\", but works faster. Starting from
74 version 0.8.11, \"MSIE 6.0; ... SV1\" is excluded from this
75 mask.
76
77More information on these directives and their options can be found on
78the [Module
79ngx~httpgzipmodule~](https://nginx.org/en/docs/http/ngx_http_gzip_module.html)
80page in Nginx\'s documentation.
content/blog/2022-12-01-nginx-compression.org deleted −73
@@ -1,73 +0,0 @@
1#+title: Enable GZIP Compression in Nginx
2#+date: 2022-12-01
3#+description: Learn how to enable compression in Nginx.
4#+filetags: :nginx:
5
6* Text Compression
7Text compression allows a web server to serve text-based resources
8faster than uncompressed data. This can speed up things like First
9Contentful Paint, Tie to Interactive, and Speed Index.
10
11* Enable Nginx Compression with gzip
12In order to enable text compression on Nginx, we need to enable it
13within the configuration file:
14
15#+begin_src sh
16nano /etc/nginx/nginx.conf
17#+end_src
18
19Within the =http= block, find the section that shows something like the
20block below. This is the default gzip configuration I found in my
21=nginx.conf= file on Alpine Linux 3.17. Yours may look slightly
22different, just make sure that you're not creating any duplicate gzip
23options.
24
25#+begin_src conf
26# Enable gzipping of responses.
27#gzip on;
28
29# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
30gzip_vary on;
31#+end_src
32
33Remove the default gzip lines and replace them with the following:
34
35#+begin_src conf
36# Enable gzipping of responses.
37gzip on;
38gzip_vary on;
39gzip_min_length 10240;
40gzip_proxied expired no-cache no-store private auth;
41gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
42gzip_disable "MSIE [1-6]";
43#+end_src
44
45* Explanations of ngx_{httpgzipmodule} Options
46Each of the lines above enables a different aspect of the gzip response
47for Nginx. Here are the full explanations:
48
49- =gzip= -- Enables or disables gzipping of responses.
50- =gzip_vary= -- Enables or disables inserting the "Vary:
51 Accept-Encoding" response header field if the directives gzip,
52 gzip_{static}, or gunzip are active.
53- =gzip_min_length= -- Sets the minimum length of a response that will
54 be gzipped. The length is determined only from the "Content-Length"
55 response header field.
56- =gzip_proxied= -- Enables or disables gzipping of responses for
57 proxied requests depending on the request and response. The fact that
58 the request is proxied is determined by the presence of the "Via"
59 request header field.
60- =gzip_types= -- Enables gzipping of responses for the specified MIME
61 types in addition to "text/html". The special value “*” matches any
62 MIME type (0.8.29). Responses with the "text/html" type are always
63 compressed.
64- =gzip_disable= -- Disables gzipping of responses for requests with
65 "User-Agent" header fields matching any of the specified regular
66 expressions.
67 - The special mask "msie6" (0.7.12) corresponds to the regular
68 expression "MSIE [4-6].", but works faster. Starting from version
69 0.8.11, "MSIE 6.0; ... SV1" is excluded from this mask.
70
71More information on these directives and their options can be found on
72the [[https://nginx.org/en/docs/http/ngx_http_gzip_module.html][Module
73ngx_{httpgzipmodule}]] page in Nginx's documentation.
content/blog/2022-12-07-nginx-wildcard-redirect.org → content/blog/2022-12-07-nginx-wildcard-redirect.md renamed +40 −33
@@ -1,9 +1,12 @@
1#+title: Redirect Nginx Subdomains & Trailing Content with Regex 1+++
2#+date: 2022-12-07 2date = 2022-12-07
3#+description: A simple Nginx configuration to redirect all subdomains and trailing content. 3title = "Redirect Nginx Subdomains and Trailing Content with Regex"
4#+filetags: :nginx: 4description = ""
5draft = false
6+++
7
8# Problem
5 9
6* Problem
7I recently migrated domains and replaced the old webpage with a simple 10I recently migrated domains and replaced the old webpage with a simple
8info page with instructions to users on how to edit their bookmarks and 11info page with instructions to users on how to edit their bookmarks and
9URLs to get to the page they were seeking. 12URLs to get to the page they were seeking.
@@ -11,12 +14,13 @@ URLs to get to the page they were seeking.
11This was not ideal as it left the work up to the user and may have 14This was not ideal as it left the work up to the user and may have
12caused friction for users who accessed my RSS feed. 15caused friction for users who accessed my RSS feed.
13 16
14* Solution 17# Solution
18
15Instead, I finally found a solution that allows me to redirect both 19Instead, I finally found a solution that allows me to redirect both
16subdomains AND trailing content. For example, both of these URLs now 20subdomains AND trailing content. For example, both of these URLs now
17redirect properly using the logic I'll explain below: 21redirect properly using the logic I\'ll explain below:
18 22
19#+begin_src txt 23``` txt
20# Example 1 - Simple base domain redirect with trailing content 24# Example 1 - Simple base domain redirect with trailing content
21https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ 25https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
22 26
@@ -24,22 +28,23 @@ https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
24https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 28https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
25-> 29->
26https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 30https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
27#+end_src 31```
28 32
29Go ahead, try the URLs if you want to test them. 33Go ahead, try the URLs if you want to test them.
30 34
31** Nginx Config 35## Nginx Config
36
32To make this possible. I needed to configure a proper redirect scheme in 37To make this possible. I needed to configure a proper redirect scheme in
33my Nginx configuration. 38my Nginx configuration.
34 39
35#+begin_src sh 40```sh
36doas nano /etc/nginx/http.d/domain1.conf 41doas nano /etc/nginx/http.d/domain1.conf
37#+end_src 42```
38 43
39Within this file, I had one block configured to redirect HTTP requests 44Within this file, I had one block configured to redirect HTTP requests
40to HTTPS for the base domain and all subdomains. 45to HTTPS for the base domain and all subdomains.
41 46
42#+begin_src conf 47``` conf
43server { 48server {
44 listen [::]:80; 49 listen [::]:80;
45 listen 80; 50 listen 80;
@@ -55,14 +60,14 @@ server {
55 60
56 return 404; 61 return 404;
57} 62}
58#+end_src 63```
59 64
60For the base domain, I have another =server= block dedicated to 65For the base domain, I have another `server` block dedicated
61redirecting all base domain requests. You can see that the =rewrite= 66to redirecting all base domain requests. You can see that the
62line is instructing Nginx to gather all trailing content and append it 67`rewrite` line is instructing Nginx to gather all trailing
63to the new =domain2.com= URL. 68content and append it to the new `domain2.com` URL.
64 69
65#+begin_src conf 70``` conf
66server { 71server {
67 listen [::]:443 ssl http2; 72 listen [::]:443 ssl http2;
68 listen 443 ssl http2; 73 listen 443 ssl http2;
@@ -74,21 +79,23 @@ server {
74 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 79 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
75 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 80 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
76} 81}
77#+end_src 82```
78 83
79Finally, the tricky part is figuring out how to tell Nginx to redirect 84Finally, the tricky part is figuring out how to tell Nginx to redirect
80while keeping both a subdomain and trailing content intact. I found that 85while keeping both a subdomain and trailing content intact. I found that
81the easiest way to do this is to give it a =server= block of its own. 86the easiest way to do this is to give it a `server` block of
87its own.
82 88
83Within this block, we need to do some regex on the =server_name= line 89Within this block, we need to do some regex on the
84before we can rewrite anything. This creates a variable called 90`server_name` line before we can rewrite anything. This
85=subdomain=. 91creates a variable called `subdomain`.
86 92
87Once the server gets to the =rewrite= line, it pulls the =subdomain= 93Once the server gets to the `rewrite` line, it pulls the
88variable from above and uses it on the new =domain2.com= domain before 94`subdomain` variable from above and uses it on the new
89appending the trailing content (=$request_uri=). 95`domain2.com` domain before appending the trailing content
96(`$request_uri`).
90 97
91#+begin_src conf 98``` conf
92server { 99server {
93 listen [::]:443 ssl http2; 100 listen [::]:443 ssl http2;
94 listen 443 ssl http2; 101 listen 443 ssl http2;
@@ -100,17 +107,17 @@ server {
100 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 107 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
101 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 108 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
102} 109}
103#+end_src 110```
104 111
105That's all there is to it. With this, I simply restarted Nginx and 112That\'s all there is to it. With this, I simply restarted Nginx and
106watched the redirections work in-action. 113watched the redirections work in-action.
107 114
108#+begin_src sh 115```sh
109doas rc-service nginx restart 116doas rc-service nginx restart
110#+end_src 117```
111 118
112Looking back on it, I wish I had done this sooner. Who knows how many 119Looking back on it, I wish I had done this sooner. Who knows how many
113people went looking for my sites or bookmarks and gave up when they saw 120people went looking for my sites or bookmarks and gave up when they saw
114the redirect instructions page. 121the redirect instructions page.
115 122
116Oh well, it's done now. Live and learn. 123Oh well, it\'s done now. Live and learn.
content/blog/2022-12-17-st.md added +95
@@ -0,0 +1,95 @@
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
11terminal implementation for X made by the
12[suckless](https://suckless.org) team.
13
14This post walks through the dependencies needed and process to build and
15install `st` on Fedora Workstation.
16
17## Obtain Files
18
19To start, obtain the source files for `st` via
20`git clone`.
21
22```sh
23mkdir ~/suckless && cd ~/suckless
24git clone https://git.suckless.org/st && cd st
25```
26
27## Dependencies
28
29Once you have the files and are in the `st` directory, ensure
30the following packages are installed.
31
32```sh
33sudo dnf update && sudo dnf upgrade
34sudo dnf install gcc patch libX11-devel libXft-devel
35```
36
37## Building
38
39Before building, ensure that you read the README file.
40
41```sh
42cat README
43```
44
45Once you\'ve read the instructions, open the `config.mk` file
46and ensure it matches your setup. If you\'re not sure, leave the default
47options within the file.
48
49Finally, you can build `st` with the following command.
50Ensure you run as root (e.g., `sudo`) or else you may not end
51up with a usable application file.
52
53```sh
54sudo make clean install
55```
56
57## Customization (Patches)
58
59Note that customizing `st` requires you to modify the source
60files or to download one of the [available
61patches](https://st.suckless.org/patches/) for suckless.org.
62
63If you\'ve already installed `st` and want to customize or
64install a patch, start by uninstalling the current program.
65
66```sh
67cd ~/suckless/st
68sudo make uninstall
69```
70
71Next, grab the `<path>.diff` file from the page of the patch
72you chose. For example, I will be using the
73[defaultfontsize](https://st.suckless.org/patches/defaultfontsize/)
74patch in the below example.
75
76```sh
77wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
78```
79
80Once the file is downloaded inside the `st` folder, apply the
81patch and re-install the program. You may need to install the
82`patch` command if you don\'t have it installed already (you
83should have installed it above).
84
85```sh
86patch -i st-defaultfontsize-20210225-4ef0cbd.diff
87sudo make clean install
88```
89
90Once installed, you can use the default font size patch to launch
91`st` with any font size you wish:
92
93```sh
94st -z 16
95```
content/blog/2022-12-17-st.org deleted −87
@@ -1,87 +0,0 @@
1#+title: Simple Terminal
2#+date: 2022-12-17
3#+description: Instructions for building simple terminal on Fedora.
4#+filetags: :linux:
5
6* st
7[[https://st.suckless.org][st]] standards for Simple Terminal, a simple
8terminal implementation for X made by the
9[[https://suckless.org][suckless]] team.
10
11This post walks through the dependencies needed and process to build and
12install =st= on Fedora Workstation.
13
14** Obtain Files
15To start, obtain the source files for =st= via =git clone=.
16
17#+begin_src sh
18mkdir ~/suckless && cd ~/suckless
19git clone https://git.suckless.org/st && cd st
20#+end_src
21
22** Dependencies
23Once you have the files and are in the =st= directory, ensure the
24following packages are installed.
25
26#+begin_src sh
27sudo dnf update && sudo dnf upgrade
28sudo dnf install gcc patch libX11-devel libXft-devel
29#+end_src
30
31** Building
32Before building, ensure that you read the README file.
33
34#+begin_src sh
35cat README
36#+end_src
37
38Once you've read the instructions, open the =config.mk= file and ensure
39it matches your setup. If you're not sure, leave the default options
40within the file.
41
42Finally, you can build =st= with the following command. Ensure you run
43as root (e.g., =sudo=) or else you may not end up with a usable
44application file.
45
46#+begin_src sh
47sudo make clean install
48#+end_src
49
50** Customization (Patches)
51Note that customizing =st= requires you to modify the source files or to
52download one of the [[https://st.suckless.org/patches/][available
53patches]] for suckless.org.
54
55If you've already installed =st= and want to customize or install a
56patch, start by uninstalling the current program.
57
58#+begin_src sh
59cd ~/suckless/st
60sudo make uninstall
61#+end_src
62
63Next, grab the =<path>.diff= file from the page of the patch you chose.
64For example, I will be using the
65[[https://st.suckless.org/patches/defaultfontsize/][defaultfontsize]]
66patch in the below example.
67
68#+begin_src sh
69wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
70#+end_src
71
72Once the file is downloaded inside the =st= folder, apply the patch and
73re-install the program. You may need to install the =patch= command if
74you don't have it installed already (you should have installed it
75above).
76
77#+begin_src sh
78patch -i st-defaultfontsize-20210225-4ef0cbd.diff
79sudo make clean install
80#+end_src
81
82Once installed, you can use the default font size patch to launch =st=
83with any font size you wish:
84
85#+begin_src sh
86st -z 16
87#+end_src
content/blog/2022-12-23-alpine-desktop.org → content/blog/2022-12-23-alpine-desktop.md renamed +94 −82
@@ -1,86 +1,92 @@
1#+title: Alpine Linux as a Desktop OS 1+++
2#+date: 2022-12-23 2date = 2022-12-23
3#+description: Learn how to set up Alpine Linux with Sway to use as a desktop operating system. 3title = "Alpine Linux as a Desktop OS"
4#+filetags: :linux: 4description = ""
5#+slug: alpine-desktop 5draft = false
6+++
7
8
9# Isn\'t Alpine Linux for Servers?
6 10
7* Isn't Alpine Linux for Servers?
8This is a question I see a lot when people are presented with an example 11This is a question I see a lot when people are presented with an example
9of Alpine Linux running as a desktop OS. 12of Alpine Linux running as a desktop OS.
10 13
11While Alpine is small, fast, and minimal, that doesn't stop it from 14While Alpine is small, fast, and minimal, that doesn\'t stop it from
12functioning at a productive level for desktop users. 15functioning at a productive level for desktop users.
13 16
14This post is documentation of how I installed and modified Alpine Linux 17This post is documentation of how I installed and modified Alpine Linux
15to become my daily desktop OS. 18to become my daily desktop OS.
16 19
17* Installation 20# Installation
21
18Note that I cover the installation of Alpine Linux in my other post, so 22Note that I cover the installation of Alpine Linux in my other post, so
19I won't repeat it here: [[../alpine-linux/][Alpine Linux: My New 23I won\'t repeat it here: [Alpine Linux: My New Server
20Server OS]]. 24OS](../alpine-linux/).
21 25
22Basically, get a bootable USB or whatever you prefer with Alpine on it, 26Basically, get a bootable USB or whatever you prefer with Alpine on it,
23boot the ISO, and run the setup script. 27boot the ISO, and run the setup script.
24 28
25#+begin_src sh 29```sh
26setup-alpine 30setup-alpine
27#+end_src 31```
28 32
29Once you have gone through all the options and installer finishes 33Once you have gone through all the options and installer finishes
30without errors, reboot. 34without errors, reboot.
31 35
32#+begin_src sh 36```sh
33reboot 37reboot
34#+end_src 38```
39
40# Initial Setup
35 41
36* Initial Setup
37Once Alpine is installed and the machine has rebooted, login is as root 42Once Alpine is installed and the machine has rebooted, login is as root
38initially or =su= to root once you log in as your user. From here, you 43initially or `su` to root once you log in as your user. From
39should start by updating and upgrading the system in case the ISO was 44here, you should start by updating and upgrading the system in case the
40not fully up-to-date. 45ISO was not fully up-to-date.
41 46
42#+begin_src sh 47```sh
43# Update and upgrade system 48# Update and upgrade system
44apk -U update && apk -U upgrade 49apk -U update && apk -U upgrade
45 50
46# Add an editor so we can enable the community repository 51# Add an editor so we can enable the community repository
47apk add nano 52apk add nano
48#+end_src 53```
49 54
50You need to uncomment the =community= repository for your version of 55You need to uncomment the `community` repository for your
51Alpine Linux. 56version of Alpine Linux.
52 57
53For v3.17, the =repositories= file should look like this: 58For v3.17, the `repositories` file should look like this:
54 59
55#+begin_src sh 60```sh
56nano /etc/apk/repositories 61nano /etc/apk/repositories
57#+end_src 62```
58 63
59#+begin_src conf 64``` conf
60#/media/sda/apks 65#/media/sda/apks
61http://mirrors.gigenet.com/alpinelinux/v3.17/main 66http://mirrors.gigenet.com/alpinelinux/v3.17/main
62http://mirrors.gigenet.com/alpinelinux/v3.17/community 67http://mirrors.gigenet.com/alpinelinux/v3.17/community
63#http://mirrors.gigenet.com/alpinelinux/edge/main 68#http://mirrors.gigenet.com/alpinelinux/edge/main
64#http://mirrors.gigenet.com/alpinelinux/edge/community 69#http://mirrors.gigenet.com/alpinelinux/edge/community
65#http://mirrors.gigenet.com/alpinelinux/edge/testing 70#http://mirrors.gigenet.com/alpinelinux/edge/testing
66#+end_src 71```
67 72
68#+begin_src sh 73```sh
69# Add the rest of your packages 74# Add the rest of your packages
70apk add linux-firmware iwd doas git curl wget 75apk add linux-firmware iwd doas git curl wget
71 76
72# Add yourself to the wheel group so you can use the doas command 77# Add yourself to the wheel group so you can use the doas command
73adduser $USER wheel 78adduser $USER wheel
74#+end_src 79```
80
81# Window Manager (Desktop)
75 82
76* Window Manager (Desktop) 83The [Sway installation guide](https://wiki.alpinelinux.org/wiki/Sway)
77The [[https://wiki.alpinelinux.org/wiki/Sway][Sway installation guide]]
78has everything you need to get Sway working on Alpine. 84has everything you need to get Sway working on Alpine.
79 85
80However, I'll include a brief list of the commands I ran and their 86However, I\'ll include a brief list of the commands I ran and their
81purpose for posterity here. 87purpose for posterity here.
82 88
83#+begin_src sh 89```sh
84# Add eudev and set it up 90# Add eudev and set it up
85apk add eudev 91apk add eudev
86setup-devd udev 92setup-devd udev
@@ -117,21 +123,22 @@ apk add \ # Install optional dependencies:
117 swaylock swaylockd \ # lockscreen tool 123 swaylock swaylockd \ # lockscreen tool
118 swaybg \ # wallpaper daemon 124 swaybg \ # wallpaper daemon
119 swayidle # idle management (DPMS) daemon 125 swayidle # idle management (DPMS) daemon
120#+end_src 126```
121 127
122Once you have the packages installed and set-up, you need to export the 128Once you have the packages installed and set-up, you need to export the
123=XDG_RUNTIME_DIR= upon login. To do this, edit your =.profile= file. 129`XDG_RUNTIME_DIR` upon login. To do this, edit your
130`.profile` file.
124 131
125If you use another shell, such as =zsh=, you need to edit that shell's 132If you use another shell, such as `zsh`, you need to edit
126profile (e.g., =~/.zprofile=)! 133that shell\'s profile (e.g., `~/.zprofile`)!
127 134
128#+begin_src sh 135```sh
129nano ~/.profile 136nano ~/.profile
130#+end_src 137```
131 138
132Within the file, paste this: 139Within the file, paste this:
133 140
134#+begin_src sh 141```sh
135if test -z "${XDG_RUNTIME_DIR}"; then 142if test -z "${XDG_RUNTIME_DIR}"; then
136 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir 143 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir
137 if ! test -d "${XDG_RUNTIME_DIR}"; then 144 if ! test -d "${XDG_RUNTIME_DIR}"; then
@@ -139,19 +146,20 @@ if test -z "${XDG_RUNTIME_DIR}"; then
139 chmod 0700 "${XDG_RUNTIME_DIR}" 146 chmod 0700 "${XDG_RUNTIME_DIR}"
140 fi 147 fi
141fi 148fi
142#+end_src 149```
143 150
144Once that's complete, you can launch Sway manually. 151Once that\'s complete, you can launch Sway manually.
145 152
146#+begin_src sh 153```sh
147dbus-run-session -- sway 154dbus-run-session -- sway
148#+end_src 155```
156
157## Personal Touches
149 158
150** Personal Touches
151I also added the following packages, per my personal preferences and 159I also added the following packages, per my personal preferences and
152situation. 160situation.
153 161
154#+begin_src sh 162```sh
155doas apk add brightnessctl \ # Brightness controller 163doas apk add brightnessctl \ # Brightness controller
156 zsh \ # Shell 164 zsh \ # Shell
157 firefox \ # Browser 165 firefox \ # Browser
@@ -161,89 +169,93 @@ doas apk add brightnessctl \ # Brightness controller
161 neomutt \ # CLI email client 169 neomutt \ # CLI email client
162 thunderbird \ # GUI email client 170 thunderbird \ # GUI email client
163 gnupg # GPG key manager 171 gnupg # GPG key manager
164#+end_src 172```
165 173
166From here, I use my Syncthing storage to pull all the configuration 174From here, I use my Syncthing storage to pull all the configuration
167files I stored from prior desktops, such as 175files I stored from prior desktops, such as [my
168[[https://git.sr.ht/~cmc/dotfiles][my dotfiles]]. 176dotfiles](https://git.sr.ht/~cmc/dotfiles).
177
178# Resolving Issues
169 179
170* Resolving Issues 180## WiFi Issues
171** WiFi Issues
172I initially tried to set up my Wi-Fi the standard way with =iwd=, but it
173didn't work.
174 181
175Here is what I initially tried (I did all of this as =root=): 182I initially tried to set up my Wi-Fi the standard way with
183`iwd`, but it didn\'t work.
176 184
177#+begin_src sh 185Here is what I initially tried (I did all of this as `root`):
186
187```sh
178apk add iwd 188apk add iwd
179rc-service iwd start 189rc-service iwd start
180iwctl station wlan0 connect <SSID> # This will prompt for the password 190iwctl station wlan0 connect <SSID> # This will prompt for the password
181rc-update add iwd boot && rc-update add dbus boot 191rc-update add iwd boot && rc-update add dbus boot
182#+end_src 192```
183 193
184Then, I added the Wi-Fi entry to the bottom of the networking interface 194Then, I added the Wi-Fi entry to the bottom of the networking interface
185file: 195file:
186 196
187#+begin_src sh 197```sh
188nano /etc/network/interfaces 198nano /etc/network/interfaces
189#+end_src 199```
190 200
191#+begin_src conf 201``` conf
192auto wlan0 202auto wlan0
193iface wlan0 inet dhcp 203iface wlan0 inet dhcp
194#+end_src 204```
195 205
196Finally, restart the networking service: 206Finally, restart the networking service:
197 207
198#+begin_src sh 208```sh
199rc-service networking restart 209rc-service networking restart
200#+end_src 210```
201 211
202My Wi-Fi interface would receive an IP address from the router, but it 212My Wi-Fi interface would receive an IP address from the router, but it
203could not ping anything in the network. To solve the Wi-Fi issues, I 213could not ping anything in the network. To solve the Wi-Fi issues, I
204originally upgraded to Alpine's =edge= repositories, which was 214originally upgraded to Alpine\'s `edge` repositories, which
205unnecessary. 215was unnecessary.
206 216
207Really, the solution was to enable the =NameResolvingService=resolvconf= 217Really, the solution was to enable the
208in =/etc/iwd/main.conf=. 218`NameResolvingService=resolvconf` in
219`/etc/iwd/main.conf`.
209 220
210#+begin_src sh 221```sh
211doas nano /etc/iwd/main.conf 222doas nano /etc/iwd/main.conf
212#+end_src 223```
213 224
214#+begin_src conf 225``` conf
215[Network] 226[Network]
216 227
217NameResolvingService=resolvconf 228NameResolvingService=resolvconf
218#+end_src 229```
219 230
220Once I finished this process, my Wi-Fi is working flawlessly. 231Once I finished this process, my Wi-Fi is working flawlessly.
221 232
222** Sound Issues 233## Sound Issues
234
223Same as with the Wi-Fi, I had no sound and could not control the 235Same as with the Wi-Fi, I had no sound and could not control the
224mute/unmute or volume buttons on my laptop. 236mute/unmute or volume buttons on my laptop.
225 237
226To resolve this, I installed 238To resolve this, I installed
227[[https://wiki.alpinelinux.org/wiki/PipeWire][pipewire]]. 239[pipewire](https://wiki.alpinelinux.org/wiki/PipeWire).
228 240
229#+begin_src sh 241```sh
230# Add your user to the following groups 242# Add your user to the following groups
231addgroup $USER audio 243addgroup $USER audio
232addgroup $USER video 244addgroup $USER video
233 245
234# Install pipewire and other useful packages 246# Install pipewire and other useful packages
235apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa 247apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa
236#+end_src 248```
237 249
238Finally, I needed to add =/usr/libexec/pipewire-launcher= to my 250Finally, I needed to add `/usr/libexec/pipewire-launcher` to
239=.config/sway/config= file so that Pipewire would run every time I 251my `.config/sway/config` file so that Pipewire would run
240launched sway. 252every time I launched sway.
241 253
242#+begin_src sh 254```sh
243nano ~/.config/sway/config 255nano ~/.config/sway/config
244#+end_src 256```
245 257
246#+begin_src conf 258``` conf
247# Run pipewire audio server 259# Run pipewire audio server
248exec /usr/libexec/pipewire-launcher 260exec /usr/libexec/pipewire-launcher
249 261
@@ -252,9 +264,9 @@ bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT
252bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% 264bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5%
253bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle 265bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle
254bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle 266bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle
255#+end_src 267```
256 268
257Note that I do not use bluetooth or screen sharing, so I won't cover 269Note that I do not use bluetooth or screen sharing, so I won\'t cover
258those options in this post. 270those options in this post.
259 271
260Other than these issues, I have a working Alpine desktop. No other 272Other than these issues, I have a working Alpine desktop. No other
content/blog/2023-01-03-recent-website-changes.org → content/blog/2023-01-03-recent-website-changes.md renamed +45 −33
@@ -1,36 +1,44 @@
1#+title: Recent Website Changes 1+++
2#+date: 2023-01-03 2date = 2023-01-03
3#+description: A retrospective of recent changes to my website. 3title = "Recent Website Changes"
4#+filetags: :dev: 4description = ""
5draft = false
6+++
7
8# The State of This Website
5 9
6* The State of This Website
7Over the years, this website has changed URLs, styles, content focus, 10Over the years, this website has changed URLs, styles, content focus,
8and much more. However, it seems that I am never done, as I am yet again 11and much more. However, it seems that I am never done, as I am yet again
9changing this page to fit my current needs and wants. 12changing this page to fit my current needs and wants.
10 13
11While this site was already minimal (~12kb), it contained a lot of 14While this site was already minimal (\~12kb), it contained a lot of
12disorganized content and some poorly chosen color schemes. 15disorganized content and some poorly chosen color schemes.
13 16
14The recent updates attempt to fix these items while focusing on what I 17The recent updates attempt to fix these items while focusing on what I
15truly care about here: the content within each page. 18truly care about here: the content within each page.
16 19
17* Recent Changes 20# Recent Changes
18I've made quite a few changes to the style of this website today, both 21
22I\'ve made quite a few changes to the style of this website today, both
19in the CSS and in the HTML. 23in the CSS and in the HTML.
20 24
21Here are some of the key takeaways from today's changes: 25Here are some of the key takeaways from today\'s changes:
26
271. Reduce the width of the website from `60em` to
28 `40em`.
292. Remove breadcrumb navigation and replaced with a simple \"Return
30 Home\" link when visiting anything other than the homepage.
313. Remove syntax highlighting from code blocks. CSS now loads from a
32 single file.
334. Move blog posts on the homepage to the top, pushing tertiary content
34 down.
355. Update font-family from `monospace` to
36 `sans-serif` for readability.
37
38# Future Focus
22 39
231. Reduce the width of the website from =60em= to =40em=. 40## Accessibility
242. Remove breadcrumb navigation and replaced with a simple "Return Home"
25 link when visiting anything other than the homepage.
263. Remove syntax highlighting from code blocks. CSS now loads from a
27 single file.
284. Move blog posts on the homepage to the top, pushing tertiary content
29 down.
305. Update font-family from =monospace= to =sans-serif= for readability.
31 41
32* Future Focus
33** Accessibility
34My main focus amidst all the changes this past year was accessibility. I 42My main focus amidst all the changes this past year was accessibility. I
35tried to improve the accessibility of all pages on this site as well as 43tried to improve the accessibility of all pages on this site as well as
36I know how. For example, I added aria labels to identify different 44I know how. For example, I added aria labels to identify different
@@ -39,7 +47,8 @@ navigation menus in November.
39I want to continue this in 2023 and learn more about the accessibility 47I want to continue this in 2023 and learn more about the accessibility
40features I can utilize to help those who may need them. 48features I can utilize to help those who may need them.
41 49
42** Content First 50## Content First
51
43Beyond accessibility, I am making a concerted effort to bring the 52Beyond accessibility, I am making a concerted effort to bring the
44content of this site to the forefront and push the tertiary information 53content of this site to the forefront and push the tertiary information
45(e.g., About Me, Services, etc.) down below the content. 54(e.g., About Me, Services, etc.) down below the content.
@@ -48,27 +57,30 @@ Further, I want to review and edit previous blog posts for grammar,
48accuracy, dead links, and more. Where necessary, I may even add a blurb 57accuracy, dead links, and more. Where necessary, I may even add a blurb
49at the top of old posts that are no longer accurate or helpful. 58at the top of old posts that are no longer accurate or helpful.
50 59
51** Style 60## Style
61
52As always, I will be searching for ways to reduce distractions and bring 62As always, I will be searching for ways to reduce distractions and bring
53a minimalistic approach to this site. While there are certainly more 63a minimalistic approach to this site. While there are certainly more
54drastic measures I could take, such as making this a 64drastic measures I could take, such as making this a [Shinobi
55[[https://shinobi.bt.ht][Shinobi Website]] or a [[https://nocss.club][No 65Website](https://shinobi.bt.ht) or a [No CSS
56CSS Website]], I prefer to keep some modern features here. 66Website](https://nocss.club), I prefer to keep some modern features
67here.
57 68
58Two ideas have been floating around in my head, but I am not sure how I 69Two ideas have been floating around in my head, but I am not sure how I
59feel about these approaches yet: 70feel about these approaches yet:
60 71
611. Going all-in with Emacs org-mode and creating a script to 721. Going all-in with Emacs org-mode and creating a script to
62 auto-publish all my blog posts from org-mode to plain HTML files and 73 auto-publish all my blog posts from org-mode to plain HTML files and
63 then letting my CSS style it after the fact. 74 then letting my CSS style it after the fact.
642. Focus on publishing for Gemini or Gopher and then utilize a 752. Focus on publishing for Gemini or Gopher and then utilize a
65 conversion program to translate the pages to HTML. 76 conversion program to translate the pages to HTML.
77
78# Looking Back
66 79
67* Looking Back
68As I sit here and finalize the changes, as well as this post, I am happy 80As I sit here and finalize the changes, as well as this post, I am happy
69with the current state of this website. It's certainly a long way from 81with the current state of this website. It\'s certainly a long way from
70my first attempts (parallax background images, anyone?) and it's good to 82my first attempts (parallax background images, anyone?) and it\'s good
71see the progress. 83to see the progress.
72 84
73I can only hope and work hard to ensure that I make more meaningful 85I can only hope and work hard to ensure that I make more meaningful
74progress by this time next year. 86progress by this time next year.
content/blog/2023-01-05-mass-unlike-tumblr-posts.org → content/blog/2023-01-05-mass-unlike-tumblr-posts.md renamed +43 −35
@@ -1,15 +1,19 @@
1#+title: How to Easily Mass Unlike Tumblr Posts with Javascript 1+++
2#+date: 2023-01-05 2date = 2023-01-05
3#+description: Learn how to unlike Tumblr posts en masse in the browser. 3title = " How to Easily Mass Unlike Tumblr Posts with Javascript"
4#+filetags: :dev: 4description = ""
5draft = false
6+++
7
8# The Dilemma
5 9
6* The Dilemma
7The dilemma I had was pretty simple: I wanted to unlike all the posts I 10The dilemma I had was pretty simple: I wanted to unlike all the posts I
8have liked on Tumblr so that I could follow a new focus on blogs and 11have liked on Tumblr so that I could follow a new focus on blogs and
9start fresh. Otherwise, Tumblr will keep recommending content based on 12start fresh. Otherwise, Tumblr will keep recommending content based on
10your previous likes. 13your previous likes.
11 14
12* The Solution 15# The Solution
16
13I searched the web for a while and noted that most solutions referenced 17I searched the web for a while and noted that most solutions referenced
14Tumblr setting and dashboard pages that no longer exist. Additionally, I 18Tumblr setting and dashboard pages that no longer exist. Additionally, I
15did not want to install a third party extension to do this, as some 19did not want to install a third party extension to do this, as some
@@ -19,12 +23,13 @@ Luckily, I used Javascript for a while a few years ago and figured it
19would be easy enough to script a solution, as long as Tumblr had a 23would be easy enough to script a solution, as long as Tumblr had a
20system for the unlike buttons. 24system for the unlike buttons.
21 25
22** Identifying Unlike Buttons 26## Identifying Unlike Buttons
23Tumblr's unlike buttons are structured as you can see in the following 27
24code block. All unlike buttons have an =aria-label= with a value of 28Tumblr\'s unlike buttons are structured as you can see in the following
25=Unlike=. 29code block. All unlike buttons have an `aria-label` with a
30value of `Unlike`.
26 31
27#+begin_src html 32``` html
28<button class="TRX6J" aria-label="Unlike"> 33<button class="TRX6J" aria-label="Unlike">
29 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1"> 34 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1">
30 <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23" 35 <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23"
@@ -33,53 +38,56 @@ code block. All unlike buttons have an =aria-label= with a value of
33 </svg> 38 </svg>
34 </span> 39 </span>
35</button> 40</button>
36#+end_src 41```
37 42
38** Running a Script to Unlike All Likes 43## Running a Script to Unlike All Likes
39To run this script, you will need to load the 44
40[[https://www.tumblr.com/likes][Likes | Tumblr]] page while logged in to 45To run this script, you will need to load the [Likes \|
41your account. 46Tumblr](https://www.tumblr.com/likes) page while logged in to your
47account.
42 48
43Further, be sure to scroll down to the bottom and force Tumblr to load 49Further, be sure to scroll down to the bottom and force Tumblr to load
44more posts so that this script unlikes more posts at a time. 50more posts so that this script unlikes more posts at a time.
45 51
46Once you are logged in and the page is loaded, open the Developer Tools 52Once you are logged in and the page is loaded, open the Developer Tools
47and be sure you're on the "Console" tab. It should look something like 53and be sure you\'re on the \"Console\" tab. It should look something
48this (this is in Firefox, Chromium should be similar): 54like this (this is in Firefox, Chromium should be similar):
49 55
50#+caption: Firefox Dev !Tools 56![Firefox Dev
51[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png]] 57!Tools](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png)
52 58
53All you need to do is paste the following snippet into the dev console. 59All you need to do is paste the following snippet into the dev console.
54This code will collect all unlike buttons (=elements=) and then click 60This code will collect all unlike buttons (`elements`) and
55each button to unlike it. 61then click each button to unlike it.
56 62
57Optionally, you can comment-out the line =elements[i].click();= and 63Optionally, you can comment-out the line
58uncomment the =console.log()= lines to simply print out information 64`elements[i].click();` and uncomment the
59without performing any actions. This can be useful to debug issues or 65`console.log()` lines to simply print out information without
60confirm that the code below isn't doing anything you don't want it to. 66performing any actions. This can be useful to debug issues or confirm
67that the code below isn\'t doing anything you don\'t want it to.
61 68
62#+begin_src javascript 69``` javascript
63const elements = document.querySelectorAll('[aria-label="Unlike"]'); 70const elements = document.querySelectorAll('[aria-label="Unlike"]');
64// console.log(elements); // 👉 [button] 71// console.log(elements); // 👉 [button]
65 72
66for (let i=0; i < elements.length; i++) { 73for (let i=0; i < elements.length; i++) {
67 // console.log(elements[i]); 74 // console.log(elements[i]);
68 elements[i].click(); 75 elements[i].click();
69} 76}
70#+end_src 77```
78
79# Results
71 80
72* Results 81The results were quick for my situation, as it unliked \~200 posts
73The results were quick for my situation, as it unliked ~200 posts within 82within 2-3 seconds. I am not sure how this will perform on larger sets
742-3 seconds. I am not sure how this will perform on larger sets of likes 83of likes (or if Tumblr has a limit to unliking posts).
75(or if Tumblr has a limit to unliking posts).
76 84
77You can see the below screenshot showing that I pasted the snippet into 85You can see the below screenshot showing that I pasted the snippet into
78the console, pressed Enter, and then the posts are automatically 86the console, pressed Enter, and then the posts are automatically
79unliked. 87unliked.
80 88
81#+caption: Script !Results 89![Script
82[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png]] 90!Results](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png)
83 91
84Thinking about this further, I would bet that this would be fairly 92Thinking about this further, I would bet that this would be fairly
85simple to package into a browser add-on so that users could install the 93simple to package into a browser add-on so that users could install the
content/blog/2023-01-08-fedora-login-manager.org → content/blog/2023-01-08-fedora-login-manager.md renamed +23 −18
@@ -1,10 +1,13 @@
1#+title: How to Remove the Login Manager from Fedora i3 1+++
2#+date: 2023-01-08 2date = 2023-01-08
3#+description: Learn how to completely remove the login manager from Fedora i3. 3title = "How to Remove the Login Manager from Fedora i3"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
5 7
6* Fedora i3's Login Manager 8# Fedora i3\'s Login Manager
7Since I use the i3 spin of Fedora Workstation, I don't like to have a 9
10Since I use the i3 spin of Fedora Workstation, I don\'t like to have a
8login manager installed by default. As of the current version of Fedora 11login manager installed by default. As of the current version of Fedora
9i3, the default login manager is LightDM. 12i3, the default login manager is LightDM.
10 13
@@ -12,29 +15,31 @@ If this is no longer the case, you can search for currently-installed
12packages with the following command and see if you can identify a 15packages with the following command and see if you can identify a
13different login manager. 16different login manager.
14 17
15#+begin_src sh 18```sh
16sudo dnf list installed 19sudo dnf list installed
17#+end_src 20```
21
22# Removing the Login Manager
18 23
19* Removing the Login Manager
20In order to remove the login manager, simply uninstall the package. 24In order to remove the login manager, simply uninstall the package.
21 25
22#+begin_src sh 26```sh
23sudo dnf remove lightdm 27sudo dnf remove lightdm
24#+end_src 28```
29
30# Launching i3 Manually
25 31
26* Launching i3 Manually
27In order to launch i3 manually, you need to set up your X session 32In order to launch i3 manually, you need to set up your X session
28properly. To start, create or edit the =~/.xinitrc= file to include the 33properly. To start, create or edit the `~/.xinitrc` file to
29following at the bottom. 34include the following at the bottom.
30 35
31#+begin_src config 36``` config
32exec i3 37exec i3
33#+end_src 38```
34 39
35Now, whenever you log in to the TTY, you can launch your desktop with 40Now, whenever you log in to the TTY, you can launch your desktop with
36the following command. 41the following command.
37 42
38#+begin_src sh 43```sh
39startx 44startx
40#+end_src 45```
content/blog/2023-01-21-flatpak-symlinks.md added +51
@@ -0,0 +1,51 @@
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
11on your system for one reason or another, you likely get annoyed with
12opening a terminal and manually running the Flatpak app with the lengthy
13`flatpak run ...` command.
14
15In the past, I manually created aliases in my `.zshrc` file
16for certain apps. For example, an alias would look like the example
17below.
18
19This would allow me to run the command fast within the terminal, but it
20wouldn\'t allow me to run it in an application launcher.
21
22```sh
23# ~/.zshrc
24alias librewolf = "flatpak run io.gitlab.librewolf-community"
25```
26
27However, I now use a much faster and better method that integrates with
28the tiling WMs I use and their application launchers -
29`dmenu` and `bemenu`.
30
31# Creating Symlinks for Flatpak Apps
32
33Let\'s use the example of Librewolf below. I can install the application
34like so:
35
36```sh
37flatpak install flathub io.gitlab.librewolf-community
38```
39
40Once installed, I can create a symlink to link the flatpak app to my new
41symlink in a location commonly included in your PATH. In this case, I
42chose `/usr/bin`. You may need to choose a different location
43if `/usr/bin` isn\'t in your PATH.
44
45```sh
46ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf
47```
48
49Once complete, you should be able to launch the app using the command
50name you chose above in the symlink (`librewolf`) from a
51terminal or from your application launcher!
content/blog/2023-01-21-flatpak-symlinks.org deleted −46
@@ -1,46 +0,0 @@
1#+title: Running Flatpak Apps with Symlinks
2#+date: 2023-01-21
3#+description: Learn how to run Flatpak apps through menu launchers with symlinks.
4#+filetags: :linux:
5
6* Running Flatpak Apps Should Be Faster
7If you're like me and use Flatpak for those pesky apps that cannot run
8on your system for one reason or another, you likely get annoyed with
9opening a terminal and manually running the Flatpak app with the lengthy
10=flatpak run ...= command.
11
12In the past, I manually created aliases in my =.zshrc= file for certain
13apps. For example, an alias would look like the example below.
14
15This would allow me to run the command fast within the terminal, but it
16wouldn't allow me to run it in an application launcher.
17
18#+begin_src sh
19# ~/.zshrc
20alias librewolf = "flatpak run io.gitlab.librewolf-community"
21#+end_src
22
23However, I now use a much faster and better method that integrates with
24the tiling WMs I use and their application launchers - =dmenu= and
25=bemenu=.
26
27* Creating Symlinks for Flatpak Apps
28Let's use the example of Librewolf below. I can install the application
29like so:
30
31#+begin_src sh
32flatpak install flathub io.gitlab.librewolf-community
33#+end_src
34
35Once installed, I can create a symlink to link the flatpak app to my new
36symlink in a location commonly included in your PATH. In this case, I
37chose =/usr/bin=. You may need to choose a different location if
38=/usr/bin= isn't in your PATH.
39
40#+begin_src sh
41ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf
42#+end_src
43
44Once complete, you should be able to launch the app using the command
45name you chose above in the symlink (=librewolf=) from a terminal or
46from your application launcher!
content/blog/2023-01-23-random-wireguard.md added +117
@@ -0,0 +1,117 @@
1+++
2date = 2023-01-23
3title = "Connecting to a Random Mullvad Wireguard Host on Boot"
4description = ""
5draft = false
6+++
7
8# Mullvad Wireguard
9
10If you\'re using an OS that does not support one of Mullvad\'s apps,
11you\'re likely using the Wireguard configuration files instead.
12
13If not, the first step is to visit Mullvad\'s [Wireguard configuration
14files](https://mullvad.net/en/account/#/wireguard-config) page and
15download a ZIP of the configuration files you want to use.
16
17Personally, I downloaded all configuration files across the world and
18chose my connections using the script below.
19
20Once the files are downloaded, unzip them and move them to your
21preferred location:
22
23```sh
24cd Downloads
25unzip mullvad_wireguard_linux_all_all.zip
26mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/
27```
28
29### Creating a Script to Connect to a Random Host
30
31Once you have a folder of Wireguard configuration files from Mullvad,
32you can create a script to randomly connect to any one of the locations.
33
34Start by creating a shell script - mine is called `vpn.sh`.
35
36```sh
37nano ~/vpn.sh
38```
39
40Within this script, you can paste the following info. Note that I
41specify `us-*` in my script, which means that it will only
42consider US-based VPN locations. You can alter this or simply change it
43`*` to consider all locations.
44
45```sh
46#!/bin/sh
47
48ls /home/$USER/mullvad/us-** |sort -R |tail -n 1 |while read file; do
49 # Replace `doas` with `sudo` if your machine uses `sudo`,
50 # or remove `doas` if users don't need to su to run wg-quick
51 doas wg-quick up $file;
52 printf "\nCreated Mullvad wireguard connection with file: $file";
53 printf "\n\nPrinting new IP info:\n"
54 curl https://am.i.mullvad.net/connected
55done
56```
57
58Once you\'ve modified the script to your liking, add executable
59permissions and run the script:
60
61```sh
62chmod +x ~/vpn.sh
63~/vpn.sh
64```
65
66The output should look like the following:
67
68``` txt
69doas (user@host) password:
70
71# ... The script will process all of the iptables and wg commands here
72
73Created Mullvad wireguard connection with file: /home/user/mullvad/us-nyc-wg-210.conf
74
75Printing new IP info:
76You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99
77```
78
79That\'s all there is to it. You can see your new location and IP via the
80`printf` and `curl` commands included in the
81script.
82
83You can also go to the [Connection Check ​\|
84Mullvad](https://mullvad.net/en/check/) page to see if you are fully
85connected to Mullvad and if any leaks exist.
86
87![Mullvad Connection
88Check](https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png)
89
90# Disconnecting from the Wireguard Connection
91
92If you forget which connection you\'re using, you can execute the
93following command to see where Wireguard is currently connected:
94
95```sh
96wg show
97```
98
99This command will show you the Wireguard interfaces and should output a
100connection like so: `interface: us-lax-wg-104`.
101
102Once you have this, just disconnect using that files\' full path:
103
104```sh
105wg-quick down /home/user/mullvad/us-lax-wg-104.conf
106```
107
108I have a TODO item on figuring out how to easily export an environment
109variable that contains the configuration file\'s full name, so that I
110can just execute the following:
111
112```sh
113# Ideal situation if I can export the $file variable to the environment
114wg-quick down $file
115```
116
117If you have an idea on how to do this, email me!
content/blog/2023-01-23-random-wireguard.org deleted −112
@@ -1,112 +0,0 @@
1#+title: Connecting to a Random Mullvad Wireguard Host on Boot
2#+date: 2023-01-23
3#+description: Learn how to connect to a random Mullvad Wireguard host on boot.
4#+filetags: :linux:
5
6* Mullvad Wireguard
7If you're using an OS that does not support one of Mullvad's apps,
8you're likely using the Wireguard configuration files instead.
9
10If not, the first step is to visit Mullvad's
11[[https://mullvad.net/en/account/#/wireguard-config][Wireguard
12configuration files]] page and download a ZIP of the configuration files
13you want to use.
14
15Personally, I downloaded all configuration files across the world and
16chose my connections using the script below.
17
18Once the files are downloaded, unzip them and move them to your
19preferred location:
20
21#+begin_src sh
22cd Downloads
23unzip mullvad_wireguard_linux_all_all.zip
24mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/
25#+end_src
26
27*** Creating a Script to Connect to a Random Host
28Once you have a folder of Wireguard configuration files from Mullvad,
29you can create a script to randomly connect to any one of the locations.
30
31Start by creating a shell script - mine is called =vpn.sh=.
32
33#+begin_src sh
34nano ~/vpn.sh
35#+end_src
36
37Within this script, you can paste the following info. Note that I
38specify =us-*= in my script, which means that it will only consider
39US-based VPN locations. You can alter this or simply change it =*= to
40consider all locations.
41
42#+begin_src sh
43#!/bin/sh
44
45ls /home/$USER/mullvad/us-** |sort -R |tail -n 1 |while read file; do
46 # Replace `doas` with `sudo` if your machine uses `sudo`,
47 # or remove `doas` if users don't need to su to run wg-quick
48 doas wg-quick up $file;
49 printf "\nCreated Mullvad wireguard connection with file: $file";
50 printf "\n\nPrinting new IP info:\n"
51 curl https://am.i.mullvad.net/connected
52done
53#+end_src
54
55Once you've modified the script to your liking, add executable
56permissions and run the script:
57
58#+begin_src sh
59chmod +x ~/vpn.sh
60~/vpn.sh
61#+end_src
62
63The output should look like the following:
64
65#+begin_src txt
66doas (user@host) password:
67
68# ... The script will process all of the iptables and wg commands here
69
70Created Mullvad wireguard connection with file: /home/user/mullvad/us-nyc-wg-210.conf
71
72Printing new IP info:
73You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99
74#+end_src
75
76That's all there is to it. You can see your new location and IP via the
77=printf= and =curl= commands included in the script.
78
79You can also go to the [[https://mullvad.net/en/check/][Connection Check
80​| Mullvad]] page to see if you are fully connected to Mullvad and if any
81leaks exist.
82
83#+caption: Mullvad Connection Check
84[[https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png]]
85
86* Disconnecting from the Wireguard Connection
87If you forget which connection you're using, you can execute the
88following command to see where Wireguard is currently connected:
89
90#+begin_src sh
91wg show
92#+end_src
93
94This command will show you the Wireguard interfaces and should output a
95connection like so: =interface: us-lax-wg-104=.
96
97Once you have this, just disconnect using that files' full path:
98
99#+begin_src sh
100wg-quick down /home/user/mullvad/us-lax-wg-104.conf
101#+end_src
102
103I have a TODO item on figuring out how to easily export an environment
104variable that contains the configuration file's full name, so that I can
105just execute the following:
106
107#+begin_src sh
108# Ideal situation if I can export the $file variable to the environment
109wg-quick down $file
110#+end_src
111
112If you have an idea on how to do this, email me!
content/blog/2023-01-28-self-hosting-wger.md added +154
@@ -0,0 +1,154 @@
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
11those who enjoy self-hosting their data. You can also register an
12account on their main website if you\'d prefer to try without
13self-hosting.
14
15## Features
16
17I didn\'t see a full listing of features anywhere, so I compiled this
18list of my own after installing wger:
19
201. Dashboard
21
22 - Dashboard view of Workout Schedule, Nutrition Plan, Weight
23 Graph, & last 5 Weight Logs
24
252. Training
26
27 - Workout Log
28 - Workout Schedule
29 - Calendar (shows weight logs and Bad/Neutral/Good days)
30 - Gallery (shows images you upload)
31 - Workout templates
32 - Public templates
33 - Exercises
34
353. Nutrition
36
37 - Nutrition plans
38 - BMI calculator
39 - Daily calories calculator
40 - Ingredient overview
41
424. Body Weight
43
44 - Weight overview
45
46## Documentation
47
48In order to self-host wger, I opted to use the Docker version of the
49application. You can read the README within the
50[wger-project/docker](https://github.com/wger-project/docker) project on
51GitHub for information and installation instructions.
52
53## Installation
54
55To start the installation, I created a folder for wger and started
56creating the three necessary files:
57
58```sh
59mkdir ~/wger && mkdir ~/wger/config
60touch ~/wger/docker-compose.yml && \
61touch ~/wger/config/prod.env && \
62touch ~/wger/config/nginx.conf
63```
64
65Once you have the folders and files created, you will need to copy the
66contents of the `docker-compose.yml`, `prod.env`,
67and `nginx.conf` from the GitHub link above.
68
69A few notes to explain the changes I made to the default files:
70
71- I updated the `ALLOW_REGISTRAION` variable in
72 `prod.env` to `False` after I created an
73 account via my LAN connection, **before\*** I connected this app to
74 a publicly-available domain.
75- I uncommented and updated `CSRF_TRUSTED_ORIGINS` to be
76 equal to the public version of this app:
77 `https://wger.example.com`.
78- I updated the port within `docker-compose.yml`, within
79 the `nginx` block. The port I updated this to will be
80 reflected in my nginx configuration file on the server (NOT the wger
81 nginx.conf file).
82
83## Deploy
84
85Once all files are created and modified to your needs, simply start the
86container.
87
88```sh
89docker-compose up -d
90```
91
92You can now visit the website on your LAN by going to
93`localhost:YOUR_PORT` or by the server\'s IP, if you\'re not
94on the same machine that is running the container.
95
96If you wish to connect this app to a public domain name, you\'ll need to
97point an `A` DNS record from the domain to your server\'s
98public IP. You\'ll then need to create a configuration file for
99whichever web server or reverse proxy you\'re using.
100
101Wger\'s README suggests the following reverse proxy configuration for
102Nginx:
103
104``` conf
105upstream wger {
106 # This port should match the port in the `nginx` block of docker-compose.yml
107 # If the container is running on this same machine, replace this with
108 # server 127.0.0.1:8080
109 server 123.456.789.0:8080;
110}
111
112server {
113 listen 80;
114 listen [::]:443 ssl;
115 listen 443 ssl;
116
117 location / {
118 proxy_pass http://wger;
119 proxy_set_header Host $http_host;
120 proxy_set_header X-Real-IP $remote_addr;
121 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
122 proxy_set_header X-Forwarded-Proto $scheme;
123 proxy_redirect off;
124 }
125
126 server_name my.domain.example.com;
127
128 ssl_certificate /path/to/https/certificate.crt;
129 ssl_certificate_key /path/to/https/certificate.key;
130}
131```
132
133# Thoughts on Wger
134
135I\'m still playing around with the app itself, but it seems to be a
136solid all-around workout manager, weight log, and food log.
137
138I like that the weight log graph is fluid and updates quickly. You can
139also import or export data in CSV format if you\'d like to move your
140data elsewhere.
141
142The workout manager is slightly odd, as it requires you to enter sets
143and reps for each exercise when you enter it into the plan. Then, when
144you add a log entry for performing a workout, you then add what you
145actually performed, in terms of reps and weight.
146
147I haven\'t tried the food log yet and I likely will not, at least for a
148while. I have no need for a food log or calorie tracker at the moment.
149
150## Screenshot Example
151
152You can see an example of a dashboard with placeholder data here:
153
154![wger dashboard](https://img.cleberg.net/blog/20230128-wger/wger.png)
content/blog/2023-01-28-self-hosting-wger.org deleted −143
@@ -1,143 +0,0 @@
1#+title: Self-Hosting Wger Workout Manager
2#+date: 2023-01-28
3#+description: A guide to self-hosting the Wger application on your own server.
4#+filetags: :selfhosting:
5
6* Wger: The Self-Hosted Workout Manager
7[[https://wger.de][Wger Workout Manager]] is a fitness tracking tool for
8those who enjoy self-hosting their data. You can also register an
9account on their main website if you'd prefer to try without
10self-hosting.
11
12** Features
13I didn't see a full listing of features anywhere, so I compiled this
14list of my own after installing wger:
15
161. Dashboard
17
18 - Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, &
19 last 5 Weight Logs
20
212. Training
22
23 - Workout Log
24 - Workout Schedule
25 - Calendar (shows weight logs and Bad/Neutral/Good days)
26 - Gallery (shows images you upload)
27 - Workout templates
28 - Public templates
29 - Exercises
30
313. Nutrition
32
33 - Nutrition plans
34 - BMI calculator
35 - Daily calories calculator
36 - Ingredient overview
37
384. Body Weight
39
40 - Weight overview
41
42** Documentation
43In order to self-host wger, I opted to use the Docker version of the
44application. You can read the README within the
45[[https://github.com/wger-project/docker][wger-project/docker]] project
46on GitHub for information and installation instructions.
47
48** Installation
49To start the installation, I created a folder for wger and started
50creating the three necessary files:
51
52#+begin_src sh
53mkdir ~/wger && mkdir ~/wger/config
54touch ~/wger/docker-compose.yml && \
55touch ~/wger/config/prod.env && \
56touch ~/wger/config/nginx.conf
57#+end_src
58
59Once you have the folders and files created, you will need to copy the
60contents of the =docker-compose.yml=, =prod.env=, and =nginx.conf= from
61the GitHub link above.
62
63A few notes to explain the changes I made to the default files:
64
65- I updated the =ALLOW_REGISTRAION= variable in =prod.env= to =False=
66 after I created an account via my LAN connection, *before** I connected
67 this app to a publicly-available domain.
68- I uncommented and updated =CSRF_TRUSTED_ORIGINS= to be equal to the
69 public version of this app: =https://wger.example.com=.
70- I updated the port within =docker-compose.yml=, within the =nginx=
71 block. The port I updated this to will be reflected in my nginx
72 configuration file on the server (NOT the wger nginx.conf file).
73
74** Deploy
75Once all files are created and modified to your needs, simply start the
76container.
77
78#+begin_src sh
79docker-compose up -d
80#+end_src
81
82You can now visit the website on your LAN by going to
83=localhost:YOUR_PORT= or by the server's IP, if you're not on the same
84machine that is running the container.
85
86If you wish to connect this app to a public domain name, you'll need to
87point an =A= DNS record from the domain to your server's public IP.
88You'll then need to create a configuration file for whichever web server
89or reverse proxy you're using.
90
91Wger's README suggests the following reverse proxy configuration for
92Nginx:
93
94#+begin_src conf
95upstream wger {
96 # This port should match the port in the `nginx` block of docker-compose.yml
97 # If the container is running on this same machine, replace this with
98 # server 127.0.0.1:8080
99 server 123.456.789.0:8080;
100}
101
102server {
103 listen 80;
104 listen [::]:443 ssl;
105 listen 443 ssl;
106
107 location / {
108 proxy_pass http://wger;
109 proxy_set_header Host $http_host;
110 proxy_set_header X-Real-IP $remote_addr;
111 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
112 proxy_set_header X-Forwarded-Proto $scheme;
113 proxy_redirect off;
114 }
115
116 server_name my.domain.example.com;
117
118 ssl_certificate /path/to/https/certificate.crt;
119 ssl_certificate_key /path/to/https/certificate.key;
120}
121#+end_src
122
123* Thoughts on Wger
124I'm still playing around with the app itself, but it seems to be a solid
125all-around workout manager, weight log, and food log.
126
127I like that the weight log graph is fluid and updates quickly. You can
128also import or export data in CSV format if you'd like to move your data
129elsewhere.
130
131The workout manager is slightly odd, as it requires you to enter sets
132and reps for each exercise when you enter it into the plan. Then, when
133you add a log entry for performing a workout, you then add what you
134actually performed, in terms of reps and weight.
135
136I haven't tried the food log yet and I likely will not, at least for a
137while. I have no need for a food log or calorie tracker at the moment.
138
139** Screenshot Example
140You can see an example of a dashboard with placeholder data here:
141
142#+caption: wger dashboard
143[[https://img.cleberg.net/blog/20230128-wger/wger.png]]
content/blog/2023-02-02-exploring-hare.md added +172
@@ -0,0 +1,172 @@
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
11short. I won\'t be going into depth on the specification or anything
12that technical.
13
14Instead, I will simply be talking about how I (a relatively basic
15hobbyist programmer) have been playing with Hare and what intrigues me
16about the language.
17
18# Hare
19
20The [Hare](https://harelang.org) programming language is a
21straightforward language that should look familiar if you\'ve ever
22programmed with C, Rust, or other languages that aim to build software
23at the system-level.
24
25The Hare homepage states the following:
26
27> Hare is a systems programming language designed to be simple, stable,
28> and robust. Hare uses a static type system, manual memory management,
29> and minimal runtime. It is well-suited to writing operating systems,
30> system tools, compilers, networking software, and other low-level,
31> high performance tasks.
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`
41repositories.
42
43```sh
44doas apk add hare hare-doc
45```
46
47However, I was able to install Hare from scratch on Fedora Linux a short
48while ago, which was also very easy to do. If you need further
49instructions and Hare doesn\'t have a package on your system, take a
50look at the [Hare Installation](https://harelang.org/installation/)
51page.
52
53## Creating a Test Project
54
55In order to play with the language, I created
56[hare-test](https://git.sr.ht/~cmc/hare-projects) and will be putting
57any of my Hare-related adventures in here.
58
59> **Update:\*** I also created a simple Hare program for creating a file
60> from user input:
61> [files.ha](https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha)
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
68random number generation and passing parameters between functions.
69
70```sh
71nano rgb.ha
72```
73
74Within this file, I was able to easily import a few of the [standard
75library modules](https://harelang.org/tutorials/stdlib/):
76`fmt`, `math::random`, and `datetime`.
77
78With these modules, I created two functions:
79
801. `main`: This function calls the `generate_rgb`
81 function and then prints out the returned values.
822. `generate_rgb`: This function uses the current Unix epoch
83 time to generate a pseudo-random value and uses this value to create
84 three more random values between 0 and 255. These three numbers
85 represent a color in RGB format.
86
87> **Note**: Some syntax coloring may look odd, as Zola currently
88> doesn\'t have a syntax highlighting theme for Hare. Instead, I\'m
89> using the C theme, which may not be exactly accurate when coloring the
90> code below.
91
92```c
93use datetime;
94use fmt;
95use math::random;
96
97export fn main() void = {
98 const rgb = generate_rgb();
99 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
100};
101
102fn generate_rgb() []u64 = {
103 // Use the current Unix epoch time as the seed value
104 let datetime = datetime::epochunix(&datetime::now());
105
106 // Generate initial pseudo-random value
107 // You must cast the datetime from int to u64
108 let x = random::init(datetime: u64);
109
110 // Generate RGB values between (0, 255) using pseudo-random init value
111 let r = random::u64n(&x, 255);
112 let g = random::u64n(&x, 255);
113 let b = random::u64n(&x, 255);
114
115 // Structure data as array and return
116 let rgb_array: [3]u64 = [r, g, b];
117 return rgb_array;
118};
119```
120
121## Running a Program
122
123Once you have a Hare file written and ready to run, you simply need to
124run it:
125
126```sh
127hare run file.ha
128```
129
130You can also compile the program into an executable:
131
132```sh
133hare build -o example file.ha
134./example
135```
136
137## Initial Thoughts
138
1391. Documentation Improvements Would Help
140
141 While I was able to piece everything together eventually, the
142 biggest downfall right now in Hare\'s documentation. For such a new
143 project, the documentation is in a great spot. However, bare
144 specifications don\'t help as much as a brief examples section
145 would.
146
147 For example, it took me a while to figure out what the
148 `u64n` function was looking for. I could tell that it
149 took two parameters and the second was my max value (255), but
150 couldn\'t figure out what the first value should be. Eventually, I
151 inspected the `random.ha` file in the [Hare source
152 code](https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha)
153 and found the test suite that helped me discover that it needed an
154 `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
172 it and see if I can make anything of particular value.
content/blog/2023-02-02-exploring-hare.org deleted −169
@@ -1,169 +0,0 @@
1#+title: Exploring the Hare Programming Language
2#+date: 2023-02-02
3#+description: A retrospective on my first time using the Hare Programming Language.
4#+filetags: :dev:
5
6* A Quick Note
7By no means am I a professional developer, so this post will be rather
8short. I won't be going into depth on the specification or anything that
9technical.
10
11Instead, I will simply be talking about how I (a relatively basic
12hobbyist programmer) have been playing with Hare and what intrigues me
13about the language.
14
15* Hare
16The [[https://harelang.org][Hare]] programming language is a
17straightforward language that should look familiar if you've ever
18programmed with C, Rust, or other languages that aim to build software
19at the system-level.
20
21The Hare homepage states the following:
22
23#+begin_quote
24Hare is a systems programming language designed to be simple, stable,
25and robust. Hare uses a static type system, manual memory management,
26and minimal runtime. It is well-suited to writing operating systems,
27system tools, compilers, networking software, and other low-level, high
28performance tasks.
29
30#+end_quote
31
32I have found this all to be true while playing with it for the first
33time today. In the next few sections, I'm going to walk through my
34installation and first program.
35
36** Installation
37I'm currently running Alpine Linux on my Thinkpad, so the installation
38was quite easy as there is a package for Hare in the =apk= repositories.
39
40#+begin_src sh
41doas apk add hare hare-doc
42#+end_src
43
44However, I was able to install Hare from scratch on Fedora Linux a short
45while ago, which was also very easy to do. If you need further
46instructions and Hare doesn't have a package on your system, take a look
47at the [[https://harelang.org/installation/][Hare Installation]] page.
48
49** Creating a Test Project
50In order to play with the language, I created
51[[https://git.sr.ht/~cmc/hare-projects][hare-test]] and will be putting
52any of my Hare-related adventures in here.
53
54#+begin_quote
55*Update:** I also created a simple Hare program for creating a file from
56user input:
57[[https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha][files.ha]]
58
59#+end_quote
60
61Luckily, Hare doesn't require any complex set-up tools or build
62environment. Once you have Hare installed, you simply need to create a
63file ending with =.ha= and you can run a Hare program.
64
65I created a file called =rgb.ha= in order to test out the random number
66generation and passing parameters between functions.
67
68#+begin_src sh
69nano rgb.ha
70#+end_src
71
72Within this file, I was able to easily import a few of the
73[[https://harelang.org/tutorials/stdlib/][standard library modules]]:
74=fmt=, =math::random=, and =datetime=.
75
76With these modules, I created two functions:
77
781. =main=: This function calls the =generate_rgb= function and then
79 prints out the returned values.
802. =generate_rgb=: This function uses the current Unix epoch time to
81 generate a pseudo-random value and uses this value to create three
82 more random values between 0 and 255. These three numbers represent a
83 color in RGB format.
84
85#+begin_quote
86*Note*: Some syntax coloring may look odd, as Zola currently doesn't
87have a syntax highlighting theme for Hare. Instead, I'm using the C
88theme, which may not be exactly accurate when coloring the code below.
89
90#+end_quote
91
92#+begin_src C
93use datetime;
94use fmt;
95use math::random;
96
97export fn main() void = {
98 const rgb = generate_rgb();
99 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
100};
101
102fn generate_rgb() []u64 = {
103 // Use the current Unix epoch time as the seed value
104 let datetime = datetime::epochunix(&datetime::now());
105
106 // Generate initial pseudo-random value
107 // You must cast the datetime from int to u64
108 let x = random::init(datetime: u64);
109
110 // Generate RGB values between (0, 255) using pseudo-random init value
111 let r = random::u64n(&x, 255);
112 let g = random::u64n(&x, 255);
113 let b = random::u64n(&x, 255);
114
115 // Structure data as array and return
116 let rgb_array: [3]u64 = [r, g, b];
117 return rgb_array;
118};
119#+end_src
120
121** Running a Program
122Once you have a Hare file written and ready to run, you simply need to
123run it:
124
125#+begin_src sh
126hare run file.ha
127#+end_src
128
129You can also compile the program into an executable:
130
131#+begin_src sh
132hare build -o example file.ha
133./example
134#+end_src
135
136** Initial Thoughts
1371. Documentation Improvements Would Help
138
139 While I was able to piece everything together eventually, the biggest
140 downfall right now in Hare's documentation. For such a new project,
141 the documentation is in a great spot. However, bare specifications
142 don't help as much as a brief examples section would.
143
144 For example, it took me a while to figure out what the =u64n=
145 function was looking for. I could tell that it took two parameters
146 and the second was my max value (255), but couldn't figure out what
147 the first value should be. Eventually, I inspected the =random.ha=
148 file in the
149 [[https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha][Hare
150 source code]] and found the test suite that helped me discover that
151 it needed an =init()= value in the form of =&var=.
152
1532. More Basic Modules
154
155 This is another point that comes from Hare being new and awaiting
156 more contributions, but there are some basic functions that I would
157 personally enjoy seeing in Hare, such as one to convert decimal
158 (base 10) values to hexadecimal (base 16).
159
160 If I'm feeling comfortable with my math, I may work on the list of
161 functions I want and see if any can make it into the Hare source
162 code.
163
1643. Overall Thoughts
165
166 Overall, I actually really enjoy Hare. It's not as tedious to get a
167 project up and running as Rust, but it's also simpler and more
168 user-friendly than learning C. I am going to continue playing with it
169 and see if I can make anything of particular value.
content/blog/2023-05-22-burnout.org → content/blog/2023-05-22-burnout.md renamed +25 −21
@@ -1,17 +1,20 @@
1#+title: RE: Burnout 1+++
2#+date: 2023-05-22 2date = 2023-05-22
3#+description: A response to Drew DeVault's burnout post. 3title = "RE: Burnout"
4#+filetags: :personal: 4description = ""
5 5draft = false
6* RE: Burnout 6+++
7I recently read 7
8[[https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html][Drew 8# RE: Burnout {#re-burnout-1}
9DeVault's post on burnout]] around the same time I was pulling out of a 9
10burnout rut myself earlier this month. Finally, seeing the light at the 10I recently read [Drew DeVault\'s post on
11end of my burnout tunnel made me want to write my first post back on 11burnout](https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html)
12this topic. 12around the same time I was pulling out of a burnout rut myself earlier
13 13this month. Finally, seeing the light at the end of my burnout tunnel
14* Busy Seasons on Busy Seasons 14made me want to write my first post back on this topic.
15
16# Busy Seasons on Busy Seasons
17
15My career deals with busy seasons, generally driven by client demand. 18My career deals with busy seasons, generally driven by client demand.
16This last year, I dealt with a harsh busy season from Aug to Oct 2022 to 19This last year, I dealt with a harsh busy season from Aug to Oct 2022 to
17issue a few SOC reports for the period ending 2022-09-30. Immediately 20issue a few SOC reports for the period ending 2022-09-30. Immediately
@@ -24,18 +27,19 @@ The result? A busy season that has lasted from August 2022 through
24today. I will likely be rushing throughout the next month or two before 27today. I will likely be rushing throughout the next month or two before
25I have a brief break and need to focus on the 2023-09-30 SOC reports 28I have a brief break and need to focus on the 2023-09-30 SOC reports
26again. While auditing and consulting always involve a busy season, this 29again. While auditing and consulting always involve a busy season, this
27is the first time I've had one last 9+ months without a break. 30is the first time I\'ve had one last 9+ months without a break.
31
32While it\'s been tough, I have a handful of breaks pre-planned
33throughout this next cycle and should be able to moderate the level of
34commitment required for each client.
28 35
29While it's been tough, I have a handful of breaks pre-planned throughout 36# Refocusing
30this next cycle and should be able to moderate the level of commitment
31required for each client.
32 37
33* Refocusing
34Outside of work, I finally have time to work on hobbies such as this 38Outside of work, I finally have time to work on hobbies such as this
35website, programming, athletics, games, etc. 39website, programming, athletics, games, etc.
36 40
37You may have noticed my absence if you're in the same channels, forums, 41You may have noticed my absence if you\'re in the same channels, forums,
38and rooms that I am, but I should finally be active again. 42and rooms that I am, but I should finally be active again.
39 43
40I'm hoping to break an item out of my backlog soon and start working on 44I\'m hoping to break an item out of my backlog soon and start working on
41building a new project or hack around with a stale one. 45building a new project or hack around with a stale one.
content/blog/2023-06-08-goaccess-geoip.md added +70
@@ -0,0 +1,70 @@
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
11analyzer and interactive viewer that runs in a terminal in \*nix systems
12or through your browser.
13
14# Installation
15
16To start, you\'ll need to install GoAccess for your OS. Here\'s an
17example for Debian-based distros:
18
19```sh
20sudo apt install goaccess
21```
22
23Next, find any number of the MaxMind GeoIP database files on GitHub or
24another file hosting website. We\'re going to use P3TERX\'s version in
25this example:
26
27```sh
28wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
29```
30
31Be sure to save this file in an easy to remember location!
32
33# Usage
34
35In order to utilize the full capabilities of GoAccess and MMDB, start
36with the command template below and customize as necessary. This will
37export an HTML view of the GoAccess dashboard, showing all relevant
38information related to that site\'s access log. You can also omit the
39`-o output.html` parameter if you prefer to view the data
40within the CLI instead of creating an HTML file.
41
42With the addition of the GeoIP Database parameter, section
43`16 - Geo Location` will be added with the various countries
44that are associated with the collected IP addresses.
45
46```sh
47zcat /var/log/nginx/example.access.log.*.gz | goaccess \
48--geoip-database=/home/user/GeoLite2-City.mmdb \
49--date-format=%d/%b/%Y \
50--time-format=%H:%M:%S \
51--log-format=COMBINED \
52-o output.html \
53/var/log/nginx/example.access.log -
54```
55
56## Example Output
57
58See below for an example of the HTML output:
59
60![GoAccess
61HTML](https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png)
62
63You can also see the GeoIP card created by the integration of the
64MaxMind database information.
65
66![GoAccess
67GeoIP](https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png)
68
69That\'s all there is to it! Informational data is provided in an
70organized fashion with minimal effort.
content/blog/2023-06-08-goaccess-geoip.org deleted −64
@@ -1,64 +0,0 @@
1#+title: Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data
2#+date: 2023-06-08
3#+description: Learn how to use GoAccess and MaxMind to evaluate visitors to your web server.
4#+filetags: :sysadmin:
5
6* Overview
7[[https://goaccess.io/][GoAccess]] is an open source real-time web log
8analyzer and interactive viewer that runs in a terminal in *nix systems
9or through your browser.
10
11* Installation
12To start, you'll need to install GoAccess for your OS. Here's an example
13for Debian-based distros:
14
15#+begin_src sh
16sudo apt install goaccess
17#+end_src
18
19Next, find any number of the MaxMind GeoIP database files on GitHub or
20another file hosting website. We're going to use P3TERX's version in
21this example:
22
23#+begin_src sh
24wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
25#+end_src
26
27Be sure to save this file in an easy to remember location!
28
29* Usage
30In order to utilize the full capabilities of GoAccess and MMDB, start
31with the command template below and customize as necessary. This will
32export an HTML view of the GoAccess dashboard, showing all relevant
33information related to that site's access log. You can also omit the
34=-o output.html= parameter if you prefer to view the data within the CLI
35instead of creating an HTML file.
36
37With the addition of the GeoIP Database parameter, section
38=16 - Geo Location= will be added with the various countries that are
39associated with the collected IP addresses.
40
41#+begin_src sh
42zcat /var/log/nginx/example.access.log.*.gz | goaccess \
43--geoip-database=/home/user/GeoLite2-City.mmdb \
44--date-format=%d/%b/%Y \
45--time-format=%H:%M:%S \
46--log-format=COMBINED \
47-o output.html \
48/var/log/nginx/example.access.log -
49#+end_src
50
51** Example Output
52See below for an example of the HTML output:
53
54#+caption: GoAccess HTML
55[[https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png]]
56
57You can also see the GeoIP card created by the integration of the
58MaxMind database information.
59
60#+caption: GoAccess GeoIP
61[[https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png]]
62
63That's all there is to it! Informational data is provided in an
64organized fashion with minimal effort.
content/blog/2023-06-08-self-hosting-baikal.org → content/blog/2023-06-08-self-hosting-baikal.md renamed +63 −53
@@ -1,30 +1,35 @@
1#+title: Self-Hosting Baikal Server (CalDAV & CardDAV) 1+++
2#+date: 2023-06-08 2date = 2023-06-08
3#+description: A guide to self-hosting the Baikal application on your own server. 3title = "Self-Hosting Baikal Server (CalDAV & CardDAV)"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* What is Baikal? 8# What is Baikal?
7[[https://sabre.io/baikal/][Baikal]] is a lightweight CalDAV + CardDAV 9
10[Baikal](https://sabre.io/baikal/) is a lightweight CalDAV + CardDAV
8server that you can self-host on your own machine. While I have tried (& 11server that you can self-host on your own machine. While I have tried (&
9failed) to get this CalDAV + CardDAV server running before, it was quite 12failed) to get this CalDAV + CardDAV server running before, it was quite
10easy this time. Not really sure what I did differently this time, but 13easy this time. Not really sure what I did differently this time, but
11I'm documenting my process here to ensure I don't forget. 14I\'m documenting my process here to ensure I don\'t forget.
15
16# Installation
12 17
13* Installation 18First, create a folder on your server and open a
14First, create a folder on your server and open a =docker-compose.yml= 19`docker-compose.yml` file for editing:
15file for editing:
16 20
17#+begin_src sh 21```sh
18mkdir baikal && cd baikal 22mkdir baikal && cd baikal
19nano docker-compose.yml 23nano docker-compose.yml
20#+end_src 24```
21 25
22Within this file, you'll need to paste the information below. You can 26Within this file, you\'ll need to paste the information below. You can
23customize the =ports= section to use any port on your server to pass 27customize the `ports` section to use any port on your server
24through to port 80 in the container. You can also edit the =volumes= 28to pass through to port 80 in the container. You can also edit the
25section to use docker volumes instead of local folders. 29`volumes` section to use docker volumes instead of local
30folders.
26 31
27#+begin_src conf 32``` conf
28version: "2" 33version: "2"
29services: 34services:
30 baikal: 35 baikal:
@@ -35,59 +40,63 @@ services:
35 volumes: 40 volumes:
36 - ./config:/var/www/baikal/config 41 - ./config:/var/www/baikal/config
37 - ./data:/var/www/baikal/Specific 42 - ./data:/var/www/baikal/Specific
38#+end_src 43```
39 44
40Once finished with editing, save and close the file. Then, launch the 45Once finished with editing, save and close the file. Then, launch the
41docker container: 46docker container:
42 47
43#+begin_src sh 48```sh
44sudo docker-compose up -d 49sudo docker-compose up -d
45#+end_src 50```
51
52# Intial Setup
46 53
47* Intial Setup
48As long as no issues came up when starting the container, you should be 54As long as no issues came up when starting the container, you should be
49able to visit the server's set-up page at =http://<server_ip>:<port>=. 55able to visit the server\'s set-up page at
50The application will ask you to create an administrator account and 56`http://<server_ip>:<port>`. The application will ask you to
51choose the database type for your storage. Personally, I opted to use 57create an administrator account and choose the database type for your
52SQLite. 58storage. Personally, I opted to use SQLite.
53 59
54Make sure the administrator credentials are adequate to protect against 60Make sure the administrator credentials are adequate to protect against
55common attacks. 61common attacks.
56 62
57* Creating Users 63# Creating Users
58Once you've set up the application, you will be greeted by the Dashboard 64
59page, which will show the version of the app, status of the 65Once you\'ve set up the application, you will be greeted by the
66Dashboard page, which will show the version of the app, status of the
60admin/CalDAV/CardDAV services, and the number of users, calendars, 67admin/CalDAV/CardDAV services, and the number of users, calendars,
61events, address books, and contacts. 68events, address books, and contacts.
62 69
63#+caption: Baikal Dashboard 70![Baikal
64[[https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png]] 71Dashboard](https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png)
65 72
66To create a new user, navigate to the =Users and resources= page. This 73To create a new user, navigate to the `Users and resources`
67process is as simple as entering a username, password, and email. 74page. This process is as simple as entering a username, password, and
75email.
68 76
69Once a user has been created, you can create any number of calendars and 77Once a user has been created, you can create any number of calendars and
70address books for user, as well as inspect their information. 78address books for user, as well as inspect their information.
71 79
72* Setting Up a Public URL 80# Setting Up a Public URL
81
73Once your application is working locally, you can open access remotely 82Once your application is working locally, you can open access remotely
74via a URL by using a reverse-proxy like Nginx. 83via a URL by using a reverse-proxy like Nginx.
75 84
76As a prerequisite, you must own a domain name and set up DNS records to 85As a prerequisite, you must own a domain name and set up DNS records to
77point a domain name to the server hosting Baikal. 86point a domain name to the server hosting Baikal.
78 87
79Start by navigating to your web server's configuration directory and 88Start by navigating to your web server\'s configuration directory and
80create a new file for this application. 89create a new file for this application.
81 90
82#+begin_src sh 91```sh
83cd /etc/nginx/sites-available/ 92cd /etc/nginx/sites-available/
84nano dav 93nano dav
85#+end_src 94```
86 95
87Within this file, paste in the configuration from below and change 96Within this file, paste in the configuration from below and change
88=dav.example.com= to match the URL you'll be using. 97`dav.example.com` to match the URL you\'ll be using.
89 98
90#+begin_src conf 99``` conf
91server { 100server {
92 if ($host ~ ^[^.]+\.example\.com$) { 101 if ($host ~ ^[^.]+\.example\.com$) {
93 return 301 https://$host$request_uri; 102 return 301 https://$host$request_uri;
@@ -117,34 +126,35 @@ server {
117 include /etc/letsencrypt/options-ssl-nginx.conf; 126 include /etc/letsencrypt/options-ssl-nginx.conf;
118 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 127 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
119} 128}
120#+end_src 129```
121 130
122For Nginx on Ubuntu, you'll need to symlink the configuration file to 131For Nginx on Ubuntu, you\'ll need to symlink the configuration file to
123the =sites-enabled= directory and then restart Nginx. 132the `sites-enabled` directory and then restart Nginx.
124 133
125#+begin_src sh 134```sh
126sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav 135sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav
127sudo systemctl restart nginx.service 136sudo systemctl restart nginx.service
128#+end_src 137```
129 138
130At this point, the Baikal server should be available over the internet 139At this point, the Baikal server should be available over the internet
131at the URL configured above! 140at the URL configured above!
132 141
133* Configuring Clients 142# Configuring Clients
143
134Lastly, configuring clients and applications is essential to making sure 144Lastly, configuring clients and applications is essential to making sure
135the DAV server is being fully utilized. 145the DAV server is being fully utilized.
136 146
137You can also use the =Users and resources= page to inspect a user's 147You can also use the `Users and resources` page to inspect a
138personal link to their calendars and address books by clicking the info 148user\'s personal link to their calendars and address books by clicking
139(i) button. It will show a URI like 149the info (i) button. It will show a URI like
140=/dav.php/calendars/your-user/default/=. 150`/dav.php/calendars/your-user/default/`.
141 151
142However, I found that the following URL works for most applications: 152However, I found that the following URL works for most applications:
143=/dav.php/principals/your-user/=. 153`/dav.php/principals/your-user/`.
144 154
145I used the =principals= URL above for Thunderbird (calendar, tasks, and 155I used the `principals` URL above for Thunderbird (calendar,
146contacts), as well as iOS (calendar, tasks, and contacts) and everything 156tasks, and contacts), as well as iOS (calendar, tasks, and contacts) and
147works flawlessly so far. 157everything works flawlessly so far.
148 158
149Syncing is quick between the server and clients, and I haven't seen any 159Syncing is quick between the server and clients, and I haven\'t seen any
150disruptions in the service or data integrity. 160disruptions in the service or data integrity.
content/blog/2023-06-18-unifi-ip-blocklist.md added +88
@@ -0,0 +1,88 @@
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
11network you manage, you may know that Unifi is only somewhat decent at
12identifying and blocking 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
17functionality and identification. For example, I have my UDM Pro set to
18identify and block almost all categories of threats available within the
19Unifi settings. However, I regularly identify abusive actors on my web
20server via the server logs.
21
22In addition, I have identified IP addresses and subnets directly within
23Unifi\'s logs that the UDM did not block for whatever reason.
24
25This guide is meant to be another step in the process to manually block
26abusive IP addresses or subnets that you have identified but are not
27being automatically blocked yet.
28
29# Create an IP Group Profile
30
31To start, login to the Unifi machine\'s web GUI and navigate to the
32Network app \> Settings \> Profiles.
33
34Within this page, choose the `IP Groups` tab and click
35`Create New`.
36
37![Network
38Profiles](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png)
39
40Each IP Group profile can be used as one of three options:
41
421. Port Group
432. IPv4 Address/Subnet
443. IPv6 Address/Subnet
45
46In this example, I\'m creating an IPv4 Address/Subnet group and adding a
47few different IP addresses and a subnet. Once you\'ve added all IP
48addresses and subnets, click the `Apply` button that should
49appear at the bottom.
50
51![Network Profile
52IPs](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png)
53
54At this point, the IPv4 Address/Subnet has been created but not yet
55used.
56
57# Drop IP Group Profile via the Unifi Firewall
58
59To instruct the Unifi machine to block the profile we just created, we
60need to navigate to the Network app \> Settings \> Firewall & Security.
61
62Within this screen, find the Firewall Rules table and click
63`Create Entry`. This entry should contain the following
64settings:
65
66- Type: `Internet In`
67- Description: `<Your Custom Rule>`
68- Rule Applied: `Before Predefined Rules`
69- Action: `Drop`
70- Source Type: `Port/IP Group`
71- IPv4 Address Group:
72 `<Name of the Group Profile You Created Above>`
73
74Customize the remaining configurations to your liking, and then save and
75enable the firewall rule.
76
77![Firewall
78Rule](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png)
79
80Once enabled, the Unifi machine will be able to drop all incoming
81connections from the defined IP addresses and subnets within the created
82profile.
83
84> As a personal aside to this topic, I\'m looking for a convenient way
85> to update the firewall rules or profiles remotely (within the LAN)
86> from the web server to accelerate this process. If you have an idea on
87> how to automatically update Unifi IP groups or firewall rules, let me
88> know!
content/blog/2023-06-18-unifi-ip-blocklist.org deleted −82
@@ -1,82 +0,0 @@
1#+title: Block IP Addresses and Subnets with Unifi Network Firewall
2#+date: 2023-06-18
3#+description: Learn how to use the Unifi Network Firewall to block IP addresses and subnets.
4#+filetags: :network:
5
6* Identifying Abusive IPs
7If you're like me and use Unifi network equipment at the edge of the
8network you manage, you may know that Unifi is only somewhat decent at
9identifying and blocking IPs that represent abusive or threat actors.
10
11While Unifi has a
12[[https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management][threat
13management]] tool inside their Network application, it can be lacking in
14functionality and identification. For example, I have my UDM Pro set to
15identify and block almost all categories of threats available within the
16Unifi settings. However, I regularly identify abusive actors on my web
17server via the server logs.
18
19In addition, I have identified IP addresses and subnets directly within
20Unifi's logs that the UDM did not block for whatever reason.
21
22This guide is meant to be another step in the process to manually block
23abusive IP addresses or subnets that you have identified but are not
24being automatically blocked yet.
25
26* Create an IP Group Profile
27To start, login to the Unifi machine's web GUI and navigate to the
28Network app > Settings > Profiles.
29
30Within this page, choose the =IP Groups= tab and click =Create New=.
31
32#+caption: Network Profiles
33[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png]]
34
35Each IP Group profile can be used as one of three options:
36
371. Port Group
382. IPv4 Address/Subnet
393. IPv6 Address/Subnet
40
41In this example, I'm creating an IPv4 Address/Subnet group and adding a
42few different IP addresses and a subnet. Once you've added all IP
43addresses and subnets, click the =Apply= button that should appear at
44the bottom.
45
46#+caption: Network Profile IPs
47[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png]]
48
49At this point, the IPv4 Address/Subnet has been created but not yet
50used.
51
52* Drop IP Group Profile via the Unifi Firewall
53To instruct the Unifi machine to block the profile we just created, we
54need to navigate to the Network app > Settings > Firewall & Security.
55
56Within this screen, find the Firewall Rules table and click
57=Create Entry=. This entry should contain the following settings:
58
59- Type: =Internet In=
60- Description: =<Your Custom Rule>=
61- Rule Applied: =Before Predefined Rules=
62- Action: =Drop=
63- Source Type: =Port/IP Group=
64- IPv4 Address Group: =<Name of the Group Profile You Created Above>=
65
66Customize the remaining configurations to your liking, and then save and
67enable the firewall rule.
68
69#+caption: Firewall Rule
70[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png]]
71
72Once enabled, the Unifi machine will be able to drop all incoming
73connections from the defined IP addresses and subnets within the created
74profile.
75
76#+begin_quote
77As a personal aside to this topic, I'm looking for a convenient way to
78update the firewall rules or profiles remotely (within the LAN) from the
79web server to accelerate this process. If you have an idea on how to
80automatically update Unifi IP groups or firewall rules, let me know!
81
82#+end_quote
content/blog/2023-06-20-audit-review-template.md added +81
@@ -0,0 +1,81 @@
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
11audit test results, focusing on work done as part of a financial
12statement audit (FSA) or service organization controls (SOC) report.
13
14While there are numerous different things to review and look for - all
15varying wildly depending on the report, client, and tester - this list
16serves as a solid base foundation for a reviewer.
17
18I have used this throughout my career as a starting point to my reviews,
19and it has worked wonders for creating a consistent and objective
20template to my reviews. The goal is to keep this base high-level enough
21to be used on a wide variety of engagements, while still ensuring that
22all key areas are covered.
23
24# Review Template
25
261. [ ] Check all documents for spelling and grammar.
272. [ ] Ensure all acronyms are fully explained upon first use.
283. [ ] For all people referenced, use their full names and job titles
29 upon first use.
304. [ ] All supporting documents must cross-reference to the lead sheet
31 and vice-versa.
325. [ ] Verify that the control has been adequately tested:
33 - [ ] **Test of Design**: Did the tester obtain information
34 regarding how the control should perform normally and abnormally
35 (e.g., emergency scenarios)?
36 - [ ] **Test of Operating Effectiveness**: Did the tester inquire,
37 observe, inspect, or re-perform sufficient evidence to support
38 their conclusion over the control? Inquiry alone is not
39 adequate!
406. [ ] For any information used in the control, whether by the control
41 operator or by the tester, did the tester appropriately document the
42 source (system or person), extraction method, parameters, and
43 completeness and accuracy (C&A)?
44 - [ ] For any reports, queries, etc. used in the extraction, did
45 the tester include a copy and notate C&A considerations?
467. [ ] Did the tester document the specific criteria that the control
47 is being tested against?
488. [ ] Did the tester notate in the supporting documents where each
49 criterion was satisfied?
509. [ ] If testing specific policies or procedures, are the documents
51 adequate?
52 - [ ] e.g., a test to validate that a review of policy XYZ occurs
53 periodically should also evaluate the sufficiency of the policy
54 itself, if meant to cover the risk that such a policy does not
55 exist and is not reviewed.
5610. [ ] Does the test cover the appropriate period under review?
57 - [ ] If the test is meant to cover only a portion of the audit
58 period, do other controls exist to mitigate the risks that exist
59 for the remainder of the period?
6011. [ ] For any computer-aided audit tools (CAATs) or other automation
61 techniques used in the test, is the use of such tools explained and
62 appropriately documented?
6312. [ ] If prior-period documentation exists, are there any missing
64 pieces of evidence that would further enhance the quality of the
65 test?
6613. [ ] Was any information discovered during the walkthrough or inquiry
67 phase that was not incorporated into the test?
6814. [ ] Are there new rules or expectations from your company\'s
69 internal guidance or your regulatory bodies that would affect the
70 audit approach for this control?
7115. [ ] Was an exception, finding, or deficiency identified as a result
72 of this test?
73 - [ ] Was the control deficient in design, operation, or both?
74 - [ ] What was the root cause of the finding?
75 - [ ] Does the finding indicate other findings or potential fraud?
76 - [ ] What\'s the severity and scope of the finding?
77 - [ ] Do other controls exist as a form of compensation against
78 the finding\'s severity, and do they mitigate the risk within
79 the control objective?
80 - [ ] Does the finding exist at the end of the period, or was it
81 resolved within the audit period?
content/blog/2023-06-20-audit-review-template.org deleted −76
@@ -1,76 +0,0 @@
1#+title: Audit Testing Review Template
2#+date: 2023-06-20
3#+description: A handy reference template for audit review.
4#+filetags: :audit:
5
6* Overview
7This post is a /very/ brief overview on the basic process to review
8audit test results, focusing on work done as part of a financial
9statement audit (FSA) or service organization controls (SOC) report.
10
11While there are numerous different things to review and look for - all
12varying wildly depending on the report, client, and tester - this list
13serves as a solid base foundation for a reviewer.
14
15I have used this throughout my career as a starting point to my reviews,
16and it has worked wonders for creating a consistent and objective
17template to my reviews. The goal is to keep this base high-level enough
18to be used on a wide variety of engagements, while still ensuring that
19all key areas are covered.
20
21* Review Template
221. [ ] Check all documents for spelling and grammar.
232. [ ] Ensure all acronyms are fully explained upon first use.
243. [ ] For all people referenced, use their full names and job titles
25 upon first use.
264. [ ] All supporting documents must cross-reference to the lead sheet
27 and vice-versa.
285. [ ] Verify that the control has been adequately tested:
29 - [ ] *Test of Design*: Did the tester obtain information regarding
30 how the control should perform normally and abnormally (e.g.,
31 emergency scenarios)?
32 - [ ] *Test of Operating Effectiveness*: Did the tester inquire,
33 observe, inspect, or re-perform sufficient evidence to support
34 their conclusion over the control? Inquiry alone is not adequate!
356. [ ] For any information used in the control, whether by the control
36 operator or by the tester, did the tester appropriately document the
37 source (system or person), extraction method, parameters, and
38 completeness and accuracy (C&A)?
39 - [ ] For any reports, queries, etc. used in the extraction, did the
40 tester include a copy and notate C&A considerations?
417. [ ] Did the tester document the specific criteria that the control is
42 being tested against?
438. [ ] Did the tester notate in the supporting documents where each
44 criterion was satisfied?
459. [ ] If testing specific policies or procedures, are the documents
46 adequate?
47 - [ ] e.g., a test to validate that a review of policy XYZ occurs
48 periodically should also evaluate the sufficiency of the policy
49 itself, if meant to cover the risk that such a policy does not
50 exist and is not reviewed.
5110. [ ] Does the test cover the appropriate period under review?
52 - [ ] If the test is meant to cover only a portion of the audit
53 period, do other controls exist to mitigate the risks that exist
54 for the remainder of the period?
5511. [ ] For any computer-aided audit tools (CAATs) or other automation
56 techniques used in the test, is the use of such tools explained and
57 appropriately documented?
5812. [ ] If prior-period documentation exists, are there any missing
59 pieces of evidence that would further enhance the quality of the
60 test?
6113. [ ] Was any information discovered during the walkthrough or inquiry
62 phase that was not incorporated into the test?
6314. [ ] Are there new rules or expectations from your company's internal
64 guidance or your regulatory bodies that would affect the audit
65 approach for this control?
6615. [ ] Was an exception, finding, or deficiency identified as a result
67 of this test?
68 - [ ] Was the control deficient in design, operation, or both?
69 - [ ] What was the root cause of the finding?
70 - [ ] Does the finding indicate other findings or potential fraud?
71 - [ ] What's the severity and scope of the finding?
72 - [ ] Do other controls exist as a form of compensation against the
73 finding's severity, and do they mitigate the risk within the
74 control objective?
75 - [ ] Does the finding exist at the end of the period, or was it
76 resolved within the audit period?
content/blog/2023-06-23-byobu.org → content/blog/2023-06-23-byobu.md renamed +21 −17
@@ -1,33 +1,37 @@
1#+title: Byobu 1+++
2#+date: 2023-06-23 2date = 2023-06-23
3#+description: Learning about the Byobu application for terminals. 3title = "Byobu"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
5 7
6* Byobu 8# Byobu {#byobu-1}
7[[https://www.byobu.org][byobu]] is a command-line tool that allows you 9
8to use numerous screens within a single terminal emulator instance. More 10[byobu](https://www.byobu.org) is a command-line tool that allows you to
9specifically, it's a text based window manager, using either =screen= or 11use numerous screens within a single terminal emulator instance. More
10=tmux=. 12specifically, it\'s a text based window manager, using either
13`screen` or `tmux`.
11 14
12This post is mostly just a self-reference as I explore byobu, so I may 15This post is mostly just a self-reference as I explore byobu, so I may
13come back later and update this post with more content. 16come back later and update this post with more content.
14 17
15** Screenshot 18## Screenshot
19
16Take a look below at my current multi-window set-up in byobu while I 20Take a look below at my current multi-window set-up in byobu while I
17write this blog post: 21write this blog post:
18 22
19#+caption: byobu 23![byobu](https://img.cleberg.net/blog/20230623-byobu/byobu.png)
20[[https://img.cleberg.net/blog/20230623-byobu/byobu.png]] 24
25### Keybindings
21 26
22*** Keybindings
23You can open the help menu with either of the following commands; they 27You can open the help menu with either of the following commands; they
24will both open the same manpage: 28will both open the same manpage:
25 29
26#+begin_src sh 30```sh
27byobu --help 31byobu --help
28# or 32# or
29man byobu 33man byobu
30#+end_src 34```
31 35
32While the manpage contains a ton of information about the functionality 36While the manpage contains a ton of information about the functionality
33of byobu (such as status notifications, sessions, and windows), the 37of byobu (such as status notifications, sessions, and windows), the
@@ -35,7 +39,7 @@ first location to explore should be the keybindings section.
35 39
36The keybindings are configured as follows: 40The keybindings are configured as follows:
37 41
38#+begin_src txt 42``` txt
39byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or 43byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or
40within .screenrc if byobu-export was used). The common key bindings are: 44within .screenrc if byobu-export was used). The common key bindings are:
41 45
@@ -63,4 +67,4 @@ Ctrl-a R - Reload profile
63Ctrl-a ! - Toggle key bindings on and off 67Ctrl-a ! - Toggle key bindings on and off
64Ctrl-a k - Kill the current window 68Ctrl-a k - Kill the current window
65Ctrl-a ~ - Save the current window's scrollback buffer 69Ctrl-a ~ - Save the current window's scrollback buffer
66#+end_src 70```
content/blog/2023-06-23-self-hosting-convos.org → content/blog/2023-06-23-self-hosting-convos.md renamed +72 −63
@@ -1,39 +1,44 @@
1#+title: Self-Hosting Convos IRC Web Client 1+++
2#+date: 2023-06-23 2date = 2023-06-23
3#+description: A guide to self-hosting the Convos application on your own server. 3title = "Self-Hosting Convos IRC Web Client"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* Convos 8# Convos
7[[https://convos.chat/][Convos]] is an always-online web client for IRC. 9
10[Convos](https://convos.chat/) is an always-online web client for IRC.
8It has a few features that made it attractive to me as a self-hosted 11It has a few features that made it attractive to me as a self-hosted
9option: 12option:
10 13
11- Extremely simple Docker Compose installation method. 14- Extremely simple Docker Compose installation method.
12- Runs in the background and monitors chats even while you're not logged 15- Runs in the background and monitors chats even while you\'re not
13 in. 16 logged in.
14- Neatly organized sidebar for conversation and client settings. 17- Neatly organized sidebar for conversation and client settings.
15- Ability to connect to different hosts and create profiles for hosts. 18- Ability to connect to different hosts and create profiles for hosts.
16- By default, registration is closed to the public. You can enable 19- By default, registration is closed to the public. You can enable
17 public registration on the Settings page or generate invitation links 20 public registration on the Settings page or generate invitation
18 on the Users page. 21 links on the Users page.
19- Customization of the client theme, organization name and URL, admin 22- Customization of the client theme, organization name and URL, admin
20 email, and video service. 23 email, and video service.
21 24
22* Docker Installation 25# Docker Installation
23To install Convos, simply create a directory and a =docker-compose.yml= 26
24file: 27To install Convos, simply create a directory and a
25 28`docker-compose.yml` file:
26#+begin_src sh 29
30```sh
27mkdir ~/convos && cd ~/convos 31mkdir ~/convos && cd ~/convos
28nano docker-compose.yml 32nano docker-compose.yml
29#+end_src 33```
30 34
31With the =docker-compose.yml= file open, paste the configuration below 35With the `docker-compose.yml` file open, paste the
32into the file. You can customize the host port to be something unique, 36configuration below into the file. You can customize the host port to be
33such as =21897:3000=. You can also change the =data= folder to be a 37something unique, such as `21897:3000`. You can also change
34docker volume instead, if you prefer. 38the `data` folder to be a docker volume instead, if you
39prefer.
35 40
36#+begin_src config 41``` config
37version: '3' 42version: '3'
38 43
39services: 44services:
@@ -46,31 +51,33 @@ services:
46 environment: 51 environment:
47 - CONVOS_REVERSE_PROXY=1 52 - CONVOS_REVERSE_PROXY=1
48 restart: always 53 restart: always
49#+end_src 54```
50 55
51Save the =docker-compose.yml= file and bring the container up: 56Save the `docker-compose.yml` file and bring the container
57up:
52 58
53#+begin_src sh 59```sh
54sudo docker-compose up -d 60sudo docker-compose up -d
55#+end_src 61```
56 62
57At this point, Convos is available at =<server_ip>:3000= but not 63At this point, Convos is available at `<server_ip>:3000` but
58available to the public. 64not available to the public.
59 65
60* Nginx Reverse Proxy 66# Nginx Reverse Proxy
61If you're using Nginx, you can create a configuration file for the 67
68If you\'re using Nginx, you can create a configuration file for the
62convos application. Start by opening the file: 69convos application. Start by opening the file:
63 70
64#+begin_src sh 71```sh
65nano /etc/nginx/sites-available/convos 72nano /etc/nginx/sites-available/convos
66#+end_src 73```
67 74
68Within the nginx configuration file, paste the following content and be 75Within the nginx configuration file, paste the following content and be
69sure to update =convos.example.com= to match your domain and 76sure to update `convos.example.com` to match your domain and
70=127.0.0.1:3000= to match the port you opened in the 77`127.0.0.1:3000` to match the port you opened in the
71=docker-compose.yml= file. 78`docker-compose.yml` file.
72 79
73#+begin_src config 80``` config
74# Host and port where convos is running 81# Host and port where convos is running
75upstream convos_upstream { server 127.0.0.1:3000; } 82upstream convos_upstream { server 127.0.0.1:3000; }
76 83
@@ -108,53 +115,55 @@ server {
108 include /etc/letsencrypt/options-ssl-nginx.conf; 115 include /etc/letsencrypt/options-ssl-nginx.conf;
109 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 116 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
110} 117}
111#+end_src 118```
112 119
113Once the file is saved, link it to the =sites-enabled= directory and 120Once the file is saved, link it to the `sites-enabled`
114restart Nginx. 121directory and restart Nginx.
115 122
116#+begin_src sh 123```sh
117sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos 124sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos
118sudo systemctl restart nginx.service 125sudo systemctl restart nginx.service
119#+end_src 126```
127
128# Screenshots
120 129
121* Screenshots
122See the screenshots below for an example of the Convos homepage and an 130See the screenshots below for an example of the Convos homepage and an
123excerpt of the chat screen. 131excerpt of the chat screen.
124 132
125There are numerous themes to choose from; the theme shown in the images 133There are numerous themes to choose from; the theme shown in the images
126below is Dracula. 134below is Dracula.
127 135
128#+caption: Convos Home 136![Convos
129[[https://img.cleberg.net/blog/20230623-convos/convos_home.png]] 137Home](https://img.cleberg.net/blog/20230623-convos/convos_home.png)
138
139![Convos
140Chat](https://img.cleberg.net/blog/20230623-convos/convos_chat.png)
130 141
131#+caption: Convos Chat 142# Registering a Nickname
132[[https://img.cleberg.net/blog/20230623-convos/convos_chat.png]]
133 143
134* Registering a Nickname
135Hop into the server channel so that you can message NickServ. In the 144Hop into the server channel so that you can message NickServ. In the
136case of Convos, the default sever is libera.chat. Simply click the 145case of Convos, the default sever is libera.chat. Simply click the
137=libera= conversation at the top of the sidebar to open it. Once the 146`libera` conversation at the top of the sidebar to open it.
138chat is open, you can claim a nickname by typing: 147Once the chat is open, you can claim a nickname by typing:
139 148
140#+begin_src txt 149``` txt
141/nick <nick> 150/nick <nick>
142#+end_src 151```
143 152
144If the nickname is available, and you'd like to register the nickname to 153If the nickname is available, and you\'d like to register the nickname
145yourself, you'll need to type another command: 154to yourself, you\'ll need to type another command:
146 155
147#+begin_src txt 156``` txt
148/msg NickServ REGISTER 157/msg NickServ REGISTER
149<password> <email> 158<password> <email>
150#+end_src 159```
151 160
152On libera.chat, the server will send a confirmation email with a command 161On libera.chat, the server will send a confirmation email with a command
153that you must message in IRC to verify registration of the nickname: 162that you must message in IRC to verify registration of the nickname:
154 163
155#+begin_src txt 164``` txt
156/msg NickServ VERIFY REGISTER <nick> <verification_code> 165/msg NickServ VERIFY REGISTER <nick> <verification_code>
157#+end_src 166```
158 167
159Once entered, the server should confirm registration of the nickname to 168Once entered, the server should confirm registration of the nickname to
160the supplied email with the password specified. 169the supplied email with the password specified.
content/blog/2023-06-28-backblaze-b2.md added +189
@@ -0,0 +1,189 @@
1+++
2date = 2023-06-28
3title = "Getting Started with Backblaze B2 Cloud Storage"
4description = ""
5draft = false
6+++
7
8# Overview
9
10Backblaze [B2 Cloud
11Storage](https://www.backblaze.com/b2/cloud-storage.html) is an
12inexpensive and reliable on-demand cloud storage and backup solution.
13
14The service starts at \$5/TB/month (\$0.005/GB/month) with a download
15rate of \$0.01/GB/month.
16
17However, there are free tiers:
18
19- The first 10 GB of storage is free.
20- The first 1 GB of data downloaded each day is free.
21- Class A transactions are free.
22- The first 2500 Class B transactions each day are free.
23- The first 2500 Class C transactions each day are free.
24
25You can see which API calls fall into categories A, B, or C here:
26[Pricing Organized by API
27Calls](https://www.backblaze.com/b2/b2-transactions-price.html).
28
29For someone like me, who wants an offsite backup of their server\'s
30`/home/` directory and various other server configs that fall
31under 10 GB total, Backblaze is a great solution from a financial
32perspective.
33
34# Create An Account
35
36To start with Backblaze, you\'ll need to [create a free
37account](https://www.backblaze.com/b2/sign-up.html) - no payment method
38is required to sign up.
39
40Once you have an account, you can test out the service with their web
41GUI, their mobile app, or their CLI tool. I\'m going to use the CLI tool
42below to test a file upload and then sync an entire directory to my
43Backblaze bucket.
44
45# Create a Bucket
46
47Before you can start uploading, you need to create a bucket. If you\'re
48familiar with other object storage services, this will feel familiar. If
49not, it\'s pretty simple to create one.
50
51As their webpage says:
52
53> A bucket is a container that holds files that are uploaded into B2
54> Cloud Storage. The bucket name must be globally unique and must have a
55> minimum of 6 characters. A limit of 100 buckets may be created per
56> account. An unlimited number of files may be uploaded into a bucket.
57
58Once you click the `Create a Bucket` button on their webpage
59or mobile app, you need to provide the following:
60
61- Bucket Unique Name
62- Files in Bucket are: `Private` or `Public`
63- Default Encryption: `Disable` or `Enable`
64- Object Lock: `Disable` or `Enable`
65
66For my bucket, I created a private bucket with encryption enabled and
67object lock disabled.
68
69Once your bucket is created, you can test the upload/download feature on
70their web GUI or mobile app! At this point, you have a fully functional
71bucket and account.
72
73# Linux CLI Tool
74
75## Installation
76
77To install the `b2` CLI tool, you\'ll need to download it
78from the [CLI
79Tools](https://www.backblaze.com/docs/cloud-storage-command-line-tools)
80page. I recommend copying the URL from the link that says
81`Linux` and using wget to download it, as shown below.
82
83Once downloaded, make the file executable and move it to a location on
84your `$PATH`, so that you can execute that command from
85anywhere on the machine.
86
87```sh
88wget <b2_cli_url>
89chmod +x b2_linux
90mv b2_linux /usr/bin/b2
91```
92
93## Log In
94
95The first step after installation is to log in. To do this, execute the
96following command and provide your `<applicationKeyId>` and
97`<applicationKey>`.
98
99If you don\'t want to provide these values in the command itself, you
100can simply execute the base command and it will request them in an
101interactive prompt.
102
103```sh
104# if you want to provide the keys directly:
105b2 authorize-account [<applicationKeyId>] [<applicationKey>]
106
107# or, if you don't want your keys in your shell history:
108b2 authorize-account
109```
110
111## Upload a Test File
112
113In order to test the functionality of the CLI tool, I\'ll start by
114uploading a single test file to the bucket I created above. We can do
115this with the `upload_file` function.
116
117The command is issued as follows:
118
119```sh
120b2 upload_file <bucket_name> <local_file> <remote_file>
121```
122
123In my situation, I executed the following command with my username.
124
125```sh
126b2 upload_file my_unique_bucket /home/<user>/test.md test.md
127```
128
129To confirm that the file was uploaded successfully, list the files in
130your bucket:
131
132```sh
133b2 ls <bucket_name>
134```
135
136``` txt
137test.md
138```
139
140## Sync a Directory
141
142If you have numerous files, you can use the `sync` function
143to perform functionality similar to `rsync`, where you can
144check what\'s in your bucket and sync anything that is new or modified.
145
146The command is issued as follows:
147
148```sh
149b2 sync <source file location> <B2 bucket destination>
150```
151
152In my case, I can sync my user\'s entire home directory to my bucket
153without specifying any of the files directly:
154
155```sh
156b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
157```
158
159# Caveats
160
161## Timing of Updates to the Web GUI
162
163When performing actions over a bucket, there is a slight delay in the
164web GUI when inspecting a bucket or its file. Note that simple actions
165such as uploading or deleting files may have a delay of a few minutes up
166to 24 hours. In my experience (\<10 GB and \~20,000 files), any actions
167took only a few minutes to update across clients.
168
169## Symlinks
170
171Note that symlinks are resolved by b2, so if you have a link from
172`/home/<user>/nas-storage` that symlinks out to a
173`/mnt/nas-storage` folder that has 10TB of data,
174`b2` will resolve that link and start uploading all 10TB of
175data linked within the folder.
176
177If you\'re not sure if you have any symlinks, a symlink will look like
178this (note the `->` symbol):
179
180```sh
181> ls -lha
182lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
183```
184
185You can recursively find symlink in a path with the following command:
186
187```sh
188ls -lR /path/to/search | grep '^l'
189```
content/blog/2023-06-28-backblaze-b2.org deleted −176
@@ -1,176 +0,0 @@
1#+title: Getting Started with Backblaze B2 Cloud Storage
2#+date: 2023-06-28
3#+description: An introduction to the free ttier of Backblaze B2 Cloud Storage.
4#+filetags: :sysadmin:
5
6* Overview
7Backblaze [[https://www.backblaze.com/b2/cloud-storage.html][B2 Cloud
8Storage]] is an inexpensive and reliable on-demand cloud storage and
9backup solution.
10
11The service starts at $5/TB/month ($0.005/GB/month) with a download rate
12of $0.01/GB/month.
13
14However, there are free tiers:
15
16- The first 10 GB of storage is free.
17- The first 1 GB of data downloaded each day is free.
18- Class A transactions are free.
19- The first 2500 Class B transactions each day are free.
20- The first 2500 Class C transactions each day are free.
21
22You can see which API calls fall into categories A, B, or C here:
23[[https://www.backblaze.com/b2/b2-transactions-price.html][Pricing
24Organized by API Calls]].
25
26For someone like me, who wants an offsite backup of their server's
27=/home/= directory and various other server configs that fall under 10
28GB total, Backblaze is a great solution from a financial perspective.
29
30* Create An Account
31To start with Backblaze, you'll need to
32[[https://www.backblaze.com/b2/sign-up.html][create a free account]] -
33no payment method is required to sign up.
34
35Once you have an account, you can test out the service with their web
36GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool
37below to test a file upload and then sync an entire directory to my
38Backblaze bucket.
39
40* Create a Bucket
41Before you can start uploading, you need to create a bucket. If you're
42familiar with other object storage services, this will feel familiar. If
43not, it's pretty simple to create one.
44
45As their webpage says:
46
47#+begin_quote
48A bucket is a container that holds files that are uploaded into B2 Cloud
49Storage. The bucket name must be globally unique and must have a minimum
50of 6 characters. A limit of 100 buckets may be created per account. An
51unlimited number of files may be uploaded into a bucket.
52
53#+end_quote
54
55Once you click the =Create a Bucket= button on their webpage or mobile
56app, you need to provide the following:
57
58- Bucket Unique Name
59- Files in Bucket are: =Private= or =Public=
60- Default Encryption: =Disable= or =Enable=
61- Object Lock: =Disable= or =Enable=
62
63For my bucket, I created a private bucket with encryption enabled and
64object lock disabled.
65
66Once your bucket is created, you can test the upload/download feature on
67their web GUI or mobile app! At this point, you have a fully functional
68bucket and account.
69
70* Linux CLI Tool
71** Installation
72To install the =b2= CLI tool, you'll need to download it from the
73[[https://www.backblaze.com/docs/cloud-storage-command-line-tools][CLI
74Tools]] page. I recommend copying the URL from the link that says
75=Linux= and using wget to download it, as shown below.
76
77Once downloaded, make the file executable and move it to a location on
78your =$PATH=, so that you can execute that command from anywhere on the
79machine.
80
81#+begin_src sh
82wget <b2_cli_url>
83chmod +x b2_linux
84mv b2_linux /usr/bin/b2
85#+end_src
86
87** Log In
88The first step after installation is to log in. To do this, execute the
89following command and provide your =<applicationKeyId>= and
90=<applicationKey>=.
91
92If you don't want to provide these values in the command itself, you can
93simply execute the base command and it will request them in an
94interactive prompt.
95
96#+begin_src sh
97# if you want to provide the keys directly:
98b2 authorize-account [<applicationKeyId>] [<applicationKey>]
99
100# or, if you don't want your keys in your shell history:
101b2 authorize-account
102#+end_src
103
104** Upload a Test File
105In order to test the functionality of the CLI tool, I'll start by
106uploading a single test file to the bucket I created above. We can do
107this with the =upload_file= function.
108
109The command is issued as follows:
110
111#+begin_src sh
112b2 upload_file <bucket_name> <local_file> <remote_file>
113#+end_src
114
115In my situation, I executed the following command with my username.
116
117#+begin_src sh
118b2 upload_file my_unique_bucket /home/<user>/test.md test.md
119#+end_src
120
121To confirm that the file was uploaded successfully, list the files in
122your bucket:
123
124#+begin_src sh
125b2 ls <bucket_name>
126#+end_src
127
128#+begin_src txt
129test.md
130#+end_src
131
132** Sync a Directory
133If you have numerous files, you can use the =sync= function to perform
134functionality similar to =rsync=, where you can check what's in your
135bucket and sync anything that is new or modified.
136
137The command is issued as follows:
138
139#+begin_src sh
140b2 sync <source file location> <B2 bucket destination>
141#+end_src
142
143In my case, I can sync my user's entire home directory to my bucket
144without specifying any of the files directly:
145
146#+begin_src sh
147b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
148#+end_src
149
150* Caveats
151** Timing of Updates to the Web GUI
152When performing actions over a bucket, there is a slight delay in the
153web GUI when inspecting a bucket or its file. Note that simple actions
154such as uploading or deleting files may have a delay of a few minutes up
155to 24 hours. In my experience (<10 GB and ~20,000 files), any actions
156took only a few minutes to update across clients.
157
158** Symlinks
159Note that symlinks are resolved by b2, so if you have a link from
160=/home/<user>/nas-storage= that symlinks out to a =/mnt/nas-storage=
161folder that has 10TB of data, =b2= will resolve that link and start
162uploading all 10TB of data linked within the folder.
163
164If you're not sure if you have any symlinks, a symlink will look like
165this (note the =->= symbol):
166
167#+begin_src sh
168> ls -lha
169lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
170#+end_src
171
172You can recursively find symlink in a path with the following command:
173
174#+begin_src sh
175ls -lR /path/to/search | grep '^l'
176#+end_src
content/blog/2023-06-30-self-hosting-voyager.org → content/blog/2023-06-30-self-hosting-voyager.md renamed +50 −42
@@ -1,50 +1,56 @@
1#+title: Self-Hosting Voyager - A Lemmy Web Client 1+++
2#+date: 2023-06-30 2date = 2023-06-30
3#+description: A guide to self-hosting the Voyager application on your own server. 3title = "Self-Hosting Voyager - A Lemmy Web Client"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* Installation Guide 8# Installation Guide
7[[https://github.com/aeharding/voyager][Voyager]] is a mobile-first 9
8Lemmy web client, based on iOS design standards. It follows very closely 10[Voyager](https://github.com/aeharding/voyager) is a mobile-first Lemmy
9to Apollo's design. 11web client, based on iOS design standards. It follows very closely to
12Apollo\'s design.
10 13
11This post is a guide showing how I was able to build and launch my own 14This post is a guide showing how I was able to build and launch my own
12instance of Voyager via Docker Compose. 15instance of Voyager via Docker Compose.
13 16
14** Clone the Repository 17## Clone the Repository
18
15Start by cloning the repository and entering it: 19Start by cloning the repository and entering it:
16 20
17#+begin_src sh 21```sh
18git clone https://github.com/aeharding/voyager 22git clone https://github.com/aeharding/voyager
19cd voyager 23cd voyager
20#+end_src 24```
25
26## Build the Image
21 27
22** Build the Image
23With this repository, you can build the image yourself without any 28With this repository, you can build the image yourself without any
24further configuration. When complete, it'll give you the image ID for 29further configuration. When complete, it\'ll give you the image ID for
25you to run. 30you to run.
26 31
27#+begin_src sh 32```sh
28sudo docker build . 33sudo docker build .
29# Successfully built 5f00723cb5be 34# Successfully built 5f00723cb5be
30#+end_src 35```
31 36
32With the image ID above, you can run the container and pass the 37With the image ID above, you can run the container and pass the
33requested port =5314= through or use a custom port, if you wish. 38requested port `5314` through or use a custom port, if you
39wish.
34 40
35You can also set the =CUSTOM_LEMMY_SERVERS= environment variable if you 41You can also set the `CUSTOM_LEMMY_SERVERS` environment
36want to add to the default suggested login servers. This must be set 42variable if you want to add to the default suggested login servers. This
37with a comma separated list of suggested servers. The first instance in 43must be set with a comma separated list of suggested servers. The first
38the list will be the default view for logged-out users. 44instance in the list will be the default view for logged-out users.
39 45
40I will be using a =docker-compose.yml= file to run this container, 46I will be using a `docker-compose.yml` file to run this
41instead of a =docker run= command. 47container, instead of a `docker run` command.
42 48
43#+begin_src sh 49```sh
44nano docker-compose.yml 50nano docker-compose.yml
45#+end_src 51```
46 52
47#+begin_src conf 53``` conf
48version: "2" 54version: "2"
49services: 55services:
50 voyager: 56 voyager:
@@ -54,29 +60,30 @@ services:
54 - "<custom_port>:5314" 60 - "<custom_port>:5314"
55 environment: 61 environment:
56 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org 62 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org
57#+end_src 63```
58 64
59#+begin_src sh 65```sh
60sudo docker-compose up -d 66sudo docker-compose up -d
61#+end_src 67```
62 68
63The web app will now be available at the following address: 69The web app will now be available at the following address:
64=<machine_ip>:<custom_port>=. If you are running it on your local 70`<machine_ip>:<custom_port>`. If you are running it on your
65device, try =localhost:<custom_port>=. 71local device, try `localhost:<custom_port>`.
72
73## Reverse Proxy
66 74
67** Reverse Proxy 75If you want to visit this app via an external URL or domain name,
68If you want to visit this app via an external URL or domain name, you'll 76you\'ll need to set up a reverse proxy. The example below uses Nginx as
69need to set up a reverse proxy. The example below uses Nginx as a 77a reverse proxy.
70reverse proxy.
71 78
72Simply create the configuration file, paste the contents below, save the 79Simply create the configuration file, paste the contents below, save the
73file, symlink the file, and restart Nginx. 80file, symlink the file, and restart Nginx.
74 81
75#+begin_src sh 82```sh
76sudo nano /etc/nginx/sites-available/voyager 83sudo nano /etc/nginx/sites-available/voyager
77#+end_src 84```
78 85
79#+begin_src conf 86``` conf
80server { 87server {
81 if ($host ~ ^[^.]+\.example\.com$) { 88 if ($host ~ ^[^.]+\.example\.com$) {
82 return 301 https://$host$request_uri; 89 return 301 https://$host$request_uri;
@@ -106,14 +113,15 @@ server {
106 include /etc/letsencrypt/options-ssl-nginx.conf; 113 include /etc/letsencrypt/options-ssl-nginx.conf;
107 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 114 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
108} 115}
109#+end_src 116```
110 117
111#+begin_src sh 118```sh
112sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager 119sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager
113sudo systemctl restart nginx.service 120sudo systemctl restart nginx.service
114#+end_src 121```
115 122
116The site will now be available at the =server_name= you specified above! 123The site will now be available at the `server_name` you
124specified above!
117 125
118You can visit my instance at 126You can visit my instance at
119[[https://voyager.cleberg.net][voyager.cleberg.net]] for an example. 127[voyager.cleberg.net](https://voyager.cleberg.net) for an example.
content/blog/2023-07-12-wireguard-lan.org → content/blog/2023-07-12-wireguard-lan.md renamed +61 −59
@@ -1,28 +1,31 @@
1#+title: Enable LAN Access in Mullvad Wireguard Conf Files 1+++
2#+date: 2023-07-12 2date = 2023-07-12
3#+description: Learn how to enable LAN access manually in Mullvad configuration files. 3title = "Enable LAN Access in Mullvad Wireguard Configuration Files"
4#+filetags: :linux: 4description = ""
5 5draft = false
6* Download Configuration Files from Mullvad 6+++
7To begin, you'll need 7
8[[https://mullvad.net/account/wireguard-config][Wireguard configuration 8# Download Configuration Files from Mullvad
9files from Mullvad]]. You can choose any of the options as you download 9
10them. For example, I enabled the kill switch, selected all countries, 10To begin, you\'ll need [Wireguard configuration files from
11and selected a few content filters. 11Mullvad](https://mullvad.net/account/wireguard-config). You can choose
12any of the options as you download them. For example, I enabled the kill
13switch, selected all countries, and selected a few content filters.
12 14
13Once downloaded, unzip the files and move them to the Wireguard folder 15Once downloaded, unzip the files and move them to the Wireguard folder
14on your system. 16on your system.
15 17
16#+begin_src sh 18```sh
17cd ~/Downloads 19cd ~/Downloads
18unzip mullvad_wireguard_linux_all_all.zip 20unzip mullvad_wireguard_linux_all_all.zip
19doas mv *.conf /etc/wireguard/ 21doas mv *.conf /etc/wireguard/
20#+end_src 22```
23
24## Configuration File Layout
21 25
22** Configuration File Layout
23The default configuration files will look something like this: 26The default configuration files will look something like this:
24 27
25#+begin_src conf 28``` conf
26[Interface] 29[Interface]
27# Device: <redacted> 30# Device: <redacted>
28PrivateKey = <redacted> 31PrivateKey = <redacted>
@@ -35,46 +38,45 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
35PublicKey = <redacted> 38PublicKey = <redacted>
36AllowedIPs = <redacted> 39AllowedIPs = <redacted>
37Endpoint = <redacted> 40Endpoint = <redacted>
38#+end_src 41```
39 42
40#+begin_quote 43> Note: If you didn\'t select the kill switch option, you won\'t see the
41Note: If you didn't select the kill switch option, you won't see the 44> `PostUp` and `PreDown` lines. In this case,
42=PostUp= and =PreDown= lines. In this case, you'll need to modify the 45> you\'ll need to modify the script below to simply append those lines
43script below to simply append those lines to the =[Interface]= block. 46> to the `[Interface]` block.
44#+end_quote
45 47
46* Editing the Configuration Files 48# Editing the Configuration Files
47Once you have the files, you'll need to edit them and replace the
48=PostUp= and =PreDown= lines to enable LAN access.
49 49
50I recommend that you do this process as root, since you'll need to be 50Once you have the files, you\'ll need to edit them and replace the
51able to access files in =/etc/wireguard=, which are generally owned by 51`PostUp` and `PreDown` lines to enable LAN access.
52root. You can also try using =sudo= or =doas=, but I didn't test that
53scenario so you may need to adjust, as necessary.
54 52
55#+begin_src sh 53I recommend that you do this process as root, since you\'ll need to be
54able to access files in `/etc/wireguard`, which are generally
55owned by root. You can also try using `sudo` or
56`doas`, but I didn\'t test that scenario so you may need to
57adjust, as necessary.
58
59```sh
56su 60su
57#+end_src 61```
58 62
59Create the Python file that we'll be using to update the Wireguard 63Create the Python file that we\'ll be using to update the Wireguard
60configuration files. 64configuration files.
61 65
62#+begin_src sh 66```sh
63nano replace.py 67nano replace.py
64#+end_src 68```
65 69
66Within the Python file, copy and paste the logic below. This script will 70Within the Python file, copy and paste the logic below. This script will
67open a directory, loop through every configuration file within the 71open a directory, loop through every configuration file within the
68directory, and replace the =PostUp= and =PreDown= lines with the new 72directory, and replace the `PostUp` and `PreDown`
69LAN-enabled iptables commands. 73lines with the new LAN-enabled iptables commands.
70
71#+begin_quote
72Note: If your LAN is on a subnet other than =192.168.1.0/24=, you'll
73need to update the Python script below appropriately.
74 74
75#+end_quote 75> Note: If your LAN is on a subnet other than
76> `192.168.1.0/24`, you\'ll need to update the Python script
77> below appropriately.
76 78
77#+begin_src python 79``` python
78import os 80import os
79import fileinput 81import fileinput
80 82
@@ -93,25 +95,25 @@ for file in os.listdir(dir):
93 print(line, end="") 95 print(line, end="")
94 96
95print("--- done ---") 97print("--- done ---")
96#+end_src 98```
97 99
98Once you're done, save and close the file. You can now run the Python 100Once you\'re done, save and close the file. You can now run the Python
99script and watch as each file is updated. 101script and watch as each file is updated.
100 102
101#+begin_src sh 103```sh
102python3 replace.py 104python3 replace.py
103#+end_src 105```
104 106
105To confirm it worked, you can =cat= one of the configuration files to 107To confirm it worked, you can `cat` one of the configuration
106inspect the new logic and connect to one to test it out. 108files to inspect the new logic and connect to one to test it out.
107 109
108#+begin_src sh 110```sh
109cat /etc/wireguard/us-chi-wg-001.conf 111cat /etc/wireguard/us-chi-wg-001.conf
110#+end_src 112```
111 113
112The configuration files should now look like this: 114The configuration files should now look like this:
113 115
114#+begin_src conf 116``` conf
115[Interface] 117[Interface]
116# Device: <redacted> 118# Device: <redacted>
117PrivateKey = <redacted> 119PrivateKey = <redacted>
@@ -124,20 +126,20 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
124PublicKey = <redacted> 126PublicKey = <redacted>
125AllowedIPs = <redacted> 127AllowedIPs = <redacted>
126Endpoint = <redacted> 128Endpoint = <redacted>
127#+end_src 129```
128 130
129If you connect to a Wireguard interface, such as =us-chi-wg-001=, you 131If you connect to a Wireguard interface, such as
130can test your SSH functionality and see that it works even while on the 132`us-chi-wg-001`, you can test your SSH functionality and see
131VPN. 133that it works even while on the VPN.
132 134
133#+begin_src sh 135```sh
134wg-quick up us-chi-wg-001 136wg-quick up us-chi-wg-001
135ssh user@lan-host 137ssh user@lan-host
136#+end_src 138```
137 139
138To confirm your VPN connection, you can curl Mullvad's connection API: 140To confirm your VPN connection, you can curl Mullvad\'s connection API:
139 141
140#+begin_src sh 142```sh
141curl https://am.i.mullvad.net/connected 143curl https://am.i.mullvad.net/connected
142# 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>
143#+end_src 145```
content/blog/2023-07-19-plex-transcoder-errors.org → content/blog/2023-07-19-plex-transcoder-errors.md renamed +35 −28
@@ -1,57 +1,64 @@
1#+title: How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.' 1+++
2#+date: 2023-07-19 2date = 2023-07-19
3#+description: Learn how to avoid Plex conversion errors caused by subtitles. 3title = "How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.'"
4#+filetags: :sysadmin: 4description = ""
5draft = false
6+++
7
8# Plex Transcoder Error
5 9
6* Plex Transcoder Error
7Occasionally, you may see an error in your Plex client that references a 10Occasionally, you may see an error in your Plex client that references a
8failure with the transcoder conversion process. The specific error 11failure with the transcoder conversion process. The specific error
9wording is: 12wording is:
10 13
11#+begin_src txt 14``` txt
12Conversion failed. The transcoder failed to start up. 15Conversion failed. The transcoder failed to start up.
13#+end_src 16```
17
18# Debugging the Cause
14 19
15* Debugging the Cause 20In order to get a better look at what is causing the error, I\'m going
16In order to get a better look at what is causing the error, I'm going to 21to observe the Plex console while the error occurs. To do this, open the
17observe the Plex console while the error occurs. To do this, open the 22Plex web client, go to `Settings` \> `Manage` \>
18Plex web client, go to =Settings= > =Manage= > =Console=. Now, try to 23`Console`. Now, try to play the title again and watch to see
19play the title again and watch to see which errors occur. 24which errors occur.
20 25
21In my case, you can see the errors below are related to a subtitle file 26In my case, you can see the errors below are related to a subtitle file
22(=.srt=) causing the transcoder to crash. 27(`.srt`) causing the transcoder to crash.
23 28
24#+begin_src txt 29``` txt
25Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt 30Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt
26Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file 31Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file
27Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID. 32Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID.
28Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available 33Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available
29Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt" 34Jul 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"
30#+end_src 35```
36
37# Solving the Error
31 38
32* Solving the Error
33In my case, I simply removed the subtitle file because it was not 39In my case, I simply removed the subtitle file because it was not
34critical to keep. You may also avoid this by turning off subtitles if 40critical to keep. You may also avoid this by turning off subtitles if
35you don't want to delete the file. 41you don\'t want to delete the file.
36 42
37#+begin_src sh 43```sh
38rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt" 44rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt"
39#+end_src 45```
40 46
41Once the subtitle is removed from the directory or subtitles are turned 47Once the subtitle is removed from the directory or subtitles are turned
42off, try to play the title again. At this point, it should play without 48off, try to play the title again. At this point, it should play without
43error. If not, reopen or refresh your Plex client and the Plex server: 49error. If not, reopen or refresh your Plex client and the Plex server:
44 50
45#+begin_src sh 51```sh
46sudo systemctl restart plexmediaserver.service 52sudo systemctl restart plexmediaserver.service
47#+end_src 53```
48 54
49* Related Discussion 55# Related Discussion
50Looking at 56
51[[https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2][a 57Looking at [a related Plex forum
52related Plex forum post]], it seems that =.srt= subtitles are the core 58post](https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2),
53issue here. However, Plex does not seem to have a resolution that fixes 59it seems that `.srt` subtitles are the core issue here.
54these errors. 60However, Plex does not seem to have a resolution that fixes these
61errors.
55 62
56Unfortunately, I would suggest converting subtitle formats, burning the 63Unfortunately, I would suggest converting subtitle formats, burning the
57subtitles into the title, or simply removing subtitles when they cause 64subtitles into the title, or simply removing subtitles when they cause
content/blog/2023-08-18-agile-auditing.md added +159
@@ -0,0 +1,159 @@
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
12been picking up steam as a beneficial tool to use in the external and
13internal auditing world.
14
15This blog post will walk through commonly used terms within Agile,
16Scrum, and Kanban in order to translate these terms and roles into
17audit-specific terms.
18
19Whether your team is in charge of a financial statement audit, an
20attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms
21used throughout this post should still apply.
22
23# Agile
24
25To start, I\'ll take a look at Agile.
26
27> The Agile methodology is a project management approach that involves
28> breaking the project into phases and emphasizes continuous
29> collaboration and improvement. Teams follow a cycle of planning,
30> executing, and evaluating.
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, [twelve
47principles](https://agilemanifesto.org/principles.html) were also
48written 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
64 current knowledge of the engagement\'s status and any potential
65 findings, rather than waiting for pre-set meetings or the end of the
66 engagement to communicate.
67- Engagement teams must be able to respond to change in an
68 engagement\'s schedule, scope, or environment to ensure that the
69 project is completed in a timely manner and that all relevant areas
70 are tested.
71 - In terms of an audit department\'s portfolio, they must be able
72 to respond to changes in their company\'s or client\'s
73 environment and be able to dynamically change their audit plan
74 accordingly.
75
76# Scrum
77
78The above section discusses the high-level details of the Agile
79philosophy and how an audit team can potentially mold that mindset into
80the audit world, but how does a team implement these ideas?
81
82There are many methods that use an Agile mindset, but I prefer
83[Scrum](https://en.wikipedia.org/wiki/Scrum_(software_development)).
84Scrum is a framework based on Agile that enables a team to work through
85a project through a series of roles, ceremonies, artifacts, and values.
86
87Let\'s dive into each of these individually.
88
89## Scrum Team
90
91A scrum project is only as good as the team running the project.
92Standard scrum teams are separated into three distinct areas:
93
941. **Product Owner (Client Contact)**: The client contact is the audit
95 equivalent of the product owner in Scrum. They are responsible for
96 partnering with the engagement or audit team to ensure progress is
97 being made, priorities are established, and clear guidance is given
98 when questions or findings arise within each sprint.
992. **Scrum Master (Engagement Lead)**: The engagement or audit team
100 lead is responsible for coaching the team and the client contact on
101 the scrum process, tracking team progress against plan, scheduling
102 necessary resources, and helping remove obstacles.
1033. **Scrum Developers (Engagement Members)**: The engagement or audit
104 team is the set of team members responsible for getting the work
105 done. These team members will work on each task, report progress,
106 resolve obstacles, and collaborate with other team members and the
107 client contact to ensure goals are being met.
108
109## Scrum Ceremonies
110
111Scrum ceremonies are events that are performed on a regular basis.
112
1131. **Sprint Planning**: The team works together to plan the upcoming
114 sprint goal and which user stories (tasks) will be added to the
115 sprint to achieve that goal.
1162. **Sprint**: The time period, typically at least one week and no more
117 than one month in length, where the team works on the stories and
118 anything in the backlog.
1193. **Daily Scrum**: A very short meeting held each day, typically 15
120 minutes, to quickly emphasize alignment on the sprint goal and plan
121 the next 24 hours. Each team member may share what they did the day
122 before, what they\'ll do today, and any obstacles to their work.
1234. **Sprint Review**: At the end of each sprint, the team will gather
124 and discuss the progress, obstacles, and backlog from the previous
125 sprint.
1265. **Sprint Retrospective**: More specific than the sprint review, the
127 retrospective is meant to discuss what worked and what did not work
128 during the sprint. This may be processes, tools, people, or even
129 things related to the Scrum ceremonies.
130
131One additional ceremony that may be applicable is organizing the
132backlog. This is typically the responsibility of the engagement leader
133and is meant to prioritize and clarify what needs to be done to complete
134items in the backlog.
135
136## Artifacts
137
138While artifacts are generally not customizable in the audit world (i.e.,
139each control test must include some kind of working paper with evidence
140supporting the test results), I wanted to include some quick notes on
141associating scrum artifact terms with an audit.
142
1431. **Product Backlog**: This is the overall backlog of unfinished audit
144 tasks from all prior sprints.
1452. **Sprint Backlog**: This is the backlog of unfinished audit tasks
146 from one individual sprint.
1473. **Increment**: This is the output of each sprint - generally this is
148 best thought of as any documentation prepared during the sprint,
149 such as risk assessments, control working papers, deficiency
150 analysis, etc.
151
152# Kanban
153
154Last but not least, Kanban is a methodology that relies on boards to
155categorize work into distinct, descriptive categories that allow an
156agile or scrum team to effectively plan the work of a sprint or project.
157
158See Atlassian\'s [Kanban](https://www.atlassian.com/agile/kanban) page
159for more information.
content/blog/2023-08-18-agile-auditing.org deleted −137
@@ -1,137 +0,0 @@
1#+title: Agile Auditing: An Introduction
2#+date: <2023-08-18>
3#+description: A quick introduction to using the Agile methodology in an audit.
4#+filetags: :audit:
5
6* What is Agile Auditing?
7[[https://en.wikipedia.org/wiki/Agile_software_development][Agile]], the collaborative philosophy behind many software development methods,
8has been picking up steam as a beneficial tool to use in the external and
9internal auditing world.
10
11This blog post will walk through commonly used terms within Agile, Scrum, and
12Kanban in order to translate these terms and roles into audit-specific terms.
13
14Whether your team is in charge of a financial statement audit, an attestation
15(SOC 1, SOC 2, etc.), or a unique internal audit, the terms used throughout this
16post should still apply.
17
18* Agile
19To start, I'll take a look at Agile.
20
21#+begin_quote
22The Agile methodology is a project management approach that involves breaking
23the project into phases and emphasizes continuous collaboration and improvement.
24Teams follow a cycle of planning, executing, and evaluating.
25#+end_quote
26
27While this approach may seem familiar to what audit teams have historically
28done, an audit team must make distinct changes in their mentality and how they
29approach and manage a project.
30
31** Agile Values
32The Agile Manifesto, written in 2001 at a summit in Utah, contain a set of four
33main values that comprise the Agile approach:
34
351. Individuals and interactions over processes and tools.
362. Working software over comprehensive documentation.
373. Customer collaboration over contract negotiation.
384. Responding to change over following a plan.
39
40Beyond the four values, [[https://agilemanifesto.org/principles.html][twelve principles]] were also written as part of the
41summit.
42
43In order to relate these values to an audit or attestation engagement, we need
44to shift the focus from software development to the main goal of an engagement:
45completing sufficient audit testing to address to relevant risks over the
46processes and controls at hand.
47
48Audit Examples:
49
50- Engagement teams must value the team members, client contacts, and their
51 interactions over the historical processes and tools that have been used.
52- Engagement teams must value a final report that contains sufficient audit
53 documentation over excessive documentation or scope creep.
54- Engagement teams must collaborate with the audit clients as much as feasible
55 to ensure that both sides are constantly updated with current knowledge of the
56 engagement's status and any potential findings, rather than waiting for
57 pre-set meetings or the end of the engagement to communicate.
58- Engagement teams must be able to respond to change in an engagement's
59 schedule, scope, or environment to ensure that the project is completed in a
60 timely manner and that all relevant areas are tested.
61 - In terms of an audit department's portfolio, they must be able to respond to
62 changes in their company's or client's environment and be able to
63 dynamically change their audit plan accordingly.
64
65* Scrum
66The above section discusses the high-level details of the Agile philosophy and
67how an audit team can potentially mold that mindset into the audit world, but
68how does a team implement these ideas?
69
70There are many methods that use an Agile mindset, but I prefer [[https://en.wikipedia.org/wiki/Scrum_(software_development)][Scrum]]. Scrum is a
71framework based on Agile that enables a team to work through a project through a
72series of roles, ceremonies, artifacts, and values.
73
74Let's dive into each of these individually.
75
76** Scrum Team
77A scrum project is only as good as the team running the project. Standard scrum
78teams are separated into three distinct areas:
79
801. *Product Owner (Client Contact)*: The client contact is the audit equivalent
81 of the product owner in Scrum. They are responsible for partnering with the
82 engagement or audit team to ensure progress is being made, priorities are
83 established, and clear guidance is given when questions or findings arise
84 within each sprint.
852. *Scrum Master (Engagement Lead)*: The engagement or audit team lead is
86 responsible for coaching the team and the client contact on the scrum
87 process, tracking team progress against plan, scheduling necessary resources,
88 and helping remove obstacles.
893. *Scrum Developers (Engagement Members)*: The engagement or audit team is the
90 set of team members responsible for getting the work done. These team members
91 will work on each task, report progress, resolve obstacles, and collaborate
92 with other team members and the client contact to ensure goals are being met.
93
94** Scrum Ceremonies
95Scrum ceremonies are events that are performed on a regular basis.
96
971. *Sprint Planning*: The team works together to plan the upcoming sprint goal
98 and which user stories (tasks) will be added to the sprint to achieve that
99 goal.
1002. *Sprint*: The time period, typically at least one week and no more than one
101 month in length, where the team works on the stories and anything in the
102 backlog.
1033. *Daily Scrum*: A very short meeting held each day, typically 15 minutes, to
104 quickly emphasize alignment on the sprint goal and plan the next 24 hours.
105 Each team member may share what they did the day before, what they'll do
106 today, and any obstacles to their work.
1074. *Sprint Review*: At the end of each sprint, the team will gather and discuss
108 the progress, obstacles, and backlog from the previous sprint.
1095. *Sprint Retrospective*: More specific than the sprint review, the
110 retrospective is meant to discuss what worked and what did not work during
111 the sprint. This may be processes, tools, people, or even things related to
112 the Scrum ceremonies.
113
114One additional ceremony that may be applicable is organizing the backlog. This
115is typically the responsibility of the engagement leader and is meant to
116prioritize and clarify what needs to be done to complete items in the backlog.
117
118** Artifacts
119While artifacts are generally not customizable in the audit world (i.e., each
120control test must include some kind of working paper with evidence supporting
121the test results), I wanted to include some quick notes on associating scrum
122artifact terms with an audit.
123
1241. *Product Backlog*: This is the overall backlog of unfinished audit tasks from
125 all prior sprints.
1262. *Sprint Backlog*: This is the backlog of unfinished audit tasks from one
127 individual sprint.
1283. *Increment*: This is the output of each sprint - generally this is best
129 thought of as any documentation prepared during the sprint, such as risk
130 assessments, control working papers, deficiency analysis, etc.
131
132* Kanban
133Last but not least, Kanban is a methodology that relies on boards to categorize
134work into distinct, descriptive categories that allow an agile or scrum team to
135effectively plan the work of a sprint or project.
136
137See Atlassian's [[https://www.atlassian.com/agile/kanban][Kanban]] page for more information.
content/blog/2023-09-15-self-hosting-gitweb.org → content/blog/2023-09-15-self-hosting-gitweb.md renamed +30 −24
@@ -1,12 +1,16 @@
1#+title: Self-Hosting GitWeb via Nginx 1+++
2#+date: 2023-09-15 2date = 2023-09-15
3#+description: A guide to self-hosting GitWeb using the Nginx web server. 3title = "Self-Hosting Gitweb via Nginx"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* Overview 8# Overview
7[[https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb][GitWeb]] is a 9
10[GitWeb](https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb) is a
8simple web-based visualizer for git repositories. By default, GitWeb 11simple web-based visualizer for git repositories. By default, GitWeb
9will only run with the =lighttpd= or =webrick= web servers. 12will only run with the `lighttpd` or `webrick` web
13servers.
10 14
11However, this guide will show you how to keep GitWeb running in the 15However, this guide will show you how to keep GitWeb running in the
12background and display information for all repositories in a chosen 16background and display information for all repositories in a chosen
@@ -14,24 +18,25 @@ directory.
14 18
15See below for the final result: 19See below for the final result:
16 20
17#+caption: Gitweb 21![Gitweb](https://img.cleberg.net/blog/20230915-gitweb/gitweb.png)
18[[https://img.cleberg.net/blog/20230915-gitweb/gitweb.png]] 22
23# Install Dependencies
19 24
20* Install Dependencies 25To start, you\'ll need install the following packages:
21To start, you'll need install the following packages:
22 26
23#+begin_src sh 27```sh
24sudo apt install git gitweb fcgiwrap nginx 28sudo apt install git gitweb fcgiwrap nginx
25#+end_src 29```
30
31# Configure Nginx
26 32
27* Configure Nginx
28Once installed, create an Nginx configuration file. 33Once installed, create an Nginx configuration file.
29 34
30#+begin_src sh 35```sh
31sudo nano /etc/nginx/sites-available/git.example.com 36sudo nano /etc/nginx/sites-available/git.example.com
32#+end_src 37```
33 38
34#+begin_src conf 39``` conf
35server { 40server {
36 listen 80; 41 listen 80;
37 server_name example.com; 42 server_name example.com;
@@ -50,23 +55,24 @@ server {
50 index index.cgi; 55 index index.cgi;
51 } 56 }
52} 57}
53#+end_src 58```
54 59
55To make the configuration active, you need to symlink it and then 60To make the configuration active, you need to symlink it and then
56restart Nginx. 61restart Nginx.
57 62
58#+begin_src sh 63```sh
59sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com 64sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com
60sudo systemctl restart nginx.service 65sudo systemctl restart nginx.service
61#+end_src 66```
62 67
63The GitWeb application should now be available via the URL you set in 68The GitWeb application should now be available via the URL you set in
64the Nginx configuration above. 69the Nginx configuration above.
65 70
66* Customize GitWeb 71# Customize GitWeb
72
67If you need to, you can customize many things about Gitweb by editing 73If you need to, you can customize many things about Gitweb by editing
68the [[https://git-scm.com/docs/gitweb.conf][gitweb.conf]] file. 74the [gitweb.conf](https://git-scm.com/docs/gitweb.conf) file.
69 75
70#+begin_src sh 76```sh
71sudo nano /etc/gitweb.conf 77sudo nano /etc/gitweb.conf
72#+end_src 78```
content/blog/2023-09-19-audit-sql-scripts.org → content/blog/2023-09-19-audit-sql-scripts.md renamed +52 −41
@@ -1,21 +1,25 @@
1#+title: Useful SQL Scripts for Auditing Logical Access 1+++
2#+date: 2023-09-19 2date = 2023-09-19
3#+description: A reference of SQL scripts for auditing logical access for common databases. 3title = "Useful SQL Scripts for Auditing Logical Access"
4#+filetags: :audit: 4description = ""
5draft = false
6+++
7
8# Overview
5 9
6* Overview
7When you have to scope a database into your engagement, you may be 10When you have to scope a database into your engagement, you may be
8curious how to best extract the information from the database. While 11curious how to best extract the information from the database. While
9there are numerous different methods to extract this type of 12there are numerous different methods to extract this type of
10information, I'm going to show an example of how to gather all users and 13information, I\'m going to show an example of how to gather all users
11privileges from three main database types: Oracle, Microsoft SQL, and 14and privileges from three main database types: Oracle, Microsoft SQL,
12MySQL. 15and MySQL.
16
17# Oracle
13 18
14* Oracle
15You can use the following SQL script to see all users and their 19You can use the following SQL script to see all users and their
16privileges in an Oracle database: 20privileges in an Oracle database:
17 21
18#+begin_src sql 22``` sql
19SELECT 23SELECT
20 grantee AS "User", 24 grantee AS "User",
21 privilege AS "Privilege" 25 privilege AS "Privilege"
@@ -31,37 +35,40 @@ FROM
31 dba_tab_privs 35 dba_tab_privs
32WHERE 36WHERE
33 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs); 37 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs);
34#+end_src 38```
35 39
36This script queries the =dba_sys_privs= and =dba_tab_privs= views to 40This script queries the `dba_sys_privs` and
37retrieve system and table-level privileges respectively. It then 41`dba_tab_privs` views to retrieve system and table-level
38combines the results using =UNION ALL= to show all users and their 42privileges respectively. It then combines the results using
39associated privileges. Please note that this method does not extract 43`UNION ALL` to show all users and their associated
40information from the =dba_role_privs= table - use the method below for 44privileges. Please note that this method does not extract information
45from the `dba_role_privs` table - use the method below for
41that data. 46that data.
42 47
43Please note that you might need appropriate privileges (e.g., DBA 48Please note that you might need appropriate privileges (e.g., DBA
44privileges) to access these views, and you should exercise caution when 49privileges) to access these views, and you should exercise caution when
45querying system tables in a production Oracle database. 50querying system tables in a production Oracle database.
46 51
47** Alternative Oracle Query 52## Alternative Oracle Query
48You can also extract each table's information separately and perform 53
54You can also extract each table\'s information separately and perform
49processing outside the database to explore and determine the information 55processing outside the database to explore and determine the information
50necessary for the audit: 56necessary for the audit:
51 57
52#+begin_src sql 58``` sql
53SELECT ** FROM sys.dba_role_privs; 59SELECT ** FROM sys.dba_role_privs;
54SELECT ** FROM sys.dba_sys_privs; 60SELECT ** FROM sys.dba_sys_privs;
55SELECT ** FROM sys.dba_tab_privs; 61SELECT ** FROM sys.dba_tab_privs;
56SELECT ** FROM sys.dba_users; 62SELECT ** FROM sys.dba_users;
57#+end_src 63```
64
65# Microsoft SQL
58 66
59* Microsoft SQL
60You can use the following SQL script to see all users and their 67You can use the following SQL script to see all users and their
61privileges in a Microsoft SQL Server database 68privileges in a Microsoft SQL Server database
62([[https://stackoverflow.com/a/30040784][source]]): 69([source](https://stackoverflow.com/a/30040784)):
63 70
64#+begin_src sql 71``` sql
65/* 72/*
66Security Audit Report 73Security Audit Report
671) List all access provisioned to a sql user or windows user/group directly 741) List all access provisioned to a sql user or windows user/group directly
@@ -92,7 +99,7 @@ ObjectName : Name of the object that the user/role is assigned permissions
92 definitions. 99 definitions.
93ColumnName : Name of the column of the object that the user/role is assigned permissions on. This value 100ColumnName : Name of the column of the object that the user/role is assigned permissions on. This value
94 is only populated if the object is a table, view or a table value function. 101 is only populated if the object is a table, view or a table value function.
95,*/ 102*/
96 103
97--List all access provisioned to a sql user or windows user/group directly 104--List all access provisioned to a sql user or windows user/group directly
98SELECT 105SELECT
@@ -206,41 +213,45 @@ ORDER BY
206 perm.[permission_name], 213 perm.[permission_name],
207 perm.[state_desc], 214 perm.[state_desc],
208 obj.type_desc--perm.[class_desc] 215 obj.type_desc--perm.[class_desc]
209#+end_src 216```
217
218# MySQL
210 219
211* MySQL
212You can use the following SQL script to see all users and their 220You can use the following SQL script to see all users and their
213privileges in a MySQL database: 221privileges in a MySQL database:
214 222
215#+begin_src sh 223```sh
216mysql -u root -p 224mysql -u root -p
217#+end_src 225```
218 226
219Find all users and hosts with access to the database: 227Find all users and hosts with access to the database:
220 228
221#+begin_src sql 229``` sql
222SELECT ** FROM information_schema.user_privileges; 230SELECT ** FROM information_schema.user_privileges;
223#+end_src 231```
224 232
225This script retrieves user information and their associated 233This script retrieves user information and their associated
226database-level privileges from the =information_schema.user_privileges= 234database-level privileges from the
227table in MySQL. It lists various privileges such as SELECT, INSERT, 235`information_schema.user_privileges` table in MySQL. It lists
228UPDATE, DELETE, CREATE, and more for each user and database combination. 236various privileges such as SELECT, INSERT, UPDATE, DELETE, CREATE, and
237more for each user and database combination.
229 238
230Please note that you may need appropriate privileges (e.g., =SELECT= 239Please note that you may need appropriate privileges (e.g.,
231privileges on =information_schema.user_privileges=) to access this 240`SELECT` privileges on
241`information_schema.user_privileges`) to access this
232information in a MySQL database. Additionally, some privileges like 242information in a MySQL database. Additionally, some privileges like
233GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all 243GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all
234users and databases. 244users and databases.
235 245
236** Alternative MySQL Query 246## Alternative MySQL Query
247
237You can also grab individual sets of data from MySQL if you prefer to 248You can also grab individual sets of data from MySQL if you prefer to
238join them after extraction. I have marked the queries below with 249join them after extraction. I have marked the queries below with
239=SELECT ...= and excluded most =WHERE= clauses for brevity. You should 250`SELECT ...` and excluded most `WHERE` clauses for
240determine the relevant privileges in-scope and query for those 251brevity. You should determine the relevant privileges in-scope and query
241privileges to reduce the length of time to query. 252for those privileges to reduce the length of time to query.
242 253
243#+begin_src sql 254``` sql
244-- Global Permissions 255-- Global Permissions
245SELECT ... FROM mysql.user; 256SELECT ... FROM mysql.user;
246 257
@@ -259,4 +270,4 @@ WHERE db = @db_name;
259-- Password Configuration 270-- Password Configuration
260SHOW GLOBAL VARIABLES LIKE 'validate_password%'; 271SHOW GLOBAL VARIABLES LIKE 'validate_password%';
261SHOW VARIABLES LIKE 'validate_password%'; 272SHOW VARIABLES LIKE 'validate_password%';
262#+end_src 273```
content/blog/2023-10-04-digital-minimalism.md added +107
@@ -0,0 +1,107 @@
1+++
2date = 2023-10-04
3title = "Digital Minimalism"
4description = ""
5draft = false
6+++
7
8I\'ve written [a note about minimalism](file:///wiki/#digital-garden)
9before, but I wanted to dedicate some time to reflect on digital
10minimalism and how I\'ve been able to minimize the impact of digital
11devices in my life.
12
13> These changes crept up on us and happened fast, before we had a chance
14> to step back and ask what we really wanted out of the rapid advances
15> of the past decade. We added new technologies to the periphery of our
16> experience for minor reasons, then woke one morning to discover that
17> they had colonized the core of our daily life. We didn\'t, in other
18> words, sign up for the digital world in which we\'re currently
19> entrenched; we seem to have stumbled backward into it.
20>
21> *(Digital Minimalism, 2019)*
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
32 each individual item provides in isolation.
332. Optimization is important.
34 - Digital minimalists believe that deciding a particular
35 technology supports something they value is only the first step.
36 To truly extract its full potential benefit, it\'s necessary to
37 think 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
41 engage with new technologies. This source of satisfaction is
42 independent of the specific decisions they make and is one of
43 the biggest reasons that minimalism tends to be immensely
44 meaningful to its 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
54 as 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
75 phone to just eight apps on the home screen and four in the dock. If
76 I need 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
79 at 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,
97 etc.
98- Let yourself be bored and wander into creativity.
99
100# Making Progress
101
102I\'ll be taking notes as I continue down this journey and hope to see
103positive trends. I\'ve always been a minimalist in the physical world
104and it feels refreshing to filter out the clutter that has come to
105dominate my digital life over the years.
106
107I\'m excited to see where this journey leads.
content/blog/2023-10-04-digital-minimalism.org deleted −100
@@ -1,100 +0,0 @@
1#+title: Digital Minimalism
2#+date: 2023-10-04
3#+description: My personal retrospective on digital minimalism.
4#+filetags: :personal:
5
6I've written [[/wiki/#digital-garden][a note about minimalism]] before,
7but I wanted to dedicate some time to reflect on digital minimalism and
8how I've been able to minimize the impact of digital devices in my life.
9
10#+begin_quote
11These changes crept up on us and happened fast, before we had a chance
12to step back and ask what we really wanted out of the rapid advances of
13the past decade. We added new technologies to the periphery of our
14experience for minor reasons, then woke one morning to discover that
15they had colonized the core of our daily life. We didn't, in other
16words, sign up for the digital world in which we're currently
17entrenched; we seem to have stumbled backward into it.
18
19/(Digital Minimalism, 2019)/
20
21#+end_quote
22
23* The Principles of Digital Minimalism
24As noted in Cal Newport's book, /Digital Minimalism/, there are three
25main principles to digital minimalism that I tend to agree with:
26
271. Clutter is costly.
28 - Digital minimalists recognize that cluttering their time and
29 attention with too many devices, apps, and services creates an
30 overall negative cost that can swamp the small benefits that each
31 individual item provides in isolation.
322. Optimization is important.
33 - Digital minimalists believe that deciding a particular technology
34 supports something they value is only the first step. To truly
35 extract its full potential benefit, it's necessary to think
36 carefully about how they'll use the technology.
373. Intentionality is satisfying.
38 - Digital minimalists derive significant satisfaction from their
39 general commitment to being more intentional about how they engage
40 with new technologies. This source of satisfaction is independent
41 of the specific decisions they make and is one of the biggest
42 reasons that minimalism tends to be immensely meaningful to its
43 practitioners.
44
45* Taking Action
46In order to put the logic into practice, I've created a few new habits
47and continued performing old habits that are working well:
48
49** Using Devices With Intention
50- I already rarely use "social media", mostly limited to forums such as
51 Hacker News and Tildes, so I've just tweaked my behavior to stop
52 looking for content in those places when I'm bored.
53- Use devices with intention. Each time I pick up a digital device,
54 there should be an intention to use the device to improve my current
55 situation. No more endless scrolling or searching for something to
56 interest me.
57
58** Prevent Distractions
59- Disable (most) notifications on all devices. I spent 15-30 minutes
60 going through the notifications on my phone, watch, and computer to
61 ensure that only a select few apps have the ability to interrupt me:
62 Calendar, Messages, Phone, Reminders, & Signal.
63- Disable badges for any apps except the ones mentioned in the bullet
64 above.
65- Set-up focus profiles across devices so that I can enable different
66 modes, such as Personal when I only want to see notifications from
67 people I care about or Do Not Disturb, where absolutely nothing can
68 interrupt me.
69- Clean up my home screens. This one was quite easy as I already
70 maintain a minimalist set-up, but I went extreme by limiting my phone
71 to just eight apps on the home screen and four in the dock. If I need
72 another app, I'll have to search or use the app library.
73- Remove the work profile from my phone. This was a tough decision as
74 having my work profile on my device definitely makes my life easier at
75 times, but it also has quite a negative effect when I'm "always
76 online" and can see the notifications and team activity 24/7. I
77 believe creating a distinct barrier between my work and personal
78 devices will be beneficial in the end.
79
80** Creating Alternative Activities
81This is the most difficult piece, as most of my hobbies and interests
82lie in the digital world. However, I'm making a concerted effort to put
83devices down unless necessary and force myself to perform other
84activities in the physical world instead.
85
86I've started with a few basics that are always readily available to me:
87
88- Do a chore, such as organizing or cleaning.
89- Read a book, study a piece of art, etc.
90- Exercise or get outdoors.
91- Participate in a hobby, such as photography, birding, disc golf, etc.
92- Let yourself be bored and wander into creativity.
93
94* Making Progress
95I'll be taking notes as I continue down this journey and hope to see
96positive trends. I've always been a minimalist in the physical world and
97it feels refreshing to filter out the clutter that has come to dominate
98my digital life over the years.
99
100I'm excited to see where this journey leads.
content/blog/2023-10-11-self-hosting-authelia.org → content/blog/2023-10-11-self-hosting-authelia.md renamed +103 −88
@@ -1,10 +1,13 @@
1#+title: Self-Hosting Authelia 1+++
2#+date: 2023-10-11 2date = 2023-10-11
3#+description: A guide to self-hosting the Authelia application on your own server. 3title = "Self-Hosting Authelia"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* Overview 8# Overview
7[[https://www.authelia.com/][Authelia]] is an open-source authentication 9
10[Authelia](https://www.authelia.com/) is an open-source authentication
8service that allows you to place a portal between end users on the 11service that allows you to place a portal between end users on the
9internet and self-hosted services on your server. 12internet and self-hosted services on your server.
10 13
@@ -13,38 +16,42 @@ authentication for any such user before allowing them to access a
13specific service on your domain. 16specific service on your domain.
14 17
15This guide will walk through a standard installation of Authelia for 18This guide will walk through a standard installation of Authelia for
16=example.com=, using =auth.example.com= as Authelia's authentication 19`example.com`, using `auth.example.com` as
17domain and =teddit.example.com= as the website we want to protect behind 20Authelia\'s authentication domain and `teddit.example.com` as
18the authentication portal. 21the website we want to protect behind the authentication portal.
22
23# Prerequisites
19 24
20* Prerequisites
21This guide assumes you have the following already set-up: 25This guide assumes you have the following already set-up:
22 26
23- A registered domain with DNS pointing to your server. 27- A registered domain with DNS pointing to your server.
24- A subdomain for Authelia (=auth.example.com=) and a subdomain to 28- A subdomain for Authelia (`auth.example.com`) and a
25 protect via Authelia (=app.example.com=). 29 subdomain to protect via Authelia (`app.example.com`).
26- A working Nginx web server. 30- A working Nginx web server.
27- Docker and docker-compose installed. 31- Docker and docker-compose installed.
32
33# Installation
28 34
29* Installation
30This guide will walk through each installation step one-by-one, starting 35This guide will walk through each installation step one-by-one, starting
31with the container and finishing by cleaning up external access via an 36with the container and finishing by cleaning up external access via an
32Nginx reverse proxy. 37Nginx reverse proxy.
33 38
34** Docker-Compose 39## Docker-Compose
40
35To start, create a directory for Authelia and create a 41To start, create a directory for Authelia and create a
36=docker-compose.yml= file. 42`docker-compose.yml` file.
37 43
38#+begin_src sh 44```sh
39mkdir ~/authelia 45mkdir ~/authelia
40nano ~/authelia/docker-compose.yml 46nano ~/authelia/docker-compose.yml
41#+end_src 47```
42 48
43Within this file, paste the following content. If you prefer a different 49Within this file, paste the following content. If you prefer a different
44local port, modify the port on the left side of the colon on the 50local port, modify the port on the left side of the colon on the
45=9091:9091= line. Be sure to modify the =TZ= variable to your timezone. 51`9091:9091` line. Be sure to modify the `TZ`
52variable to your timezone.
46 53
47#+begin_src yml 54``` yml
48version: '3.3' 55version: '3.3'
49 56
50services: 57services:
@@ -57,52 +64,55 @@ services:
57 - 9091:9091 64 - 9091:9091
58 environment: 65 environment:
59 - TZ=America/Chicago 66 - TZ=America/Chicago
60#+end_src 67```
61 68
62Start the container with docker-compose: 69Start the container with docker-compose:
63 70
64#+begin_src sh 71```sh
65sudo docker-compose up -d 72sudo docker-compose up -d
66#+end_src 73```
67 74
68After the first start, the container will automatically exit and require 75After the first start, the container will automatically exit and require
69you to modify the app's configuration files before continuing. Read on 76you to modify the app\'s configuration files before continuing. Read on
70to learn more. 77to learn more.
71 78
72** Authelia Configuration 79## Authelia Configuration
80
73To configure Authelia before we restart the container, we need to open 81To configure Authelia before we restart the container, we need to open
74the =config= directory and modify the files. Start by editing the 82the `config` directory and modify the files. Start by editing
75=configuration.yml= file, where all of Authelia's settings are stored. 83the `configuration.yml` file, where all of Authelia\'s
84settings are stored.
76 85
77My personal preference is to copy the original configuration file to a 86My personal preference is to copy the original configuration file to a
78backup file and edit a fresh copy. 87backup file and edit a fresh copy.
79 88
80#+begin_src sh 89```sh
81sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk 90sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk
82sudo nano ~/authelia/config/configuration.yml 91sudo nano ~/authelia/config/configuration.yml
83#+end_src 92```
84 93
85Within the blank =configuration.yml= file, paste the following 94Within the blank `configuration.yml` file, paste the
86information. You will need to make quite a few updates, so be sure to 95following information. You will need to make quite a few updates, so be
87read each line carefully and modify as necessary. 96sure to read each line carefully and modify as necessary.
88 97
89The major required changes are: 98The major required changes are:
90 99
91- Any instances of =example.com= should be replaced by your domain. 100- Any instances of `example.com` should be replaced by your
92- =jwt_secret= - Use the =pwgen 40 1= command to generate a secret for 101 domain.
93 yourself. 102- `jwt_secret` - Use the `pwgen 40 1` command to
94- =access_control= - Set the Authelia domain to bypass here, as well as 103 generate a secret for yourself.
95 any subdomains you want to protect. 104- `access_control` - Set the Authelia domain to bypass
96- =session= > =secret= - Use the =pwgen 40 1= command to generate a 105 here, as well as any subdomains you want to protect.
97 secret for yourself. 106- `session` \> `secret` - Use the
98- =regulation= - Set the variables here to restrict login attempts and 107 `pwgen 40 1` command to generate a secret for yourself.
99 bans. 108- `regulation` - Set the variables here to restrict login
100- =storage= > =encryption_key= - Use the =pwgen 40 1= command to 109 attempts and bans.
101 generate a secret for yourself. 110- `storage` \> `encryption_key` - Use the
102- =smtp= - If you have access to an SMTP service, set up the information 111 `pwgen 40 1` command to generate a secret for yourself.
103 here to active outgoing emails. 112- `smtp` - If you have access to an SMTP service, set up
104 113 the information here to active outgoing emails.
105#+begin_src yml 114
115``` yml
106# yamllint disable rule:comments-indentation 116# yamllint disable rule:comments-indentation
107--- 117---
108############################################################################### 118###############################################################################
@@ -191,25 +201,26 @@ notifier:
191 skip_verify: false 201 skip_verify: false
192 minimum_version: TLS1.2 202 minimum_version: TLS1.2
193... 203...
194#+end_src 204```
205
206## Authelia Users
195 207
196** Authelia Users
197Next, create the users file for authentication. 208Next, create the users file for authentication.
198 209
199#+begin_src sh 210```sh
200sudo nano ~/authelia/config/users_database.yml 211sudo nano ~/authelia/config/users_database.yml
201#+end_src 212```
202 213
203Within the file, you will need to create an entry for each user that 214Within the file, you will need to create an entry for each user that
204needs access to Authelia. The =my_username= entry will be the username 215needs access to Authelia. The `my_username` entry will be the
205used on the login page. 216username used on the login page.
206 217
207To generate the password, go to [[https://argon2.online][Argon2 Hash 218To generate the password, go to [Argon2 Hash
208Generator]], generate a random salt, and make sure the rest of the 219Generator](https://argon2.online), generate a random salt, and make sure
209settings match the =authentication_backend= section of 220the rest of the settings match the `authentication_backend`
210=configuration.yml= file. 221section of `configuration.yml` file.
211 222
212#+begin_src yaml 223``` yaml
213users: 224users:
214 my_username: 225 my_username:
215 displayname: "My User" 226 displayname: "My User"
@@ -220,30 +231,32 @@ users:
220 groups: 231 groups:
221 - admins 232 - admins
222 - dev 233 - dev
223#+end_src 234```
224 235
225Once the app is configured, restart the container from scratch. 236Once the app is configured, restart the container from scratch.
226 237
227#+begin_src sh 238```sh
228cd ~/authelia 239cd ~/authelia
229sudo docker-compose down && sudo docker-compose up -d 240sudo docker-compose down && sudo docker-compose up -d
230#+end_src 241```
242
243## Nginx: Authelia Domain
231 244
232** Nginx: Authelia Domain
233Once the container is running and configured, the final step is to 245Once the container is running and configured, the final step is to
234configure external access to the server via Nginx reverse proxy. 246configure external access to the server via Nginx reverse proxy.
235 247
236Start by creating the Authelia domain. 248Start by creating the Authelia domain.
237 249
238#+begin_src sh 250```sh
239sudo nano /etc/nginx/sites-available/auth 251sudo nano /etc/nginx/sites-available/auth
240#+end_src 252```
241 253
242Within this file, paste the following information and be sure to update 254Within this file, paste the following information and be sure to update
243=example.com= to your domain. Make sure the =$upstream_authelia= 255`example.com` to your domain. Make sure the
244variable matches the location of your Authelia container. 256`$upstream_authelia` variable matches the location of your
257Authelia container.
245 258
246#+begin_src conf 259``` conf
247server { 260server {
248 if ($host ~ ^[^.]+\.example\.com$) { 261 if ($host ~ ^[^.]+\.example\.com$) {
249 return 301 https://$host$request_uri; 262 return 301 https://$host$request_uri;
@@ -295,29 +308,30 @@ server {
295 } 308 }
296 309
297} 310}
298#+end_src 311```
299 312
300Next, symlink the file and restart Nginx. If there are errors, be sure 313Next, symlink the file and restart Nginx. If there are errors, be sure
301to resolve those before moving on. 314to resolve those before moving on.
302 315
303#+begin_src sh 316```sh
304sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth 317sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth
305sudo systemctl restart nginx.service 318sudo systemctl restart nginx.service
306#+end_src 319```
320
321## Nginx: Protected Domain(s)
307 322
308** Nginx: Protected Domain(s)
309Now that Authelia is accessible externally, you need to configure the 323Now that Authelia is accessible externally, you need to configure the
310domain you intend to protect with Authelia. In this example, I'm 324domain you intend to protect with Authelia. In this example, I\'m
311protecting =teddit.example.com=. 325protecting `teddit.example.com`.
312 326
313Similar to the process above, paste the content and update the relevant 327Similar to the process above, paste the content and update the relevant
314variables. 328variables.
315 329
316#+begin_src sh 330```sh
317sudo nano /etc/nginx/sites-available/teddit 331sudo nano /etc/nginx/sites-available/teddit
318#+end_src 332```
319 333
320#+begin_src conf 334``` conf
321server { 335server {
322 if ($host ~ ^[^.]+\.example\.com$) { 336 if ($host ~ ^[^.]+\.example\.com$) {
323 return 301 https://$host$request_uri; 337 return 301 https://$host$request_uri;
@@ -419,25 +433,26 @@ server {
419 proxy_buffers 64 256k; 433 proxy_buffers 64 256k;
420 } 434 }
421} 435}
422#+end_src 436```
423 437
424Same as before, symlink the file and restart Nginx. 438Same as before, symlink the file and restart Nginx.
425 439
426#+begin_src sh 440```sh
427sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit 441sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit
428sudo systemctl restart nginx.service 442sudo systemctl restart nginx.service
429#+end_src 443```
444
445# Results
430 446
431* Results
432When visiting the protected domain, you will now be redirected to your 447When visiting the protected domain, you will now be redirected to your
433authentication domain and presented with the Authelia login portal. 448authentication domain and presented with the Authelia login portal.
434 449
435#+caption: Authelia Portal 450![Authelia
436[[https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png]] 451Portal](https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png)
437 452
438Once you've successfully authenticated, you can visit your 453Once you\'ve successfully authenticated, you can visit your
439authentication domain directly and see that you're currently 454authentication domain directly and see that you\'re currently
440authenticated to any domain protected by Authelia. 455authenticated to any domain protected by Authelia.
441 456
442#+caption: Authelia Success 457![Authelia
443[[https://img.cleberg.net/blog/20231010-authelia/authelia_success.png]] 458Success](https://img.cleberg.net/blog/20231010-authelia/authelia_success.png)
content/blog/2023-10-15-alpine-ssh-hardening.org → content/blog/2023-10-15-alpine-ssh-hardening.md renamed +41 −37
@@ -1,71 +1,75 @@
1#+title: SSH Hardening for Alpine Linux 1+++
2#+date: 2023-10-15 2date = 2023-10-15
3#+description: A quick guide to harden SSH configuration on Alpine. 3title = "SSH Hardening for Alpine Linux"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
7
8# Overview
5 9
6* Overview
7This guide follows the standard 10This guide follows the standard
8[[https://www.ssh-audit.com/hardening_guides.html][ssh-audit]] hardening 11[ssh-audit](https://www.ssh-audit.com/hardening_guides.html) hardening
9guide, tweaked for Alpine Linux. 12guide, tweaked for Alpine Linux.
10 13
11* Hardening Guide 14# Hardening Guide
12These steps must be performed as root. You can try to use =doas= or 15
13=sudo=, but there may be issues. 16These steps must be performed as root. You can try to use
17`doas` or `sudo`, but there may be issues.
14 18
151. Re-generate the RSA and ED25519 keys 191. Re-generate the RSA and ED25519 keys
16 20
17#+begin_src sh 21```sh
18rm /etc/ssh/ssh_host_* 22rm /etc/ssh/ssh_host_*
19ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" 23ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
20ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" 24ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
21#+end_src 25```
22 26
232. [@2] Remove small Diffie-Hellman moduli 272. Remove small Diffie-Hellman moduli
24 28
25#+begin_src sh 29```sh
26awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe 30awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe
27mv /etc/ssh/moduli.safe /etc/ssh/moduli 31mv /etc/ssh/moduli.safe /etc/ssh/moduli
28#+end_src 32```
29 33
303. [@3] Enable the RSA and ED25519 HostKey directives in the 343. Enable the RSA and ED25519 HostKey directives in the
31 /etc/ssh/sshd_{config} file 35 /etc/ssh/sshd~config~ file
32 36
33#+begin_src sh 37```sh
34sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config 38sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config
35#+end_src 39```
36 40
374. [@4] Restrict supported key exchange, cipher, and MAC algorithms 414. Restrict supported key exchange, cipher, and MAC algorithms
38 42
39#+begin_src sh 43```sh
40echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf 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
41#+end_src 45```
42 46
435. [@5] Include the /etc/ssh/sshd_{config}.d directory 475. Include the /etc/ssh/sshd~config~.d directory
44 48
45#+begin_src sh 49```sh
46echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config 50echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config
47#+end_src 51```
48 52
496. [@6] Restart OpenSSH server 536. Restart OpenSSH server
50 54
51#+begin_src sh 55```sh
52rc-service sshd restart 56rc-service sshd restart
53#+end_src 57```
58
59# Testing SSH
54 60
55* Testing SSH 61You can test the results with the `ssh-audit` python script.
56You can test the results with the =ssh-audit= python script.
57 62
58#+begin_src sh 63```sh
59pip3 install ssh-audit 64pip3 install ssh-audit
60ssh-audit localhost 65ssh-audit localhost
61#+end_src 66```
62 67
63If everything succeeded, the results will show as all green. If anything 68If everything succeeded, the results will show as all green. If anything
64is yellow, orange, or red, you may need to tweak additional settings. 69is yellow, orange, or red, you may need to tweak additional settings.
65 70
66#+begin_src txt 71``` txt
67,#+caption: ssh audit 72#+caption: ssh audit
68#+end_src 73```
69 74
70#+caption: ssh-audit 75![ssh-audit](https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png)
71[[https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png]]
content/blog/2023-10-17-self-hosting-anonymousoverflow.org → content/blog/2023-10-17-self-hosting-anonymousoverflow.md renamed +36 −28
@@ -1,30 +1,36 @@
1#+title: Self-Hosting AnonymousOverflow 1+++
2#+date: 2023-10-17 2date = 2023-10-17
3#+description: A guide to self-hosting the AnonymousOverflow application on your own server. 3title = "Self-Hosting AnonymousOverflow"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
7
8# Overview
5 9
6* Overview
7I recently launched an instance of AnonymousOverflow at 10I recently launched an instance of AnonymousOverflow at
8[[https://ao.cleberg.net][ao.cleberg.net]] and wanted to write a brief 11[ao.cleberg.net](https://ao.cleberg.net) and wanted to write a brief
9post on how easy it is to install with Docker Compose and Nginx. 12post on how easy it is to install with Docker Compose and Nginx.
10 13
11This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse 14This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse
12proxy. 15proxy.
13 16
14* Installation 17# Installation
15** Docker Compose 18
19## Docker Compose
20
16To install AnonymousOverflow, start by creating a directory for the 21To install AnonymousOverflow, start by creating a directory for the
17application and create its =docker-compose.yml= file. 22application and create its `docker-compose.yml` file.
18 23
19#+begin_src sh 24```sh
20mkdir ~/anonymousoverflow && cd ~/anonymousoverflow 25mkdir ~/anonymousoverflow && cd ~/anonymousoverflow
21nano docker-compose.yml 26nano docker-compose.yml
22#+end_src 27```
23 28
24Within this file, paste the following information. Be sure to change the 29Within this file, paste the following information. Be sure to change the
25=APP_URL=, =JWT_SIGNING_SECRET=, and =ports= to match your needs. 30`APP_URL`, `JWT_SIGNING_SECRET`, and
31`ports` to match your needs.
26 32
27#+begin_src yaml 33``` yaml
28version: '3' 34version: '3'
29 35
30services: 36services:
@@ -37,30 +43,32 @@ services:
37 ports: 43 ports:
38 - '9380:8080' 44 - '9380:8080'
39 restart: 'always' 45 restart: 'always'
40#+end_src 46```
41 47
42Save and exit the file when complete. You can now launch the container 48Save and exit the file when complete. You can now launch the container
43and access it via your local network. 49and access it via your local network.
44 50
45#+begin_src sh 51```sh
46sudo docker-compose up -d 52sudo docker-compose up -d
47#+end_src 53```
54
55## Nginx Reverse Proxy
48 56
49** Nginx Reverse Proxy
50If you want to access this service outside the local network, I 57If you want to access this service outside the local network, I
51recommend using Nginx as a reverse proxy. 58recommend using Nginx as a reverse proxy.
52 59
53Let's start by creating a configuration file. 60Let\'s start by creating a configuration file.
54 61
55#+begin_src sh 62```sh
56sudo nano /etc/nginx/sites-available/ao 63sudo nano /etc/nginx/sites-available/ao
57#+end_src 64```
58 65
59Within this file, paste the following content and repace 66Within this file, paste the following content and repace
60=ao.example.com= with your URL. You may need to update the SSL 67`ao.example.com` with your URL. You may need to update the
61certificate statements if your certificates are in a different location. 68SSL certificate statements if your certificates are in a different
69location.
62 70
63#+begin_src conf 71``` conf
64server { 72server {
65 if ($host ~ ^[^.]+\.cleberg\.net$) { 73 if ($host ~ ^[^.]+\.cleberg\.net$) {
66 return 301 https://$host$request_uri; 74 return 301 https://$host$request_uri;
@@ -112,16 +120,16 @@ server {
112 proxy_buffers 64 256k; 120 proxy_buffers 64 256k;
113 } 121 }
114} 122}
115#+end_src 123```
116 124
117Save and exit the file when complete. On Ubuntu, you will need to 125Save and exit the file when complete. On Ubuntu, you will need to
118symlink the configuration file before it will be recognized by Nginx. 126symlink the configuration file before it will be recognized by Nginx.
119Once complete, simply restart the web server. 127Once complete, simply restart the web server.
120 128
121#+begin_src sh 129```sh
122sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao 130sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao
123sudo systemctl restart nginx.service 131sudo systemctl restart nginx.service
124#+end_src 132```
125 133
126The website will now be available publicly. Visit 134The website will now be available publicly. Visit [my
127[[https://ao.cleberg.net][my instance]] for an example. 135instance](https://ao.cleberg.net) for an example.
content/blog/2023-11-08-scli.md added +158
@@ -0,0 +1,158 @@
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
9allows you to connect to your Signal messenger account. This program
10utilizes a two-pane display that shows you chats on the left and the
11focused conversation on the right.
12
13This guide will show you how to install `scli` and its
14dependencies on Alpine Linux, which requires some extra work due to
15musl.
16
17If you\'re using a non-musl system, you can likely following the
18`scli` README and download the packaged binaries for an
19easier installation process.
20
21# Dependencies
22
23In order to use `scli`, you need a few dependencies:
24
25- `openjdk17-jre` - Used as a dependency for the
26 `signal-cli` tool. Version may vary.
27- `signal-cli` - Used as the backbone of the
28 `scli` tool.
29- `findutils` - Replaces the standard Busybox version of
30 `xargs`.
31- `urwid` - A console user interface library for Python.
32- `urwid-readline` - For GNU emacs-like keybinds on the
33 input line.
34- `qrencode` - Displays a QR code in the terminal to link
35 the device using your phone. Not necessary if you\'re only linking
36 on desktop and can copy/paste the connection URL.
37
38Let\'s start by installing the packages available via Alpine\'s
39repositories. Be sure to install the latest version of
40`openjdk`. If you run into Java-related issues, uninstall
41`openjdk` and install an older version.
42
43```sh
44doas apk add openjdk17-jre findutils qrencode
45```
46
47Next, let\'s install `signal-cli`. Be sure to export the
48version of `signal-cli` that you want. I use version
49`0.12.4` below, but that may be outdated by the time you\'re
50reading this.
51
52```sh
53export VERSION="0.12.4"
54wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
55doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
56doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
57```
58
59Finally, install the `urwid` packages using the Python
60packaging utility.
61
62```sh
63pip3 install urwid urwid-readline
64```
65
66# Installation
67
68Now that we have all of the dependencies we need, we can install
69`scli`. Start by simply cloning the repository.
70
71```sh
72git clone https://github.com/isamert/scli
73```
74
75When I cloned this repository on 2023-11-08, I found a bug in the logic
76that required a fix. You must edit the `scli` file and
77replace the one instance of `RLIMIT_OFILE` with
78`RLIMIT_NOFILE`.
79
80```sh
81cd scli
82nano scli
83```
84
85Once complete, you can move this program to anywhere on your
86`$PATH`. I chose the following directory.
87
88```sh
89doas mv scli /usr/local/bin/scli
90```
91
92# Initial Setup
93
94Now that everything is installed, we can login and configure the client.
95Start by generating a connection link.
96
97```sh
98signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
99```
100
101This will generate a connection link and related QR code for you to use
102to link the devices together. Once complete, **wait patiently\*** for
103the connection process to finish.
104
105Once it completes, it will exit and return you to the prompt. From here,
106you need to perform an initial `receive` command to start
107things off. The `USERNAME` variable should be your phone
108number, such as `+15551237890`.
109
110```sh
111signal-cli -u USERNAME receive
112```
113
114Also be sure to test the daemon to ensure it works properly. If no
115errors occur, it\'s working. If you run into errors because you\'re not
116running a DBUS session, see my notes below.
117
118```sh
119signal-cli -u USERNAME daemon
120```
121
122Once the initial reception is complete, you are ready to use
123`scli`.
124
125This process will differ depending on your desktop environment (DE). If
126you are running a DE, you likely have a DBUS session running already and
127can simply launch the program.
128
129However, if you\'re like me and running your computer straight on the
130TTY without a DE, you\'ll need to start a DBUS session for this program.
131
132```sh
133# If you're not running a DBUS session yet, you need to start one for scli
134dbus-run-session -- scli
135
136# OR - If you're already running a DBUS session, simply run scli
137scli
138```
139
140# Configuration
141
142Lastly, there are a number of configuration options that you can pass
143via the command or in the `~/.config/sclirc` file. See the
144Github README for more information on configuration options.
145
146```sh
147nano ~/.config/sclirc
148```
149
150``` conf
151# ~/.config/sclirc
152
153wrap-at = 80
154enable-notifications = true
155```
156
157That\'s it! Following this guide, I have a functional `scli`
158program that successfully sends messages to my contacts and myself!
content/blog/2023-11-08-scli.org deleted −145
@@ -1,145 +0,0 @@
1#+title: Installing scli on Alpine Linux (musl)
2#+date: 2023-11-08
3#+description: Learn how to properly configure scli for musl-based systems.
4#+filetags: :linux:
5
6[[https://github.com/isamert/scli][scli]] is a command-line tool that
7allows you to connect to your Signal messenger account. This program
8utilizes a two-pane display that shows you chats on the left and the
9focused conversation on the right.
10
11This guide will show you how to install =scli= and its dependencies on
12Alpine Linux, which requires some extra work due to musl.
13
14If you're using a non-musl system, you can likely following the =scli=
15README and download the packaged binaries for an easier installation
16process.
17
18* Dependencies
19In order to use =scli=, you need a few dependencies:
20
21- =openjdk17-jre= - Used as a dependency for the =signal-cli= tool.
22 Version may vary.
23- =signal-cli= - Used as the backbone of the =scli= tool.
24- =findutils= - Replaces the standard Busybox version of =xargs=.
25- =urwid= - A console user interface library for Python.
26- =urwid-readline= - For GNU emacs-like keybinds on the input line.
27- =qrencode= - Displays a QR code in the terminal to link the device
28 using your phone. Not necessary if you're only linking on desktop and
29 can copy/paste the connection URL.
30
31Let's start by installing the packages available via Alpine's
32repositories. Be sure to install the latest version of =openjdk=. If you
33run into Java-related issues, uninstall =openjdk= and install an older
34version.
35
36#+begin_src sh
37doas apk add openjdk17-jre findutils qrencode
38#+end_src
39
40Next, let's install =signal-cli=. Be sure to export the version of
41=signal-cli= that you want. I use version =0.12.4= below, but that may
42be outdated by the time you're reading this.
43
44#+begin_src sh
45export VERSION="0.12.4"
46wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
47doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
48doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
49#+end_src
50
51Finally, install the =urwid= packages using the Python packaging
52utility.
53
54#+begin_src sh
55pip3 install urwid urwid-readline
56#+end_src
57
58* Installation
59Now that we have all of the dependencies we need, we can install =scli=.
60Start by simply cloning the repository.
61
62#+begin_src sh
63git clone https://github.com/isamert/scli
64#+end_src
65
66When I cloned this repository on 2023-11-08, I found a bug in the logic
67that required a fix. You must edit the =scli= file and replace the one
68instance of =RLIMIT_OFILE= with =RLIMIT_NOFILE=.
69
70#+begin_src sh
71cd scli
72nano scli
73#+end_src
74
75Once complete, you can move this program to anywhere on your =$PATH=. I
76chose the following directory.
77
78#+begin_src sh
79doas mv scli /usr/local/bin/scli
80#+end_src
81
82* Initial Setup
83Now that everything is installed, we can login and configure the client.
84Start by generating a connection link.
85
86#+begin_src sh
87signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
88#+end_src
89
90This will generate a connection link and related QR code for you to use
91to link the devices together. Once complete, *wait patiently** for the
92connection process to finish.
93
94Once it completes, it will exit and return you to the prompt. From here,
95you need to perform an initial =receive= command to start things off.
96The =USERNAME= variable should be your phone number, such as
97=+15551237890=.
98
99#+begin_src sh
100signal-cli -u USERNAME receive
101#+end_src
102
103Also be sure to test the daemon to ensure it works properly. If no
104errors occur, it's working. If you run into errors because you're not
105running a DBUS session, see my notes below.
106
107#+begin_src sh
108signal-cli -u USERNAME daemon
109#+end_src
110
111Once the initial reception is complete, you are ready to use =scli=.
112
113This process will differ depending on your desktop environment (DE). If
114you are running a DE, you likely have a DBUS session running already and
115can simply launch the program.
116
117However, if you're like me and running your computer straight on the TTY
118without a DE, you'll need to start a DBUS session for this program.
119
120#+begin_src sh
121# If you're not running a DBUS session yet, you need to start one for scli
122dbus-run-session -- scli
123
124# OR - If you're already running a DBUS session, simply run scli
125scli
126#+end_src
127
128* Configuration
129Lastly, there are a number of configuration options that you can pass
130via the command or in the =~/.config/sclirc= file. See the Github README
131for more information on configuration options.
132
133#+begin_src sh
134nano ~/.config/sclirc
135#+end_src
136
137#+begin_src conf
138# ~/.config/sclirc
139
140wrap-at = 80
141enable-notifications = true
142#+end_src
143
144That's it! Following this guide, I have a functional =scli= program that
145successfully sends messages to my contacts and myself!
content/blog/2023-12-03-unifi-nextdns.org → content/blog/2023-12-03-unifi-nextdns.md renamed +73 −62
@@ -1,94 +1,105 @@
1#+title: How to Install NextDNS on the Unifi Dream Machine 1+++
2#+date: 2023-12-03 2date = 2023-12-03
3#+description: A guide to properly install the NextDNS client on the UDM Pro. 3title = "How to Install NextDNS on the Unifi Dream Machine"
4#+filetags: :network: 4description = ""
5draft = false
6+++
7
8# Overview
5 9
6* Overview
7I recently installed NextDNS on my Unifi Dream Machine router using the 10I recently installed NextDNS on my Unifi Dream Machine router using the
8[[https://github.com/nextdns/nextdns/wiki/UnifiOS][UnifiOS]] wiki page 11[UnifiOS](https://github.com/nextdns/nextdns/wiki/UnifiOS) wiki page on
9on NextDNS's GitHub repository. 12NextDNS\'s GitHub repository.
10 13
11As a result of this, I wanted to write down the process in case the wiki 14As a result of this, I wanted to write down the process in case the wiki
12or installer ever gets lost. 15or installer ever gets lost.
13 16
14* Wiki 17# Wiki
18
15The following is copied from the wiki page linked above, with one 19The following is copied from the wiki page linked above, with one
16difference in the =ssh= command. 20difference in the `ssh` command.
17 21
18Install instructions for Unifi Dream Machine (UDM) standard and pro 22Install instructions for Unifi Dream Machine (UDM) standard and pro
19routers. 23routers.
20 24
21** Install 25## Install
26
22Enable SSH: 27Enable SSH:
23 28
24- Go to your unifi admin interface and select your device (not the 29- Go to your unifi admin interface and select your device (not the
25 controller settings, but the Dream Machine settings) 30 controller settings, but the Dream Machine settings)
26- Click on "Settings" at the bottom of the page 31- Click on \"Settings\" at the bottom of the page
27- Go to the "Advanced" section on the left pan 32- Go to the \"Advanced\" section on the left pan
28- Enable SSH 33- Enable SSH
29- Set a SSH password 34- Set a SSH password
30 35
31Connect to your router using =ssh root@xxx.xxx.xxx.xxx= with the 36Connect to your router using `ssh root@xxx.xxx.xxx.xxx` with
32password you configured. 37the password you configured.
33 38
34Run the following command and follow the instructions: 39Run the following command and follow the instructions:
35 40
36#+begin_src sh 41```sh
37sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"' 42sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
38#+end_src 43```
39 44
40Note: Queries from the UDM itself won't be routed to NextDNS nor 45Note: Queries from the UDM itself won\'t be routed to NextDNS nor
41encrypted due to current system limitation. All traffic from other 46encrypted due to current system limitation. All traffic from other
42devices on then network will. 47devices on then network will.
43 48
44** Upgrade 49## Upgrade
50
45To upgrade to the last version, simply re-run the installer above. If a 51To upgrade to the last version, simply re-run the installer above. If a
46new version is available, the upgrade action will added to the list of 52new version is available, the upgrade action will added to the list of
47possible actions. 53possible actions.
48 54
49** Uninstall 55## Uninstall
50To uninstall, re-run the installer above and select "Remove" in the 56
57To uninstall, re-run the installer above and select \"Remove\" in the
51menu. 58menu.
52 59
53** Troubleshooting 60## Troubleshooting
61
54If the installation fail, please the installer in debug mode and contact 62If the installation fail, please the installer in debug mode and contact
55us at team@nextdns.io with the transcript of the installation: 63us at team@nextdns.io with the transcript of the installation:
56 64
57#+begin_src sh 65```sh
58sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"' 66sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
59#+end_src 67```
68
69### Content Filtering Conflict
60 70
61*** Content Filtering Conflict
62NextDNS CLI and the UDM Content Filtering or the Ad Blocking features 71NextDNS CLI and the UDM Content Filtering or the Ad Blocking features
63are incompatible. If you want to use NextDNS CLI, please make sure they 72are incompatible. If you want to use NextDNS CLI, please make sure they
64are disabled. 73are disabled.
65 74
66To disable Content Filtering, go to Settings > Network, then for each 75To disable Content Filtering, go to Settings \> Network, then for each
67network, set the Content Filtering feature to None 76network, set the Content Filtering feature to None
68 77
69To disable Ad Blocking, go to Settings > Application Firewall. In the 78To disable Ad Blocking, go to Settings \> Application Firewall. In the
70General tab, uncheck the Ad Blocking checkbox. 79General tab, uncheck the Ad Blocking checkbox.
71 80
72*** APT Error 81### APT Error
82
73If you get an apt error as follow: 83If you get an apt error as follow:
74 84
75#+begin_src sh 85```sh
76E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80] 86E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80]
77#+end_src 87```
78 88
79You may try to following: 89You may try to following:
80 90
81#+begin_src sh 91```sh
82sed -i -e 's/deb.debian.org/archive.debian.org/g' \ 92sed -i -e 's/deb.debian.org/archive.debian.org/g' \
83 -e 's|security.debian.org|archive.debian.org/|g' \ 93 -e 's|security.debian.org|archive.debian.org/|g' \
84 -e '/stretch-updates/d' /etc/apt/sources.list 94 -e '/stretch-updates/d' /etc/apt/sources.list
85#+end_src 95```
96
97## install.sh
86 98
87** install.sh 99Here are the contents of the `install.sh` file used above, as
88Here are the contents of the =install.sh= file used above, as of 100of 2023-12-03:
892023-12-03:
90 101
91#+begin_src sh 102```sh
92#!/bin/sh 103#!/bin/sh
93 104
94main() { 105main() {
@@ -313,13 +324,13 @@ install_bin() {
313 fi 324 fi
314 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path" 325 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path"
315 case "$INSTALL_RELEASE" in 326 case "$INSTALL_RELEASE" in
316 ,*/*) 327 */*)
317 # Snapshot 328 # Snapshot
318 branch=${INSTALL_RELEASE%/*} 329 branch=${INSTALL_RELEASE%/*}
319 hash=${INSTALL_RELEASE#*/} 330 hash=${INSTALL_RELEASE#*/}
320 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz" 331 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
321 ;; 332 ;;
322 ,*) 333 *)
323 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz" 334 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz"
324 ;; 335 ;;
325 esac 336 esac
@@ -621,12 +632,12 @@ install_type() {
621 echo "$FORCE_INSTALL_TYPE"; return 0 632 echo "$FORCE_INSTALL_TYPE"; return 0
622 fi 633 fi
623 case "$INSTALL_RELEASE" in 634 case "$INSTALL_RELEASE" in
624 ,*/*) 635 */*)
625 case $OS in 636 case $OS in
626 ubios) 637 ubios)
627 echo "ubios_snapshot"; return 0 638 echo "ubios_snapshot"; return 0
628 ;; 639 ;;
629 ,*) 640 *)
630 # Snapshot mode always use binary install 641 # Snapshot mode always use binary install
631 echo "bin"; return 0 642 echo "bin"; return 0
632 ;; 643 ;;
@@ -654,7 +665,7 @@ install_type() {
654 . /etc/os-release 665 . /etc/os-release
655 major=$(echo "$VERSION_ID" | cut -d. -f1) 666 major=$(echo "$VERSION_ID" | cut -d. -f1)
656 case $major in 667 case $major in
657 ,*[!0-9]*) 668 *[!0-9]*)
658 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then 669 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then
659 # No opkg support before 19.07.0-rc2 670 # No opkg support before 19.07.0-rc2
660 echo "bin" 671 echo "bin"
@@ -663,7 +674,7 @@ install_type() {
663 echo "openwrt" 674 echo "openwrt"
664 fi 675 fi
665 ;; 676 ;;
666 ,*) 677 *)
667 if [ "$major" -lt 19 ]; then 678 if [ "$major" -lt 19 ]; then
668 # No opkg support before 19.07.0-rc2 679 # No opkg support before 19.07.0-rc2
669 echo "bin" 680 echo "bin"
@@ -709,7 +720,7 @@ install_type() {
709 # TODO: pkg for xbps 720 # TODO: pkg for xbps
710 echo "bin" 721 echo "bin"
711 ;; 722 ;;
712 ,*) 723 *)
713 log_error "Unsupported installation for $(detect_os)" 724 log_error "Unsupported installation for $(detect_os)"
714 return 1 725 return 1
715 ;; 726 ;;
@@ -861,7 +872,7 @@ ask_bool() {
861 false) 872 false)
862 msg="$msg [y|N]: " 873 msg="$msg [y|N]: "
863 ;; 874 ;;
864 ,*) 875 *)
865 msg="$msg (y/n): " 876 msg="$msg (y/n): "
866 esac 877 esac
867 while true; do 878 while true; do
@@ -879,7 +890,7 @@ ask_bool() {
879 echo "false" 890 echo "false"
880 return 0 891 return 0
881 ;; 892 ;;
882 ,*) 893 *)
883 echo "Invalid input, use yes or no" 894 echo "Invalid input, use yes or no"
884 ;; 895 ;;
885 esac 896 esac
@@ -891,12 +902,12 @@ detect_endiannes() {
891 # Some firmwares do not contain hexdump, for those, try to detect endianness 902 # Some firmwares do not contain hexdump, for those, try to detect endianness
892 # differently. 903 # differently.
893 case $(cat /proc/cpuinfo) in 904 case $(cat /proc/cpuinfo) in
894 ,*BCM5300*) 905 *BCM5300*)
895 # RT-AC66U does not support Merlin version over 380.70 which 906 # RT-AC66U does not support Merlin version over 380.70 which
896 # lacks hexdump command. 907 # lacks hexdump command.
897 echo "le" 908 echo "le"
898 ;; 909 ;;
899 ,*) 910 *)
900 log_error "Cannot determine endianness" 911 log_error "Cannot determine endianness"
901 return 1 912 return 1
902 ;; 913 ;;
@@ -927,10 +938,10 @@ detect_goarch() {
927 arm) 938 arm)
928 # FreeBSD does not include arm version 939 # FreeBSD does not include arm version
929 case "$(sysctl -b hw.model 2>/dev/null)" in 940 case "$(sysctl -b hw.model 2>/dev/null)" in
930 ,*A9*) 941 *A9*)
931 echo "armv7" 942 echo "armv7"
932 ;; 943 ;;
933 ,*) 944 *)
934 # Unknown version, fallback to the lowest 945 # Unknown version, fallback to the lowest
935 echo "armv5" 946 echo "armv5"
936 ;; 947 ;;
@@ -954,7 +965,7 @@ detect_goarch() {
954 # "out of memory allocating heap arena metadata". 965 # "out of memory allocating heap arena metadata".
955 echo "armv7" 966 echo "armv7"
956 ;; 967 ;;
957 ,*) 968 *)
958 echo "arm64" 969 echo "arm64"
959 ;; 970 ;;
960 esac 971 esac
@@ -966,7 +977,7 @@ detect_goarch() {
966 # TODO: detect hardfloat 977 # TODO: detect hardfloat
967 echo "$(uname -m)$(detect_endiannes)_softfloat" 978 echo "$(uname -m)$(detect_endiannes)_softfloat"
968 ;; 979 ;;
969 ,*) 980 *)
970 log_error "Unsupported GOARCH: $(uname -m)" 981 log_error "Unsupported GOARCH: $(uname -m)"
971 return 1 982 return 1
972 ;; 983 ;;
@@ -993,7 +1004,7 @@ detect_goos() {
993 OpenBSD) 1004 OpenBSD)
994 echo "openbsd" 1005 echo "openbsd"
995 ;; 1006 ;;
996 ,*) 1007 *)
997 log_error "Unsupported GOOS: $(uname -s)" 1008 log_error "Unsupported GOOS: $(uname -s)"
998 return 1 1009 return 1
999 esac 1010 esac
@@ -1070,7 +1081,7 @@ detect_os() {
1070 OpenBSD) 1081 OpenBSD)
1071 echo "openbsd"; return 0 1082 echo "openbsd"; return 0
1072 ;; 1083 ;;
1073 ,*) 1084 *)
1074 esac 1085 esac
1075 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)" 1086 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)"
1076 return 1 1087 return 1
@@ -1089,7 +1100,7 @@ guess_host_type() {
1089 darwin|steamos) 1100 darwin|steamos)
1090 echo "workstation" 1101 echo "workstation"
1091 ;; 1102 ;;
1092 ,*) 1103 *)
1093 echo "unsure" 1104 echo "unsure"
1094 ;; 1105 ;;
1095 esac 1106 esac
@@ -1148,7 +1159,7 @@ bin_location() {
1148 steamos) 1159 steamos)
1149 echo "$HOME/.local/bin/nextdns" 1160 echo "$HOME/.local/bin/nextdns"
1150 ;; 1161 ;;
1151 ,*) 1162 *)
1152 log_error "Unknown bin location for $OS" 1163 log_error "Unknown bin location for $OS"
1153 ;; 1164 ;;
1154 esac 1165 esac
@@ -1156,12 +1167,12 @@ bin_location() {
1156 1167
1157is_version_current() { 1168is_version_current() {
1158 case "$INSTALL_RELEASE" in 1169 case "$INSTALL_RELEASE" in
1159 ,*/*) 1170 */*)
1160 # Snapshot 1171 # Snapshot
1161 hash=${INSTALL_RELEASE#*/} 1172 hash=${INSTALL_RELEASE#*/}
1162 test "0.0.0-$hash" = "$CURRENT_RELEASE" 1173 test "0.0.0-$hash" = "$CURRENT_RELEASE"
1163 ;; 1174 ;;
1164 ,*) 1175 *)
1165 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE" 1176 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE"
1166 ;; 1177 ;;
1167 esac 1178 esac
@@ -1186,7 +1197,7 @@ get_release() {
1186 curl) cmd="curl -A curl -s" ;; 1197 curl) cmd="curl -A curl -s" ;;
1187 wget) cmd="wget -qO- -U curl" ;; 1198 wget) cmd="wget -qO- -U curl" ;;
1188 openssl) cmd="openssl_get" ;; 1199 openssl) cmd="openssl_get" ;;
1189 ,*) 1200 *)
1190 log_error "Cannot retrieve latest version" 1201 log_error "Cannot retrieve latest version"
1191 return 1202 return
1192 ;; 1203 ;;
@@ -1238,4 +1249,4 @@ openssl_get() {
1238 1249
1239umask 0022 1250umask 0022
1240main 1251main
1241#+end_src 1252```
content/blog/2024-01-08-dont-say-hello.md added +29
@@ -0,0 +1,29 @@
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
9again... only to immediately run into the dilemma of people sending me
10cliffhanger messages again.
11
12# No Hello
13
14A year or two ago, I discovered [no hello](https://nohello.net/en/) and
15have thought about it often since then. I\'ve even sent it to a few
16people since then (who wouldn\'t take offense to it).
17
18I work in a fast-paced environment where efficiency is extremely
19valuable. Therefore, I have always held a deep displeasure for
20conversations where people start with \"Hello\" and then.
21
22I searched back through my work messages and found that I received \~50
23messages from \~10 people last year from people that contained \"hi\",
24\"hey\", or \"hello\" and did not contain any indication of the purpose
25of the conversation. I also noticed that a few of the users were
26responsible for the large majority of the cliffhangers.
27
28There\'s no real point to this post, just a desparate request for people
29to please stop doing this.
content/blog/2024-01-08-dont-say-hello.org deleted −26
@@ -1,26 +0,0 @@
1#+title: Don't Say Hello
2#+date: 2024-01-08
3#+description: A short post describing my displeasure with cliffhanger conversations.
4#+filetags: :personal:
5
6I recently came back from a winter break and have started working
7again... only to immediately run into the dilemma of people sending me
8cliffhanger messages again.
9
10* No Hello
11A year or two ago, I discovered [[https://nohello.net/en/][no hello]]
12and have thought about it often since then. I've even sent it to a few
13people since then (who wouldn't take offense to it).
14
15I work in a fast-paced environment where efficiency is extremely
16valuable. Therefore, I have always held a deep displeasure for
17conversations where people start with "Hello" and then.
18
19I searched back through my work messages and found that I received ~50
20messages from ~10 people last year from people that contained "hi",
21"hey", or "hello" and did not contain any indication of the purpose of
22the conversation. I also noticed that a few of the users were
23responsible for the large majority of the cliffhangers.
24
25There's no real point to this post, just a desparate request for people
26to please stop doing this.
content/blog/2024-01-09-macos-customization.md added +181
@@ -0,0 +1,181 @@
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
9describe some simple options to customize macOS beyond the normal
10built-in settings menu.
11
12While not all-encompassing, the options below should be a good start for
13anyone looking to dive down the rabbit hole.
14
15# Basics
16
17## Package Management
18
19To install a lot of software on macOS, you will need
20[Homebrew](https://brew.sh/). You can use their installation script to
21get started. Simply open the `Terminal` application and paste
22the following snippet:
23
24```sh
25/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
26```
27
28This will allow you to easily install and manage applications and other
29software easily through the `brew` command.
30
31## Terminal
32
33If you\'re serious about customizing your macOS system, I highly
34recommend installing a terminal emulator that you like and if you\'re
35not comfortable on the command line yet, start learning. A lot of
36customization options require you to edit hidden files, which is easiest
37in a terminal.
38
39There are options like iTerm2, Kitty, Alacritty, Hyper, Warp, or the
40built-in Terminal app.
41
42I use [iTerm2](https://iterm2.com/), which can be installed with
43Homebrew:
44
45```sh
46brew install iterm2
47```
48
49![iTerm2](https://img.cleberg.net/blog/20240109-macos-customization/iterm2.png)
50
51To install color schemes, such as the Dracula scheme shown in the
52screenshot above, you visit [iTerm
53Themes](https://iterm2colorschemes.com/) and follow their installation
54instructions to install any of the themes.
55
56# Desktop
57
58## Window Management
59
60[yabai](https://github.com/koekeishiya/yabai) is a tiling window manager
61for macOS. While other window managers exist, I found that most of them
62struggled to create logical layouts and to allow me to easily move
63windows around the screen.
64
65Some advanced settings for yabai are only available if partially disable
66System Integrity Protection (SIP). However, I chose not to do this and
67it hasn\'t affected my basic usage of yabai at all.
68
69Refer to the [yabai
70wiki](https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release))
71for installation instructions. You will need to ensure that yabai is
72allowed to access the accessibility and screen recording APIs.
73
74You can see a basic three-pane layout atuomatically configured by yabai
75for me as I opened the windows below.
76
77![yabai window
78manager](https://img.cleberg.net/blog/20240109-macos-customization/yabai.png)
79
80## Keyboard Shortcuts
81
82[skhd](https://github.com/koekeishiya/skhd) is a simple hotkey daemon
83that allows you to define hotkeys in a file for usage on your system.
84
85Installation is simple:
86
87```sh
88brew install koekeishiya/formulae/skhd
89skhd --start-service
90```
91
92After installation, be sure to allow `skhd` access to the
93accessibility API in the macOS privacy settings.
94
95You can configure your hotkeys in the `~/.config/skhd/skhdrc`
96file:
97
98```sh
99nano ~/.config/skhd/skhdrc
100```
101
102For example, I have hotkeys to open my browser and terminal:
103
104``` conf
105# Terminal
106cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
107
108# Browser
109cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
110```
111
112## Widgets
113
114[uebersicht](https://github.com/felixhageloh/uebersicht/) is a handy
115desktop-based widget tool with a plethora of community-made widgets
116available in the [widgets
117gallery](https://tracesof.net/uebersicht-widgets/). You can also write
118your own widgets with this tool.
119
120To install, simply download the latest release from the [uebersicht
121website](https://tracesof.net/uebersicht/) and copy it to the
122Applications folder.
123
124See below for an example of the
125[Mond](https://tracesof.net/uebersicht-widgets/#Mond) widget in action.
126
127![uebersicht desktop
128widgets](https://img.cleberg.net/blog/20240109-macos-customization/uebersicht.png)
129
130## Status Bar
131
132[SketchyBar](https://github.com/FelixKratz/SketchyBar) is a customizable
133replacement for the macOS status or menu bar.
134
135You can browse a discussion where various users shared their
136[configurations](https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top)
137for inspiration or to copy their dotfiles.
138
139See below for a quick (& slightly broken) copy of
140[zer0yu\'s](https://github.com/zer0yu/dotfiles) SketchyBar
141configuration.
142
143![SketchyBar](https://img.cleberg.net/blog/20240109-macos-customization/sketchybar.png)
144
145## Dock
146
147The easiest way to customize the dock is to install
148[uBar](https://ubarapp.com/), which uses a Windows-like menu bar as the
149default style.
150
151However, the built-in macOS dock cannot be disabled and can only be set
152to \"always hidden\". This can be annoying as it will pop out any time
153your mouse cursor passes closely to the dock edge of the screen. Because
154of this, I simply use the built-in dock instead of customizing it with
155third-party software.
156
157Regardless, see below for the default installation style of uBar.
158
159![uBar](https://img.cleberg.net/blog/20240109-macos-customization/ubar.png)
160
161## Application Icons
162
163You can also customize the icon of any application in macOS, which will
164show up in Finder, the Dock, Launchpad, search results, etc. I recommend
165using [macOSicons](https://macosicons.com/) to download icons you want,
166and then apply them by following this process.
167
1681. Open the Finder application.
1692. Navigate to the `Applications` folder.
1703. Right-click an application of your choice, and select
171 `Get Info`.
1724. Drag the image you downloaded on top of the application\'s icon at
173 the top of information window (you will see a green \"plus\" symbol
174 when you\'re hovering over it).
1755. Release the new icon on top of the old icon and it will update!
176
177You can see an example of me dragging a new `signal.icns`
178file onto my Signal.app information window to update it below:
179
180![replace macOS
181icons](https://img.cleberg.net/blog/20240109-macos-customization/replace_icon.png)
content/blog/2024-01-09-macos-customization.org deleted −170
@@ -1,170 +0,0 @@
1#+title: Customizing macOS
2#+date: 2024-01-09
3#+description: Learn how to customize macOS beyond the standard, built-in options provided by Apple.
4#+filetags: :apple:
5
6I have been using macOS more than Linux lately, so I wrote this post to
7describe some simple options to customize macOS beyond the normal
8built-in settings menu.
9
10While not all-encompassing, the options below should be a good start for
11anyone looking to dive down the rabbit hole.
12
13* Basics
14** Package Management
15To install a lot of software on macOS, you will need
16[[https://brew.sh/][Homebrew]]. You can use their installation script to
17get started. Simply open the =Terminal= application and paste the
18following snippet:
19
20#+begin_src sh
21/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
22#+end_src
23
24This will allow you to easily install and manage applications and other
25software easily through the =brew= command.
26
27** Terminal
28If you're serious about customizing your macOS system, I highly
29recommend installing a terminal emulator that you like and if you're not
30comfortable on the command line yet, start learning. A lot of
31customization options require you to edit hidden files, which is easiest
32in a terminal.
33
34There are options like iTerm2, Kitty, Alacritty, Hyper, Warp, or the
35built-in Terminal app.
36
37I use [[https://iterm2.com/][iTerm2]], which can be installed with
38Homebrew:
39
40#+begin_src sh
41brew install iterm2
42#+end_src
43
44#+caption: iTerm2
45[[https://img.cleberg.net/blog/20240109-macos-customization/iterm2.png]]
46
47To install color schemes, such as the Dracula scheme shown in the
48screenshot above, you visit [[https://iterm2colorschemes.com/][iTerm
49Themes]] and follow their installation instructions to install any of
50the themes.
51
52* Desktop
53** Window Management
54[[https://github.com/koekeishiya/yabai][yabai]] is a tiling window
55manager for macOS. While other window managers exist, I found that most
56of them struggled to create logical layouts and to allow me to easily
57move windows around the screen.
58
59Some advanced settings for yabai are only available if partially disable
60System Integrity Protection (SIP). However, I chose not to do this and
61it hasn't affected my basic usage of yabai at all.
62
63Refer to the
64[[https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)][yabai
65wiki]] for installation instructions. You will need to ensure that yabai
66is allowed to access the accessibility and screen recording APIs.
67
68You can see a basic three-pane layout atuomatically configured by yabai
69for me as I opened the windows below.
70
71#+caption: yabai window manager
72[[https://img.cleberg.net/blog/20240109-macos-customization/yabai.png]]
73
74** Keyboard Shortcuts
75[[https://github.com/koekeishiya/skhd][skhd]] is a simple hotkey daemon
76that allows you to define hotkeys in a file for usage on your system.
77
78Installation is simple:
79
80#+begin_src sh
81brew install koekeishiya/formulae/skhd
82skhd --start-service
83#+end_src
84
85After installation, be sure to allow =skhd= access to the accessibility
86API in the macOS privacy settings.
87
88You can configure your hotkeys in the =~/.config/skhd/skhdrc= file:
89
90#+begin_src sh
91nano ~/.config/skhd/skhdrc
92#+end_src
93
94For example, I have hotkeys to open my browser and terminal:
95
96#+begin_src conf
97# Terminal
98cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
99
100# Browser
101cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
102#+end_src
103
104** Widgets
105[[https://github.com/felixhageloh/uebersicht/][uebersicht]] is a handy
106desktop-based widget tool with a plethora of community-made widgets
107available in the [[https://tracesof.net/uebersicht-widgets/][widgets
108gallery]]. You can also write your own widgets with this tool.
109
110To install, simply download the latest release from the
111[[https://tracesof.net/uebersicht/][uebersicht website]] and copy it to
112the Applications folder.
113
114See below for an example of the
115[[https://tracesof.net/uebersicht-widgets/#Mond][Mond]] widget in
116action.
117
118#+caption: uebersicht desktop widgets
119[[https://img.cleberg.net/blog/20240109-macos-customization/uebersicht.png]]
120
121** Status Bar
122[[https://github.com/FelixKratz/SketchyBar][SketchyBar]] is a
123customizable replacement for the macOS status or menu bar.
124
125You can browse a discussion where various users shared their
126[[https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top][configurations]]
127for inspiration or to copy their dotfiles.
128
129See below for a quick (& slightly broken) copy of
130[[https://github.com/zer0yu/dotfiles][zer0yu's]] SketchyBar
131configuration.
132
133#+caption: SketchyBar
134[[https://img.cleberg.net/blog/20240109-macos-customization/sketchybar.png]]
135
136** Dock
137The easiest way to customize the dock is to install
138[[https://ubarapp.com/][uBar]], which uses a Windows-like menu bar as
139the default style.
140
141However, the built-in macOS dock cannot be disabled and can only be set
142to "always hidden". This can be annoying as it will pop out any time
143your mouse cursor passes closely to the dock edge of the screen. Because
144of this, I simply use the built-in dock instead of customizing it with
145third-party software.
146
147Regardless, see below for the default installation style of uBar.
148
149#+caption: uBar
150[[https://img.cleberg.net/blog/20240109-macos-customization/ubar.png]]
151
152** Application Icons
153You can also customize the icon of any application in macOS, which will
154show up in Finder, the Dock, Launchpad, search results, etc. I recommend
155using [[https://macosicons.com/][macOSicons]] to download icons you
156want, and then apply them by following this process.
157
1581. Open the Finder application.
1592. Navigate to the =Applications= folder.
1603. Right-click an application of your choice, and select =Get Info=.
1614. Drag the image you downloaded on top of the application's icon at the
162 top of information window (you will see a green "plus" symbol when
163 you're hovering over it).
1645. Release the new icon on top of the old icon and it will update!
165
166You can see an example of me dragging a new =signal.icns= file onto my
167Signal.app information window to update it below:
168
169#+caption: replace macOS icons
170[[https://img.cleberg.net/blog/20240109-macos-customization/replace_icon.png]]
content/blog/2024-01-13-local-llm.md added +111
@@ -0,0 +1,111 @@
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
11in the popular chatbot form, as part of my job and have decided to see
12if there\'s a consistent and reliable way to interact with these models
13on Apple devices without sacrificing privacy or requiring in-depth
14technical setup.
15
16My requirements for this test:
17
18- Open source platform
19- On-device model files
20- Minimal required configuration
21- Preferably pre-built, but a simple build process is acceptable
22
23I tested a handful of apps and have summarized my favorite (so far) for
24macOS and iOS below.
25
26> TL;DR - Here are the two that met my requirements and I have found the
27> easiest to install and use so far:
28
29- macOS: [Ollama](https://ollama.ai/)
30- iOS : [LLM Farm](https://llmfarm.site/)
31
32# macOS
33
34[Ollama](https://ollama.ai/) 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 [Ollama download
38page](https://ollama.ai/download/mac) and install it by unzipping the
39`Ollama.app` file and moving it to the
40`Applications` folder.
41
42If you prefer the command line, you can run these commands after the
43download finished:
44
45```sh
46cd ~/Downloads && \
47unzip Ollama-darwin.zip && \
48mv ~/Downloads/Ollama.app /Applications/
49```
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
53chat session in the terminal. You can startfully using the application
54at this point.
55
56![Ollama](https://img.cleberg.net/blog/20240113-local-llm/ollama.png)
57
58If you don\'t want to use the default `llama2` model, you can
59download and run additional models found on the
60[Models](https://ollama.ai/library) page.
61
62To see the information for the currently-used model, you can run the
63`/show info` command in the chat.
64
65![Model
66Info](https://img.cleberg.net/blog/20240113-local-llm/ollama_info.png)
67
68## Community Integrations
69
70I highly recommend browsing the [Community
71Integrations](https://github.com/jmorganca/ollama#community-integrations)
72section of the project to see how you would prefer to extend Ollama
73beyond a simple command-line interface. There are options for APIs,
74browser UIs, advanced terminal configurations, and more.
75
76![Ollama
77SwiftUI](https://img.cleberg.net/blog/20240113-local-llm/ollama-swiftui.png)
78
79# iOS
80
81While there are a handful of decent macOS options, it was quite
82difficult to find an iOS app that offered an open source platform
83without an extensive configuration and building process. I found LLM
84Farm to be decent enough in quality to sit at the top of my list -
85however, it\'s definitely not user friendly enough for me to consider
86using it on a daily basis.
87
88[LLM Farm](https://llmfarm.site/) is available on TestFlight, so
89there\'s no manual build process required. However, you can view the
90[LLMFarm repository](https://github.com/guinmoon/LLMFarm) if you wish.
91
92The caveat is that you will have to manually download the model files
93from the links in the
94[models.md](https://github.com/guinmoon/LLMFarm/blob/main/models.md)
95file to your iPhone to use the app - there\'s currently no option in the
96app to reach out and grab the latest version of any supported model.
97
98Once you have a file downloaded, you simply create a new chat and select
99the downloaded model file and ensure the inference matches the
100requirement in the `models.md` file.
101
102See below for a test of the ORCA Mini v3 model:
103
104 Chat List Chat
105 ------------------------------------------------------------------------- -------------------------------------------------------------------
106 ![](https://img.cleberg.net/blog/20240113-local-llm/llm_farm_chats.png) ![](https://img.cleberg.net/blog/20240113-local-llm/llm_farm.png)
107
108[Enchanted](https://github.com/AugustDev/enchanted) is also an iOS for
109private AI models, but it requires a public-facing Ollama API, which did
110not meet my \"on device requirement.\" Nonetheless, it\'s an interesting
111looking app and I will likely set it up to test soon.
content/blog/2024-01-13-local-llm.org deleted −108
@@ -1,108 +0,0 @@
1#+title: Running Local LLMs on macOS and iOS
2#+date: 2024-01-13
3#+description: Finding some useful applications for running local LLMs on macOS and iOS.
4#+filetags: :apple:
5
6* Requirements
7I've recently started playing with large language models (LLMs), mostly
8in the popular chatbot form, as part of my job and have decided to see
9if there's a consistent and reliable way to interact with these models
10on Apple devices without sacrificing privacy or requiring in-depth
11technical setup.
12
13My requirements for this test:
14
15- Open source platform
16- On-device model files
17- Minimal required configuration
18- Preferably pre-built, but a simple build process is acceptable
19
20I tested a handful of apps and have summarized my favorite (so far) for
21macOS and iOS below.
22
23#+begin_quote
24TL;DR - Here are the two that met my requirements and I have found the
25easiest to install and use so far:
26
27#+end_quote
28
29- macOS: [[https://ollama.ai/][Ollama]]
30- iOS : [[https://llmfarm.site/][LLM Farm]]
31
32* macOS
33[[https://ollama.ai/][Ollama]] is a simple Go application for macOS and
34Linux that can run various LLMs locally.
35
36For macOS, you can download the pplication on the
37[[https://ollama.ai/download/mac][Ollama download page]] and install it
38by unzipping the =Ollama.app= file and moving it to the =Applications=
39folder.
40
41If you prefer the command line, you can run these commands after the
42download finished:
43
44#+begin_src sh
45cd ~/Downloads && \
46unzip Ollama-darwin.zip && \
47mv ~/Downloads/Ollama.app /Applications/
48#+end_src
49
50After running the app, the app will ask you to open a terminal and run
51the default =llama2= model, which will open an interactive chat session
52in the terminal. You can startfully using the application at this point.
53
54#+caption: Ollama
55[[https://img.cleberg.net/blog/20240113-local-llm/ollama.png]]
56
57If you don't want to use the default =llama2= model, you can download
58and run additional models found on the
59[[https://ollama.ai/library][Models]] page.
60
61To see the information for the currently-used model, you can run the
62=/show info= command in the chat.
63
64#+caption: Model Info
65[[https://img.cleberg.net/blog/20240113-local-llm/ollama_info.png]]
66
67** Community Integrations
68I highly recommend browsing the
69[[https://github.com/jmorganca/ollama#community-integrations][Community
70Integrations]] section of the project to see how you would prefer to
71extend Ollama beyond a simple command-line interface. There are options
72for APIs, browser UIs, advanced terminal configurations, and more.
73
74#+caption: Ollama SwiftUI
75[[https://img.cleberg.net/blog/20240113-local-llm/ollama-swiftui.png]]
76
77* iOS
78While there are a handful of decent macOS options, it was quite
79difficult to find an iOS app that offered an open source platform
80without an extensive configuration and building process. I found LLM
81Farm to be decent enough in quality to sit at the top of my list -
82however, it's definitely not user friendly enough for me to consider
83using it on a daily basis.
84
85[[https://llmfarm.site/][LLM Farm]] is available on TestFlight, so
86there's no manual build process required. However, you can view the
87[[https://github.com/guinmoon/LLMFarm][LLMFarm repository]] if you wish.
88
89The caveat is that you will have to manually download the model files
90from the links in the
91[[https://github.com/guinmoon/LLMFarm/blob/main/models.md][models.md]]
92file to your iPhone to use the app - there's currently no option in the
93app to reach out and grab the latest version of any supported model.
94
95Once you have a file downloaded, you simply create a new chat and select
96the downloaded model file and ensure the inference matches the
97requirement in the =models.md= file.
98
99See below for a test of the ORCA Mini v3 model:
100
101| Chat List | Chat |
102|------------------------------------------------------------------------+------------------------------------------------------------------|
103| [[https://img.cleberg.net/blog/20240113-local-llm/llm_farm_chats.png]] | [[https://img.cleberg.net/blog/20240113-local-llm/llm_farm.png]] |
104
105[[https://github.com/AugustDev/enchanted][Enchanted]] is also an iOS for
106private AI models, but it requires a public-facing Ollama API, which did
107not meet my "on device requirement." Nonetheless, it's an interesting
108looking app and I will likely set it up to test soon.
content/blog/2024-01-26-audit-dashboard.md added +188
@@ -0,0 +1,188 @@
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
9old-school audit trackers into interactive tools that provide useful
10insights and potential action plans.
11
12With these tools, we are going to build the following dashboard:
13
14![](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png)
15![](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png)
16
17# Requirements
18
19This project assumes the following:
20
21- You have access to Alteryx Designer and Power BI Desktop.
22 - If you only have Power BI Desktop, you may need to perform some
23 analysis in Power BI instead of Alteryx.
24- Your data is in a format that can be imported into Alteryx and/or
25 Power BI.
26- You have a basic understanding of data types and visualization.
27
28# Alteryx: Data Preparation & Analysis
29
30## Import Data
31
32With Alteryx, importing data is easy with the use of the
33`Input Data` tool. Simply drag this tool onto the canvas from
34the `In/Out` tab in the Ribbon to create it as a node.
35
36You can choose the File Format manually or simply connect to your
37file/database and let Alteryx determine the format for you. For this
38example, we will be importing an Excel file and changing the
39`Start Data Import on Line` variable to `2`.
40
41![Alteryx Excel
42Import](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_import.png)
43
44## Transform Data
45
46Next, let\'s replace null data and remove whitespace to clean up our
47data. We can do this with the `Data Cleansing` tool in the
48`Preparation` tab in the Ribbon.
49
50Ensure that the following options are enabled:
51
52- Replace Nulls
53 - Replace with Blanks (String Fields)
54 - Replace with 0 (Numeric Fields)
55- Remove Unwanted Characters
56 - Leading and Trailing Whitespace
57
58![Data
59Cleansing](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_cleansing.png)
60
61For our next step, we will transform the date fields from strings to
62datetime format. Add a `Datetime` tool for each field you
63want to transform - in the example below, I am using the tool twice for
64the \"Started On\" and \"Submitted On\" fields.
65
66![Data
67Transformation](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_transformation.png)
68
69Now that the dates are in the correct format, let\'s perform a
70calculation based on those fields. Start by adding a `Filter`
71tool, naming a new Output Column, and pasting the formula below into it
72(the two fields used in this formula must match the output of the
73`Datetime` tools above):
74
75``` txt
76DateTimeDiff([SubmittedOn_Out],[StartedOn_Out], "days")
77```
78
79![Data
80Analysis](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_analysis.png)
81
82## Export Data
83
84Finalize the process by exporting the transformed data set to a new
85file, for use in the following visualization step.
86
87![Data
88Export](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_export.png)
89
90# Power BI: Data Visualization
91
92## Import Data
93
94To start, open the Power BI Desktop application. Upon first use, Power
95BI will ask if you want to open an existing dashboard or import new
96data.
97
98As we are creating our first dashboard, let\'s import our data. In my
99example below, I\'m importing data from the \"Tracker\" sheet of the
100Excel file I\'m using for this project.
101
102During this process, I also imported the export from the Alteryx
103workflow above. Therefore, we have two different files available for use
104in our dashboard.
105
106![Excel
107Tracker](https://img.cleberg.net/blog/20240126-audit-dashboard/excel_tracker.png)
108
109![Power BI Excel
110Import](https://img.cleberg.net/blog/20240126-audit-dashboard/powerbi_import.png)
111
112## Add Visuals
113
114To create the dashboard below, you will need to follow the list
115instructions below and format as needed:
116
117![](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png)
118![](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png)
119
120Instructions to create the visuals above:
121
122- `Text Box`: Explain the name and purpose of the
123 dashboard. You can also add images and logos at the top of the
124 dashboard.
125- `Donut Chart`: Overall status of the project.
126 - `Legend`: Status
127 - `Values`: Count of Status
128- `Stacked Column Chart`: Task count by assignee.
129 - `X-axis`: Preparer
130 - `Y-axis`: Count of Control ID
131 - `Legend`: Status
132- `Treemap`: Top N client submitters by average days to
133 submit.
134 - `Details`: Preparer
135 - `Values`: Sum of Avg~DaysToSubmit~
136- `Line Chart`: Projected vs. actual hours over time.
137- `Clustered Bar Chart`: Projected vs. actual hours per
138 person.
139- `Slicer & Table` - Upcoming due dates.
140 - `Slicer`:
141 - `Values`: Date Due
142 - `Table`:
143 - `Columns`: Count of Control ID, Date Due,
144 Preparer, Status
145
146## Format the Dashboard
147
148You can choose a theme in the View tab of the Ribbon. You can even
149browse for custom JSON files that define themes, such as ones found
150online or custom ones created by your organization.
151
152For each visual, you can click the `Format` button in the
153`Visualizations` side pane and explore the options. You can
154custom options such as:
155
156- Visual
157 - Legend
158 - Colors
159 - Data labels
160 - Category labels
161- General
162 - Properties
163 - Title
164 - Effects
165 - Header icons
166 - Tooltips
167 - Alt text
168
169You can always look online for inspiration when trying to decide how
170best to organize and style your dashboard.
171
172# Sharing the Results
173
174Generally, you have a few different options for sharing your dashboards
175with others:
176
1771. Export the dashboard as a PDF in the file menu of Power BI. This
178 will export all tabs and visuals as they are set when the export
179 button is pressed. You will lose all interactivity with this option.
1802. Send the full Power BI file to those you wish to share the
181 dashboard. This will retain all settings and interactivity. However,
182 you will also need to send the source files if they need to refresh
183 the dashboard and you will need to re-send the files if you make
184 updates.
1853. Store the dashboard in a synced location, such as a shared drive or
186 Microsoft Teams. Depending on how a user configures their local
187 Windows paths, the data source paths may not be compatible for all
188 users with such a setup.
content/blog/2024-01-26-audit-dashboard.org deleted −171
@@ -1,171 +0,0 @@
1#+title: Building an Audit Status Dashboard
2#+date: 2024-01-26
3#+description: Learn how to utilize Alteryx Designer and Power BI Desktop to build a simple status tracking dashboard for an audit or other engagement.
4#+filetags: :audit:
5
6Alteryx and Power BI are powerful tools that can help turn your
7old-school audit trackers into interactive tools that provide useful
8insights and potential action plans.
9
10With these tools, we are going to build the following dashboard:
11
12[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png]]
13[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png]]
14
15* Requirements
16This project assumes the following:
17
18- You have access to Alteryx Designer and Power BI Desktop.
19 - If you only have Power BI Desktop, you may need to perform some
20 analysis in Power BI instead of Alteryx.
21- Your data is in a format that can be imported into Alteryx and/or
22 Power BI.
23- You have a basic understanding of data types and visualization.
24
25* Alteryx: Data Preparation & Analysis
26** Import Data
27With Alteryx, importing data is easy with the use of the =Input Data=
28tool. Simply drag this tool onto the canvas from the =In/Out= tab in the
29Ribbon to create it as a node.
30
31You can choose the File Format manually or simply connect to your
32file/database and let Alteryx determine the format for you. For this
33example, we will be importing an Excel file and changing the
34=Start Data Import on Line= variable to =2=.
35
36#+caption: Alteryx Excel Import
37[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_import.png]]
38
39** Transform Data
40Next, let's replace null data and remove whitespace to clean up our
41data. We can do this with the =Data Cleansing= tool in the =Preparation=
42tab in the Ribbon.
43
44Ensure that the following options are enabled:
45
46- Replace Nulls
47 - Replace with Blanks (String Fields)
48 - Replace with 0 (Numeric Fields)
49- Remove Unwanted Characters
50 - Leading and Trailing Whitespace
51
52#+caption: Data Cleansing
53[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_cleansing.png]]
54
55For our next step, we will transform the date fields from strings to
56datetime format. Add a =Datetime= tool for each field you want to
57transform - in the example below, I am using the tool twice for the
58"Started On" and "Submitted On" fields.
59
60#+caption: Data Transformation
61[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_transformation.png]]
62
63Now that the dates are in the correct format, let's perform a
64calculation based on those fields. Start by adding a =Filter= tool,
65naming a new Output Column, and pasting the formula below into it (the
66two fields used in this formula must match the output of the =Datetime=
67tools above):
68
69#+begin_src txt
70DateTimeDiff([SubmittedOn_Out],[StartedOn_Out], "days")
71#+end_src
72
73#+caption: Data Analysis
74[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_analysis.png]]
75
76** Export Data
77Finalize the process by exporting the transformed data set to a new
78file, for use in the following visualization step.
79
80#+caption: Data Export
81[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_export.png]]
82
83* Power BI: Data Visualization
84** Import Data
85To start, open the Power BI Desktop application. Upon first use, Power
86BI will ask if you want to open an existing dashboard or import new
87data.
88
89As we are creating our first dashboard, let's import our data. In my
90example below, I'm importing data from the "Tracker" sheet of the Excel
91file I'm using for this project.
92
93During this process, I also imported the export from the Alteryx
94workflow above. Therefore, we have two different files available for use
95in our dashboard.
96
97#+caption: Excel Tracker
98[[https://img.cleberg.net/blog/20240126-audit-dashboard/excel_tracker.png]]
99
100#+caption: Power BI Excel Import
101[[https://img.cleberg.net/blog/20240126-audit-dashboard/powerbi_import.png]]
102
103** Add Visuals
104To create the dashboard below, you will need to follow the list
105instructions below and format as needed:
106
107[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png]]
108[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png]]
109
110Instructions to create the visuals above:
111
112- =Text Box=: Explain the name and purpose of the dashboard. You can
113 also add images and logos at the top of the dashboard.
114- =Donut Chart=: Overall status of the project.
115 - =Legend=: Status
116 - =Values=: Count of Status
117- =Stacked Column Chart=: Task count by assignee.
118 - =X-axis=: Preparer
119 - =Y-axis=: Count of Control ID
120 - =Legend=: Status
121- =Treemap=: Top N client submitters by average days to submit.
122 - =Details=: Preparer
123 - =Values=: Sum of Avg_DaysToSubmit
124- =Line Chart=: Projected vs. actual hours over time.
125- =Clustered Bar Chart=: Projected vs. actual hours per person.
126- =Slicer & Table= - Upcoming due dates.
127 - =Slicer=:
128 - =Values=: Date Due
129 - =Table=:
130 - =Columns=: Count of Control ID, Date Due, Preparer, Status
131
132** Format the Dashboard
133You can choose a theme in the View tab of the Ribbon. You can even
134browse for custom JSON files that define themes, such as ones found
135online or custom ones created by your organization.
136
137For each visual, you can click the =Format= button in the
138=Visualizations= side pane and explore the options. You can custom
139options such as:
140
141- Visual
142 - Legend
143 - Colors
144 - Data labels
145 - Category labels
146- General
147 - Properties
148 - Title
149 - Effects
150 - Header icons
151 - Tooltips
152 - Alt text
153
154You can always look online for inspiration when trying to decide how
155best to organize and style your dashboard.
156
157* Sharing the Results
158Generally, you have a few different options for sharing your dashboards
159with others:
160
1611. Export the dashboard as a PDF in the file menu of Power BI. This will
162 export all tabs and visuals as they are set when the export button is
163 pressed. You will lose all interactivity with this option.
1642. Send the full Power BI file to those you wish to share the dashboard.
165 This will retain all settings and interactivity. However, you will
166 also need to send the source files if they need to refresh the
167 dashboard and you will need to re-send the files if you make updates.
1683. Store the dashboard in a synced location, such as a shared drive or
169 Microsoft Teams. Depending on how a user configures their local
170 Windows paths, the data source paths may not be compatible for all
171 users with such a setup.
content/blog/2024-01-27-tableau-dashboard.md added +166
@@ -0,0 +1,166 @@
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
9free to create simple dashboards.
10
11I will be creating simple visuals from an Omaha crime data set and
12combining them to create the dashboard below. You can view this
13dashboard interactively online 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
31as adding a custom `datetime` column. You could do this step
32in any software you prefer, but I prefer Python as its free, easy to
33use, and has a plethora of support 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
81incidents between 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
86application that allows you to create visualizations by uploading data
87sources. Note that there\'s no way to keep the data and visualizations
88private, so don\'t upload anything private.
89
90After creating an account, you can click the `Create` \>
91`Web Authoring` link to create your first visualization.
92
93![Create
94Button](https://img.cleberg.net/blog/20240127-tableau-dashboard/create_button.png)
95
96## Upload the Data
97
98Once you\'ve opened your first project, Tableau will ask you to connect
99to your data. For this project, click the
100`Upload from computer` button and select the CSV file
101previously combined in the step above.
102
103![Connect
104Data](https://img.cleberg.net/blog/20240127-tableau-dashboard/connect_data.png)
105
106Once connected, you can refresh the preview of the data with the
107`Refresh Data Source` button in the toolbar.
108
109![Data
110Preview](https://img.cleberg.net/blog/20240127-tableau-dashboard/data_preview.png)
111
112If you need to edit any of the data types, column names, etc., you can
113do so now. Once complete, generate an extract so that you can start
114creating visualizations.
115
116## Create Visualizations
117
118To start, create a worksheet in the toolbar at the bottom of the screen.
119
120Within this screen, select a column from the `Data` side bar
121on the left and drag it into the `Columns` or
122`Rows` area of the canvas.
123
124See below for the map visualization. You can recreate this by adding the
125following fields:
126
127- `Columns`: Lon
128- `Rows`: Lat
129- `Marks`:
130 - Description
131 - Datetime
132- `Filters`: Datetime
133
134![Worksheet
135Editing](https://img.cleberg.net/blog/20240127-tableau-dashboard/worksheet_edit.png)
136
137You can repeat this process for each visualization you want to create.
138Explore your options by dragging data fields to different areas and by
139opening the field options to explore what operations can be performed on
140different data types (e.g., average, count, etc.).
141
142## Create Dashboard
143
144To create a dashboard, click the button on the toolbar at the bottom of
145the screen. Within the dashboard, drag each sheet from the left side bar
146onto the dashboard canvas.
147
148![Dashboard
149Editing](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard_edit.png)
150
151## Formatting
152
153You can explore a ton of different formatting options throughout the
154worksheets and dashboard. Specifically for maps, you can alter the map
155layers, background, and visible features through the `Map`
156menu in the top file menu of the editing screen.
157
158In the finished dashboard below, I opted for a dark mode with a map that
159showed county lines and city names.
160
161There\'s a ton of other options available to be used in a dashboard like
162this, but this project shows a quick preview of what you can do in
163Tableau Public.
164
165![Tableau
166Dashboard](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png)
content/blog/2024-01-27-tableau-dashboard.org deleted −156
@@ -1,156 +0,0 @@
1#+title: Data Visualization: Mapping Omaha Crime Data with Tableau
2#+date: 2024-01-27
3#+description: Learn how to utilize Tableau Public to build a dashboard to map crimes across Omaha.
4#+filetags: :data:
5
6In this project, I am going to show you how to use Tableau Public for
7free to create simple dashboards.
8
9I will be creating simple visuals from an Omaha crime data set and
10combining them to create the dashboard below. You can view this
11dashboard interactively online here:
12[[https://public.tableau.com/app/profile/c.c7042/viz/OmahaCrimeData2015-2023/OmahaCrimeData2015-2023#1][Omaha
13Crime Data (2015 - 2023)]].
14
15#+caption: Tableau Dashboard
16[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png]]
17
18* Gather the Data
19You can download incident data from the Omaha Police Department on their
20[[https://police.cityofomaha.org/crime-information/incident-data-download][Incident
21Data Download]] page. They currently have files for the years 2015
22through 2023.
23
24Each file will be downloaded as a CSV file, approximately 3 MB - 8 MB.
25
26* Clean and Transform the Data
27I have used Python to combine the files into a single CSV file, as well
28as adding a custom =datetime= column. You could do this step in any
29software you prefer, but I prefer Python as its free, easy to use, and
30has a plethora of support resources online.
31
32Start by opening a terminal, navigating to your Downloads directory, and
33creating a python script.
34
35#+begin_src sh
36cd ~/Downloads
37nano data_processing.py
38#+end_src
39
40Within the Python script, paste the following:
41
42#+begin_src python
43# Import modules
44import pandas as pd
45import glob
46import os
47
48# Import the data
49path = r"~/Downloads/*.csv"
50files = glob.glob(path)
51
52list = []
53
54for file in files:
55 df_tmp = pd.read_csv(file)
56 li.append(df_tmp)
57
58df = pd.concat(list, axis=0, ignore_index=True)
59
60# Create a combined datetime column
61df["datetime"] = pd.to_datetime(
62 df["date"] + " " + df["time"],
63 format="%m/%d/%Y %H:%M:%S"
64)
65df.head()
66
67# Export the combined data
68df.to_csv(r"~/Downloads/combined_incidents.csv")
69#+end_src
70
71Once pasted, save and close the file. You can execute the file like so:
72
73#+begin_src sh
74python3 data_processing.py
75#+end_src
76
77After this, you should have a combined data file that contains all
78incidents between 2015 and 2023. Mine was approximately 55 MB.
79
80* Tableau Public
81[[https://public.tableau.com/][Tableau Public]] is a free-to-use web
82application that allows you to create visualizations by uploading data
83sources. Note that there's no way to keep the data and visualizations
84private, so don't upload anything private.
85
86After creating an account, you can click the =Create= > =Web Authoring=
87link to create your first visualization.
88
89#+caption: Create Button
90[[https://img.cleberg.net/blog/20240127-tableau-dashboard/create_button.png]]
91
92** Upload the Data
93Once you've opened your first project, Tableau will ask you to connect
94to your data. For this project, click the =Upload from computer= button
95and select the CSV file previously combined in the step above.
96
97#+caption: Connect Data
98[[https://img.cleberg.net/blog/20240127-tableau-dashboard/connect_data.png]]
99
100Once connected, you can refresh the preview of the data with the
101=Refresh Data Source= button in the toolbar.
102
103#+caption: Data Preview
104[[https://img.cleberg.net/blog/20240127-tableau-dashboard/data_preview.png]]
105
106If you need to edit any of the data types, column names, etc., you can
107do so now. Once complete, generate an extract so that you can start
108creating visualizations.
109
110** Create Visualizations
111To start, create a worksheet in the toolbar at the bottom of the screen.
112
113Within this screen, select a column from the =Data= side bar on the left
114and drag it into the =Columns= or =Rows= area of the canvas.
115
116See below for the map visualization. You can recreate this by adding the
117following fields:
118
119- =Columns=: Lon
120- =Rows=: Lat
121- =Marks=:
122 - Description
123 - Datetime
124- =Filters=: Datetime
125
126#+caption: Worksheet Editing
127[[https://img.cleberg.net/blog/20240127-tableau-dashboard/worksheet_edit.png]]
128
129You can repeat this process for each visualization you want to create.
130Explore your options by dragging data fields to different areas and by
131opening the field options to explore what operations can be performed on
132different data types (e.g., average, count, etc.).
133
134** Create Dashboard
135To create a dashboard, click the button on the toolbar at the bottom of
136the screen. Within the dashboard, drag each sheet from the left side bar
137onto the dashboard canvas.
138
139#+caption: Dashboard Editing
140[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard_edit.png]]
141
142** Formatting
143You can explore a ton of different formatting options throughout the
144worksheets and dashboard. Specifically for maps, you can alter the map
145layers, background, and visible features through the =Map= menu in the
146top file menu of the editing screen.
147
148In the finished dashboard below, I opted for a dark mode with a map that
149showed county lines and city names.
150
151There's a ton of other options available to be used in a dashboard like
152this, but this project shows a quick preview of what you can do in
153Tableau Public.
154
155#+caption: Tableau Dashboard
156[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png]]
content/blog/2024-02-06-zfs.org → content/blog/2024-02-06-zfs.md renamed +129 −116
@@ -1,7 +1,9 @@
1#+title: How to Create a ZFS Pool on Ubuntu Linux 1+++
2#+date: 2024-02-06 2date = 2024-02-06
3#+description: Learn how to create a simple ZFS pool on Ubuntu Linux. 3title = "How to Create a ZFS Pool on Ubuntu Linux"
4#+filetags: :linux: 4description = ""
5draft = false
6+++
5 7
6This post details the process I used to create ZFS pools, datasets, and 8This post details the process I used to create ZFS pools, datasets, and
7snapshots on Ubuntu Server. 9snapshots on Ubuntu Server.
@@ -9,59 +11,62 @@ snapshots on Ubuntu Server.
9I found the following pages very helpful while going through this 11I found the following pages very helpful while going through this
10process: 12process:
11 13
12- [[https://ubuntu.com/tutorials/setup-zfs-storage-pool][Setup a ZFS 14- [Setup a ZFS storage
13 storage pool]] 15 pool](https://ubuntu.com/tutorials/setup-zfs-storage-pool)
14- [[https://wiki.ubuntu.com/Kernel/Reference/ZFS][Kernel/Reference/ZFS]] 16- [Kernel/Reference/ZFS](https://wiki.ubuntu.com/Kernel/Reference/ZFS)
15- [[https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/][ZFS for 17- [ZFS for
16 Dummies]] 18 Dummies](https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/)
19
20# Installation
17 21
18* Installation
19To start, I installed the ZFS package with the following command: 22To start, I installed the ZFS package with the following command:
20 23
21#+begin_src sh 24```sh
22sudo apt install zfsutils-linux 25sudo apt install zfsutils-linux
23#+end_src 26```
24 27
25Once installed, you can check the version to see if it installed 28Once installed, you can check the version to see if it installed
26correctly. 29correctly.
27 30
28#+begin_src sh 31```sh
29> zsf --version 32> zsf --version
30 33
31zfs-2.1.5-1ubuntu6~22.04.2 34zfs-2.1.5-1ubuntu6~22.04.2
32zfs-kmod-2.1.5-1ubuntu6~22.04.1 35zfs-kmod-2.1.5-1ubuntu6~22.04.1
33#+end_src 36```
37
38# ZFS Configuration
34 39
35* ZFS Configuration
36Now that ZFS is installed, we can create and configure the pool. 40Now that ZFS is installed, we can create and configure the pool.
37 41
38You have various options for configuring ZFS pools that all come 42You have various options for configuring ZFS pools that all come
39different pros and cons. I suggest visiting the links at the top of this 43different pros and cons. I suggest visiting the links at the top of this
40post or searching online for the best configuration for your use-case. 44post or searching online for the best configuration for your use-case.
41 45
42- Striped VDEVs (Raid0) 46- Striped VDEVs (Raid0)
43- Mirrored VDEVs (Raid1) 47- Mirrored VDEVs (Raid1)
44- Striped Mirrored VDEVs (Raid10) 48- Striped Mirrored VDEVs (Raid10)
45- RAIDz (Raid5) 49- RAIDz (Raid5)
46- RAIDz2 (Raidd6) 50- RAIDz2 (Raidd6)
47- RAIDz3 51- RAIDz3
48- Nested RAIDz (Raid50, Raid60) 52- Nested RAIDz (Raid50, Raid60)
49 53
50I will be using Raid10 in this guide. However, the majority of the steps 54I will be using Raid10 in this guide. However, the majority of the steps
51are the same regardless of your chosen pool configuration. 55are the same regardless of your chosen pool configuration.
52 56
53** Creating the Pool 57## Creating the Pool
54To start, let's list the disks available to use. You can use =fdisk=
55command to see all available disks.
56 58
57#+begin_src sh 59To start, let\'s list the disks available to use. You can use
60`fdisk` command to see all available disks.
61
62```sh
58sudo fdisk -l 63sudo fdisk -l
59#+end_src 64```
60 65
61Or, if you currently have them mounted, you can use the =df= command to 66Or, if you currently have them mounted, you can use the `df`
62view your disks. 67command to view your disks.
63 68
64#+begin_src sh 69```sh
65> sudo df -h 70> sudo df -h
66 71
67Filesystem Size Used Avail Use% Mounted on 72Filesystem Size Used Avail Use% Mounted on
@@ -72,35 +77,35 @@ Filesystem Size Used Avail Use% Mounted on
72/dev/sdd1 9.1T 8.7T 435G 96% /mnt/white-01 77/dev/sdd1 9.1T 8.7T 435G 96% /mnt/white-01
73/dev/sde1 7.3T 28K 6.9T 1% /mnt/red-03 78/dev/sde1 7.3T 28K 6.9T 1% /mnt/red-03
74/dev/sdf1 7.3T 28K 6.9T 1% /mnt/red-04 79/dev/sdf1 7.3T 28K 6.9T 1% /mnt/red-04
75#+end_src 80```
76 81
77If you're going to use mounted disks, make sure to umount them before 82If you\'re going to use mounted disks, make sure to umount them before
78creating the pool. 83creating the pool.
79 84
80#+begin_src sh 85```sh
81sudo umount /dev/sda1 86sudo umount /dev/sda1
82sudo umount /dev/sdb1 87sudo umount /dev/sdb1
83#+end_src 88```
84 89
85Now that I've identified the disks I want to use and have them 90Now that I\'ve identified the disks I want to use and have them
86unmounted, let's create the pool. For this example, I will call it 91unmounted, let\'s create the pool. For this example, I will call it
87=tank=. 92`tank`.
88 93
89#+begin_src sh 94```sh
90sudo zpool create -f -m /mnt/pool tank mirror /dev/sda /dev/sdb 95sudo zpool create -f -m /mnt/pool tank mirror /dev/sda /dev/sdb
91#+end_src 96```
92 97
93See below for the results of the new ZFS pool named =tank=, with a vdev 98See below for the results of the new ZFS pool named `tank`,
94automatically named =mirror-0=. 99with a vdev automatically named `mirror-0`.
95 100
96#+begin_src sh 101```sh
97> zfs list 102> zfs list
98 103
99NAME USED AVAIL REFER MOUNTPOINT 104NAME USED AVAIL REFER MOUNTPOINT
100tank 396K 7.14T 96K /tank 105tank 396K 7.14T 96K /tank
101#+end_src 106```
102 107
103#+begin_src sh 108```sh
104> zpool status 109> zpool status
105 110
106 pool: tank 111 pool: tank
@@ -114,35 +119,36 @@ config:
114 sdb ONLINE 0 0 0 119 sdb ONLINE 0 0 0
115 120
116errors: No known data errors 121errors: No known data errors
117#+end_src 122```
118 123
119We can also look at the mounted filesystem to see where the pool is 124We can also look at the mounted filesystem to see where the pool is
120mounted and some quick stats. 125mounted and some quick stats.
121 126
122#+begin_src sh 127```sh
123> df -h 128> df -h
124 129
125Filesystem Size Used Avail Use% Mounted on 130Filesystem Size Used Avail Use% Mounted on
126... 131...
127tank 7.2T 128K 7.2T 1% /tank 132tank 7.2T 128K 7.2T 1% /tank
128#+end_src 133```
134
135## Expanding the Pool
129 136
130** Expanding the Pool
131If you want to expand this pool, you will need to add a new VDEV to the 137If you want to expand this pool, you will need to add a new VDEV to the
132pool. Since I am using 2 disks per VDEV, I will need to add a new 2-disk 138pool. Since I am using 2 disks per VDEV, I will need to add a new 2-disk
133VDEV to the existing =tank= pool. 139VDEV to the existing `tank` pool.
134 140
135#+begin_src sh 141```sh
136sudo zpool add tank mirror /dev/sdX /dev/sdY 142sudo zpool add tank mirror /dev/sdX /dev/sdY
137#+end_src 143```
138 144
139If you're adding disks of different sizes, you'll need to use the =-f= 145If you\'re adding disks of different sizes, you\'ll need to use the
140flag. Keep in mind that the max size will be limited to the smallest 146`-f` flag. Keep in mind that the max size will be limited to
141disk added. 147the smallest disk added.
142 148
143#+begin_src sh 149```sh
144sudo zpool add -f tank mirror /dev/sdX /dev/sdY 150sudo zpool add -f tank mirror /dev/sdX /dev/sdY
145#+end_src 151```
146 152
147I added two 8TB hard drives and this process took around 10 seconds to 153I added two 8TB hard drives and this process took around 10 seconds to
148complete. 154complete.
@@ -151,39 +157,42 @@ When viewing the pool again, you can see that the pool has now doubled
151in size. We have 14.3 TB useable space and the same space used for 157in size. We have 14.3 TB useable space and the same space used for
152mirroring. 158mirroring.
153 159
154#+begin_src sh 160```sh
155> zfs list 161> zfs list
156 162
157NAME USED AVAIL REFER MOUNTPOINT 163NAME USED AVAIL REFER MOUNTPOINT
158tank 145G 14.3T 104K /tank 164tank 145G 14.3T 104K /tank
159tank/cloud 145G 14.3T 145G /tank/cloud 165tank/cloud 145G 14.3T 145G /tank/cloud
160tank/media 96K 14.3T 96K /tank/media 166tank/media 96K 14.3T 96K /tank/media
161#+end_src 167```
168
169### Converting Disks
162 170
163*** Converting Disks
164Some disks, such as NTFS-formatted drives, will need to be partitioned 171Some disks, such as NTFS-formatted drives, will need to be partitioned
165and formatted prior to being added to the pool. 172and formatted prior to being added to the pool.
166 173
167Start by identifying the disks you want to format and add to the pool. 174Start by identifying the disks you want to format and add to the pool.
168 175
169#+begin_src sh 176```sh
170sudo fdisk -l | grep /dev 177sudo fdisk -l | grep /dev
171#+end_src 178```
172 179
173I am going to format my =/dev/sdc= and =/dev/sdd= disks with the =fdisk= 180I am going to format my `/dev/sdc` and `/dev/sdd`
174command. 181disks with the `fdisk` command.
175 182
176See below for instructions on how to use =fdisk=. Here's what I did to 183See below for instructions on how to use `fdisk`. Here\'s
177create basic Linux formatted disks: 184what I did to create basic Linux formatted disks:
178 185
179- =g= : Create GPT partition table 186- `g` : Create GPT partition table
180- =n= : Create a new partition, hit Enter for all default options 187- `n` : Create a new partition, hit Enter for all default
181- =t= : Change partition type to =20= for =Linux filesystem= 188 options
182- =w= : Write the changes to disk and exit 189- `t` : Change partition type to `20` for
190 `Linux filesystem`
191- `w` : Write the changes to disk and exit
183 192
184I repeated this process for both disks. 193I repeated this process for both disks.
185 194
186#+begin_src sh 195```sh
187> sudo fdisk /dev/sdc 196> sudo fdisk /dev/sdc
188 197
189Welcome to fdisk (util-linux 2.37.2). 198Welcome to fdisk (util-linux 2.37.2).
@@ -229,96 +238,100 @@ Help:
229 G create a new empty SGI (IRIX) partition table 238 G create a new empty SGI (IRIX) partition table
230 o create a new empty DOS partition table 239 o create a new empty DOS partition table
231 s create a new empty Sun partition table 240 s create a new empty Sun partition table
232#+end_src 241```
233 242
234Once the drives are formatted, we can add these disks to the pool. 243Once the drives are formatted, we can add these disks to the pool.
235 244
236#+begin_src sh 245```sh
237sudo zpool add tank mirror /dev/sdc /dev/sdd 246sudo zpool add tank mirror /dev/sdc /dev/sdd
238#+end_src 247```
239 248
240When we list the pool again, we can see that our size is now updated to 249When we list the pool again, we can see that our size is now updated to
241approximately 22TB. This represents my hard drives totalling 45.6TB when 250approximately 22TB. This represents my hard drives totalling 45.6TB when
242shown with =fdisk -l=, with a Raid10 configuration using 22TB for 251shown with `fdisk -l`, with a Raid10 configuration using 22TB
243mirroring and 22TB of useable space. 252for mirroring and 22TB of useable space.
244 253
245#+begin_src sh 254```sh
246> zfs list 255> zfs list
247 256
248NAME USED AVAIL REFER MOUNTPOINT 257NAME USED AVAIL REFER MOUNTPOINT
249tank 145G 21.7T 104K /tank 258tank 145G 21.7T 104K /tank
250tank/cloud 145G 21.7T 145G /tank/cloud 259tank/cloud 145G 21.7T 145G /tank/cloud
251tank/media 145GT 21.7T 96K /tank/media 260tank/media 145GT 21.7T 96K /tank/media
252#+end_src 261```
262
263## Creating Datasets
253 264
254** Creating Datasets 265According to [ZFS
255According to 266Terminology](https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html),
256[[https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html][ZFS 267a `dataset` can refer to "clones, file systems, snapshots,
257Terminology]], a =dataset= can refer to “clones, file systems, 268and volumes.
258snapshots, and volumes.
259 269
260For this guide, I will use the =dataset= term to refer to file systems 270For this guide, I will use the `dataset` term to refer to
261created under a pool. 271file systems created under a pool.
262 272
263Within my =tank= pool, I am going to create some datasets to help 273Within my `tank` pool, I am going to create some datasets to
264organize my files. This will give me location to store data rather than 274help organize my files. This will give me location to store data rather
265simply dumping everything at the =/tank/= location. 275than simply dumping everything at the `/tank/` location.
266 276
267#+begin_src sh 277```sh
268sudo zfs create tank/cloud 278sudo zfs create tank/cloud
269sudo zfs create tank/media 279sudo zfs create tank/media
270#+end_src 280```
271 281
272Once created, you can see these datasets in the output of your pool 282Once created, you can see these datasets in the output of your pool
273list: 283list:
274 284
275#+begin_src sh 285```sh
276> zfs list 286> zfs list
277NAME USED AVAIL REFER MOUNTPOINT 287NAME USED AVAIL REFER MOUNTPOINT
278tank 752K 7.14T 104K /tank 288tank 752K 7.14T 104K /tank
279tank/cloud 96K 7.14T 96K /tank/cloud 289tank/cloud 96K 7.14T 96K /tank/cloud
280tank/media 96K 7.14T 96K /tank/media 290tank/media 96K 7.14T 96K /tank/media
281#+end_src 291```
282 292
283** Creating Snapshots 293## Creating Snapshots
284Next, let's create our first snapshot. We can do this by calling the
285=snapshot= command and give it an output name. I will be throwing the
286current date and time into my example.
287 294
288#+begin_src sh 295Next, let\'s create our first snapshot. We can do this by calling the
296`snapshot` command and give it an output name. I will be
297throwing the current date and time into my example.
298
299```sh
289sudo zfs snapshot tank@$(date '+%Y-%m-%d_%H-%M') 300sudo zfs snapshot tank@$(date '+%Y-%m-%d_%H-%M')
290#+end_src 301```
291 302
292We can list the snapshots in our pool with the following command: 303We can list the snapshots in our pool with the following command:
293 304
294#+begin_src sh 305```sh
295> zfs list -t snapshot 306> zfs list -t snapshot
296NAME USED AVAIL REFER MOUNTPOINT 307NAME USED AVAIL REFER MOUNTPOINT
297tank@2024-02-06_19-41 0B - 104K - 308tank@2024-02-06_19-41 0B - 104K -
298#+end_src 309```
310
311## Destroy Snapshots
299 312
300** Destroy Snapshots
301You can always destroy snapshots that are no longer needed: 313You can always destroy snapshots that are no longer needed:
302 314
303#+begin_src sh 315```sh
304sudo zfs destroy tank@2024-02-06_19-41 316sudo zfs destroy tank@2024-02-06_19-41
305#+end_src 317```
306 318
307Once deleted, they will no longer appear in the list: 319Once deleted, they will no longer appear in the list:
308 320
309#+begin_src sh 321```sh
310> zfs list -t snapshot 322> zfs list -t snapshot
311no datasets available 323no datasets available
312#+end_src 324```
313 325
314* My Thoughts on ZFS So Far 326# My Thoughts on ZFS So Far
315- I sacrificed 25TB to be able to mirror my data, but I feel more 327
316 comfortable with the potential to save my data by quickly replacing a 328- I sacrificed 25TB to be able to mirror my data, but I feel more
317 disk if I need to. 329 comfortable with the potential to save my data by quickly replacing
318- The set-up was surprisingly easy and fast. 330 a disk if I need to.
319- Disk I/O is fast as well. I was worried that the data transfer speeds 331- The set-up was surprisingly easy and fast.
320 would be slower due to the RAID configuration. 332- Disk I/O is fast as well. I was worried that the data transfer
321- Media streaming and transcoding has seen no noticeable drop in 333 speeds would be slower due to the RAID configuration.
322 performance. 334- Media streaming and transcoding has seen no noticeable drop in
323- My only limitation really is the number of HDD bays in my server HDD 335 performance.
324 cage. 336- My only limitation really is the number of HDD bays in my server HDD
337 cage.
content/blog/2024-02-13-ubuntu-emergency-mode.md added +75
@@ -0,0 +1,75 @@
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
11myself stuck in Ubuntu\'s emergency mode after the first reboot I
12performed after creating the ZFS pool.
13
14My server was stuck in the boot process and showed the following error
15on the screen:
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
25noticed the root cause was related to a very long search for certain
26drives. I kept seeing 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
33that asked Ubuntu to mount two disks on boot, but I had recently changed
34those disks to be part of my ZFS pool instead. Therefore, Ubuntu was
35trying to identify and mount a disk that 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
42user\'s password, 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
49following lines at the bottom of the file. You can comment-out a line by
50prepending a `#` symbol at the beginning of the line. You can
51also delete the line entirely.
52
53``` conf
54# What it looked like when running into the issue:
55UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
56UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
57
58# What I changed it to, in order to fix the issue:
59# UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
60# UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
61```
62
63Once removing the lines above from the `/etc/fstab` file,
64save and exit the file by hitting the `Ctrl` + `x`
65key combo.
66
67You can now hit `Ctrl` + `D` to continue, or
68reboot:
69
70```sh
71systemctl reboot
72```
73
74Once rebooted, I was able to watch the machine boot properly and launch
75to the TTY login screen without errors!
content/blog/2024-02-13-ubuntu-emergency-mode.org deleted −69
@@ -1,69 +0,0 @@
1#+title: Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File
2#+date: 2024-02-13
3#+description: Learn how to fix a missing disk issue that can lock you in Ubuntu's emergency mode.
4#+filetags: :linux:
5
6* The Problem
7I recently [[../zfs/][migrated my hard drives to a ZFS pool]] and
8found myself stuck in Ubuntu's emergency mode after the first reboot I
9performed after creating the ZFS pool.
10
11My server was stuck in the boot process and showed the following error
12on the screen:
13
14#+begin_src txt
15You are in emergency mode.
16After logging in, type "journalctl -xb" to view system logs,
17"systemctl reboot" to reboot, "systemctl default"
18or ^D to try again to boot into default mode".
19#+end_src
20
21After rebooting the server and watching the logs scroll on a monitor, I
22noticed the root cause was related to a very long search for certain
23drives. I kept seeing errors like this:
24
25#+begin_src txt
26[ TIME ] Timed out waiting of device dev-disk-by/[disk-uuid]
27#+end_src
28
29I realized that I had not removed the =/etc/fstab= references that asked
30Ubuntu to mount two disks on boot, but I had recently changed those
31disks to be part of my ZFS pool instead. Therefore, Ubuntu was trying to
32identify and mount a disk that was not available.
33
34Now that we have an idea of the issue, let's move to solution.
35
36* The Solution
37In order to fix the issue, I waited until I was allowed to type the root
38user's password, and then I executed the following command:
39
40#+begin_src sh
41nano /etc/fstab
42#+end_src
43
44Within the =fstab= file, I needed to comment/remove the following lines
45at the bottom of the file. You can comment-out a line by prepending a
46=#= symbol at the beginning of the line. You can also delete the line
47entirely.
48
49#+begin_src conf
50# What it looked like when running into the issue:
51UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
52UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
53
54# What I changed it to, in order to fix the issue:
55# UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
56# UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
57#+end_src
58
59Once removing the lines above from the =/etc/fstab= file, save and exit
60the file by hitting the =Ctrl= + =x= key combo.
61
62You can now hit =Ctrl= + =D= to continue, or reboot:
63
64#+begin_src sh
65systemctl reboot
66#+end_src
67
68Once rebooted, I was able to watch the machine boot properly and launch
69to the TTY login screen without errors!
content/blog/2024-02-21-self-hosting-otter-wiki.org → content/blog/2024-02-21-self-hosting-otter-wiki.md renamed +52 −44
@@ -1,36 +1,41 @@
1#+title: Self-Hosting An Otter Wiki 1+++
2#+date: <2024-02-21> 2date = 2024-02-21
3#+description: Learn how to easily self-host An Otter Wiki. 3title = "Self-Hosting an Otter Wiki"
4#+filetags: :selfhosting: 4description = ""
5draft = false
6+++
5 7
6* An Otter Wiki 8# An Otter Wiki
7[[https://otterwiki.com/][An Otter Wiki]] is an easy to use wiki
8software that takes almost no effort to set up and maintain.
9 9
10* Installation 10[An Otter Wiki](https://otterwiki.com/) is an easy to use wiki software
11To install An Otter Wiki, I'm going to use Docker Compose to create the 11that takes almost no effort to set up and maintain.
12
13# Installation
14
15To install An Otter Wiki, I\'m going to use Docker Compose to create the
12container and then use Nginx as a reverse proxy to allow external access 16container and then use Nginx as a reverse proxy to allow external access
13via a subdomain I own. 17via a subdomain I own.
14 18
15** Docker Compose 19## Docker Compose
16Start by creating a directory for the container's files. 20
21Start by creating a directory for the container\'s files.
17 22
18#+begin_src sh 23```sh
19mkdir ~/otterwiki 24mkdir ~/otterwiki
20#+end_src 25```
21 26
22Next, create the =docker-compose.yml= file to define the container's 27Next, create the `docker-compose.yml` file to define the
23parameters. 28container\'s parameters.
24 29
25#+begin_src sh 30```sh
26nano ~/otterwiki/docker-compose.yml 31nano ~/otterwiki/docker-compose.yml
27#+end_src 32```
28 33
29Within the file, paste the following content. You can read the project's 34Within the file, paste the following content. You can read the
30documentation if you want to further override or customize the 35project\'s documentation if you want to further override or customize
31container. 36the container.
32 37
33#+begin_src conf 38``` conf
34version: '3' 39version: '3'
35services: 40services:
36 otterwiki: 41 otterwiki:
@@ -42,32 +47,34 @@ services:
42 - 8337:80 47 - 8337:80
43 volumes: 48 volumes:
44 - ./app-data:/app-data 49 - ./app-data:/app-data
45#+end_src 50```
46 51
47Once the file is saved and closed, you can launch the container. 52Once the file is saved and closed, you can launch the container.
48 53
49#+begin_src sh 54```sh
50cd ~/otterwiki 55cd ~/otterwiki
51sudo docker-compose up -d 56sudo docker-compose up -d
52#+end_src 57```
53 58
54The container is now available at =localhost:8337=. Next, we will use 59The container is now available at `localhost:8337`. Next, we
55Nginx to serve this app externally. 60will use Nginx to serve this app externally.
56 61
57** Nginx 62## Nginx
58To access the app externally, let's set up a reverse proxy. I'll start 63
64To access the app externally, let\'s set up a reverse proxy. I\'ll start
59by creating the Nginx configuration file for my wiki. 65by creating the Nginx configuration file for my wiki.
60 66
61#+begin_src sh 67```sh
62sudo nano /etc/nginx/sites-available/wiki 68sudo nano /etc/nginx/sites-available/wiki
63#+end_src 69```
64 70
65Within the file, I have saved the following content. This assumes you 71Within the file, I have saved the following content. This assumes you
66already have a TLS/SSL cert to use with this subdomain. If not, simply 72already have a TLS/SSL cert to use with this subdomain. If not, simply
67remove the =ssl_*= variables, remove the =80= server block, and change 73remove the `ssl_*` variables, remove the `80`
68the =443= server block to =80= to serve the app without SSL. 74server block, and change the `443` server block to
75`80` to serve the app without SSL.
69 76
70#+begin_src conf 77``` conf
71server { 78server {
72 if ($host ~ ^[^.]+\.example\.com$) { 79 if ($host ~ ^[^.]+\.example\.com$) {
73 return 301 https://$host$request_uri; 80 return 301 https://$host$request_uri;
@@ -101,35 +108,36 @@ server {
101 proxy_set_header X-Forwarded-Host $http_host; 108 proxy_set_header X-Forwarded-Host $http_host;
102 } 109 }
103} 110}
104#+end_src 111```
105 112
106Save and close the configuration file. On Nginx, we need to symlink the 113Save and close the configuration file. On Nginx, we need to symlink the
107file to enable it. 114file to enable it.
108 115
109#+begin_src sh 116```sh
110sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki 117sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki
111#+end_src 118```
112 119
113Once enabled, restart the Nginx server to start serving the app 120Once enabled, restart the Nginx server to start serving the app
114externally. 121externally.
115 122
116#+begin_src sh 123```sh
117sudo systemctl restart nginx.service 124sudo systemctl restart nginx.service
118#+end_src 125```
126
127# Screenshots
119 128
120* Screenshots
121In the screenshot below, you can see the initial launch screen for An 129In the screenshot below, you can see the initial launch screen for An
122Otter Wiki. Start by creating your admin account and configure the app 130Otter Wiki. Start by creating your admin account and configure the app
123as necessary. 131as necessary.
124 132
125#+caption: An Otter Wiki - Initial Screen 133![An Otter Wiki - Initial
126[[https://img.cleberg.net/blog/20240221-otterwiki/otterwiki.png]] 134Screen](https://img.cleberg.net/blog/20240221-otterwiki/otterwiki.png)
127 135
128You can also see the default editing screen for creating and editing 136You can also see the default editing screen for creating and editing
129pages. It's as easy as typing Markdown and hitting the save button. 137pages. It\'s as easy as typing Markdown and hitting the save button.
130 138
131#+caption: An Otter Wiki - Editing Screen 139![An Otter Wiki - Editing
132[[https://img.cleberg.net/blog/20240221-otterwiki/otterwiki-edit.png]] 140Screen](https://img.cleberg.net/blog/20240221-otterwiki/otterwiki-edit.png)
133 141
134Overall, I like An Otter Wiki and will be looking forward to seeing how 142Overall, I like An Otter Wiki and will be looking forward to seeing how
135much use I get out of it going forward. 143much use I get out of it going forward.
content/blog/2024-03-13-doom-emacs.md added +362
@@ -0,0 +1,362 @@
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
11treemacs side pane open with the project contents, multiple buffers
12tiled next to each other, and the help pane open at the bottomm.
13
14The themes are `doom-homage-white` and
15`doom-homage-black`.
16
17![Doom Emacs Light
18Mode](https://img.cleberg.net/blog/20240314-doom-emacs/light.png)
19
20![Doom Emacs Dark
21Mode](https://img.cleberg.net/blog/20240314-doom-emacs/dark.png)
22
23## Getting Started
24
25I have been switching back and forth between
26[markdown](https://en.wikipedia.org/wiki/Markdown) and
27[org-mode](https://en.wikipedia.org/wiki/Org-mode) recently for my
28personal note taking, wiki, and even this blog. As a result, I have been
29stumbling further into the world of Emacs and found myself at a point
30where I now prefer to do most of my basic editing within Emacs.
31
32I\'ll leave the markdown vs. org-mode debate for another post, but I
33love org-mode\'s extensibility and interactive nature within Emacs, but
34it becomes very unwieldy in any other client implementation of
35org-mode - especially on iOS. On the flip side, markdown is limited in
36functionality and fractured into different standards, but it\'s simple
37and popular enough that there are a plethora of great clients to choose
38from that will get the job done.
39
40For now, I want to focus on how I have been using Emacs and some of the
41things that would have helped me learn it faster had I known where to
42start.
43
44### Installation
45
46This post focuses on [Doom
47Emacs](https://github.com/doomemacs/doomemacs), which is an Emacs
48framework that provides an alternative experience to the vanilla [GNU
49Emacs](https://www.gnu.org/software/emacs/).
50
51The [Getting Start
52Guide](https://github.com/doomemacs/doomemacs/blob/master/docs/getting_started.org)
53has an extremely detailed walkthrough of installation for all systems,
54so please refer to that guide for up-to-date instructions.
55
56I chose to install on macOS, using the Homebrew option with the
57`railwaycat/emacsmacport` version of Emacs.
58
59Once the program is installed, you can run the program by typing
60`emacs` in a terminal. If you installed a version of Emacs
61that supports both a GUI and TUI, you will have to run
62`emacs -nw` to get the TUI instead of the default GUI.
63
64### Configuration
65
66Once installed, you can configure Doom by editing the files within the
67`~/.doom.d/` directory. This directory holds four files:
68
691. `config.el` - Personal configuration file
702. `custom.el` - Custom set variables
713. `init.el` - Doom modules and load order, must run
72 `doom sync` after modifying
734. `packages.el` - Declare packages to install in this file,
74 then run `doom sync` to install
75
76I only needed a few customizations for my configuration, so I\'ll list
77them below.
78
79```lisp
80;; ~/.doom.d/config.el
81(setq doom-theme 'doom-homage-black)
82(setq display-line-numbers-type t)
83(setq org-directory "~/Documents/Notes/")
84
85;; lengthy org-publish directives at the bottom of the file
86```
87
88```lisp
89;; ~/.doom.d/init.el
90(doom! :input
91 :completion
92 company ; the ultimate code completion backend
93 vertico ; the search engine of the future
94
95 :ui
96 doom ; what makes DOOM look the way it does
97 doom-dashboard ; a nifty splash screen for Emacs
98 (emoji +unicode) ; 🙂
99 hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
100 minimap ; show a map of the code on the side
101 modeline ; snazzy, Atom-inspired modeline, plus API
102 ophints ; highlight the region an operation acts on
103 (popup +defaults) ; tame sudden yet inevitable temporary windows
104 tabs ; a tab bar for Emacs
105 treemacs ; a project drawer, like neotree but cooler
106 (vc-gutter +pretty) ; vcs diff in the fringe
107 vi-tilde-fringe ; fringe tildes to mark beyond EOB
108 workspaces ; tab emulation, persistence & separate workspaces
109
110 :editor
111 (evil +everywhere); come to the dark side, we have cookies
112 file-templates ; auto-snippets for empty files
113 fold ; (nigh) universal code folding
114 snippets ; my elves. They type so I don't have to
115
116 :emacs
117 dired ; making dired pretty [functional]
118 electric ; smarter, keyword-based electric-indent
119 undo ; persistent, smarter undo for your inevitable mistakes
120 vc ; version-control and Emacs, sitting in a tree
121
122 :term
123 term ; basic terminal emulator for Emacs
124
125 :checkers
126 syntax ; tasing you for every semicolon you forget
127
128 :tools
129 (eval +overlay) ; run code, run (also, repls)
130 lookup ; navigate your code and its documentation
131 magit ; a git porcelain for Emacs
132
133 :os
134 (:if (featurep :system 'macos) macos) ; improve compatibility with macOS
135
136 :lang
137 common-lisp ; if you've seen one lisp, you've seen them all
138 emacs-lisp ; drown in parentheses
139 markdown ; writing docs for people to ignore
140 org ; organize your plain life in plain text
141 python ; beautiful is better than ugly
142 sh ; she sells {ba,z,fi}sh shells on the C xor
143
144 :app
145 irc ; how neckbeards socialize
146 (rss +org) ; emacs as an RSS reader
147
148 (default +bindings +smartparens))
149```
150
151If you\'re editing these files within Doom directly, remember to run
152`SPC h r r` to reload the configuration. Also remember to run
153`doom sync` for any changes to the `init.el` or
154`packages.el` files.
155
156## Basic Functionality
157
158I kept a cheat sheet note open at first with all of the basic functions
159typed out, copied as I went through the tutorial. After a little while,
160I no longer needed it. I highly recommend writing down the most
161applicable shortcuts for your preferred functionality and refer back to
162it until you\'ve memorized it.
163
164Memorizing the shortcuts will differ based on the type of Emacs
165framework being used. Personally, migrating from vanilla Emacs to Doom
166Emacs simplified everything by a large factor and instantly enabled me
167to start working on my projects, eliminating most of the hurdles I was
168running into. The vanilla emacs hotkeys became obnoxious and I actually
169stopped using Emacs entirely for about a month before trying Doom.
170
171For me, the first logical step is to interact with the local filesystem.
172To do this, I needed to know how to open directories, open files, save
173files, discard changes, close files, and switch between open files. Here
174are some example shortcuts I\'ve written down in order to accomplish
175file-based actions.
176
177 Doom Hotkey Emacs Hotkey Description
178 ---------------------------- --------------------------- ----------------------------------------
179 `SPC :` `C-x` Run functions
180 `SPC f f` `C-x f` Open file in buffer
181 `SPC f d` `C-x d` Open directory with `dired`
182 `i` `C-x C-q` Edit current buffer (insert mode)
183 `q` `C-x C-q` Quit out of insert mode
184 `SPC f s` `C-x s` Save current buffer
185 `SPC b k` `C-x k` Kill current buffer
186 `SPC w h/j/k/l` `C-x o`\[^1^\] Move left/down/up/right to next buffer
187
188\[^1^\] Doom\'s evil-window functionality is a bit different from GNU
189Emacs, but you can always switch to the \"other\" buffer with
190`C-x o` or `C-x b` to get a list of buffers to
191select.
192
193In general, when in Doom, you can press `SPC` and wait a
194second for the help pane to appear with all available hotkey options.
195For example, you can press `SPC`, wait for the help pane, and
196then select a key such as `g` to enter the git help pane and
197explore further command options.
198
199## Editing
200
201Next in my process is to dive into editing for any languages I\'m
202currently using. In this post, I will just cover Markdown and Org-Mode
203but I have also been slowly adoping some Python and general web dev
204tools as well.
205
206### Markdown
207
208![Markdown
209Preview](https://img.cleberg.net/blog/20240314-doom-emacs/markdown.png)
210
211Markdown is fairly simple as the syntax is limited, so just make sure
212the `~/.doom.d/init.el` includes the `markdown`
213declaration in the `:lang` section.
214
215This package includes the following hotkey menus. The insert and toggle
216menu expands further, allowing you to insert various markdown elements
217and toggle things like link hiding.
218
219 Doom Hotkey Function
220 ----------------------------------------- --------------------------
221 `SPC m '` markdown-edit-code-block
222 `SPC m e` markdown-export
223 `SPC m i` +insert
224 `SPC m o` markdown-open
225 `SPC m p` markdown-preview
226 `SPC m t` +toggle
227 `SPC : markdown-table-align` markdown-table-align
228
229### Org-Mode
230
231![Org-Mode
232Preview](https://img.cleberg.net/blog/20240314-doom-emacs/org.png)
233
234Similar to the markdown section above, ensure that the
235`~/.doom.d/init.el` includes the `org` declaration
236in the `:lang` section.
237
238There are a few hot keys, but a quick search with `SPC : org`
239shows that there are 865 possible org-related functions you can run. I
240won\'t possibly be able to list them all, so I will simply cover a few
241of the basic commands I use myself.
242
243 Doom Hotkey Function
244 --------------------------- ---------------------------------------
245 `SPC m t` org-todo
246 `SPC n t` org-todo-list
247 `SPC o A` org-agenda
248 `SPC X` org-capture
249 `SPC m p p` org-priority
250 `SPC m d s` org-schedule
251 `TAB` org-cycle
252 `SHIFT TAB` Collapse/open all headings in buffer
253 `M-q` Format/wrap current section
254 `M-Left/Right` Demote/promote current heading
255 `M-Down/Up` Shift current heading section down/up
256
2571. Org-Publish
258
259 Org includes a [publishing management
260 system](https://orgmode.org/manual/Publishing.html) by default that
261 allows you to export org files to Org, iCalendar, HTML, LaTex,
262 Markdown, ODT, and Plain Text. Most of these can be exported into
263 another buffer and opened, or simply to an external file.
264
265 While inside an org file, simply run `SPC m e` or
266 `M-x org-export-dispatch` to open the export menu. This
267 menu will show all options and ask you to select an option. If you
268 want to export to HTML, simply press `h` and then
269 `H` (As HTML buffer), `h` (As HTML file), or
270 `o` (As HTML file and open).
271
2722. Projects
273
274 Some publishing options are easier with a defined project in Emacs.
275 To create a project within Emacs, I use two methods:
276
277 1. Add the project via the projectile command `SPC p a`.
278 Does not always work for me.
279 2. Add an empty `.projectile` file in the project root.
280
281 Once a project has been created, you can create custom publishing
282 actions within your `~/.doom.d/config.el` file. For
283 example, here\'s a test project I created to try and convert this
284 blog to org-mode recently.
285
286 ```lisp
287 ;; org-publish
288 (require 'ox-publish)
289
290 (defun my/org-sitemap-date-entry-format (entry style project) "Format ENTRY in
291 org-publish PROJECT Sitemap format ENTRY ENTRY STYLE format that includes
292 date." (let ((filename (org-publish-find-title entry project))) (if (= (length
293 filename) 0) (format "*%s*" entry) (format "{{{timestamp(%s)}}}
294 [[file:%s][%s]]" (format-time-string "%Y-%m-%d" (org-publish-find-date entry
295 project)) entry filename))))
296
297 (setq org-export-global-macros '(("timestamp" . "@@html:<time datetime='[$1]'
298 class='timestamp'>[$1]</time>@@")))
299
300 (setq org-publish-project-alist
301 `(("blog"
302 :base-directory "~/Source/cleberg.net/"
303 :base-extension "org"
304 :recursive t
305 :publishing-directory "~/Source/cleberg.net/public/"
306 :publishing-function org-html-publish-to-html
307 ;; HTML5
308 :html-doctype "html5"
309 :html-html5-fancy t
310 ;; Disable some Org's HTML defaults
311 :html-head-include-scripts nil
312 :html-head-include-default-style nil
313 :section-numbers nil
314 :with-title nil
315 ;; Sitemap
316 :auto-sitemap t
317 :sitemap-title: "Sitemap"
318 :sitemap-sort-files anti-chronologically
319 ; :sitemap-function my/org-sitemap-date-entry-format
320 ;; Customize HTML output
321 :html-divs ((preamble "header" "preamble")
322 (content "main" "content")
323 (postamble "footer" "postamble"))
324 :html-head "<meta name='theme-color' content='#111' media='(prefers-color-scheme: dark)'>
325 <meta name='theme-color' content='#fff' media='(prefers-color-scheme: light)'>
326 <link rel='stylesheet' href='/syntax-theme-dark.css' media='(prefers-color-scheme: dark)'>
327 <link rel='stylesheet' href='/syntax-theme-light.css' media='(prefers-color-scheme: light)'>
328 <link rel='stylesheet' href='/styles.css' type='text/css'>"
329 :html-preamble "<nav class='site-nav' aria-label='site-nav' role='navigation'>
330 <ul>
331 <li><a href='/'>Home</a></li>
332 <li><a href='/blog/'>Blog</a></li>
333 <li><a href='/services/'>Services</a></li>
334 <li><a href='/wiki/'>Wiki</a></li>
335 </ul></nav>
336 <h1>%t</h1>
337 <time datetime='%d'>%d</time>"
338 :html-postamble "
339 <p>Last build: %T</p>
340 <p>Created with %c</p>"
341 )
342
343 ("static"
344 :base-directory "~/Source/cleberg.net/static/"
345 :base-extension "css\\|txt\\|jpg\\|gif\\|png"
346 :recursive t
347 :publishing-directory "~/Source/cleberg.net/public/"
348 :publishing-function org-publish-attachment)
349
350 ("cleberg.net" :components ("blog" "static"))))
351 ```
352
353## General Thoughts
354
355I have enjoyed Doom Emacs (far more than GNU Emacs) and will likely
356continue to use it as my main editor for the time being. Org-Mode is
357certainly the largest factor here, as I far prefer it over Markdown due
358to its inherent features and detailed markup options. However, working
359with org-mode on iOS has been a pain and I will have to see if there\'s
360an easier way to resolve those issues or if going back to separate
361Markdown, Reminders, and Calendar apps is easier to work with than an
362all-in-one org solution.
content/blog/2024-03-13-doom-emacs.org deleted −354
@@ -1,354 +0,0 @@
1#+title: Doom Emacs & Org-Mode
2#+date: <2024-03-14 Thu 16:19:23>
3#+description: A quick look at my setup with Doom Emacs and the Org-Mode syntax.
4
5** Screenshots
6
7These screenshots are showing a project opened with projectile, a
8treemacs side pane open with the project contents, multiple buffers
9tiled next to each other, and the help pane open at the bottomm.
10
11The themes are =doom-homage-white= and =doom-homage-black=.
12
13#+caption: Doom Emacs Light Mode
14[[https://img.cleberg.net/blog/20240314-doom-emacs/light.png]]
15
16#+caption: Doom Emacs Dark Mode
17[[https://img.cleberg.net/blog/20240314-doom-emacs/dark.png]]
18
19** Getting Started
20
21I have been switching back and forth between
22[[https://en.wikipedia.org/wiki/Markdown][markdown]] and
23[[https://en.wikipedia.org/wiki/Org-mode][org-mode]] recently for my
24personal note taking, wiki, and even this blog. As a result, I have been
25stumbling further into the world of Emacs and found myself at a point
26where I now prefer to do most of my basic editing within Emacs.
27
28I'll leave the markdown vs. org-mode debate for another post, but I love
29org-mode's extensibility and interactive nature within Emacs, but it
30becomes very unwieldy in any other client implementation of org-mode -
31especially on iOS. On the flip side, markdown is limited in
32functionality and fractured into different standards, but it's simple
33and popular enough that there are a plethora of great clients to choose
34from that will get the job done.
35
36For now, I want to focus on how I have been using Emacs and some of the
37things that would have helped me learn it faster had I known where to
38start.
39
40*** Installation
41
42This post focuses on [[https://github.com/doomemacs/doomemacs][Doom
43Emacs]], which is an Emacs framework that provides an alternative
44experience to the vanilla [[https://www.gnu.org/software/emacs/][GNU
45Emacs]].
46
47The
48[[https://github.com/doomemacs/doomemacs/blob/master/docs/getting_started.org][Getting
49Start Guide]] has an extremely detailed walkthrough of installation for
50all systems, so please refer 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=
56in a terminal. If you installed a version of Emacs that supports both a
57GUI and TUI, you will have to run =emacs -nw= to get the TUI instead of
58the default GUI.
59
60*** Configuration
61
62Once installed, you can configure Doom by editing the files within the
63=~/.doom.d/= directory. This directory holds four files:
64
651. =config.el= - Personal configuration file
662. =custom.el= - Custom set variables
673. =init.el= - Doom modules and load order, must run =doom sync= after
68 modifying
694. =packages.el= - Declare packages to install in this file, then run
70 =doom sync= to install
71
72I only needed a few customizations for my configuration, so I'll list
73them below.
74
75#+begin_src lisp
76;; ~/.doom.d/config.el
77(setq doom-theme 'doom-homage-black)
78(setq display-line-numbers-type t)
79(setq org-directory "~/Documents/Notes/")
80
81;; lengthy org-publish directives at the bottom of the file
82#+end_src
83
84#+begin_src lisp
85;; ~/.doom.d/init.el
86(doom! :input
87 :completion
88 company ; the ultimate code completion backend
89 vertico ; the search engine of the future
90
91 :ui
92 doom ; what makes DOOM look the way it does
93 doom-dashboard ; a nifty splash screen for Emacs
94 (emoji +unicode) ; 🙂
95 hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
96 minimap ; show a map of the code on the side
97 modeline ; snazzy, Atom-inspired modeline, plus API
98 ophints ; highlight the region an operation acts on
99 (popup +defaults) ; tame sudden yet inevitable temporary windows
100 tabs ; a tab bar for Emacs
101 treemacs ; a project drawer, like neotree but cooler
102 (vc-gutter +pretty) ; vcs diff in the fringe
103 vi-tilde-fringe ; fringe tildes to mark beyond EOB
104 workspaces ; tab emulation, persistence & separate workspaces
105
106 :editor
107 (evil +everywhere); come to the dark side, we have cookies
108 file-templates ; auto-snippets for empty files
109 fold ; (nigh) universal code folding
110 snippets ; my elves. They type so I don't have to
111
112 :emacs
113 dired ; making dired pretty [functional]
114 electric ; smarter, keyword-based electric-indent
115 undo ; persistent, smarter undo for your inevitable mistakes
116 vc ; version-control and Emacs, sitting in a tree
117
118 :term
119 term ; basic terminal emulator for Emacs
120
121 :checkers
122 syntax ; tasing you for every semicolon you forget
123
124 :tools
125 (eval +overlay) ; run code, run (also, repls)
126 lookup ; navigate your code and its documentation
127 magit ; a git porcelain for Emacs
128
129 :os
130 (:if (featurep :system 'macos) macos) ; improve compatibility with macOS
131
132 :lang
133 common-lisp ; if you've seen one lisp, you've seen them all
134 emacs-lisp ; drown in parentheses
135 markdown ; writing docs for people to ignore
136 org ; organize your plain life in plain text
137 python ; beautiful is better than ugly
138 sh ; she sells {ba,z,fi}sh shells on the C xor
139
140 :app
141 irc ; how neckbeards socialize
142 (rss +org) ; emacs as an RSS reader
143
144 (default +bindings +smartparens))
145#+end_src
146
147If you're editing these files within Doom directly, remember to run
148=SPC h r r= to reload the configuration. Also remember to run
149=doom sync= for any changes to the =init.el= or =packages.el= files.
150
151** Basic Functionality
152
153I kept a cheat sheet note open at first with all of the basic functions
154typed out, copied as I went through the tutorial. After a little while,
155I no longer needed it. I highly recommend writing down the most
156applicable shortcuts for your preferred functionality and refer back to
157it until you've memorized it.
158
159Memorizing the shortcuts will differ based on the type of Emacs
160framework being used. Personally, migrating from vanilla Emacs to Doom
161Emacs simplified everything by a large factor and instantly enabled me
162to start working on my projects, eliminating most of the hurdles I was
163running into. The vanilla emacs hotkeys became obnoxious and I actually
164stopped using Emacs entirely for about a month before trying Doom.
165
166For me, the first logical step is to interact with the local filesystem.
167To do this, I needed to know how to open directories, open files, save
168files, discard changes, close files, and switch between open files. Here
169are some example shortcuts I've written down in order to accomplish
170file-based actions.
171
172| Doom Hotkey | Emacs Hotkey | Description |
173|-----------------+--------------+----------------------------------------|
174| =SPC := | =C-x= | Run functions |
175| =SPC f f= | =C-x f= | Open file in buffer |
176| =SPC f d= | =C-x d= | Open directory with =dired= |
177| =i= | =C-x C-q= | Edit current buffer (insert mode) |
178| =q= | =C-x C-q= | Quit out of insert mode |
179| =SPC f s= | =C-x s= | Save current buffer |
180| =SPC b k= | =C-x k= | Kill current buffer |
181| =SPC w h/j/k/l= | =C-x o=[^1] | Move left/down/up/right to next buffer |
182
183[^1] Doom's evil-window functionality is a bit different from GNU Emacs,
184but you can always switch to the "other" buffer with =C-x o= or =C-x b=
185to get a list of buffers to select.
186
187In general, when in Doom, you can press =SPC= and wait a second for the
188help pane to appear with all available hotkey options. For example, you
189can press =SPC=, wait for the help pane, and then select a key such as
190=g= to enter the git help pane and explore further command options.
191
192** Editing
193
194Next in my process is to dive into editing for any languages I'm
195currently using. In this post, I will just cover Markdown and Org-Mode
196but I have also been slowly adoping some Python and general web dev
197tools as well.
198
199*** Markdown
200
201#+caption: Markdown Preview
202[[https://img.cleberg.net/blog/20240314-doom-emacs/markdown.png]]
203
204Markdown is fairly simple as the syntax is limited, so just make sure
205the =~/.doom.d/init.el= includes the =markdown= declaration in the
206=:lang= section.
207
208This package includes the following hotkey menus. The insert and toggle
209menu expands further, allowing you to insert various markdown elements
210and toggle things like link hiding.
211
212| Doom Hotkey | Function |
213|------------------------------+--------------------------|
214| =SPC m '= | markdown-edit-code-block |
215| =SPC m e= | markdown-export |
216| =SPC m i= | +insert |
217| =SPC m o= | markdown-open |
218| =SPC m p= | markdown-preview |
219| =SPC m t= | +toggle |
220| =SPC : markdown-table-align= | markdown-table-align |
221
222*** Org-Mode
223
224#+caption: Org-Mode Preview
225[[https://img.cleberg.net/blog/20240314-doom-emacs/org.png]]
226
227Similar to the markdown section above, ensure that the
228=~/.doom.d/init.el= includes the =org= declaration in the =:lang=
229section.
230
231There are a few hot keys, but a quick search with =SPC : org= shows that
232there are 865 possible org-related functions you can run. I won't
233possibly be able to list them all, so I will simply cover a few of the
234basic commands I use myself.
235
236| Doom Hotkey | Function |
237|----------------+---------------------------------------|
238| =SPC m t= | org-todo |
239| =SPC n t= | org-todo-list |
240| =SPC o A= | org-agenda |
241| =SPC X= | org-capture |
242| =SPC m p p= | org-priority |
243| =SPC m d s= | org-schedule |
244| =TAB= | org-cycle |
245| =SHIFT TAB= | Collapse/open all headings in buffer |
246| =M-q= | Format/wrap current section |
247| =M-Left/Right= | Demote/promote current heading |
248| =M-Down/Up= | Shift current heading section down/up |
249
250**** Org-Publish
251
252Org includes a [[https://orgmode.org/manual/Publishing.html][publishing
253management system]] by default that allows you to export org files to
254Org, iCalendar, HTML, LaTex, Markdown, ODT, and Plain Text. Most of
255these can be exported into another buffer and opened, or simply to an
256external file.
257
258While inside an org file, simply run =SPC m e= or
259=M-x org-export-dispatch= to open the export menu. This menu will show
260all options and ask you to select an option. If you want to export to
261HTML, simply press =h= and then =H= (As HTML buffer), =h= (As HTML
262file), or =o= (As HTML file and open).
263
264**** Projects
265
266Some publishing options are easier with a defined project in Emacs. To
267create a project within Emacs, I use two methods:
268
2691. Add the project via the projectile command =SPC p a=. Does not always
270 work for me.
2712. Add an empty =.projectile= file in the project root.
272
273Once a project has been created, you can create custom publishing
274actions within your =~/.doom.d/config.el= file. For example, here's a
275test project I created to try and convert this blog to org-mode
276recently.
277
278#+begin_src lisp
279;; org-publish
280(require 'ox-publish)
281
282(defun my/org-sitemap-date-entry-format (entry style project) "Format ENTRY in
283 org-publish PROJECT Sitemap format ENTRY ENTRY STYLE format that includes
284 date." (let ((filename (org-publish-find-title entry project))) (if (= (length
285 filename) 0) (format "*%s*" entry) (format "{{{timestamp(%s)}}}
286 [[file:%s][%s]]" (format-time-string "%Y-%m-%d" (org-publish-find-date entry
287 project)) entry filename))))
288
289(setq org-export-global-macros '(("timestamp" . "@@html:<time datetime='[$1]'
290 class='timestamp'>[$1]</time>@@")))
291
292(setq org-publish-project-alist
293 `(("blog"
294 :base-directory "~/Source/cleberg.net/"
295 :base-extension "org"
296 :recursive t
297 :publishing-directory "~/Source/cleberg.net/public/"
298 :publishing-function org-html-publish-to-html
299 ;; HTML5
300 :html-doctype "html5"
301 :html-html5-fancy t
302 ;; Disable some Org's HTML defaults
303 :html-head-include-scripts nil
304 :html-head-include-default-style nil
305 :section-numbers nil
306 :with-title nil
307 ;; Sitemap
308 :auto-sitemap t
309 :sitemap-title: "Sitemap"
310 :sitemap-sort-files anti-chronologically
311 ; :sitemap-function my/org-sitemap-date-entry-format
312 ;; Customize HTML output
313 :html-divs ((preamble "header" "preamble")
314 (content "main" "content")
315 (postamble "footer" "postamble"))
316 :html-head "<meta name='theme-color' content='#111' media='(prefers-color-scheme: dark)'>
317 <meta name='theme-color' content='#fff' media='(prefers-color-scheme: light)'>
318 <link rel='stylesheet' href='/syntax-theme-dark.css' media='(prefers-color-scheme: dark)'>
319 <link rel='stylesheet' href='/syntax-theme-light.css' media='(prefers-color-scheme: light)'>
320 <link rel='stylesheet' href='/styles.css' type='text/css'>"
321 :html-preamble "<nav class='site-nav' aria-label='site-nav' role='navigation'>
322 <ul>
323 <li><a href='/'>Home</a></li>
324 <li><a href='/blog/'>Blog</a></li>
325 <li><a href='/services/'>Services</a></li>
326 <li><a href='/wiki/'>Wiki</a></li>
327 </ul></nav>
328 <h1>%t</h1>
329 <time datetime='%d'>%d</time>"
330 :html-postamble "
331 <p>Last build: %T</p>
332 <p>Created with %c</p>"
333 )
334
335 ("static"
336 :base-directory "~/Source/cleberg.net/static/"
337 :base-extension "css\\|txt\\|jpg\\|gif\\|png"
338 :recursive t
339 :publishing-directory "~/Source/cleberg.net/public/"
340 :publishing-function org-publish-attachment)
341
342 ("cleberg.net" :components ("blog" "static"))))
343#+end_src
344
345** General Thoughts
346
347I have enjoyed Doom Emacs (far more than GNU Emacs) and will likely
348continue to use it as my main editor for the time being. Org-Mode is
349certainly the largest factor here, as I far prefer it over Markdown due
350to its inherent features and detailed markup options. However, working
351with org-mode on iOS has been a pain and I will have to see if there's
352an easier way to resolve those issues or if going back to separate
353Markdown, Reminders, and Calendar apps is easier to work with than an
354all-in-one org solution.
content/blog/2024-03-15-self-hosting-ddns-updater.org → content/blog/2024-03-15-self-hosting-ddns-updater.md renamed +84 −77
@@ -1,77 +1,83 @@
1#+title: Self-Hosting DDNS Updater 1+++
2#+date: <2024-03-15 Fri 14:49:59> 2date = 2024-03-15
3#+description: A guide to self-hosting the DDNS Updater container. 3title = "Self-Hosting DDNS Updater"
4description = ""
5draft = false
6+++
4 7
5#+caption: DDNS Updater Web View 8![DDNS Updater Web
6[[https://img.cleberg.net/blog/20240315-ddns-updater/ddns.png]] 9View](https://img.cleberg.net/blog/20240315-ddns-updater/ddns.png)
7 10
8[[https://github.com/qdm12/ddns-updater][DDNS Updater]] is a program to 11[DDNS Updater](https://github.com/qdm12/ddns-updater) is a program to
9keep DNS A and/or AAAA records updated for multiple DNS providers. 12keep DNS A and/or AAAA records updated for multiple DNS providers.
10 13
11If you've read any of my other posts, you'll notice that I have been 14If you\'ve read any of my other posts, you\'ll notice that I have been
12searching for and using a few different DDNS updating solutions for 15searching for and using a few different DDNS updating solutions for
13years. You'll also notice that I love any projects that offer a Docker 16years. You\'ll also notice that I love any projects that offer a Docker
14Compose solution. 17Compose solution.
15 18
16Luckily, DDNS Upater fits both of these preferences. 19Luckily, DDNS Upater fits both of these preferences.
17 20
18** Installation 21## Installation
19 22
20To get started, always make sure to review the project's 23To get started, always make sure to review the project\'s
21[[https://github.com/qdm12/ddns-updater/blob/master/README.md][README]]. 24[README](https://github.com/qdm12/ddns-updater/blob/master/README.md).
22I'll be documenting my steps below, but they may have changed by the 25I\'ll be documenting my steps below, but they may have changed by the
23time you read this. 26time you read this.
24 27
25The first step is to set up the directories and files required for the 28The first step is to set up the directories and files required for the
26project. 29project.
27 30
28#+begin_src sh 31```sh
29mkdir ~/ddns-updater 32mkdir ~/ddns-updater
30mkdir ~/ddns-updater/data 33mkdir ~/ddns-updater/data
31touch ~/ddns-updater/data/config.json 34touch ~/ddns-updater/data/config.json
32#+end_src 35```
33 36
34*** Configuration 37### Configuration
35 38
36The main configuration you need to update is the =data/config.json= 39The main configuration you need to update is the
37file. There is a large list of supported providers in the README, but 40`data/config.json` file. There is a large list of supported
38I'm going to use Cloudflare in this example. 41providers in the README, but I\'m going to use Cloudflare in this
42example.
39 43
40#+begin_src sh 44```sh
41nano ~/ddns-updater/data/config.json 45nano ~/ddns-updater/data/config.json
42#+end_src 46```
43 47
44When setting up the configuration for Cloudflare, you'll need the 48When setting up the configuration for Cloudflare, you\'ll need the
45following: 49following:
46 50
47- Required Parameters 51- Required Parameters
48 - ="zone_identifier"= is the Zone ID of your site from the domain 52 - `"zone_identifier"` is the Zone ID of your site from
49 overview page 53 the domain overview page
50 - ="host"= is your host and can be ="@"=, a subdomain or the wildcard 54 - `"host"` is your host and can be `"@"`, a
51 ="*"=. See 55 subdomain or the wildcard `"*"`. See [this issue
52 [[https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949][this 56 comment for
53 issue comment for context]]. 57 context](https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949).
54 - ="ttl"= integer value for record TTL in seconds (specify 1 for 58 - `"ttl"` integer value for record TTL in seconds
55 automatic) 59 (specify 1 for automatic)
56 - One of the following 60 - One of the following ([how to find API
57 ([[https://developers.cloudflare.com/fundamentals/api/get-started/][how 61 keys](https://developers.cloudflare.com/fundamentals/api/get-started/)):
58 to find API keys]]): 62 - Email `"email"` and Global API Key
59 - Email ="email"= and Global API Key ="key"= 63 `"key"`
60 - User service key ="user_service_key"= 64 - User service key `"user_service_key"`
61 - API Token ="token"=, configured with DNS edit permissions for your 65 - API Token `"token"`, configured with DNS edit
62 DNS name's zone 66 permissions for your DNS name\'s zone
63- Optional Parameters 67- Optional Parameters
64 - ="proxied"= can be set to =true= to use the proxy services of 68 - `"proxied"` can be set to `true` to use
65 Cloudflare 69 the proxy services of Cloudflare
66 - ="ip_version"= can be =ipv4= (A records), or =ipv6= (AAAA records) 70 - `"ip_version"` can be `ipv4` (A records),
67 or =ipv4 or ipv6= (update one of the two, depending on the public 71 or `ipv6` (AAAA records) or
68 ip found). It defaults to =ipv4 or ipv6=. 72 `ipv4 or ipv6` (update one of the two, depending on
69 - ="ipv6_suffix"= is the IPv6 interface identifier suffix to use. It 73 the public ip found). It defaults to `ipv4 or ipv6`.
70 can be for example =0:0:0:0:72ad:8fbb:a54e:bedd/64=. If left empty, 74 - `"ipv6_suffix"` is the IPv6 interface identifier
71 it defaults to no suffix and the raw public IPv6 address obtained is 75 suffix to use. It can be for example
72 used in the record updating. 76 `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it
73 77 defaults to no suffix and the raw public IPv6 address obtained
74#+begin_src conf 78 is used in the record updating.
79
80``` conf
75{ 81{
76 "settings": [ 82 "settings": [
77 { 83 {
@@ -87,12 +93,12 @@ following:
87 } 93 }
88 ] 94 ]
89} 95}
90#+end_src 96```
91 97
92Once you have configured the provider of your choice, correct the file 98Once you have configured the provider of your choice, correct the file
93and directory permissions and ownership. 99and directory permissions and ownership.
94 100
95#+begin_src sh 101```sh
96cd ~/ddns_updater 102cd ~/ddns_updater
97# Owned by user ID of Docker container (1000) 103# Owned by user ID of Docker container (1000)
98chown -R 1000 data 104chown -R 1000 data
@@ -100,18 +106,18 @@ chown -R 1000 data
100chmod 700 data 106chmod 700 data
101# read access only 107# read access only
102chmod 400 data/config.json 108chmod 400 data/config.json
103#+end_src 109```
104 110
105*** Docker Compose 111### Docker Compose
106 112
107After creating the project structure, let's create the 113After creating the project structure, let\'s create the
108=docker-compose.yml= file. 114`docker-compose.yml` file.
109 115
110#+begin_src sh 116```sh
111nano ~/ddns_-pdater/docker-compose.yml 117nano ~/ddns_-pdater/docker-compose.yml
112#+end_src 118```
113 119
114#+begin_src config 120``` config
115version: "3.7" 121version: "3.7"
116services: 122services:
117 ddns-updater: 123 ddns-updater:
@@ -147,20 +153,20 @@ services:
147 - LOG_CALLER=hidden 153 - LOG_CALLER=hidden
148 - SHOUTRRR_ADDRESSES= 154 - SHOUTRRR_ADDRESSES=
149 restart: always 155 restart: always
150#+end_src 156```
151 157
152After configuring your preferences in the =docker-compose.yml=, launch 158After configuring your preferences in the
153the container. 159`docker-compose.yml`, launch the container.
154 160
155#+begin_src sh 161```sh
156cd ~/ddns-updater 162cd ~/ddns-updater
157sudo docker-compose up -d 163sudo docker-compose up -d
158#+end_src 164```
159 165
160If you've launched this on your local machine, you can launch 166If you\'ve launched this on your local machine, you can launch
161=localhost:8097= in your browser to see the results. 167`localhost:8097` in your browser to see the results.
162 168
163*** Nginx Reverse Proxy 169### Nginx Reverse Proxy
164 170
165If you launched this service on a server, other machine, or just want to 171If you launched this service on a server, other machine, or just want to
166access it remotely via a domain name, you can use Nginx as a reverse 172access it remotely via a domain name, you can use Nginx as a reverse
@@ -168,13 +174,13 @@ proxy to expose the service publicly.
168 174
169Start by creating the Nginx configuration file. 175Start by creating the Nginx configuration file.
170 176
171#+begin_src sh 177```sh
172sudo nano /etc/nginx/sites-available/ddns 178sudo nano /etc/nginx/sites-available/ddns
173#+end_src 179```
174 180
175Here's a basic example that should work properly. 181Here\'s a basic example that should work properly.
176 182
177#+begin_src conf 183``` conf
178server { 184server {
179 # If using 443, remember to include your ssl_certificate 185 # If using 443, remember to include your ssl_certificate
180 # and ssl_certificate_key 186 # and ssl_certificate_key
@@ -190,12 +196,12 @@ server {
190 # see the full example below if necessary 196 # see the full example below if necessary
191 } 197 }
192} 198}
193#+end_src 199```
194 200
195Here's a full example that uses my Authelia authentication service to 201Here\'s a full example that uses my Authelia authentication service to
196require authentication before someone can access the web page. 202require authentication before someone can access the web page.
197 203
198#+begin_src conf 204``` conf
199server { 205server {
200 if ($host ~ ^[^.]+\.example\.com$) { 206 if ($host ~ ^[^.]+\.example\.com$) {
201 return 301 https://$host$request_uri; 207 return 301 https://$host$request_uri;
@@ -302,13 +308,14 @@ server {
302 # real_ip_recursive on; 308 # real_ip_recursive on;
303 } 309 }
304} 310}
305#+end_src 311```
306 312
307When complete, simply link the file and restart the web server. 313When complete, simply link the file and restart the web server.
308 314
309#+begin_src sh 315```sh
310sudo ln -s /etc/nginx/sites-available/ddns /etc/nginx/sites-enabled/ddns 316sudo ln -s /etc/nginx/sites-available/ddns /etc/nginx/sites-enabled/ddns
311sudo systemctl restart nginx.service 317sudo systemctl restart nginx.service
312#+end_src 318```
313 319
314Your ddns-updater service will now be available via =ddns.example.com=! 320Your ddns-updater service will now be available via
321`ddns.example.com`!
content/blog/2024-03-29-org-blog.org → content/blog/2024-03-29-org-blog.md renamed +113 −95
@@ -1,75 +1,87 @@
1#+title: Blogging in Org-Mode 1+++
2#+date: <2024-03-29 Fri 09:00:00> 2date = 2024-03-29
3#+description: A guide to blogging with org-mode, no third-party tools required. 3title = "Blogging in Org-Mode"
4#+filetags: :dev: 4description = ""
5#+slug: org-blog 5draft = false
6 6+++
7First and foremost, apologies to those who subscribe via RSS as I know that my 7
8feed duplicated itself when I moved this blog over to org-mode last night. 8First and foremost, apologies to those who subscribe via RSS as I know
9 9that my feed duplicated itself when I moved this blog over to org-mode
10This post focuses specifically on the configuration and tools I use to blog from 10last night.
11Emacs with Org-Mode and does not focus on Emacs or Org-Mode themselves. Refer to 11
12the post I wrote about [[/blog/doom-emacs-org-mode.html][Doom Emacs & Org-Mode]] for more information about my base 12This post focuses specifically on the configuration and tools I use to
13Emacs configuration. 13blog from Emacs with Org-Mode and does not focus on Emacs or Org-Mode
14 14themselves. Refer to the post I wrote about [Doom Emacs &
15* Weblorg 15Org-Mode](file:///blog/doom-emacs-org-mode.html) for more information
16 16about my base Emacs configuration.
17The first step in blogging with Org-Mode is to choose a method to convert the 17
18source files to HTML and publish them. The Worg site maintains a nice list of 18# Weblorg
19[[https://orgmode.org/worg/org-blog-wiki.html][Blogs and Wikis with Org]], but the tools are inevitably different and 19
20opinionated, so you'll need to find what works for you. 20The first step in blogging with Org-Mode is to choose a method to
21 21convert the source files to HTML and publish them. The Worg site
22I tried using Jekyll, Hugo, ox-hugo, Nikola, Blorg, org-static-blog, and the 22maintains a nice list of [Blogs and Wikis with
23native org-publish functions before finally settling on Weblorg. For one reason 23Org](https://orgmode.org/worg/org-blog-wiki.html), but the tools are
24or another, the other solutions were a drastic step down from my previous 24inevitably different and opinionated, so you\'ll need to find what works
25workflow that used [[https://www.getzola.org/][Zola]] with Markdown content. 25for you.
26 26
27[[https://github.com/emacs-love/weblorg][Weblorg]] is a static site generator for [[https://orgmode.org/][org-mode]], built for use within [[https://www.gnu.org/software/emacs/][Emacs]]. 27I tried using Jekyll, Hugo, ox-hugo, Nikola, Blorg, org-static-blog, and
28Since it's written in Emacs Lisp, there's no need to install other languages or 28the native org-publish functions before finally settling on Weblorg. For
29frameworks to get started. More than that, you can write in any editor you 29one reason or another, the other solutions were a drastic step down from
30please and simply invoke the Emacs build process with the =--script= parameter 30my previous workflow that used [Zola](https://www.getzola.org/) with
31instead of requiring you to blog inside Emacs. 31Markdown content.
32 32
33** Installation 33[Weblorg](https://github.com/emacs-love/weblorg) is a static site
34 34generator for [org-mode](https://orgmode.org/), built for use within
35The [[https://emacs.love/weblorg/doc/index.html][Getting Started]] page details broad installation requirements. I am using 35[Emacs](https://www.gnu.org/software/emacs/). Since it\'s written in
36Doom Emacs on macOS, which requires you to add the package to the 36Emacs Lisp, there\'s no need to install other languages or frameworks to
37=~/.doom.d/packages.el= file and configure the =publish.el= file slightly 37get started. More than that, you can write in any editor you please and
38differently. 38simply invoke the Emacs build process with the `--script`
39 39parameter instead of requiring you to blog inside Emacs.
40To start, add the =htmlize= and =weblorg= packages to Doom, sync the changes, 40
41and reload. 41## Installation
42 42
43#+begin_src sh 43The [Getting Started](https://emacs.love/weblorg/doc/index.html) page
44details broad installation requirements. I am using Doom Emacs on macOS,
45which requires you to add the package to the
46`~/.doom.d/packages.el` file and configure the
47`publish.el` file slightly differently.
48
49To start, add the `htmlize` and `weblorg` packages
50to Doom, sync the changes, and reload.
51
52```sh
44nano ~/.doom.d/packages.el 53nano ~/.doom.d/packages.el
45#+end_src 54```
46 55
47#+begin_src lisp 56```lisp
48(package! htmlize) 57(package! htmlize)
49(package! weblorg) 58(package! weblorg)
50#+end_src 59```
51 60
52#+begin_src sh 61```sh
53doom sync 62doom sync
54#+end_src 63```
55 64
56Either re-open Emacs or hit =SPC h r r= to reload the changes. 65Either re-open Emacs or hit `SPC h r r` to reload the
66changes.
57 67
58** Configuration 68## Configuration
59 69
60Now that I've installed weblorg, I need to configure the project. I'll start by 70Now that I\'ve installed weblorg, I need to configure the project. I\'ll
61navigating to my site's source code and creating a =publish.el= file. 71start by navigating to my site\'s source code and creating a
72`publish.el` file.
62 73
63#+begin_src sh 74```sh
64cd ~/Source/cleberg.net && nano publish.el 75cd ~/Source/cleberg.net && nano publish.el
65#+end_src 76```
66 77
67Since I'm using Doom, Emacs will not automatically load the packages I need 78Since I\'m using Doom, Emacs will not automatically load the packages I
68later in the build process. To compensate, my =publish.el= file needs to 79need later in the build process. To compensate, my
69explicitly tell Emacs where Doom stores the =htmlize=, =weblorg=, and 80`publish.el` file needs to explicitly tell Emacs where Doom
70=templatel= packages. 81stores the `htmlize`, `weblorg`, and
82`templatel` packages.
71 83
72#+begin_src lisp 84```lisp
73;; explicity load packages since I'm using Doom Emacs 85;; explicity load packages since I'm using Doom Emacs
74(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/emacs-htmlize") 86(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/emacs-htmlize")
75(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/weblorg") 87(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/weblorg")
@@ -170,19 +182,21 @@ explicitly tell Emacs where Doom stores the =htmlize=, =weblorg=, and
170 182
171;; fire the engine and export all the files declared in the routes above 183;; fire the engine and export all the files declared in the routes above
172(weblorg-export) 184(weblorg-export)
173#+end_src 185```
174 186
175* Project 187# Project
176 188
177** Structure 189## Structure
178 190
179The project structure for weblorg is highly customizable and the main 191The project structure for weblorg is highly customizable and the main
180restriction is that the =publish.el= file must point to the correct paths. 192restriction is that the `publish.el` file must point to the
193correct paths.
181 194
182For my blog, I prefer to keep the blog content out of the top-level directory. 195For my blog, I prefer to keep the blog content out of the top-level
183This results in the following structure (shortened for brevity): 196directory. This results in the following structure (shortened for
197brevity):
184 198
185#+begin_src txt 199``` txt
186.build/ 200.build/
187content/ 201content/
188 blog/ 202 blog/
@@ -206,54 +220,58 @@ theme/
206 wiki.html 220 wiki.html
207build.sh 221build.sh
208publish.el 222publish.el
209#+end_src 223```
210 224
211This is simply my preferred structure and you can alter it to fit your needs. 225This is simply my preferred structure and you can alter it to fit your
212The key here really is that you can customize at will, as long as the 226needs. The key here really is that you can customize at will, as long as
213=publish.el= file matches. 227the `publish.el` file matches.
214 228
215** Build & Deploy 229## Build & Deploy
216 230
217Once you're content with the status of the project, you're ready to build and 231Once you\'re content with the status of the project, you\'re ready to
218deploy the blog. 232build and deploy the blog.
219 233
220My process utilizes a =build.sh= script that combines the steps I take every 234My process utilizes a `build.sh` script that combines the
221time. 235steps I take every time.
222 236
223#+begin_src sh 237```sh
224touch build.sh && chmod +x build.sh && nano build.sh 238touch build.sh && chmod +x build.sh && nano build.sh
225#+end_src 239```
226 240
227Within this script, I do the following: 241Within this script, I do the following:
228 242
2291. Remove any files within the =.build= directory that I use to store published 2431. Remove any files within the `.build` directory that I use
230 files. 244 to store published files.
2312. Set the environment variable to =prod= to ensure the =base_url= matches my 2452. Set the environment variable to `prod` to ensure the
232 configuration in =publish.el=. 246 `base_url` matches my configuration in
2333. Build the site with Emacs & =publish.el=. 247 `publish.el`.
2344. Use =scp= to copy files to my site's public directory on my server. 2483. Build the site with Emacs & `publish.el`.
2494. Use `scp` to copy files to my site\'s public directory on
250 my server.
235 251
236#+begin_src sh 252```sh
237rm -rf .build/* && \ 253rm -rf .build/* && \
238ENV=prod emacs --script publish.el && \ 254ENV=prod emacs --script publish.el && \
239scp -r .build/* ubuntu:/var/www/cleberg.net/ 255scp -r .build/* ubuntu:/var/www/cleberg.net/
240#+end_src 256```
241 257
242*** Time to Build 258### Time to Build
243 259
244My only current complaints are: 260My only current complaints are:
245 261
2461. Errors messages are not helpful. It takes work to determine what the error is 2621. Errors messages are not helpful. It takes work to determine what the
247 and where it's coming from. I generally have to sit and watch the build 263 error is and where it\'s coming from. I generally have to sit and
248 process to see the file that weblorg pubslishes right before the error 264 watch the build process to see the file that weblorg pubslishes
249 occurred. 265 right before the error occurred.
2502. The build process re-builds every single file on each run, which takes a long 2662. The build process re-builds every single file on each run, which
251 time for a blog of my size. See below for the last time I measured. 267 takes a long time for a blog of my size. See below for the last time
268 I measured.
252 269
253#+begin_src sh 270```sh
254> time ./build.sh 271> time ./build.sh
255 272
256./build.sh 35.46s user 0.59s system 85% cpu 41.965 total 273./build.sh 35.46s user 0.59s system 85% cpu 41.965 total
257#+end_src 274```
258 275
259Overall, I have thoroughly enjoyed using weblog and will continue to use it going forward until I find something better. 276Overall, I have thoroughly enjoyed using weblog and will continue to use
277it going forward until I find something better.
content/blog/2024-04-06-convert-onenote-to-markdown.md added +115
@@ -0,0 +1,115 @@
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,
9such as Markdown or Org-Mode, you\'re in luck. I use a solution that
10doesn\'t require other programs, such as Evernote or Notion. Personally,
11I used this solution on a managed corporate laptop that doesn\'t allow
12installation of other programs like 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
19(`.docx`):
20
211. Open OneNote desktop.
222. Select `File` and then `Export`.
233. Select the scope of content to export, such as `Tab` or
24 `Page`.
254. Name and save the file in an easy to remember location. I recommend
26 your Downloads or Desktop folder.
27
28See below for a visual walkthrough of the export process.
29
30![OneNote
31Notebook](https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/onenote.png)
32
33![OneNote Section Export
34Options](https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/export.png)
35
36![Exported Word
37Document](https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/word.png)
38
39# Download Pandoc
40
41Start by downloading Pandoc from their [GitHub
42releases](https://github.com/jgm/pandoc/releases) page. I cannot install
43`.msi` files on my corporate laptop, so I downloaded the
44`pandoc-3.1.12.3-windows-x86_64.zip` file, which contains a
45simple `.exe` file that you do not need to install - you will
46simply run it from the command line below.
47
48Once downloaded, unzip the archive and move the `pandoc.exe`
49file to the same folder where your Word documents were saved above. If
50you prefer, you can move this file to an easier location, such as
51`C:\Users\youruser\Downloads`.
52
53# Convert Word to Markdown
54
55In this example, I will be converting the Word documents to Markdown,
56but Pandoc supports [a ton of different formats for
57conversion](https://github.com/jgm/pandoc?tab=readme-ov-file#the-universal-markup-converter).
58Choose the format you prefer and then modify the following commands as
59needed.
60
61To perform the conversion, open the Command Prompt. If you can\'t find
62it, open the start menu and search for it.
63
64Within the command prompt, navigate to the directory where you stored
65the `pandoc.exe` file and the Word documents.
66
67```ps1
68cd "C:\Users\yourusername\Downloads"
69```
70
71You can verify that you\'re in the correct directory with the
72`dir` command.
73
74```ps1
75dir
76```
77
78Once you have verified that you have the command prompt open in the
79correct directory with the `pandoc.exe` and the Word
80documents, you can run the following loop to convert all Word documents
81to Markdown.
82
83```ps1
84for %f in (*.docx) do (pandoc.exe --extract-media=. --wrap=preserve "%f" -o "%f.md")
85```
86
87This loop will perform the following actions:
88
891. Find all documents matching the pattern `*.docx`, which
90 means all Word documents ending with that file extension.
912. Iterate through all files found in step 1.
923. For each file, perform the pandoc command.
934. Within the pandoc command, `--extract-media` saves all
94 media found in the files to the current folder, with pandoc
95 automatically creating a `media` subfolder to hold all
96 images.
975. Within the pandoc command, `--wrap=preserve` will attempt
98 to prseerve the wrapping from the source document.
996. Within the pandoc command, the final step is to specify the output
100 path with `-o`. This option adds the `.md`
101 file extension to recognize the output files as Markdown files.
102
103![Pandoc
104Conversion](https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/command.png)
105
106If you want to export to another format, simply specify the
107`-f=`/`--from=` and `-t=`/`--to=` options.
108
109For example, you can convert the Word document to org-mode. You can also
110convert to one format and subsequently convert to other formats as
111needed.
112
113```ps1
114pandoc.exe -f docx -t org file.docx
115```
content/blog/2024-04-06-convert-onenote-to-markdown.org deleted −102
@@ -1,102 +0,0 @@
1#+title: Convert OneNote to Markdown (or Org-Mode)
2#+date: <2024-04-06 Sat 10:00:00>
3#+description: Learn how to convert OneNote pages or tabs to another format, such as Markdown or Org-Mode.
4#+filetags: :dev:
5#+slug: convert-onenote-to-markdown
6
7If you're looking to convert your OneNote content to another format, such as
8Markdown or Org-Mode, you're in luck. I use a solution that doesn't require
9other programs, such as Evernote or Notion. Personally, I used this solution on
10a managed corporate laptop that doesn't allow installation of other programs
11like these.
12
13This solution uses OneNote and Pandoc on Windows 10.
14
15* Export OneNote Content to Word
16
17To start, export any pages or tabs from OneNote to the Word format (=.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 your
23 Downloads or Desktop folder.
24
25See below for a visual walkthrough of the export process.
26
27#+caption: OneNote Notebook
28[[https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/onenote.png]]
29
30#+caption: OneNote Section Export Options
31[[https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/export.png]]
32
33#+caption: Exported Word Document
34[[https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/word.png]]
35
36* Download Pandoc
37
38Start by downloading Pandoc from their [[https://github.com/jgm/pandoc/releases][GitHub releases]] page. I cannot install
39=.msi= files on my corporate laptop, so I downloaded the
40=pandoc-3.1.12.3-windows-x86_64.zip= file, which contains a simple =.exe= file
41that you do not need to install - you will simply run it from the command line
42below.
43
44Once downloaded, unzip the archive and move the =pandoc.exe= file to the same
45folder where your Word documents were saved above. If you prefer, you can move
46this file to an easier location, such as =C:\Users\youruser\Downloads=.
47
48* Convert Word to Markdown
49
50In this example, I will be converting the Word documents to Markdown, but Pandoc
51supports [[https://github.com/jgm/pandoc?tab=readme-ov-file#the-universal-markup-converter][a ton of different formats for conversion]]. Choose the format you prefer
52and then modify the following commands as needed.
53
54To perform the conversion, open the Command Prompt. If you can't find it, open
55the start menu and search for it.
56
57Within the command prompt, navigate to the directory where you stored the
58=pandoc.exe= file and the Word documents.
59
60#+begin_src cli
61cd "C:\Users\yourusername\Downloads"
62#+end_src
63
64You can verify that you're in the correct directory with the =dir= command.
65
66#+begin_src cli
67dir
68#+end_src
69
70Once you have verified that you have the command prompt open in the correct
71directory with the =pandoc.exe= and the Word documents, you can run the
72following loop to convert all Word documents to Markdown.
73
74#+begin_src cli
75for %f in (*.docx) do (pandoc.exe --extract-media=. --wrap=preserve "%f" -o "%f.md")
76#+end_src
77
78This loop will perform the following actions:
79
801. Find all documents matching the pattern =*.docx=, which means all Word
81 documents ending with that file extension.
822. Iterate through all files found in step 1.
833. For each file, perform the pandoc command.
844. Within the pandoc command, =--extract-media= saves all media found in the
85 files to the current folder, with pandoc automatically creating a =media=
86 subfolder to hold all images.
875. Within the pandoc command, =--wrap=preserve= will attempt to prseerve the
88 wrapping from the source document.
896. Within the pandoc command, the final step is to specify the output path with
90 =-o=. This option adds the =.md= file extension to recognize the output files
91 as Markdown files.
92
93#+caption: Pandoc Conversion
94[[https://img.cleberg.net/blog/20240406-convert-onenote-to-markdown/command.png]]
95
96If you want to export to another format, simply specify the =-f=/=--from= and =-t=/=--to= options.
97
98For example, you can convert the Word document to org-mode. You can also convert to one format and subsequently convert to other formats as needed.
99
100#+begin_src cli
101pandoc.exe -f docx -t org file.docx
102#+end_src
content/blog/2024-04-08-docker-local-web-server.md added +149
@@ -0,0 +1,149 @@
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
9to observe the changes.
10
11```sh
12python3 -m http.server
13```
14
15However, this approach has its limitations. For example, this approach
16does not enable logging or access controls. You also need to customize
17`SimpleHTTPServer` if you have advanced needs from your web
18server.
19
20So, I went to find an alternative that is almost as easy and far more
21extensible and found Docker Desktop to be a suitable replacement.
22
23# Docker Desktop
24
25## Installation
26
27[Docker Desktop](https://www.docker.com/products/docker-desktop/) is a
28desktop GUI for the phenomenal Docker container software. This allows
29you to manage containers, images, volumes, environments, and extensions
30via an easy-to-use GUI.
31
32To install, open the link above and click the `Download`
33button for your platform. I\'m going through this process on an M2
34Macbook, so I downloaded the Mac - Apple Chip version.
35
36Open the installer and follow the installation process until the
37application finishes the installation process.
38
39![Docker Desktop on
40macOS](https://img.cleberg.net/blog/20240408-docker-local-web-server/docker-desktop.png)
41
42## Creating an Nginx Container
43
44I prefer to use the command line to create containers, so the following
45commands will be input via the terminal.
46
47The following command will create a container, using the
48`nginx` image:
49
501. `-d`: Run this container as a daemon (detach)
512. `-p`: Allocate a port in the format
52 `<external>:<internal>`
533. `-i`: Keep STDIN open even if not attached
544. `-t`: Allocate a pseudo-TTY
555. `-p`: Allocate a port in the format
56 `<external>:<internal>`
576. `--rm`: Remove the container once it\'s done running
58
59```sh
60docker run -it --rm -d -p 8000:80 --name web nginx
61```
62
63You can navigate to <http://localhost:8000> to see the resulting page.
64
65![Default Nginx
66Container](https://img.cleberg.net/blog/20240408-docker-local-web-server/default-container.png)
67
68## Customizing the Nginx Container
69
70Now that I have a container running the Nginx web server, I need to link
71some volumes so that I can modify the site configuration and provide the
72web files to serve.
73
74Let\'s start with the new command, which adds two volumes:
75
761. `<your_content>:/usr/share/nginx/html`: This is the
77 directory where you will provide the web pages for the server to
78 serve.
792. `<your_config>:/etc/nginx/conf.d/default.conf`: This is
80 the Nginx configuration file for your site.
81
82To see the updates, you can delete the previous container in the GUI or
83run `docker stop web` to stop the container. Once stopped,
84you can run the new `docker run` command below.
85
86```sh
87docker 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
88```
89
90Here\'s an example of my development configuration file.
91
92``` conf
93# nginx-config.conf
94server {
95 server_name cleberg.net www.cleberg.net;
96
97 root /usr/share/nginx/html;
98 index index.html;
99 autoindex on;
100
101 access_log /var/log/nginx/cleberg.net.access.log;
102 error_log /var/log/nginx/cleberg.net.error.log;
103
104 location / {
105 try_files $uri $uri/ =404;
106 }
107
108 listen [::]:80;
109 listen 80;
110}
111```
112
113![](https://img.cleberg.net/blog/20240408-docker-local-web-server/custom-container.png)
114
115# Customizing Deployment Actions
116
117I am currently blogging with [weblorg](https://emacs.love/weblorg/),
118which uses a custom `publish.el` file to build the static
119site. Within this file, I have configured my deployment process to check
120for the `ENV` variable in thesh and if it\'s set to
121`prod`, the script will set the base URLs to
122`https://cleberg.net`. If not, it sets the base URLs to
123`localhost:8000` (which matches the port used in the
124container above).
125
126Therefore, I have modified my `build.sh` script to build with
127`localhost` URLs if `ENV` is not set to
128`prod`. It also prevents the build process from sending the
129built files to the production web server.
130
131```sh
132#!/bin/bash
133
134if [ "$ENV" == "prod" ]; then
135 echo "Environment = Production" && \
136 rm -rf .build/* && \
137 emacs --script publish.el && \
138 scp -r .build/* ubuntu:/var/www/cleberg.net/
139else
140 echo "Environment = Development" && \
141 rm -rf .build/* && \
142 emacs --script publish.el
143fi
144```
145
146You can modify the container in numerous ways and this approach allows
147you to create complex scenarios for your web development purposes. I
148highly recommend switching over to a container-based approach for
149simple, local web development.
content/blog/2024-04-08-docker-local-web-server.org deleted −137
@@ -1,137 +0,0 @@
1#+title: Using Docker Desktop to Create a Development Web Server
2#+date: <2024-04-08 Mon 10:05:45>
3#+description: Learn how to launch local web servers with Docker Desktop.
4#+filetags: dev, nginx
5#+slug: docker-local-web-server
6
7When developing websites locally, I often use a simple Python web server to
8observe the changes.
9
10#+begin_src shell
11python3 -m http.server
12#+end_src
13
14However, this approach has its limitations. For example, this approach does not
15enable logging or access controls. You also need to customize =SimpleHTTPServer=
16if you have advanced needs from your web server.
17
18So, I went to find an alternative that is almost as easy and far more extensible
19and found Docker Desktop to be a suitable replacement.
20
21* Docker Desktop
22
23** Installation
24
25[[https://www.docker.com/products/docker-desktop/][Docker Desktop]] is a desktop GUI for the phenomenal Docker container software.
26This allows you to manage containers, images, volumes, environments, and
27extensions via 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 downloaded the
31Mac - Apple Chip version.
32
33Open the installer and follow the installation process until the application
34finishes 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 commands
42will 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 shell
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 some
65volumes so that I can modify the site configuration and provide the web files to
66serve.
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 you will
71 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 run
76=docker stop web= to stop the container. Once stopped, you can run the new
77=docker run= command below.
78
79#+begin_src shell
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]], which uses a custom =publish.el= file to
111build the static site. Within this file, I have configured my deployment process
112to check for the =ENV= variable in the shell and if it's set to =prod=, the
113script will set the base URLs to =https://cleberg.net=. If not, it sets the base
114URLs to =localhost:8000= (which matches the port used in the container above).
115
116Therefore, I have modified my =build.sh= script to build with =localhost= URLs
117if =ENV= is not set to =prod=. It also prevents the build process from sending
118the built files to the production web server.
119
120#+begin_src shell
121#!/bin/bash
122
123if [ "$ENV" == "prod" ]; then
124 echo "Environment = Production" && \
125 rm -rf .build/* && \
126 emacs --script publish.el && \
127 scp -r .build/* ubuntu:/var/www/cleberg.net/
128else
129 echo "Environment = Development" && \
130 rm -rf .build/* && \
131 emacs --script publish.el
132fi
133#+end_src
134
135You can modify the container in numerous ways and this approach allows you to
136create complex scenarios for your web development purposes. I highly recommend
137switching over to a container-based approach for simple, local web development.
content/blog/2024-04-18-mu4e.org → content/blog/2024-04-18-mu4e.md renamed +127 −104
@@ -1,77 +1,86 @@
1#+title: Email in Doom Emacs with Mu4e on macOS 1+++
2#+date: <2024-04-18 Thu 10:49:00> 2date = 2024-04-18
3#+description: A short tutorial on configuring the Mu4e email client for Doom Emacs. 3title = "Email in Doom Emacs with Mu4e on macOS"
4#+filetags: emacs, org-mode, productivity 4description = ""
5#+slug: mu4e 5draft = false
6 6+++
7This post was heavily inspired by [[https://macowners.club/posts/email-emacs-mu4e-macos/][Email setup in Emacs with Mu4e on macOS]], but 7
8with my own tweaks for a single-account configuration and some Doom-specific 8This post was heavily inspired by [Email setup in Emacs with Mu4e on
9macOS](https://macowners.club/posts/email-emacs-mu4e-macos/), but with
10my own tweaks for a single-account configuration and some Doom-specific
9configurations. 11configurations.
10 12
11* Overview 13# Overview
12 14
13[[https://github.com/emacsmirror/mu4e][Mu4e]] is an Emacs-based email client based on [[https://www.djcbsoftware.nl/code/mu/][mu]], an indexer that stores email in 15[Mu4e](https://github.com/emacsmirror/mu4e) is an Emacs-based email
14the Maildir format. 16client based on [mu](https://www.djcbsoftware.nl/code/mu/), an indexer
17that stores email in the Maildir format.
15 18
16This blog post covers the installation of Mu4e in Doom Emacs on macOS. This 19This blog post covers the installation of Mu4e in Doom Emacs on macOS.
17guide should be very similar to GNU Emacs and Linux-based systems, with a few 20This guide should be very similar to GNU Emacs and Linux-based systems,
18tweaks required in the various configuration files. 21with a few tweaks required in the various configuration files.
22
23# Installation
24
25## Prerequisites
26
27### Create the Folder Structure
19 28
20* Installation
21** Prerequisites
22*** Create the Folder Structure
23Start by creating the base folder structure: 29Start by creating the base folder structure:
24 30
25#+begin_src sh 31```sh
26mkdir ~/.maildir 32mkdir ~/.maildir
27mkdir ~/.maildir/example # use whatever name you want to call your email account 33mkdir ~/.maildir/example # use whatever name you want to call your email account
28mkdir ~/.maildir/certificates # used to store system root certificates 34mkdir ~/.maildir/certificates # used to store system root certificates
29#+end_src 35```
30 36
31*** Store Email Account Passwords in macOS Keychain 37### Store Email Account Passwords in macOS Keychain
32Next, I will be using the macOS Keychain to store my email account passwords
33using the command below.
34 38
35#+begin_src sh 39Next, I will be using the macOS Keychain to store my email account
40passwords using the command below.
41
42```sh
36security add-generic-password -s mu4e-example -a you@example.com -w 43security add-generic-password -s mu4e-example -a you@example.com -w
37#+end_src 44```
38 45
39This will prompt you to input your password twice to confirm. Keep the 46This will prompt you to input your password twice to confirm. Keep the
40=mu4e-example= name in mind, as you will need to reference it later in the IMAP 47`mu4e-example` name in mind, as you will need to reference it
41and SMTP configuration files. 48later in the IMAP and SMTP configuration files.
42 49
43*** Store Root Certificates 50### Store Root Certificates
44 51
45In order to use IMAP and SMTP, we need to provide certificates to the local 52In order to use IMAP and SMTP, we need to provide certificates to the
46services. We will use the macOS defaults for this. 53local services. We will use the macOS defaults for this.
47 54
481. Open =Keychain Access.app=. 551. Open `Keychain Access.app`.
492. Select =System Roots= in the sidebar. 562. Select `System Roots` in the sidebar.
503. Select all items with =CMD + a=. 573. Select all items with `CMD + a`.
514. Export selected items with =SHIFT + CMD + a= to the file 584. Export selected items with `SHIFT + CMD + a` to the file
52 =~/.maildir/certificates/root-certificates.pem=. 59 `~/.maildir/certificates/root-certificates.pem`.
53 60
54*** Install Dependencies 61### Install Dependencies
55 62
56Install =mbsync= (via =isync=) to fetch emails via IMAP, =mu= to index emails, 63Install `mbsync` (via `isync`) to fetch emails via
57and =msmtp= to send emails via SMTP. 64IMAP, `mu` to index emails, and `msmtp` to send
65emails via SMTP.
58 66
59#+begin_src sh 67```sh
60brew install mu isync msmtp 68brew install mu isync msmtp
61#+end_src 69```
62 70
63** Installing Mu4e 71## Installing Mu4e
64 72
65Within Doom Emacs, we can install Mu4e by enabling the package. 73Within Doom Emacs, we can install Mu4e by enabling the package.
66 74
67#+begin_src sh 75```sh
68nano ~/.doom.d/init.el 76nano ~/.doom.d/init.el
69#+end_src 77```
70 78
71In this file, uncomment the =mu4e= line within the =:email= section. You can 79In this file, uncomment the `mu4e` line within the
72also enable the =+org= and =+gmail= options if you prefer. 80`:email` section. You can also enable the `+org`
81and `+gmail` options if you prefer.
73 82
74#+begin_src elisp 83```lisp
75(doom! :input 84(doom! :input
76 ... 85 ...
77 :email 86 :email
@@ -79,32 +88,35 @@ also enable the =+org= and =+gmail= options if you prefer.
79 ;;(mu4e +org +gmail) 88 ;;(mu4e +org +gmail)
80 ... 89 ...
81 (default +bindings +smartparens)) 90 (default +bindings +smartparens))
82#+end_src 91```
92
93# Configuration
83 94
84* Configuration 95As an overall suggestion, I create the following configuration files in
85As an overall suggestion, I create the following configuration files in the 96the `~/.maildir` directory and using symlinks to their proper
86=~/.maildir= directory and using symlinks to their proper locations so that I 97locations so that I can backup and restore these files easily.
87can backup and restore these files easily.
88 98
89#+begin_src sh 99```sh
90touch ~/.maildir/.mbsyncrc && \ 100touch ~/.maildir/.mbsyncrc && \
91touch ~/.maildir/.msmtprc && \ 101touch ~/.maildir/.msmtprc && \
92ln -s /Users/username/.maildir/.mbsyncrc /Users/username/.mbsyncrc && \ 102ln -s /Users/username/.maildir/.mbsyncrc /Users/username/.mbsyncrc && \
93ln -s /Users/username/.maildir/.msmtprc /Users/username/.msmtprc 103ln -s /Users/username/.maildir/.msmtprc /Users/username/.msmtprc
94#+end_src 104```
105
106You can also create these files in your home directory and skip the
107symlinking process above.
95 108
96You can also create these files in your home directory and skip the symlinking 109## IMAP
97process above.
98 110
99** IMAP 111Next, let\'s configure `mbsync` in the file created above.
100Next, let's configure =mbsync= in the file created above. Paste the following 112Paste the following information and customize it to match your mail
101information and customize it to match your mail provider's information. 113provider\'s information.
102 114
103#+begin_src sh 115```sh
104nano ~/.maildir/.mbsyncrc 116nano ~/.maildir/.mbsyncrc
105#+end_src 117```
106 118
107#+begin_src conf 119``` conf
108IMAPAccount example 120IMAPAccount example
109Host imap.example.com 121Host imap.example.com
110User dummy@example.com 122User dummy@example.com
@@ -130,44 +142,52 @@ Create Near
130Sync All 142Sync All
131Expunge Both 143Expunge Both
132SyncState * 144SyncState *
133#+end_src 145```
134 146
135** SMTP 147## SMTP
136Next, let's configured =msmtprc= in the file created above. Paste the following
137information and customize it to match your mail provider's information.
138 148
139#+begin_src sh 149Next, let\'s configured `msmtprc` in the file created above.
150Paste the following information and customize it to match your mail
151provider\'s information.
152
153```sh
140nano ~/.maildir/.msmtprc 154nano ~/.maildir/.msmtprc
141#+end_src 155```
142 156
143,#+begin_src conf 157```conf
158# Set default values for all the accounts.
144defaults 159defaults
145logfile ~/.maildir/msmtp.log 160logfile ~/.maildir/msmtp.log
146tls_trust_file ~/.maildir/certificates/root-certificates.pem 161tls_trust_file ~/.maildir/certificates/root-certificates.pem
147 162
148account example 163# ======================================================================
164
165account startmail
149auth on 166auth on
150host smtp.example.com 167host smtp.startmail.com
151port 465 168port 465
152protocol smtp 169protocol smtp
153from you@example.com 170from hello@cleberg.net
154user you@example.com 171user hello@cleberg.net
155passwordeval security find-generic-password -s mu4e-example -a you@example.com -w 172passwordeval security find-generic-password -s startmail -a hello@cleberg.net -w
156tls on 173tls on
157tls_starttls off 174tls_starttls off
158 175
159account default : example 176# ======================================================================
160#+end_src 177
178account default : startmail
179```
161 180
162** Doom Emacs 181## Doom Emacs
163Finally, we need to configure Doom Emacs to use the proper packages and set some
164variables and functions.
165 182
166#+begin_src sh 183Finally, we need to configure Doom Emacs to use the proper packages and
184set some variables and functions.
185
186```sh
167nano ~/.doom.d/config.el 187nano ~/.doom.d/config.el
168#+end_src 188```
169 189
170#+begin_src elisp 190```lisp
171;; load packages and programs 191;; load packages and programs
172(use-package mu4e 192(use-package mu4e
173 :load-path "/Users/username/.emacs.d/modules/email/mu4e/") 193 :load-path "/Users/username/.emacs.d/modules/email/mu4e/")
@@ -251,50 +271,53 @@ nano ~/.doom.d/config.el
251 271
252;; mu4e address completion 272;; mu4e address completion
253(add-hook 'mu4e-compose-mode-hook 'company-mode) 273(add-hook 'mu4e-compose-mode-hook 'company-mode)
254#+end_src 274```
255 275
256Be sure to sync Doom to update the current configurations. 276Be sure to sync Doom to update the current configurations.
257 277
258#+begin_src sh 278```sh
259doom sync 279doom sync
260#+end_src 280```
281
282If you have Doom open, execute `SPC h r r` to reload the new
283configurations.
261 284
262If you have Doom open, execute =SPC h r r= to reload the new configurations. 285# Initial Sync
263 286
264* Initial Sync 287Once you have configured all of the relevant files, you can perform an
265Once you have configured all of the relevant files, you can perform an initial 288initial sync. Note that you can perform syncing within Mu4e itself afer
266sync. Note that you can perform syncing within Mu4e itself afer this. 289this.
267 290
268#+begin_src sh 291```sh
269mbsync -aV 292mbsync -aV
270#+end_src 293```
271 294
272Once you sync the data, you can index the emails. 295Once you sync the data, you can index the emails.
273 296
274#+begin_src sh 297```sh
275mu init -m ~/.maildir --my-address you@example.com && \ 298mu init -m ~/.maildir --my-address you@example.com && \
276mu index 299mu index
277#+end_src 300```
278 301
279The emails will now to be ready to use! 302The emails will now to be ready to use!
280 303
281* Screenshots 304# Screenshots
282 305
283You can now launch Doom and open Mu4e with =SPC o m=. You can also explore the 306You can now launch Doom and open Mu4e with `SPC o m`. You can
284Mu4e options with =SPC : mu4e=. 307also explore the Mu4e options with `SPC : mu4e`.
285 308
286The home page shows various options and metdata about the account you've opened. 309The home page shows various options and metdata about the account
310you\'ve opened.
287 311
288#+caption: Mu4e Home Page 312![Mu4e Home Page](https://img.cleberg.net/blog/20240418-mu4e/mu4e.png)
289[[https://img.cleberg.net/blog/20240418-mu4e/mu4e.png]]
290 313
291When composing an email, you can see the metadata fields at the top and compose 314When composing an email, you can see the metadata fields at the top and
292the message below the indicator line. 315compose the message below the indicator line.
293 316
294#+caption: Composition Screen 317![Composition
295[[https://img.cleberg.net/blog/20240418-mu4e/draft.png]] 318Screen](https://img.cleberg.net/blog/20240418-mu4e/draft.png)
296 319
297When in a folder, you can view emails in a pane and perform actions as needed. 320When in a folder, you can view emails in a pane and perform actions as
321needed.
298 322
299#+caption: Inbox 323![Inbox](https://img.cleberg.net/blog/20240418-mu4e/inbox.png)
300[[https://img.cleberg.net/blog/20240418-mu4e/inbox.png]]
content/blog/_index.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Blog"
3sort_by = "date"
4template = "blog.html"
5+++\ No newline at end of file
content/index.org deleted −2
@@ -1,2 +0,0 @@
1#+title: cleberg.net
2#+description: Just a blip of ones and zeroes.
content/salary.md added +35
@@ -0,0 +1,35 @@
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 information is posted publicly to ensure others in my position have a solid reference point when determining if their current or proposed salary is appropriate.
9
10While sites like Glassdoor are locking salary data behind a paywall, LinkedIn is discontinuing LinkedIn Salary, and helpful websites like Big 4 Transparency are extremely rare, I wanted to provide my personal data publicly and freely to those who need it.
11
12I have seen what can happen when great employees don't know the market values for their skills and I happily help those in my teams, so I'm happy to extend this information to the online community.
13
14As a final note, there are numerous reasons that people in the same role are paid differently (expertise, years of experience, certifications, education, etc.) and that the data in this table should only be used as a single point of reference, not the whole story.
15
16# Salary Data
17
18Note: When in a role that gives periodic raises, I will create a new record with the new base salary in the table below. See the KPMG records for an example of a raise while in the same role.
19
20| Title | Company | Location | Start | End | Salary |
21|------------------------------------------------|------------------------|----------------|---------|---------|----------|
22| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2023-10 | Current | $116,700 |
23| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2022-06 | 2023-10 | $110,000 |
24| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | $89,500 |
25| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | $72,000 |
26| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | $65,000 |
27| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | $58,000 |
28| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | $20/hour |
29| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | $16/hour |
30| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | $7/hour |
31| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | $14/hour |
32
33This page was inspired by [Xe](https://xeiaso.net/salary-transparency/), and I'm quoting the following wording from them as I want to reiterate this piece:
34
35> Please consider publishing your salary data like this as well. By open, voluntary transparency we can help to end stigmas around discussing pay and help ensure that the next generations of people in tech are treated fairly. Stigmas thrive in darkness but die in the light of day. You can help end the stigma by playing your cards out in the open like this.
content/salary/index.org deleted −54
@@ -1,54 +0,0 @@
1#+title: Salary
2#+description: A historical log of my jobs and salaries.
3#+slug: index
4
5* Salary Transparency
6
7The data below details the base salary information for each job I've held. This
8information is posted publicly to ensure others in my position have a solid
9reference point when determining if their current or proposed salary is
10appropriate.
11
12While sites like Glassdoor are locking salary data behind a paywall, LinkedIn is
13discontinuing LinkedIn Salary, and helpful websites like Big 4 Transparency are
14extremely rare, I wanted to provide my personal data publicly and freely to
15those who need it.
16
17I have seen what can happen when great employees don't know the market values
18for their skills and I happily help those in my teams, so I'm happy to extend
19this information to the online community.
20
21As a final note, there are numerous reasons that people in the same role are
22paid differently (expertise, years of experience, certifications, education,
23etc.) and that the data in this table should only be used as a single point of
24reference, not the whole story.
25
26* Salary Data
27
28Note: When in a role that gives periodic raises, I will create a new record with
29the new base salary in the table below. See the KPMG records for an example of a
30raise while in the same role.
31
32| Title | Company | Location | Start | End | Salary |
33|------------------------------------------------+------------------------+----------------+---------+---------+----------|
34| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2023-10 | Current | $116,700 |
35| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2022-06 | 2023-10 | $110,000 |
36| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | $89,500 |
37| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | $72,000 |
38| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | $65,000 |
39| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | $58,000 |
40| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | $20/hour |
41| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | $16/hour |
42| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | $7/hour |
43| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | $14/hour |
44
45This page was inspired by [[https://xeiaso.net/salary-transparency/][Xe]], and I'm quoting the following wording from them as
46I want to reiterate this piece:
47
48#+begin_quote
49Please consider publishing your salary data like this as well. By open,
50voluntary transparency we can help to end stigmas around discussing pay and help
51ensure that the next generations of people in tech are treated fairly. Stigmas
52thrive in darkness but die in the light of day. You can help end the stigma by
53playing your cards out in the open like this.
54#+end_quote
content/services.md added +17
@@ -0,0 +1,17 @@
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 password/secret sharing
10- [GotHub](https://gh.cleberg.net/) - An alternative front-end for GitHub
11- [ifconfig.php](https://ip.cleberg.net/) - A \"whatsmyip\" tool
12- [Invidious](https://invidious.cleberg.net/) - A YouTube proxy
13- [Office](https://office.cleberg.net/) - The world\'s smallest office suite
14- [Org-Live](https://org.cleberg.net/) - A basic org-mode editor for the web I built
15- [SearXNG](https://search.cleberg.net/) - A privacy-respecting, open metasearch engine
16
17See the [git log](https://git.cleberg.net/?p=cleberg.net.git;a=history;f=content/services/index.org;h=b9ecca2567a02711a33bb633d45f790610ed9214;hb=HEAD) if you want to see changes that have been made.
content/services/index.org deleted −16
@@ -1,16 +0,0 @@
1#+title: Services
2#+description: Publicly-available services hosted on cleberg.net.
3#+slug: index
4
5- [[https://ao.cleberg.net/][AnonymousOverflow]] - A StackOverflow proxy
6- [[https://cyberchef.cleberg.net/][CyberChef]] - The Cyber Swiss Army Knife
7- [[https://files.cleberg.net/][FileArchive]] - An interesting file archive
8- [[https://paste.cleberg.net/][FlashPaper]] - One-time encrypted password/secret sharing
9- [[https://gh.cleberg.net/][GotHub]] - An alternative front-end for GitHub
10- [[https://ip.cleberg.net/][ifconfig.php]] - A "whatsmyip" tool
11- [[https://invidious.cleberg.net/][Invidious]] - A YouTube proxy
12- [[https://office.cleberg.net/][Office]] - The world's smallest office suite
13- [[https://org.cleberg.net/][Org-Live]] - A basic org-mode editor for the web I built
14- [[https://search.cleberg.net/][SearXNG]] - A privacy-respecting, open metasearch engine
15
16See the [[https://git.cleberg.net/?p=cleberg.net.git;a=history;f=content/services/index.org;h=b9ecca2567a02711a33bb633d45f790610ed9214;hb=HEAD][git log]] if you want to see changes that have been made.
content/wiki/_index.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Wiki"
3sort_by = "title"
4template = "wiki.html"
5+++\ No newline at end of file
content/wiki/ai.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "AI"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/ai.org deleted −3
@@ -1,3 +0,0 @@
1#+title: AI
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/audit.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Audit"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/audit.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Audit
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/automation.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Automation"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/automation.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Automation
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/blogroll.md added +35
@@ -0,0 +1,35 @@
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 Sites](https://greycoder.com/a-list-of-text-only-new-sites/)
22 - [Harvard Law Review](https://harvardlawreview.org/)
23 - [Hyperlinked Text](https://sjmulder.nl/en/textonly.html)
24 - [Plain-text web design](https://medium.com/@letsworkshop/plain-text-web-design-a78ccaf9dbc0)
25 - [Plaintext World](https://plaintextworld.com/)
26 - [Words](https://justinjackson.ca/words.html)
27
28## Webrings
29
30Instead of listing my personal favorites, I\'m just going to drop a link to [brisay's webring list](https://brisray.com/web/webring-list.htm), which contains 237 webrings for a total of 7078 websites, as of 2024-03-15.
31
32## Everything Else
33
34 - [Dead Simple Sites](https://deadsimplesites.com/)
35 - [Planet Emacslife](https://planet.emacslife.com/)
content/wiki/blogroll.org deleted −34
@@ -1,34 +0,0 @@
1#+title: Blogroll
2#+date: <2024-04-12 Fri 22:43:30>
3
4* Aggregators
5
6- [[https://1mb.club/][1MB Club]]
7- [[https://250kb.club/][250KB Club]]
8- [[https://512kb.club/][512KB Club]]
9- [[https://darktheme.club/][Darktheme Club]]
10- [[https://nocss.club/][No CSS Club]]
11- [[https://no-js.club/][No-JS Club]]
12- [[https://blogroll.org/][Ye Olde Blogroll]]
13
14* Plain Text
15
16A list of various plaintext websites and lists.
17
18- [[https://greycoder.com/a-list-of-text-only-new-sites/][A List Of Text-Only & Minimalist News Sites]]
19- [[https://harvardlawreview.org/][Harvard Law Review]]
20- [[https://sjmulder.nl/en/textonly.html][Hyperlinked Text]]
21- [[https://medium.com/@letsworkshop/plain-text-web-design-a78ccaf9dbc0][Plain-text web design]]
22- [[https://plaintextworld.com/][Plaintext World]]
23- [[https://justinjackson.ca/words.html][Words]]
24
25* Webrings
26
27Instead of listing my personal favorites, I'm just going to drop a link to
28[[https://brisray.com/web/webring-list.htm][brisay's webring list]], which contains 237 webrings for a total of 7078 websites,
29as of 2024-03-15.
30
31* Everything Else
32
33- [[https://deadsimplesites.com/][Dead Simple Sites]]
34- [[https://planet.emacslife.com/][Planet Emacslife]]
content/wiki/cli.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "CLI"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/cli.org deleted −3
@@ -1,3 +0,0 @@
1#+title: CLI
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/data.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Data"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/data.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Data
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/editors.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Editors"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/editors.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Editors
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/foss.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "FOSS"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/foss.org deleted −3
@@ -1,3 +0,0 @@
1#+title: FOSS
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/git.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Git"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/git.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Git
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/hardware.md added +95
@@ -0,0 +1,95 @@
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
45enterprise (network) hardware. I try to keep the lab energy efficient
46and quiet as my top priorities.
47
48### IoT
49
50A collection of mainly smart lights, sensors, and smart appliances. My first preference is to disable all networking for new smart devices or simply not connect internet in the first place (e.g. I never enable internet on my smart TVs). If the smart device requires LAN access, I will connect the device to my guest-restricted IoT network. As a last resort, I will set-up the internet but monitor the DNS lookups via NextDNS and forcibly block any domains I do not want the device to be using. If the device is egregious or shady, I\'ll just sell it and either replace it or live without it.
51
52- Other Appliances (washer, dryer, humidifier, fans, etc.)
53 - [Roomba i7+](https://about.irobot.com/sitecore/content/north-america/irobot-us/home/roomba/i7-series)
54 - [Philips Hue A19 Bulbs](https://www.philips-hue.com/en-us/p/hue-white-and-color-ambiance-a19---e26-smart-bulb---60-w--3-pack-/046677562786) x 15
55 - [Philips Hue Play Light Bars](https://www.philips-hue.com/en-us/p/hue-bundle-play-blk-ext/33001)
56 - [Philips Hue Smart Bridge](https://www.philips-hue.com/en-us/p/hue-bridge/046677458478) + play light bars and a ton of bulbs
57 - [UP Chime](https://store.ui.com/us/en/collections/unifi-camera-security-special-chime)
58 - [UP-Sense](https://store.ui.com/us/en/collections/unifi-camera-security-special-sensor) x 2
59 - [USP-Plug](https://store.ui.com/us/en/products/unifi-smart-power)
60 - [UVC G4 Instant](https://store.ui.com/us/en/collections/unifi-camera-security-compact-wifi-connected) x 3
61 - [UVC G4 Doorbell Pro](https://store.ui.com/us/en/collections/unifi-camera-security-special-wifi-doorbell)
62
63### Network
64
65A rack-mounted Dream Machine Pro, connected downstream to an access point, mesh extender, and a couple ethernet switches.
66
67 - [UDM-Pro](https://store.ui.com/us/en/collections/unifi-dream-machine/products/udm-pro)
68 - [USW-24-PoE](https://store.ui.com/us/en/collections/unifi-switching-standard-power-over-ethernet/products/usw-24-poe)
69 - [USW-Lite-8-PoE](https://store.ui.com/us/en/collections/unifi-switching-utility-poe/products/usw-lite-8-poe)
70 - [U6-Pro](https://store.ui.com/us/en/collections/unifi-wifi-flagship-high-capacity/products/u6-pro)
71 - [U6-Extender](https://store.ui.com/us/en/collections/unifi-wifi-inwall-outlet-mesh)
72 - [USW 24-Port Patch Panel](https://store.ui.com/us/en/collections/unifi-accessory-tech-installations-rackmount/products/uacc-rack-panel-patch-blank-24)
73
74### Servers
75
761. Rack-Mount Server
77
78 I wasn't happy with using low-powered PCs as servers and I knew I did not want the ear-shattering enterprise rack-mounted servers, so I built my own.
79
80 | Category | Details |
81 |--------------------|----------------------------------------|
82 | Case | Rosewill RSV-R4100U 4U |
83 | Motherboard | NZXT B550 |
84 | CPU | AMD Ryzen 7 5700G with Radeon Graphics |
85 | RAM | 64GB RAM (2x32GB) |
86 | Storage (On-board) | Western Digital 500GB M.2 NVME SSD |
87 | Storage (HDD Bay) | 48TB HDD |
88 | PSU | Corsair RM850 PSU |
89
902. Other
91
92 These ran as my main servers before I built the rack-mounted server above. I have shut these down indefinitely for now as I have no use for them.
93
94 - Dell OptiPlex
95 - Raspberry Pi 4
content/wiki/hardware.org deleted −114
@@ -1,114 +0,0 @@
1#+title: Hardware
2#+date: <2024-03-16 Sat 15:42:58>
3
4** Desktop
5
6*** macOS
7
8Probably should have added more RAM but Macbooks are stupid expensive.
9
10| Category | Details |
11|----------+---------------------------------------------------------|
12| Model | [[https://www.apple.com/macbook-pro/][Macbook Pro 16”]] |
13| CPU | Apple M2 Pro |
14| RAM | 16GB |
15| Storage | 512GB SSD |
16
17*** Linux
18
19A beauty.
20
21| Category | Details |
22|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------|
23| 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]] |
24| CPU | AMD Ryzen 5 5625U with Radeon Graphics |
25| RAM | 16 GB |
26| Storage | 256 GB SSD |
27
28** Mobile
29
30Previously used a Pixel 6 & Pixel 7 with GrapheneOS.
31
32| Category | Details |
33|----------+-------------------------------------------------------------|
34| Model | [[https://www.apple.com/iphone-15-pro/][iPhone 15 Pro Max]] |
35| CPU | A17 Pro |
36| RAM | 8GB |
37| Storage | 256GB |
38
39** Homelab
40
41I run a small homelab with a mix of consumer (compute/storage) and
42enterprise (network) hardware. I try to keep the lab energy efficient
43and quiet as my top priorities.
44
45*** IoT
46
47A collection of mainly smart lights, sensors, and smart appliances. My
48first preference is to disable all networking for new smart devices or
49simply not connect internet in the first place (e.g. I never enable
50internet on my smart TVs). If the smart device requires LAN access, I
51will connect the device to my guest-restricted IoT network. As a last
52resort, I will set-up the internet but monitor the DNS lookups via
53NextDNS and forcibly block any domains I do not want the device to be
54using. If the device is egregious or shady, I'll just sell it and either
55replace it or live without it.
56
57- Other Appliances (washer, dryer, humidifier, fans, etc.)
58- [[https://about.irobot.com/sitecore/content/north-america/irobot-us/home/roomba/i7-series][Roomba
59 i7+]]
60- [[https://www.philips-hue.com/en-us/p/hue-white-and-color-ambiance-a19---e26-smart-bulb---60-w--3-pack-/046677562786][Philips
61 Hue A19 Bulbs]] x 15
62- [[https://www.philips-hue.com/en-us/p/hue-bundle-play-blk-ext/33001][Philips
63 Hue Play Light Bars]]
64- [[https://www.philips-hue.com/en-us/p/hue-bridge/046677458478][Philips
65 Hue Smart Bridge]] + play light bars and a ton of bulbs
66- [[https://store.ui.com/us/en/collections/unifi-camera-security-special-chime][UP
67 Chime]]
68- [[https://store.ui.com/us/en/collections/unifi-camera-security-special-sensor][UP-Sense]]
69 x 2
70- [[https://store.ui.com/us/en/products/unifi-smart-power][USP-Plug]]
71- [[https://store.ui.com/us/en/collections/unifi-camera-security-compact-wifi-connected][UVC
72 G4 Instant]] x 3
73- [[https://store.ui.com/us/en/collections/unifi-camera-security-special-wifi-doorbell][UVC
74 G4 Doorbell Pro]]
75
76*** Network
77
78A rack-mounted Dream Machine Pro, connected downstream to an access
79point, mesh extender, and a couple ethernet switches.
80
81- [[https://store.ui.com/us/en/collections/unifi-dream-machine/products/udm-pro][UDM-Pro]]
82- [[https://store.ui.com/us/en/collections/unifi-switching-standard-power-over-ethernet/products/usw-24-poe][USW-24-PoE]]
83- [[https://store.ui.com/us/en/collections/unifi-switching-utility-poe/products/usw-lite-8-poe][USW-Lite-8-PoE]]
84- [[https://store.ui.com/us/en/collections/unifi-wifi-flagship-high-capacity/products/u6-pro][U6-Pro]]
85- [[https://store.ui.com/us/en/collections/unifi-wifi-inwall-outlet-mesh][U6-Extender]]
86- [[https://store.ui.com/us/en/collections/unifi-accessory-tech-installations-rackmount/products/uacc-rack-panel-patch-blank-24][USW
87 24-Port Patch Panel]]
88
89*** Servers
90
91**** Rack-Mount Server
92
93I wasn't happy with using low-powered PCs as servers and I knew I did
94not want the ear-shattering enterprise rack-mounted servers, so I built
95my own.
96
97| Category | Details |
98|--------------------+----------------------------------------|
99| Case | Rosewill RSV-R4100U 4U |
100| Motherboard | NZXT B550 |
101| CPU | AMD Ryzen 7 5700G with Radeon Graphics |
102| RAM | 64GB RAM (2x32GB) |
103| Storage (On-board) | Western Digital 500GB M.2 NVME SSD |
104| Storage (HDD Bay) | 48TB HDD |
105| PSU | Corsair RM850 PSU |
106
107**** Other
108
109These ran as my main servers before I built the rack-mounted server
110above. I have shut these down indefinitely for now as I have no use for
111them.
112
113- Dell OptiPlex
114- Raspberry Pi 4
content/wiki/ios.md added +151
@@ -0,0 +1,151 @@
1+++
2title = "iOS"
3updated = 2024-03-13
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 the apps I have used at one point or another across my different iPhones.
12
13(`*`) = My favorites
14
15## Configuration
16
17### Display
18
19- Light Mode
20 - 10:00 to 16:00
21- Dark Mode
22 - 16:00 to 10:00
23
24### Focus Modes
25
26- Personal Focus
27 - 06:00 to 21:00
28 - Allow Notifications From:
29 - Alarms
30 - Calendar
31 - Contacts (1 person)
32 - Messages
33 - Phone
34 - Reminders
35 - Signal
36 - UniFi Protect
37- Sleep Focus
38 - 21:00 to 06:00
39 - Allow Notifications From:
40 - Alarms
41 - Contacts (1 person)
42 - Reminders
43 - Signal
44
45### Privacy & Security
46
47I generally follow the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) by only permitting the bare minimum privileges and revoking as soon as they are no longer required.
48
49Here's the baseline I start with:
50
51- Disable:
52 - Analytics & Improvements
53 - Apple Advertising
54 - Apple ID \> Sign-In & Security \> Two-Factor Authentication
55 - Location Services \> System Services \> Product Improvement
56 - Tracking \> Allow Apps to Request to Track
57 - Safari \> Advanced \> Privacy Preserving Ad Measurement
58- Enable:
59 - Apple ID \> iCloud \> Advanced Data Protection
60 - Apple ID \> Personal Information \> Communication Preferences
61 - App Privacy Report
62 - Location Services only for Camera, Find My, UDisc, & WiFiman
63 (`While Using`)
64 - Safari \> Prevent Cross-Site Tracking
65 - Safari \> Hide IP Address
66 - Safari \> Advanced \> Advanced Tracking and Fingerprinting
67 Protection
68
69## Native Apps
70
71### Business
72
73- [Element](https://apps.apple.com/us/app/element-messenger/id1083446067) - A cross-platform messenger, based on Matrix
74- [LinkedIn](https://apps.apple.com/us/app/linkedin-network-job-finder/id288429040) - One of the only social media apps I use
75
76### Developer Tools
77
78- [Harbour](https://testflight.apple.com/join/F2vK7xo4) - Easily manage your Portainer service
79- [iSH](https://apps.apple.com/us/app/ish-shell/id1436902243) - A local shell with SSH functionality
80
81### Entertainment
82
83- [Plex](https://apps.apple.com/us/app/plex-watch-live-tv-and-movies/id383457673) - A client for the Plex Media Server
84- [Steam](https://apps.apple.com/us/app/steam-mobile/id495369748) - The top gaming marketplace for computers
85
86### Lifestyle
87
88- [Home](https://apps.apple.com/us/app/home/id1110145103) (`*`) - Apple homekit powered smart home manager
89- [Hue](https://apps.apple.com/us/app/philips-hue/id1055281310) - Philips Hue smart home manager
90- [iRobot](https://apps.apple.com/us/app/irobot-home/id1012014442) - Manage iRobot Roomba devices
91- [UniFi Protect](https://apps.apple.com/us/app/unifi-protect/id1392492235) - View and manage most UniFi Protect cameras and settings
92
93### Music
94
95- [Apple Music](https://apps.apple.com/us/app/apple-music/id1108187390) (`*`) - Apple\'s native music streaming app
96- [Plexamp](https://apps.apple.com/us/app/plexamp/id1500797510) (`*`) - Top-notch music app for your Plex Media Server, with a neural network that provides excellent radio/shuffle suggestions
97
98### News
99
100- [NetNewsWire](https://apps.apple.com/us/app/netnewswire-rss-reader/id1480640210) - A free and open source RSS reader for Mac, iPhone, and iPad
101
102### Photo & Video
103
104- [Aislingeach](https://testflight.apple.com/join/Q6WyyEpS) - A quick way to generate and rate images from the Stable Horde
105- [Unsplash](https://apps.apple.com/us/app/unsplash/id1290631746) - Premium images, mostly free
106
107### Productivity
108
109- [beorg](https://apps.apple.com/us/app/beorg-to-do-list-agenda/id1238649962) - An org-mode editor, outline, and scheduler with paid extensions
110- [Bitwarden](https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744) (`*`) - An open source password manager
111- [Cryptomator](https://apps.apple.com/us/app/cryptomator/id1560822163) - A cross-platform encryption program
112- [Obsidian](https://apps.apple.com/us/app/obsidian-connected-notes/id1557175442) (`*`) - A nice Markdown-based editor based on a \"vault\" structure. Offers a paid sync solution and community extensions
113- [Strongbox](https://apps.apple.com/us/app/strongbox-password-manager/id897283731) - Keepass password manager for iOS & macOS
114- [UniFi Network](https://apps.apple.com/us/app/unifi/id1057750338) - View and manage most UniFi Network settings
115
116### Safari Extensions
117
118- [AdGuard](https://apps.apple.com/us/app/adguard-adblock-privacy/id1047223162) -
119 Ad blocker
120- [Dark Reader](https://apps.apple.com/us/app/dark-reader-for-safari/id1438243180) - Dark mode for all the sites
121- [PiPifier](https://apps.apple.com/us/app/pipifier/id1234771095) - Force videos to support PiP
122- [Privacy Redirect](https://apps.apple.com/us/app/privacy-redirect/id1578144015) - Redirect select websites to others, usually to privacy-focused alternatives
123
124### Social Networking
125
126- [MultiTab T](https://apps.apple.com/us/app/multitab-for-tumblr/id1071533778) (`*`) - A gallery-based Tumblr client with some unique features, such as tab history and sync
127- [Signal](https://apps.apple.com/us/app/signal-private-messenger/id874139669) (`*`) - A simple, powerful, and secure messenger
128- [Three Cheers](https://testflight.apple.com/join/mpVk1qIy) - A client for Tildes.net with a design focus that matches the intent of Tildes
129- [Voyager](https://apps.apple.com/us/app/voyager-for-lemmy/id6451429762) - A Lemmy client
130
131### Sports
132
133- [Apple Sports](https://apps.apple.com/us/app/apple-sports/id6446788829) - Apple\'s new sports app - lacks notifications and live events
134- [UDisc](https://apps.apple.com/us/app/udisc-disc-golf/id1072228953) - Disc golf course maps, score cards, and more
135
136### Utilities
137
138- [Backblaze](https://apps.apple.com/us/app/backblaze/id628638330) - Quickly view and manage Backblaze b2 cloud storage
139- [Mullvad VPN](https://apps.apple.com/us/app/mullvad-vpn/id1488466513) (`*`) - A private VPN service
140- [OTP Auth](https://apps.apple.com/us/app/otp-auth/id659877384) (`*`) - A minimalistic OTP app with support for biometrics, custom icons, import/export, and iCloud sync
141- [Plex Dash](https://apps.apple.com/us/app/plex-dash/id1500797677) - Stats about your Plex Media Server
142- [Safari](https://apps.apple.com/us/app/safari/id1146562112) - iOS default browser
143- [Unifi WiFiman](https://apps.apple.com/us/app/ubiquiti-wifiman/id1385561119) - Create visual layouts of WiFi strength and save heat maps to your phone
144
145## Web Apps & Shortcuts
146
147- [Brutalist Report](https://brutalist.report/) - Minimal news aggregator
148- [_Cyber.Report](https://cyber.report/) - Cybersecurity news aggregator
149- [Hacker News](https://news.ycombinator.com/) - Mostly technical news
150- [NextDNS](https://nextdns.io/) - NextDNS statistics dashboard
151- [Readspike](https://readspike.com/) - Minimal news aggregator
content/wiki/ios.org deleted −156
@@ -1,156 +0,0 @@
1#+title: iOS
2#+date: <2024-03-13 Wed 08:00:00>
3
4Related:
5
6- [[/wiki/hardware.html][Hardware]]
7
8My primary mobile OS. Currently running iOS 17. This wiki page contains most of
9the apps I have used at one point or another across my different iPhones.
10
11(=*=) = My favorites
12
13** Configuration
14
15*** Display
16
17- Light Mode
18 - 10:00 to 16:00
19- Dark Mode
20 - 16:00 to 10:00
21
22*** Focus Modes
23
24- Personal Focus
25 - 06:00 to 21:00
26 - Allow Notifications From:
27 - Alarms
28 - Calendar
29 - Contacts (1 person)
30 - Messages
31 - Phone
32 - Reminders
33 - Signal
34 - UniFi Protect
35- Sleep Focus
36 - 21:00 to 06:00
37 - Allow Notifications From:
38 - Alarms
39 - Contacts (1 person)
40 - Reminders
41 - Signal
42
43*** Privacy & Security
44
45I generally follow the [[https://en.wikipedia.org/wiki/Principle_of_least_privilege][principle of least privilege]] by only permitting the bare
46minimum privileges and revoking as soon as they are no longer required.
47
48Here's the baseline I start with:
49
50- Disable:
51 - Analytics & Improvements
52 - Apple Advertising
53 - Apple ID > Sign-In & Security > Two-Factor Authentication
54 - Location Services > System Services > Product Improvement
55 - Tracking > Allow Apps to Request to Track
56 - Safari > Advanced > Privacy Preserving Ad Measurement
57- Enable:
58 - Apple ID > iCloud > Advanced Data Protection
59 - Apple ID > Personal Information > Communication Preferences
60 - App Privacy Report
61 - Location Services only for Camera, Find My, UDisc, & WiFiman
62 (=While Using=)
63 - Safari > Prevent Cross-Site Tracking
64 - Safari > Hide IP Address
65 - Safari > Advanced > Advanced Tracking and Fingerprinting Protection
66
67** Native Apps
68
69*** Business
70
71- [[https://apps.apple.com/us/app/element-messenger/id1083446067][Element]] - A cross-platform messenger, based on Matrix
72- [[https://apps.apple.com/us/app/linkedin-network-job-finder/id288429040][LinkedIn]] - One of the only social media apps I use
73
74
75*** Developer Tools
76
77- [[https://testflight.apple.com/join/F2vK7xo4][Harbour]] - Easily manage your Portainer service
78- [[https://apps.apple.com/us/app/ish-shell/id1436902243][iSH]] - A local shell with SSH functionality
79
80*** Entertainment
81
82- [[https://apps.apple.com/us/app/plex-watch-live-tv-and-movies/id383457673][Plex]] - A client for the Plex Media Server
83- [[https://apps.apple.com/us/app/steam-mobile/id495369748][Steam]] - The top gaming marketplace for computers
84
85*** Lifestyle
86
87- [[https://apps.apple.com/us/app/home/id1110145103][Home]] (=*=) - Apple homekit powered smart home manager
88- [[https://apps.apple.com/us/app/philips-hue/id1055281310][Hue]] - Philips Hue smart home manager
89- [[https://apps.apple.com/us/app/irobot-home/id1012014442][iRobot]] - Manage iRobot Roomba devices
90- [[https://apps.apple.com/us/app/unifi-protect/id1392492235][UniFi Protect]] - View and manage most UniFi Protect cameras and settings
91
92*** Music
93
94- [[https://apps.apple.com/us/app/apple-music/id1108187390][Apple Music]] (=*=) - Apple's native music streaming app
95- [[https://apps.apple.com/us/app/plexamp/id1500797510][Plexamp]] (=*=) - Top-notch music app for your Plex Media Server, with a neural
96 network that provides excellent radio/shuffle suggestions
97
98*** News
99
100- [[https://apps.apple.com/us/app/netnewswire-rss-reader/id1480640210][NetNewsWire]] - A free and open source RSS reader for Mac, iPhone, and iPad
101
102*** Photo & Video
103
104- [[https://testflight.apple.com/join/Q6WyyEpS][Aislingeach]] - A quick way to generate and rate images from the Stable Horde
105- [[https://apps.apple.com/us/app/unsplash/id1290631746][Unsplash]] - Premium images, mostly free
106
107*** Productivity
108
109- [[https://apps.apple.com/us/app/beorg-to-do-list-agenda/id1238649962][beorg]] (=*=) - An org-mode editor, outline, and scheduler with paid extensions
110- [[https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744][Bitwarden]] (=*=) - An open source password manager
111- [[https://apps.apple.com/us/app/cryptomator/id1560822163][Cryptomator]] - A cross-platform encryption program
112- [[https://apps.apple.com/us/app/obsidian-connected-notes/id1557175442][Obsidian]] - A nice Markdown-based editor based on a "vault" structure.
113 Offers a paid sync solution and community extensions
114- [[https://apps.apple.com/us/app/strongbox-password-manager/id897283731][Strongbox]] - Keepass password manager for iOS & macOS
115- [[https://apps.apple.com/us/app/unifi/id1057750338][UniFi Network]] - View and manage most UniFi Network settings
116
117*** Safari Extensions
118
119- [[https://apps.apple.com/us/app/adguard-adblock-privacy/id1047223162][AdGuard]] - Ad blocker
120- [[https://apps.apple.com/us/app/dark-reader-for-safari/id1438243180][Dark Reader]] - Dark mode for all the sites
121- [[https://apps.apple.com/us/app/pipifier/id1234771095][PiPifier]] - Force videos to support PiP
122- [[https://apps.apple.com/us/app/privacy-redirect/id1578144015][Privacy Redirect]] - Redirect select websites to others, usually to
123 privacy-focused alternatives
124
125*** Social Networking
126
127- [[https://apps.apple.com/us/app/multitab-for-tumblr/id1071533778][MultiTab T]] (=*=) - A gallery-based Tumblr client with some unique features,
128 such as tab history and sync
129- [[https://apps.apple.com/us/app/signal-private-messenger/id874139669][Signal]] (=*=) - A simple, powerful, and secure messenger
130- [[https://testflight.apple.com/join/mpVk1qIy][Three Cheers]] - A client for Tildes.net with a design focus that matches the
131 intent of Tildes
132- [[https://apps.apple.com/us/app/voyager-for-lemmy/id6451429762][Voyager]] - A Lemmy client
133
134*** Sports
135
136- [[https://apps.apple.com/us/app/apple-sports/id6446788829][Apple Sports]] - Apple's new sports app - lacks notifications and live events
137- [[https://apps.apple.com/us/app/udisc-disc-golf/id1072228953][UDisc]] - Disc golf course maps, score cards, and more
138
139*** Utilities
140
141- [[https://apps.apple.com/us/app/backblaze/id628638330][Backblaze]] - Quickly view and manage Backblaze b2 cloud storage
142- [[https://apps.apple.com/us/app/mullvad-vpn/id1488466513][Mullvad VPN]] (=*=) - A private VPN service
143- [[https://apps.apple.com/us/app/otp-auth/id659877384][OTP Auth]] (=*=) - A minimalistic OTP app with support for biometrics, custom
144 icons, import/export, and iCloud sync
145- [[https://apps.apple.com/us/app/plex-dash/id1500797677][Plex Dash]] - Stats about your Plex Media Server
146- [[https://apps.apple.com/us/app/safari/id1146562112][Safari]] - iOS default browser
147- [[https://apps.apple.com/us/app/ubiquiti-wifiman/id1385561119][Unifi WiFiman]] - Create visual layouts of WiFi strength and save heat maps to
148 your phone
149
150** Web Apps & Shortcuts
151
152- [[https://brutalist.report/][Brutalist Report]] - Minimal news aggregator
153- [[https://cyber.report/][_Cyber.Report]] - Cybersecurity news aggregator
154- [[https://news.ycombinator.com/][Hacker News]] - Mostly technical news
155- [[https://nextdns.io/][NextDNS]] - NextDNS statistics dashboard
156- [[https://readspike.com/][Readspike]] - Minimal news aggregator
content/wiki/linux.org → content/wiki/linux.md renamed +10 −12
@@ -1,21 +1,19 @@
1#+title: Linux 1+++
2#+date: <2024-04-01 Mon 08:00:00> 2title = "Linux"
3#+draft: t 3updated = 2024-03-07
4draft = true
5+++
4 6
5Related: 7Related:
6 8
7- [[/wiki/hardware.html][Hardware]] 9- [Hardware](/wiki/hardware/)
10
11## Alpine Linux
8 12
9** Alpine Linux
10:PROPERTIES:
11:CUSTOM_ID: alpine-linux
12:END:
13My secondary laptop OS. 13My secondary laptop OS.
14 14
15** Ubuntu Server 15## Ubuntu Server
16:PROPERTIES: 16
17:CUSTOM_ID: ubuntu-server
18:END:
19My primary server OS. 17My primary server OS.
20 18
21- Applications 19- Applications
content/wiki/macos.md added +148
@@ -0,0 +1,148 @@
1+++
2title = "macOS"
3updated = 2024-04-10
4draft = false
5+++
6
7Related:
8
9- [Hardware](/wiki/hardware/)
10
11My primary OS. Currently running macOS Sonoma 14. This wiki page contains most of the apps I have used at one point or another across my different Macbooks.
12
13(`*`) = My favorites
14
15## Configuration
16
17### Disable System Services
18
19- [Disabling and Enabling System Integrity
20 Protection](https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection)
21- Disable Gatekeeper: `sudo spctl --master-disable`
22
23### Dotfiles
24
25These are probably out of date, but they give a general idea of how I configure my machine.
26
27```conf
28# ~/.zshrc
29export PATH="/opt/homebrew/bin:$PATH"
30export PATH="$HOME/.emacs.d/bin:$PATH"
31export EDITOR="/opt/homebrew/bin/emacs -nw"
32ZSH_THEME="bureau"
33plugins=(git zsh-autosuggestions)
34source $ZSH/oh-my-zsh.sh
35```
36
37```conf
38# ~/.zprofile
39eval "$(/opt/homebrew/bin/brew shellenv)"
40```
41
42```conf
43# ~/.config/skhd/skhdrc
44cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
45cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
46```
47
48```conf
49# ~/.config/yabai/yabairc
50yabai -m config \
51 mouse_follows_focus off \
52 focus_follows_mouse off \
53 window_origin_display default \
54 window_placement second_child \
55 window_zoom_persist on \
56 window_shadow on \
57 window_animation_duration 0.0 \
58 window_animation_frame_rate 120 \
59 window_opacity_duration 0.0 \
60 active_window_opacity 1.0 \
61 normal_window_opacity 0.90 \
62 window_opacity off \
63 insert_feedback_color 0xffd75f5f \
64 split_ratio 0.50 \
65 split_type auto \
66 auto_balance off \
67 top_padding 15 \
68 bottom_padding 15 \
69 left_padding 15 \
70 right_padding 15 \
71 window_gap 10 \
72 layout bsp \
73 mouse_modifier fn \
74 mouse_action1 move \
75 mouse_action2 resize \
76 mouse_drop_action swap
77echo "yabai configuration loaded.."
78```
79
80## Software
81
82### Browsers
83
84- [Librewolf](https://librewolf.net/) (`*`) - Custom version of Firefox, focused on privacy and security
85 - [Bitwarden](https://bitwarden.com/) - An open source password manager
86 - [Dark Reader](https://darkreader.org/) - Dark mode for all the websites
87 - [Libredirect](https://libredirect.github.io/) - Automatic web redirections
88 - [Strongbox](https://strongboxsafe.com/) - Keepass password manager for iOS & macOS
89 - [uBlock Origin](https://ublockorigin.com/) - Free, open-source ad content blocker
90- [Ungoogled Chromium](https://github.com/ungoogled-software/ungoogled-chromium) - Google Chromium, sans integration with Google
91- [eww](https://www.gnu.org/software/emacs/manual/html_mono/eww.html) - Emacs Web Wowser, for TUI browsing
92
93### Communications
94
95- [Element](https://element.io/) (`*`) - Matrix\'s default GUI client
96- [gomuks](https://github.com/tulir/gomuks) - A terminal based Matrix client
97- [Thunderbird](https://www.thunderbird.net/) (`*`) - An open source email client by Mozilla
98- [Signal](https://signal.org/) (`*`) - A simple, powerful, and secure messenger
99
100### Development
101
102- [Docker Desktop](https://www.docker.com/products/docker-desktop/) - Docker containers for your desktop
103 - [open-webui](https://github.com/open-webui/open-webui) - User-friendly WebUI for LLMs
104- [iTerm2](https://iterm2.com/) (`*`) - The best terminal for macOS, hands down
105- [Podman Desktop](https://podman-desktop.io/) (`*`) - Open source tool for containers and Kubernetes
106- [Xcode](https://developer.apple.com/xcode/) - Apple\'s IDE
107- [zsh](https://en.wikipedia.org/wiki/Z_shell) (`*`) - My shell preference due to its plugin and theme community
108 - [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions) - Fish-like autosuggestions for zsh
109 - [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting) - Fish shell like syntax highlighting for Zsh
110
111### Editors
112
113- [Doom Emacs](https://github.com/doomemacs/doomemacs) (`*`) - An Emacs framework, great for working in org-mode
114- [Obsidian](https://obsidian.md/) - A nice Markdown-based editor based on a "vault" structure. Offers a paid sync solution and community extensions
115- [Standard Notes](https://standardnotes.com/) - A simple text editor focused on privacy and security. Offers a paid sync solution and community extensions
116- [VSCodium](https://vscodium.com/) - VS Code without proprietary blobs
117
118### Media
119
120- [Luminar](https://skylum.com/luminar) - Luminar offers top-notch photo editing features
121- [Minecraft](https://www.minecraft.net/) - Block mining simulator
122- [NetNewsWire](https://netnewswire.com/) - A free and open source RSS reader for Mac, iPhone, and iPad
123- [Plex](https://www.plex.tv/) (`*`) - Desktop client for the Plex Media Server
124- [Steam](https://store.steampowered.com/) - The top gaming marketplace for computers
125- [Transmission](https://transmissionbt.com/) (`*`) - A Fast, Easy and Free Bittorrent Client
126- [VLC](https://www.videolan.org/vlc/) - A free and open source cross-platform multimedia player
127
128### Package Management
129
130- [Homebrew](https://brew.sh/) (`*`) - The Missing Package Manager for macOS (or Linux)
131- [MacPorts](https://www.macports.org/) - A system to compile, install, and manage open source software
132
133### Utilities
134
135- [Bartender 5](https://www.macbartender.com/Bartender5/) (`*`) - Easy control and customization over the native macOS menu bar
136- [BetterDisplay](https://betterdisplay.pro/) - Allows you to tweak a ton of features of built-in and external screens, such as scaling, configuration overrides, and color/brightness upscaling
137- [Bitwarden](https://bitwarden.com/) - An open source password manager
138- [Ollama](https://ollama.com/) - Run Llama 2, Code Llama, and other models locally on your machine
139 - [Ollama Swift](https://github.com/kghandour/Ollama-SwiftUI) - User Interface made for Ollama.ai using Swift
140- [LittleSnitch](https://obdev.at/products/littlesnitch/index.html) - Shows all network connections on your Macbook, including system and privileged services
141- [MicroSnitch](https://obdev.at/products/microsnitch/index.html) - Camera & microphone monitoring and alterting service
142- [Mullvad](https://mullvad.net/) (`*`) - A private VPN service
143- [skhd](https://github.com/koekeishiya/skhd) (`*`) - Simple hotkey daemon for macOS
144- [Strongbox](https://strongboxsafe.com/) - Keepass password manager for iOS & macOS
145- [Syncthing](https://syncthing.net/) (`*`) - Continuous file synchronization
146- [TinkerTool](https://www.bresink.com/osx/TinkerTool.html) - Unlock hidden configuration options for macOS
147- [yabai](https://github.com/koekeishiya/yabai) (`*`) - Automatic window tiling
148- [yt-dlp](https://github.com/yt-dlp/yt-dlp) - A youtube-dl fork with additional features and fixes
content/wiki/macos.org deleted −152
@@ -1,152 +0,0 @@
1#+title: macOS
2#+date: <2024-04-10 Wed 08:36:50>
3
4Related:
5
6- [[/wiki/hardware.html][Hardware]]
7
8My primary OS. Currently running macOS Sonoma 14. This wiki page contains most
9of the apps I have used at one point or another across my different Macbooks.
10
11(=*=) = My favorites
12
13** Configuration
14
15*** Disable System Services
16
17- [[https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection][Disabling and Enabling System Integrity Protection]]
18- Disable Gatekeeper: =sudo spctl --master-disable=
19
20*** Dotfiles
21
22These are probably out of date, but they give a general idea of how I configure
23my machine.
24
25#+begin_src config
26# ~/.zshrc
27export PATH="/opt/homebrew/bin:$PATH"
28export PATH="$HOME/.emacs.d/bin:$PATH"
29export EDITOR="/opt/homebrew/bin/emacs -nw"
30ZSH_THEME="bureau"
31plugins=(git zsh-autosuggestions)
32source $ZSH/oh-my-zsh.sh
33#+end_src
34
35#+begin_src config
36# ~/.zprofile
37eval "$(/opt/homebrew/bin/brew shellenv)"
38#+end_src
39
40#+begin_src config
41# ~/.config/skhd/skhdrc
42cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
43cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
44#+end_src
45
46#+begin_src config
47# ~/.config/yabai/yabairc
48yabai -m config \
49 mouse_follows_focus off \
50 focus_follows_mouse off \
51 window_origin_display default \
52 window_placement second_child \
53 window_zoom_persist on \
54 window_shadow on \
55 window_animation_duration 0.0 \
56 window_animation_frame_rate 120 \
57 window_opacity_duration 0.0 \
58 active_window_opacity 1.0 \
59 normal_window_opacity 0.90 \
60 window_opacity off \
61 insert_feedback_color 0xffd75f5f \
62 split_ratio 0.50 \
63 split_type auto \
64 auto_balance off \
65 top_padding 15 \
66 bottom_padding 15 \
67 left_padding 15 \
68 right_padding 15 \
69 window_gap 10 \
70 layout bsp \
71 mouse_modifier fn \
72 mouse_action1 move \
73 mouse_action2 resize \
74 mouse_drop_action swap
75echo "yabai configuration loaded.."
76#+end_src
77
78** Software
79
80*** Browsers
81
82- [[https://librewolf.net/][Librewolf]] (=*=) - Custom version of Firefox, focused on privacy and security
83 - [[https://bitwarden.com/][Bitwarden]] - An open source password manager
84 - [[https://darkreader.org/][Dark Reader]] - Dark mode for all the websites
85 - [[https://libredirect.github.io/][Libredirect]] - Automatic web redirections
86 - [[https://strongboxsafe.com/][Strongbox]] - Keepass password manager for iOS & macOS
87 - [[https://ublockorigin.com/][uBlock Origin]] - Free, open-source ad content blocker
88- [[https://github.com/ungoogled-software/ungoogled-chromium][Ungoogled Chromium]] - Google Chromium, sans integration with Google
89- [[https://www.gnu.org/software/emacs/manual/html_mono/eww.html][eww]] - Emacs Web Wowser, for TUI browsing
90
91*** Communications
92
93- [[https://element.io/][Element]] (=*=) - Matrix's default GUI client
94- [[https://github.com/tulir/gomuks][gomuks]] - A terminal based Matrix client
95- [[https://www.thunderbird.net/][Thunderbird]] (=*=) - An open source email client by Mozilla
96- [[https://signal.org/][Signal]] (=*=) - A simple, powerful, and secure messenger
97
98*** Development
99
100- [[https://www.docker.com/products/docker-desktop/][Docker Desktop]] - Docker containers for your desktop
101 - [[https://github.com/open-webui/open-webui][open-webui]] - User-friendly WebUI for LLMs
102- [[https://iterm2.com/][iTerm2]] (=*=) - The best terminal for macOS, hands down
103- [[https://podman-desktop.io/][Podman Desktop]] (=*=) - Open source tool for containers and Kubernetes
104- [[https://developer.apple.com/xcode/][Xcode]] - Apple's IDE
105- [[https://en.wikipedia.org/wiki/Z_shell][zsh]] (=*=) - My shell preference due to its plugin and theme community
106 - [[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]] - Fish-like autosuggestions for zsh
107 - [[https://github.com/zsh-users/zsh-syntax-highlighting][zsh-syntax-highlighting]] - Fish shell like syntax highlighting for Zsh
108
109*** Editors
110
111- [[https://github.com/doomemacs/doomemacs][Doom Emacs]] (=*=) - An Emacs framework, great for working in org-mode
112- [[https://obsidian.md/][Obsidian]] - A nice Markdown-based editor based on a "vault" structure.
113 Offers a paid sync solution and community extensions
114- [[https://standardnotes.com/][Standard Notes]] - A simple text editor focused on privacy and security. Offers
115 a paid sync solution and community extensions
116- [[https://vscodium.com/][VSCodium]] - VS Code without proprietary blobs
117
118*** Media
119
120- [[https://skylum.com/luminar][Luminar]] - Luminar offers top-notch photo editing features
121- [[https://www.minecraft.net/][Minecraft]] - Block mining simulator
122- [[https://netnewswire.com/][NetNewsWire]] - A free and open source RSS reader for Mac, iPhone, and iPad
123- [[https://www.plex.tv/][Plex]] (=*=) - Desktop client for the Plex Media Server
124- [[https://store.steampowered.com/][Steam]] - The top gaming marketplace for computers
125- [[https://transmissionbt.com/][Transmission]] (=*=) - A Fast, Easy and Free Bittorrent Client
126- [[https://www.videolan.org/vlc/][VLC]] - A free and open source cross-platform multimedia player
127
128** Package Management
129
130- [[https://brew.sh/][Homebrew]] (=*=) - The Missing Package Manager for macOS (or Linux)
131- [[https://www.macports.org/][MacPorts]] - A system to compile, install, and manage open source software
132
133*** Utilities
134
135- [[https://www.macbartender.com/Bartender5/][Bartender 5]] (=*=) - Easy control and customization over the native macOS menu
136 bar
137- [[https://betterdisplay.pro/][BetterDisplay]] - Allows you to tweak a ton of features of built-in and external
138 screens, such as scaling, configuration overrides, and color/brightness
139 upscaling
140- [[https://bitwarden.com/][Bitwarden]] - An open source password manager
141- [[https://ollama.com/][Ollama]] - Run Llama 2, Code Llama, and other models locally on your machine
142 - [[https://github.com/kghandour/Ollama-SwiftUI][Ollama Swift]] - User Interface made for Ollama.ai using Swift
143- [[https://obdev.at/products/littlesnitch/index.html][LittleSnitch]] - Shows all network connections on your Macbook, including system
144 and privileged services
145- [[https://obdev.at/products/microsnitch/index.html][MicroSnitch]] - Camera & microphone monitoring and alterting service
146- [[https://mullvad.net/][Mullvad]] (=*=) - A private VPN service
147- [[https://github.com/koekeishiya/skhd][skhd]] (=*=) - Simple hotkey daemon for macOS
148- [[https://strongboxsafe.com/][Strongbox]] - Keepass password manager for iOS & macOS
149- [[https://syncthing.net/][Syncthing]] (=*=) - Continuous file synchronization
150- [[https://www.bresink.com/osx/TinkerTool.html][TinkerTool]] - Unlock hidden configuration options for macOS
151- [[https://github.com/koekeishiya/yabai][yabai]] (=*=) - Automatic window tiling
152- [[https://github.com/yt-dlp/yt-dlp][yt-dlp]] - A youtube-dl fork with additional features and fixes
content/wiki/management.md added +11
@@ -0,0 +1,11 @@
1+++
2title = "Management"
3updated = 2024-03-07
4draft = true
5+++
6
7## Time Management
8
9## People Management
10
11## Project Management
content/wiki/management.org deleted −16
@@ -1,16 +0,0 @@
1#+title: Management
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
4
5** Time Management
6:PROPERTIES:
7:CUSTOM_ID: time-management
8:END:
9** People Management
10:PROPERTIES:
11:CUSTOM_ID: people-management
12:END:
13** Project Management
14:PROPERTIES:
15:CUSTOM_ID: project-management
16:END:
content/wiki/minimalism.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Minimalism"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/minimalism.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Minimalism
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/networking.md added +27
@@ -0,0 +1,27 @@
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/networking.org deleted −48
@@ -1,48 +0,0 @@
1#+title: Networking
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
4
5** Bluetooth
6:PROPERTIES:
7:CUSTOM_ID: bluetooth
8:END:
9** DNS
10:PROPERTIES:
11:CUSTOM_ID: dns
12:END:
13** Gemini
14:PROPERTIES:
15:CUSTOM_ID: gemini
16:END:
17** Gopher
18:PROPERTIES:
19:CUSTOM_ID: gopher
20:END:
21** HTTP
22:PROPERTIES:
23:CUSTOM_ID: http
24:END:
25** IoT
26:PROPERTIES:
27:CUSTOM_ID: iot
28:END:
29** Nginx
30:PROPERTIES:
31:CUSTOM_ID: nginx
32:END:
33** SSH
34:PROPERTIES:
35:CUSTOM_ID: ssh
36:END:
37** SSL
38:PROPERTIES:
39:CUSTOM_ID: ssl
40:END:
41** VPN
42:PROPERTIES:
43:CUSTOM_ID: vpn
44:END:
45** Wi-Fi
46:PROPERTIES:
47:CUSTOM_ID: wi-fi
48:END:
content/wiki/plaintext.md added +11
@@ -0,0 +1,11 @@
1+++
2title = "Plaintext"
3updated = 2024-03-07
4draft = true
5+++
6
7## Markdown
8
9## Org-Mode
10
11## AsciiDoc
content/wiki/plaintext.org deleted −16
@@ -1,16 +0,0 @@
1#+title: Plaintext
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
4
5** Markdown
6:PROPERTIES:
7:CUSTOM_ID: markdown
8:END:
9** Org-Mode
10:PROPERTIES:
11:CUSTOM_ID: org-mode
12:END:
13** AsciiDoc
14:PROPERTIES:
15:CUSTOM_ID: asciidoc
16:END:
content/wiki/privacy.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Privacy"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/privacy.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Privacy
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/python.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Python"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/python.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Python
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/security.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Security"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/security.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Security
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/selfhosted.md added +5
@@ -0,0 +1,5 @@
1+++
2title = "Self-Hosted"
3updated = 2024-03-07
4draft = true
5+++\ No newline at end of file
content/wiki/selfhosted.org deleted −3
@@ -1,3 +0,0 @@
1#+title: Self-Hosting
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
content/wiki/windows.md added +18
@@ -0,0 +1,18 @@
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
content/wiki/windows.org deleted −18
@@ -1,18 +0,0 @@
1#+title: Windows
2#+date: <2024-04-01 Mon 08:00:00>
3#+draft: t
4
5** Windows (Work)
6:PROPERTIES:
7:CUSTOM_ID: windows-work
8:END:
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
nginx-dev.conf deleted −17
@@ -1,17 +0,0 @@
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}
publish.el deleted −98
@@ -1,98 +0,0 @@
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)
theme/static/.well-known/security.txt → static/.well-known/security.txt renamed
theme/static/gpg.txt → static/gpg.txt renamed
theme/static/robots.txt → static/robots.txt renamed
static/styles.css added +562
@@ -0,0 +1,562 @@
1/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2
3/* Document
4 ========================================================================== */
5
6/**
7 * 1. Correct the line height in all browsers.
8 * 2. Prevent adjustments of font size after orientation changes in iOS.
9 */
10
11html {
12 line-height: 1.15; /* 1 */
13 -webkit-text-size-adjust: 100%; /* 2 */
14}
15
16/* Sections
17 ========================================================================== */
18
19/**
20 * Remove the margin in all browsers.
21 */
22
23body {
24 margin: 0;
25}
26
27/**
28 * Render the `main` element consistently in IE.
29 */
30
31main {
32 display: block;
33}
34
35/**
36 * Correct the font size and margin on `h1` elements within `section` and
37 * `article` contexts in Chrome, Firefox, and Safari.
38 */
39
40h1 {
41 font-size: 2em;
42 margin: 0.67em 0;
43}
44
45/* Grouping content
46 ========================================================================== */
47
48/**
49 * 1. Add the correct box sizing in Firefox.
50 * 2. Show the overflow in Edge and IE.
51 */
52
53hr {
54 box-sizing: content-box; /* 1 */
55 height: 0; /* 1 */
56 overflow: visible; /* 2 */
57}
58
59/**
60 * 1. Correct the inheritance and scaling of font size in all browsers.
61 * 2. Correct the odd `em` font sizing in all browsers.
62 */
63
64pre {
65 font-family: monospace, monospace; /* 1 */
66 font-size: 1em; /* 2 */
67}
68
69/* Text-level semantics
70 ========================================================================== */
71
72/**
73 * Remove the gray background on active links in IE 10.
74 */
75
76a {
77 background-color: transparent;
78}
79
80/**
81 * 1. Remove the bottom border in Chrome 57-
82 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 */
84
85abbr[title] {
86 border-bottom: none; /* 1 */
87 text-decoration: underline; /* 2 */
88 text-decoration: underline dotted; /* 2 */
89}
90
91/**
92 * Add the correct font weight in Chrome, Edge, and Safari.
93 */
94
95b,
96strong {
97 font-weight: bolder;
98}
99
100/**
101 * 1. Correct the inheritance and scaling of font size in all browsers.
102 * 2. Correct the odd `em` font sizing in all browsers.
103 */
104
105code,
106kbd,
107samp {
108 font-family: monospace, monospace; /* 1 */
109 font-size: 1em; /* 2 */
110}
111
112/**
113 * Add the correct font size in all browsers.
114 */
115
116small {
117 font-size: 80%;
118}
119
120/**
121 * Prevent `sub` and `sup` elements from affecting the line height in
122 * all browsers.
123 */
124
125sub,
126sup {
127 font-size: 75%;
128 line-height: 0;
129 position: relative;
130 vertical-align: baseline;
131}
132
133sub {
134 bottom: -0.25em;
135}
136
137sup {
138 top: -0.5em;
139}
140
141/* Embedded content
142 ========================================================================== */
143
144/**
145 * Remove the border on images inside links in IE 10.
146 */
147
148img {
149 border-style: none;
150}
151
152/* Forms
153 ========================================================================== */
154
155/**
156 * 1. Change the font styles in all browsers.
157 * 2. Remove the margin in Firefox and Safari.
158 */
159
160button,
161input,
162optgroup,
163select,
164textarea {
165 font-family: inherit; /* 1 */
166 font-size: 100%; /* 1 */
167 line-height: 1.15; /* 1 */
168 margin: 0; /* 2 */
169}
170
171/**
172 * Show the overflow in IE.
173 * 1. Show the overflow in Edge.
174 */
175
176button,
177input {
178 /* 1 */
179 overflow: visible;
180}
181
182/**
183 * Remove the inheritance of text transform in Edge, Firefox, and IE.
184 * 1. Remove the inheritance of text transform in Firefox.
185 */
186
187button,
188select {
189 /* 1 */
190 text-transform: none;
191}
192
193/**
194 * Correct the inability to style clickable types in iOS and Safari.
195 */
196
197button,
198[type="button"],
199[type="reset"],
200[type="submit"] {
201 -webkit-appearance: button;
202}
203
204/**
205 * Remove the inner border and padding in Firefox.
206 */
207
208button::-moz-focus-inner,
209[type="button"]::-moz-focus-inner,
210[type="reset"]::-moz-focus-inner,
211[type="submit"]::-moz-focus-inner {
212 border-style: none;
213 padding: 0;
214}
215
216/**
217 * Restore the focus styles unset by the previous rule.
218 */
219
220button:-moz-focusring,
221[type="button"]:-moz-focusring,
222[type="reset"]:-moz-focusring,
223[type="submit"]:-moz-focusring {
224 outline: 1px dotted ButtonText;
225}
226
227/**
228 * Correct the padding in Firefox.
229 */
230
231fieldset {
232 padding: 0.35em 0.75em 0.625em;
233}
234
235/**
236 * 1. Correct the text wrapping in Edge and IE.
237 * 2. Correct the color inheritance from `fieldset` elements in IE.
238 * 3. Remove the padding so developers are not caught out when they zero out
239 * `fieldset` elements in all browsers.
240 */
241
242legend {
243 box-sizing: border-box; /* 1 */
244 color: inherit; /* 2 */
245 display: table; /* 1 */
246 max-width: 100%; /* 1 */
247 padding: 0; /* 3 */
248 white-space: normal; /* 1 */
249}
250
251/**
252 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
253 */
254
255progress {
256 vertical-align: baseline;
257}
258
259/**
260 * Remove the default vertical scrollbar in IE 10+.
261 */
262
263textarea {
264 overflow: auto;
265}
266
267/**
268 * 1. Add the correct box sizing in IE 10.
269 * 2. Remove the padding in IE 10.
270 */
271
272[type="checkbox"],
273[type="radio"] {
274 box-sizing: border-box; /* 1 */
275 padding: 0; /* 2 */
276}
277
278/**
279 * Correct the cursor style of increment and decrement buttons in Chrome.
280 */
281
282[type="number"]::-webkit-inner-spin-button,
283[type="number"]::-webkit-outer-spin-button {
284 height: auto;
285}
286
287/**
288 * 1. Correct the odd appearance in Chrome and Safari.
289 * 2. Correct the outline style in Safari.
290 */
291
292[type="search"] {
293 -webkit-appearance: textfield; /* 1 */
294 outline-offset: -2px; /* 2 */
295}
296
297/**
298 * Remove the inner padding in Chrome and Safari on macOS.
299 */
300
301[type="search"]::-webkit-search-decoration {
302 -webkit-appearance: none;
303}
304
305/**
306 * 1. Correct the inability to style clickable types in iOS and Safari.
307 * 2. Change font properties to `inherit` in Safari.
308 */
309
310::-webkit-file-upload-button {
311 -webkit-appearance: button; /* 1 */
312 font: inherit; /* 2 */
313}
314
315/* Interactive
316 ========================================================================== */
317
318/*
319 * Add the correct display in Edge, IE 10+, and Firefox.
320 */
321
322details {
323 display: block;
324}
325
326/*
327 * Add the correct display in all browsers.
328 */
329
330summary {
331 display: list-item;
332}
333
334/* Misc
335 ========================================================================== */
336
337/**
338 * Add the correct display in IE 10+.
339 */
340
341template {
342 display: none;
343}
344
345/**
346 * Add the correct display in IE 10.
347 */
348
349[hidden] {
350 display: none;
351}
352
353/* CUSTOM CSS */
354:root {
355 --bg: #fff;
356 --bg-bright: #eee;
357 --fg: #111;
358 --link: #00f;
359 --code: #f00;
360 --border: #ccc;
361}
362
363@media (prefers-reduced-motion: no-preference) {
364 :root {
365 scroll-behavior: smooth;
366 }
367}
368
369body {
370 background-color: var(--bg);
371 color: var(--fg);
372 font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console,
373 monospace;
374 font-size: 0.9rem;
375 line-height: 1.5;
376 max-width: 50em;
377 margin: 0 auto;
378 padding: 0 1rem;
379}
380
381.site-nav,
382footer {
383 border-color: var(--fg);
384}
385
386footer {
387 border-top: 1px dotted;
388 padding-top: 1rem;
389 margin: 1rem 0;
390}
391
392footer p {
393 margin: 0;
394}
395
396.site-nav {
397 border-bottom: 1px dotted;
398}
399
400.site-nav ul {
401 list-style-type: none;
402 display: flex;
403 align-items: center;
404 padding: 0;
405}
406
407.site-nav ul li {
408 margin-right: 0.5rem;
409}
410
411h1,
412h2,
413h3,
414h4 {
415 color: var(--fg);
416}
417
418a,
419a:visited {
420 color: var(--link);
421 text-decoration: none;
422}
423
424a:hover {
425 text-decoration: underline;
426}
427
428img {
429 width: 100%;
430}
431
432table {
433 border-collapse: collapse;
434 color: var(--fg);
435 border-color: var(--bg-bright);
436 font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console,
437 monospace;
438 font-size: 0.9rem;
439}
440
441thead,
442th,
443tr,
444td {
445 border: 1px solid var(--border);
446 padding: 0.25rem;
447}
448
449pre > code,
450time,
451code {
452 font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console,
453 monospace;
454 font-size: inherit;
455}
456
457pre {
458 background-color: var(--bg-bright) !important;
459 margin: 0.5rem 0;
460 padding: 0.5rem;
461 overflow-x: auto;
462}
463
464:not(pre) > code {
465 color: var(--code);
466 font-family: inherit;
467}
468
469aside {
470 background-color: var(--bg-bright);
471 padding: 1rem;
472}
473
474blockquote {
475 background-color: var(--bg-bright);
476 border-left: 5px solid var(--link);
477 margin: 1rem 0;
478 padding: 0.5rem 1rem;
479}
480
481blockquote p {
482 margin: 0;
483}
484
485.post,
486.wiki-post {
487 display: flex;
488}
489
490.post time {
491 flex-shrink: 0;
492 margin-right: 0.5rem;
493}
494
495.wiki-post {
496 justify-content: space-between;
497}
498
499.wiki-post:first-of-type {
500 border-bottom: 1px dotted var(--fg);
501 margin-bottom: 1rem;
502}
503
504.wiki-post p {
505 margin: 0;
506}
507
508.post-metadata {
509 border: 1px dotted var(--fg);
510 border-top: none;
511 padding: 1rem;
512}
513
514.post-metadata h1 {
515 margin: 0;
516}
517
518.post-metadata p {
519 margin: 0;
520}
521
522details {
523 margin-top: 1rem;
524}
525
526summary {
527 font-weight: bold;
528}
529
530.footnote-definition {
531 display: flex;
532 justify-content: flex-start;
533 align-items: top;
534 margin: 1rem 0;
535}
536
537.footnote-definition sup {
538 font-size: inherit;
539 top: 0;
540 line-height: inherit;
541 padding-right: 0.5rem;
542}
543
544.footnote-definition sup::after {
545 content: ".";
546}
547
548.footnote-definition p {
549 display: inline-block;
550 margin: 0;
551}
552
553@media (prefers-color-scheme: dark) {
554 :root {
555 --bg: #141617;
556 --bg-bright: #191c1d;
557 --fg: #b2aea9;
558 --link: #ff79c6;
559 --code: #ffb454;
560 --border: #333;
561 }
562}
static/styles.min.css added +1
@@ -0,0 +1 @@
1/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:initial}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--bg:#fff;--bg-bright:#eee;--fg:#111;--link:#00f;--code:#f00;--border:#ccc}@media(prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{background-color:var(--bg);color:var(--fg);font-family:Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;font-size:.9rem;line-height:1.5;max-width:50em;margin:0 auto;padding:0 1rem}.site-nav,footer{border-color:var(--fg)}footer{border-top:1px dotted;padding-top:1rem;margin:1rem 0}footer p{margin:0}.site-nav{border-bottom:1px dotted}.site-nav ul{list-style-type:none;display:flex;align-items:center;padding:0}.site-nav ul li{margin-right:.5rem}h1,h2,h3,h4{color:var(--fg)}a,a:visited{color:var(--link);text-decoration:none}a:hover{text-decoration:underline}img{width:100%}table{border-collapse:collapse;color:var(--fg);border-color:var(--bg-bright);font-family:Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;font-size:.9rem}thead,th,tr,td{border:1px solid var(--border);padding:.25rem}pre>code,time,code{font-family:Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;font-size:inherit}pre{background-color:var(--bg-bright)!important;margin:.5rem 0;padding:.5rem;overflow-x:auto}:not(pre)>code{color:var(--code);font-family:inherit}aside{background-color:var(--bg-bright);padding:1rem}blockquote{background-color:var(--bg-bright);border-left:5px solid var(--link);margin:1rem 0;padding:.5rem 1rem}blockquote p{margin:0}.post,.wiki-post{display:flex}.post time{flex-shrink:0;margin-right:.5rem}.wiki-post{justify-content:space-between}.wiki-post:first-of-type{border-bottom:1px dotted var(--fg);margin-bottom:1rem}.wiki-post p{margin:0}.post-metadata{border:1px dotted var(--fg);border-top:none;padding:1rem}.post-metadata h1{margin:0}.post-metadata p{margin:0}details{margin-top:1rem}summary{font-weight:700}.footnote-definition{display:flex;justify-content:flex-start;align-items:top;margin:1rem 0}.footnote-definition sup{font-size:inherit;top:0;line-height:inherit;padding-right:.5rem}.footnote-definition sup::after{content:"."}.footnote-definition p{display:inline-block;margin:0}@media(prefers-color-scheme:dark){:root{--bg:#141617;--bg-bright:#191c1d;--fg:#b2aea9;--link:#ff79c6;--code:#ffb454;--border:#333}}\ No newline at end of file
static/syntax-theme-dark.css added +280
@@ -0,0 +1,280 @@
1/*
2 * theme "ayu" generated by syntect
3 */
4
5.z-code {
6 color: #bfbab0;
7 background-color: #0f1419;
8}
9
10.z-comment, .z-punctuation.z-definition.z-comment {
11 color: #5c6773;
12font-style: italic;
13}
14.z-variable {
15 color: #bfbab0;
16}
17.z-string, .z-constant.z-other.z-symbol {
18 color: #c2d94c;
19}
20.z-constant.z-numeric {
21 color: #f29718;
22}
23.z-string.z-regexp, .z-constant.z-character.z-escape {
24 color: #95e6cb;
25}
26.z-constant.z-language {
27 color: #f29718;
28}
29.z-constant.z-character, .z-constant.z-other {
30 color: #f29718;
31}
32.z-variable.z-member {
33 color: #ec5f67;
34}
35.z-keyword, .z-keyword.z-operator.z-word {
36 color: #ff7733;
37}
38.z-keyword.z-operator {
39 color: #f29668;
40}
41.z-punctuation.z-separator, .z-punctuation.z-terminator {
42 color: #bfbab0;
43}
44.z-punctuation.z-section {
45 color: #bfbab0;
46}
47.z-punctuation.z-accessor {
48 color: #f29668;
49}
50.z-punctuation.z-definition.z-annotation {
51 color: #bfbab0;
52}
53.z-variable.z-other.z-dollar.z-only.z-js, .z-variable.z-other.z-object.z-dollar.z-only.z-js, .z-variable.z-type.z-dollar.z-only.z-js, .z-support.z-class.z-dollar.z-only.z-js {
54 color: #e6b673;
55}
56.z-storage {
57 color: #ff7733;
58}
59.z-storage.z-type {
60 color: #ff7733;
61}
62.z-entity.z-name.z-function {
63 color: #ffb454;
64}
65.z-entity.z-name, .z-entity.z-name.z-tag, .z-entity.z-name.z-label) {
66 color: #59c2ff;
67}
68.z-entity.z-other.z-inherited-class {
69 color: #59c2ff;
70text-decoration: underline;
71}
72.z-variable.z-parameter {
73 color: #f29718;
74}
75.z-variable.z-language {
76 color: #39bae6;
77font-style: italic;
78}
79.z-entity.z-name.z-tag, .z-meta.z-tag.z-sgml {
80 color: #39bae6;
81}
82.z-punctuation.z-definition.z-tag.z-end, .z-punctuation.z-definition.z-tag.z-begin, .z-punctuation.z-definition.z-tag {
83 color: #39bae6;
84}
85.z-entity.z-other.z-attribute-name {
86 color: #ffb454;
87}
88.z-variable.z-function, .z-variable.z-annotation {
89 color: #ffb454;
90}
91.z-support.z-function, .z-support.z-macro {
92 color: #f07178;
93}
94.z-support.z-constant {
95 color: #f29668;
96font-style: italic;
97}
98.z-support.z-type, .z-support.z-class {
99 color: #39bae6;
100font-style: italic;
101}
102.z-invalid {
103 color: #ff3333;
104}
105.z-invalid.z-deprecated {
106 color: #ffffff;
107 background-color: #ff7733;
108}
109.z-meta.z-diff, .z-meta.z-diff.z-header {
110 color: #c594c5;
111}
112.z-source.z-ruby .z-variable.z-other.z-readwrite {
113 color: #ffb454;
114}
115.z-source.z-css .z-entity.z-name.z-tag, .z-source.z-sass .z-entity.z-name.z-tag, .z-source.z-scss .z-entity.z-name.z-tag, .z-source.z-less .z-entity.z-name.z-tag, .z-source.z-stylus .z-entity.z-name.z-tag {
116 color: #59c2ff;
117}
118.z-source.z-css .z-support.z-type, .z-source.z-sass .z-support.z-type, .z-source.z-scss .z-support.z-type, .z-source.z-less .z-support.z-type, .z-source.z-stylus .z-support.z-type {
119 color: #5c6773;
120}
121.z-support.z-type.z-property-name {
122 color: #39bae6;
123}
124.z-constant.z-numeric.z-line-number.z-find-in-files {
125 color: #5c6773;
126}
127.z-constant.z-numeric.z-line-number.z-match {
128 color: #ff7733;
129}
130.z-entity.z-name.z-filename.z-find-in-files {
131 color: #c2d94c;
132}
133.z-message.z-error {
134 color: #ff3333;
135}
136.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
137 color: #39bae6;
138}
139.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
140 color: #39bae6;
141}
142.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
143 color: #39bae6;
144}
145.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
146 color: #39bae6;
147}
148.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
149 color: #39bae6;
150}
151.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
152 color: #39bae6;
153}
154.z-source.z-json .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
155 color: #39bae6;
156}
157.z-source.z-json .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
158 color: #39bae6;
159}
160.z-source.z-json .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
161 color: #39bae6;
162}
163.z-markup.z-heading {
164 color: #ff7733;
165font-weight: bold;
166}
167.z-string.z-other.z-link, .z-markup.z-underline.z-link {
168 color: #95e6cb;
169text-decoration: underline;
170font-style: italic;
171}
172.z-punctuation.z-definition.z-image {
173 color: #ffb454;
174}
175.z-markup.z-italic {
176 color: #f07178;
177font-style: italic;
178}
179.z-markup.z-bold {
180 color: #f07178;
181font-weight: bold;
182}
183.z-markup.z-italic .z-markup.z-bold, .z-markup.z-bold .z-markup.z-italic {
184font-weight: bold;
185font-style: italic;
186}
187.z-markup.z-raw {
188 background-color: #bfbab0;
189}
190.z-markup.z-raw.z-inline {
191 background-color: #bfbab0;
192}
193.z-meta.z-separator {
194 color: #5c6773;
195 background-color: #bfbab0;
196font-weight: bold;
197}
198.z-markup.z-quote {
199 color: #f29718;
200font-style: italic;
201}
202.z-markup.z-list.z-numbered.z-bullet, .z-markup.z-list .z-punctuation.z-definition.z-list_item {
203 color: #95e6cb;
204}
205.z-markup.z-inserted {
206 color: #c2d94c;
207}
208.z-markup.z-changed {
209 color: #39bae6;
210}
211.z-markup.z-deleted {
212 color: #f07178;
213}
214.z-markup.z-strike {
215 color: #e6b673;
216}
217.z-markup.z-table {
218 color: #39bae6;
219 background-color: #bfbab0;
220}
221.z-text.z-html.z-markdown .z-markup.z-raw.z-inline {
222 color: #f29668;
223}
224.z-text.z-html.z-markdown .z-meta.z-dummy.z-line-break {
225 color: #5c6773;
226}
227.z-markup.z-raw.z-block.z-fenced.z-markdown {
228 color: #bfbab0;
229 background-color: #bfbab0;
230}
231.z-punctuation.z-definition.z-fenced.z-markdown, .z-variable.z-language.z-fenced.z-markdown {
232 color: #5c6773;
233 background-color: #bfbab0;
234}
235.z-variable.z-language.z-fenced.z-markdown {
236 color: #5c6773;
237}
238.z-markup.z-inserted.z-git_gutter {
239 color: #c2d94c;
240}
241.z-markup.z-changed.z-git_gutter {
242 color: #39bae6;
243}
244.z-markup.z-deleted.z-git_gutter {
245 color: #ff3333;
246}
247.z-markup.z-ignored.z-git_gutter {
248 color: #20272c;
249}
250.z-markup.z-untracked.z-git_gutter {
251 color: #20272c;
252}
253.z-gutter_color {
254 color: #ffffff;
255}
256.z-acejump.z-label.z-blue {
257 color: #ffffff;
258 background-color: #39bae6;
259}
260.z-acejump.z-label.z-green {
261 color: #ffffff;
262 background-color: #c2d94c;
263}
264.z-acejump.z-label.z-orange {
265 color: #ffffff;
266 background-color: #ff7733;
267}
268.z-acejump.z-label.z-purple {
269 color: #ffffff;
270 background-color: #f07178;
271}
272.z-sublimelinter.z-mark.z-warning {
273 color: #39bae6;
274}
275.z-sublimelinter.z-gutter-mark {
276 color: #ffffff;
277}
278.z-sublimelinter.z-mark.z-error {
279 color: #ff3333;
280}
static/syntax-theme-light.css added +407
@@ -0,0 +1,407 @@
1/*
2 * theme "GitHub" generated by syntect
3 */
4
5.z-code {
6 color: #323232;
7 background-color: #ffffff;
8}
9
10.z-comment {
11 color: #969896;
12font-style: italic;
13}
14.z-string {
15 color: #183691;
16}
17.z-regexp-operator {
18 color: #a71d5d;
19}
20.z-string.z-regexp.z-characterclass .z-punctuation.z-definition.z-string.z-begin, .z-string.z-regexp.z-characterclass .z-punctuation.z-definition.z-string.z-end {
21 color: #a71d5d;
22}
23.z-constant.z-numeric {
24 color: #0086b3;
25}
26.z-constant.z-language {
27 color: #0086b3;
28}
29.z-constant.z-character, .z-constant.z-other, .z-variable.z-other.z-constant {
30 color: #0086b3;
31}
32.z-variable {
33 color: #323232;
34}
35.z-keyword {
36 color: #a71d5d;
37font-weight: bold;
38}
39.z-bitwise-operator {
40 color: #a71d5d;
41font-weight: bold;
42}
43.z-storage {
44 color: #a71d5d;
45font-weight: bold;
46}
47.z-storage.z-type {
48 color: #a71d5d;
49font-weight: bold;
50}
51.z-entity.z-name.z-class {
52 color: #0086b3;
53}
54.z-entity.z-other.z-inherited-class {
55 color: #0086b3;
56}
57.z-entity.z-name.z-function {
58 color: #795da3;
59font-weight: bold;
60}
61.z-variable.z-parameter {
62 color: #323232;
63}
64.z-entity.z-name.z-tag {
65 color: #63a35c;
66}
67.z-entity.z-other.z-attribute-name {
68 color: #795da3;
69}
70.z-support.z-function {
71 color: #62a35c;
72}
73.z-support.z-constant {
74 color: #0086b3;
75}
76.z-support.z-type, .z-support.z-class {
77 color: #0086b3;
78}
79.z-support.z-other.z-variable {
80 color: #323232;
81}
82.z-invalid, .z-invalid.z-illegal, .z-invalid.z-deprecated {
83 color: #b52a1d;
84 background-color: #f5f5f5;
85font-weight: bold;
86}
87.z-entity.z-name.z-filename.z-find-in-files {
88 color: #323232;
89font-weight: bold;
90}
91.z-constant.z-numeric.z-line-number.z-find-in-files, .z-constant.z-numeric.z-line-number.z-match.z-find-in-files {
92 color: #b3b3b3;
93}
94.z-meta.z-diff.z-header {
95 color: #969896;
96 background-color: #ffffff;
97font-style: italic;
98}
99.z-meta.z-diff.z-header .z-punctuation.z-definition.z-from-file.z-diff {
100 color: #bd2c00;
101 background-color: #ffecec;
102font-weight: bold;
103font-style: italic;
104}
105.z-meta.z-diff.z-header .z-punctuation.z-definition.z-to-file.z-diff {
106 color: #55a532;
107 background-color: #eaffea;
108font-weight: bold;
109font-style: italic;
110}
111.z-meta.z-diff.z-range {
112 color: #969896;
113font-weight: bold;
114font-style: italic;
115}
116.z-markup.z-deleted {
117 background-color: #ffecec;
118}
119.z-markup.z-deleted .z-punctuation.z-definition.z-inserted {
120 color: #bd2c00;
121font-weight: bold;
122}
123.z-markup.z-inserted {
124 background-color: #eaffea;
125}
126.z-markup.z-inserted .z-punctuation.z-definition.z-inserted {
127 color: #55a532;
128font-weight: bold;
129}
130.z-markup.z-deleted.z-git_gutter {
131 color: #bd2c00;
132}
133.z-markup.z-inserted.z-git_gutter {
134 color: #55a532;
135}
136.z-markup.z-changed.z-git_gutter {
137 color: #0086b3;
138}
139.z-markup.z-ignored.z-git_gutter {
140 color: #b3b3b3;
141}
142.z-markup.z-untracked.z-git_gutter {
143 color: #b3b3b3;
144}
145.z-source.z-css .z-punctuation.z-definition.z-entity {
146 color: #323232;
147}
148.z-source.z-css .z-entity.z-other.z-attribute-name.z-pseudo-class, .z-source.z-css .z-entity.z-other.z-attribute-name.z-pseudo-element {
149 color: #a71d5d;
150}
151.z-source.z-css .z-meta.z-value, .z-source.z-css .z-support.z-constant, .z-source.z-css .z-support.z-function {
152 color: #323232;
153}
154.z-source.z-css .z-constant.z-other.z-color {
155 color: #ed6a43;
156}
157.z-source.z-scss .z-punctuation.z-definition.z-entity {
158 color: #323232;
159}
160.z-source.z-scss .z-entity.z-other.z-attribute-name.z-pseudo-class, .z-source.z-scss .z-entity.z-other.z-attribute-name.z-pseudo-element {
161 color: #a71d5d;
162}
163.z-source.z-scss .z-support.z-constant.z-property-value, .z-source.z-scss .z-support.z-function {
164 color: #323232;
165}
166.z-source.z-scss .z-variable {
167 color: #a71d5d;
168}
169.z-variable.z-language.z-this.z-js {
170 color: #ed6a43;
171}
172.z-source.z-js .z-entity.z-name.z-function {
173 color: #323232;
174}
175.z-source.z-js .z-meta.z-function .z-entity.z-name.z-function, .z-source.z-js .z-entity.z-name.z-function .z-meta.z-function {
176 color: #795da3;
177font-weight: bold;
178}
179.z-entity.z-name.z-type.z-new.z-js {
180 color: #795da3;
181}
182.z-variable.z-language.z-prototype.z-js {
183 color: #0086b3;
184}
185.z-source.z-js .z-support.z-function {
186 color: #0086b3;
187}
188.z-support.z-type.z-object.z-console.z-js {
189 color: #795da3;
190}
191.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
192 color: #183691;
193font-weight: bold;
194}
195.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
196 color: #323232;
197}
198.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
199 color: #183691;
200font-weight: bold;
201}
202.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
203 color: #323232;
204}
205.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
206 color: #183691;
207font-weight: bold;
208}
209.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
210 color: #323232;
211}
212.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
213 color: #183691;
214font-weight: bold;
215}
216.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
217 color: #323232;
218}
219.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
220 color: #183691;
221font-weight: bold;
222}
223.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
224 color: #323232;
225}
226.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
227 color: #183691;
228font-weight: bold;
229}
230.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
231 color: #323232;
232}
233.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
234 color: #183691;
235font-weight: bold;
236}
237.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
238 color: #323232;
239}
240.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
241 color: #183691;
242font-weight: bold;
243}
244.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
245 color: #323232;
246}
247.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
248 color: #183691;
249font-weight: bold;
250}
251.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
252 color: #323232;
253}
254.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
255 color: #183691;
256font-weight: bold;
257}
258.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
259 color: #323232;
260}
261.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
262 color: #183691;
263font-weight: bold;
264}
265.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
266 color: #323232;
267}
268.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
269 color: #183691;
270font-weight: bold;
271}
272.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
273 color: #323232;
274}
275.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
276 color: #183691;
277font-weight: bold;
278}
279.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
280 color: #323232;
281}
282.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
283 color: #183691;
284font-weight: bold;
285}
286.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
287 color: #323232;
288}
289.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
290 color: #183691;
291font-weight: bold;
292}
293.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
294 color: #323232;
295}
296.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
297 color: #183691;
298font-weight: bold;
299}
300.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
301 color: #323232;
302}
303.z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
304 color: #183691;
305font-weight: bold;
306}
307.z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
308 color: #323232;
309}
310.z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
311 color: #183691;
312font-weight: bold;
313}
314.z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
315 color: #323232;
316}
317.z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
318 color: #183691;
319font-weight: bold;
320}
321.z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
322 color: #323232;
323}
324.z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
325 color: #183691;
326font-weight: bold;
327}
328.z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
329 color: #323232;
330}
331.z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
332 color: #183691;
333font-weight: bold;
334}
335.z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
336 color: #323232;
337}
338.z-source.z-python .z-keyword {
339font-weight: bold;
340}
341.z-source.z-python .z-storage {
342font-weight: bold;
343}
344.z-source.z-python .z-storage.z-type {
345font-weight: bold;
346}
347.z-source.z-python .z-entity.z-name.z-function {
348 color: #323232;
349font-weight: bold;
350}
351.z-source.z-php .z-entity.z-name.z-type.z-class {
352 color: #323232;
353font-weight: bold;
354}
355.z-variable.z-language.z-ruby {
356 color: #ed6a43;
357}
358.z-entity.z-name.z-type.z-module.z-ruby {
359 color: #795da3;
360font-weight: bold;
361}
362.z-entity.z-name.z-type.z-class.z-ruby {
363 color: #795da3;
364font-weight: bold;
365}
366.z-entity.z-other.z-inherited-class.z-ruby {
367 color: #795da3;
368font-weight: bold;
369}
370.z-punctuation.z-definition.z-heading.z-markdown, .z-punctuation.z-definition.z-italic.z-markdown, .z-punctuation.z-definition.z-bold.z-markdown, .z-punctuation.z-definition.z-string.z-begin.z-markdown, .z-punctuation.z-definition.z-string.z-end.z-markdown, .z-punctuation.z-definition.z-metadata.z-markdown, .z-punctuation.z-definition.z-link.z-markdown, .z-punctuation.z-definition.z-blockquote.z-markdown, .z-punctuation.z-definition.z-raw.z-markdown {
371 color: #a71d5d;
372}
373.z-text.z-html.z-markdown .z-meta.z-separator {
374 color: #b3b3b3;
375}
376.z-text.z-html.z-markdown .z-markup.z-heading {
377font-weight: bold;
378}
379.z-text.z-html.z-markdown .z-markup.z-raw.z-block {
380 color: #323232;
381}
382.z-text.z-html.z-markdown .z-markup.z-raw.z-inline {
383 color: #323232;
384}
385.z-text.z-html.z-markdown .z-meta.z-link, .z-text.z-html.z-markdown .z-meta.z-image {
386 color: #4183c4;
387}
388.z-text.z-html.z-markdown .z-markup.z-underline.z-link, .z-text.z-html.z-markdown .z-constant.z-other.z-reference {
389font-style: italic;
390}
391.z-text.z-html.z-markdown .z-markup.z-list {
392 color: #ed6a43;
393}
394.z-text.z-html.z-markdown .z-markup.z-bold {
395font-weight: bold;
396}
397.z-text.z-html.z-markdown .z-markup.z-italic {
398font-style: italic;
399}
400.z-text.z-html.z-markdown .z-markup.z-bold .z-markup.z-italic {
401font-weight: bold;
402font-style: italic;
403}
404.z-text.z-html.z-markdown .z-markup.z-italic .z-markup.z-bold {
405font-weight: bold;
406font-style: italic;
407}
templates/base.html added +48
@@ -0,0 +1,48 @@
1<!doctype html>
2<html lang="en">
3<head>
4 {% block title %}
5 <title>cleberg.net</title>
6 {% endblock %}
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <meta name="theme-color" content="#111" media="(prefers-color-scheme: dark)">
9 <meta name="theme-color" content="#fff" media="(prefers-color-scheme: light)">
10 {% if page %}
11 {% if page.description %}
12 <meta name="description" content="{{ page.description }}">
13 {% elif config.description %}
14 <meta name="description" content="{{ config.description }}">
15 {% endif %}
16 {% elif config.description %}
17 <meta name="description" content="{{ config.description }}" />
18 {% endif %}
19
20 {% block rss %}
21 <link rel="alternate" type="application/atom+xml" title="RSS" href="{{ get_url(path="atom.xml", trailing_slash=false) }}">
22 {% endblock %}
23
24 {% block css %}
25 <link rel="stylesheet" href="/styles.min.css">
26 {% endblock css %}
27
28 {% block extra_head %}{% endblock extra_head %}
29</head>
30<body>
31 <nav class="site-nav" aria-label="site-nav" role="navigation">
32 <ul>
33 <li><a href="/">Home</a></li>
34 <li><a href="/blog/">Blog</a></li>
35 <li><a href="/services/">Services</a></li>
36 <li><a href="/wiki/">Wiki</a></li>
37 </ul>
38 </nav>
39
40 <main>{% block content %}{% endblock content %}</main>
41
42 <footer>
43 <p>Last build: {{ now() | date(format="%Y-%m-%d %H:%M:%S") }}</p>
44 <p><a href="https://git.cleberg.net/?p=cleberg.net.git;a=summary" target="_blank" rel="noopener">Source Code</a></p>
45 <p><a href="/atom.xml">Atom RSS Feed</a></p>
46 </footer>
47</body>
48</html>
templates/blog.html added +18
@@ -0,0 +1,18 @@
1{% extends "base.html" %}
2
3{% block content %}
4<h1>{{ section.title }}</h1>
5<p>Use <code>Ctrl + F</code> to search blog post titles for keywords.</p>
6<p>You can also add the <a href="/atom.xml">Atom RSS Feed</a> to your feed reader.</p>
7{% set post_date = "1970-01-01" | date(format="%Y") %}
8{% for page in section.pages %}
9 {% if post_date != page.date | date(format="%Y") %}
10 {% set_global post_date = page.date | date(format="%Y") %}
11 <h2>{{ post_date }}</h2>
12 {% endif %}
13 <div class="post">
14 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date | date(format='%Y-%m-%d') }}</time>
15 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
16 </div>
17{% endfor %}
18{% endblock content %}
templates/index.html added +42
@@ -0,0 +1,42 @@
1{% extends "base.html" %}
2
3{% block content %}
4<section>
5 <h1>cleberg.net</h1>
6 <pre>pub rsa4096 2022-11-16 [SC]
7 <a href="/gpg.txt">3917 973F B159 BBB8 6194 5385 6945 1A51 7AC0 CB37</a>
8uid [ultimate] Christian Cleberg &lt;hello@cleberg.net&gt;</pre>
9</section>
10<section>
11 <h2>Recent Blog Posts</h2>
12 {% set section = get_section(path="blog/_index.md") %}
13 {% set i = 0 %}
14 {% for page in section.pages %}
15 {% if i < 3%}
16 {% set_global i = i + 1%}
17 <div class="post">
18 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date | date(format='%Y-%m-%d') }}</time>
19 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
20 </div>
21 {% endif %}
22 {% endfor %}
23 <br>
24 <a href="/blog/">All Posts &rarr;</a>
25</section>
26<section>
27 <h2>Everything Else</h2>
28 <ul>
29 <li><a href="/salary/">Salary</a></li>
30 <li><a href="/services/">Services</a></li>
31 <li><a href="/wiki/">Wiki</a></li>
32 </ul>
33</section>
34<section>
35 <h2>Contact</h2>
36 <ul>
37 <li>hello [at] cleberg.net (<a href="/gpg.txt">GPG</a>)</li>
38 <li><a href="https://matrix.to/#/@cyborg:matrix.tchncs.de" target="_blank" rel="noopener">@cyborg:@matrix.tchncs.de</a></li>
39 <li><a href="https://git.cleberg.net/" target="_blank" rel="noopener">Git</a></li>
40 </ul>
41</section>
42{% endblock content %}
templates/page.html added +51
@@ -0,0 +1,51 @@
1{% extends "base.html" %}
2
3{% block title %}
4<title>{{ page.title }} | cleberg.net</title>
5{% endblock %}
6
7{% block css %}
8<link rel="stylesheet" href="/syntax-theme-dark.css" media="(prefers-color-scheme: dark)">
9<link rel="stylesheet" href="/syntax-theme-light.css" media="(prefers-color-scheme: light)">
10<link rel="stylesheet" href="/styles.css">
11{% endblock css %}
12
13{% block content %}
14<section class="post-metadata">
15 <h1>{{ page.title }}</h1>
16 {% if page.date %}
17 <p><time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time></p>
18 {% endif %}
19 {% if page.updated %}
20 <p>Last Updated: <time datetime="{{ page.updated | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.updated }}</time></p>
21 {% endif %}
22 <p>{{ page.word_count }} words; {{ page.reading_time }} minute(s)</p>
23{% if page.toc %}
24 <details open>
25 <summary>Table of Contents</summary>
26 <nav class="page-nav" aria-label="page-nav" role="navigation">
27 <ul>
28 {% for h1 in page.toc %}
29 <li>
30 <a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
31 {% if h1.children %}
32 <ul>
33 {% for h2 in h1.children %}
34 <li>
35 <a href="{{ h2.permalink | safe }}">{{ h2.title }}</a>
36 </li>
37 {% endfor %}
38 </ul>
39 {% endif %}
40 </li>
41 {% endfor %}
42 </ul>
43 </nav>
44 </details>
45{% endif %}
46</section>
47
48<article role="article">
49{{ page.content | safe }}
50</article>
51{% endblock content %}
templates/wiki.html added +16
@@ -0,0 +1,16 @@
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') }}">{{ page.updated | date(format='%Y-%m-%d')}}</p>
14 </div>
15{% endfor %}
16{% endblock content %}
theme/static/styles.css deleted −39
@@ -1,39 +0,0 @@
1body {
2 font-family:
3 Menlo,
4 Consolas,
5 Monaco,
6 Liberation Mono,
7 Lucida Console,
8 monospace;
9 font-size: 0.9rem;
10 margin: 0 auto;
11 max-width: 50em;
12 padding: 1rem;
13}
14
15img {
16 height: auto;
17 max-width: 100%;
18 width: 100%;
19}
20
21pre {
22 border: 1px solid #000;
23 overflow: scroll;
24 padding: 0.5rem;
25}
26
27:not(pre) code {
28 color: #f00;
29}
30
31nav ul {
32 padding: 0;
33}
34
35nav li {
36 display: inline;
37 list-style: none;
38 padding-right: 0.25rem;
39}
theme/static/styles.min.css deleted −1
@@ -1 +0,0 @@
1body{font-family:Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;font-size:.9rem;margin:0 auto;max-width:50em;padding:1rem}img{height:auto;max-width:100%;width:100%}pre{border:1px solid #000;overflow:scroll;padding:.5rem}:not(pre) code{color:red}nav ul{padding:0}nav li{display:inline;list-style:none;padding-right:.25rem}\ No newline at end of file
theme/templates/base.html deleted −22
@@ -1,22 +0,0 @@
1
2<!doctype html>
3<html lang="en-us">
4<head>
5 {% block head %}
6 <meta charset="utf-8">
7 <title>
8 {% block subtitle %}{% endblock %}
9 {% block title %}{{ site_name | default("weblorg website") }}{% endblock %}
10 </title>
11 <meta name="viewport" content="width=device-width, initial-scale=1">
12 {% if site_owner is defined %}<meta name="author" content="{{ site_owner }}" >{% endif %}
13 {% if site_description is defined %}<meta name="description" content="{{ site_description }}" >{% endif %}
14 {% if site_keywords is defined %}<meta name="keywords" content="{{ site_keywords }}" >{% endif %}
15 <link rel="stylesheet" href="{{ url_for("static", file="styles.min.css") }}" type="text/css">
16 {% block meta %}{% endblock %}
17 {% endblock %}
18</head>
19<body>
20 <main>{% block main %}{% endblock %}</main>
21</body>
22</html>
theme/templates/blog.html deleted −15
@@ -1,15 +0,0 @@
1{% extends "base.html" %}
2
3{% block subtitle %}Blog | {% endblock %}
4{% block main %}
5<p><a href="/">&larr; home</a></p>
6<h1>Blog</h1>
7<p>Use <code>Ctrl + F</code> to search blog post titles for keywords.</p>
8<p>You can also add the <a href="/feed.xml">RSS Feed</a> to your feed reader.</p>
9{% for post in posts %}
10<div class="post">
11 <time datetime='{{ post.date | strftime("%Y-%m-%d") }}'>{{ post.date|strftime("%Y-%m-%d") }}</time>
12 <a href='{{ url_for("blog", slug=post.slug) }}'>{{ post.title }}</a>
13</div>
14{% endfor %}
15{% endblock %}
theme/templates/feed.xml deleted −37
@@ -1,37 +0,0 @@
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("Simple Blog") }}</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 deleted −55
@@ -1,55 +0,0 @@
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 <table>
10 <thead>
11 <tr>
12 <th>link</th>
13 <th>desc</th>
14 </tr>
15 </thead>
16 <tbody>
17 <tr>
18 <td><a href="/blog/">blog</a></td>
19 <td>my personal blog</td>
20 </tr>
21 <tr>
22 <td><a href="https://git.cleberg.net">git</a></td>
23 <td>personal git hosting</td>
24 </tr>
25 <tr>
26 <td><a href="https://matrix.to/#/@cyborg:matrix.tchncs.de">matrix</a></td>
27 <td>encrypted chat</td>
28 </tr>
29 <tr>
30 <td><a href="/feed.xml">rss</a></td>
31 <td>feed for my personal blog</td>
32 </tr>
33 <tr>
34 <td><a href="/salary/">salary</a></td>
35 <td>my salary log</td>
36 </tr>
37 <tr>
38 <td><a href="/services/">services</a></td>
39 <td>my public services</td>
40 </tr>
41 <tr>
42 <td><a href="/wiki/">wiki</a></td>
43 <td>my personal wiki</td>
44 </tr>
45 </tbody>
46 </table>
47</section>
48<footer>
49 <p>
50 Last Build: &lt;<time datetime='{{ now() | strftime("%Y-%m-%d %H:%M:%S") }}'
51 >{{ now() | strftime("%Y-%m-%d %a %H:%M:%S") }}</time
52 >&gt;
53 </p>
54</footer>
55{% endblock %}
theme/templates/page.html deleted −12
@@ -1,12 +0,0 @@
1{% extends "base.html" %}
2
3{% block subtitle %}
4 {{ post.title }} |
5{% endblock %}
6{% block main %}
7<p><a href="/">&larr; home</a></p>
8<article role="article">
9 <h1>{{ post.title }}</h1>
10 <section>{{ post.html | safe }}</section>
11</article>
12{% endblock %}
theme/templates/post.html deleted −22
@@ -1,22 +0,0 @@
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<p><a href="/">&larr; home</a></p>
11<article role="article">
12 <section class="post-metadata">
13 <h1 class="post__title">{{ post.title }}</h1>
14 {% if post.date is defined %}
15 <time datetime='{{ post.date|strftime("%Y-%m-%d") }}'
16 >&lt;{{ post.date|strftime("%Y-%m-%d %a %H:%M:%S") }}&gt;</time
17 >
18 {% endif %}
19 </section>
20 <section>{{ post.html | safe }}</section>
21</article>
22{% endblock %}
theme/templates/wiki.html deleted −14
@@ -1,14 +0,0 @@
1{% extends "base.html" %}
2
3{% block subtitle %}Wiki | {% endblock %}
4{% block main %}
5<p><a href="/">&larr; home</a></p>
6<h1>Wiki</h1>
7<p>Use <code>Ctrl + F</code> to search wiki pages for keywords.</p>
8{% for post in posts %}
9 <div class="post">
10 <time datetime='{{ post.date | strftime("%Y-%m-%d") }}'>{{ post.date|strftime("%Y-%m-%d") }}</time>
11 <a href='{{ url_for("wiki", slug=post.slug) }}'>{{ post.title }}</a>
12 </div>
13{% endfor %}
14{% endblock %}