Commit 25945b8fea

25945b8fead989cca09a23983623b63ce36dcc0c

parent: 22b526be60

Unsigned

cmc <hello@cleberg.net> · 2024-01-09 02:11 UTC

feat: total re-write from Emacs org-mode to Zola markdown

Layout: unified · split

.gitignore +2 −1
@@ -1,2 +1,3 @@
1public/ 1public/
2sitemap.org 2static/syntax*
3.DS_Store
README.md added +38
@@ -0,0 +1,38 @@
1# cleberg.net
2
3## Overview
4
5This website & blog uses Markdown and [Zola](https://www.getzola.org/).
6
7## Configuration
8
9If you want to replicate this project structure, you'll need to customize your
10=.emacs= file appropriately, or load a custom =.el= file.
11
12If you're within emacs while changing =.emacs=, you'll need to reload the
13configuration with =M-x load-file= and hit enter to reload the current file
14(=.emacs=).
15
16See the [[./elisp/publish.el][publish.el]] file for this repository's
17configuration.
18
19* Building
20
21Local testing:
22
23```sh
24zola serve
25```
26
27Building:
28
29```sh
30zola build
31```
32
33If you want to build locally and push remotely, you can include `zola build`
34in your CI\CD pipeline, or simply use `scp` after building.
35
36```sh
37scp -r public/* user@remote_host:/var/www/your_website/
38```
README.org deleted −35
@@ -1,35 +0,0 @@
1#+title: README
2
3* Overview
4
5This project uses [[https://orgmode.org/][org-mode]] to structure all documents, including the index page
6and sub-pages such as blog posts.
7
8* Configuration
9
10If you want to replicate this project structure, you'll need to customize your
11=.emacs= file appropriately, or load a custom =.el= file.
12
13If you're within emacs while changing =.emacs=, you'll need to reload the
14configuration with =M-x load-file= and hit enter to reload the current file
15(=.emacs=).
16
17See the [[./elisp/publish.el][publish.el]] file for this repository's
18configuration.
19
20* Building
21
22When ready to publish, open any of the =.org= files in the project and execute
23=M-x org-publish-current-project=.
24
25If you want to force emacs to re-publish all files, including files without
26changes, execute =C-u M-x org-publish-current-project=.
27
28The resulting files will be published to the =public/= directory within the
29repository, which is ignored via the =.gitignore= file. If you wish to publish
30the files remotely, you can change the =:publishing-directory= variable in
31=publish.el= or do a sync step outside of emacs:
32
33#+begin_src sh
34scp -r public/* ubuntu:/var/www/cleberg.net/
35#+end_src
blog/2018-11-28-cpp-compiler.org deleted −127
@@ -1,127 +0,0 @@
1#+date:2018-11-28
2#+title: The C++ Compiler
3
4* A Brief Introduction
5
6[[https://en.wikipedia.org/wiki/C%2B%2B][C++]] is a general-purpose programming language with object-oriented, generic, and
7functional features in addition to facilities for low-level memory manipulation.
8
9The source code, shown in the snippet below, must be compiled before it can be
10executed. There are many steps and intricacies to the compilation process, and
11this post was a personal exercise to learn and remember as much information as I
12can.
13
14#+BEGIN_SRC cpp
15#include <iostream>
16
17int main()
18{
19 std::cout << "Hello, world!\n";
20}
21#+END_SRC
22
23* Compilation Process
24
25** An Overview
26
27Compiling C++ projects is a frustrating task most days. Seemingly nonexistent
28errors keeping your program from successfully compiling can be annoying
29(especially since you know you wrote it perfectly the first time, right?).
30
31I'm learning more and more about C++ these days and decided to write this
32concept down so that I can cement it even further in my own head. However, C++
33is not the only compiled language. Check out [[https://en.wikipedia.org/wiki/Compiled_language][the Wikipedia entry for compiled
34languages]] for more examples of compiled languages.
35
36I'll start with a wonderful, graphical way to conceptualize the C++ compiler.
37View [[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
38graphic and an explanation. The goal of the compilation process is to take the
39C++ code and produce a shared library, dynamic library, or an executable file.
40
41** Compilation Phases
42
43Let's break down the compilation process. There are four major steps to
44compiling C++ code.
45
46*** Step 1
47
48The first step is to expand the source code file to meet all dependencies. The
49C++ preprocessor includes the code from all the header files, such as
50=#include <iostream>=. Now, what does that mean? The previous example includes
51the =iostream= header. This tells the computer that you want to use the
52=iostream= standard library, which contains classes and functions written in the
53core language. This specific header allows you to manipulate input/output
54streams. After all this, you'll end up which a temporary file that contains the
55expanded source code.
56
57In the example of the C++ code above, the =iostream= class would be included
58in the expanded code.
59
60*** Step 2
61
62After the code is expanded, the compiler comes into play. The compiler takes the
63C++ code and converts this code into the assembly language, understood by the
64platform. You can see this in action if you head over to the [[https://godbolt.org][GodBolt Compiler
65Explorer]], which shows C++ being converted into assembly dynamically.
66
67For example, the =Hello, world!= code snippet above compiles into the following
68assembly code:
69
70#+BEGIN_SRC asm
71.LC0:
72 .string "Hello, world!\n"
73main:
74 push rbp
75 mov rbp, rsp
76 mov esi, OFFSET FLAT:.LC0
77 mov edi, OFFSET FLAT:_ZSt4cout
78 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*)
79 mov eax, 0
80 pop rbp
81 ret
82__static_initialization_and_destruction_0(int, int):
83 push rbp
84 mov rbp, rsp
85 sub rsp, 16
86 mov DWORD PTR [rbp-4], edi
87 mov DWORD PTR [rbp-8], esi
88 cmp DWORD PTR [rbp-4], 1
89 jne .L5
90 cmp DWORD PTR [rbp-8], 65535
91 jne .L5
92 mov edi, OFFSET FLAT:_ZStL8__ioinit
93 call std::ios_base::Init::Init() [complete object constructor]
94 mov edx, OFFSET FLAT:__dso_handle
95 mov esi, OFFSET FLAT:_ZStL8__ioinit
96 mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
97 call __cxa_atexit
98.L5:
99 nop
100 leave
101 ret
102_GLOBAL__sub_I_main:
103 push rbp
104 mov rbp, rsp
105 mov esi, 65535
106 mov edi, 1
107 call __static_initialization_and_destruction_0(int, int)
108 pop rbp
109 ret
110#+END_SRC
111
112*** Step 3
113
114Third, the assembly code generated by the compiler is assembled into the object
115code for the platform. Essentially, this is when the compiler takes the assembly
116code and assembles it into machine code in a binary format. After researching
117this online, I figured out that a lot of compilers will allow you to stop
118compilation at this step. This would be useful for compiling each source code
119file separately. This saves time later if a single file changes; only that file
120needs to be recompiled.
121
122*** Step 4
123
124Finally, the object code file generated by the assembler is linked together with
125the object code files for any library functions used to produce a shared
126library, dynamic library, or an executable file. It replaces all references to
127undefined symbols with the correct addresses.
blog/2018-12-08-aes-encryption.org deleted −103
@@ -1,103 +0,0 @@
1#+date: 2018-12-08
2#+title: AES Encryption
3
4* Basic AES
5
6If you're not familiar with encryption techniques, [[https://en.wikipedia.org/wiki/Advanced_Encryption_Standard][AES]] is the *Advanced
7Encryption Standard*. This specification was established by the National
8Institute of Standards and Technology, sub-selected from the Rijndael family of
9ciphers (128, 192, and 256 bits) in 2001. Furthering its popularity and status,
10the US government chose AES as their default encryption method for top-secret
11data, removing the previous standard which had been in place since 1977.
12
13AES has proven to be an extremely safe encryption method, with 7-round and
148-round attacks making no material improvements since the release of this
15encryption standard almost two decades ago.
16
17#+BEGIN_QUOTE
18Though many papers have been published on the cryptanalysis of AES, the
19fastest single-key attacks on round-reduced AES variants [20, 33] so far are
20only slightly more powerful than those proposed 10 years ago [23,24].
21- [[http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf][Bogdonav, et al.]]
22#+END_QUOTE
23
24* How Secure is AES?
25
26In theory, AES-256 is non-crackable due to the massive number of combinations
27that can be produced. However, AES-128 is no longer recommended as a viable
28implementation to protect important data.
29
30A 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
31understand. Basically AES encrypts the data by obscuring the relationship
32between the data and the encrypted data. Additionally, this method spreads the
33message out. Lastly, the key produced by AES is the secret to decrypting
34it. Someone may know the method of AES, but without the key, they are powerless.
35
36To obscure and spread the data out, AES creates a substitution-permutation
37network. 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
38network sends the data through a set of S boxes (using the unique key) to
39substitute the bits with another block of bits. Then, a P box will permutate, or
40rearrange, the bits. This is done over and over, with the key being derived from
41the last round. For AES, the key size specifies the number of transformation
42rounds: 10, 12, and 14 rounds for 128-bit, 192-bit, and 256-bit keys,
43respectively.
44
45* The Process
46
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
67are a few different attacks that have been used to show vulnerabilities with the
68use of this technology. Side-channel attacks use inadvertent leaks of data from
69the hardware 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?
80
81There are limitless reasons to enable encryption at-rest or in-transit for
82various aspects of your digital life. You can research specific examples, such
83as [[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
84list a few basic reasons to always enable encryption, where feasible:
85
861. Privacy is a human right and is recognized as a national right in some
87 countries (e.g., [[https://www.law.cornell.edu/wex/fourth_amendment][US Fourth Amendment]]).
882. "Why not?" Encryption rarely affects performance or speed, so there's usually
89 not a reason to avoid it in the first place.
903. Your digital identity and activity (texts, emails, phone calls, online
91 accounts, etc.) are extremely valuable and can result in terrible
92 consequences, such as identity theft, if leaked to other parties. Encrypting
93 this data prevents such leaks from ruining lives.
944. Wiping or factory-resetting does not actually wipe all data from the storage
95 device. There are methods to read data from the physical disks/boards inside
96 devices.
975. Corporations, governments, and other nefarious groups/individuals are
98 actively looking for ways to collect personal information about anyone they
99 can. If someone's data is unencrypted, that person may become a target due to
100 the ease of data collection.
101
102**Read More:**
103- [[http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf][Federal Information Processing Standards Publication 197]]
blog/2019-01-07-useful-css.org deleted −181
@@ -1,181 +0,0 @@
1#+date: 2019-01-07
2#+title: Useful CSS Snippets
3
4* Introduction to CSS
5
6[[https://en.wikipedia.org/wiki/CSS][CSS]], the language used to markup HTML code and make it "pretty", is one of the
7most effective ways to increase the attractiveness of a website. It can also
8lead to increased user engagement, retention, and satisfaction. In fact, there
9are whole career fields are dedicated to the improvement of user experiences,
10known as UI design and UX design.
11
12Some web developers are used to the common CSS properties, such as element
13sizing, fonts, colors, etc., but are not as well versed in less-used properties
14and values such as =flexbox=, =clip-path=, and =transform=. This article will
15provide some insight into the less-used and unique CSS properties.
16
17* CSS Variables
18
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
45
46Box shadows were once my mortal enemy. No matter how hard I tried, I just
47couldn't get them to work how I wanted. Because of this, my favorite discovery
48has been CSSMatic's [[https://www.cssmatic.com/box-shadow][box shadow generator]]. It provides an excellent tool to
49generate box shadows using their simple sliders. Surprisingly, this is the
50reason I learned how box shadows work! You can use the sliders and watch how the
51CSS code changes in the image that is displayed. Through this, you should
52understand that the basic structure for box shadows is:
53
54#+BEGIN_SRC css
55box-shadow: inset horizontal vertical blur spread color;
56#+END_SRC
57
58Now, let's look at some basic examples! You can copy and paste the following
59code into a site like CodePen or your own HTML files. Feel free to play around
60with the code, experiment, and learn.
61
62**Box Shadow #1**
63
64#+BEGIN_SRC html
65<div class="shadow-examples">
66 <div class="box effect1">
67 <h3>Effect 1</h3>
68 </div>
69</div>
70#+END_SRC
71
72#+BEGIN_SRC css
73.box h3 {
74 text-align: center;
75 position: relative;
76 top: 80px;
77}
78.box {
79 width: 70%;
80 height: 200px;
81 background: #fff;
82 margin: 40px auto;
83}
84.effect1 {
85 box-shadow: 0 10px 6px -6px #777;
86}
87#+END_SRC
88
89**Box Shadow #2**
90
91#+BEGIN_SRC html
92<div class="shadow-examples">
93 <div class="box effect2">
94 <h3>Effect 2</h3>
95 </div>
96</div>
97#+END_SRC
98
99#+BEGIN_SRC css
100.box h3 {
101 text-align: center;
102 position: relative;
103 top: 80px;
104}
105.box {
106 width: 70%;
107 height: 200px;
108 background: #fff;
109 margin: 40px auto;
110}
111.effect2 {
112 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
113}
114#+END_SRC
115
116Try these box shadows out on your own and see how changing each shadow value
117works.
118
119* CSS Flexbox
120
121Now, let's move on to the best part of this article: flexbox. The flexbox is by
122far my favorite new toy. I originally stumbled across this solution after
123looking for more efficient ways of centering content horizontally AND
124vertically. I had used a few hack-ish methods before, but flexbox throws those
125out the window. The best part of it all is that flexbox is /dead simple/.
126
127Flexbox pertains to the parent div of any element. You want the parent to be the
128flexbox in which items are arranged to use the flex methods. It's easier to see
129this in action that explained, so let's see an example.
130
131**Flexbox**
132
133#+BEGIN_SRC html
134<div class="flex-examples">
135 <div class="sm-box">
136 <h3>1</h3>
137 </div>
138 <div class="sm-box">
139 <h3>2</h3>
140 </div>
141</div>
142#+END_SRC
143
144#+BEGIN_SRC css
145.flex-examples {
146 display: flex;
147 flex-wrap: wrap;
148 justify-content: flex-start;
149 align-items: center;
150 padding: 10px;
151 background-color: #f2f2f2;
152}
153.sm-box {
154 display: flex;
155 justify-content: center;
156 align-items: center;
157 width: 20%;
158 height: 100px;
159 background: #fff;
160 margin: 40px 10px;
161}
162#+END_SRC
163
164You may notice that we no longer need to use the =top= property for the =h3=
165elements in our code. This is because we set the display box to be a flex
166container for the small boxes, AND we made the small boxes flex containers for
167their elements (the h3 tags). Flex boxes can be nested like this to center
168content that is inside centered content.
169
170For the example above, we designated the =justify-content= property to be
171=flex-start= so that the boxes stack from the left side of the screen. This
172property can be changed to =center= to make the boxes appear in the center of
173the screen.
174
175For an interactive example, [[https://codepen.io/LandonSchropp/pen/KpzzGo][check out this CodePen]] from [[https://codepen.io/LandonSchropp/][LandonScropp]]. Resize the
176window with dice to see how they collapse and re-align.
177
178* Even More CSS
179
180For more inspiration, you can visit [[https://www.codepen.io][CodePen]], [[https://dribbble.com][Dribbble]], or [[https://uimovement.com][UI Movement]] to browse
181the collections of many amazing web designers.
blog/2019-09-09-audit-analytics.org deleted −213
@@ -1,213 +0,0 @@
1#+date: 2019-09-09
2#+title: Data Analysis in Auditing
3
4* What Are Data Analytics?
5
6A quick aside before I dive into this post: `data analytics` is a vague term
7that has become popular in recent years. Think of a `data analytic` as the
8output of any data analysis you perform. For example, a pivot table or a pie
9chart could be a data analytic.
10
11[[https://en.wikipedia.org/wiki/Data_analysis][Data analysis]] is a process that utilizes statistics and other mathematical
12methods to discover useful information within datasets. This involves examining,
13cleaning, transforming, and modeling data so that you can use the data to
14support an opinion, create more useful viewpoints, and gain knowledge to
15implement into audit planning or risk assessments.
16
17One of the common mistakes that managers (and anyone new to the process) make is
18assuming that everything involved with this process is "data analytics". In
19fact, data analytics are only a small part of the process.
20
21See *Figure 1* for a more accurate representation of where data analysis sits
22within the full process. This means that data analysis does not include
23querying or extracting data, selecting samples, or performing audit tests.
24These steps can be necessary for an audit (and may even be performed by the same
25associates), but they are not data analytics.
26
27#+CAPTION: The Intelligence Cycle
28[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/intelligence_cycle-min.png]]
29
30* Current Use of Analytics in Auditing
31
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
41a systematic, disciplined approach to evaluate and improve the effectiveness
42of risk management, control and governance processes.
43
44- The IIA's Definition of Internal Audit
45#+END_QUOTE
46
47Part of the blame for the slow adoption of data analysis can be attributed to
48the fact that internal auditing is strongly based on tradition and following the
49precedents set by previous auditors. However, there can be no progress without
50auditors who are willing to break the mold and test new audit techniques. In
51fact, as of 2018, [[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
52analytics]] in North America. This number should be as close as possible to
53100%. I have never been part of an audit that would not have benefited from data
54analytics.
55
56So, how do internal audit functions remedy this situation? It's definitely not
57as easy as walking into work on Monday and telling your Chief Audit Executive
58that you're going to start implementing analytics in the next audit. You need a
59plan and a system to make the analysis process as effective as possible.
60
61* The DELTA Model
62
63One of the easiest ways to experiment with data analytics and gain an
64understanding of the processes is to implement them within your own department.
65But how do we do this if we've never worked with analysis before? One of the
66most common places to start is to research some data analysis models currently
67available. For this post, we'll take a look at the DELTA model. You can take a
68look at **Figure 2** for a quick overview of the model.
69
70The DELTA model sets a few guidelines for areas wanting to implement data
71analytics so that the results can be as comprehensive as possible:
72
73- *Data*: Must be clean, accessible, and (usually) unique.
74- *Enterprise-Wide Focus*: Key data systems and analytical resources must be
75 available for use (by the Internal Audit Function).
76- *Leaders*: Must promote a data analytics approach and show the value of
77 analytical results.
78- *Targets*: Must be set for key areas and risks that the analytics can be
79 compared against (KPIs).
80- *Analysts*: There must be auditors willing and able to perform data 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
87
88Once the Internal Audit Function has decided that they want to start using data
89analytics internally and have ensured they're properly set up to do so, they
90need to figure out what they will be testing against. Key Performance Indicators
91(KPIs) are qualitative or quantitative factors that can be evaluated and
92assessed to determine if the department is performing well, usually compared to
93historical or industry benchmarks. Once KPIs have been agreed upon and set,
94auditors can use data analytics to assess and report on these KPIs. This allows
95the person performing the analytics the freedom to express opinions on the
96results, whereas the results are ambiguous if no KPIs exist.
97
98It should be noted that tracking KPIs in the department can help ensure you have
99a rigorous Quality Assurance and Improvement Program (QAIP) in accordance with
100some applicable standards, such as IPPF Standard 1300.
101
102#+BEING_QUOTE
103The chief audit executive must develop and maintain a quality assurance and
104improvement program that covers all aspects of the internal audit activity.
105
106- IPPF Standard 1300
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#+END_QUOTE
121
122The hardest part of finding KPIs is to determine which KPIs are appropriate for
123your department. Since every department is different and has different goals,
124KPIs will vary drastically between companies. To give you an idea of where to
125look, here are some ideas I came up with when discussing the topic with a few
126colleagues.
127
128- Efficiency/Budgeting:
129 - Audit hours to staff utilization ratio (annual hours divided by total annual
130 work hours).
131 - Audit hours compared to the number of audits completed.
132 - Time between audit steps or to complete the whole audit. E.g., time from
133 fieldwork completion to audit report issuance.
134- Reputation:
135 - The frequency that management has requested the services of the IAF.
136 - Management, audit committee, or external audit satisfaction survey results.
137 - Education, experience, certifications, tenure, and training of the auditors
138 on staff.
139- Quality:
140 - Number and frequency of audit findings. Assign monetary or numerical values,
141 if possible.
142 - Percentage of recommendations issued and implemented.
143- Planning:
144 - Percentage or number of key risks audited per year or per audit.
145 - Proportion of audit universe audited per year.
146
147* Data Analysis Tools
148
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
173
174Once an auditor has finally found the right data, KPIs, and tools, they must
175report these results so that actions can be taken. Performing in-depth data
176analysis is only useful if the results are understood by the audiences of the
177data. The best way to create this understanding is to visualize the results of
178the data. Let's take a look at some of the best options to visualize and report
179the results you've found.
180
181Some of the most popular commercial tools for visualization are Microsoft
182PowerBI and Tableau Desktop. However, other tools exist such as JMP, Plotly,
183Qlikview, Alteryx, or D3. Some require commercial licenses while others are
184simply free to use. For corporate data, you may want to make sure that the tool
185does not communicate any of the data outside the company (such as cloud
186storage). I won't be going into depth on any of these tools since visualization
187is largely a subjective and creative experience, but remember to constantly
188explore new options as you repeat the process.
189
190Lastly, let's take a look at an example of data visualization. This example
191comes 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
192of entry-level US jobs that require experience. *Figure 3* shows us an
193easy-to-digest picture of the data. We can quickly tell that only about 12.5% of
194entry-level jobs don't require experience.
195
196This is the kind of result that easily describes the data for you. However, make
197sure to include an explanation of what the results mean. Don't let the reader
198assume what the data means, especially if it relates to a complex subject. /Tell
199a story/ about the data and why the results matter. For example, *Figure 4*
200shows a part of the explanation the author gives to illustrate his point.
201
202#+CAPTION: Entry-Level Visualization
203[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example-min.png]]
204
205#+CAPTION: Visualization Explanation
206[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example_explanation-min.png]]
207
208* Wrap-Up
209
210While this is not an all-encompassing program that you can just adopt into your
211department, it should be enough to get anyone started on the process of
212understanding and implementing data analytics. Always remember to continue
213learning and exploring new options as your processes grow and evolve.
blog/2019-12-03-the-ansoff-matrix.org deleted −135
@@ -1,135 +0,0 @@
1#+date: 2019-12-03
2#+title: The Ansoff Matrix
3
4* Overview
5
6As the world of business evolves, managers must approach business planning and
7strategy with a contemporary mindset. According to Dess, McNamara, Eisner, and
8Lee, managers must be willing to adapt to the modern business environment by
9going beyond "'incremental management', whereby they view their job as making a
10series of small, minor changes to improve the efficiency of the firm's
11operations"[fn:1].
12
13One reason that strategic management is crucial is because most businesses that
14fail in the United States each year fail due to a lack of strategic focus or
15direction[fn:2]. The rate of failure for businesses with poor strategies shows
16that strategic planning and management are crucial to a business's strength and
17longevity, injecting the critical factors of growth and direction into a
18company's business plan.
19
20One of the most significant strategic planning and management frameworks that
21companies can use is the [[https://en.wikipedia.org/wiki/Ansoff_matrix][Ansoff Matrix]]. While this framework has unique purposes
22and use-cases, it can effectively help an organization grow and
23compete. Specifically, the Ansoff matrix is one of the most effective frameworks
24for companies who want to focus on increasing sales revenue or
25profitability[fn:3].
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
40** Market Penetration
41
42The most straightforward strategy in the Ansoff matrix is to focus on existing
43products in existing markets, also known as market penetration[fn:3]. Companies
44such as Coca-Cola have used market penetration successfully by investing a lot
45of money to get further value out of their current markets. Coca-Cola does this
46by introducing new features such as Christmas-themed bottles, personal names on
47the bottles, and other marketing schemes.
48
49** Market Development
50
51Market development extends existing products into new markets in an attempt to
52increase the number of buyers. One interesting way that Coca-Cola used this
53strategy comes from the stigma that Diet Coke is a woman's drink[fn:4]. Coca-Cola
54introduced Coca-Cola Zero, which contained the same nutritional content as Diet
55Coke, but was packaged in a dark black can to appear more "manly"[fn:4].
56
57** Product Development
58
59Product development uses existing markets to introduce new products so that the
60firm can better meet customer needs[fn:4]. The extreme end of diversification is
61home to companies such as Johnson & Johnson, a healthcare company that has
62developed a business portfolio of more than 60,000 different products[fn:5].
63Johnson & Johnson's dedication to continuous diversification has led them to a
64balance sheet rating of "AAA", industry recognition for diversification, and
65increases in their investor dividends for 57 consecutive years[fn:6].
66
67** Related Diversification
68
69Diversification, the final strategy of the Ansoff Matrix, is more difficult than
70the others since it involves exploring both new markets and new products.
71Related diversification is a diversification strategy that closely relates to
72the firm's core business. Coca-Cola's best example of related diversification is
73its acquisition of Glaceau and Vitamin Water, which expanded their drinking
74lines of business[fn:4].
75
76** Unrelated Diversification
77
78Unrelated diversification is a diversification strategy that does not really
79relate to the firm's core business but still diversifies their business
80portfolio. A good example of this would be a coffee company who has decided to
81enter the market for bicycle sales. The main purpose of this strategy is to an
82extremely diverse company that will not go bankrupt if one market goes through
83difficult times. However, this requires a lot of independent skills and heavy
84investments since the company most likely cannot easily transfer knowledge
85between the markets they compete in.
86
87* Requirements for Success
88
89To use the Ansoff Matrix framework, managers need to formulate corporate goals
90and objectives. Without goals and direction, management frameworks do not
91present much practical utility. Further, the Ansoff Matrix requires the managers
92involved to make tactical decisions and create a path for the company to take
93toward their goals. Lastly, both the Ansoff Matrix needs to consider both
94internal and external perspectives throughout the strategy formulation process.
95
96One interesting probability is that companies will be using multiple strategic
97planning and management frameworks at the same time. While this may sound like
98it could crowd the management process, there are numerous reasons to do so. For
99example, the Ansoff Matrix and the Balanced Scorecard are relatively popular,
100and they cover entirely different parts of a company's strategy. Using the
101results from the Balanced Scorecard could inform a company of the potential
102product and market demands, such as from customer or supplier survey results,
103to help the company determine which Ansoff Matrix strategy to pursue.
104However, a combined approach at this level would require mature frameworks
105and focused managers who are able to strategize at a high level.
106
107Lastly, it should be noted that the author of the Ansoff matrix, Igor Ansoff,
108often used the term [[https://en.wikipedia.org/wiki/Analysis_paralysis][paralysis by analysis]] to explain the mistake of companies
109who overuse analysis and spend too much time planning. Companies need to
110understand the utility of a strategic management framework while ensuring that
111the company is poised to execute as efficiently as they have planned.
112
113* Footnotes
114
115[fn:1]: Dess, G. G., McNamara, G., Eisner, A. B., Lee, S. H. (2019). Strategic
116management: Text & cases, ninth edition. New York, NY: McGraw-Hill Education.
117
118[fn:2]: Juneja, P. (n.d.). Benefits of strategic management. Management Study
119Guide. Retrieved from
120https://www.managementstudyguide.com/strategic-management-benefits.htm.
121
122[fn:3]: Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing
123Concepts. London: Palgrave.
124
125[fn:4]: Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
126Retrieved from
127https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/.
128
129[fn:5]: Lemke, T. (2019). The most diversified companies in the stock market. The
130balance. Retrieved from
131https://www.thebalance.com/the-most-diversified-companies-in-the-stock-market-4169730.
132
133[fn:6]: Johnson & Johnson. (2018). 2018 Investor Fact Sheet. [PDF file]. Retrieved
134from
135http://www.investor.jnj.com/\_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000.
blog/2019-12-16-password-security.org deleted −107
@@ -1,107 +0,0 @@
1#+date: 2019-12-16
2#+title: Password Security
3
4* Users
5
6** Why Does It Matter?
7
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?
17
18The first place to start with any personal security check-up is to gather a list
19of all the different websites, apps, or programs that require you to have login
20credentials. Optionally, once you know where your information is being stored,
21you can sort the list from the most-important items such as banks or government
22logins to less important items such as your favorite meme site. You will want to
23ensure that your critical logins are secure before getting to the others.
24
25Once you think you have a good idea of all your different authentication
26methods, I recommend using a password manager such as [[https://bitwarden.com/][Bitwarden]]. Using a
27password manager allows you to automatically save your logins, create randomized
28passwords, and transfer passwords across devices. However, you'll need to
29memorize your "vault password" that allows you to open the password manager.
30It's important to make this something hard to guess since it would allow anyone
31who has it to access every password you've stored in there.
32
33Personally, I recommend using a [[https://en.wikipedia.org/wiki/Passphrase][passphrase]] instead of a [[https://en.wikipedia.org/wiki/Password][password]] for your vault
34password. Instead of using a string of characters (whether random or simple),
35use a phrase and add in symbols and a number. For example, your vault password
36could be =Racing-Alphabet-Gourd-Parrot3=. Swap the symbols out for whichever
37symbol you want, move the number around, and fine-tune the passphrase until you
38are confident that you can remember it whenever necessary.
39
40Once you've stored your passwords, make sure you continually check up on your
41account and make sure you aren't following bad password practices. Krebs on
42Security has a great [[https://krebsonsecurity.com/password-dos-and-donts/][blog post on password recommendations]]. Any time that a data
43breach happens, make sure you check to see if you were included, and if you need
44to reset any account passwords.
45
46* Developers
47
48** What Are the Basic Requirements?
49
50When developing any password-protected application, there are a few basic rules
51that anyone should follow even if they do not follow any official guidelines
52such as NIST. The foremost practice is to require users to use passwords that
53are at least 8 characters and cannot easily be guessed. This sounds extremely
54simple, but it requires quite a few different strategies. First, the application
55should check the potential passwords against a dictionary of insecure passwords
56such =password=, =1234abc=, or =application_name=.
57
58Next, the application should offer guidance on the strength of passwords being
59entered during enrollment. Further, NIST officially recommends *not*
60implementing any composition rules that make passwords hard to remember (e.g.
61passwords with letters, numbers, and special characters) and instead encouraging
62the use of long pass phrases which can include spaces. It should be noted that
63to be able to keep spaces within passwords, all unicode characters should be
64supported, and passwords should not be truncated.
65
66** What Does NIST Recommend?
67
68The National Institute of Standards and Technology ([[https://www.nist.gov][NIST]]) in the US Department
69of Commerce regularly publishes information around information security and
70digital identity guidelines. Recently, NIST published [[https://pages.nist.gov/800-63-3/sp800-63b.html][Special Publication
71800-63b]]: Digital Identity Guidelines and Authentication and Lifecycle
72Management.
73
74#+BEGIN_QUOTE
75A Memorized Secret authenticator - commonly referred to as a password or, if
76numeric, a PIN - is a secret value intended to be chosen and memorized by the
77user. Memorized secrets need to be of sufficient complexity and secrecy that
78it would be impractical for an attacker to guess or otherwise discover the
79correct secret value. A memorized secret is something you know.
80
81- NIST Special Publication 800-63B
82#+END_QUOTE
83
84NIST offers a lot of guidance on passwords, but I'm going to highlight just a
85few of the important factors:
86
87- Require passwords to be a minimum of 8 characters (6 characters if randomly
88 generated and be generated using an approved random bit generator).
89- Compare potential passwords against a list that contains values known to be
90 commonly-used, expected, or compromised.
91- Offer guidance on password strength, such as a strength meter.
92- Implement a rate-limiting mechanism to limit the number of failed
93 authentication attempts for each user account.
94- Do not require composition rules for passwords and do not require passwords to
95 be changed periodically (unless compromised).
96- Allow pasting of user identification and passwords to facilitate the use of
97 password managers.
98- Allow users to view the password as it is being entered.
99- Use secure forms of communication and storage, including salting and hashing
100 passwords using a one-way key derivation function.
101
102NIST offers further guidance on other devices that require specific security
103policies, querying for passwords, and more. All the information discussed so far
104comes from [[https://pages.nist.gov/800-63-3/sp800-63b.html][NIST SP800-63b]] but NIST offers a lot of information on digital
105identities, enrollment, identity proofing, authentication, lifecycle management,
106federation, and assertions in the total [[https://pages.nist.gov/800-63-3/][NIST SP800-63 Digital Identity
107Guidelines]].
blog/2020-01-25-linux-software.org deleted −250
@@ -1,250 +0,0 @@
1#+date: 2020-01-25
2#+title: Linux Software
3
4* GUI Applications
5
6** Etcher
7
8[[https://img.cleberg.net/blog/20200125-the-best-linux-software/etcher.png]]
9
10[[https://www.balena.io/etcher/][Etcher]] is a quick and easy way to burn ISO images to CDs and USB devices. There
11are two different ways you can install this program. First, you can navigate to
12the [[https://www.balena.io/etcher/][official website]] and download the AppImage file, which can run without
13installation.
14
15However, AppImage files are not executable by default, so you'll either need to
16right-click to open the properties of the file and click the "Allow executing
17file as program" box in the Permissions tab or use the following command:
18
19#+BEGIN_SRC sh
20chmod u+x FILE_NAME
21#+END_SRC
22
23If you don't like AppImage files or just prefer repositories, you can use the
24following commands to add the author's repository and install it through the
25command-line only.
26
27First, you'll have to echo the repo and write it to a list file:
28
29#+BEGIN_SRC sh
30echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
31#+END_SRC
32
33Next, add the application keys to Ubuntu's keyring:
34
35#+BEGIN_SRC sh
36sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
37#+END_SRC
38
39Finally, update the repositories and install the app.
40
41#+BEGIN_SRC sh
42sudo apt update && sudo apt install balena-etcher-electron
43#+END_SRC
44
45Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
46
47#+BEGIN_SRC sh
48sudo pacman -S etcher
49#+END_SRC
50
51** Atom
52
53[[https://img.cleberg.net/blog/20200125-the-best-linux-software/atom.png]]
54
55[[https://atom.io][Atom]] is the self-proclaimed "hackable text editor for the 21st century". This
56text 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
57add-ons available to customize the layout and abilities of the app.
58
59First, add the Atom repository to your sources.
60
61#+BEGIN_SRC sh
62sudo add-apt-repository ppa:webupd8team/atom
63#+END_SRC
64
65Next, update your package listings and install atom.
66
67#+BEGIN_SRC sh
68sudo apt update && sudo apt install atom
69#+END_SRC
70
71If you have issues updating your packages with the Atom repository, you'll need
72to use the snap package described below instead of the repository. To remove the
73repository we just added, use this command:
74
75#+BEGIN_SRC sh
76sudo add-apt-repository -r ppa:webupd8team/atom
77#+END_SRC
78
79You can also install Atom as a snap package, but it must be installed with the
80`--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
81about why you need the classic flag.
82
83#+BEGIN_SRC sh
84snap install atom --classic
85#+END_SRC
86
87Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
88
89#+BEGIN_SRC sh
90sudo pacman -S atom
91#+END_SRC
92
93---
94
95** Visual Studio Code
96
97[[https://img.cleberg.net/blog/20200125-the-best-linux-software/vscode.png]]
98
99[[https://code.visualstudio.com][Visual Studio Code]] is yet another fantastic choice for programming on Linux,
100especially if you need those extra add-ons to spice up your late-night coding
101sessions. 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]],
102who makes a ton of great themes for VS Code, Atom, and various Linux desktop
103environments.
104
105To install VS Code, you'll need to download the `.deb` file from the official
106website. Once you've downloaded the file, either double-click it to install
107through the Software Center or run the following command:
108
109#+BEGIN_SRC sh
110sudo dpkg -i FILE_NAME.deb
111#+END_SRC
112
113You can also install VS Code as a snap package, but it must be installed with
114the `--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
115about why you need the classic flag.
116
117#+BEGIN_SRC sh
118snap install code --classic
119#+END_SRC
120
121Using Arch, Manjaro, or another distro using the AUR? Use these commands
122instead:
123
124#+BEGIN_SRC sh
125sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
126#+END_SRC
127
128** GNOME Tweaks
129
130[[https://img.cleberg.net/blog/20200125-the-best-linux-software/gnome-tweaks.png]]
131
132[[https://gitlab.gnome.org/GNOME/gnome-tweaks][Gnome Tweaks]] is the ultimate tool to use if you want to customize your GNOME
133desktop environment. This is how you can switch application themes (GTK), shell
134themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you just need
135to install the official package.
136
137#+BEGIN_SRC sh
138sudo apt install gnome-tweaks
139#+END_SRC
140
141If you've installed Manjaro or Arch with Gnome, you should have the tweak tool
142pre-installed. If you're on Fedora, this tool is available as an official
143package:
144
145#+BEGIN_SRC sh
146sudo dnf install gnome-tweaks
147#+END_SRC
148
149** Steam
150
151[[https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png]]
152
153[[https://steampowered.com][Steam]] is one of the most popular gaming libraries for computers and is one of
154the main reasons that many people have been able to switch to Linux in recent
155years, thanks to Steam Proton, which makes it easier to play games not
156officially created for Linux platforms.
157
158To install Steam on Ubuntu, you just need to install the official package.
159
160#+BEGIN_SRC sh
161sudo apt install steam-installer
162#+END_SRC
163
164For Arch-based systems, you'll simply need to install the =steam= package.
165However, this requires that you enable the =multilib= source. To do so, use the
166following command:
167
168#+BEGIN_SRC sh
169sudo nano /etc/pacman.conf
170#+END_SRC
171
172Now, scroll down and uncomment the =multilib= section.
173
174#+BEGIN_SRC config
175# Before:
176#[multilib]
177#Include = /etc/pacman.d/mirrorlist
178
179# After:
180[multilib]
181Include = /etc/pacman.d/mirrorlist
182#+END_SRC
183
184Finally, install the program:
185
186#+BEGIN_SRC sh
187sudo pacman -S steam
188#+END_SRC
189
190[[./2020-01-26-steam-on-ntfs-drives.html][Problem Launching Steam Games? Click Here.]]
191
192* Command-Line Packages
193
194** neofetch
195
196[[https://img.cleberg.net/blog/20200125-the-best-linux-software/neofetch.png]]
197
198[[https://github.com/dylanaraps/neofetch][Neofetch]] is a customizable tool used in the command-line to show system
199information. This is exceptionally useful if you want to see your system's
200information quickly without the clutter of some resource-heavy GUI apps.
201
202This is an official package if you're running Ubuntu 17.04 or later, so simply
203use the following command:
204
205#+BEGIN_SRC sh
206sudo apt install neofetch
207#+END_SRC
208
209If you're running Ubuntu 16.10 or earlier, you'll have to use a series of
210commands:
211
212#+BEGIN_SRC sh
213sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
214#+END_SRC
215
216Using Arch, Manjaro, or another distro using the AUR? Use this command instead:
217
218#+BEGIN_SRC sh
219sudo pacman -S neofetch
220#+END_SRC
221
222** yt-dlp
223
224[[https://img.cleberg.net/blog/20200125-the-best-linux-software/yt-dlp.png]]
225
226[[https://github.com/yt-dlp/yt-dlp][yt-dlp]] is an extremely handy command-line tool that allows you to download video
227or audio files from various websites, such as YouTube. There are a ton of
228different options when running this package, so be sure to run `yt-dlp --help`
229first to look through everything you can do (or give up and search for the best
230config online).
231
232While this shouldn't be a problem for most users, yt-dlp requires Python 2.6,
2332.7, or 3.2+ to work correctly, so install Python if you don't have it already.
234You can check to see if you have Python installed by running:
235
236#+BEGIN_SRC sh
237python -V
238#+END_SRC
239
240To get the youtube-dl package, simply curl the URL and output the results.
241
242#+BEGIN_SRC sh
243sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
244#+END_SRC
245
246Finally, make the file executable so that it can be run from the command-line.
247
248#+BEGIN_SRC sh
249sudo chmod a+rx /usr/local/bin/yt-dlp
250#+END_SRC
blog/2020-01-26-steam-on-ntfs.org deleted −88
@@ -1,88 +0,0 @@
1#+date: 2020-01-26
2#+title: Linux Gaming Tweak: Steam on NTFS Drives
3
4* Auto-Mount Steam Drives
5
6[[https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png]]
7
8If you want to see how to install Steam on Linux, see my other post: [[./2020-01-25-linux-software.html][Linux Software]].
9
10Are you having trouble launching games, even though they've installed correctly?
11This may happen if you're storing your games on an NTFS-formatted drive. This
12shouldn't be an issue if you're storing your games on the same drive that Steam
13is on, but some gamers prefer to put Steam on their main drive and game files on
14another SSD or HDD.
15
16To fix this problem, you'll need to try a few things. First, you'll need to
17install the =ntfs-3g= package, which is meant for better interoperability with
18Linux.
19
20#+BEGIN_SRC sh
21sudo apt install ntfs-3g
22#+END_SRC
23
24Next, you should set up the =/etc/fstab= file to automatically mount your drives
25on boot. To automatically mount your drives when the computer boots up, you'll
26have to create the folders you want to mount your drive to first. I store mine
27in the =/mnt= folder using names that I'll recognize, but you can create your
28folders wherever you want.
29
30#+BEGIN_SRC sh
31mkdir /path/to/folder
32#+END_SRC
33
34For example:
35
36#+BEGIN_SRC sh
37mkdir /mnt/steam_library
38#+END_SRC
39
40To automatically mount drives upon system boot, you will need to collect a few
41items. The UUID is the identification number connected to whichever drive you're
42using to store Steam games.
43
44Drives are usually labeled similar to =/dev/nvme0n1p1= or =/dev/sda1=, so you'll
45need to find the line in the output of the command below that correlates to your
46drive and copy the UUID over to the =/etc/fstab= file.
47
48#+BEGIN_SRC sh
49sudo blkid | grep UUID=
50#+END_SRC
51
52Next, you'll need your =uid= and =gid=. To find these, run the following
53command:
54
55#+BEGIN_SRC sh
56id -u && id -g
57#+END_SRC
58
59Now that you have collected the necessary information, open the `/etc/fstab`
60file:
61
62#+BEGIN_SRC sh
63sudo nano /etc/fstab
64#+END_SRC
65
66Each drive you want to mount on boot should have its own line in the
67=/etc/fstab= file that looks similar to this:
68
69#+BEGIN_SRC config
70UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
71#+END_SRC
72
73Now all you need to do is unmount your drive and re-mount it. You can unmount
74the drive by doing this (be sure to use the correct drive name here):
75
76#+BEGIN_SRC sh
77sudo umount /dev/sdxX
78#+END_SRC
79
80You can re-mount all your drives by executing the following:
81
82#+BEGIN_SRC sh
83sudo mount -a
84#+END_SRC
85
86If you don't know what your drive name is, or you're nervous about unmounting
87and re-mounting, simply reboot your computer, and it will be done for you
88automatically.
blog/2020-02-09-cryptography-basics.org deleted −163
@@ -1,163 +0,0 @@
1#+date: 2020-02-09
2#+title: Cryptography Basics
3
4* Similar Article Available
5
6If you haven't already, feel free to read my post on [[./2018-12-08-aes-encryption.html][AES Encryption]].
7
8* What is Cryptography?
9
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
17to render its meaning unintelligible (i.e., to hide its semantic content),
18prevent its 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
33
34To create or evaluate a cryptographic system, you need to know the essential
35pieces to the system:
36
37- *Encryption Algorithm (Primitive):* A mathematical process that encrypts and
38 decrypts data.
39- *Encryption Key:* A string of bits used within the encryption algorithm as
40 the secret that allows successful encryption or decryption of data.
41- *Key Length (Size):* The maximum number of bits within the encryption key.
42 It's important to remember that key size is regulated in many countries.
43- *Message Digest:* A smaller, fixed-size bit string version of the original
44 message. This is practically infeasible to reverse, which is why it's commonly
45 used to verify integrity.
46
47* Symmetric Systems (Secret Key Cryptography)
48
49Symmetric cryptography utilizes a secret, bidirectional key to perform both
50encryption and decryption of the data. The most common implementation of
51symmetric cryptography is the Advanced Encryption Standard, which uses keys that
52are 128 bits to 256 bits in size. This standard came after the National
53Institute of Standards and Technology (NIST) decided to retire the Data
54Encryption Standard (DES) in 2001.
55
56Since brute force attacks strongly correlate with key length, the 56-bit key
57length of DES was considered insecure after it was publicly broken in under 24
58hours. However, there is a modern implementation of DES called Triple DES where
59the DES method is applied three times to each data block.
60
61The main advantages to symmetric systems are the ease of use, since only one key
62is required for both encryption and decryption, and the simplicity of the
63algorithms. This helps with bulk data encryption that may unnecessarily waste
64time and power using asymmetric systems.
65
66However, symmetric systems have disadvantages to keep in mind. Since the key is
67private, it can be difficult to safely distribute keys to communication
68partners. Additionally, the key cannot be used to sign messages since it's
69necessary to keep the key private.
70
71* Asymmetric Systems (Public Key Cryptography)
72
73Asymmetric cryptography utilizes two keys within the system: a secret key that
74is privately-held and a public key that can be distributed freely. The
75interesting aspect of asymmetric cryptography is that either key can be used to
76encrypt the data, there's no rule that dictates which key must be used for
77encryption. Once one key is used to encrypt the data, only the other key can be
78used to decrypt the data. This means that if the private key encrypts the data,
79only the public key can decrypt the data.
80
81An advantage of this system is that if you successfully decrypt data using one
82of the keys, you can be sure of the sender since only the other key could have
83encrypted the data.
84
85One of the major implementations of an asymmetric system is a digital signature.
86A digital signature can be generated using the sender's private key, or a
87one-way hash function and is used to provide assurance for the integrity and
88authenticity of the message. A couple common message digest algorithms are
89SHA-256 and SHA-512, which securely compress data and produce a 128-bit message
90digest.
91
92It should be noted that man-in-the-middle attacks are one of the risks with
93digital signatures and public keys. To combat this, applications often use a
94public key infrastructure (PKI) to independently authenticate the validity of
95signatures and keys.
96
97Due to the large key size and [[https://crypto.stackexchange.com/a/591][inefficient mathematical functions]] of asymmetric
98encryption, elliptical curve cryptography (ECC) is often used to increase
99security while using fewer resources.
100
101* Applications of Cryptographic Systems
102
103There are quite a few implementations of cryptographic systems around the world.
104Here are a few popular examples:
105
106*Transport Layer Security (TLS):* One of the most famous cryptographic
107solutions created is TLS, a session-layered or connection-layered internet
108protocol that allows for secure communications between browsers and servers.
109Using handshakes, peer negotiation, and authentication allows TLS to prevent
110eavesdropping and malicious transformation of data. The major reason for TLS
111popularity is that a major vulnerability was found in the SSL protocol in 2014.
112Instead of SSL, TLS can be used with HTTP to form HTTPS and is the preferred
113method for modern web development due to its increased security.
114
115*Secure Hypertext Transfer Protocol (HTTPS):* An application layer protocol
116that allows for secure transport of data between servers and web clients. One of
117the unique parts of HTTPS is that it uses a secured port number instead of the
118default web port address.
119
120*Virtual Private Network (VPN):* VPNs are made to securely extend a private
121network across public networks by utilizing an encrypted layered tunneling
122protocol paired with an authentication method, such as usernames and passwords.
123This technology originally allowed remote employees to access their company's
124data but have evolved into one of the top choices for anyone who wishes to mask
125their sensitive personal data.
126
127*Internet Protocol Security (IPSec):* This protocol suite facilitates
128communication between two or more hosts or subnets by authenticating and
129encrypting the data packets. IPSec is used in a lot of VPNs to establish the VPN
130connection through the transport and tunnel mode encryption methods. IPSec
131encrypts just the data portion of packets in the transport methods, but it
132encrypts both the data and headers in the tunnel method (introducing an
133additional header for authentication).
134
135*Secure Shell (SSH):* SSH is another network protocol used to protect network
136services by authenticating users through a secure channel. This protocol is
137often used for command-line (shell) functions such as remote shell commands,
138logins, and file transfers.
139
140*Kerberos:* Developed by MIT, Kerberos is a computer-network authentication
141protocol that works on the basis of tickets to allow nodes communicating over a
142non-secure network to prove their identity to one another securely. This is most
143commonly used in business environments when used as the authentication and
144encryption method for Windows Active Directory (AD).
145
146* Cybersecurity Controls
147
148If you're someone who needs solutions on how to control risks associated with
149utilizing a crytograhpic system, start with a few basic controls:
150
151- *Policies:* A policy on the use of cryptographic controls for protection
152 of information is implemented and is in accordance with organizational
153 objectives.
154- *Key management:* A policy on the use, protection and lifetime of
155 cryptographic keys is implemented through the entire application lifecycle.
156- *Key size:* The organization has researched the optimal key size for their
157 purposes, considering national laws, required processing power, and
158 longevity of the solution.
159- *Algorithm selection:* Implemented algorithms are sufficiently appropriate
160 for the business of the organization, robust, and align with recommended
161 guidelines.
162- *Protocol configuration:* Protocols have been reviewed and configured
163 suitable to the purpose of the business.
blog/2020-03-25-session-messenger.org deleted −127
@@ -1,127 +0,0 @@
1#+date: 2020-03-25
2#+title: Session Private Messenger
3
4* Privacy Warning
5
6The company behind Session (Loki Foundation) is from Australia. If you didn't
7know, Australia has introduced [[https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf][legislation]] mandating companies comply with
8government requests to build backdoor access into applications. For more
9information, read my article on [[./2020-01-25-aes-encryption.html][AES Encryption]].
10
11* About Session
12
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
37
38Since most people are looking for an alternative to a popular chat app, I am
39going to list out the features that Session has so that you are able to
40determine if the app would suit your needs:
41
42- Multiple device linking (via QR code or ID)
43- App locking via device screen lock, password, or fingerprint
44- Screenshot blocking
45- Incognito keyboard
46- Read receipts and typing indicators
47- Mobile notification customization
48- Old message deletion and conversation limit
49- Backups
50- Recovery phrase
51- Account deletion, including ID, messages, sessions, and contacts
52
53* Downloads
54
55I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and iOS 15.
56All apps have worked well without many issues.
57
58Below is a brief overview of the Session app on Linux. To get this app, you'll
59need to go to the [[https://getsession.org/download/][Downloads]] page and click to link to the operating system
60you're using.
61
62For Linux, it will download an AppImage that you'll need to enable with the
63following command:
64
65#+BEGIN_SRC sh
66sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
67#+END_SRC
68
69#+CAPTION: Session Download Options
70[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_downloads.png]]
71
72* Creating an Account
73
74Once you've installed the app, simply run the app and create your unique Session
75ID. It will look something like this:
76=05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608=.
77
78You'll need to set a display name and, optionally, a password. If you set a
79password, you will need to enter it every time you open the app.
80
81#+CAPTION: Session Login (Linux)
82[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_linux_login.png]]
83
84#+CAPTION: Session Login (macOS)
85[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_login.png]]
86
87#+CAPTION: Password Authentication
88[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_password_authentication.png]]
89
90* Start Messaging
91
92Once you've created your account and set up your profile details, the next step
93is to start messaging other people. To do so, you'll need to share your Session
94ID with other people. From this point, it's fairly straightforward and acts like
95any other messaging app, so I won't dive into much detail here.
96
97** macOS
98
99#+CAPTION: macOS Conversations
100[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_conversations.png]]
101
102One key feature to note is that the desktop application now provides a helpful
103pop-up box explaining the process that Session uses to hide your IP address:
104
105#+CAPTION: IP Address Help Box
106[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_ip.png]]
107
108** iOS
109
110The mobile app is quite simple and effective, giving you all the standard mobile
111messaging options you'd expect.
112
113#+CAPTION: iOS App
114[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_ios.png]]
115
116* Potential Issues
117
118I've discovered one annoying issue that would prevent from using this app
119regularly. On a mobile device, there have been issues with receiving messages on
120time. Even with battery optimization disabled and no network restrictions,
121Session notifications sometimes do not display until I open the app or the
122conversation itself and wait a few moments. This is actually one of the reasons
123I stopped using Signal (this seems fixed as of my updates in 2021/2022,
124so I wouldn't worry about this issue anymore).
125
126Looking for another messenger instead of Session? I recommend Signal, Matrix,
127and IRC.
blog/2020-05-03-homelab.org deleted −151
@@ -1,151 +0,0 @@
1#+date: 2020-05-03
2#+title: An Inside Look at My Homelab
3
4* What is a Homelab?
5
6Starting as a developer, I have largely stayed away from hardware-based hobbies
7(other than building a gaming desktop). However, as the quarantine for COVID-19
8stretches out further and further, I found myself bored and in search of new
9hobbies. After spending the last few months browsing the [[https://www.reddit.com/r/homelab/][r/homelab]] subreddit, I
10decided it was time to jump in and try things out for myself.
11
12Since I am a beginner and just recently graduated from college, everything I've
13done so far in my homelab is fairly low-budget.
14
15* Hardware
16
17#+CAPTION: HomeLab Diagram
18[[https://img.cleberg.net/blog/20200503-homelab/homelab-min.png]]
19
20** Raspberry Pi 4
21
22Luckily, I had actually purchased a [[https://www.raspberrypi.org/products/raspberry-pi-4-model-b/][Raspberry Pi 4]] before the quarantine started
23so that I could try to keep Plex Media Center running, even while my desktop
24computer was turned off. I started here, using the Pi to hold Plex and Pi-hole
25until I grew tired with the slow performance.
26
27Here are the specifications for the Pi 4:
28
29- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
30- 4GB LPDDR4-3200 SDRAM
31- Gigabit Ethernet
32- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
33- 64 GB MicroSD Card
34
35** Dell Optiplex 5040
36
37Since I wasn't happy with the Pi as my main server, I turned to Craigslist. I
38know a lot of other homelabbers use Ebay, but I can't seem to ever trust it
39enough to purchase items on there. So I used Craigslist and found a Dell
40Optiplex 5040 desktop computer on sale for $90. While this computer might be
41underpowered, it was one of the few computers under $100 that was available
42during quarantine.
43
44Here are the specifications for the Dell Optiplex 5040:
45
46- Intel Core i3 6100
47- 8GB RAM DDR3
48- Intel HD Graphics
49- Gigabit Ethernet
50- 500GB Hard Drive
51
52While this hardware would be awful for a work computer or a gaming rig, it
53turned out to be wonderful for my server purposes. The only limitation I have
54found so far is the CPU. The i3-6100 only has enough power for a single 4k video
55transcode at a time. I haven't tested more than three 1080p streams at a time,
56but the maximum amount of streams I've ever actually used is two.
57
58** WD easystore 10TB & 8TB
59
60Application storage and temporary files are stored on the internal hard drive of
61the server, but all media files (movies, tv, games, books, etc) are stored
62externally on my WD easystore hard drive. Creating auto-boot configurations in
63the =/etc/fstab= file on my server allows the hard drives to automatically mount
64whenever I need to restart my server.
65
66#+BEGIN_QUOTE
67Update: In March 2022, I shucked the hard drives out of their external cases,
68put some Kapton tape on the third power pin to prevent power shutdowns, and
69stuck them inside my server tower using internal SATA cables.
70#+END_QUOTE
71
72** Netgear Unmanaged Switch
73
74To manage all the ethernet cords used by my homelab, my desktop, and my living
75room media center, I purchased an 8-port gigabit ethernet switch for $50 at my
76local computer store. This is probably much more than I should have spent on an
77unmanaged switch, but I am comfortable with the choice.
78
79** TP-Link Managed Switch
80
81Since I use the unmanaged switch to group all living room devices together, I
82use the managed switch to configure VLANs and secure my network.
83
84** Arris TM1602A Modem & Sagecom Fast 5280 Router
85
86My default modem and router, provided by my ISP, are fairly standard. The Arris
87modem supports DOCSIS 3.0, which is something that I definitely wanted as a
88minimum. The Sagecom router is also standard, no fancy bells or whistles.
89However, it does support DHCP and DHCPv6, which is something you can use to
90route all household traffic through a pi-hole or firewall.
91
92** TP-Link EAP
93
94In order to gain better control over the network, I use my own wireless access
95point instead of the one included in the Sagecom router above. Now I can control
96and organize all of my ethernet connections through the VLANs on the managed
97switch and wireless connections through the VLANS on the EAP.
98
99** Generic Printer
100
101The last piece to my homelab is a standard wireless printer. Nothing special
102here.
103
104* Software
105
106** Ubuntu Server 20.04
107
108While the 20.04 version of Ubuntu was just released, I always like to experiment
109with new features (and I don't mind breaking my system - it just gives me more
110experience learning how to fix things). So, I have Ubuntu Server 20.04 installed
111on the Dell Optiplex server and Ubuntu Server 19.10 installed on the Raspberry
112Pi. Once I find an acceptable use for the Pi, I will most likely switch the
113operating system.
114
115** Docker
116
117I am /very/ new to Docker, but I have had a lot of fun playing with it so far.
118Docker is used to create containers that can hold all the contents of a system
119without interfering with other software on the same system. So far, I have
120successfully installed pi-hole, GitLab, Gogs, and Nextcloud in containers.
121However, I opted to delete all of those so that I can reconfigure them more
122professionally at a later time.
123
124** Plex Media Server
125
126Plex is a media center software that allows you to organize your movies, TV
127shows, music, photos, and videos automatically. It will even download metadata
128for you so that you can easily browse these collections.
129
130** Pi-hole
131
132Pi-hole is an alternative ad-blocker that runs at the DNS level, allowing you to
133block traffic when it hits your network, so that you can reject any traffic you
134deem to be bad. Pi-hole uses blacklists and whitelists to decide which traffic
135block and, luckily, there are a lot of pre-made lists out there on Reddit,
136GitHub, etc.
137
138** Nextcloud
139
140While I had trouble with the Docker version of Nextcloud, I was very successful
141when setting up the snap version. Using this, I was able to map Nextcloud to a
142subdomain of a domain I own in Namecheap. Additionally, Nextcloud has an
143integration with Let's Encrypt that allows me to issue certificates
144automatically to any new domain I authorize.
145
146** Webmin
147
148To monitor my servers, and the processes running on them, I use the Webmin
149dashboard. This was fairly painless to set up, and I currently access it
150straight through the server's IP address. In the future, I will be looking to
151configure Webmin to use a custom domain just like Nextcloud.
blog/2020-05-19-customizing-ubuntu.org deleted −185
@@ -1,185 +0,0 @@
1#+date: 2020-05-19
2#+title: Beginner's Guide: Customizing Ubuntu
3
4* More Information
5
6For inspiration on designing your \*nix computer, check out the [[https://libredd.it/r/unixporn][r/unixporn]]
7subreddit!
8
9* Customizing Ubuntu
10
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
23#+CAPTION: Gnome Tweaks
24[[https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-tweaks-min.png]]
25
26Ubuntu 20.04 ships with the default desktop environment [[https://www.gnome.org/][Gnome]], which includes
27the handy =gnome-tweaks= tool to quickly change designs. To install this, just
28open your terminal and enter the following command:
29
30#+BEGIN_SRC sh
31sudo apt install gnome-tweaks
32#+END_SRC
33
34After you've finished installing the tool, simply launch the Tweaks application,
35and you'll be able to access the various customization options available by
36default on Ubuntu. You might even like some of the pre-installed options.
37
38** GNOME Application Themes
39
40To change the themes applied to applications in GNOME, you will need to change
41the Applications dropdown in the Appearance section of Tweaks. To add more
42themes, you will have to find your preferred theme online and follow the steps
43below to have it show up in the Tweaks tool. While you may find themes anywhere,
44one of the most popular sites for GNOME themes is [[https://www.gnome-look.org/][gnome-look.org]]. This website
45contains themes for applications, shells, icons, and cursors.
46
47Steps to import themes into Tweaks:
48
491. Download the theme.
502. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you will need
51 to extract the contents. This is easiest when opening the file explorer,
52 right-clicking the compressed file, and choosing "Extract here."
533. Move the theme folder to =/usr/share/themes/=. You can do so with the
54 following command: =sudo mv theme-folder/ /usr/share/themes/=.
55 - Icons and cursors will be moved to the =/usr/share/icons/= folder.
56 - Fonts will be moved to the =/usr/share/fonts/= folder Alternatively, you
57 can move them to the =/usr/share/fonts/opentype/= or
58 =/usr/share/fonts/opentype/= folders, if you have a specific font type.
594. Close tweaks if it is open. Re-open Tweaks and your new theme will be
60 available in the Applications dropdown in the Appearance section of Tweaks.
61
62If the theme is not showing up after you've moved it into the themes folder, you
63may have uncompressed the folder into a sub-folder. You can check this by
64entering the theme folder and listing the contents:
65
66#+BEGIN_SRC sh
67cd /usr/share/themes/Mojave-Dark && ls -la
68#+END_SRC
69
70This is an example of what the contents of your theme folder should look like.
71If you just see another folder there, you should move that folder up into the
72=/usr/share/themes/= folder.
73
74#+BEGIN_SRC sh
75cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
76#+END_SRC
77
78** GNOME Shell Themes
79
80To change the appearance of the title bar, default dock, app menu, and other
81parts of the GNOME shell, you'll need to install the [[https://extensions.gnome.org/extension/19/user-themes/][user themes]] extension on
82[[https://extensions.gnome.org/][Gnome Extensions]]. To be able to install extensions, you will first need to
83install the browser extension that the website instructs you to. See this
84screenshot for the blue box with a link to the extension.
85
86#+CAPTION: Gnome Extensions
87[[https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-extensions-min.png]]
88
89After the browser extension is installed, you will need to install the native
90host connector:
91
92#+BEGIN_SRC sh
93sudo apt install chrome-gnome-shell
94#+END_SRC
95
96Finally, you can go the [[https://extensions.gnome.org/extension/19/user-themes/][user themes]] extension page and click the installation
97button. This will enable the Shell option in Tweaks. Now you can move shell
98themes to the =/usr/share/themes= directory, using the same steps mentioned in
99the previous section, and enable the new theme in Tweaks.
100
101** Icons & Cursors
102
103Icons and cursors are installed exactly the same way, so I'm grouping these
104together in this post. Both of these items will need to follow the same process
105as installing themes, except you will want to move your font folders to the
106=/usr/share/icons/= directory instead.
107
108** Fonts
109
110Fonts are one of the overlooked parts of customization, but a good font can make
111the whole screen look different. For example, I have installed the [[https://github.com/IBM/plex/releases][IBM Plex]]
112fonts on my system. This follows the same process as installing themes, except
113you will want to move your font folders to the =/usr/share/fonts/= directory
114instead.
115
116** Terminal
117
118If you spend a lot of time typing commands, you know how important the style and
119functionality of the terminal is. After spending a lot of time using the default
120GNOME terminal with [[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][unix shell]], I decided to try some different options. I ended
121up choosing [[https://terminator-gtk3.readthedocs.io/en/latest/][Terminator]] with [[https://en.wikipedia.org/wiki/Z_shell][zsh]].
122
123Terminator is great if you need to open multiple terminals at one time by simply
124right-clicking and splitting the screen into as many terminals as you want.
125While this project hasn't been updated in a while, [[https://github.com/gnome-terminator/terminator/issues/1][it is coming under new
126development]]. However, this terminal is great and I haven't experienced any
127errors yet.
128
129For the shell choice, I decided to choose zsh after trying it out on a fresh
130Manjaro installation. Zsh is great if you like to change the themes of your
131terminal, include icons, or add plugins.
132
133The desktop uses the [[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]] to suggest past commands as you
134type. In addition, it suggests corrections if you misspell a command. Lastly, it
135uses the =af-magic= theme, which adds dashed lines between commands, moving the
136user@host tag to the right side of the terminal, and changes the colors. There
137are plenty of plugins and themes to choose from. Just figure out what you like
138and add it to your =~/.zshrc= file!
139
140*** Steps to Replicate My Terminal
141
142To install zsh on Ubuntu, enter the following command into a terminal:
143
144#+BEGIN_SRC sh
145sudo apt install zsh
146#+END_SRC
147
148Then, enter the next command to activate zsh:
149
150#+BEGIN_SRC sh
151sudo chsh -s $(which zsh) $(whoami)
152#+END_SRC
153
154To install Terminator on Ubuntu:
155
156#+BEGIN_SRC sh
157sudo apt install terminator
158#+END_SRC
159
160To install Oh My Zsh on Ubuntu:
161
162#+BEGIN_SRC sh
163sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
164#+END_SRC
165
166To install zsh-autosuggestions via Oh My Zsh:
167
168#+BEGIN_SRC sh
169git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
170#+END_SRC
171
172Then, add the following plugin wording to your =~/.zshrc= file (the default
173config usually has the =git= plugin activated, so just add any other plugins to
174the parentheses separated by a space):
175
176#+BEGIN_SRC sh
177nano ~/.zshrc
178#+END_SRC
179
180#+BEGIN_SRC sh
181plugins=(git zsh-autosuggestions)
182#+END_SRC
183
184Finally, you need to log out of your computer and log back in so your user shell
185can refresh.
blog/2020-07-20-video-game-sales.org deleted −176
@@ -1,176 +0,0 @@
1#+date: 2020-07-20
2#+title: Data Exploration: Video Game Sales
3
4* Background Information
5
6This dataset (obtained from [[https://www.kaggle.com/gregorut/videogamesales/data][Kaggle]]) contains a list of video games with sales
7greater than 100,000 copies. It was generated by a scrape of vgchartz.com.
8
9Fields include:
10
11- Rank: Ranking of overall sales
12- Name: The game name
13- Platform: Platform of the game release (i.e. PC,PS4, etc.)
14- Year: Year of the game's release
15- Genre: Genre of the game
16- Publisher: Publisher of the game
17- NA_Sales: Sales in North America (in millions)
18- EU_Sales: Sales in Europe (in millions)
19- JP_Sales: Sales in Japan (in millions)
20- Other_Sales: Sales in the rest of the world (in millions)
21- Global_Sales: Total worldwide sales.
22
23There are 16,598 records. 2 records were dropped due to incomplete information.
24
25* Import the Data
26
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
45#+BEGIN_SRC python
46# With the description function, we can see the basic stats. For example, we can 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
63#+BEGIN_SRC python
64# This function plots the global sales by platform
65sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
66#+END_SRC
67
68#+CAPTION: Plot of Global Sales by Platform
69[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/03_plot-min.png]]
70
71#+BEGIN_SRC python
72# This function plots the global sales by genre
73sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
74#+END_SRC
75
76#+CAPTION: Plot of Global Sales by Genre
77[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/04_plot-min.png]]
78
79#+BEGIN_SRC python
80# This function plots the global sales by year
81sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
82#+END_SRC
83
84#+CAPTION: Plot of Global Sales by Year
85[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/05_plot-min.png]]
86
87#+BEGIN_SRC python
88# This function plots four different lines to show sales from different regions.
89# The global sales plot line is commented-out, but can be included for comparison
90df2 = df.groupby('Year').sum()
91years = range(1980,2019)
92
93a = df2['NA_Sales']
94b = df2['EU_Sales']
95c = df2['JP_Sales']
96d = df2['Other_Sales']
97# e = df2['Global_Sales']
98
99fig, ax = plt.subplots(figsize=(12,12))
100ax.set_ylabel('Region Sales (in Millions)')
101ax.set_xlabel('Year')
102
103ax.plot(years, a, label='NA_Sales')
104ax.plot(years, b, label='EU_Sales')
105ax.plot(years, c, label='JP_Sales')
106ax.plot(years, d, label='Other_Sales')
107# ax.plot(years, e, label='Global_Sales')
108
109ax.legend()
110plt.show()
111#+END_SRC
112
113#+CAPTION: Plot of Regional Sales by Year
114[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/06_plot-min.png]]
115
116* Investigate Outliers
117
118#+BEGIN_SRC python
119# Find the game with the highest sales in North America
120df.loc[df['NA_Sales'].idxmax()]
121
122Rank 1
123Name Wii Sports
124Platform Wii
125Year 2006
126Genre Sports
127Publisher Nintendo
128NA_Sales 41.49
129EU_Sales 29.02
130JP_Sales 3.77
131Other_Sales 8.46
132Global_Sales 82.74
133Name: 0, dtype: object
134
135# Explore statistics in the year 2006 (highest selling year)
136df3 = df[(df['Year'] == 2006)]
137df3.describe()
138#+END_SRC
139
140#+CAPTION: Descriptive Statistics of 2006 Sales
141[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/07_2006_stats-min.png]]
142
143#+BEGIN_SRC python
144# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
145sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
146#+END_SRC
147
148#+CAPTION: Plot of 2006 Sales
149[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/08_plot-min.png]]
150
151#+BEGIN_SRC python
152# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
153# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
154df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
155#+END_SRC
156
157#+CAPTION: Outliers of 2006 Sales
158[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/09_outliers-min.png]]
159
160* Discussion
161
162The purpose of exploring datasets is to ask questions, answer questions, and
163discover intelligence that can be used to inform decision-making. So, what have
164we found in this dataset?
165
166Today we simply explored a publicly-available dataset to see what kind of
167information it contained. During that exploration, we found that video game
168sales peaked in 2006. That peak was largely due to Nintendo, who sold the top 5
169games in 2006 and has a number of games in the top-10 list for the years
1701980-2020. Additionally, the top four platforms by global sales (Wii, NES, GB,
171DS) are owned by Nintendo.
172
173We didn't explore everything this dataset has to offer, but we can tell from a
174brief analysis that Nintendo seems to rule sales in the video gaming world.
175Further analysis could provide insight into which genres, regions, publishers,
176or world events are correlated with sales.
blog/2020-08-22-redirect-github-pages.org deleted −120
@@ -1,120 +0,0 @@
1#+date: 2020-08-22
2#+title: Redirect GitHub Pages from Subdomain to the Top-Level Domain
3
4* Short answer
5
6** Step 1
7
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
13[Optional] but highly recommended
14
152.1: Remove all other top-level records (prefixed with @) of type A from your
16DNS configuration.
17
182.2: Remove a CNAME record for the second-level domain www if it is present.
19
20** Step 3
21
22Add these 5 entries to the very top of your DNS configuration:
23
24#+BEGIN_SRC txt
25@ A 185.199.108.153
26@ A 185.199.109.153
27@ A 185.199.110.153
28@ A 185.199.111.153
29www CNAME your_github_username.github.io.
30#+END_SRC
31
32Replace =your_github_username= with your actual GitHub username.
33
34** Step 4
35
36Wait for your DNS changes to propagate. DNS changes aren't effective
37immediately. They can take up to a full day to propagate.
38
39* Long answer
40
41This issue has two sides. One is the DNS configuration itself. Another one is
42the way GitHub Pages will forward HTTP requests.
43
44We need to know a few things to understand what GitHub is trying to say in their
45documentation.
46
47** DNS Entry Types
48
49There are two types of DNS records which interest us: CNAME and A.
50
51=A= is also known as =Apex= or sometimes as =root entry=. It forwards requests
52to a specified fixed IP address. =CNAME= entry forwards requests to a specified
53URL (actual valid plain text URL, not an IP address).
54
55** DNS Load balancing
56
57GitHub has one central URL address which accepts all DNS requests for GitHub
58Pages: =http://username.github.io=. That URL is resolved to different IP
59addresses based on your geographical location. Website hosted on GitHub Pages is
60a simple collection of =HTML=, =CSS= and =JS= files. GitHub distributes these
61files to different servers across the globe. So that when your browser sends a
62request from Europe, it receives data from a server in Europe. The same is valid
63for the requests from Asia and the USA.
64
65** What GitHub is trying to say
66
67Since =A= records in DNS must contain IP addresses, and they must be either
68=185.199.108.153= or =185.199.109.153= or =185.199.110.153= or
69=185.199.111.153=, there is no way to forward requests to a server located
70somewhere in Europe or Asia. Your website hosted at GitHub Pages will be
71downloaded from a central GitHub Pages server. There is a minor risk that if
72GitHub Pages DNS servers (=x.x.x.153=) are down for some reason, all custom
73domains which use fixed GitHub Pages IP addresses will not be accessible (their
74DNS requests will not be resolvable).
75
76That is why GitHub strongly suggests to either use a second-level domain for
77your GitHub Pages (e.g. =blog.example.com=) or use a DNS service provider that
78supports a record type =ALIAS= that acts as =A= record but forwards request to a
79URL address (e.g. =username.github.io=) instead of a fixed IP address.
80
81** How GitHub Pages treats HTTP requests
82
83After a DNS request for =your_github_username.github.io= is resolved into an IP
84address, e.g. =185.199.108.153= your browser sends an HTTP request to that
85server with an HTTP header =Host=. Below are =curl= examples that load the same
86website (these examples might not work if you are behind a proxy server):
87
88#+BEGIN_SRC sh
89curl --header "Host: your_github_username.github.io" http://185.199.108.153/
90curl --header "Host: www.example.com" http://185.199.108.153/
91curl --header "Host: example.com" http://185.199.108.153/
92#+END_SRC
93
94This way GitHub Pages servers know which user website to serve.
95
96#+BEGIN_QUOTE
97GitHub Pages server will automatically redirect HTTP requests to the top-level
98domain if your =CNAME= file contains =example.com= but =www.example.com= is
99requested.
100
101The same is valid if your =CNAME= file contains =www.example.com= but the
102header =Host= in the =HTTP= request contains =example.com=.
103#+END_QUOTE
104
105** Why can't I add a =CNAME= record entry that accepts a top-level request (=@=) to my DNS configuration?
106
107Quote from the GitHub Pages documentation:
108
109#+BEGIN_QUOTE
110Warning: Do not create a CNAME record for your custom apex domain! Doing so
111may cause issues with other services, such as email, on that domain.
112#+END_QUOTE
113
114* References:
115
1161. [[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]]
1172. [[https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages][My custom domain isn't working]]
1183. [[https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address][Cannot access my GitHub Pages website by IP Address]]
1194. [[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
120 (e.g. www) to the top-level domain (TLD, Apex record)?]]
blog/2020-09-01-visual-recognition.org deleted −189
@@ -1,189 +0,0 @@
1#+date: 2020-09-01
2#+title: IBM Watson Visual Recognition
3
4* What is IBM Watson?
5
6If you've never heard of [[https://www.ibm.com/watson][Watson]], this service is a suite of enterprise-ready AI
7services, applications, and tooling provided by IBM. Watson contains quite a few
8useful tools for data scientists and students, including the subject of this
9post today: visual recognition.
10
11If you'd like to view the official documentation for the Visual Recognition API,
12visit the [[https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python][API Docs]].
13
14* Prerequisites
15
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 for
27 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
33
34Okay, now let's get started.
35
36To begin, we need to install the proper Python package for IBM Watson.
37
38#+BEGIN_SRC sh
39pip install --upgrade --user "ibm-watson>=4.5.0"
40#+END_SRC
41
42Next, we need to specify the API key, version, and URL given to us when we
43created the Watson Visual Recognition service.
44
45#+BEGIN_SRC python
46apikey = "<your-apikey>"
47version = "2018-03-19"
48url = "<your-url>"
49#+END_SRC
50
51Now, let's import the necessary libraries and authenticate our service.
52
53#+BEGIN_SRC python
54import json
55from ibm_watson import VisualRecognitionV3
56from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
57
58authenticator = IAMAuthenticator(apikey)
59visual_recognition = VisualRecognitionV3(
60 version=version,
61 authenticator=authenticator
62)
63
64visual_recognition.set_service_url(url)
65#+END_SRC
66
67*[Optional]* If you'd like to tell the API not to use any data to improve
68their products, set the following header.
69
70#+BEGIN_SRC python
71visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
72#+END_SRC
73
74Now we have our API all set and ready to go. For this example, I'm going to
75include a =dict= of photos to load as we test out the API.
76
77#+BEGIN_SRC python
78data = [
79 {
80 "title": "Grizzly Bear",
81 "url": "https://example.com/photos/image1.jpg"
82 },
83 {
84 "title": "Nature Lake",
85 "url": "https://example.com/photos/image2.jpg"
86 },
87 {
88 "title": "Welcome Sign",
89 "url": "https://example.com/photos/image3.jpg"
90 },
91 {
92 "title": "Honey Badger",
93 "url": "https://example.com/photos/image4.jpg"
94 },
95 {
96 "title": "Grand Canyon Lizard",
97 "url": "https://example.com/photos/image5.jpg"
98 },
99 {
100 "title": "Castle",
101 "url": "https://example.com/photos/image6.jpg"
102 }
103]
104#+END_SRC
105
106Now that we've set up our libraries and have the photos ready, let's create a
107loop to call the API for each image. The code below shows a loop that calls the
108URL of each image and sends it to the API, requesting results with at least 60%
109confidence. The results are output to the console with dotted lines separating
110each section.
111
112In the case of an API error, the codes and explanations are output to the
113console.
114
115#+BEGIN_SRC python
116from ibm_watson import ApiException
117
118for x in range(len(data)):
119try:
120 url = data[x]["url"]
121 images_filename = data[x]["title"]
122 classes = visual_recognition.classify(
123 url=url,
124 images_filename=images_filename,
125 threshold='0.6',
126 owners=["IBM"]).get_result()
127 print("-----------------------------------------------")
128 print("Image Title: ", data[x]["title"], "\n")
129 print("Image URL: ", data[x]["url"], "\n")
130 classification_results = classes["images"][0]["classifiers"][0]["classes"]
131 for result in classification_results:
132 print(result["class"], "(", result["score"], ")")
133 print("-----------------------------------------------")
134except ApiException as ex:
135 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
136#+END_SRC
137
138* The Results
139
140Here we can see the full result set of our function above. If you view each of
141the URLs that we sent to the API, you'll be able to see that it was remarkably
142accurate. To be fair, these are clear high-resolution, clear photos shot with a
143professional camera. In reality, you will most likely be processing images that
144are lower quality and may have a lot of noise in the photo.
145
146However, we can clearly see the benefit of being able to call this API instead
147of attempting to write our own image recognition function. Each of the
148classifications returned was a fair description of the image.
149
150If you wanted to restrict the results to those that are at least 90% confident
151or greater, you would simply adjust the =threshold= in the
152=visual_recognition.classify()= function.
153
154When your program runs, it should show the output below for each photo you
155provide.
156
157#+BEGIN_SRC txt
158----------------------------------------------------------------
159Image Title: Grizzly Bear
160Image URL: https://example.com/photos/image1.jpg
161
162brown bear ( 0.944 )
163bear ( 1 )
164carnivore ( 1 )
165mammal ( 1 )
166animal ( 1 )
167Alaskan brown bear ( 0.759 )
168greenishness color ( 0.975 )
169----------------------------------------------------------------
170#+END_SRC
171
172* Discussion
173
174Now, this was a very minimal implementation of the API. We simply supplied some
175images and looked to see how accurate the results were. However, you could
176implement this type of API into many machine learning (ML) models.
177
178For example, you could be working for a company that scans their warehouses or
179inventory using drones. Would you want to pay employees to sit there and watch
180drone footage all day in order to identify or count things in the video?
181Probably not. Instead, you could use a classification system similar to this one
182in order to train your machine learning model to correctly identify items that
183the drones show through video. More specifically, you could have your machine
184learning model watch a drone fly over a field of sheep in order to count how
185many sheep are living in that field.
186
187There are many ways to implement machine learning functionality, but hopefully
188this post helped inspire some deeper thought about the tools that can help
189propel us further into the future of machine learning and AI.
blog/2020-09-22-internal-audit.org deleted −246
@@ -1,246 +0,0 @@
1#+date: 2020-09-22
2#+title: What is Internal Audit?
3
4#+CAPTION: Internal Audit Overview
5[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-overview.jpg]]
6
7* Definitions
8
9One of the many reasons that Internal Audit needs such thorough explaining to
10non-auditors is that Internal Audit can serve many purposes, depending on the
11organization's size and needs. However, the Institute of Internal Auditors (IIA)
12defines Internal Auditing as:
13
14#+BEGIN_QUOTE
15Internal auditing is an independent, objective assurance and consulting activity
16designed to add value and improve an organization's operations. It helps an
17organization accomplish its objectives by bringing a systematic, disciplined
18approach to evaluate and improve the effectiveness of risk management, control,
19and governance processes.
20#+END_QUOTE
21
22However, this definition uses quite a few terms that aren't clear unless the
23reader already has a solid understanding of the auditing profession. To further
24explain, the following is a list of definitions that can help supplement
25understanding of internal auditing.
26
27** Independent
28
29Independence is the freedom from conditions that threaten the ability of the
30internal audit activity to carry out internal audit responsibilities in an
31unbiased manner. To achieve the degree of independence necessary to effectively
32carry out the responsibilities of the internal audit activity, the chief audit
33executive has direct and unrestricted access to senior management and the board.
34This can be achieved through a dual-reporting relationship. Threats to
35independence must be managed at the individual auditor, engagement, functional,
36and organizational levels.
37
38** Objective
39
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
48
49Assurance services involve the internal auditor's objective assessment of
50evidence to provide opinions or conclusions regarding an entity, operation,
51function, process, system, or other subject matters. The internal auditor
52determines the nature and scope of an assurance engagement. Generally, three
53parties are participants in assurance services: (1) the person or group directly
54involved with the entity, operation, function, process, system, or other
55subject - (the process owner), (2) the person or group making the assessment -
56(the internal auditor), and (3) the person or group using the assessment - (the
57user).
58
59** Consulting
60
61Consulting services are advisory in nature and are generally performed at the
62specific request of an engagement client. The nature and scope of the consulting
63engagement are subject to agreement with the engagement client. Consulting
64services generally involve two parties: (1) the person or group offering the
65advice (the internal auditor), and (2) the person or group seeking and receiving
66the advice (the engagement client). When performing consulting services, the
67internal auditor should maintain objectivity and not assume management
68responsibility.
69
70** Governance, Risk Management, & Compliance (GRC)
71
72The integrated collection of capabilities that enable an organization to
73reliably achieve objectives, address uncertainty and act with integrity.
74
75* Audit Charter & Standards
76
77First, it's important to note that not every organization needs internal
78auditors. In fact, it's unwise for an organization to hire internal auditors
79unless they have regulatory requirements for auditing and have the capital to
80support the department. Internal audit is a cost center that can only affect
81revenue indirectly.
82
83Once an organization determines the need for internal assurance services, they
84will hire a Chief Audit Executive and create the audit charter. This charter is
85a document, approved by the company's governing body, that will define internal
86audit's purpose, authority, responsibility, and position within the
87organization. Fortunately, the IIA has model charters available to IIA members
88for those developing or improving their charter.
89
90Beyond the charter and organizational documents, internal auditors follow a few
91different standards in order to perform their job. First is the International
92Professional Practices Framework (IPPF) by the IIA, which is the model of
93standards for internal auditing. In addition, ISACA's Information Technology
94Assurance Framework (ITAF) helps guide auditors in reference to information
95technology (IT) compliance and assurance. Finally, additional standards such as
96FASB, GAAP, and industry-specific standards are used when performing internal
97audit work.
98
99* Three Lines of Defense
100
101[[https://theiia.org][The IIA]] released the original Three Lines of Defense model in 2013, but have
102released an updated version in 2020. Here is what the Three Lines of Defense
103model has historically looked like:
104
105#+CAPTION: 2013 Three Lines of Defense Model
106[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/three_lines_model.png]]
107
108I won't go into depth about the changes made to the model in this article.
109Instead, let's take a look at the most current model.
110
111#+CAPTION: 2020 Three Lines of Defense Model
112[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/updated_three_lines_model.png]]
113
114The updated model forgets the strict idea of areas performing their own
115functions or line of defense. Instead of talking about management, risk, and
116internal audit as 1-2-3, the new model creates a more fluid and cooperative
117model.
118
119Looking at this model from an auditing perspective shows us that auditors will
120need to align, communicate, and collaborate with management, including business
121area managers and chief officers, as well as reporting to the governing body.
122The governing body will instruct internal audit /functionally/ on their goals
123and track their progress periodically.
124
125However, the internal audit department will report /administratively/ to a chief
126officer in the company for the purposes of collaboration, direction, and
127assistance with the business. Note that in most situations, the governing body
128is the audit committee on the company's board of directors.
129
130The result of this structure is that internal audit is an independent and
131objective function that can provide assurance over the topics they audit.
132
133* Audit Process
134
135A normal audit will generally follow the same process, regardless of the topic.
136However, certain special projects or abnormal business areas may call for
137changes to the audit process. The audit process is not set in stone, it's simply
138a set of best practices so that audits can be performed consistently.
139
140#+CAPTION: The Internal Audit Process
141[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-process.jpg]]
142
143While different organizations may tweak the process, it will generally follow
144this flow:
145
146** 1. Risk Assessment
147
148The risk assessment part of the process has historically been performed
149annually, but many organizations have moved to performing this process much more
150frequently. In fact, some organizations are moving to an agile approach that can
151take new risks into the risk assessment and re-prioritize risk areas on-the-go.
152To perform a risk assessment, leaders in internal audit will research industry
153risks, consult with business leaders around the company, and perform analyses on
154company data.
155
156Once a risk assessment has been documented, the audit department has a
157prioritized list of risks that can be audited. This is usually in the form of
158auditable entities, such as business areas or departments.
159
160** 2. Planning
161
162During the planning phase of an audit, auditors will meet with the business area
163to discuss the various processes, controls, and risks applicable to the
164business. This helps the auditors determine the scope limits for the audit, as
165well as timing and subject-matter experts. Certain documents will be created in
166this phase that will be used to keep the audit on-track an in-scope as it goes
167forward.
168
169** 3. Testing
170
171The testing phase, also known as fieldwork or execution, is where internal
172auditors will take the information they've discovered and test it against
173regulations, industry standards, company rules, best practices, as well as
174validating that any processes are complete and accurate. For example, an audit
175of HR would most likely examine processes such as employee on-boarding, employee
176termination, security of personally identifiable information (PII), or the IT
177systems involved in these processes. Company standards would be examined and
178compared against how the processes are actually being performed day-to-day, as
179well as compared against regulations such as the Equal Employment Opportunity
180(EEO), American with Disabilities Act, and National Labor Relations Act.
181
182** 4. Reporting
183
184Once all the tests have been completed, the audit will enter the reporting
185phase. This is when the audit team will conclude on the evidence they've
186collected, interviews they've held, and any opinions they've formed on the
187controls in place. A summary of the audit findings, conclusions, and specific
188recommendations are officially communicated to the client through a draft
189report. Clients have the opportunity to respond to the report and submit an
190action plan and time frame. These responses become part of the final report
191which is distributed to the appropriate level of administration.
192
193** 5. Follow-Up
194
195After audits have been completed and management has formed action plans and time
196frames for audit issues, internal audit will follow up once that due date has
197arrived. In most cases, the follow-up will simply consist of a meeting to
198discuss how the action plan has been completed and to request documentation to
199prove it.
200
201* Audit Department Structure
202
203While an internal audit department is most often thought of as a team of
204full-time employees, there are actually many different ways in which a
205department can be structured. As the world becomes more digital and fast-paced,
206outsourcing has become a more attractive option for some organizations. Internal
207audit can be fully outsourced or partially outsourced, allowing for flexibility
208in cases where turnover is high.
209
210In addition, departments can implement a rotational model. This allows for
211interested employees around the organization to rotate into the internal audit
212department for a period of time, allowing them to obtain knowledge of risks and
213controls and allowing the internal audit team to obtain more business area
214knowledge. This program is popular in very large organizations, but
215organizations tend to rotate lower-level audit staff instead of managers. This
216helps prevent any significant knowledge loss as auditors rotate out to business
217areas.
218
219* Consulting
220
221Consulting is not an easy task at any organization, especially for a department
222that can have negative perceptions within the organization as the "compliance
223police." However, once an internal audit department has delivered value to
224organization, adding consulting to their suite of services is a smart move. In
225most cases, Internal Audit can insert themselves into a consulting role without
226affecting the process of project management at the company. This means that
227internal audit can add objective assurance and opinions to business areas as
228they develop new processes, instead of coming in periodically to audit an area
229and file issues that could have been fixed at the beginning.
230
231* Data Science & Data Analytics
232
233#+CAPTION: Data Science Skill Set
234[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/data-science-skillset.png]]
235
236One major piece of the internal audit function in the modern world is data
237science. While the process is data science, most auditors will refer to anything
238in this realm as data analytics. Hot topics such as robotic process automation
239(RPA), machine learning (ML), and data mining have taken over the auditing world
240in recent years. These technologies have been immensely helpful with increasing
241the effectiveness and efficiency of auditors.
242
243For example, mundane and repetitive tasks can be automated in order for auditors
244to make more room in their schedules for labor-intensive work. Further, auditors
245will need to adapt technologies like machine learning in order to extract more
246value from the data they're using to form conclusions.
blog/2020-10-12-mediocrity.org deleted −111
@@ -1,111 +0,0 @@
1#+date: 2020-10-12
2#+title: On the Pursuit of Mediocrity
3
4* Perfect is the Enemy of Good
5
6As the saying goes, "the best is the enemy of the good." As we strive for
7perfection, we often fail to realize the implications of such an undertaking.
8Attempting to reach perfection is often unrealistic. Even worse, it can get in
9the way of achieving a good outcome. In certain situations, we try so hard to
10achieve the ideal solution that we have burned the bridges that would have
11allowed us to reach a lesser yet still superb solution.
12
13Philosophers throughout history have inspected this plight from many viewpoints.
14Greek mythology speaks of the [[https://en.wikipedia.org/wiki/Golden_mean_(philosophy)][golden mean]], which uses the story of Icarus to
15illustrate that sometimes "the middle course" is the best solution. In this
16story, Daedalus, a famous artist of his time, built feathered wings for himself
17and his son so that they might escape the clutches of King Minos. Daedalus warns
18his beloved son whom he loved so much to "fly the middle course", between the
19sea spray and the sun's heat. Icarus did not heed his father; he flew up and up
20until the sun melted the wax off his wings. For not heeding the middle course,
21he fell into the sea and drowned.
22
23More recently, management scholars have explored the [[https://en.wikipedia.org/wiki/Pareto_principle][Pareto principle]] and found
24that as we increase the frequency of something, or strive to perform actions to
25achieve some form of perfection, we run into [[https://en.wikipedia.org/wiki/Diminishing_returns][diminishing returns]].
26
27Even further, Harold Demsetz is noted as coining the term [[https://en.wikipedia.org/wiki/Nirvana_fallacy][the Nirvana fallacy]] in
281969, which shows the fallacy of comparing actual things with unrealistic,
29idealized alternatives. This is another trap that we may fall into, where we are
30constantly thinking of the ultimate solutions to problems, when something more
31realistic needs to be considered.
32
33Over and over throughout history, we've found that perfection is often
34unrealistic and unachievable. However, we push ourselves and our peers to "give
35100%" or "go the extra mile," while it may be that the better course is to give
36a valuable level of effort while considering the effects of further effort on
37the outcome. Working harder does not always help us achieve loftier goals.
38
39This has presented itself to me most recently during my time studying at my
40university. I was anxious and feeling the stresses of my courses, career, and
41personal life for quite a while, which was greatly affecting how well I was
42doing at school and my level of effort at work. One day, I happened to be
43talking to my father when he said something simple that hit home:
44
45#+BEGIN_QUOTE
46All you can do is show up and do your best.
47Worrying about the outcomes won't affect the outcome itself.
48#+END_QUOTE
49
50The thought was extremely straightforward and uncomplicated, yet it was
51something that I had lost sight of during my stress-filled years at school. Ever
52since then, I've found myself pausing and remembering that quote every time I
53get anxious or stressed. It helps to stop and think "Can I do anything to affect
54the outcome, or am I simply worrying over something I can't change?"
55
56* When Mediocrity Isn't Enough
57
58One problem with the philosophies presented in this post is that they are
59implemented far too often in situations where mediocrity simply isn't adequate.
60For example, let's take a look at digital user data, specifically
61personally-identifiable information (PII). As a cybersecurity auditor in the
62United States, I have found that most companies are concerned more with
63compliance than any actual safeguards over the privacy or protection of user
64data. Other than companies who have built their reputation on privacy and
65security, most companies will use [[https://en.wikipedia.org/wiki/Satisficing][satisficing]] as their primary decision-making
66strategy around user data.
67
68#+BEGIN_QUOTE
69Satisficing is a decision-making strategy or cognitive heuristic that entails
70searching through the available alternatives until an acceptability threshold
71is met.
72#+END_QUOTE
73
74This means that each decision will be met with certain possible solutions until
75one of the solutions meets their minimum acceptable standards. For companies
76that deal with user data, the minimum-acceptable standards come from three
77areas:
78
791. Laws and regulations
802. Competitive pressure
813. Risk of monetary or reputation loss
82
83Working with project management or auditing, the primary concern here is the
84risk of legal ramifications. Since the primary risk comes from laws and
85regulations, companies will require that any project that involves user data
86must follow all the rules of those laws so that the company can protect itself
87from fines or other penalties.
88
89Following this, companies will consider best practices in order to place itself
90in a competitive position (e.g. Google vs. Apple) and review any recent or
91ongoing litigation against companies regarding user data. In a perfect company,
92management would then consider the ethical responsibilities of their
93organization and discuss their responsibilities over things like
94personally-identifiable information.
95
96However, as we mentioned above, most companies follow the idea of satisficing,
97which states that they have met the minimum acceptable standards and can now
98move on to other decisions. Modern business culture in the United States
99dictates that profits are the golden measure of how well a company or manager is
100performing, so we often don't think about our responsibilities beyond these
101basic standards.
102
103Not all situations demand excellence, but I believe that applying any philosophy
104as a broad stroke across one's life can be a mistake. We must be able to think
105critically about what we are doing as we do it and ask ourselves a few
106questions. Have I done everything I can in this situation? Is mediocrity an
107acceptable outcome, or should we strive for perfection, even if we can't attain
108it?
109
110Taking a few moments to think critically throughout our day, as we make
111decisions, can have a tremendous effect on the outcomes we create.
blog/2020-12-27-website-redesign.org deleted −87
@@ -1,87 +0,0 @@
1#+date: 2020-12-27
2#+title: Redesigning My Website: The 5 KB Result
3
4* A Brief History
5
6As a form of continuous learning and entertainment, I've been running a handful
7of websites since 2016 when I took my first programming courses in college. I
8maintain one main website, the place I consider the official website to
9represent me. Under this site, I have a handful of subdirectories and
10subdomains.
11
12One of the parts I've enjoyed the most about web development is the aspect of
13designing an identity for a web page and working to find exciting ways to
14display the site's content. Inevitably, this means I've changed the designs for
15my websites more times than I could possibly count. Since I don't really host
16anything on my main webpage that's vital, it allows me the freedom to change
17things as inspiration strikes.
18
19Historically, I've relied on core utilities for spacing, components, and layouts
20from [[https://getbootstrap.com][Bootstrap]] and added custom CSS for fonts, accents, colors, and other
21items. I also tend to create sites with no border radius on items, visible
22borders, and content that takes up the entire screen (using whitespace inside
23components instead of whitespace around my components).
24
25* The Redesign Process
26
27About a week ago, I found myself wishing for a new design yet again. The prior
28design was largely inspired by IBM's [[https://www.carbondesignsystem.com][Carbon Design System]] and relied on jQuery,
29Bootstrap, along with some compressed [[https://en.wikipedia.org/wiki/WebP][.webp]] images.
30
31To anyone who knows my preferences toward web design - and even in my personal
32life - it should be no surprise that I immediately started looking for
33inspiration on minimalism. While there are some decent minimalistic designs on
34sites like [[https://dribbble.com/search/shots/popular/web-design?q=minimalism][Dribbble]], people seem to mostly discuss [[https://brutalist-web.design][brutalist web design]] when you
35ask about minimalism. While brutalist web design doesn't have to be minimal, it
36often is.
37
38I suppose, in a way, I did create a brutalist website since my HTML is semantic
39and accessible, hyperlinks are colored and underlined, and all native browser
40functions like scrolling and the back button work as expected. However, I didn't
41think about brutalism while designing these sites.
42
43The new design followed a simple design process. I walked through the screens on
44my blog and asked myself: "Is this element necessary for a user?" This allowed
45me to first start by removing all javascript, which had the sole purpose of
46allowing users to open a collapsed navbar on mobile. Replacing the collapsible
47navbar allowed me to remove both jQuery and Bootstrap's javascript.
48
49Next, I removed things like author names (since I'm literally the only person
50who will ever write on this site), multiple ways to click on a blog post card,
51blog post descriptions, and the scroll-to-top button. It also helped to move all
52categories to a single page, rather than have each category on its own page.
53
54The final big piece to finish the "[[https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method][KonMari]]"-like part of my process was to
55remove Bootstrap CSS in its entirety. However, this meant pulling out a few very
56useful classes, such as `.img-fluid` and the default font stacks to keep in my
57custom CSS.
58
59After removing all the unnecessary pieces, I was finally able to reorganize my
60content and add a very small amount of custom CSS to make everything pretty.
61This took a brief amount of time, effectively just consisting of me
62converting =<div>= tags into things like =<ul>= lists and choosing accent
63colors.
64
65* The Results
66
67** Reflection
68
69So, what did all of this reorganizing do to my webpages? Well, first, my
70websites are now *ridiculously fast*. Since the prior designs were also minimal
71and didn't have many images, they measured up in Firefox's Network Monitor
72around 300 KB - 600KB. After making the changes, my main site is at 5 KB
73transferred (22 KB total), and my blog is at 6.5 KB transferred (13 KB
74total). *That means the redesigned pages are less than 2% the size of the old
75designs.*
76
77Google Lighthouse ranks the new webpage as 100 in performance, accessibility,
78and best practices, with SEO at 92 since they think tap targets are not sized
79appropriately for mobile users. First contextual paints of the pages are under
800.8 seconds with 0 ms of blocking time. However, the blog subdomain ranks at 100
81for all four categories! First contextual paints of the blog homepage are under
821.0 seconds with 0 ms of blocking time, due to the fact that the CSS for my blog
83is within a separate CSS file, and the CSS for my main website is simply
84embedded in the HTML file.
85
86Now that everything is complete, I can confidently say I'm happy with the result
87and proud to look at the fastest set of websites I've created so far.
blog/2020-12-28-neon-drive.org deleted −83
@@ -1,83 +0,0 @@
1#+date: 2020-12-28
2#+title: Neon Drive: A Nostalgic 80s Arcade Racing Game
3
4* Game Description
5
6[[https://store.steampowered.com/app/433910/Neon_Drive/][Neon Drive]] presents itself as a simple arcade-style game inspired by the arcade
7race games of the 1980s, yet it has managed to take up hours of my life without
8much effort. The game description, directly from the Steam page, is intriguing
9enough to entice anyone who's been looking for a good arcade racing game:
10
11#+BEGIN_QUOTE
12Neon Drive is a slick retro-futuristic arcade game that will make your brain
13melt. You've been warned. From beautiful cityscapes and ocean roads to
14exploding enemy spaceships, Neon Drive has it all.
15#+END_QUOTE
16
17* Gameplay
18
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
35
36Taking a look at the main menu, you can see that Neon Drive plays into every
37stereotype you can think of around retro-futuristic, synthwave arcades (in a
38good way).
39
40#+CAPTION: Neon Drive Menu
41[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_menu.png]]
42
43Once you get into the first level, we see that the choice of car fits right in
44with 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
45F40]]. Each new level comes with new color schemes and cars, so you should never
46get tired of the aesthetic.
47
48#+CAPTION: Neon Drive Race
49[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_race.png]]
50
51Personally, I love the orange and blue colors used in level 2:
52
53#+CAPTION: Level 2
54[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_level_2.png]]
55
56If you're the competitive type and getting 100% on all arcade machines isn't
57enough, there are leaderboards for the regular part of the game, and the
58endurance game mode.
59
60#+CAPTION: Leaderboard
61[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_leaderboard.png]]
62
63* Other Suggestions
64
65Neon Drive sits nicely within the well-founded cult genre of Outrun. Other games
66that I've enjoyed in this same spectrum are:
67
68- [[https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/][Far Cry 3: Blood Dragon]]
69- [[https://store.steampowered.com/app/1239690/Retrowave/][Retrowave]]
70- [[https://store.steampowered.com/app/732810/Slipstream/][Slipstream]]
71
72Although these games aren't necessarily in the same genre, they do have aspects
73that place them close enough to interest gamers that enjoyed Neon Drive:
74
75- [[https://store.steampowered.com/app/311800/Black_Ice/][Black Ice]]
76- [[https://store.steampowered.com/app/746850/Cloudpunk/][Cloudpunk]]
77- [[https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/][Need 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.
blog/2020-12-29-zork.org deleted −80
@@ -1,80 +0,0 @@
1#+date: 2020-12-29
2#+title: Zork: Let's Explore a Classic
3
4* Download (Free)
5
6Before we get into the game itself, you should know that you can download Zork
7for free from Infocom's [[http://infocom-if.org/downloads/downloads.html][download page]]. So feel free to boot it up and take a
8ride back to the 1980s with this masterpiece.
9
10* Game Description
11
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
22game. However, you must explore the map, solve puzzles, and avoid being eaten by
23a grue to collect these treasures.
24
25* The Map
26
27Since Zork is a vast and complex game, it helps to have a map as you explore and
28collect your trophies. However, if you want to play the game as it was truly
29intended, you should try to play it without using the map.
30
31#+CAPTION: Zork Map
32[[https://img.cleberg.net/blog/20201229-zork/zork_map.png]]
33
34/[[https://www.filfre.net/2012/01/exploring-zork-part-1/][Map Source]]/
35
36* In-Game Screenshots
37
38After playing the game (for the first time ever) for several weeks around 2014,
39I was finally able to beat the game with some online help to find the last
40couple items. As I was writing this post, I installed the game again to grab
41some screenshots to show off the true glory of this game. As noted in [[https://www.filfre.net/2012/01/exploring-zork-part-1/][Jimmy
42Maher's playthrough]], the original Zork games looked quite a bit different due to
43the older hardware of computers like the Apple II and multiple bug fixes that
44Infocom pushed out after the game's initial release. My play-through uses the
45[[https://store.steampowered.com/app/570580/Zork_Anthology/][Zork Anthology]] version, which utilizes DOSBox on Windows.
46
47The first screenshot here shows the introductory information, which doesn't
48include instructions of any kind for the player. If you haven't played text
49adventures before, try to use simple commands like "go west," "look around," or
50"hit troll with elvish sword."
51
52#+CAPTION: Zork Screen, pt. 1
53[[https://img.cleberg.net/blog/20201229-zork/zork_01.png]]
54
55In this second screenshot, we see the player has entered the house and found the
56trophy case in the living room. The lantern and sword in this room allow the
57player to explore dark areas and attack enemies. If you don't use the lantern,
58you won't be able to see anything in dark areas, and you may be eaten by a grue.
59
60#+CAPTION: Zork Screen, pt. 2
61[[https://img.cleberg.net/blog/20201229-zork/zork_02.png]]
62
63Finally, we see that the player has found the first treasure: a jewel-encrusted
64egg. These treasures can be taken back to the house and placed in the trophy
65case or carried until you feel like you want to put things away.
66
67#+CAPTION: Zork Screen, pt 3.
68[[https://img.cleberg.net/blog/20201229-zork/zork_03.png]]
69
70* Conclusion
71
72It's been quite a few years since I first played Zork, but I clearly remember
73the late nights and bloodshot eyes that helped me find all the treasures. This
74game is well worth the time and effort, even though the text-based aspect may be
75off-putting to gamers who didn't have to grow up playing games without graphics.
76However, I believe that the strategy and skills learned in early video games
77like Zork can actually help you, even when playing newer games.
78
79If you do decide to play Zork, you can download Zork I, II, and III from
80Infocom's [[http://infocom-if.org/downloads/downloads.html][download page]] for free or search the internet for an online version.
blog/2021-10-09-apache-redirect.org deleted −47
@@ -1,47 +0,0 @@
1#+title: Apache Redirect HTML Files to a Directory
2#+date: 2021-10-10
3
4** The Problem
5:PROPERTIES:
6:CUSTOM_ID: the-problem
7:END:
8After recently switching static site generators (SSG), my blog URLs
9changed with no option to preserve the classic =.html= extension at the
10end of my blog post URLs.
11
12I really disliked using my old SSG ([[https://jekyllrb.com][Jekyll]])
13and prefer my new tool ([[https://www.getzola.org][Zola]]) much more, so
14I was determined to figure out a way to get the proper redirect set up
15so that people who find my posts online aren't constantly met by 404
16errors.
17
18** The Solution
19:PROPERTIES:
20:CUSTOM_ID: the-solution
21:END:
22To solve this problem, I really needed to solve two pieces:
23
241. Redirect all blog post URL requests from =/blog/some-post.html= to
25 =/blog/some-post/=.
262. Ensure that no other =.html= files are redirected, such as
27 =index.html=.
28
29After /a lot/ of tweaking and testing, I believe I have finally found
30the solution. The solution is shown below.
31
32#+begin_src config
33RewriteEngine On
34RewriteCond %{REQUEST_URI} !\index.html$ [NC]
35RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
36#+end_src
37
38This piece of code in the Apache =.conf= or =.htaccess= file will do the
39following:
40
411. Turn on the RewriteEngine so that we can modify URLs.
422. Ignore any =index.html= files from the rule we are about to specify.
433. Find any =.html= files within the website directory and redirect it
44 to exclude the file extension.
454. The final piece is adding the trailing slash (=/=) at the end of the
46 URL - you'll notice that I don't have an Apache rule for that since
47 Apache handles that automatically.
blog/2022-02-17-exiftool.org deleted −67
@@ -1,67 +0,0 @@
1#+title: Stripping Image Metadata with exiftool
2#+date: 2022-02-17
3
4** Why Strip Metadata?
5:PROPERTIES:
6:CUSTOM_ID: why-strip-metadata
7:END:
8Okay, so you want to strip metadata from your photos. Perhaps you take
9pictures of very rare birds, and the location metadata is a gold mine
10for poachers, or perhaps you're just privacy-oriented like me and prefer
11to strip metadata from publicly-available images.
12
13There are various components of image metadata that you may want to
14delete before releasing a photo to the public. Here's an incomplete list
15of things I could easily see just by inspecting a photo on my laptop:
16
17- Location (Latitude & Longitude)
18- Dimensions
19- Device Make & Model
20- Color Space
21- Color Profile
22- Focal Length
23- Alpha Channel
24- Red Eye
25- Metering Mode
26- F Number
27
28Regardless of your reasoning, I'm going to explain how I used the
29=exiftool= package in Linux to automatically strip metadata from all
30images in a directory (+ subdirectories).
31
32** Installing =exiftool=
33:PROPERTIES:
34:CUSTOM_ID: installing-exiftool
35:END:
36First things first: we need to install the tool. I'm running Debian 11
37on my server (Ubuntu will work the same), so the command is as simple
38as:
39
40#+begin_src sh
41sudo apt install exiftool
42#+end_src
43
44There are different tools that can accomplish the same thing across
45distributions, but I really only care to test out this one package.
46
47** Recursively Strip Data
48:PROPERTIES:
49:CUSTOM_ID: recursively-strip-data
50:END:
51I actually use this tool extensively to strip any photos uploaded to the
52website that serves all the images for my blog (=img.cleberg.net=).
53
54The following command is incredibly useful and can be modified to
55include any image extensions that =exiftool= supports:
56
57#+begin_src sh
58exiftool -r -all= -ext jpg -ext png /path/to/directory/
59#+end_src
60
61See below for the results of my most recent usage of =exiftool= after I
62uploaded the image for this blog post. You can see that the command will
63let you know how many directories were scanned, how many images were
64updated, and how many images were unchanged.
65
66#+caption: exiftool results
67[[https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png]]
blog/2022-02-20-nginx-caching.org deleted −75
@@ -1,75 +0,0 @@
1#+title: Caching Static Content with Nginx
2#+date: 2022-02-20
3
4** Update Your Nginx Config to Cache Static Files
5:PROPERTIES:
6:CUSTOM_ID: update-your-nginx-config-to-cache-static-files
7:END:
8If you run a website on Nginx that serves static content (i.e., content
9that is not dynamic and changing with interactions from the user), you
10would likely benefit from caching that content on the client-side. If
11you're used to Apache and looking for the Nginx equivalent, this post
12should help.
13
14Luckily, setting up the cache is as easy as identifying the file types
15you want to cache and determining the expiration length. To include more
16file types, simply use the bar separator (=|=) and type the new file
17extension you want to include.
18
19#+begin_src config
20server {
21 ...
22
23 location ~* .(css|js|jpg|jpeg|gif|png|ico)$ {
24 expires 30d;
25 }
26
27 ...
28}
29#+end_src
30
31I have seen some people who prefer to set =expires= as =365d= or even
32=max=, but that is only for stable, infrequently changing websites. As
33my site often changes (i.e., I'm never content with my website), I need
34to know that my readers are seeing the new content without waiting too
35long.
36
37So, I went ahead and set the expiration date at =30d=, which is short
38enough to refresh for readers but long enough that clients/browsers
39won't be re-requesting the static files too often, hopefully resulting
40in faster loading times, as images should be the only thing slowing down
41my site.
42
43** Testing Results
44:PROPERTIES:
45:CUSTOM_ID: testing-results
46:END:
47To test my changes to the Nginx configuration, I used the
48[[https://addons.mozilla.org/en-US/firefox/addon/http-header-live/][HTTP
49Header Live]] extension on my Gecko browser and used the sidebar to
50inspect the headers of a recent image from my blog.
51
52In the image below, you can see that the =Cache-Control= header is now
53present and set to 2592000, which is 30 days represented in seconds (30
54days _ 24 hours/day _ 60 minutes/hour * 60 seconds/minute = 2,592,000
55seconds).
56
57The =Expires= field is now showing 22 March 2022, which is 30 days from
58the day of this post, 20 February 2022.
59
60#+caption: Image Headers
61[[https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png]]
62
63** Caveats
64:PROPERTIES:
65:CUSTOM_ID: caveats
66:END:
67Remember that this caching system is *client-side*, which means that
68content is only cached for as long as a client allows it. For example,
69my browser purges all caches, data, etc. upon exit, so this caching
70policy will only work as long as my browser remains open and running.
71
72If you need to test updates to your site, you'll need to clear the cache
73to see updates for any file extension you configured. This can often be
74done with the =Shift + F5= or =Ctrl + F5= key combinations in most
75browsers.
blog/2022-02-22-tuesday.org deleted −38
@@ -1,38 +0,0 @@
1#+title: Tuesday
2#+date: 2022-02-22
3
4** Tuesday, Twosday
5:PROPERTIES:
6:CUSTOM_ID: tuesday-twosday
7:END:
8I'm taking a break from my usual technology-related posts and writing
9about something a little more enjoyable today.
10
11Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
12Twosday.
13
14Let's look at everything that fell in place today:
15
161. Written in the =m-dd-yy= or =dd-m-yy= formats, today is 2-22-22 or
17 22-2-22, which is a neat little palindrome in either format. (The
18 last ubiquitous six-digit palindrome was 1-11-11.)
192. Today is Tuesday, which is why everyone is using the nickname Twosday
20 to call out these similarities.
213. Falling on Tuesday means today is the 2nd day of the week (for most
22 cultures. For the US, it's the 3rd day of the week since we start on
23 Sunday).
244. The only culture I could find with a connection to a =2= is that some
25 Slavic languages derived their version of Tuesday from the Old Church
26 Slavonic word =въторъ=, meaning "the second."
275. Written in the classic monospaced, digital font (think of digital
28 clocks from the 80s/90s), there is nice symmetry to the numbers
29 ([[https://img.cleberg.net/blog/20220222-tuesday/digital_font.png][view
30 the image here]]!).
316. This one isn't naturally-occurring, but it seems people around the
32 world are celebrating the day. For example, a group is putting
33 together
34 [[https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597][a
35 wedding of 222 couples at the California State Capitol in
36 Sacramento]], concluding at exactly 2:22 PM. These couples will
37 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
38 for $222.22.
blog/2022-03-03-financial-database.org deleted −272
@@ -1,272 +0,0 @@
1#+title: Maintaining a Personal Financial Database
2#+date: 2022-03-03
3
4** Personal Financial Tracking
5:PROPERTIES:
6:CUSTOM_ID: personal-financial-tracking
7:END:
8For the last 6-ish years, I've tracked my finances in a spreadsheet.
9This is common practice in the business world, but any good dev will
10cringe at the thought of storing long-term data in a spreadsheet. A
11spreadsheet is not for long-term storage or as a source of data to pull
12data/reports.
13
14As I wanted to expand the functionality of my financial data (e.g.,
15adding more reports), I decided to migrate the data into a database. To
16run reports, I would query the database and use a language like Python
17or Javascript to process the data, perform calculations, and visualize
18the data.
19
20** SQLite
21:PROPERTIES:
22:CUSTOM_ID: sqlite
23:END:
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 for
28 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 and backup.
35I keep this file in my cloud storage and pull it up whenever needed.
36
37*** GUI Editing
38:PROPERTIES:
39:CUSTOM_ID: gui-editing
40:END:
41Since I didn't want to try and import 1000--1500 records into my new
42database via the command line, I opted to use
43[[https://sqlitebrowser.org/][DB Browser for SQLite (DB4S)]] as a GUI
44tool. This application is excellent, and I don't see myself going back
45to the CLI when working in this database.
46
47DB4S allows you to copy a range of cells from a spreadsheet and paste it
48straight into the SQL table. I used this process for all 36 accounts,
491290 account statements, and 126 pay statements. Overall, I'm guessing
50this took anywhere between 4--8 hours. In comparison, it probably took
51me 2-3 days to initially create the spreadsheet.
52
53#+caption: DB4S
54[[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png]]
55
56*** Schema
57:PROPERTIES:
58:CUSTOM_ID: schema
59:END:
60The schema for this database is actually extremely simple and involves
61only three tables (for now):
62
631. Accounts
642. Statements
653. Payroll
66
67*Accounts*
68
69The Accounts table contains summary information about an account, such
70as a car loan or a credit card. By viewing this table, you can find
71high-level data, such as interest rate, credit line, or owner.
72
73#+begin_src sql
74CREATE TABLE "Accounts" (
75 "AccountID" INTEGER NOT NULL UNIQUE,
76 "AccountType" TEXT,
77 "AccountName" TEXT,
78 "InterestRate" NUMERIC,
79 "CreditLine" NUMERIC,
80 "State" TEXT,
81 "Owner" TEXT,
82 "Co-Owner" TEXT,
83 PRIMARY KEY("AccountID" AUTOINCREMENT)
84)
85#+end_src
86
87*Statements*
88
89The Statements table uses the same unique identifier as the Accounts
90table, meaning you can join the tables to find a monthly statement for
91any of the accounts listed in the Accounts table. Each statement has an
92account ID, statement date, and total balance.
93
94#+begin_src sql
95CREATE TABLE "Statements" (
96 "StatementID" INTEGER NOT NULL UNIQUE,
97 "AccountID" INTEGER,
98 "StatementDate" INTEGER,
99 "Balance" NUMERIC,
100 PRIMARY KEY("StatementID" AUTOINCREMENT),
101 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
102)
103#+end_src
104
105*Payroll*
106
107The Payroll table is a separate entity, unrelated to the Accounts or
108Statements tables. This table contains all information you would find on
109a pay statement from an employer. As you change employers or obtain new
110perks/benefits, just add new columns to adapt to the new data.
111
112#+begin_src sql
113CREATE TABLE "Payroll" (
114 "PaycheckID" INTEGER NOT NULL UNIQUE,
115 "PayDate" TEXT,
116 "Payee" TEXT,
117 "Employer" TEXT,
118 "JobTitle" TEXT,
119 "IncomeRegular" NUMERIC,
120 "IncomePTO" NUMERIC,
121 "IncomeHoliday" NUMERIC,
122 "IncomeBonus" NUMERIC,
123 "IncomePTOPayout" NUMERIC,
124 "IncomeReimbursements" NUMERIC,
125 "FringeHSA" NUMERIC,
126 "FringeStudentLoan" NUMERIC,
127 "Fringe401k" NUMERIC,
128 "PreTaxMedical" NUMERIC,
129 "PreTaxDental" NUMERIC,
130 "PreTaxVision" NUMERIC,
131 "PreTaxLifeInsurance" NUMERIC,
132 "PreTax401k" NUMERIC,
133 "PreTaxParking" NUMERIC,
134 "PreTaxStudentLoan" NUMERIC,
135 "PreTaxOther" NUMERIC,
136 "TaxFederal" NUMERIC,
137 "TaxSocial" NUMERIC,
138 "TaxMedicare" NUMERIC,
139 "TaxState" NUMERIC,
140 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
141)
142#+end_src
143
144*** Python Reporting
145:PROPERTIES:
146:CUSTOM_ID: python-reporting
147:END:
148Once I created the database tables and imported all my data, the only
149step left was to create a process to report and visualize on various
150aspects of the data.
151
152In order to explore and create the reports I'm interested in, I utilized
153a two-part process involving Jupyter Notebooks and Python scripts.
154
155**** Step 1: Jupyter Notebooks
156:PROPERTIES:
157:CUSTOM_ID: step-1-jupyter-notebooks
158:END:
159When I need to explore data, try different things, and re-run my code
160cell-by-cell, I use Jupyter Notebooks. For example, I explored the
161=Accounts= table until I found the following useful information:
162
163#+begin_src python
164import sqlite3
165import pandas as pd
166import matplotlib
167
168# Set up database filename and connect
169db = "finances.sqlite"
170connection = sqlite3.connect(db)
171df = pd.read_sql_query("SELECT * FROM Accounts", connection)
172
173# Set global matplotlib variables
174%matplotlib inline
175matplotlib.rcParams['text.color'] = 'white'
176matplotlib.rcParams['axes.labelcolor'] = 'white'
177matplotlib.rcParams['xtick.color'] = 'white'
178matplotlib.rcParams['ytick.color'] = 'white'
179matplotlib.rcParams['legend.labelcolor'] = 'black'
180
181# Display graph
182df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
183#+end_src
184
185**** Step 2: Python Scripts
186:PROPERTIES:
187:CUSTOM_ID: step-2-python-scripts
188:END:
189Once I explored enough through the notebooks and had a list of reports I
190wanted, I moved on to create a Python project with the following
191structure:
192
193#+begin_example
194finance/
195├── notebooks/
196│ │ ├── account_summary.ipynb
197│ │ ├── account_details.ipynb
198│ │ └── payroll.ipynb
199├── public/
200│ │ ├── image-01.png
201│ │ └── image-0X.png
202├── src/
203│ └── finance.sqlite
204├── venv/
205├── _init.py
206├── database.py
207├── process.py
208├── requirements.txt
209└── README.md
210#+end_example
211
212This structure allows me to:
213
2141. Compile all required python packages into =requirements.txt= for easy
215 installation if I move to a new machine.
2162. Activate a virtual environment in =venv/= so I don't need to maintain
217 a system-wide Python environment just for this project.
2183. Keep my =notebooks/= folder to continuously explore the data as I see
219 fit.
2204. Maintain a local copy of the database in =src/= for easy access.
2215. Export reports, images, HTML files, etc. to =public/=.
222
223Now, onto the differences between the code in a Jupyter Notebook and the
224actual Python files. To create the report in the Notebook snippet above,
225I created the following function inside =process.py=:
226
227#+begin_src python
228# Create summary pie chart
229def summary_data(accounts: pandas.DataFrame) -> None:
230 accounts_01 = accounts[accounts["Owner"] == "Person01"]
231 accounts_02 = accounts[accounts["Owner"] == "Person02"]
232 for x in range(1, 4):
233 if x == 1:
234 df = accounts
235 account_string = "All Accounts"
236 elif x == 2:
237 df = accounts_01
238 account_string = "Person01's Accounts"
239 elif x == 3:
240 df = accounts_02
241 account_string = "Person02's Accounts"
242 print(f"Generating pie chart summary image for {account_string}...")
243 summary_chart = (
244 df.groupby(["AccountType"])
245 .sum()
246 .plot.pie(
247 title=f"Credit Line by Type for {account_string}",
248 y="CreditLine",
249 autopct="%1.1f%%",
250 )
251 )
252 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
253#+end_src
254
255The result? A high-quality pie chart that is read directly by the
256=public/index.html= template I use.
257
258#+caption: Summary Pie Chart
259[[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png]]
260
261Other charts generated by this project include:
262
263- Charts of account balances over time.
264- Line chart of effective tax rate (taxes divided by taxable income).
265- Salary projections and error limits using past income and inflation
266 rates.
267- Multi-line chart of gross income, taxable income, and net income.
268
269The best thing about this project? I can improve it at any given time,
270shaping it into whatever helps me the most for that time. I imagine that
271I will be introducing an asset tracking table soon to track the
272depreciating value of cars, houses, etc. Who knows what's next?
blog/2022-03-08-plex-migration.org deleted −270
@@ -1,270 +0,0 @@
1#+title: Migrating Plex to New Server (+ Nvidia Transcoding)
2#+date: 2022-03-08
3
4** Migration Phases
5:PROPERTIES:
6:CUSTOM_ID: migration-phases
7:END:
8I recently decided to migrate my server from an old OptiPlex desktop
9machine to a custom-built tower with better hardware in every category.
10In order to do this, I would need to properly migrate a full Plex
11installation.
12
13The second part of this migration is that the new server uses an Nvidia
14GPU and does not have any integrated graphics, which requires extra work
15for installation, but provides much better hardware transcoding options
16for Plex.
17
18Therefore, I have broken this migration down into three phases:
19
201. [[#phase-1-configure-the-new-server][Configure the New Server]]
212. [[#phase-2-migrate-plex-data-devices][Migrate Plex Data & Devices]]
223. [[#phase-3-configure-gpu-transcoding][Configure GPU Transcoding]]
23
24--------------
25
26** Phase 1: Configure the New Server
27:PROPERTIES:
28:CUSTOM_ID: phase-1-configure-the-new-server
29:END:
30*** Choosing an OS
31:PROPERTIES:
32:CUSTOM_ID: choosing-an-os
33:END:
34In order to migrate Plex to my new server, I first needed to choose an
35appropriate operating system (OS) and install it on the machine. Given
36that I have encountered numerous issues installing other Linux
37distributions properly with Nvidia graphics, I chose
38[[https://ubuntu.com/download/server][Ubuntu Server]].
39
40The first step is to create a bootable USB with Ubuntu Server. This is
41easy with [[https://www.balena.io/etcher/][Etcher]], an app that runs on
42many different platforms. Just download the Ubuntu Server =.iso= image,
43launch Etcher, and install the =.iso= on the USB.
44
45Once the USB is created, insert it into my server, reboot, and click
46=Esc= (or any of the =F1-12= keys) until the BIOS menu appears. Finally,
47launch the USB boot drive.
48
49*** Booting with Nvidia
50:PROPERTIES:
51:CUSTOM_ID: booting-with-nvidia
52:END:
53In order to install Ubuntu Server with an Nvidia Graphics card (and no
54integrated graphics on this device for some reason), you'll have to
55configure the boot menu to allow different graphics drivers to be
56loaded.
57
58When booting from the USB, the machine will launch the initial
59installation menu. From this menu, type =e= to view the default command
60options that come with the device - it's a good idea to take a photo of
61this screen, so you can enter these commands on the next screen (along
62with adding support for Nvidia).
63
64Finally, type =Ctrl + C= to enter the command line. From this command
65line, enter the commands found on the =e= screen. *Remember to add
66=nomodeset= to the =linux ...= line so that your Nvidia device will
67display the installation screens properly!*
68
69Here's an example of the commands I pulled from the =e= screen and
70entered on the command line.
71
72#+begin_src sh
73setparams 'Install Ubuntu Server'
74setgfxpayload=keep
75linux /casper/vmlinuz quiet nomodeset ---
76initrd /casper/initrd
77boot
78#+end_src
79
80#+caption: Ubuntu Server Installation 01
81[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png]]
82
83#+caption: Ubuntu Server Installation 02
84[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png]]
85
86Once the machine is rebooted, enter the =e= screen again and add
87=nomodeset= to the =linux ...= line again and press =Ctrl + X= to save
88the boot options.
89
90The machine is now fully installed and can properly display on an
91external display using the Nvidia GPU.
92
93Always remember to update and upgrade on a new installation:
94
95#+begin_src sh
96sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
97#+end_src
98
99--------------
100
101** Phase 2: Migrate Plex Data & Devices
102:PROPERTIES:
103:CUSTOM_ID: phase-2-migrate-plex-data-devices
104:END:
105This phase uses the great Plex article on migrations
106([[https://support.plex.tv/articles/201370363-move-an-install-to-another-system/][Move
107an Installation to Another System]]) and adds a bit more information to
108help with commands and context.
109
110*** Terminology
111:PROPERTIES:
112:CUSTOM_ID: terminology
113:END:
114*Source:* The original server that is being replaced.\\
115*Destination:* The new server.\\
116*Client:* Any application that can be used to modify settings for both
117source/destination.
118
119*** Step 01: [Client] Update Settings
120:PROPERTIES:
121:CUSTOM_ID: step-01-client-update-settings
122:END:
123Open up a Plex app and /disable/ the =Account= > =Library= >
124=Empty trash automatically after every scan= preference for the source
125server.
126
127*** Step 02: [Destination] Install Plex
128:PROPERTIES:
129:CUSTOM_ID: step-02-destination-install-plex
130:END:
131Open up the [[https://www.plex.tv/media-server-downloads/][Plex Media
132Server download page]] and copy the link for the appropriate platform.
133
134Execute the following commands on the destination server to install
135Plex:
136
137#+begin_src sh
138wget <url>
139sudo dpkg -i <filename>
140sudo systemctl stop plexmediaserver.service
141#+end_src
142
143*** Step 03: [Source] Stop Plex & Migrate Data
144:PROPERTIES:
145:CUSTOM_ID: step-03-source-stop-plex-migrate-data
146:END:
147First, stop the Plex service so that no data is created or modified
148during the migration.
149
150#+begin_src sh
151sudo systemctl stop plexmediaserver.service
152#+end_src
153
154Next, copy the data to the new server. To find where the Plex data
155directory is located, Plex has another excellent article available:
156[[https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/][Where
157is the Plex Media Server data directory located?]].
158
159There are many ways to copy the data to the new server and will largely
160depend on the size of the folder being copied. Personally, my data
161folder was ~23GB and I opted to simply use the =scp= command to copy the
162files over SSH.
163
164This process was throttled by the old server's slow HDD and ports and
165took approximately 90 minutes to complete. In comparison, moving the
166data from the new server's =home/user/= directory to the
167=/var/.../Plex Media Server= directory took 2-3 minutes.
168
169#+begin_src sh
170scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
171#+end_src
172
173*** Step 04: [Destination] Update File Permissions
174:PROPERTIES:
175:CUSTOM_ID: step-04-destination-update-file-permissions
176:END:
177In case you move the data directory to a common area on the new server,
178it will have to be moved to the proper location before Plex can function
179properly:
180
181#+begin_src sh
182mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
183#+end_src
184
185To ensure permissions were retained properly, the server will need to
186show that all files and folders in the data directory are owned by
187=plex:plex= (or whichever user is running the Plex application).
188
189#+begin_src sh
190sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
191#+end_src
192
193Finally, start the service and check the status.
194
195#+begin_src sh
196sudo systemctl start plexmediaserver.service
197sudo systemctl status plexmediaserver.service
198#+end_src
199
200*** Step 05: [Client] Update Libraries & Metadata
201:PROPERTIES:
202:CUSTOM_ID: step-05-client-update-libraries-metadata
203:END:
204The first step - now that the new server is up and running - is to sign
205out of the client and sign back in. Once this is done, update any
206library locations, if necessary. This was unnecessary in my case since I
207simply moved my storage drives from the source server to the destination
208server.
209
210Next, perform the following actions in the client:
211
2121. On the left sidebar, click =More= > Three-Dot Menu >
213 =Scan Library Files=
2142. /Enable/ the =Account= > =Library= >
215 =Empty trash automatically after every scan= preference for the
216 source server.
2173. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
218 > =Empty Trash=
2194. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
220 > =Clean Bundles=
2215. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
222 > =Optimize Database=
223
224Finally, double-check the Remote Access settings to make sure no changes
225have caused issues with accessing the server from outside the network.
226
227In my case, I use a single port forwarding rule in my router and needed
228to update the Local LAN IP Address to the new server IP address.
229
230#+caption: Router port forwarding
231[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png]]
232
233--------------
234
235** Phase 3: Configure GPU Transcoding
236:PROPERTIES:
237:CUSTOM_ID: phase-3-configure-gpu-transcoding
238:END:
239The final piece to the migration is enabling hardware transcoding so
240that Plex can fully utilize the new Nvidia GPU available in the server.
241The first step is to install Nvidia graphics drivers. This process may
242take a few minutes, but the commands are pretty simple:
243
244#+begin_src sh
245sudo add-apt-repository ppa:graphics-drivers/ppa
246sudo apt update
247sudo apt-get install ubuntu-drivers-common
248sudo ubuntu-drivers autoinstall
249#+end_src
250
251Finally, reboot so that the changes are loaded:
252
253#+begin_src sh
254sudo reboot now
255#+end_src
256
257To ensure that the Nvidia graphics drivers are working properly, run the
258following command to view the available GPUs, statistics, and processes:
259
260#+begin_src sh
261sudo nvidia-smi
262#+end_src
263
264#+caption: nvidia-smi
265[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png]]
266
267Finally, enable hardware transcoding settings in the Plex application:
268
269#+caption: Plex transcoding settings
270[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png]]
blog/2022-03-24-server-hardening.org deleted −386
@@ -1,386 +0,0 @@
1#+title: Hardening a Public-Facing Home Server
2#+date: 2022-03-24
3
4** Post Updates
5:PROPERTIES:
6:CUSTOM_ID: post-updates
7:END:
8
9#+begin_quote
10After reviewing this post today (2022-10-04), I noticed quite a few gaps
11in my write-up and wanted to add a few things, even though this blog is
12really just a retrospective and knowledge dump for myself. I left things
13intact and simply crossed them out (+like this+) for posterity.
14
15#+end_quote
16
17** Planning Data Flows & Security
18:PROPERTIES:
19:CUSTOM_ID: planning-data-flows-security
20:END:
21*** My Personal Data Flow
22:PROPERTIES:
23:CUSTOM_ID: my-personal-data-flow
24:END:
25#+begin_src txt
26 ┌───────┐ ┌─────────────────┐
27 ┌──► VLAN1 ├───► Private Devices │
28 │ └───────┘ └─────────────────┘
29┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
30│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
31└──────────┘ └────────┘ └──────────┘ └────────┘ │
32 │ ┌───────┐ ┌───────────────┐
33 └──► VLAN2 ├───► Public Server │
34 └───────┘ └───────────────┘
35#+end_src
36
37*** Thought Process
38:PROPERTIES:
39:CUSTOM_ID: thought-process
40:END:
41To serve content from your home server and harden your security posture,
42you have to think about the transport of data from =server= to =client=.
43
44Let's start with the actual server itself. Think about the following:
45
46- Do I have a firewall enabled? Do I need to update this to allow new
47 ports or IPs?
48- Do I have an IPS/IDS that may prevent outside traffic?
49- Do I have any other security software installed?
50- Are the services hosted inside Docker containers, behind a reverse
51 proxy, or virtualized? If so, are they configured to allow outside
52 traffic?
53
54Once the data leaves the server, where does it go? In my case, it goes
55to a managed switch. In this case, I asked the following:
56
57- What configurations is the switch using?
58- Am I using VLANs?
59 - Yes, I am using 802.1Q VLANs.
60- Are the VLANs configured properly?
61 - Yes, as shown in the [[#switch][Switch]] section below, I have a
62 separate VLAN to allow outside traffic to and from the server alone.
63 No other devices, except for a service port, and in that VLAN.
64
65At this point, the data has been processed through the switch. Where
66does it go next? In my case, it's pretty simple: it goes to the
67router/modem device.
68
69- Does my ISP block any ports that I need?
70 - This is an important step that a lot of people run into when
71 self-hosting at home. Use an online port-checker tool for your IP or
72 call your ISP if you think ports are blocked.
73- Is there a router firewall?
74 - Yes, I checked that it's configured to allow the ports I need to run
75 my services publicly. Common web servers and reverse proxies require
76 ports 80 and 443, but other services like media servers or games can
77 require unique ports, so be sure to check the documentation for your
78 service(s).
79- Are there any other settings affecting inbound/outbound traffic?
80 - Schedules or access blocks
81 - Static Routing
82 - QoS
83 - Port Forwarding
84 - DMZ Hosting
85 - Remote Management (this can sometimes mess with services that also
86 require the use of ports 80 and 443)
87
88Once the data leaves my router, it goes to the upstream ISP and can be
89accessed publicly.
90
91** Server
92:PROPERTIES:
93:CUSTOM_ID: server
94:END:
95+The services I run on my server are installed straight into the OS,
96without any use of Docker or VMs, so I don't need any extra application
97configuration to make them accessible to the outside world.+
98
99As of 2022-10-04, the paragraph above is no longer true as I now run a
100reverse proxy with Nginx and host many services inside Docker. However,
101it doesn't change anything regarding this post as I still just need to
102open ports 80 & 443 and create the necessary website configuration
103files.
104
105When creating new services - either installed directly on bare metal or
106within something like Docker - I ensure that I read through the
107documentation thoroughly to understand a few key things: - What network
108activities should this app perform (if any)? Using which ports and
109protocols? - Does this app require any commands/services to be run as
110=root=? - Does this app log errors, authentication failures/successes,
111or anything else that would be useful for an investigation?
112
113For extra security, I use limit all incoming connections to SSH
114connections through my server firewall (=ufw=) and disable common SSH
115settings. After all of that, I use =fail2ban= as a preventative measure
116against brute-force login attempts.
117
118As another piece of security, you can randomize your SSH port to ensure
119that random scanners or attackers can't easily try to force their way
120into your network. For example, you can edit the port rules in your
121server to block all connection requests to port =22= but forward all
122remote connections from port =12345= to your server's port =22=. Then
123you just need to SSH to your network via your randomized port.
124
125*** =ufw=
126:PROPERTIES:
127:CUSTOM_ID: ufw
128:END:
129To see how to configure =ufw=, see my other post:
130[[/blog/secure-your-network-with-the-uncomplicated-firewall/][Secure
131Your Network with the Uncomplicated Firewall]].
132
133The general notion with an on-device firewall is that you want to deny
134all incoming connections by default and then selectively open certain
135ports for services or users that you know need access.
136
137If you know that you will only be logging into this server from a
138certain set or list of IPs, you can always set the firewall to only
139allow connections to port 22 from those IPs.
140
141For a quick start to only allow SSH connections to the server, use this:
142
143#+begin_src sh
144sudo ufw default deny incoming
145sudo ufw default allow outgoing
146sudo ufw allow 22
147sudo ufw enable
148#+end_src
149
150#+caption: ufw
151[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png]]
152
153*** =ssh=
154:PROPERTIES:
155:CUSTOM_ID: ssh
156:END:
157**** Using SSH Keys
158:PROPERTIES:
159:CUSTOM_ID: using-ssh-keys
160:END:
161First, make sure you have an SSH keypair generated on the device(s) that
162you'll be using to log in to the server. If you don't have an SSH key,
163run this command:
164
165#+begin_src sh
166ssh-keygen
167#+end_src
168
169Now that we have an SSH key, copy it to the server with the following
170command, which will ask for the user's password before accepting the
171key:
172
173#+begin_src sh
174ssh-copy-id my_user@my_server
175#+end_src
176
177If you have multiple keys, you'll need to specify which to use. After
178it's complete, =ssh= back into the server as that user and make sure it
179doesn't ask for a password.
180
181**** Disable Password & Root Authentication
182:PROPERTIES:
183:CUSTOM_ID: disable-password-root-authentication
184:END:
185Now that we can access the server without a password, we will disable
186password authentication and disable anyone from using =ssh= to login as
187=root=.
188
189To do this, open the =sshd_config= file:
190
191#+begin_src sh
192sudo nano /etc/ssh/sshd_config
193#+end_src
194
195You'll need to update the parameters to the values below. If one of
196these rules is commented-out or doesn't exist, create the rule at the
197bottom of the file.
198
199#+begin_src config
200PermitRootLogin no
201PasswordAuthentication no
202PubkeyAuthentication yes
203#+end_src
204
205Finally, restart the =ssh= service:
206
207#+begin_src sh
208sudo systemctl restart sshd.service
209#+end_src
210
211To test that everything's working so far, open ANOTHER terminal and try
212logging in as =root= over SSH. It is very important that you keep your
213current SSH session open and test with an additional session, or you
214will lock yourself out at some point and will need to use a recovery
215method (e.g., hooking monitor up to home server) to get yourself back
216in.
217
218**** Enable MFA for =ssh=
219:PROPERTIES:
220:CUSTOM_ID: enable-mfa-for-ssh
221:END:
222This part is optional, but I highly recommend it. So far, we've ensured
223that no one can log into our user on the server without using our secret
224key, and we've ensured that no one can log in remotely as =root=. Next,
225you can enable MFA authentication for =ssh= connections.
226
227This process involves editing a couple files and installing an MFA
228package, so I will not include all the details in this post. To see how
229to configure MFA for =ssh=, see my other post:
230[[/blog/enable-totp-mfa-for-ssh/][Enabling MFA for SSH]].
231
232#+caption: SSH MFA
233[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png]]
234
235*** =fail2ban=
236:PROPERTIES:
237:CUSTOM_ID: fail2ban
238:END:
239I haven't written a post on how I use =fail2ban=, but it's quite simple.
240I use the default =sshd= jail, but you can always create new jails for
241respective applications or ports. For example, if you use Nginx as your
242web server, you can use the =nginx-http-auth= jail.
243
244In order to get it up and running, use the following commands:
245
246#+begin_src sh
247sudo apt install fail2ban
248sudo fail2ban-client start sshd
249sudo fail2ban-client status sshd
250#+end_src
251
252This should be used as a last-resort defense and shouldn't be a
253replacement for the security measures mentioned above.
254
255#+caption: fail2ban
256[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png]]
257
258** Switch
259:PROPERTIES:
260:CUSTOM_ID: switch
261:END:
262Between the router and any local devices is my managed switch, which is
263used to create VLANs. The example below shows how I would isolate the
264VLANs if I were starting to host a single service at home.
265
266*** 802.1Q VLAN Configuration
267:PROPERTIES:
268:CUSTOM_ID: q-vlan-configuration
269:END:
270In this configuration, port 8 is the public server that needs to be
271accessed from the outside. Port 23 is my 'dedicated service port' for
272this server. In order to SSH to this server, I need to plug my laptop
273into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a
274monitor and keyboard directly to the server to manage it.
275
276| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
277|---------+-----------+--------------+--------------+----------------|
278| 1 | Default | 1-24 | | 1-24 |
279| 2 | Server | 1,8,23 | | 1,8,23 |
280
281*** 802.1Q VLAN PVID Setting
282:PROPERTIES:
283:CUSTOM_ID: q-vlan-pvid-setting
284:END:
285Once the VLAN is created, I simply add the =VLAN ID= of =2= as the
286=PVID= for any related ports (in this case, see that ports =8= and =23=
287have a PVID of =2=).
288
289| Port | PVID |
290|------+------|
291| 1 | 1 |
292| 2 | 1 |
293| 3 | 1 |
294| 4 | 1 |
295| 5 | 1 |
296| 6 | 1 |
297| 7 | 1 |
298| 8 | 2 |
299| 9 | 1 |
300| 10 | 1 |
301| 11 | 1 |
302| 12 | 1 |
303| 13 | 1 |
304| 14 | 1 |
305| 15 | 1 |
306| 16 | 1 |
307| 17 | 1 |
308| 18 | 1 |
309| 19 | 1 |
310| 20 | 1 |
311| 21 | 1 |
312| 22 | 1 |
313| 23 | 2 |
314| 24 | 1 |
315
316** Router
317:PROPERTIES:
318:CUSTOM_ID: router
319:END:
320On my router, the configuration was as easy as opening the firewall
321settings and unblocking the ports I needed for my services (e.g.,
322HTTP/S, Plex, SSH, MySQL, etc.).
323
324+Since I'm relying on an ISP-provided modem/router combo for now (not by
325choice), I do not use any other advanced settings on my router that
326would inhibit any valid traffic to these services.+
327
328The paragraph above regarding the ISP-owned router is no longer accurate
329as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
330this router, I enabled port forwarding/firewall rules, segregate the
331network based on the device, and enable traffic restrictions (e.g.,
332silently drop traffic from certain countries and threat categories).
333
334If you have the option with your ISP, I recommend using a personal
335router with software that you are familiar with so that you can explore
336all the options available to you.
337
338** Physical Security
339:PROPERTIES:
340:CUSTOM_ID: physical-security
341:END:
342One large piece of self-hosting that people generally don't discuss
343online is physical security. However, physical security is very
344important for everyone who hosts a server like this. Exactly /how/
345important it is depends on the server use/purpose.
346
347If you self-host customer applications that hold protected data (HIPAA,
348GDPR, COPPA, etc.), then physical security is extremely important and
349cannot be ignored. If you simply host a blog and some hobby sites, then
350it's a relatively minor consideration, but one you still need to think
351about.
352
353*** Location
354:PROPERTIES:
355:CUSTOM_ID: location
356:END:
357The first consideration is quite simple: location. - Is the server
358within a property you own or housed on someone else's property? - Is it
359nearby (in your house, in your work office, in your neighbor's garage,
360in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
361there climate considerations, such as humidity, fires, tornadoes,
362monsoons? - Do you have emergency equipment nearby in case of emergency?
363
364*** Hardware Ownership
365:PROPERTIES:
366:CUSTOM_ID: hardware-ownership
367:END:
368Secondly, consider the hardware itself: - Do you own the server in its
369entirety? - Are any other users able to access the server, even if your
370data/space is segregated? - If you're utilizing a third party, do they
371have any documentation to show responsibility? This could be a SOC 1/2/3
372report, ISO compliance report, internal security/safety documentation.
373
374*** Physical Controls
375:PROPERTIES:
376:CUSTOM_ID: physical-controls
377:END:
378Regardless of who owns the hardware, ensure that there are adequate
379safeguards in place, if necessary. These usually don't apply to small
380home servers and are usually covered already if you're utilizing a third
381party.
382
383These can include: - Server bezel locks - Server room locks - physical,
384digital, or biometric authentication - Security cameras - Raised
385floors/lowered ceilings with proper guards/gates in-place within the
386floors or ceilings - Security personnel - Log sheets and/or guest badges
blog/2022-04-02-nginx-reverse-proxy.org deleted −247
@@ -1,247 +0,0 @@
1#+title: Set-Up a Reverse Proxy with Nginx
2#+date: 2022-04-02
3
4** What is a Reverse Proxy?
5:PROPERTIES:
6:CUSTOM_ID: what-is-a-reverse-proxy
7:END:
8A reverse proxy is a server that is placed between local servers or
9services and clients/users (e.g., the internet). The reverse proxy
10intercepts all requests from clients at the network edge and uses its
11configuration files to determine where each request should be sent.
12
13*** A Brief Example
14:PROPERTIES:
15:CUSTOM_ID: a-brief-example
16:END:
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 clients to
29request 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 client is
33requesting valid data, based on my configuration.
34
35If the user is requesting =example.com= and my configuration files say
36that Server_01 holds that data, Nginx will send the user to Server_01.
37If I were to change the configuration so that =example.com= is routed to
38Server_02, that same user would be sent to Server_02 instead.
39
40#+begin_src txt
41┌──────┐ ┌───────────┐
42│ User │─┐ ┌──► Server_01 │
43└──────┘ │ │ └───────────┘
44 │ ┌──────────┐ ┌───────────────┐ │ ┌───────────┐
45 ├────► Internet ├───► Reverse Proxy ├─────├──► Server_02 │
46 │ └──────────┘ └───────────────┘ │ └───────────┘
47┌──────┐ │ │ ┌───────────┐
48│ User │─┘ └──► Server_03 │
49└──────┘ └───────────┘
50#+end_src
51
52** Reverse Proxy Options
53:PROPERTIES:
54:CUSTOM_ID: reverse-proxy-options
55:END:
56There are a lot of options when it comes to reverse proxy servers, so
57I'm just going to list a few of the options I've heard recommended over
58the last few years:
59
60- [[https://nginx.com][Nginx]]
61- [[https://caddyserver.com][Caddy]]
62- [[https://traefik.io/][Traefik]]
63- [[https://www.haproxy.org/][HAProxy]]
64- [[https://ubuntu.com/server/docs/proxy-servers-squid][Squid]]
65
66In this post, we will be using Nginx as our reverse proxy, running on
67Ubuntu Server 20.04.4 LTS.
68
69** Nginx Reverse Proxy Example
70:PROPERTIES:
71:CUSTOM_ID: nginx-reverse-proxy-example
72:END:
73*** Local Applications
74:PROPERTIES:
75:CUSTOM_ID: local-applications
76:END:
77You may be like me and have a lot of applications running on your local
78network that you'd like to expose publicly with a domain.
79
80In my case, I have services running in multiple Docker containers within
81a single server and want a way to visit those services from anywhere
82with a URL. For example, on my local network,
83[[https://dashy.to][Dashy]] runs through port 4000 (=localhost:4000=)
84and [[https://github.com/louislam/uptime-kuma][Uptime Kuma]] runs
85through port 3001 (=localhost:3001=).
86
87In order to expose these services to the public, I will need to do the
88following:
89
901. Set up DNS records for a domain or subdomain (one per service) to
91 point toward the IP address of the server.
922. Open up the server network's HTTP and HTTPS ports (80 & 443) so that
93 the reverse proxy can accept traffic and determine where to send it.
943. Install the reverse proxy software.
954. Configure the reverse proxy to recognize which service should get
96 traffic from any of the domains or subdomains.
97
98*** Step 1: DNS Configuration
99:PROPERTIES:
100:CUSTOM_ID: step-1-dns-configuration
101:END:
102To start, update your DNS configuration so that you have an =A= record
103for each domain or subdomain.
104
105The =A= records should point toward the public IP address of the server.
106If you don't know the public IP address, log in to the server and run
107the following command:
108
109#+begin_src sh
110curl ifconfig.co
111#+end_src
112
113In the DNS example below, =xxx.xxx.xxx.xxx= is the public IP address of
114the server.
115
116#+begin_src config
117example.com A xxx.xxx.xxx.xxx
118uptime.example.com A xxx.xxx.xxx.xxx
119dashy.example.com A xxx.xxx.xxx.xxx
120www CNAME example.com
121#+end_src
122
123Finally, ensure the DNS has propagated correctly with
124[[https://dnschecker.org][DNS Checker]] by entering your domains or
125subdomains in the search box and ensuring the results are showing the
126correct IP address.
127
128*** Step 2: Open Network Ports
129:PROPERTIES:
130:CUSTOM_ID: step-2-open-network-ports
131:END:
132This step will be different depending on which router you have in your
133home. If you're not sure, try to visit
134[[http://192.168.1.1][192.168.1.1]] in your browser. Login credentials
135are usually written on a sticker somewhere on your modem/router.
136
137Once you're able to log in to your router, find the Port Forwarding
138settings. You will need to forward ports =80= and =443= to whichever
139machine is running the reverse proxy.
140
141In my case, the table below shows the port-forwarding rules I've
142created. In this table, =xxx.xxx.xxx.xxx= is the local device IP of the
143reverse proxy server, it will probably be an IP between =192.168.1.1=
144and =192.168.1.255=.
145
146| NAME | FROM | PORT | DEST PORT/IP | ENABLED |
147|-------+------+------+-----------------+---------|
148| HTTP | ​* | 80 | xxx.xxx.xxx.xxx | TRUE |
149| HTTPS | ​* | 443 | xxx.xxx.xxx.xxx | TRUE |
150
151Once configured, these rules will direct all web traffic to your reverse
152proxy.
153
154*** Step 3: Nginx Installation
155:PROPERTIES:
156:CUSTOM_ID: step-3-nginx-installation
157:END:
158To install Nginx, simply run the following command:
159
160#+begin_src sh
161sudo apt install nginx
162#+end_src
163
164If you have a firewall enabled, open up ports =80= and =443= on your
165server so that Nginx can accept web traffic from the router.
166
167For example, if you want to use =ufw= for web traffic and SSH, run the
168following commands:
169
170#+begin_src sh
171sudo ufw allow 'Nginx Full'
172sudo ufw allow SSH
173sudo ufw enable
174#+end_src
175
176*** Step 4: Nginx Configuration
177:PROPERTIES:
178:CUSTOM_ID: step-4-nginx-configuration
179:END:
180Now that we have domains pointing toward the server, the only step left
181is to configure the reverse proxy to direct traffic from domains to
182local services.
183
184To start, you'll need to create a configuration file for each domain in
185=/etc/nginx/sites-available/=. They will look identical except for the
186=server_name= variable and the =proxy_pass= port.
187
188Dashy:
189
190#+begin_src sh
191nano /etc/nginx/sites-available/dashy.example.com
192#+end_src
193
194#+begin_src config
195server {
196 listen 80;
197 server_name dashy.example.com;
198
199 location / {
200 proxy_pass http://localhost:4000;
201 }
202}
203#+end_src
204
205Uptime:
206
207#+begin_src sh
208nano /etc/nginx/sites-available/uptime.example.com
209#+end_src
210
211#+begin_src config
212server {
213 listen 80;
214 server_name uptime.example.com;
215
216 location / {
217 proxy_pass http://localhost:3001;
218 }
219}
220#+end_src
221
222Once the configuration files are created, you will need to enable them
223with the =symlink= command:
224
225#+begin_src sh
226sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
227#+end_src
228
229Voilà! Your local services should now be available through their URLs.
230
231** HTTPS with Certbot
232:PROPERTIES:
233:CUSTOM_ID: https-with-certbot
234:END:
235If you've followed along, you'll notice that your services are only
236available via HTTP (not HTTPS).
237
238If you want to enable HTTPS for your new domains, you will need to
239generate SSL/TLS certificates for them. The easiest way to generate
240certificates on Nginx is [[https://certbot.eff.org][Certbot]]:
241
242#+begin_src sh
243sudo apt install snapd; sudo snap install core; sudo snap refresh core
244sudo snap install --classic certbot
245sudo ln -s /snap/bin/certbot /usr/bin/certbot
246sudo certbot --nginx
247#+end_src
blog/2022-06-24-fedora-i3.org deleted −170
@@ -1,170 +0,0 @@
1#+title: Rebooting My Love Affair with Linux
2#+date: 2022-06-24
3
4** Leaving macOS
5:PROPERTIES:
6:CUSTOM_ID: leaving-macos
7:END:
8As I noted [[/blog/foss-macos-apps][in a recent post]], I have been
9planning on migrating from macOS back to a Linux-based OS. I am happy to
10say that I have finally completed my migration and am now stuck in the
11wonderful world of Linux again.
12
13My decision to leave macOS really came down to just a few important
14things:
15
16- Apple Security (Gatekeeper) restricting me from running any software I
17 want. Even if you disable Gatekeeper and allow software to bypass the
18 rest of the device installation security, you still have to repeat
19 that process every time the allowed software is updated.
20- macOS sends out nearly constant connections, pings, telemetry, etc. to
21 a myriad of mysterious Apple services. I'm not even going to dive into
22 how many macOS apps have constant telemetry on, as well.
23- Lastly, I just /really/ missed the customization and freedom that
24 comes with Linux. Being able to switch to entirely new kernel, OS, or
25 desktop within minutes is a freedom I took for granted when I switched
26 to macOS.
27
28Now that I've covered macOS, I'm going to move on to more exciting
29topics: my personal choice of OS, DE, and various customizations I'm
30using.
31
32** Fedora
33:PROPERTIES:
34:CUSTOM_ID: fedora
35:END:
36After trying a ton of distros (I think I booted and tested around 20-25
37distros), I finally landed on [[https://getfedora.org/][Fedora Linux]].
38I have quite a bit of experience with Fedora and enjoy the =dnf= package
39manager. Fedora allows me to keep up-to-date with recent software (I'm
40looking at you, Debian), but still provides a level of stability you
41don'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 all the
51software I need. Slackware worked well as a tui, but I wasn't skilled
52enough to get a tiling window manager (WM) working on it.
53
54*** i3
55:PROPERTIES:
56:CUSTOM_ID: i3
57:END:
58One of the reasons I settled on Fedora is that it comes with an official
59i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
60the biggest things I wanted to do as soon as I adopted Linux again.
61
62I will probably set up a dotfile repository soon, so that I don't lose
63any of my configurations, but nothing big has been configured thus far.
64
65The two main things I have updated in i3wm are natural scrolling and
66binding my brightness keys to the =brightnessctl= program.
67
68**** Natural Scrolling
69:PROPERTIES:
70:CUSTOM_ID: natural-scrolling
71:END:
72You can enable natural scrolling by opening the following file:
73
74#+begin_src sh
75sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
76#+end_src
77
78Within the =40-libinput.conf= file, find the following input sections
79and enable the natural scrolling option.
80
81This is the =pointer= section:
82
83#+begin_src conf
84Section "InputClass"
85 Identifier "libinput pointer catchall"
86 MatchIsPointer "on"
87 MatchDevicePath "/dev/input/event*"
88 Driver "libinput"
89 Option "NaturalScrolling" "True"
90EndSection
91#+end_src
92
93This is the =touchpad= section:
94
95#+begin_src conf
96Section "InputClass"
97 Identifier "libinput touchpad catchall"
98 MatchIsTouchpad "on"
99 MatchDevicePath "/dev/input/event*"
100 Driver "libinput"
101 Option "NaturalScrolling" "True"
102EndSection
103#+end_src
104
105**** Enabling Brightness Keys
106:PROPERTIES:
107:CUSTOM_ID: enabling-brightness-keys
108:END:
109Likewise, enabling brightness key functionality is as simple as binding
110the keys to the =brightnessctl= program.
111
112To do this, open up your i3 config file. Mine is located here:
113
114#+begin_src sh
115nano /home/<my-user>/.config/i3/config
116#+end_src
117
118#+begin_src conf
119# Use brightnessctl to adjust brightness.
120bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
121bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
122#+end_src
123
124**** =polybar=
125:PROPERTIES:
126:CUSTOM_ID: polybar
127:END:
128Instead of using the default =i3status= bar, I have opted to use
129=polybar= instead (as you can also see in the screenshot above).
130
131My config for this menu bar is basically just the default settings with
132modified colors and an added battery block to quickly show me the
133machine's battery info.
134
135**** =alacritty=
136:PROPERTIES:
137:CUSTOM_ID: alacritty
138:END:
139Not much to say on this part yet, as I haven't configured it much, but I
140installed =alacritty= as my default terminal, and I am using =zsh= and
141the shell.
142
143** Software Choices
144:PROPERTIES:
145:CUSTOM_ID: software-choices
146:END:
147Again, I'm not going to say much that I haven't said yet in other blog
148posts, so I'll just do a quick rundown of the apps I installed
149immediately after I set up the environment.
150
151Flatpak Apps:
152
153- Cryptomator
154- pCloud
155- Signal
156
157Fedora Packages:
158
159- gomuks
160- neomutt
161- neofetch
162- Firefox
163 - uBlock Origin
164 - Bitwarden
165 - Stylus
166 - Privacy Redirect
167
168Other:
169
170- exiftool
blog/2022-07-01-git-server.org deleted −678
@@ -1,678 +0,0 @@
1#+title: Self-Hosting a Personal Git Server
2#+date: 2022-07-01
3
4** My Approach to Self-Hosting Git
5:PROPERTIES:
6:CUSTOM_ID: my-approach-to-self-hosting-git
7:END:
8I have often tried to self-host my Git repositories, but have always
9fallen short when I tried to find a suitable web interface to show on
10the front-end.
11
12After a few years, I have finally found a combination of methods that
13allow me to easily self-host my projects, view them on the web, and
14access them from anywhere.
15
16Before I dive into the details, I want to state a high-level summary of
17my self-hosted Git approach:
18
19- This method uses the =ssh://= (read & write) and =git://= (read-only)
20 protocols for push and pull access.
21 - For the =git://= protocol, I create a =git-daemon-export-ok= file in
22 any repository that I want to be cloneable by anyone.
23 - The web interface I am using (=cgit=) allows simple HTTP cloning by
24 default. I do not disable this setting as I want beginners to be
25 able to clone one of my repositories even if they don't know the
26 proper method.
27- I am not enabling Smart HTTPS for any repositories. Updates to
28 repositories must be pushed via SSH.
29- Beyond the actual repository management, I am using =cgit= for the
30 front-end web interface.
31 - If you use the =scan-path=<path>= configuration in the =cgitrc=
32 configuration file to automatically find repositories, you can't
33 exclude a repository from =cgit= if it's stored within the path that
34 =cgit= reads. To host private repositories, you'd need to set up
35 another directory that =cgit= can't read.
36
37** Assumptions
38:PROPERTIES:
39:CUSTOM_ID: assumptions
40:END:
41For the purposes of this walkthrough, I am assuming you have a URL
42(=git.example.com=) or IP address (=207.84.26.991=) addressed to the
43server that you will be using to host your git repositories.
44
45** Adding a Git User
46:PROPERTIES:
47:CUSTOM_ID: adding-a-git-user
48:END:
49In order to use the SSH method associated with git, we will need to add
50a user named =git=. If you have used the SSH method for other git
51hosting sites, you are probably used to the following syntax:
52
53#+begin_src sh
54git clone [user@]server:project.git
55#+end_src
56
57The syntax above is an =scp=-like syntax for using SSH on the =git= user
58on the server to access your repository.
59
60Let's delete any remnants of an old =git= user, if any, and create the
61new user account:
62
63#+begin_src sh
64sudo deluser --remove-home git
65sudo adduser git
66#+end_src
67
68*** Import Your SSH Keys to the Git User
69:PROPERTIES:
70:CUSTOM_ID: import-your-ssh-keys-to-the-git-user
71:END:
72Once the =git= user is created, you will need to copy your public SSH
73key on your local development machine to the =git= user on the server.
74
75If you don't have an SSH key yet, create one with this command:
76
77#+begin_src sh
78ssh-keygen
79#+end_src
80
81Once you create the key pair, the public should be saved to
82=~/.ssh/id_rsa.pub=.
83
84If your server still has password-based authentication available, you
85can copy it over to your user's home directory like this:
86
87#+begin_src sh
88ssh-copy-id git@server
89#+end_src
90
91Otherwise, copy it over to any user that you can access.
92
93#+begin_src sh
94scp ~/.ssh/id_rsa.pub your_user@your_server:
95#+end_src
96
97Once on the server, you will need to copy the contents into the =git=
98user's =authorized_keys= file:
99
100#+begin_src sh
101cat id_rsa.pub > /home/git/.ssh/authorized_keys
102#+end_src
103
104*** (Optional) Disable Password-Based SSH
105:PROPERTIES:
106:CUSTOM_ID: optional-disable-password-based-ssh
107:END:
108If you want to lock down your server and ensure that no one can
109authenticate in via SSH with a password, you will need to edit your SSH
110configuration.
111
112#+begin_src sh
113sudo nano /etc/ssh/sshd_config
114#+end_src
115
116Within this file, find the following settings and set them to the values
117I am showing below:
118
119#+begin_src conf
120PermitRootLogin no
121PasswordAuthentication no
122AuthenticationMethods publickey
123#+end_src
124
125You may have other Authentication Methods required in your personal
126set-up, so the key here is just to ensure that =AuthenticationMethods=
127does not allow passwords.
128
129** Setting up the Base Directory
130:PROPERTIES:
131:CUSTOM_ID: setting-up-the-base-directory
132:END:
133Now that we have set up a =git= user to handle all transport methods, we
134need to set up the directory that we will be using as our base of all
135repositories.
136
137In my case, I am using =/git= as my source folder. To create this folder
138and assign it to the user we created, execute the following commands:
139
140#+begin_src sh
141sudo mkdir /git
142sudo chown -R git:git /git
143#+end_src
144
145** Creating a Test Repository
146:PROPERTIES:
147:CUSTOM_ID: creating-a-test-repository
148:END:
149On your server, switch over to the =git= user in order to start managing
150git files.
151
152#+begin_src sh
153su git
154#+end_src
155
156Once logged-in as the =git= user, go to your base directory and create a
157test repository.
158
159#+begin_src sh
160cd /git
161mkdir test.git && cd test.git
162git init --bare
163#+end_src
164
165If you want to make this repo viewable/cloneable to the public via the
166=git://= protocol, you need to create a =git-daemon-export-ok= file
167inside the repository.
168
169#+begin_src sh
170touch git-daemon-export-ok
171#+end_src
172
173** Change the Login Shell for =git=
174:PROPERTIES:
175:CUSTOM_ID: change-the-login-shell-for-git
176:END:
177To make sure that the =git= user is only used for git operations and
178nothing else, you need to change the user's login shell. To do this,
179simply use the =chsh= command:
180
181#+begin_src sh
182sudo chsh git
183#+end_src
184
185The interactive prompt will ask which shell you want the =git= user to
186use. You must use the following value:
187
188#+begin_src sh
189/usr/bin/git-shell
190#+end_src
191
192Once done, no one will be able to SSH to the =git= user or execute
193commands other than the standard git commands.
194
195** Opening the Firewall
196:PROPERTIES:
197:CUSTOM_ID: opening-the-firewall
198:END:
199Don't forget to open up ports on the device firewall and network
200firewall if you want to access these repositories publicly. If you're
201using default ports, forward ports =22= (ssh) and =9418= (git) from your
202router to your server's IP address.
203
204If your server also has a firewall, ensure that the firewall allows the
205same ports that are forwarded from the router. For example, if you use
206=ufw=:
207
208#+begin_src sh
209sudo ufw allow 22
210sudo ufw allow 9418
211#+end_src
212
213*** Non-Standard SSH Ports
214:PROPERTIES:
215:CUSTOM_ID: non-standard-ssh-ports
216:END:
217If you use a non-standard port for SSH, such as =9876=, you will need to
218create an SSH configuration file on your local development machine in
219order to connect to your server's git repositories.
220
221To do this, you'll need to define your custom port on your client
222machine in your =~/.ssh/config= file:
223
224#+begin_src sh
225nano ~/.ssh/config
226#+end_src
227
228#+begin_src conf
229Host git.example.com
230 # HostName can be a URL or an IP address
231 HostName git.example.com
232 Port 9876
233 User git
234#+end_src
235
236*** Testing SSH
237:PROPERTIES:
238:CUSTOM_ID: testing-ssh
239:END:
240There are two main syntaxes you can use to manage git over SSH:
241
242- =git clone [user@]server:project.git=
243- =git clone ssh://[user@]server/project.git=
244
245I prefer the first, which is an =scp=-like syntax. To test it, try to
246clone the test repository you set up on the server:
247
248#+begin_src sh
249git clone git@git.example.com:/git/test.git
250#+end_src
251
252** Enabling Read-Only Access
253:PROPERTIES:
254:CUSTOM_ID: enabling-read-only-access
255:END:
256If you want people to be able to clone any repository where you've
257placed a =git-daemon-export-ok= file, you will need to start the git
258daemon.
259
260To do this on a system with =systemd=, create a service file:
261
262#+begin_src sh
263sudo nano /etc/systemd/system/git-daemon.service
264#+end_src
265
266Inside the =git-daemon.service= file, paste the following:
267
268#+begin_src conf
269[Unit]
270Description=Start Git Daemon
271
272[Service]
273ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
274
275Restart=always
276RestartSec=500ms
277
278StandardOutput=syslog
279StandardError=syslog
280SyslogIdentifier=git-daemon
281
282User=git
283Group=git
284
285[Install]
286WantedBy=multi-user.target
287#+end_src
288
289Once created, enable and start the service:
290
291#+begin_src sh
292sudo systemctl enable git-daemon.service
293sudo systemctl start git-daemon.service
294#+end_src
295
296To clone read-only via the =git://= protocol, you can use the following
297syntax:
298
299#+begin_src sh
300git clone git://git.example.com/test.git
301#+end_src
302
303** Migrating Repositories
304:PROPERTIES:
305:CUSTOM_ID: migrating-repositories
306:END:
307At this point, we have a working git server that works with both SSH and
308read-only access.
309
310For each of the repositories I had hosted a different provider, I
311executed the following commands in order to place a copy on my server as
312my new source of truth:
313
314Server:
315
316#+begin_src sh
317su git
318mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
319git init --bare
320
321# If you want to make this repo viewable/cloneable to the public
322touch git-daemon-export-ok
323#+end_src
324
325Client:
326
327#+begin_src sh
328git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
329git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
330git push
331#+end_src
332
333** Optional Web View: =cgit=
334:PROPERTIES:
335:CUSTOM_ID: optional-web-view-cgit
336:END:
337If you want a web viewer for your repositories, you can use various
338tools, such as =gitweb=, =cgit=, or =klaus=. I chose =cgit= due to its
339simple interface and fairly easy set-up (compared to others). Not to
340mention that the [[https://git.kernel.org/][Linux kernel uses =cgit=]].
341
342*** Docker Compose
343:PROPERTIES:
344:CUSTOM_ID: docker-compose
345:END:
346Instead of using my previous method of using a =docker run= command,
347I've updated this section to use =docker-compose= instead for an easier
348installation and simpler management and configuration.
349
350In order to use Docker Compose, you will set up a =docker-compose.yml=
351file to automatically connect resources like the repositories, =cgitrc=,
352and various files or folders to the =cgit= container you're creating:
353
354#+begin_src sh
355mkdir ~/cgit && cd ~/cgit
356nano docker-compose.yml
357#+end_src
358
359#+begin_src conf
360# docker-compose.yml
361version: '3'
362
363services:
364 cgit:
365 image: invokr/cgit
366 volumes:
367 - /git:/git
368 - ./cgitrc:/etc/cgitrc
369 - ./logo.png:/var/www/htdocs/cgit/logo.png
370 - ./favicon.png:/var/www/htdocs/cgit/favicon.png
371 - ./filters:/var/www/htdocs/cgit/filters
372 ports:
373 - "8763:80"
374 restart: always
375#+end_src
376
377Then, just start the container:
378
379#+begin_src sh
380sudo docker-compose up -d
381#+end_src
382
383Once it's finished installing, you can access the site at
384=<SERVER_IP>:8763= or use a reverse-proxy service to forward =cgit= to a
385URL, such as =git.example.com=. See the next section for more details on
386reverse proxying a URL to a local port.
387
388*** Nginx Reverse Proxy
389:PROPERTIES:
390:CUSTOM_ID: nginx-reverse-proxy
391:END:
392I am using Nginx as my reverse proxy so that the =cgit= Docker container
393can use =git.example.com= as its URL. To do so, I simply created the
394following configuration file:
395
396#+begin_src sh
397sudo nano /etc/nginx/sites-available/git.example.com
398#+end_src
399
400#+begin_src conf
401server {
402 listen 80;
403 server_name git.example.com;
404
405 if ($host = git.example.com) {
406 return 301 https://$host$request_uri;
407 }
408
409 return 404;
410}
411
412server {
413 server_name git.example.com;
414 listen 443 ssl http2;
415
416 location / {
417 # The final `/` is important.
418 proxy_pass http://localhost:8763/;
419 add_header X-Frame-Options SAMEORIGIN;
420 add_header X-XSS-Protection "1; mode=block";
421 proxy_redirect off;
422 proxy_buffering off;
423 proxy_set_header Host $host;
424 proxy_set_header X-Real-IP $remote_addr;
425 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
426 proxy_set_header X-Forwarded-Proto $scheme;
427 proxy_set_header X-Forwarded-Port $server_port;
428 }
429
430 # INCLUDE ANY SSL CERTS HERE
431 include /etc/letsencrypt/options-ssl-nginx.conf;
432 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
433}
434#+end_src
435
436Once created, symlink it and restart the web server.
437
438#+begin_src sh
439sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
440sudo systemctl restart nginx.service
441#+end_src
442
443As we can see below, my site at =git.example.com= is available and
444running:
445
446*** Settings Up Git Details
447:PROPERTIES:
448:CUSTOM_ID: settings-up-git-details
449:END:
450Once you have =cgit= running, you can add some small details, such as
451repository owners and descriptions by editing the following files within
452each repository.
453
454Alternatively, you can use the =cgitrc= file to edit these details if
455you only care to edit them for the purpose of seeing them on your
456website.
457
458The =description= file within the repository on your server will display
459the description online.
460
461#+begin_src sh
462cd /git/example.git
463nano description
464#+end_src
465
466You can add a =[gitweb]= block to the =config= file in order to display
467the owner of the repository.
468
469#+begin_src sh
470cd /git/example.git
471nano config
472#+end_src
473
474#+begin_src conf
475[gitweb]
476 owner = "YourName"
477#+end_src
478
479Note that you can ignore the configuration within each repository and
480simply set up this information in the =cgitrc= file, if you want to do
481it that way.
482
483*** Editing =cgit=
484:PROPERTIES:
485:CUSTOM_ID: editing-cgit
486:END:
487In order to edit certain items within =cgit=, you need to edit the
488=cgitrc= file.
489
490#+begin_src sh
491nano ~/cgit/cgitrc
492#+end_src
493
494Below is an example configuration for =cgitrc=. You can find all the
495configuration options within the [configuration manual]
496(https://git.zx2c4.com/cgit/plain/cgitrc.5.txt).
497
498#+begin_src conf
499css=/cgit.css
500logo=/logo.png
501favicon=/favicon.png
502robots=noindex, nofollow
503
504enable-index-links=1
505enable-commit-graph=1
506enable-blame=1
507enable-log-filecount=1
508enable-log-linecount=1
509enable-git-config=1
510
511clone-url=git://git.example.com/$CGIT_REPO_URL ssh://git@git.example.com:/git/$CGIT_REPO_URL
512
513root-title=My Git Website
514root-desc=My personal git repositories.
515
516# Allow download of tar.gz, tar.bz2 and zip-files
517snapshots=tar.gz tar.bz2 zip
518
519##
520## List of common mimetypes
521##
522mimetype.gif=image/gif
523mimetype.html=text/html
524mimetype.jpg=image/jpeg
525mimetype.jpeg=image/jpeg
526mimetype.pdf=application/pdf
527mimetype.png=image/png
528mimetype.svg=image/svg+xml
529
530# Highlight source code
531# source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.sh
532source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
533
534# Format markdown, restructuredtext, manpages, text files, and html files
535# through the right converters
536about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
537
538##
539## Search for these files in the root of the default branch of repositories
540## for coming up with the about page:
541##
542readme=:README.md
543readme=:readme.md
544readme=:README.mkd
545readme=:readme.mkd
546readme=:README.rst
547readme=:readme.rst
548readme=:README.html
549readme=:readme.html
550readme=:README.htm
551readme=:readme.htm
552readme=:README.txt
553readme=:readme.txt
554readme=:README
555readme=:readme
556
557# Repositories
558
559# Uncomment the following line to scan a path instead of adding repositories manually
560# scan-path=/git
561
562## Test Section
563section=git/test-section
564
565repo.url=test.git
566repo.path=/git/test.git
567repo.readme=:README.md
568repo.owner=John Doe
569repo.desc=An example repository!
570#+end_src
571
572*** Final Fixes: Syntax Highlighting & README Rendering
573:PROPERTIES:
574:CUSTOM_ID: final-fixes-syntax-highlighting-readme-rendering
575:END:
576After completing my initial install and playing around with it for a few
577days, I noticed two issues:
578
5791. Syntax highlighting did not work when viewing the source code within
580 a file.
5812. The =about= tab within a repository was not rendered to HTML.
582
583The following process fixes these issues. To start, let's go to the
584=cgit= directory where we were editing our configuration file earlier.
585
586#+begin_src sh
587cd ~/cgit
588#+end_src
589
590In here, create two folders that will hold our syntax files:
591
592#+begin_src sh
593mkdir filters && mkdir filters/html-converters && cd filters
594#+end_src
595
596Next, download the default filters:
597
598#+begin_src sh
599curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
600chmod 755 about-formatting.sh
601curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
602chmod 755 syntax-highlighting.py
603#+end_src
604
605Finally, download the HTML conversion files you need. The example below
606downloads the Markdown converter:
607
608#+begin_src sh
609cd html-converters
610curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
611chmod 755 md2html
612#+end_src
613
614If you need other filters or html-converters found within
615[[https://git.zx2c4.com/cgit/tree/filters][the cgit project files]],
616repeat the =curl= and =chmod= process above for whichever files you
617need.
618
619However, formatting will not work quite yet since the Docker cgit
620container we're using doesn't have the formatting package installed. You
621can install this easily by install Python 3+ and the =pygments= package:
622
623#+begin_src sh
624# Enter the container's command line
625sudo docker exec -it cgit bash
626#+end_src
627
628#+begin_src sh
629# Install the necessary packages and then exit
630yum update -y && \
631yum upgrade -y && \
632yum install python3 python3-pip -y && \
633pip3 install markdown pygments && \
634exit
635#+end_src
636
637*You will need to enter the cgit docker container and re-run these =yum=
638commands every time you kill and restart the container!*
639
640If not done already, we need to add the following variables to our
641=cgitrc= file in order for =cgit= to know where our filtering files are:
642
643#+begin_src conf
644# Highlight source code with python pygments-based highlighter
645source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
646
647# Format markdown, restructuredtext, manpages, text files, and html files
648# through the right converters
649about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
650#+end_src
651
652Now you should see that syntax highlighting and README rendering to the
653=about= tab is fixed.
654
655*** Theming
656:PROPERTIES:
657:CUSTOM_ID: theming
658:END:
659I won't go into much detail in this section, but you can fully theme
660your installation of =cgit= since you have access to the =cgit.css= file
661in your web root. This is another file you can add as a volume to the
662=docker-compose.yml= file if you want to edit this without entering the
663container's command line.
664
665** :warning: Remember to Back Up Your Data!
666:PROPERTIES:
667:CUSTOM_ID: warning-remember-to-back-up-your-data
668:END:
669The last thing to note is that running services on your own equipment
670means that you're assuming a level of risk that exists regarding data
671loss, catastrophes, etc. In order to reduce the impact of any such
672occurrence, I suggest backing up your data regularly.
673
674Backups can be automated via =cron=, by hooking your base directory up
675to a cloud provider, or even setting up hooks to push all repository
676info to git mirrors on other git hosts. Whatever the method, make sure
677that your data doesn't vanish in the event that your drives or servers
678fail.
blog/2022-07-25-curseradio.org deleted −114
@@ -1,114 +0,0 @@
1#+title: CurseRadio: Listening to the Radio on the Command Line
2#+date: 2022-07-25
3
4** Overview
5:PROPERTIES:
6:CUSTOM_ID: overview
7:END:
8While exploring some interesting Linux applications, I stumbled across
9[[https://github.com/chronitis/curseradio][curseradio]], a command-line
10radio player based on Python.
11
12This application is fantastic and incredibly easy to install, so I
13wanted to dedicate a post today to this app. Let's look at the features
14within the app and then walk through the installation process I took to
15get =curseradio= working.
16
17** Features
18:PROPERTIES:
19:CUSTOM_ID: features
20:END:
21#+caption: curseradio
22[[https://img.cleberg.net/blog/20220725-curseradio/curseradio.png]]
23
24The radio player itself is quite minimal. As you can see in the
25screenshot above, it contains a simple plaintext list of all available
26categories, which can be broken down further and further. In addition,
27radio shows are available for listening, alongside regular radio
28stations.
29
30For example, the =Sports= > =Pro Basketball= > =Shows= category contains
31a number of specific shows related to 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 show up at the
35top of the radio player in the =Favourites= category.
36
37*** Commands/Shortcuts
38:PROPERTIES:
39:CUSTOM_ID: commandsshortcuts
40:END:
41| Key(s) | Command |
42|------------+---------------------------------|
43| ↑, ↓ | navigate |
44| PgUp, PgDn | navigate quickly |
45| Home, End | to top/bottom |
46| Enter | open/close folders, play stream |
47| k | stop playing stream |
48| q | quit |
49| f | toggle favourite |
50
51** Installation
52:PROPERTIES:
53:CUSTOM_ID: installation
54:END:
55*** Dependencies
56:PROPERTIES:
57:CUSTOM_ID: dependencies
58:END:
59Before installing =curseradio=, a handful of system and Python packages
60are required. To get started, install =python3=, =pip3=, and =mpv= on
61your system. In this example, I'm using Fedora Linux, which uses the
62=dnf= package manager. You may need to adjust this if you're using a
63different system.
64
65#+begin_src sh
66sudo dnf install python3 pip3 mpv
67#+end_src
68
69Next, use =pip3= to install =requests=, =xdg=, and =lxml=:
70
71#+begin_src sh
72pip3 install requests xdg lxml
73#+end_src
74
75*** Repository Source Installation
76:PROPERTIES:
77:CUSTOM_ID: repository-source-installation
78:END:
79Once all the dependencies are installed, we can clone the source code
80and enter that directory:
81
82#+begin_src sh
83git clone https://github.com/chronitis/curseradio && cd curseradio
84#+end_src
85
86Once you're within the =curseradio= directory, you can install the
87application with the provided =setup.py= script.
88
89#+begin_src sh
90sudo python3 setup.py install
91#+end_src
92
93In my case, I ran into a few errors and needed to create the folders
94that curseradio wanted to use for its installation. If you don't get any
95errors, you can skip this and run the app.
96
97#+begin_src sh
98sudo mkdir /usr/local/lib/python3.10/
99sudo mkdir /usr/local/lib/python3.10/site-packages/
100#+end_src
101
102#+begin_src sh
103sudo chown -R $USER:$USER /usr/local/lib/python3.10/
104#+end_src
105
106** Run the Application
107:PROPERTIES:
108:CUSTOM_ID: run-the-application
109:END:
110Once fully installed without errors, you can run the application!
111
112#+begin_src sh
113python3 /usr/local/bin/curseradio
114#+end_src
blog/2022-09-21-graphene-os.org deleted −186
@@ -1,186 +0,0 @@
1#+title: Installing Graphene OS on the Pixel 6 Pro
2#+date: 2022-09-21
3
4** Introduction
5:PROPERTIES:
6:CUSTOM_ID: introduction
7:END:
8After using iOS for a couple of years, I finally took the plunge and
9purchased a Pixel 6 Pro in order to test and use [GrapheneOS]
10(https://grapheneos.org).
11
12The installation process was rather quick once you have the tools and
13files you need. Overall, it can be done in just a few minutes.
14
15** Gathering Tools & Files
16:PROPERTIES:
17:CUSTOM_ID: gathering-tools-files
18:END:
19*** Android Tools
20:PROPERTIES:
21:CUSTOM_ID: android-tools
22:END:
23First, in order to interact with the device, we will need the
24[[https://developer.android.com/studio/releases/platform-tools.html][Android
25platform tools]]. Find the Linux download and save the ZIP folder to
26your preferred location.
27
28Once we've downloaded the files, we will need to unzip them, enter the
29directory, and move the necessary executables to a central location,
30such as =/usr/bin/=. For this installation, we only need the =fastboot=
31and =adb= executables.
32
33#+begin_src sh
34cd ~/Downloads
35#+end_src
36
37#+begin_src sh
38unzip platform-tools_r33.0.3-linux.zip
39cd platform-tools
40sudo mv fastboot /usr/bin/
41sudo mv adb /usr/bin
42#+end_src
43
44*** GrapheneOS Files
45:PROPERTIES:
46:CUSTOM_ID: grapheneos-files
47:END:
48Next, we need the [[https://grapheneos.org/releases][GrapheneOS files]]
49for our device and model. For example, the Pixel 6 Pro is codenamed
50=raven= on the release page.
51
52Once we have the links, let's download them to our working directory:
53
54#+begin_src sh
55curl -O https://releases.grapheneos.org/factory.pub
56curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
57curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
58#+end_src
59
60**** Validate Integrity
61:PROPERTIES:
62:CUSTOM_ID: validate-integrity
63:END:
64In order to validate the integrity of the downloaded files, we will need
65the =signify= package and Graphene's =factory.pub= file.
66
67#+begin_src sh
68sudo dnf install signify
69#+end_src
70
71#+begin_src sh
72curl -O https://releases.grapheneos.org/factory.pub
73#+end_src
74
75Then we can validate the files and ensure that no data was corrupted or
76modified before it was saved to our device.
77
78#+begin_src sh
79signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
80#+end_src
81
82**** Unzip Files
83:PROPERTIES:
84:CUSTOM_ID: unzip-files
85:END:
86Once the files are verified, we can unzip the Graphene image and enter
87the directory:
88
89#+begin_src sh
90unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
91#+end_src
92
93** Installation Process
94:PROPERTIES:
95:CUSTOM_ID: installation-process
96:END:
97*** Enable Developer Debugging & OEM Unlock
98:PROPERTIES:
99:CUSTOM_ID: enable-developer-debugging-oem-unlock
100:END:
101Before we can actually flash anything to the phone, we will need to
102enable OEM Unlocking, as well as either USB Debugging or Wireless
103Debugging, depending on which method we will be using.
104
105To start, enable developer mode by going to =Settings= > =About= and
106tapping =Build Number= seven (7) times. You may need to enter your PIN
107to enable this mode.
108
109Once developer mode is enabled, go to =Settings= > =System= >
110=Devloper Options= and enable OEM Unlocking, as well as USB or Wireless
111Debugging. In my case, I chose USB Debugging and performed all actions
112via USB cable.
113
114Once these options are enabled, plug the phone into the computer and
115execute the following command:
116
117#+begin_src sh
118adb devices
119#+end_src
120
121If an unauthorized error occurs, make sure the USB mode on the phone is
122changed from charging to something like "File Transfer" or "PTP." You
123can find the USB mode in the notification tray.
124
125*** Reboot Device
126:PROPERTIES:
127:CUSTOM_ID: reboot-device
128:END:
129Once we have found the device via =adb=, we can either boot into the
130bootloader interface by holding the volume down button while the phone
131reboots or by executing the following command:
132
133#+begin_src sh
134adb reboot bootloader
135#+end_src
136
137*** Unlock the Bootloader
138:PROPERTIES:
139:CUSTOM_ID: unlock-the-bootloader
140:END:
141The phone will reboot and load the bootloader screen upon startup. At
142this point, we are ready to start the actual flashing of GrapheneOS onto
143the device.
144
145*NOTE*: In my situation, I needed to use =sudo= with every =fastboot=
146command, but not with =adb= commands. I am not sure if this is standard
147or a Fedora quirk, but I'm documenting my commands verbatim in this
148post.
149
150First, we start by unlocking the bootloader so that we can load other
151ROMs:
152
153#+begin_src sh
154sudo fastboot flashing unlock
155#+end_src
156
157*** Flashing Factory Images
158:PROPERTIES:
159:CUSTOM_ID: flashing-factory-images
160:END:
161Once the phone is unlocked, we can flash it with the =flash-all.sh=
162script found inside the =raven-factory-2022091400= folder we entered
163earlier:
164
165#+begin_src sh
166sudo ./flash-all.sh
167#+end_src
168
169This process should take a few minutes and will print informational
170messages as things progress. Avoid doing anything on the phone while
171this process is operating.
172
173*** Lock the Bootloader
174:PROPERTIES:
175:CUSTOM_ID: lock-the-bootloader
176:END:
177If everything was successful, the phone should reboot a few times and
178finally land back on the bootloader screen. At this point, we can
179re-lock the bootloader to enable full verified boot and protect the
180device from unwanted flashing or erasure of data.
181
182#+begin_src sh
183sudo fastboot flashing lock
184#+end_src
185
186Once done, the device will be wiped and ready for a fresh set-up!
blog/2022-10-22-alpine-linux.org deleted −301
@@ -1,301 +0,0 @@
1#+title: Alpine Linux: My New Server OS
2#+date: 2022-10-22
3
4** Alpine Linux
5:PROPERTIES:
6:CUSTOM_ID: alpine-linux
7:END:
8[[https://alpinelinux.org][Alpine Linux]] is a very small distro, built
9on musl libc and busybox. It uses ash as the default shell, OpenRC as
10the init system, and apk as the package manager. According to their
11website, an Alpine container "requires no more than 8 MB and a minimal
12installation to disk requires around 130 MB of storage." An actual bare
13metal machine is recommended to have 100 MB of RAM and 0-700 MB of
14storage space.
15
16Historically, I've used Ubuntu's minimal installation image as my server
17OS for the last five years. Ubuntu worked well and helped as my original
18server contained an nVidia GPU and no onboard graphics, so quite a few
19distros won't boot or install without a lot of tinkering.
20
21Alpine has given me a huge increase in performance across my Docker apps
22and Nginx websites. CPU load for the new server I'm using to test Alpine
23hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @
243.70GHz.
25
26The only services I haven't moved over to Alpine are Plex Media Server
27and Syncthing, which may increase CPU load quite a bit depending on how
28many streams are running.
29
30*** Installation
31:PROPERTIES:
32:CUSTOM_ID: installation
33:END:
34In terms of installation, Alpine has an incredibly useful
35[[https://wiki.alpinelinux.org/wiki/Installation][wiki]] that will guide
36a user throughout the installation and post-installation processes, as
37well as various other articles and guides.
38
39To install Alpine, find an appropriate
40[[https://alpinelinux.org/downloads/][image to download]] and flash it
41to a USB using software such as Rufus or Etcher. I opted to use the
42Standard image 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= to access the boot
46menu. The Alpine Linux terminal will load quickly and for a login.
47
48To log in to the installation image, use the =root= account; there is no
49password. Once logged-in, execute the setup command:
50
51#+begin_src sh
52setup-alpine
53#+end_src
54
55The setup script will ask a series of questions to configure the system.
56Be sure to answer carefully or else you may have to re-configure the
57system after boot.
58
59- Keyboard Layout (Local keyboard language and usage mode, e.g., us and
60 variant of us-nodeadkeys.)
61- Hostname (The name for the computer.)
62- Network (For example, automatic IP address discovery with the "DHCP"
63 protocol.)
64- DNS Servers (Domain Name Servers to query. For privacy reasons, it is
65 NOT recommended to route every local request to servers like Google's
66 8.8.8.8 .)
67- Timezone
68- Proxy (Proxy server to use for accessing the web. Use "none" for
69 direct connections to the internet.)
70- Mirror (From where to download packages. Choose the organization you
71 trust giving your usage patterns to.)
72- SSH (Secure SHell remote access server. "Openssh" is part of the
73 default install image. Use "none" to disable remote login, e.g. on
74 laptops.)
75- NTP (Network Time Protocol client used for keeping the system clock in
76 sync with a time-server. Package "chrony" is part of the default
77 install image.)
78- Disk Mode (Select between diskless (disk="none"), "data" or "sys", as
79 described above.)
80
81Once the setup script is finished, be sure to reboot the machine and
82remove the USB device.
83
84#+begin_src sh
85reboot
86#+end_src
87
88*** Post-Installation
89:PROPERTIES:
90:CUSTOM_ID: post-installation
91:END:
92There are many things you can do once your Alpine Linux system is up and
93running, and it largely depends on what you'll use the machine for. I'm
94going to walk through my personal post-installation setup for my web
95server.
96
97**** Upgrade the System
98:PROPERTIES:
99:CUSTOM_ID: upgrade-the-system
100:END:
101First, login as =root= in order to update and upgrade the system:
102
103#+begin_src sh
104apk -U upgrade
105#+end_src
106
107**** Adding a User
108:PROPERTIES:
109:CUSTOM_ID: adding-a-user
110:END:
111I needed to add a user so that I don't need to log in as root. Note that
112if you're used to using the =sudo= command, you will now need to use the
113=doas= command on Alpine Linux.
114
115#+begin_src sh
116apk add doas
117adduser <username>
118adduser <username> wheel
119#+end_src
120
121You can now log out and log back in using the newly-created user:
122
123#+begin_src sh
124exit
125#+end_src
126
127**** Enable Community Packages
128:PROPERTIES:
129:CUSTOM_ID: enable-community-packages
130:END:
131In order to install more common packages that aren't found in the =main=
132repository, you will need to enable the =community= repository:
133
134#+begin_src sh
135doas nano /etc/apk/repositories
136#+end_src
137
138Uncomment the community line for whichever version of Alpine you're
139running:
140
141#+begin_src sh
142/media/usb/apks
143http://dl-cdn.alpinelinux.org/alpine/v3.16/main
144http://dl-cdn.alpinelinux.org/alpine/v3.16/community
145#http://dl-cdn.alpinelinux.org/alpine/edge/main
146#http://dl-cdn.alpinelinux.org/alpine/edge/community
147#http://dl-cdn.alpinelinux.org/alpine/edge/testing
148#+end_src
149
150**** Install Required Packages
151:PROPERTIES:
152:CUSTOM_ID: install-required-packages
153:END:
154Now that the community packages are available, you can install any
155packages you need. In my case, I installed the web server packages I
156need for my services:
157
158#+begin_src sh
159doas apk add nano nginx docker docker-compose ufw
160#+end_src
161
162**** SSH
163:PROPERTIES:
164:CUSTOM_ID: ssh
165:END:
166If you didn't install OpenSSH as part of the installation, you can do so
167now:
168
169#+begin_src sh
170doas apk add openssh
171#+end_src
172
173Next, either create a new key or copy your SSH key to the server from
174your current machines:
175
176#+begin_src sh
177# Create a new key
178ssh-keygen
179#+end_src
180
181If you need to copy an existing SSH key from a current machine:
182
183#+begin_src sh
184# Copy key from existing machines
185ssh-copy-id <username>@<ip_address>
186#+end_src
187
188**** Firewall
189:PROPERTIES:
190:CUSTOM_ID: firewall
191:END:
192Lastly, I installed =ufw= above as my firewall. To set up, default to
193deny incoming and allow outgoing connections. Then selectively allow
194other ports or apps as needed.
195
196#+begin_src sh
197doas ufw default deny incoming
198doas ufw default allow outgoing
199doas ufw allow SSH
200doas ufw allow "WWW Full"
201doas ufw allow 9418 # Git server port
202#+end_src
203
204**** Change Hostname
205:PROPERTIES:
206:CUSTOM_ID: change-hostname
207:END:
208If you don't like the hostname set during installation, you just need to
209edit two files. First, edit the simple hostname file:
210
211#+begin_src sh
212doas nano /etc/hostname
213#+end_src
214
215#+begin_src sh
216<hostname>
217#+end_src
218
219Next, edit the =hosts= file:
220
221#+begin_src sh
222doas nano /etc/hosts
223#+end_src
224
225#+begin_src sh
226127.0.0.1 <hostname>.local <hostname> localhost.local localhost
227::1 <hostname> <hostname>.local
228#+end_src
229
230** Nginx Web Server
231:PROPERTIES:
232:CUSTOM_ID: nginx-web-server
233:END:
234To set up my web server, I simply created the =www= user and created the
235necessary files.
236
237#+begin_src sh
238doas adduser -D -g 'www' www
239mkdir /www
240doas mkdir /www
241doas chown -R www:www /var/lib/nginx/
242doas chown -R www:www /www
243#+end_src
244
245If you're running a simple webroot, you can alter the main =nginx.conf=
246file. Otherwise, you can drop configuration files in the following
247directory. You don't need to enable or symlink the configuration file
248like you do in other systems.
249
250#+begin_src sh
251doas nano /etc/nginx/http.d/example_website.conf
252#+end_src
253
254Once the configuration is set and pointed at the =/www= directory to
255serve files, enable the Nginx service:
256
257#+begin_src sh
258# Note that 'default' must be included or Nginx will not start on boot
259doas rc-update add nginx default
260#+end_src
261
262** Docker Containers
263:PROPERTIES:
264:CUSTOM_ID: docker-containers
265:END:
266Docker works exactly the same as other systems. Either execute a
267=docker run= command or create a =docker-compose.yml= file and do
268=docker-compose up -d=.
269
270** Git Server
271:PROPERTIES:
272:CUSTOM_ID: git-server
273:END:
274I went in-depth on how to self-host a git server in another post:
275[[/blog/git-server/][Self-Hosting a Personal Git Server]].
276
277However, there are a few differences with Alpine. First note that in
278order to change the =git= user's shell, you must do a few things a
279little different:
280
281#+begin_src sh
282doas apk add libuser
283doas touch /etc/login.defs
284doas mkdir /etc/default
285doas touch /etc/default/useradd
286doas lchsh git
287#+end_src
288
289** Thoughts on Alpine
290:PROPERTIES:
291:CUSTOM_ID: thoughts-on-alpine
292:END:
293So far, I love Alpine Linux. I have no complaints about anything at this
294point, but I'm not completely finished with the migration yet. Once I'm
295able to upgrade my hardware to a rack-mounted server, I will migrate
296Plex and Syncthing over to Alpine as well - possibly putting Plex into a
297container or VM.
298
299The performance is stellar, the =apk= package manager is seamless, and
300system administration tasks are effortless. My only regret is that I
301didn't install Alpine sooner.
blog/2022-10-30-linux-display-manager.org deleted −85
@@ -1,85 +0,0 @@
1#+title: How to Disable or Change the Display Manager on Void Linux
2#+date: 2022-10-30
3
4** Display Manager Services
5:PROPERTIES:
6:CUSTOM_ID: display-manager-services
7:END:
8In order to change the
9[[https://en.wikipedia.org/wiki/Display_manager][display manager]] on
10Void Linux - or any other Linux distro - you need to identify the
11currently enabled display manager.
12
13*** Disabling the Current Display Manager
14:PROPERTIES:
15:CUSTOM_ID: disabling-the-current-display-manager
16:END:
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 =lxdm=. If
20you installed another display manager, replace =lxdm= in the following
21command with the display manager you have installed.
22
23To disable =lxdm=, simply remove the service symlink:
24
25#+begin_src sh
26sudo rm /var/service/lxdm
27#+end_src
28
29*** Enabling a New Display Manager
30:PROPERTIES:
31:CUSTOM_ID: enabling-a-new-display-manager
32:END:
33If you want to enable a new display manager, you can do so after =lxdm=
34is disabled. Make sure to replace =<new_display_manager>= with your new
35DM, such as =gdm=, =xdm=, etc.
36
37#+begin_src sh
38sudo ln -s /etc/sv/<new_display_manager> /var/service
39#+end_src
40
41** Set Up =.xinitrc=
42:PROPERTIES:
43:CUSTOM_ID: set-up-.xinitrc
44:END:
45Depending on your setup, you may need to create a few X files, such as
46=~/.xinitrc=. For my personal set-up, I created this file to launch the
47i3wm as my desktop.
48
49#+begin_src sh
50nano ~/.xinitrc
51#+end_src
52
53#+begin_src sh
54#!/bin/sh
55
56exec i3
57#+end_src
58
59If you run a desktop other than i3, simply replace =i3= with the shell
60command that launches that desktop.
61
62** Set Up Your Shell Profile
63:PROPERTIES:
64:CUSTOM_ID: set-up-your-shell-profile
65:END:
66Finally, in order to automatically launch an X session upon login, you
67will need to edit the =.bash_profile= (bash) or =.zprofile= (zsh) files
68for your shell:
69
70#+begin_src sh
71nano ~/.zprofile
72#+end_src
73
74Add the following snippet to the end of the shell profile file. This
75will execute the =startx= command upon login.
76
77#+begin_src sh
78if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
79 exec startx
80fi
81#+end_src
82
83Alternatively, you can ignore this step and simply choose to manually
84execute =startx= upon login. This can be useful if you have issues with
85your desktop or like to manually launch different desktops by choice.
blog/2022-12-01-nginx-compression.org deleted −83
@@ -1,83 +0,0 @@
1#+title: Enable GZIP Compression on Nginx
2#+date: 2022-12-01
3
4** Text Compression
5:PROPERTIES:
6:CUSTOM_ID: text-compression
7:ID: 24C2F6E7-129E-42F3-91F0-A6C6C519FA01
8:END:
9Text compression allows a web server to serve text-based resources
10faster than uncompressed data. This can speed up things like First
11Contentful Paint, Tie to Interactive, and Speed Index.
12
13** Enable Nginx Compression with gzip
14:PROPERTIES:
15:CUSTOM_ID: enable-nginx-compression-with-gzip
16:ID: C018C991-E253-4779-A702-AEB69614499F
17:END:
18In order to enable text compression on Nginx, we need to enable it
19within the configuration file:
20
21#+begin_src sh
22nano /etc/nginx/nginx.conf
23#+end_src
24
25Within the =http= block, find the section that shows something like the
26block below. This is the default gzip configuration I found in my
27=nginx.conf= file on Alpine Linux 3.17. Yours may look slightly
28different, just make sure that you're not creating any duplicate gzip
29options.
30
31#+begin_src conf
32# Enable gzipping of responses.
33#gzip on;
34
35# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
36gzip_vary on;
37#+end_src
38
39Remove the default gzip lines and replace them with the following:
40
41#+begin_src conf
42# Enable gzipping of responses.
43gzip on;
44gzip_vary on;
45gzip_min_length 10240;
46gzip_proxied expired no-cache no-store private auth;
47gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
48gzip_disable "MSIE [1-6]\.";
49#+end_src
50
51** Explanations of ngx_http_gzip_module Options
52:PROPERTIES:
53:CUSTOM_ID: explanations-of-ngx_http_gzip_module-options
54:ID: C180752B-E1B9-49A5-B180-84488068A76A
55:END:
56Each of the lines above enables a different aspect of the gzip response
57for Nginx. Here are the full explanations:
58
59- =gzip= -- Enables or disables gzipping of responses.
60- =gzip_vary= -- Enables or disables inserting the "Vary:
61 Accept-Encoding" response header field if the directives gzip,
62 gzip_static, or gunzip are active.
63- =gzip_min_length= -- Sets the minimum length of a response that will
64 be gzipped. The length is determined only from the "Content-Length"
65 response header field.
66- =gzip_proxied= -- Enables or disables gzipping of responses for
67 proxied requests depending on the request and response. The fact that
68 the request is proxied is determined by the presence of the "Via"
69 request header field.
70- =gzip_types= -- Enables gzipping of responses for the specified MIME
71 types in addition to "text/html". The special value “*” matches any
72 MIME type (0.8.29). Responses with the "text/html" type are always
73 compressed.
74- =gzip_disable= -- Disables gzipping of responses for requests with
75 "User-Agent" header fields matching any of the specified regular
76 expressions.
77 - The special mask "msie6" (0.7.12) corresponds to the regular
78 expression "MSIE [4-6].", but works faster. Starting from version
79 0.8.11, "MSIE 6.0; ... SV1" is excluded from this mask.
80
81More information on these directives and their options can be found on
82the [[https://nginx.org/en/docs/http/ngx_http_gzip_module.html][Module
83ngx_http_gzip_module]] page in Nginx's documentation.
blog/2022-12-17-st.org deleted −100
@@ -1,100 +0,0 @@
1#+title: Simple Terminal
2#+date: 2022-12-17
3
4** st
5:PROPERTIES:
6:CUSTOM_ID: st
7:END:
8[[https://st.suckless.org][st]] standards for Simple Terminal, a simple
9terminal implementation for X made by the
10[[https://suckless.org][suckless]] team.
11
12This post walks through the dependencies needed and process to build and
13install =st= on Fedora Workstation.
14
15*** Obtain Files
16:PROPERTIES:
17:CUSTOM_ID: obtain-files
18:END:
19To start, obtain the source files for =st= via =git clone=.
20
21#+begin_src sh
22mkdir ~/suckless && cd ~/suckless
23git clone https://git.suckless.org/st && cd st
24#+end_src
25
26*** Dependencies
27:PROPERTIES:
28:CUSTOM_ID: dependencies
29:END:
30Once you have the files and are in the =st= directory, ensure the
31following packages are installed.
32
33#+begin_src sh
34sudo dnf update && sudo dnf upgrade
35sudo dnf install gcc patch libX11-devel libXft-devel
36#+end_src
37
38*** Building
39:PROPERTIES:
40:CUSTOM_ID: building
41:END:
42Before building, ensure that you read the README file.
43
44#+begin_src sh
45cat README
46#+end_src
47
48Once you've read the instructions, open the =config.mk= file and ensure
49it matches your setup. If you're not sure, leave the default options
50within the file.
51
52Finally, you can build =st= with the following command. Ensure you run
53as root (e.g., =sudo=) or else you may not end up with a usable
54application file.
55
56#+begin_src sh
57sudo make clean install
58#+end_src
59
60*** Customization (Patches)
61:PROPERTIES:
62:CUSTOM_ID: customization-patches
63:END:
64Note that customizing =st= requires you to modify the source files or to
65download one of the [[https://st.suckless.org/patches/][available
66patches]] for suckless.org.
67
68If you've already installed =st= and want to customize or install a
69patch, start by uninstalling the current program.
70
71#+begin_src sh
72cd ~/suckless/st
73sudo make uninstall
74#+end_src
75
76Next, grab the =<path>.diff= file from the page of the patch you chose.
77For example, I will be using the
78[[https://st.suckless.org/patches/defaultfontsize/][defaultfontsize]]
79patch in the below example.
80
81#+begin_src sh
82wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
83#+end_src
84
85Once the file is downloaded inside the =st= folder, apply the patch and
86re-install the program. You may need to install the =patch= command if
87you don't have it installed already (you should have installed it
88above).
89
90#+begin_src sh
91patch -i st-defaultfontsize-20210225-4ef0cbd.diff
92sudo make clean install
93#+end_src
94
95Once installed, you can use the default font size patch to launch =st=
96with any font size you wish:
97
98#+begin_src sh
99st -z 16
100#+end_src
blog/2023-02-02-exploring-hare.org deleted −189
@@ -1,189 +0,0 @@
1#+title: Exploring the Hare Programming Language
2#+date: 2023-02-02
3
4** A Quick Note
5:PROPERTIES:
6:CUSTOM_ID: a-quick-note
7:END:
8By no means am I a professional developer, so this post will be rather
9short. I won't be going into depth on the specification or anything that
10technical.
11
12Instead, I will simply be talking about how I (a relatively basic
13hobbyist programmer) have been playing with Hare and what intrigues me
14about the language.
15
16** Hare
17:PROPERTIES:
18:CUSTOM_ID: hare
19:END:
20The [[https://harelang.org][Hare]] 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#+begin_quote
28Hare is a systems programming language designed to be simple, stable,
29and robust. Hare uses a static type system, manual memory management,
30and minimal runtime. It is well-suited to writing operating systems,
31system tools, compilers, networking software, and other low-level, high
32performance tasks.
33
34#+end_quote
35
36I have found this all to be true while playing with it for the first
37time today. In the next few sections, I'm going to walk through my
38installation and first program.
39
40*** Installation
41:PROPERTIES:
42:CUSTOM_ID: installation
43:END:
44I'm currently running Alpine Linux on my Thinkpad, so the installation
45was quite easy as there is a package for Hare in the =apk= repositories.
46
47#+begin_src sh
48doas apk add hare hare-doc
49#+end_src
50
51However, I was able to install Hare from scratch on Fedora Linux a short
52while ago, which was also very easy to do. If you need further
53instructions and Hare doesn't have a package on your system, take a look
54at the [[https://harelang.org/installation/][Hare Installation]] page.
55
56*** Creating a Test Project
57:PROPERTIES:
58:CUSTOM_ID: creating-a-test-project
59:END:
60In order to play with the language, I created
61[[https://git.sr.ht/~cmc/hare-projects][hare-test]] and will be putting
62any of my Hare-related adventures in here.
63
64#+begin_quote
65*Update:* I also created a simple Hare program for creating a file from
66user input:
67[[https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha][files.ha]]
68
69#+end_quote
70
71Luckily, Hare doesn't require any complex set-up tools or build
72environment. Once you have Hare installed, you simply need to create a
73file ending with =.ha= and you can run a Hare program.
74
75I created a file called =rgb.ha= in order to test out the random number
76generation and passing parameters between functions.
77
78#+begin_src sh
79nano rgb.ha
80#+end_src
81
82Within this file, I was able to easily import a few of the
83[[https://harelang.org/tutorials/stdlib/][standard library modules]]:
84=fmt=, =math::random=, and =datetime=.
85
86With these modules, I created two functions:
87
881. =main=: This function calls the =generate_rgb= function and then
89 prints out the returned values.
902. =generate_rgb=: This function uses the current Unix epoch time to
91 generate a pseudo-random value and uses this value to create three
92 more random values between 0 and 255. These three numbers represent a
93 color in RGB format.
94
95#+begin_quote
96*Note*: Some syntax coloring may look odd, as Zola currently doesn't
97have a syntax highlighting theme for Hare. Instead, I'm using the C
98theme, which may not be exactly accurate when coloring the code below.
99
100#+end_quote
101
102#+begin_src C
103use datetime;
104use fmt;
105use math::random;
106
107export fn main() void = {
108 const rgb = generate_rgb();
109 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
110};
111
112fn generate_rgb() []u64 = {
113 // Use the current Unix epoch time as the seed value
114 let datetime = datetime::epochunix(&datetime::now());
115
116 // Generate initial pseudo-random value
117 // You must cast the datetime from int to u64
118 let x = random::init(datetime: u64);
119
120 // Generate RGB values between (0, 255) using pseudo-random init value
121 let r = random::u64n(&x, 255);
122 let g = random::u64n(&x, 255);
123 let b = random::u64n(&x, 255);
124
125 // Structure data as array and return
126 let rgb_array: [3]u64 = [r, g, b];
127 return rgb_array;
128};
129#+end_src
130
131*** Running a Program
132:PROPERTIES:
133:CUSTOM_ID: running-a-program
134:END:
135Once you have a Hare file written and ready to run, you simply need to
136run it:
137
138#+begin_src sh
139hare run file.ha
140#+end_src
141
142You can also compile the program into an executable:
143
144#+begin_src sh
145hare build -o example file.ha
146./example
147#+end_src
148
149*** Initial Thoughts
150:PROPERTIES:
151:CUSTOM_ID: initial-thoughts
152:END:
153**** Documentation Improvements Would Help
154:PROPERTIES:
155:CUSTOM_ID: documentation-improvements-would-help
156:END:
157While I was able to piece everything together eventually, the biggest
158downfall right now in Hare's documentation. For such a new project, the
159documentation is in a great spot. However, bare specifications don't
160help as much as a brief examples section would.
161
162For example, it took me a while to figure out what the =u64n= function
163was looking for. I could tell that it took two parameters and the second
164was my max value (255), but couldn't figure out what the first value
165should be. Eventually, I inspected the =random.ha= file in the
166[[https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha][Hare
167source code]] and found the test suite that helped me discover that it
168needed an =init()= value in the form of =&var=.
169
170**** More Basic Modules
171:PROPERTIES:
172:CUSTOM_ID: more-basic-modules
173:END:
174This is another point that comes from Hare being new and awaiting more
175contributions, but there are some basic functions that I would
176personally enjoy seeing in Hare, such as one to convert decimal
177(base 10) values to hexadecimal (base 16).
178
179If I'm feeling comfortable with my math, I may work on the list of
180functions I want and see if any can make it into the Hare source code.
181
182**** Overall Thoughts
183:PROPERTIES:
184:CUSTOM_ID: overall-thoughts
185:END:
186Overall, I actually really enjoy Hare. It's not as tedious to get a
187project up and running as Rust, but it's also simpler and more
188user-friendly than learning C. I am going to continue playing with it
189and see if I can make anything of particular value.
blog/2023-06-18-unifi-ip-blocklist.org deleted −89
@@ -1,89 +0,0 @@
1#+title: Block IP Addresses and Subnets with the Unifi Network Firewall
2#+date: 2023-06-18
3
4** Identifying Abusive IPs
5:PROPERTIES:
6:CUSTOM_ID: identifying-abusive-ips
7:END:
8If you're like me and use Unifi network equipment at the edge of the
9network you manage, you may know that Unifi is only somewhat decent at
10identifying and blocking IPs that represent abusive or threat actors.
11
12While Unifi has a
13[[https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management][threat
14management]] tool inside their Network application, it can be lacking in
15functionality and identification. For example, I have my UDM Pro set to
16identify and block almost all categories of threats available within the
17Unifi settings. However, I regularly identify abusive actors on my web
18server via the server logs.
19
20In addition, I have identified IP addresses and subnets directly within
21Unifi's logs that the UDM did not block for whatever reason.
22
23This guide is meant to be another step in the process to manually block
24abusive IP addresses or subnets that you have identified but are not
25being automatically blocked yet.
26
27** Create an IP Group Profile
28:PROPERTIES:
29:CUSTOM_ID: create-an-ip-group-profile
30:END:
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 =Create New=.
35
36#+caption: Network Profiles
37[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png]]
38
39Each IP Group profile can be used as one of three options:
40
411. Port Group
422. IPv4 Address/Subnet
433. IPv6 Address/Subnet
44
45In this example, I'm creating an IPv4 Address/Subnet group and adding a
46few different IP addresses and a subnet. Once you've added all IP
47addresses and subnets, click the =Apply= button that should appear at
48the bottom.
49
50#+caption: Network Profile IPs
51[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png]]
52
53At this point, the IPv4 Address/Subnet has been created but not yet
54used.
55
56** Drop IP Group Profile via the Unifi Firewall
57:PROPERTIES:
58:CUSTOM_ID: drop-ip-group-profile-via-the-unifi-firewall
59:END:
60To instruct the Unifi machine to block the profile we just created, we
61need to navigate to the Network app > Settings > Firewall & Security.
62
63Within this screen, find the Firewall Rules table and click
64=Create Entry=. This entry should contain the following settings:
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: =<Name of the Group Profile You Created Above>=
72
73Customize the remaining configurations to your liking, and then save and
74enable the firewall rule.
75
76#+caption: Firewall Rule
77[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png]]
78
79Once enabled, the Unifi machine will be able to drop all incoming
80connections from the defined IP addresses and subnets within the created
81profile.
82
83#+begin_quote
84As a personal aside to this topic, I'm looking for a convenient way to
85update the firewall rules or profiles remotely (within the LAN) from the
86web server to accelerate this process. If you have an idea on how to
87automatically update Unifi IP groups or firewall rules, let me know!
88
89#+end_quote
blog/2023-06-20-audit-review-cheatsheet.org deleted −81
@@ -1,81 +0,0 @@
1#+title: Audit Review Checklist
2#+date: 2023-06-20
3
4
5** Overview
6:PROPERTIES:
7:CUSTOM_ID: overview
8:END:
9This post is a /very/ brief overview on the basic process to review
10audit test results, focusing on work done as part of a financial
11statement audit (FSA) or service organization controls (SOC) report.
12
13While there are numerous different things to review and look for - all
14varying wildly depending on the report, client, and tester - this list
15serves as a solid base foundation for a reviewer.
16
17I have used this throughout my career as a starting point to my reviews,
18and it has worked wonders for creating a consistent and objective
19template to my reviews. The goal is to keep this base high-level enough
20to be used on a wide variety of engagements, while still ensuring that
21all key areas are covered.
22
23** Cheatsheet
24:PROPERTIES:
25:CUSTOM_ID: cheatsheet
26:END:
271. [ ] Check all documents for spelling and grammar.
282. [ ] Ensure all acronyms are fully explained upon first use.
293. [ ] For all people referenced, use their full names and job titles
30 upon first use.
314. [ ] All supporting documents must cross-reference to the lead sheet
32 and vice-versa.
335. [ ] Verify that the control has been adequately tested:
34 - [ ] *Test of Design*: Did the tester obtain information regarding
35 how the control should perform normally and abnormally (e.g.,
36 emergency scenarios)?
37 - [ ] *Test of Operating Effectiveness*: Did the tester inquire,
38 observe, inspect, or re-perform sufficient evidence to support
39 their conclusion over the control? Inquiry alone is not 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 the
45 tester include a copy and notate C&A considerations?
467. [ ] Did the tester document the specific criteria that the control is
47 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 internal
69 guidance or your regulatory bodies that would affect the audit
70 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 the
78 finding's severity, and do they mitigate the risk within the
79 control objective?
80 - [ ] Does the finding exist at the end of the period, or was it
81 resolved within the audit period?
blog/2023-06-28-backblaze-b2.org deleted −207
@@ -1,207 +0,0 @@
1#+title: Getting Started with Backblaze B2 Cloud Storage
2#+date: 2023-06-28
3
4** Overview
5:PROPERTIES:
6:CUSTOM_ID: overview
7:END:
8Backblaze [[https://www.backblaze.com/b2/cloud-storage.html][B2 Cloud
9Storage]] is an inexpensive and reliable on-demand cloud storage and
10backup solution.
11
12The service starts at $5/TB/month ($0.005/GB/month) with a download rate
13of $0.01/GB/month.
14
15However, there are free tiers:
16
17- The first 10 GB of storage is free.
18- The first 1 GB of data downloaded each day is free.
19- Class A transactions are free.
20- The first 2500 Class B transactions each day are free.
21- The first 2500 Class C transactions each day are free.
22
23You can see which API calls fall into categories A, B, or C here:
24[[https://www.backblaze.com/b2/b2-transactions-price.html][Pricing
25Organized by API Calls]].
26
27For someone like me, who wants an offsite backup of their server's
28=/home/= directory and various other server configs that fall under 10
29GB total, Backblaze is a great solution from a financial perspective.
30
31** Create An Account
32:PROPERTIES:
33:CUSTOM_ID: create-an-account
34:END:
35To start with Backblaze, you'll need to
36[[https://www.backblaze.com/b2/sign-up.html][create a free account]] -
37no payment method is required to sign up.
38
39Once you have an account, you can test out the service with their web
40GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool
41below to test a file upload and then sync an entire directory to my
42Backblaze bucket.
43
44** Create a Bucket
45:PROPERTIES:
46:CUSTOM_ID: create-a-bucket
47:END:
48Before you can start uploading, you need to create a bucket. If you're
49familiar with other object storage services, this will feel familiar. If
50not, it's pretty simple to create one.
51
52As their webpage says:
53
54#+begin_quote
55A bucket is a container that holds files that are uploaded into B2 Cloud
56Storage. The bucket name must be globally unique and must have a minimum
57of 6 characters. A limit of 100 buckets may be created per account. An
58unlimited number of files may be uploaded into a bucket.
59
60#+end_quote
61
62Once you click the =Create a Bucket= button on their webpage or mobile
63app, you need to provide the following:
64
65- Bucket Unique Name
66- Files in Bucket are: =Private= or =Public=
67- Default Encryption: =Disable= or =Enable=
68- Object Lock: =Disable= or =Enable=
69
70For my bucket, I created a private bucket with encryption enabled and
71object lock disabled.
72
73Once your bucket is created, you can test the upload/download feature on
74their web GUI or mobile app! At this point, you have a fully functional
75bucket and account.
76
77** Linux CLI Tool
78:PROPERTIES:
79:CUSTOM_ID: linux-cli-tool
80:END:
81*** Installation
82:PROPERTIES:
83:CUSTOM_ID: installation
84:END:
85To install the =b2= CLI tool, you'll need to download it from the
86[[https://www.backblaze.com/docs/cloud-storage-command-line-tools][CLI
87Tools]] page. I recommend copying the URL from the link that says
88=Linux= and using wget to download it, as shown below.
89
90Once downloaded, make the file executable and move it to a location on
91your =$PATH=, so that you can execute that command from anywhere on the
92machine.
93
94#+begin_src sh
95wget <b2_cli_url>
96chmod +x b2_linux
97mv b2_linux /usr/bin/b2
98#+end_src
99
100*** Log In
101:PROPERTIES:
102:CUSTOM_ID: log-in
103:END:
104The first step after installation is to log in. To do this, execute the
105following command and provide your =<applicationKeyId>= and
106=<applicationKey>=.
107
108If you don't want to provide these values in the command itself, you can
109simply execute the base command and it will request them in an
110interactive prompt.
111
112#+begin_src sh
113# if you want to provide the keys directly:
114b2 authorize-account [<applicationKeyId>] [<applicationKey>]
115
116# or, if you don't want your keys in your shell history:
117b2 authorize-account
118#+end_src
119
120*** Upload a Test File
121:PROPERTIES:
122:CUSTOM_ID: upload-a-test-file
123:END:
124In order to test the functionality of the CLI tool, I'll start by
125uploading a single test file to the bucket I created above. We can do
126this with the =upload_file= function.
127
128The command is issued as follows:
129
130#+begin_src sh
131b2 upload_file <bucket_name> <local_file> <remote_file>
132#+end_src
133
134In my situation, I executed the following command with my username.
135
136#+begin_src sh
137b2 upload_file my_unique_bucket /home/<user>/test.md test.md
138#+end_src
139
140To confirm that the file was uploaded successfully, list the files in
141your bucket:
142
143#+begin_src sh
144b2 ls <bucket_name>
145#+end_src
146
147#+begin_src txt
148test.md
149#+end_src
150
151*** Sync a Directory
152:PROPERTIES:
153:CUSTOM_ID: sync-a-directory
154:END:
155If you have numerous files, you can use the =sync= function to perform
156functionality similar to =rsync=, where you can check what's in your
157bucket and sync anything that is new or modified.
158
159The command is issued as follows:
160
161#+begin_src sh
162b2 sync <source file location> <B2 bucket destination>
163#+end_src
164
165In my case, I can sync my user's entire home directory to my bucket
166without specifying any of the files directly:
167
168#+begin_src sh
169b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
170#+end_src
171
172** Caveats
173:PROPERTIES:
174:CUSTOM_ID: caveats
175:END:
176*** Timing of Updates to the Web GUI
177:PROPERTIES:
178:CUSTOM_ID: timing-of-updates-to-the-web-gui
179:END:
180When performing actions over a bucket, there is a slight delay in the
181web GUI when inspecting a bucket or its file. Note that simple actions
182such as uploading or deleting files may have a delay of a few minutes up
183to 24 hours. In my experience (<10 GB and ~20,000 files), any actions
184took only a few minutes to update across clients.
185
186*** Symlinks
187:PROPERTIES:
188:CUSTOM_ID: symlinks
189:END:
190Note that symlinks are resolved by b2, so if you have a link from
191=/home/<user>/nas-storage= that symlinks out to a =/mnt/nas-storage=
192folder that has 10TB of data, =b2= will resolve that link and start
193uploading all 10TB of data linked within the folder.
194
195If you're not sure if you have any symlinks, a symlink will look like
196this (note the =->= symbol):
197
198#+begin_src sh
199> ls -lha
200lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
201#+end_src
202
203You can recursively find symlink in a path with the following command:
204
205#+begin_src sh
206ls -lR /path/to/search | grep '^l'
207#+end_src
blog/2023-08-18-agile-auditing.org deleted −172
@@ -1,172 +0,0 @@
1#+title: Agile Auditing: An Introduction
2#+date: 2023-08-18
3
4** What is Agile Auditing?
5:PROPERTIES:
6:CUSTOM_ID: what-is-agile-auditing
7:END:
8[[https://en.wikipedia.org/wiki/Agile_software_development][Agile]], the
9collaborative philosophy behind many software development methods, has
10been picking up steam as a beneficial tool to use in the external and
11internal auditing world.
12
13This blog post will walk through commonly used terms within Agile,
14Scrum, and Kanban in order to translate these terms and roles into
15audit-specific terms.
16
17Whether your team is in charge of a financial statement audit, an
18attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms
19used throughout this post should still apply.
20
21** Agile
22:PROPERTIES:
23:CUSTOM_ID: agile
24:END:
25To start, I'll take a look at Agile.
26
27#+begin_quote
28The Agile methodology is a project management approach that involves
29breaking the project into phases and emphasizes continuous collaboration
30and improvement. Teams follow a cycle of planning, executing, and
31evaluating.
32
33#+end_quote
34
35While this approach may seem familiar to what audit teams have
36historically done, an audit team must make distinct changes in their
37mentality and how they approach and manage a project.
38
39*** Agile Values
40:PROPERTIES:
41:CUSTOM_ID: agile-values
42:END:
43The Agile Manifesto, written in 2001 at a summit in Utah, contain a set
44of four main values that comprise the Agile approach:
45
461. Individuals and interactions over processes and tools.
472. Working software over comprehensive documentation.
483. Customer collaboration over contract negotiation.
494. Responding to change over following a plan.
50
51Beyond the four values,
52[[https://agilemanifesto.org/principles.html][twelve principles]] were
53also written as part of the summit.
54
55In order to relate these values to an audit or attestation engagement,
56we need to shift the focus from software development to the main goal of
57an engagement: completing sufficient audit testing to address to
58relevant risks over the processes and controls at hand.
59
60Audit Examples:
61
62- Engagement teams must value the team members, client contacts, and
63 their interactions over the historical processes and tools that have
64 been used.
65- Engagement teams must value a final report that contains sufficient
66 audit documentation over excessive documentation or scope creep.
67- Engagement teams must collaborate with the audit clients as much as
68 feasible to ensure that both sides are constantly updated with current
69 knowledge of the engagement's status and any potential findings,
70 rather than waiting for pre-set meetings or the end of the engagement
71 to communicate.
72- Engagement teams must be able to respond to change in an engagement's
73 schedule, scope, or environment to ensure that the project is
74 completed in a timely manner and that all relevant areas are tested.
75 - In terms of an audit department's portfolio, they must be able to
76 respond to changes in their company's or client's environment and be
77 able to dynamically change their audit plan accordingly.
78
79** Scrum
80:PROPERTIES:
81:CUSTOM_ID: scrum
82:END:
83The above section discusses the high-level details of the Agile
84philosophy and how an audit team can potentially mold that mindset into
85the audit world, but how does a team implement these ideas?
86
87There are many methods that use an Agile mindset, but I prefer
88[[https://en.wikipedia.org/wiki/Scrum_(software_development)][Scrum]].
89Scrum is a framework based on Agile that enables a team to work through
90a project through a series of roles, ceremonies, artifacts, and values.
91
92Let's dive into each of these individually.
93
94*** Scrum Team
95:PROPERTIES:
96:CUSTOM_ID: scrum-team
97:END:
98A scrum project is only as good as the team running the project.
99Standard scrum teams are separated into three distinct areas:
100
1011. *Product Owner (Client Contact)*: The client contact is the audit
102 equivalent of the product owner in Scrum. They are responsible for
103 partnering with the engagement or audit team to ensure progress is
104 being made, priorities are established, and clear guidance is given
105 when questions or findings arise within each sprint.
1062. *Scrum Master (Engagement Lead)*: The engagement or audit team lead
107 is responsible for coaching the team and the client contact on the
108 scrum process, tracking team progress against plan, scheduling
109 necessary resources, and helping remove obstacles.
1103. *Scrum Developers (Engagement Members)*: The engagement or audit team
111 is the set of team members responsible for getting the work done.
112 These team members will work on each task, report progress, resolve
113 obstacles, and collaborate with other team members and the client
114 contact to ensure goals are being met.
115
116*** Scrum Ceremonies
117:PROPERTIES:
118:CUSTOM_ID: scrum-ceremonies
119:END:
120Scrum ceremonies are events that are performed on a regular basis.
121
1221. *Sprint Planning*: The team works together to plan the upcoming
123 sprint goal and which user stories (tasks) will be added to the
124 sprint to achieve that goal.
1252. *Sprint*: The time period, typically at least one week and no more
126 than one month in length, where the team works on the stories and
127 anything in the backlog.
1283. *Daily Scrum*: A very short meeting held each day, typically 15
129 minutes, to quickly emphasize alignment on the sprint goal and plan
130 the next 24 hours. Each team member may share what they did the day
131 before, what they'll do today, and any obstacles to their work.
1324. *Sprint Review*: At the end of each sprint, the team will gather and
133 discuss the progress, obstacles, and backlog from the previous
134 sprint.
1355. *Sprint Retrospective*: More specific than the sprint review, the
136 retrospective is meant to discuss what worked and what did not work
137 during the sprint. This may be processes, tools, people, or even
138 things related to the Scrum ceremonies.
139
140One additional ceremony that may be applicable is organizing the
141backlog. This is typically the responsibility of the engagement leader
142and is meant to prioritize and clarify what needs to be done to complete
143items in the backlog.
144
145*** Artifacts
146:PROPERTIES:
147:CUSTOM_ID: artifacts
148:END:
149While artifacts are generally not customizable in the audit world (i.e.,
150each control test must include some kind of working paper with evidence
151supporting the test results), I wanted to include some quick notes on
152associating scrum artifact terms with an audit.
153
1541. *Product Backlog*: This is the overall backlog of unfinished audit
155 tasks from all prior sprints.
1562. *Sprint Backlog*: This is the backlog of unfinished audit tasks from
157 one individual sprint.
1583. *Increment*: This is the output of each sprint - generally this is
159 best thought of as any documentation prepared during the sprint, such
160 as risk assessments, control working papers, deficiency analysis,
161 etc.
162
163** Kanban
164:PROPERTIES:
165:CUSTOM_ID: kanban
166:END:
167Last but not least, Kanban is a methodology that relies on boards to
168categorize work into distinct, descriptive categories that allow an
169agile or scrum team to effectively plan the work of a sprint or project.
170
171See Atlassian's [[https://www.atlassian.com/agile/kanban][Kanban]] page
172for more information.
blog/2023-10-04-digital-minimalism.org deleted −110
@@ -1,110 +0,0 @@
1#+title: Digital Minimalism
2#+date: 2023-10-05
3
4I've written [[/notes/minimalism][a note about minimalism]] before, but I wanted to dedicate some time
5to reflect on digital minimalism and how I've been able to minimize the impact
6of digital devices in my life.
7
8#+begin_quote
9These changes crept up on us and happened fast, before we had a chance to step
10back and ask what we really wanted out of the rapid advances of the past
11decade. We added new technologies to the periphery of our experience for minor
12reasons, then woke one morning to discover that they had colonized the core of
13our daily life. We didn't, in other words, sign up for the digital world in
14which we're currently entrenched; we seem to have stumbled backward into
15it.
16
17/(Digital Minimalism, 2019)/
18#+end_quote
19
20** The Principles of Digital Minimalism
21:PROPERTIES:
22:CUSTOM_ID: the-principles-of-digital-minimalism
23:END:
24As noted in Cal Newport's book, /Digital Minimalism/, there are three main
25principles to digital minimalism that I tend to agree with:
26
271. Clutter is costly.
28 - Digital minimalists recognize that cluttering their time and attention with
29 too many devices, apps, and services creates an overall negative cost that
30 can swamp the small benefits that each individual item provides in
31 isolation.
322. Optimization is important.
33 - Digital minimalists believe that deciding a particular technology supports
34 something they value is only the first step. To truly extract its full
35 potential benefit, it's necessary to think carefully about how they'll use
36 the technology.
373. Intentionality is satisfying.
38 - Digital minimalists derive significant satisfaction from their general
39 commitment to being more intentional about how they engage with new
40 technologies. This source of satisfaction is independent of the specific
41 decisions they make and is one of the biggest reasons that minimalism tends
42 to be immensely meaningful to its practitioners.
43
44** Taking Action
45:PROPERTIES:
46:CUSTOM_ID: taking-action
47:END:
48In order to put the logic into practice, I've created a few new habits and
49continued performing old habits that are working well:
50
51*** Using Devices With Intention
52:PROPERTIES:
53:CUSTOM_ID: using-devices-with-intention
54:END:
55- I already rarely use "social media", mostly limited to forums such as Hacker
56 News and Tildes, so I've just tweaked my behavior to stop looking for content
57 in those places when I'm bored.
58- Use devices with intention. Each time I pick up a digital device, there should
59 be an intention to use the device to improve my current situation. No more
60 endless scrolling or searching for something to interest me.
61
62*** Prevent Distractions
63:PROPERTIES:
64:CUSTOM_ID: prevent-distractions
65:END:
66- Disable (most) notifications on all devices. I spent 15-30 minutes going
67 through the notifications on my phone, watch, and computer to ensure that only
68 a select few apps have the ability to interrupt me: Calendar, Messages, Phone,
69 Reminders, & Signal.
70- Disable badges for any apps except the ones mentioned in the bullet above.
71- Set-up focus profiles across devices so that I can enable different modes,
72 such as Personal when I only want to see notifications from people I care
73 about or Do Not Disturb, where absolutely nothing can interrupt me.
74- Clean up my home screens. This one was quite easy as I already maintain a
75 minimalist set-up, but I went extreme by limiting my phone to just eight apps
76 on the home screen and four in the dock. If I need another app, I'll have to
77 search or use the app library.
78- Remove the work profile from my phone. This was a tough decision as having my
79 work profile on my device definitely makes my life easier at times, but it
80 also has quite a negative effect when I'm "always online" and can see the
81 notifications and team activity 24/7. I believe creating a distinct barrier
82 between my work and personal devices will be beneficial in the end.
83
84*** Creating Alternative Activities
85:PROPERTIES:
86:CUSTOM_ID: creating-alternative-activities
87:END:
88This is the most difficult piece, as most of my hobbies and interests lie in the
89digital world. However, I'm making a concerted effort to put devices down unless
90necessary and force myself to perform other activities in the physical world
91instead.
92
93I've started with a few basics that are always readily available to me:
94
95- Do a chore, such as organizing or cleaning.
96- Read a book, study a piece of art, etc.
97- Exercise or get outdoors.
98- Participate in a hobby, such as photography, birding, disc golf, etc.
99- Let yourself be bored and wander into creativity.
100
101** Making Progress
102:PROPERTIES:
103:CUSTOM_ID: making-progress
104:END:
105I'll be taking notes as I continue down this journey and hope to see positive
106trends. I've always been a minimalist in the physical world and it feels
107refreshing to filter out the clutter that has come to dominate my digital life
108over the years.
109
110I'm excited to see where this journey leads.
blog/2023-11-08-scli.org deleted −153
@@ -1,153 +0,0 @@
1#+title: Installing scli on Alpine Linux (musl)
2#+date: 2023-11-08
3
4[[https://github.com/isamert/scli][scli]] is a command-line tool that allows you to connect to your Signal messenger
5account. This program utilizes a two-pane display that shows you chats on the
6left and the focused conversation on the right.
7
8This guide will show you how to install =scli= and its dependencies on Alpine
9Linux, which requires some extra work due to musl.
10
11If you're using a non-musl system, you can likely following the =scli= README
12and download the packaged binaries for an easier installation process.
13
14** Dependencies
15:PROPERTIES:
16:CUSTOM_ID: dependencies
17:END:
18In order to use =scli=, you need a few dependencies:
19
20- =openjdk17-jre= - Used as a dependency for the =signal-cli= tool.
21 Version may vary.
22- =signal-cli= - Used as the backbone of the =scli= tool.
23- =findutils= - Replaces the standard Busybox version of =xargs=.
24- =urwid= - A console user interface library for Python.
25- =urwid-readline= - For GNU emacs-like keybinds on the input line.
26- =qrencode= - Displays a QR code in the terminal to link the device
27 using your phone. Not necessary if you're only linking on desktop and
28 can copy/paste the connection URL.
29
30Let's start by installing the packages available via Alpine's
31repositories. Be sure to install the latest version of =openjdk=. If you
32run into Java-related issues, uninstall =openjdk= and install an older
33version.
34
35#+begin_src sh
36doas apk add openjdk17-jre findutils qrencode
37#+end_src
38
39Next, let's install =signal-cli=. Be sure to export the version of
40=signal-cli= that you want. I use version =0.12.4= below, but that may
41be outdated by the time you're reading this.
42
43#+begin_src sh
44export VERSION="0.12.4"
45wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
46doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
47doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
48#+end_src
49
50Finally, install the =urwid= packages using the Python packaging
51utility.
52
53#+begin_src sh
54pip3 install urwid urwid-readline
55#+end_src
56
57** Installation
58:PROPERTIES:
59:CUSTOM_ID: installation
60:END:
61Now that we have all of the dependencies we need, we can install =scli=.
62Start by simply cloning the repository.
63
64#+begin_src sh
65git clone https://github.com/isamert/scli
66#+end_src
67
68When I cloned this repository on 2023-11-08, I found a bug in the logic
69that required a fix. You must edit the =scli= file and replace the one
70instance of =RLIMIT_OFILE= with =RLIMIT_NOFILE=.
71
72#+begin_src sh
73cd scli
74nano scli
75#+end_src
76
77Once complete, you can move this program to anywhere on your =$PATH=. I
78chose the following directory.
79
80#+begin_src sh
81doas mv scli /usr/local/bin/scli
82#+end_src
83
84** Initial Setup
85:PROPERTIES:
86:CUSTOM_ID: initial-setup
87:END:
88Now that everything is installed, we can login and configure the client.
89Start by generating a connection link.
90
91#+begin_src sh
92signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
93#+end_src
94
95This will generate a connection link and related QR code for you to use
96to link the devices together. Once complete, *wait patiently* for the
97connection process to finish.
98
99Once it completes, it will exit and return you to the prompt. From here,
100you need to perform an initial =receive= command to start things off.
101The =USERNAME= variable should be your phone number, such as
102=+15551237890=.
103
104#+begin_src sh
105signal-cli -u USERNAME receive
106#+end_src
107
108Also be sure to test the daemon to ensure it works properly. If no
109errors occur, it's working. If you run into errors because you're not
110running a DBUS session, see my notes below.
111
112#+begin_src sh
113signal-cli -u USERNAME daemon
114#+end_src
115
116Once the initial reception is complete, you are ready to use =scli=.
117
118This process will differ depending on your desktop environment (DE). If
119you are running a DE, you likely have a DBUS session running already and
120can simply launch the program.
121
122However, if you're like me and running your computer straight on the TTY
123without a DE, you'll need to start a DBUS session for this program.
124
125#+begin_src sh
126# If you're not running a DBUS session yet, you need to start one for scli
127dbus-run-session -- scli
128
129# OR - If you're already running a DBUS session, simply run scli
130scli
131#+end_src
132
133** Configuration
134:PROPERTIES:
135:CUSTOM_ID: configuration
136:END:
137Lastly, there are a number of configuration options that you can pass
138via the command or in the =~/.config/sclirc= file. See the Github README
139for more information on configuration options.
140
141#+begin_src sh
142nano ~/.config/sclirc
143#+end_src
144
145#+begin_src conf
146# ~/.config/sclirc
147
148wrap-at = 80
149enable-notifications = true
150#+end_src
151
152That's it! Following this guide, I have a functional =scli= program that
153successfully sends messages to my contacts and myself!
blog/index.org deleted −152
@@ -1,152 +0,0 @@
1#+title: Blog
2#+OPTIONS: toc:nil
3
4Use =C-f= to search blog post titles for keywords.
5
6* 2023
7:PROPERTIES:
8:ID: 21779F03-E65E-4A80-A958-3D08723EB8AD
9:PUBDATE: 2023-12-03 Sun 22:35
10:END:
11
12- 2023-12-03 [[./2023-12-03-unifi-nextdns.org][How to Install NextDNS on the Unifi Dream Machine]]
13- 2023-11-08 [[./2023-11-08-scli.org][Installing scli on Alpine Linux (musl)]]
14- 2023-10-17 [[./2023-10-17-self-hosting-anonymousoverflow.org][Self-Hosting AnonymousOverflow]]
15- 2023-10-15 [[./2023-10-15-alpine-ssh-hardening.org][SSH Hardening for Alpine Linux]]
16- 2023-10-11 [[./2023-10-11-self-hosting-authelia.org][Self-Hosting Authelia]]
17- 2023-10-04 [[./2023-10-04-digital-minimalism.org][Digital Minimalism]]
18- 2023-09-19 [[./2023-09-19-audit-sql-scripts.org][Useful SQL Scripts for Auditing Logical Access]]
19- 2023-09-15 [[./2023-09-15-gitweb.org][Gitweb via Nginx]]
20- 2023-08-18 [[./2023-08-18-agile-auditing.org][Agile Auditing: An Introduction]]
21- 2023-07-19 [[./2023-07-19-plex-transcoder-errors.org][How to Fix Plex Error: 'Conversion failed. The transcoder failed to start up.']]
22- 2023-07-12 [[./2023-07-12-mullvad-wireguard-lan.org][Enabling LAN Access in Mullvad Wireguard Conf Files]]
23- 2023-06-30 [[./2023-06-30-self-hosting-voyager-lemmy-client.org][Self Hosting Voyager - A Lemmy Web Client]]
24- 2023-06-28 [[./2023-06-28-backblaze-b2.org][Getting Started with Backblaze B2 Cloud Storage]]
25- 2023-06-23 [[./2023-06-23-self-hosting-convos-irc.org][Self-Hosting Convos IRC Web Client]]
26- 2023-06-23 [[./2023-06-23-byobu.org][Byobu]]
27- 2023-06-20 [[./2023-06-20-audit-review-cheatsheet.org][Cheatsheet: Review Audit Test Results]]
28- 2023-06-18 [[./2023-06-18-unifi-ip-blocklist.org][Block IP Addresses and Subnets with Unifi Network Firewall]]
29- 2023-06-08 [[./2023-06-08-self-hosting-baikal-server.org][Self-Hosting Baïkal Server (CalDAV & CardDAV)]]
30- 2023-06-08 [[./2023-06-08-goaccess-geoip.org][Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data]]
31- 2023-05-22 [[./2023-05-22-burnout.org][Burnout]]
32- 2023-02-02 [[./2023-02-02-exploring-hare.org][Exploring the Hare Programming Language]]
33- 2023-01-28 [[./2023-01-28-self-hosting-wger-workout-manager.org][Self-Hosting Wger Workout Manager]]
34- 2023-01-23 [[./2023-01-23-random-mullvad-wireguard.org][Connecting to a Random Mullvad Wireguard Host]]
35- 2023-01-21 [[./2023-01-21-running-flatpak-apps-easily.org][Running Flatpak Apps with Symlinks]]
36- 2023-01-08 [[./2023-01-08-fedora-remove-login-manager.org][Remove the Login Manager from Fedora i3]]
37- 2023-01-05 [[./2023-01-05-mass-unlike-tumblr-posts.org][How to Easily Mass Unlike Tumblr Posts With Javascript]]
38- 2023-01-03 [[./2023-01-03-recent-website-changes.org][Recent Website Changes]]
39
40* 2022
41:PROPERTIES:
42:ID: 3B281087-A3B6-46B7-8A29-77F74DAD5F15
43:PUBDATE: 2023-12-03 Sun 22:35
44:END:
45
46- 2022-12-23 [[./2022-12-23-alpine-desktop.org][Alpine Linux as a Desktop OS]]
47- 2022-12-17 [[./2022-12-17-st.org][Simple Terminal]]
48- 2022-12-07 [[./2022-12-07-nginx-wildcard-redirect.org][Redirect Nginx Subdomains & Trailing Content with Regex]]
49- 2022-12-01 [[./2022-12-01-nginx-compression.org][Enable GZIP Compression on Nginx]]
50- 2022-11-29 [[./2022-11-29-nginx-referrer-ban-list.org][Creating a Referrer Ban List on Nginx]]
51- 2022-11-27 [[./2022-11-27-server-build.org][Building a Custom Rack-Mount Server]]
52- 2022-11-11 [[./2022-11-11-nginx-tmp-errors.org][Fixing Permission Errors in /var/lib/nginx]]
53- 2022-11-07 [[./2022-11-07-matrix-synapse.org][Self-Hosting Matrix Synapse on Alpine Linux]]
54- 2022-10-30 [[./2022-10-30-linux-display-manager.org][How to Disable or Change the Display Manager on Void Linux]]
55- 2022-10-22 [[./2022-10-22-alpine-linux.org][Alpine Linux: My New Server OS]]
56- 2022-10-20 [[./2022-10-20-syncthing.org][Syncthing: A Minimal Self-Hosted Cloud Storage Solution]]
57- 2022-10-04 [[./2022-10-04-mtp-linux.org][How to Mount an MTP Mobile Device on Linux]]
58- 2022-09-21 [[./2022-09-21-graphene-os.org][Installing Graphene OS on the Pixel 6 Pro]]
59- 2022-09-17 [[./2022-09-17-serenity-os.org][Serenity OS: Testing Out a Unique System]]
60- 2022-08-31 [[./2022-08-31-privacy.com-changes.org][Concerning Changes on Privacy.com]]
61- 2022-07-31 [[./2022-07-31-bash-it.org][Upgrade Bash with Bash-It & Ble.sh]]
62- 2022-07-30 [[./2022-07-30-flac-to-opus.org][Recursive Command-Line FLAC to Opus Conversion]]
63- 2022-07-25 [[./2022-07-25-curseradio.org][CurseRadio: Listening to the Radio on the Command Line]]
64- 2022-07-14 [[./2022-07-14-gnupg.org][GNU Privacy Guard (GPG)]]
65- 2022-07-01 [[./2022-07-01-git-server.org][Self-Hosting a Personal Git Server]]
66- 2022-06-24 [[./2022-06-24-fedora-i3.org][Rebooting My Love Affair with Linux]]
67- 2022-06-22 [[./2022-06-22-daily-poetry.org][Daily Plaintext Poetry via Email]]
68- 2022-06-16 [[./2022-06-16-terminal-lifestyle.org][A Terminal Lifestyle]]
69- 2022-06-07 [[./2022-06-07-freshrss.org][Self-Hosting FreshRSS]]
70- 2022-06-04 [[./2022-06-04-njalla-dns-api.org][Dynamic DNS with Njalla API]]
71- 2022-06-01 [[./2022-06-01-ditching-cloudflare.org][Ditching Cloudflare for Njalla]]
72- 2022-04-09 [[./2022-04-09-pinetime.org][PineTime: An Open-Source SmartWatch]]
73- 2022-04-02 [[./2022-04-02-nginx-reverse-proxy.org][Set-Up a Reverse Proxy with Nginx]]
74- 2022-03-26 [[./2022-03-26-ssh-mfa.org][Enable TOTP MFA for SSH]]
75- 2022-03-24 [[./2022-03-24-server-hardening.org][Hardening a Public-Facing Home Server]]
76- 2022-03-23 [[./2022-03-23-nextcloud-on-ubuntu.org][Installing Nextcloud on Ubuntu]]
77- 2022-03-23 [[./2022-03-23-cloudflare-dns-api.org][Dynamic DNS with Cloudflare API]]
78- 2022-03-08 [[./2022-03-08-plex-migration.org][Migrating Plex to New Server (+ Nvidia Transcoding)]]
79- 2022-03-03 [[./2022-03-03-financial-database.org][Maintaining a Personal Financial Database]]
80- 2022-03-02 [[./2022-03-02-note-taking.org][Easy, Reliable Note-Taking]]
81- 2022-02-22 [[./2022-02-22-tuesday.org][Tuesday]]
82- 2022-02-20 [[./2022-02-20-nginx-caching.org][Caching Static Content with Nginx]]
83- 2022-02-17 [[./2022-02-17-exiftool.org][Stripping Image Metadata with exiftool]]
84- 2022-02-16 [[./2022-02-16-debian-and-nginx.org][Migrating to a New Web Server Setup with Debian, Nginx, and Agate]]
85- 2022-02-10 [[./2022-02-10-leaving-the-office.org][Leaving Office-Based Work In the Past]]
86
87* 2021
88:PROPERTIES:
89:ID: 7603D51F-513F-4407-9CDC-F55C555D35D4
90:PUBDATE: 2023-12-03 Sun 22:35
91:END:
92
93- 2021-12-04 [[./2021-12-04-cisa.org][I Passed the CISA!]]
94- 2021-10-09 [[./2021-10-09-apache-redirect.org][Apache Redirect HTML Files to a Directory]]
95- 2021-08-25 [[./2021-08-25-audit-sampling.org][Audit Sampling with Python]]
96- 2021-07-15 [[./2021-07-15-delete-gitlab-repos.org][How to Delete All GitLab Repositories]]
97- 2021-05-30 [[./2021-05-30-changing-git-authors.org][Changing Git Authors]]
98- 2021-04-28 [[./2021-04-28-photography.org][Jumping Back Into Photography]]
99- 2021-04-23 [[./2021-04-23-php-comment-system.org][Roll Your Own Static Commenting System in PHP]]
100- 2021-04-17 [[./2021-04-17-gemini-server.org][Hosting a Gemini Server]]
101- 2021-03-30 [[./2021-03-30-vps-web-server.org][How to Set Up a VPS Web Server]]
102- 2021-03-28 [[./2021-03-28-vaporwave-vs-outrun.org][Vaporwave vs Outrun]]
103- 2021-03-28 [[./2021-03-28-gemini-capsule.org][Launching a Gemini Capsule]]
104- 2021-03-19 [[./2021-03-19-clone-github-repos.org][How to Clone All Repositories from a GitHub or Sourcehut Account]]
105- 2021-02-19 [[./2021-02-19-macos.org][macOS: Testing Out A New OS]]
106- 2021-01-07 [[./2021-01-07-ufw.org][Secure Your Network with the Uncomplicated Firewall]]
107- 2021-01-04 [[./2021-01-04-fediverse.org][A Simple Guide to the Fediverse]]
108- 2021-01-01 [[./2021-01-01-seum.org][SEUM: Speedrunners from Hell]]
109
110* 2020
111:PROPERTIES:
112:ID: 8442803D-FB00-498D-9936-51029E80B367
113:PUBDATE: 2023-12-03 Sun 22:35
114:END:
115
116- 2020-12-29 [[./2020-12-29-zork.org][Zork: Let's Explore a Classic]]
117- 2020-12-28 [[./2020-12-28-neon-drive.org][Neon Drive: A Nostalgic 80s Arcade Racing Game]]
118- 2020-12-27 [[./2020-12-27-website-redesign.org][Redesigning My Website: The 5 KB Result]]
119- 2020-10-12 [[./2020-10-12-mediocrity.org][On the Pursuit of Mediocrity]]
120- 2020-09-25 [[./2020-09-25-happiness-map.org][Data Visualization: World Choropleth Map of Happiness]]
121- 2020-09-22 [[./2020-09-22-internal-audit.org][What is Internal Audit?]]
122- 2020-09-01 [[./2020-09-01-visual-recognition.org][IBM Watson Visual Recognition]]
123- 2020-08-29 [[./2020-08-29-php-auth-flow.org][PHP Authentication Flow]]
124- 2020-08-22 [[./2020-08-22-redirect-github-pages.org][Redirect GitHub Pages from Subdomain to the Top-Level Domain]]
125- 2020-07-26 [[./2020-07-26-business-analysis.org][Algorithmically Analyzing Local Businesses]]
126- 2020-07-20 [[./2020-07-20-video-game-sales.org][Data Exploration: Video Game Sales]]
127- 2020-05-19 [[./2020-05-19-customizing-ubuntu.org][Beginner's Guide: Customizing Ubuntu]]
128- 2020-05-03 [[./2020-05-03-homelab.org][An Inside Look at My Homelab]]
129- 2020-03-25 [[./2020-03-25-session-messenger.org][Session Private Messenger]]
130- 2020-02-09 [[./2020-02-09-cryptography-basics.org][Cryptography Basics]]
131- 2020-01-26 [[./2020-01-26-steam-on-ntfs.org][Linux Gaming Tweak: Steam on NTFS Drives]]
132- 2020-01-25 [[./2020-01-25-linux-software.org][Linux Software]]
133
134* 2019
135:PROPERTIES:
136:ID: CC0E4E85-6094-48AD-B78A-68DE97E56A17
137:PUBDATE: 2023-12-03 Sun 22:35
138:END:
139
140- 2019-12-16 [[./2019-12-16-password-security.org][Password Security]]
141- 2019-12-03 [[./2019-12-03-the-ansoff-matrix.org][The Ansoff Matrix]]
142- 2019-09-09 [[./2019-09-09-audit-analytics.org][Data Analysis in Auditing]]
143- 2019-01-07 [[./2019-01-07-useful-css.org][Useful CSS Snippets]]
144
145* 2018
146:PROPERTIES:
147:ID: 4FE32AD6-AC36-43DD-ACEF-B2D98450FB2D
148:PUBDATE: 2023-12-03 Sun 22:35
149:END:
150
151- 2018-12-08 [[./2018-12-08-aes-encryption.org][AES Encryption]]
152- 2018-11-28 [[./2018-11-28-cpp-compiler.org][The C++ Compiler]]
blog/rss.org deleted −108
@@ -1,108 +0,0 @@
1#+TITLE: cleberg.net
2
3- [[file:2023-12-03-unifi-nextdns.org][How to Install NextDNS on the Unifi Dream Machine]]
4- [[file:2021-03-28-vaporwave-vs-outrun.org][2021-03-28-vaporwave-vs-outrun]]
5- [[file:2023-06-23-self-hosting-convos-irc.org][2023-06-23-self-hosting-convos-irc]]
6- [[file:2020-07-26-business-analysis.org][Algorithmically Analyzing Local Businesses]]
7- [[file:2023-07-12-mullvad-wireguard-lan.org][2023-07-12-mullvad-wireguard-lan]]
8- [[file:2022-11-11-nginx-tmp-errors.org][2022-11-11-nginx-tmp-errors]]
9- [[file:2022-02-16-debian-and-nginx.org][2022-02-16-debian-and-nginx]]
10- [[file:2023-06-20-audit-review-cheatsheet.org][2023-06-20-audit-review-cheatsheet]]
11- [[file:2020-08-22-redirect-github-pages.org][Redirect GitHub Pages from Subdomain to the Top-Level Domain]]
12- [[file:2020-08-29-php-auth-flow.org][PHP Authentication Flow]]
13- [[file:2023-05-22-burnout.org][2023-05-22-burnout]]
14- [[file:2022-09-21-graphene-os.org][2022-09-21-graphene-os]]
15- [[file:2021-04-17-gemini-server.org][2021-04-17-gemini-server]]
16- [[file:2023-11-08-scli.org][2023-11-08-scli]]
17- [[file:2021-03-28-gemini-capsule.org][2021-03-28-gemini-capsule]]
18- [[file:2022-04-09-pinetime.org][2022-04-09-pinetime]]
19- [[file:2023-06-08-self-hosting-baikal-server.org][2023-06-08-self-hosting-baikal-server]]
20- [[file:2022-07-14-gnupg.org][2022-07-14-gnupg]]
21- [[file:2022-12-17-st.org][2022-12-17-st]]
22- [[file:2020-05-19-customizing-ubuntu.org][Beginner's Guide: Customizing Ubuntu]]
23- [[file:2022-03-23-cloudflare-dns-api.org][2022-03-23-cloudflare-dns-api]]
24- [[file:2020-09-25-happiness-map.org][Data Visualization: World Choropleth Map of Happiness]]
25- [[file:2022-03-24-server-hardening.org][2022-03-24-server-hardening]]
26- [[file:2022-02-22-tuesday.org][2022-02-22-tuesday]]
27- [[file:2021-07-15-delete-gitlab-repos.org][2021-07-15-delete-gitlab-repos]]
28- [[file:2019-12-03-the-ansoff-matrix.org][The Ansoff Matrix]]
29- [[file:2023-06-28-backblaze-b2.org][2023-06-28-backblaze-b2]]
30- [[file:2022-12-07-nginx-wildcard-redirect.org][2022-12-07-nginx-wildcard-redirect]]
31- [[file:2023-01-05-mass-unlike-tumblr-posts.org][2023-01-05-mass-unlike-tumblr-posts]]
32- [[file:2023-01-08-fedora-remove-login-manager.org][2023-01-08-fedora-remove-login-manager]]
33- [[file:2018-12-08-aes-encryption.org][AES Encryption]]
34- [[file:2020-05-03-homelab.org][An Inside Look at My Homelab]]
35- [[file:2023-09-15-gitweb.org][2023-09-15-gitweb]]
36- [[file:2022-11-29-nginx-referrer-ban-list.org][2022-11-29-nginx-referrer-ban-list]]
37- [[file:2022-03-26-ssh-mfa.org][2022-03-26-ssh-mfa]]
38- [[file:2022-12-23-alpine-desktop.org][2022-12-23-alpine-desktop]]
39- [[file:2020-01-25-linux-software.org][Linux Software]]
40- [[file:2022-02-17-exiftool.org][2022-02-17-exiftool]]
41- [[file:2021-12-04-cisa.org][2021-12-04-cisa]]
42- [[file:2020-02-09-cryptography-basics.org][Cryptography Basics]]
43- [[file:2022-07-30-flac-to-opus.org][2022-07-30-flac-to-opus]]
44- [[file:2022-06-24-fedora-i3.org][2022-06-24-fedora-i3]]
45- [[file:2020-07-20-video-game-sales.org][Data Exploration: Video Game Sales]]
46- [[file:2022-11-07-matrix-synapse.org][2022-11-07-matrix-synapse]]
47- [[file:2020-10-12-mediocrity.org][On the Pursuit of Mediocrity]]
48- [[file:2021-02-19-macos.org][2021-02-19-macos]]
49- [[file:2020-01-26-steam-on-ntfs.org][Linux Gaming Tweak: Steam on NTFS Drives]]
50- [[file:2019-01-07-useful-css.org][Useful CSS Snippets]]
51- [[file:2023-01-28-self-hosting-wger-workout-manager.org][2023-01-28-self-hosting-wger-workout-manager]]
52- [[file:2022-11-27-server-build.org][2022-11-27-server-build]]
53- [[file:2023-10-04-digital-minimalism.org][2023-10-04-digital-minimalism]]
54- [[file:2020-12-27-website-redesign.org][Redesigning My Website: The 5 KB Result]]
55- [[file:2023-09-19-audit-sql-scripts.org][2023-09-19-audit-sql-scripts]]
56- [[file:2020-12-28-neon-drive.org][Neon Drive: A Nostalgic 80s Arcade Racing Game]]
57- [[file:2023-02-02-exploring-hare.org][2023-02-02-exploring-hare]]
58- [[file:2022-09-17-serenity-os.org][2022-09-17-serenity-os]]
59- [[file:2021-10-09-apache-redirect.org][2021-10-09-apache-redirect]]
60- [[file:2022-03-08-plex-migration.org][2022-03-08-plex-migration]]
61- [[file:2023-01-21-running-flatpak-apps-easily.org][2023-01-21-running-flatpak-apps-easily]]
62- [[file:2022-06-16-terminal-lifestyle.org][2022-06-16-terminal-lifestyle]]
63- [[file:2022-10-22-alpine-linux.org][2022-10-22-alpine-linux]]
64- [[file:2019-09-09-audit-analytics.org][Data Analysis in Auditing]]
65- [[file:2023-08-18-agile-auditing.org][2023-08-18-agile-auditing]]
66- [[file:2023-01-23-random-mullvad-wireguard.org][2023-01-23-random-mullvad-wireguard]]
67- [[file:2022-02-20-nginx-caching.org][2022-02-20-nginx-caching]]
68- [[file:2022-08-31-privacy.com-changes.org][2022-08-31-privacy.com-changes]]
69- [[file:2022-03-02-note-taking.org][2022-03-02-note-taking]]
70- [[file:2021-04-28-photography.org][2021-04-28-photography]]
71- [[file:2020-09-22-internal-audit.org][What is Internal Audit?]]
72- [[file:2022-07-25-curseradio.org][2022-07-25-curseradio]]
73- [[file:2022-10-30-linux-display-manager.org][2022-10-30-linux-display-manager]]
74- [[file:2022-06-01-ditching-cloudflare.org][2022-06-01-ditching-cloudflare]]
75- [[file:2023-06-08-goaccess-geoip.org][2023-06-08-goaccess-geoip]]
76- [[file:2019-12-16-password-security.org][Password Security]]
77- [[file:2021-01-07-ufw.org][2021-01-07-ufw]]
78- [[file:2022-07-01-git-server.org][2022-07-01-git-server]]
79- [[file:2023-06-23-byobu.org][2023-06-23-byobu]]
80- [[file:2020-03-25-session-messenger.org][Session Private Messenger]]
81- [[file:2023-01-03-recent-website-changes.org][2023-01-03-recent-website-changes]]
82- [[file:2023-06-30-self-hosting-voyager-lemmy-client.org][2023-06-30-self-hosting-voyager-lemmy-client]]
83- [[file:2021-05-30-changing-git-authors.org][2021-05-30-changing-git-authors]]
84- [[file:2023-10-11-self-hosting-authelia.org][2023-10-11-self-hosting-authelia]]
85- [[file:2021-01-04-fediverse.org][2021-01-04-fediverse]]
86- [[file:2021-04-23-php-comment-system.org][2021-04-23-php-comment-system]]
87- [[file:2023-07-19-plex-transcoder-errors.org][2023-07-19-plex-transcoder-errors]]
88- [[file:2022-07-31-bash-it.org][2022-07-31-bash-it]]
89- [[file:2023-10-15-alpine-ssh-hardening.org][2023-10-15-alpine-ssh-hardening]]
90- [[file:2022-10-04-mtp-linux.org][2022-10-04-mtp-linux]]
91- [[file:2022-06-04-njalla-dns-api.org][2022-06-04-njalla-dns-api]]
92- [[file:2022-03-23-nextcloud-on-ubuntu.org][2022-03-23-nextcloud-on-ubuntu]]
93- [[file:2021-03-19-clone-github-repos.org][2021-03-19-clone-github-repos]]
94- [[file:2022-04-02-nginx-reverse-proxy.org][2022-04-02-nginx-reverse-proxy]]
95- [[file:2023-10-17-self-hosting-anonymousoverflow.org][2023-10-17-self-hosting-anonymousoverflow]]
96- [[file:2022-06-22-daily-poetry.org][2022-06-22-daily-poetry]]
97- [[file:2018-11-28-cpp-compiler.org][The C++ Compiler]]
98- [[file:2021-03-30-vps-web-server.org][2021-03-30-vps-web-server]]
99- [[file:2021-01-01-seum.org][2021-01-01-seum]]
100- [[file:2022-02-10-leaving-the-office.org][2022-02-10-leaving-the-office]]
101- [[file:2020-09-01-visual-recognition.org][IBM Watson Visual Recognition]]
102- [[file:2023-06-18-unifi-ip-blocklist.org][2023-06-18-unifi-ip-blocklist]]
103- [[file:2022-10-20-syncthing.org][2022-10-20-syncthing]]
104- [[file:2022-12-01-nginx-compression.org][2022-12-01-nginx-compression]]
105- [[file:2022-03-03-financial-database.org][2022-03-03-financial-database]]
106- [[file:2021-08-25-audit-sampling.org][2021-08-25-audit-sampling]]
107- [[file:2022-06-07-freshrss.org][2022-06-07-freshrss]]
108- [[file:2020-12-29-zork.org][Zork: Let's Explore a Classic]]\ No newline at end of file
config.toml added +18
@@ -0,0 +1,18 @@
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 = [{ theme = "dracula", filename = "syntax-theme-dark.css" }]
15external_links_target_blank = true
16lazy_async_image = true
17
18[extra]
content/blog/2018-11-28-cpp-compiler.md added +138
@@ -0,0 +1,138 @@
1+++
2date = 2018-11-28
3title = "The C++ Compiler"
4description = "Learn basics about the C++ compilation process."
5+++
6
7# A Brief Introduction
8
9[C++](https://en.wikipedia.org/wiki/C%2B%2B) is a general-purpose
10programming language with object-oriented, generic, and functional
11features in addition to facilities for low-level memory manipulation.
12
13The source code, shown in the snippet below, must be compiled before it
14can be executed. There are many steps and intricacies to the compilation
15process, and this post was a personal exercise to learn and remember as
16much information as I can.
17
18```cpp
19#include <iostream>
20
21int main()
22{
23 std::cout << "Hello, world!\n";
24}
25```
26
27# Compilation Process
28
29## An Overview
30
31Compiling C++ projects is a frustrating task most days. Seemingly
32nonexistent errors keeping your program from successfully compiling can
33be annoying (especially since you know you wrote it perfectly the first
34time, right?).
35
36I'm learning more and more about C++ these days and decided to write
37this concept down so that I can cement it even further in my own head.
38However, C++ is not the only compiled language. Check out [the Wikipedia
39entry for compiled
40languages](https://en.wikipedia.org/wiki/Compiled_language) for more
41examples of compiled languages.
42
43I'll start with a wonderful, graphical way to conceptualize the C++
44compiler. View [The C++ Compilation
45Process](https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html)
46by Kurt MacMahon, an NIU professor, to see the graphic and an
47explanation. The goal of the compilation process is to take the C++ code
48and produce a shared library, dynamic library, or an executable file.
49
50## Compilation Phases
51
52Let's break down the compilation process. There are four major steps to
53compiling C++ code.
54
55### Step 1
56
57The first step is to expand the source code file to meet all
58dependencies. The C++ preprocessor includes the code from all the header
59files, such as `#include <iostream>`. Now, what does that
60mean? The previous example includes the `iostream` header.
61This tells the computer that you want to use the `iostream`
62standard library, which contains classes and functions written in the
63core language. This specific header allows you to manipulate
64input/output streams. After all this, you'll end up which a temporary
65file that contains the expanded source code.
66
67In the example of the C++ code above, the `iostream` class
68would be included in the expanded code.
69
70### Step 2
71
72After the code is expanded, the compiler comes into play. The compiler
73takes the C++ code and converts this code into the assembly language,
74understood by the platform. You can see this in action if you head over
75to the [GodBolt Compiler Explorer](https://godbolt.org), which shows C++
76being converted into assembly dynamically.
77
78For example, the `Hello, world!` code snippet above compiles
79into the following assembly code:
80
81```asm
82.LC0:
83 .string "Hello, world!\n"
84main:
85 push rbp
86 mov rbp, rsp
87 mov esi, OFFSET FLAT:.LC0
88 mov edi, OFFSET FLAT:_ZSt4cout
89 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*)
90 mov eax, 0
91 pop rbp
92 ret
93__static_initialization_and_destruction_0(int, int):
94 push rbp
95 mov rbp, rsp
96 sub rsp, 16
97 mov DWORD PTR [rbp-4], edi
98 mov DWORD PTR [rbp-8], esi
99 cmp DWORD PTR [rbp-4], 1
100 jne .L5
101 cmp DWORD PTR [rbp-8], 65535
102 jne .L5
103 mov edi, OFFSET FLAT:_ZStL8__ioinit
104 call std::ios_base::Init::Init() [complete object constructor]
105 mov edx, OFFSET FLAT:__dso_handle
106 mov esi, OFFSET FLAT:_ZStL8__ioinit
107 mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
108 call __cxa_atexit
109.L5:
110 nop
111 leave
112 ret
113_GLOBAL__sub_I_main:
114 push rbp
115 mov rbp, rsp
116 mov esi, 65535
117 mov edi, 1
118 call __static_initialization_and_destruction_0(int, int)
119 pop rbp
120 ret
121```
122
123### Step 3
124
125Third, the assembly code generated by the compiler is assembled into the
126object code for the platform. Essentially, this is when the compiler
127takes the assembly code and assembles it into machine code in a binary
128format. After researching this online, I figured out that a lot of
129compilers will allow you to stop compilation at this step. This would be
130useful for compiling each source code file separately. This saves time
131later if a single file changes; only that file needs to be recompiled.
132
133### Step 4
134
135Finally, the object code file generated by the assembler is linked
136together with the object code files for any library functions used to
137produce a shared library, dynamic library, or an executable file. It
138replaces all references to undefined symbols with the correct addresses.
content/blog/2018-12-08-aes-encryption.md added +124
@@ -0,0 +1,124 @@
1+++
2date = 2018-12-08
3title = "AES Encryption"
4description = "An exploration of the AES encryption standard."
5+++
6
7# Basic AES
8
9If you're not familiar with encryption techniques,
10[AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) is the
11**Advanced Encryption Standard**. This specification was established by
12the National Institute of Standards and Technology, sub-selected from
13the Rijndael family of ciphers (128, 192, and 256 bits) in 2001.
14Furthering its popularity and status, the US government chose AES as
15their default encryption method for top-secret data, removing the
16previous standard which had been in place since 1977.
17
18AES has proven to be an extremely safe encryption method, with 7-round
19and 8-round attacks making no material improvements since the release of
20this encryption standard almost two decades ago.
21
22> Though many papers have been published on the cryptanalysis of AES,
23> the fastest single-key attacks on round-reduced AES variants [20,
24> 33] so far are only slightly more powerful than those proposed 10
25> years ago [23,24].
26>
27> - [Bogdonav, et
28> al.](http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf)
29
30# How Secure is AES?
31
32In theory, AES-256 is non-crackable due to the massive number of
33combinations that can be produced. However, AES-128 is no longer
34recommended as a viable implementation to protect important data.
35
36A semi-short [comic
37strip](http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html)
38from Moserware quickly explains AES for the public to understand.
39Basically AES encrypts the data by obscuring the relationship between
40the data and the encrypted data. Additionally, this method spreads the
41message out. Lastly, the key produced by AES is the secret to decrypting
42it. Someone may know the method of AES, but without the key, they are
43powerless.
44
45To obscure and spread the data out, AES creates a
46substitution-permutation network. Wikipedia has a wonderful [example of
47an SP
48network](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png)
49available. This network sends the data through a set of S boxes (using
50the unique key) to substitute the bits with another block of bits. Then,
51a P box will permutate, or rearrange, the bits. This is done over and
52over, with the key being derived from the last round. For AES, the key
53size specifies the number of transformation rounds: 10, 12, and 14
54rounds for 128-bit, 192-bit, and 256-bit keys, respectively.
55
56# The Process
57
581. \*KeyExpansion=: Using [Rijndael's key
59 schedule](https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard),
60 the keys are dynamically generated.
612. **AddRoundKey**: Each byte of the data is combined with this key
62 using bitwise xor.
633. **SubBytes**: This is followed by the substitution of each byte of
64 data.
654. **ShiftRows**: Then, the final three rows are shifted a certain
66 number of steps, dictated by the cipher.
675. **MixColumns**: After the rows have been shifted, the columns are
68 mixed and combined.
69
70This process does not necessarily stop after one full round. Steps 2
71through 5 will repeat for the number of rounds specified by the key.
72However, the final round excludes the MixColumns step. As you can see,
73this is a fairly complex process. One must have a solid understanding of
74general mathematic principles to fully understand how the sequence works
75(and to even attempt to find a weakness).
76
77According to research done by Bogdanov et al., it would take billions of
78years to brute force a 126-bit key with current hardware. Additionally,
79this brute force attack would require storing 2^88^ bits of data!
80However, there are a few different attacks that have been used to show
81vulnerabilities with the use of this technology. Side-channel attacks
82use inadvertent leaks of data from the hardware or software, which can
83allow attackers to obtain the key or run programs on a user's hardware.
84
85Please note that this is not something you should run out and try to
86implement in your `Hello, World!` app after only a few hours of
87research. While AES (basically all encryption methods) is extremely
88efficient in what it does, it takes a lot of time and patience to
89understand. If you're looking for something which currently implements
90AES, check out the [Legion of the Bouncy
91Castle](https://www.bouncycastle.org/documentation.html) for Java
92implementations of cryptographic algorithms.
93
94# Why Does Encryption Matter?
95
96There are limitless reasons to enable encryption at-rest or in-transit
97for various aspects of your digital life. You can research specific
98examples, such as [Australia passes new law to thwart strong
99encryption](https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/).
100However, I will simply list a few basic reasons to always enable
101encryption, where feasible:
102
1031. Privacy is a human right and is recognized as a national right in
104 some countries (e.g., [US Fourth
105 Amendment](https://www.law.cornell.edu/wex/fourth_amendment)).
1062. "Why not?" Encryption rarely affects performance or speed, so
107 there's usually not a reason to avoid it in the first place.
1083. Your digital identity and activity (texts, emails, phone calls,
109 online accounts, etc.) are extremely valuable and can result in
110 terrible consequences, such as identity theft, if leaked to other
111 parties. Encrypting this data prevents such leaks from ruining
112 lives.
1134. Wiping or factory-resetting does not actually wipe all data from the
114 storage device. There are methods to read data from the physical
115 disks/boards inside devices.
1165. Corporations, governments, and other nefarious groups/individuals
117 are actively looking for ways to collect personal information about
118 anyone they can. If someone's data is unencrypted, that person may
119 become a target due to the ease of data collection.
120
121****Read More:****
122
123- [Federal Information Processing Standards Publication
124 197](http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf)
content/blog/2019-01-07-useful-css.md added +199
@@ -0,0 +1,199 @@
1+++
2date = 2019-01-07
3title = "Useful CSS Snippets"
4description = "Explore some useful CSS snippets."
5+++
6
7# Introduction to CSS
8
9[CSS](https://en.wikipedia.org/wiki/CSS), the language used to markup
10HTML code and make it "pretty", is one of the most effective ways to
11increase the attractiveness of a website. It can also lead to increased
12user engagement, retention, and satisfaction. In fact, there are whole
13career fields are dedicated to the improvement of user experiences,
14known as UI design and UX design.
15
16Some web developers are used to the common CSS properties, such as
17element sizing, fonts, colors, etc., but are not as well versed in
18less-used properties and values such as `flexbox`,
19`clip-path`, and `transform`. This article will
20provide some insight into the less-used and unique CSS properties.
21
22# CSS Variables
23
24The first topic today is CSS variables. Variables are not often used by
25smaller developers. CSS variables allow you to give your website a
26well-defined structure, where you can easily reuse CSS properties
27throughout the project.
28
29You can use variables to define things, such as color palettes. Then,
30you can use these colors for backgrounds anywhere else in the HTML. This
31could be extended, where extra variables could be defined for
32`primary-text`, `quoted-text`, etc. Variables can
33also be used to define spacing (e.g. `32px` or
34`2rem`), which can then be applied to margins, padding, font
35sizes, and more.
36
37For example, here are some variables defined at the root of the website,
38which allows for any subsequent CSS rules to use those variables:
39
40```css
41:root {
42 --primary-color: black;
43 --secondary-color: white;
44}
45
46body {
47 background-color: var(--primary-color);
48 color: var(--secondary-color);
49}
50```
51
52# CSS Box Shadows
53
54Box shadows were once my mortal enemy. No matter how hard I tried, I
55just couldn't get them to work how I wanted. Because of this, my
56favorite discovery has been CSSMatic's [box shadow
57generator](https://www.cssmatic.com/box-shadow). It provides an
58excellent tool to generate box shadows using their simple sliders.
59Surprisingly, this is the reason I learned how box shadows work! You can
60use the sliders and watch how the CSS code changes in the image that is
61displayed. Through this, you should understand that the basic structure
62for box shadows is:
63
64```css
65box-shadow: inset horizontal vertical blur spread color;
66```
67
68Now, let's look at some basic examples! You can copy and paste the
69following code into a site like CodePen or your own HTML files. Feel
70free to play around with the code, experiment, and learn.
71
72****Box Shadow #1****
73
74```html
75<div class="shadow-examples">
76 <div class="box effect1">
77 <h3>Effect 1</h3>
78 </div>
79</div>
80```
81
82```css
83.box h3 {
84 text-align: center;
85 position: relative;
86 top: 80px;
87}
88.box {
89 width: 70%;
90 height: 200px;
91 background: #fff;
92 margin: 40px auto;
93}
94.effect1 {
95 box-shadow: 0 10px 6px -6px #777;
96}
97```
98
99****Box Shadow #2****
100
101```html
102<div class="shadow-examples">
103 <div class="box effect2">
104 <h3>Effect 2</h3>
105 </div>
106</div>
107```
108
109```css
110.box h3 {
111 text-align: center;
112 position: relative;
113 top: 80px;
114}
115.box {
116 width: 70%;
117 height: 200px;
118 background: #fff;
119 margin: 40px auto;
120}
121.effect2 {
122 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
123}
124```
125
126Try these box shadows out on your own and see how changing each shadow
127value works.
128
129# CSS Flexbox
130
131Now, let's move on to the best part of this article: flexbox. The
132flexbox is by far my favorite new toy. I originally stumbled across this
133solution after looking for more efficient ways of centering content
134horizontally AND vertically. I had used a few hack-ish methods before,
135but flexbox throws those out the window. The best part of it all is that
136flexbox is *dead simple*.
137
138Flexbox pertains to the parent div of any element. You want the parent
139to be the flexbox in which items are arranged to use the flex methods.
140It's easier to see this in action that explained, so let's see an
141example.
142
143****Flexbox****
144
145```html
146<div class="flex-examples">
147 <div class="sm-box">
148 <h3>1</h3>
149 </div>
150 <div class="sm-box">
151 <h3>2</h3>
152 </div>
153</div>
154```
155
156```css
157.flex-examples {
158 display: flex;
159 flex-wrap: wrap;
160 justify-content: flex-start;
161 align-items: center;
162 padding: 10px;
163 background-color: #f2f2f2;
164}
165.sm-box {
166 display: flex;
167 justify-content: center;
168 align-items: center;
169 width: 20%;
170 height: 100px;
171 background: #fff;
172 margin: 40px 10px;
173}
174```
175
176You may notice that we no longer need to use the `top`
177property for the `h3` elements in our code. This is because
178we set the display box to be a flex container for the small boxes, AND
179we made the small boxes flex containers for their elements (the h3
180tags). Flex boxes can be nested like this to center content that is
181inside centered content.
182
183For the example above, we designated the `justify-content`
184property to be `flex-start` so that the boxes stack from the
185left side of the screen. This property can be changed to
186`center` to make the boxes appear in the center of the
187screen.
188
189For an interactive example, [check out this
190CodePen](https://codepen.io/LandonSchropp/pen/KpzzGo) from
191[LandonScropp](https://codepen.io/LandonSchropp/). Resize the window
192with dice to see how they collapse and re-align.
193
194# Even More CSS
195
196For more inspiration, you can visit [CodePen](https://www.codepen.io),
197[Dribbble](https://dribbble.com), or [UI
198Movement](https://uimovement.com) to browse the collections of many
199amazing web designers.
content/blog/2019-09-09-audit-analytics.md added +237
@@ -0,0 +1,237 @@
1+++
2date = 2019-09-09
3title = "Data Analysis in Auditing"
4description = "Learn how to use data analysis in the world of auditing."
5+++
6
7# What Are Data Analytics?
8
9A quick aside before I dive into this post: `data analytics` is a
10vague term that has become popular in recent years. Think of a `data
11analytic` as the output of any data analysis you perform. For example,
12a pivot table or a pie chart could be a data analytic.
13
14[Data analysis](https://en.wikipedia.org/wiki/Data_analysis) is a
15process that utilizes statistics and other mathematical methods to
16discover useful information within datasets. This involves examining,
17cleaning, transforming, and modeling data so that you can use the data
18to support an opinion, create more useful viewpoints, and gain knowledge
19to implement into audit planning or risk assessments.
20
21One of the common mistakes that managers (and anyone new to the process)
22make is assuming that everything involved with this process is "data
23analytics". In fact, data analytics are only a small part of the
24process.
25
26See **Figure 1** for a more accurate representation of where data
27analysis sits within the full process. This means that data analysis
28does not include querying or extracting data, selecting samples, or
29performing audit tests. These steps can be necessary for an audit (and
30may even be performed by the same associates), but they are not data
31analytics.
32
33![The Intelligence
34Cycle](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/intelligence_cycle-min.png)
35
36# Current Use of Analytics in Auditing
37
38While data analysis has been an integral part of most businesses and
39departments for the better part of the last century, only recently have
40internal audit functions been adopting this practice. The internal audit
41function works exclusively to provide assurance and consulting services
42to the business areas within the firm (except for internal auditing
43firms who are hired by different companies to perform their roles).
44
45> Internal Auditing helps an organization accomplish its objectives by
46> bringing a systematic, disciplined approach to evaluate and improve
47> the effectiveness of risk management, control and governance
48> processes.
49>
50> - The IIA's Definition of Internal Audit
51
52Part of the blame for the slow adoption of data analysis can be
53attributed to the fact that internal auditing is strongly based on
54tradition and following the precedents set by previous auditors.
55However, there can be no progress without auditors who are willing to
56break the mold and test new audit techniques. In fact, as of 2018, [only
5763% of internal audit departments currently utilize data
58analytics](https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics)
59in North America. This number should be as close as possible to 100%. I
60have never been part of an audit that would not have benefited from data
61analytics.
62
63So, how do internal audit functions remedy this situation? It's
64definitely not as easy as walking into work on Monday and telling your
65Chief Audit Executive that you're going to start implementing analytics
66in the next audit. You need a plan and a system to make the analysis
67process as effective as possible.
68
69# The DELTA Model
70
71One of the easiest ways to experiment with data analytics and gain an
72understanding of the processes is to implement them within your own
73department. But how do we do this if we've never worked with analysis
74before? One of the most common places to start is to research some data
75analysis models currently available. For this post, we'll take a look
76at the DELTA model. You can take a look at ****Figure 2**** for a quick
77overview of the model.
78
79The DELTA model sets a few guidelines for areas wanting to implement
80data analytics so that the results can be as comprehensive as possible:
81
82- **Data**: Must be clean, accessible, and (usually) unique.
83- **Enterprise-Wide Focus**: Key data systems and analytical resources
84 must be available for use (by the Internal Audit Function).
85- **Leaders**: Must promote a data analytics approach and show the
86 value of analytical results.
87- **Targets**: Must be set for key areas and risks that the analytics
88 can be compared against (KPIs).
89- **Analysts**: There must be auditors willing and able to perform
90 data analytics or else the system cannot be sustained.
91
92![The Delta
93Model](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/delta-min.png)
94
95# Finding the Proper KPIs
96
97Once the Internal Audit Function has decided that they want to start
98using data analytics internally and have ensured they're properly set
99up to do so, they need to figure out what they will be testing against.
100Key Performance Indicators (KPIs) are qualitative or quantitative
101factors that can be evaluated and assessed to determine if the
102department is performing well, usually compared to historical or
103industry benchmarks. Once KPIs have been agreed upon and set, auditors
104can use data analytics to assess and report on these KPIs. This allows
105the person performing the analytics the freedom to express opinions on
106the results, whereas the results are ambiguous if no KPIs exist.
107
108It should be noted that tracking KPIs in the department can help ensure
109you have a rigorous Quality Assurance and Improvement Program (QAIP) in
110accordance with some applicable standards, such as IPPF Standard 1300.
111
112```{=org}
113#+BEING_QUOTE
114```
115The chief audit executive must develop and maintain a quality assurance
116and improvement program that covers all aspects of the internal audit
117activity.
118
119- IPPF Standard 1300
120
121```{=org}
122#+END_QUOTE
123```
124Additionally, IPPF Standard 2060 discusses reporting:
125
126> The chief audit executive must report periodically to senior
127> management and the board on the internal audit activity's purpose,
128> authority, responsibility, and performance relative to its plan and on
129> its conformance with the Code of Ethics and the Standards. Reporting
130> must also include significant risk and control issues, including fraud
131> risks, governance issues, and other matters that require the attention
132> of senior management and/or the board.
133>
134> - IPPF Standard 2060
135
136The hardest part of finding KPIs is to determine which KPIs are
137appropriate for your department. Since every department is different and
138has different goals, KPIs will vary drastically between companies. To
139give you an idea of where to look, here are some ideas I came up with
140when discussing the topic with a few colleagues.
141
142- Efficiency/Budgeting:
143 - Audit hours to staff utilization ratio (annual hours divided by
144 total annual work hours).
145 - Audit hours compared to the number of audits completed.
146 - Time between audit steps or to complete the whole audit. E.g.,
147 time from fieldwork completion to audit report issuance.
148- Reputation:
149 - The frequency that management has requested the services of the
150 IAF.
151 - Management, audit committee, or external audit satisfaction
152 survey results.
153 - Education, experience, certifications, tenure, and training of
154 the auditors on staff.
155- Quality:
156 - Number and frequency of audit findings. Assign monetary or
157 numerical values, if possible.
158 - Percentage of recommendations issued and implemented.
159- Planning:
160 - Percentage or number of key risks audited per year or per audit.
161 - Proportion of audit universe audited per year.
162
163# Data Analysis Tools
164
165Finally, to be able to analyze and report on the data analysis, auditors
166need to evaluate the tools at their disposal. There are many options
167available, but a few of the most common ones can easily get the job
168done. For example, almost every auditor already has access to Microsoft
169Excel. Excel is more powerful than most people give it credit for and
170can accomplish a lot of basic statistics without much work. If you
171don't know a lot about statistics but still want to see some of the
172more basic results, Excel is a great option.
173
174To perform more in-depth statistical analysis or to explore large
175datasets that Excel cannot handle, auditors will need to explore other
176options. The big three that have had a lot of success in recent years
177are Python, R, and ACL. ACL can be used as either a graphical tool
178(point and click) or as a scripting tool, where the auditor must write
179the scripts manually. Python and the R-language are solely scripting
180languages.
181
182The general trend in the data analytics environment is that if the tool
183allows you to do everything by clicking buttons or dragging elements,
184you won't be able to fully utilize the analytics you need. The most
185robust solutions are created by those who understand how to write the
186scripts manually. It should be noted that as the utility of a tool
187increases, it usually means that the learning curve for that tool will
188also be higher. It will take auditors longer to learn how to utilize
189Python, R, or ACL versus learning how to utilize Excel.
190
191# Visualization
192
193Once an auditor has finally found the right data, KPIs, and tools, they
194must report these results so that actions can be taken. Performing
195in-depth data analysis is only useful if the results are understood by
196the audiences of the data. The best way to create this understanding is
197to visualize the results of the data. Let's take a look at some of the
198best options to visualize and report the results you've found.
199
200Some of the most popular commercial tools for visualization are
201Microsoft PowerBI and Tableau Desktop. However, other tools exist such
202as JMP, Plotly, Qlikview, Alteryx, or D3. Some require commercial
203licenses while others are simply free to use. For corporate data, you
204may want to make sure that the tool does not communicate any of the data
205outside the company (such as cloud storage). I won't be going into
206depth on any of these tools since visualization is largely a subjective
207and creative experience, but remember to constantly explore new options
208as you repeat the process.
209
210Lastly, let's take a look at an example of data visualization. This
211example comes from a [blog post written by Kushal
212Chakrabarti](https://talent.works/2018/03/28/the-science-of-the-job-search-part-iii-61-of-entry-level-jobs-require-3-years-of-experience/)
213in 2018 about the percent of entry-level US jobs that require
214experience. **Figure 3** shows us an easy-to-digest picture of the data.
215We can quickly tell that only about 12.5% of entry-level jobs don't
216require experience.
217
218This is the kind of result that easily describes the data for you.
219However, make sure to include an explanation of what the results mean.
220Don't let the reader assume what the data means, especially if it
221relates to a complex subject. *Tell a story* about the data and why the
222results matter. For example, **Figure 4** shows a part of the
223explanation the author gives to illustrate his point.
224
225![Entry-Level
226Visualization](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example-min.png)
227
228![Visualization
229Explanation](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example_explanation-min.png)
230
231# Wrap-Up
232
233While this is not an all-encompassing program that you can just adopt
234into your department, it should be enough to get anyone started on the
235process of understanding and implementing data analytics. Always
236remember to continue learning and exploring new options as your
237processes grow and evolve.
content/blog/2019-12-03-the-ansoff-matrix.md added +205
@@ -0,0 +1,205 @@
1+++
2date = 2019-12-03
3title = "The Ansoff Matrix"
4description = "Learn about the Ansoff Matrix, a strategic management tool."
5+++
6
7# Overview
8
9As the world of business evolves, managers must approach business
10planning and strategy with a contemporary mindset. According to Dess,
11McNamara, Eisner, and Lee, managers must be willing to adapt to the
12modern business environment by going beyond "'incremental
13management', whereby they view their job as making a series of small,
14minor changes to improve the efficiency of the firm's operations"[^1].
15
16One reason that strategic management is crucial is because most
17businesses that fail in the United States each year fail due to a lack
18of strategic focus or direction[^2]. The rate of failure for businesses
19with poor strategies shows that strategic planning and management are
20crucial to a business's strength and longevity, injecting the critical
21factors of growth and direction into a company's business plan.
22
23One of the most significant strategic planning and management frameworks
24that companies can use is the [Ansoff
25Matrix](https://en.wikipedia.org/wiki/Ansoff_matrix). While this
26framework has unique purposes and use-cases, it can effectively help an
27organization grow and compete. Specifically, the Ansoff matrix is one of
28the most effective frameworks for companies who want to focus on
29increasing sales revenue or profitability[^3].
30
31This framework uses a two-by-two figure to show the four strategic
32options for companies to use in this framework: market penetration,
33market development, product development, and diversification (see
34**Figure 1**). The x-axis of the matrix focuses on the firm's markets
35and also determines if the firm is looking to enter new markets or
36innovate in its current markets. The y-axis of the matrix focuses on the
37firm's products and determines if the firm wants to pursue strategies
38around their existing products or explore new products.
39
40![The Ansoff Matrix by JaisonAbeySabu, Own work, CC BY-SA
413.0](https://img.cleberg.net/blog/20191203-the-ansoff-matrix/ansoff_matrix-min.png)
42
43# Strategic Options
44
45## Market Penetration
46
47The most straightforward strategy in the Ansoff matrix is to focus on
48existing products in existing markets, also known as market
49penetration[^4]. Companies such as Coca-Cola have used market
50penetration successfully by investing a lot of money to get further
51value out of their current markets. Coca-Cola does this by introducing
52new features such as Christmas-themed bottles, personal names on the
53bottles, and other marketing schemes.
54
55## Market Development
56
57Market development extends existing products into new markets in an
58attempt to increase the number of buyers. One interesting way that
59Coca-Cola used this strategy comes from the stigma that Diet Coke is a
60woman's drink[^5]. Coca-Cola introduced Coca-Cola Zero, which contained
61the same nutritional content as Diet Coke, but was packaged in a dark
62black can to appear more "manly"[^6].
63
64## Product Development
65
66Product development uses existing markets to introduce new products so
67that the firm can better meet customer needs[^7]. The extreme end of
68diversification is home to companies such as Johnson & Johnson, a
69healthcare company that has developed a business portfolio of more than
7060,000 different products[^8]. Johnson & Johnson's dedication to
71continuous diversification has led them to a balance sheet rating of
72"AAA", industry recognition for diversification, and increases in
73their investor dividends for 57 consecutive years[^9].
74
75## Related Diversification
76
77Diversification, the final strategy of the Ansoff Matrix, is more
78difficult than the others since it involves exploring both new markets
79and new products. Related diversification is a diversification strategy
80that closely relates to the firm's core business. Coca-Cola's best
81example of related diversification is its acquisition of Glaceau and
82Vitamin Water, which expanded their drinking lines of business[^10].
83
84## Unrelated Diversification
85
86Unrelated diversification is a diversification strategy that does not
87really relate to the firm's core business but still diversifies their
88business portfolio. A good example of this would be a coffee company who
89has decided to enter the market for bicycle sales. The main purpose of
90this strategy is to an extremely diverse company that will not go
91bankrupt if one market goes through difficult times. However, this
92requires a lot of independent skills and heavy investments since the
93company most likely cannot easily transfer knowledge between the markets
94they compete in.
95
96# Requirements for Success
97
98To use the Ansoff Matrix framework, managers need to formulate corporate
99goals and objectives. Without goals and direction, management frameworks
100do not present much practical utility. Further, the Ansoff Matrix
101requires the managers involved to make tactical decisions and create a
102path for the company to take toward their goals. Lastly, both the Ansoff
103Matrix needs to consider both internal and external perspectives
104throughout the strategy formulation process.
105
106One interesting probability is that companies will be using multiple
107strategic planning and management frameworks at the same time. While
108this may sound like it could crowd the management process, there are
109numerous reasons to do so. For example, the Ansoff Matrix and the
110Balanced Scorecard are relatively popular, and they cover entirely
111different parts of a company's strategy. Using the results from the
112Balanced Scorecard could inform a company of the potential product and
113market demands, such as from customer or supplier survey results, to
114help the company determine which Ansoff Matrix strategy to pursue.
115However, a combined approach at this level would require mature
116frameworks and focused managers who are able to strategize at a high
117level.
118
119Lastly, it should be noted that the author of the Ansoff matrix, Igor
120Ansoff, often used the term [paralysis by
121analysis](https://en.wikipedia.org/wiki/Analysis_paralysis) to explain
122the mistake of companies who overuse analysis and spend too much time
123planning. Companies need to understand the utility of a strategic
124management framework while ensuring that the company is poised to
125execute as efficiently as they have planned.
126
127# Footnotes
128
129[^1]:
130 ```example
131 Dess, G. G., McNamara, G., Eisner, A. B., Lee, S. H. (2019). Strategic
132 ```
133
134 management: Text & cases, ninth edition. New York, NY: McGraw-Hill
135 Education.
136
137[^2]:
138 ```example
139 Juneja, P. (n.d.). Benefits of strategic management. Management Study
140 ```
141
142 Guide. Retrieved from
143 <https://www.managementstudyguide.com/strategic-management-benefits.htm>.
144
145[^3]:
146 ```example
147 Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing
148 ```
149
150 Concepts. London: Palgrave.
151
152[^4]:
153 ```example
154 Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing
155 ```
156
157 Concepts. London: Palgrave.
158
159[^5]:
160 ```example
161 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
162 ```
163
164 Retrieved from
165 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
166
167[^6]:
168 ```example
169 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
170 ```
171
172 Retrieved from
173 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
174
175[^7]:
176 ```example
177 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
178 ```
179
180 Retrieved from
181 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
182
183[^8]:
184 ```example
185 Lemke, T. (2019). The most diversified companies in the stock market. The
186 ```
187
188 balance. Retrieved from
189 <https://www.thebalance.com/the-most-diversified-companies-in-the-stock-market-4169730>.
190
191[^9]:
192 ```example
193 Johnson & Johnson. (2018). 2018 Investor Fact Sheet. [PDF file]. Retrieved
194 ```
195
196 from
197 [http://www.investor.jnj.com/_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000](http://www.investor.jnj.com/\_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000).
198
199[^10]:
200 ```example
201 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
202 ```
203
204 Retrieved from
205 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
content/blog/2019-12-16-password-security.md added +126
@@ -0,0 +1,126 @@
1+++
2date = 2019-12-16
3title = "Password Security"
4description = "Password security basics."
5+++
6
7# Users
8
9## Why Does It Matter?
10
11Information security, including passwords and identities, has become one
12of the most important digital highlights of the last decade. With
13[billions of people affected by data breaches each
14year](https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/),
15there's a greater need to introduce strong information security
16systems. If you think you've been part of a breach, or you want to
17check and see, you can use [Have I Been
18Pwned](https://haveibeenpwned.com/) to see if your email has been
19involved in any public breaches. Remember that there's a possibility
20that a company experienced a breach and did not report it to anyone.
21
22## How Do I Protect Myself?
23
24The first place to start with any personal security check-up is to
25gather a list of all the different websites, apps, or programs that
26require you to have login credentials. Optionally, once you know where
27your information is being stored, you can sort the list from the
28most-important items such as banks or government logins to less
29important items such as your favorite meme site. You will want to ensure
30that your critical logins are secure before getting to the others.
31
32Once you think you have a good idea of all your different authentication
33methods, I recommend using a password manager such as
34[Bitwarden](https://bitwarden.com/). Using a password manager allows you
35to automatically save your logins, create randomized passwords, and
36transfer passwords across devices. However, you'll need to memorize
37your "vault password" that allows you to open the password manager.
38It's important to make this something hard to guess since it would
39allow anyone who has it to access every password you've stored in
40there.
41
42Personally, I recommend using a
43[passphrase](https://en.wikipedia.org/wiki/Passphrase) instead of a
44[password](https://en.wikipedia.org/wiki/Password) for your vault
45password. Instead of using a string of characters (whether random or
46simple), use a phrase and add in symbols and a number. For example, your
47vault password could be `Racing-Alphabet-Gourd-Parrot3`. Swap
48the symbols out for whichever symbol you want, move the number around,
49and fine-tune the passphrase until you are confident that you can
50remember it whenever necessary.
51
52Once you've stored your passwords, make sure you continually check up
53on your account and make sure you aren't following bad password
54practices. Krebs on Security has a great [blog post on password
55recommendations](https://krebsonsecurity.com/password-dos-and-donts/).
56Any time that a data breach happens, make sure you check to see if you
57were included, and if you need to reset any account passwords.
58
59# Developers
60
61## What Are the Basic Requirements?
62
63When developing any password-protected application, there are a few
64basic rules that anyone should follow even if they do not follow any
65official guidelines such as NIST. The foremost practice is to require
66users to use passwords that are at least 8 characters and cannot easily
67be guessed. This sounds extremely simple, but it requires quite a few
68different strategies. First, the application should check the potential
69passwords against a dictionary of insecure passwords such
70`password`, `1234abc`, or
71`application_name`.
72
73Next, the application should offer guidance on the strength of passwords
74being entered during enrollment. Further, NIST officially recommends
75**not** implementing any composition rules that make passwords hard to
76remember (e.g. passwords with letters, numbers, and special characters)
77and instead encouraging the use of long pass phrases which can include
78spaces. It should be noted that to be able to keep spaces within
79passwords, all unicode characters should be supported, and passwords
80should not be truncated.
81
82## What Does NIST Recommend?
83
84The National Institute of Standards and Technology
85([NIST](https://www.nist.gov)) in the US Department of Commerce
86regularly publishes information around information security and digital
87identity guidelines. Recently, NIST published [Special Publication
88800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html): Digital
89Identity Guidelines and Authentication and Lifecycle Management.
90
91> A Memorized Secret authenticator - commonly referred to as a password
92> or, if numeric, a PIN - is a secret value intended to be chosen and
93> memorized by the user. Memorized secrets need to be of sufficient
94> complexity and secrecy that it would be impractical for an attacker to
95> guess or otherwise discover the correct secret value. A memorized
96> secret is something you know.
97>
98> - NIST Special Publication 800-63B
99
100NIST offers a lot of guidance on passwords, but I'm going to highlight
101just a few of the important factors:
102
103- Require passwords to be a minimum of 8 characters (6 characters if
104 randomly generated and be generated using an approved random bit
105 generator).
106- Compare potential passwords against a list that contains values
107 known to be commonly-used, expected, or compromised.
108- Offer guidance on password strength, such as a strength meter.
109- Implement a rate-limiting mechanism to limit the number of failed
110 authentication attempts for each user account.
111- Do not require composition rules for passwords and do not require
112 passwords to be changed periodically (unless compromised).
113- Allow pasting of user identification and passwords to facilitate the
114 use of password managers.
115- Allow users to view the password as it is being entered.
116- Use secure forms of communication and storage, including salting and
117 hashing passwords using a one-way key derivation function.
118
119NIST offers further guidance on other devices that require specific
120security policies, querying for passwords, and more. All the information
121discussed so far comes from [NIST
122SP800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html) but NIST
123offers a lot of information on digital identities, enrollment, identity
124proofing, authentication, lifecycle management, federation, and
125assertions in the total [NIST SP800-63 Digital Identity
126Guidelines](https://pages.nist.gov/800-63-3/).
content/blog/2020-01-25-linux-software.md added +275
@@ -0,0 +1,275 @@
1+++
2date = 2020-01-25
3title = "Linux Software"
4description = "A look at some useful Linux applications."
5+++
6
7# GUI Applications
8
9## Etcher
10
11![](https://img.cleberg.net/blog/20200125-the-best-linux-software/etcher.png)
12
13[Etcher](https://www.balena.io/etcher/) is a quick and easy way to burn
14ISO images to CDs and USB devices. There are two different ways you can
15install this program. First, you can navigate to the [official
16website](https://www.balena.io/etcher/) and download the AppImage file,
17which can run without installation.
18
19However, AppImage files are not executable by default, so you'll either
20need to right-click to open the properties of the file and click the
21"Allow executing file as program" box in the Permissions tab or use
22the following command:
23
24```sh
25chmod u+x FILE_NAME
26```
27
28If you don't like AppImage files or just prefer repositories, you can
29use the following commands to add the author's repository and install
30it through the command-line only.
31
32First, you'll have to echo the repo and write it to a list file:
33
34```sh
35echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
36```
37
38Next, add the application keys to Ubuntu's keyring:
39
40```sh
41sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
42```
43
44Finally, update the repositories and install the app.
45
46```sh
47sudo apt update && sudo apt install balena-etcher-electron
48```
49
50Using Arch, Manjaro, or another distro using the AUR? Use this command
51instead:
52
53```sh
54sudo pacman -S etcher
55```
56
57## Atom
58
59![](https://img.cleberg.net/blog/20200125-the-best-linux-software/atom.png)
60
61[Atom](https://atom.io) is the self-proclaimed "hackable text editor
62for the 21st century". This text editor is made by GitHub, [now owned
63by
64Microsoft](https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/),
65and has some of the best add-ons available to customize the layout and
66abilities of the app.
67
68First, add the Atom repository to your sources.
69
70```sh
71sudo add-apt-repository ppa:webupd8team/atom
72```
73
74Next, update your package listings and install atom.
75
76```sh
77sudo apt update && sudo apt install atom
78```
79
80If you have issues updating your packages with the Atom repository,
81you'll need to use the snap package described below instead of the
82repository. To remove the repository we just added, use this command:
83
84```sh
85sudo add-apt-repository -r ppa:webupd8team/atom
86```
87
88You can also install Atom as a snap package, but it must be installed
89with the `--classic` flag. A [full explanation is
90available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement)
91if you'd like to read more about why you need the classic flag.
92
93```sh
94snap install atom --classic
95```
96
97Using Arch, Manjaro, or another distro using the AUR? Use this command
98instead:
99
100```sh
101sudo pacman -S atom
102```
103
104---
105
106## Visual Studio Code
107
108![](https://img.cleberg.net/blog/20200125-the-best-linux-software/vscode.png)
109
110[Visual Studio Code](https://code.visualstudio.com) is yet another
111fantastic choice for programming on Linux, especially if you need those
112extra add-ons to spice up your late-night coding sessions. The theme
113used in the screenshot is
114[Mars](https://marketplace.visualstudio.com/items?itemName=EliverLara.mars)
115by theme creator [Eliver Lara](https://github.com/EliverLara), who makes
116a ton of great themes for VS Code, Atom, and various Linux desktop
117environments.
118
119To install VS Code, you'll need to download the `.deb` file from the
120official website. Once you've downloaded the file, either double-click
121it to install through the Software Center or run the following command:
122
123```sh
124sudo dpkg -i FILE_NAME.deb
125```
126
127You can also install VS Code as a snap package, but it must be installed
128with the `--classic` flag. A [full explanation is
129available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement)
130if you'd like to read more about why you need the classic flag.
131
132```sh
133snap install code --classic
134```
135
136Using Arch, Manjaro, or another distro using the AUR? Use these commands
137instead:
138
139```sh
140sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
141```
142
143## GNOME Tweaks
144
145![](https://img.cleberg.net/blog/20200125-the-best-linux-software/gnome-tweaks.png)
146
147[Gnome Tweaks](https://gitlab.gnome.org/GNOME/gnome-tweaks) is the
148ultimate tool to use if you want to customize your GNOME desktop
149environment. This is how you can switch application themes (GTK), shell
150themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you
151just need to install the official package.
152
153```sh
154sudo apt install gnome-tweaks
155```
156
157If you've installed Manjaro or Arch with Gnome, you should have the
158tweak tool pre-installed. If you're on Fedora, this tool is available
159as an official package:
160
161```sh
162sudo dnf install gnome-tweaks
163```
164
165## Steam
166
167![](https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png)
168
169[Steam](https://steampowered.com) is one of the most popular gaming
170libraries for computers and is one of the main reasons that many people
171have been able to switch to Linux in recent years, thanks to Steam
172Proton, which makes it easier to play games not officially created for
173Linux platforms.
174
175To install Steam on Ubuntu, you just need to install the official
176package.
177
178```sh
179sudo apt install steam-installer
180```
181
182For Arch-based systems, you'll simply need to install the
183`steam` package. However, this requires that you enable the
184`multilib` source. To do so, use the following command:
185
186```sh
187sudo nano /etc/pacman.conf
188```
189
190Now, scroll down and uncomment the `multilib` section.
191
192```config
193# Before:
194#[multilib]
195#Include = /etc/pacman.d/mirrorlist
196
197# After:
198[multilib]
199Include = /etc/pacman.d/mirrorlist
200```
201
202Finally, install the program:
203
204```sh
205sudo pacman -S steam
206```
207
208[Problem Launching Steam Games? Click
209Here.](./2020-01-26-steam-on-ntfs-drives.html)
210
211# Command-Line Packages
212
213## neofetch
214
215![](https://img.cleberg.net/blog/20200125-the-best-linux-software/neofetch.png)
216
217[Neofetch](https://github.com/dylanaraps/neofetch) is a customizable
218tool used in the command-line to show system information. This is
219exceptionally useful if you want to see your system's information
220quickly without the clutter of some resource-heavy GUI apps.
221
222This is an official package if you're running Ubuntu 17.04 or later, so
223simply use the following command:
224
225```sh
226sudo apt install neofetch
227```
228
229If you're running Ubuntu 16.10 or earlier, you'll have to use a series
230of commands:
231
232```sh
233sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
234```
235
236Using Arch, Manjaro, or another distro using the AUR? Use this command
237instead:
238
239```sh
240sudo pacman -S neofetch
241```
242
243## yt-dlp
244
245![](https://img.cleberg.net/blog/20200125-the-best-linux-software/yt-dlp.png)
246
247[yt-dlp](https://github.com/yt-dlp/yt-dlp) is an extremely handy
248command-line tool that allows you to download video or audio files from
249various websites, such as YouTube. There are a ton of different options
250when running this package, so be sure to run `yt-dlp --help` first to
251look through everything you can do (or give up and search for the best
252config online).
253
254While this shouldn't be a problem for most users, yt-dlp requires
255Python 2.6, 2.7, or 3.2+ to work correctly, so install Python if you
256don't have it already. You can check to see if you have Python
257installed by running:
258
259```sh
260python -V
261```
262
263To get the youtube-dl package, simply curl the URL and output the
264results.
265
266```sh
267sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
268```
269
270Finally, make the file executable so that it can be run from the
271command-line.
272
273```sh
274sudo chmod a+rx /usr/local/bin/yt-dlp
275```
content/blog/2020-01-26-steam-on-ntfs.md added +95
@@ -0,0 +1,95 @@
1+++
2date = 2020-01-26
3title = "Linux Gaming Tweak: Steam on NTFS Drives"
4description = "Learn how to fix Steam NTFS issues on Linux."
5+++
6
7# Auto-Mount Steam Drives
8
9![](https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png)
10
11If you want to see how to install Steam on Linux, see my other post:
12[Linux Software](/blog/linux-software/).
13
14Are you having trouble launching games, even though they've installed
15correctly? This may happen if you're storing your games on an
16NTFS-formatted drive. This shouldn't be an issue if you're storing
17your games on the same drive that Steam is on, but some gamers prefer to
18put Steam on their main drive and game files on another SSD or HDD.
19
20To fix this problem, you'll need to try a few things. First, you'll
21need to install the `ntfs-3g` package, which is meant for
22better interoperability with Linux.
23
24```sh
25sudo apt install ntfs-3g
26```
27
28Next, you should set up the `/etc/fstab` file to
29automatically mount your drives on boot. To automatically mount your
30drives when the computer boots up, you'll have to create the folders
31you want to mount your drive to first. I store mine in the
32`/mnt` folder using names that I'll recognize, but you can
33create your folders wherever you want.
34
35```sh
36mkdir /path/to/folder
37```
38
39For example:
40
41```sh
42mkdir /mnt/steam_library
43```
44
45To automatically mount drives upon system boot, you will need to collect
46a few items. The UUID is the identification number connected to
47whichever drive you're using to store Steam games.
48
49Drives are usually labeled similar to `/dev/nvme0n1p1` or
50`/dev/sda1`, so you'll need to find the line in the output
51of the command below that correlates to your drive and copy the UUID
52over to the `/etc/fstab` file.
53
54```sh
55sudo blkid | grep UUID=
56```
57
58Next, you'll need your `uid` and `gid`. To find
59these, run the following command:
60
61```sh
62id -u && id -g
63```
64
65Now that you have collected the necessary information, open the
66`/etc/fstab` file:
67
68```sh
69sudo nano /etc/fstab
70```
71
72Each drive you want to mount on boot should have its own line in the
73`/etc/fstab` file that looks similar to this:
74
75```config
76UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
77```
78
79Now all you need to do is unmount your drive and re-mount it. You can
80unmount the drive by doing this (be sure to use the correct drive name
81here):
82
83```sh
84sudo umount /dev/sdxX
85```
86
87You can re-mount all your drives by executing the following:
88
89```sh
90sudo mount -a
91```
92
93If you don't know what your drive name is, or you're nervous about
94unmounting and re-mounting, simply reboot your computer, and it will be
95done for you automatically.
content/blog/2020-02-09-cryptography-basics.md added +178
@@ -0,0 +1,178 @@
1+++
2date = 2020-02-09
3title = "Cryptography Basics"
4description = "Learn about the basics of cryptography."
5+++
6
7# Similar Article Available
8
9If you haven't already, feel free to read my post on [AES
10Encryption](/blog/aes-encryption/).
11
12# What is Cryptography?
13
14In layman's terms, cryptography is a process that can change data from
15a readable format into an unreadable format (and vice-versa) through a
16series of processes and secrets. More technically, this is the Internet
17Security Glossary's definition:
18
19> [Cryptography is] the mathematical science that deals with
20> transforming data to render its meaning unintelligible (i.e., to hide
21> its semantic content), prevent its undetected alteration, or prevent
22> its unauthorized use. If the transformation is reversible,
23> cryptography also deals with restoring encrypted data to an
24> intelligible form.
25>
26> - [Internet Security Glossary
27> (2000)](https://tools.ietf.org/html/rfc2828)
28
29Cryptography cannot offer protection against the loss of data; it simply
30offers encryption methods to protect data at-rest and data in-traffic.
31At a high-level, encrypted is when plaintext data is encrypted to
32ciphertext (a secure form of text that cannot be understood unless
33decrypted back to plaintext). The encryption process is completed
34through the use of a mathematical function that utilizes one or more
35values called keys to encrypt or decrypt the data.
36
37# Key Elements of Cryptographic Systems
38
39To create or evaluate a cryptographic system, you need to know the
40essential pieces to the system:
41
42- **Encryption Algorithm (Primitive):** A mathematical process that
43 encrypts and decrypts data.
44- **Encryption Key:** A string of bits used within the encryption
45 algorithm as the secret that allows successful encryption or
46 decryption of data.
47- **Key Length (Size):** The maximum number of bits within the
48 encryption key. It's important to remember that key size is
49 regulated in many countries.
50- **Message Digest:** A smaller, fixed-size bit string version of the
51 original message. This is practically infeasible to reverse, which
52 is why it's commonly used to verify integrity.
53
54# Symmetric Systems (Secret Key Cryptography)
55
56Symmetric cryptography utilizes a secret, bidirectional key to perform
57both encryption and decryption of the data. The most common
58implementation of symmetric cryptography is the Advanced Encryption
59Standard, which uses keys that are 128 bits to 256 bits in size. This
60standard came after the National Institute of Standards and Technology
61(NIST) decided to retire the Data Encryption Standard (DES) in 2001.
62
63Since brute force attacks strongly correlate with key length, the 56-bit
64key length of DES was considered insecure after it was publicly broken
65in under 24 hours. However, there is a modern implementation of DES
66called Triple DES where the DES method is applied three times to each
67data block.
68
69The main advantages to symmetric systems are the ease of use, since only
70one key is required for both encryption and decryption, and the
71simplicity of the algorithms. This helps with bulk data encryption that
72may unnecessarily waste time and power using asymmetric systems.
73
74However, symmetric systems have disadvantages to keep in mind. Since the
75key is private, it can be difficult to safely distribute keys to
76communication partners. Additionally, the key cannot be used to sign
77messages since it's necessary to keep the key private.
78
79# Asymmetric Systems (Public Key Cryptography)
80
81Asymmetric cryptography utilizes two keys within the system: a secret
82key that is privately-held and a public key that can be distributed
83freely. The interesting aspect of asymmetric cryptography is that either
84key can be used to encrypt the data, there's no rule that dictates
85which key must be used for encryption. Once one key is used to encrypt
86the data, only the other key can be used to decrypt the data. This means
87that if the private key encrypts the data, only the public key can
88decrypt the data.
89
90An advantage of this system is that if you successfully decrypt data
91using one of the keys, you can be sure of the sender since only the
92other key could have encrypted the data.
93
94One of the major implementations of an asymmetric system is a digital
95signature. A digital signature can be generated using the sender's
96private key, or a one-way hash function and is used to provide assurance
97for the integrity and authenticity of the message. A couple common
98message digest algorithms are SHA-256 and SHA-512, which securely
99compress data and produce a 128-bit message digest.
100
101It should be noted that man-in-the-middle attacks are one of the risks
102with digital signatures and public keys. To combat this, applications
103often use a public key infrastructure (PKI) to independently
104authenticate the validity of signatures and keys.
105
106Due to the large key size and [inefficient mathematical
107functions](https://crypto.stackexchange.com/a/591) of asymmetric
108encryption, elliptical curve cryptography (ECC) is often used to
109increase security while using fewer resources.
110
111# Applications of Cryptographic Systems
112
113There are quite a few implementations of cryptographic systems around
114the world. Here are a few popular examples:
115
116**Transport Layer Security (TLS):** One of the most famous cryptographic
117solutions created is TLS, a session-layered or connection-layered
118internet protocol that allows for secure communications between browsers
119and servers. Using handshakes, peer negotiation, and authentication
120allows TLS to prevent eavesdropping and malicious transformation of
121data. The major reason for TLS popularity is that a major vulnerability
122was found in the SSL protocol in 2014. Instead of SSL, TLS can be used
123with HTTP to form HTTPS and is the preferred method for modern web
124development due to its increased security.
125
126**Secure Hypertext Transfer Protocol (HTTPS):** An application layer
127protocol that allows for secure transport of data between servers and
128web clients. One of the unique parts of HTTPS is that it uses a secured
129port number instead of the default web port address.
130
131**Virtual Private Network (VPN):** VPNs are made to securely extend a
132private network across public networks by utilizing an encrypted layered
133tunneling protocol paired with an authentication method, such as
134usernames and passwords. This technology originally allowed remote
135employees to access their company's data but have evolved into one of
136the top choices for anyone who wishes to mask their sensitive personal
137data.
138
139**Internet Protocol Security (IPSec):** This protocol suite facilitates
140communication between two or more hosts or subnets by authenticating and
141encrypting the data packets. IPSec is used in a lot of VPNs to establish
142the VPN connection through the transport and tunnel mode encryption
143methods. IPSec encrypts just the data portion of packets in the
144transport methods, but it encrypts both the data and headers in the
145tunnel method (introducing an additional header for authentication).
146
147**Secure Shell (SSH):** SSH is another network protocol used to protect
148network services by authenticating users through a secure channel. This
149protocol is often used for command-line (shell) functions such as remote
150shell commands, logins, and file transfers.
151
152**Kerberos:** Developed by MIT, Kerberos is a computer-network
153authentication protocol that works on the basis of tickets to allow
154nodes communicating over a non-secure network to prove their identity to
155one another securely. This is most commonly used in business
156environments when used as the authentication and encryption method for
157Windows Active Directory (AD).
158
159# Cybersecurity Controls
160
161If you're someone who needs solutions on how to control risks
162associated with utilizing a crytograhpic system, start with a few basic
163controls:
164
165- **Policies:** A policy on the use of cryptographic controls for
166 protection of information is implemented and is in accordance with
167 organizational objectives.
168- **Key management:** A policy on the use, protection and lifetime of
169 cryptographic keys is implemented through the entire application
170 lifecycle.
171- **Key size:** The organization has researched the optimal key size
172 for their purposes, considering national laws, required processing
173 power, and longevity of the solution.
174- **Algorithm selection:** Implemented algorithms are sufficiently
175 appropriate for the business of the organization, robust, and align
176 with recommended guidelines.
177- **Protocol configuration:** Protocols have been reviewed and
178 configured suitable to the purpose of the business.
content/blog/2020-03-25-session-messenger.md added +136
@@ -0,0 +1,136 @@
1+++
2date = 2020-03-25
3title = "Session Private Messenger"
4description = "Exploring the Session Private Messenger application."
5+++
6
7# Privacy Warning
8
9The company behind Session (Loki Foundation) is from Australia. If you
10didn't know, Australia has introduced
11[legislation](https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf)
12mandating companies comply with government requests to build backdoor
13access into applications. For more information, read my article on [AES
14Encryption](./2020-01-25-aes-encryption.html).
15
16# About Session
17
18[Session](https://getsession.org) is a private, cross-platform messaging
19app from the [Loki Foundation](https://loki.foundation). As someone who
20has spent years looking for quality alternatives to major messaging
21apps, I was excited when I first heard about Session. Reading through
22[Session's white paper](https://arxiv.org/pdf/2002.04609.pdf), you can
23learn the technologies behind the Session app. Part of the security of
24Session comes from the Signal protocol, which was forked as the origin
25of Session.
26
27> Session is an end-to-end encrypted messenger that removes sensitive
28> metadata collection, and is designed for people who want privacy and
29> freedom from any forms of surveillance.
30
31In general, this app promises security through end-to-end encryption,
32decentralized onion routing, and private identities. The biggest change
33that the Loki Foundation has made to the Signal protocol is removing the
34need for a phone number. Instead, a random identification string is
35generated for any session you create. This means you can create a new
36session for each device if you want to, or link new devices with your
37ID.
38
39Since Session's website and white paper describe the details of
40Session's security, I'm going to focus on using the app in this post.
41
42# Features
43
44Since most people are looking for an alternative to a popular chat app,
45I am going to list out the features that Session has so that you are
46able to determine if the app would suit your needs:
47
48- Multiple device linking (via QR code or ID)
49- App locking via device screen lock, password, or fingerprint
50- Screenshot blocking
51- Incognito keyboard
52- Read receipts and typing indicators
53- Mobile notification customization
54- Old message deletion and conversation limit
55- Backups
56- Recovery phrase
57- Account deletion, including ID, messages, sessions, and contacts
58
59# Downloads
60
61I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and
62iOS 15. All apps have worked well without many issues.
63
64Below is a brief overview of the Session app on Linux. To get this app,
65you'll need to go to the [Downloads](https://getsession.org/download/)
66page and click to link to the operating system you're using.
67
68For Linux, it will download an AppImage that you'll need to enable with
69the following command:
70
71```sh
72sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
73```
74
75![Session Download
76Options](https://img.cleberg.net/blog/20200325-session-private-messenger/session_downloads.png)
77
78# Creating an Account
79
80Once you've installed the app, simply run the app and create your
81unique Session ID. It will look something like this:
82`05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608`.
83
84You'll need to set a display name and, optionally, a password. If you
85set a password, you will need to enter it every time you open the app.
86
87![Session Login
88(Linux)](https://img.cleberg.net/blog/20200325-session-private-messenger/session_linux_login.png)
89
90![Session Login
91(macOS)](https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_login.png)
92
93![Password
94Authentication](https://img.cleberg.net/blog/20200325-session-private-messenger/session_password_authentication.png)
95
96# Start Messaging
97
98Once you've created your account and set up your profile details, the
99next step is to start messaging other people. To do so, you'll need to
100share your Session ID with other people. From this point, it's fairly
101straightforward and acts like any other messaging app, so I won't dive
102into much detail here.
103
104## macOS
105
106![macOS
107Conversations](https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_conversations.png)
108
109One key feature to note is that the desktop application now provides a
110helpful pop-up box explaining the process that Session uses to hide your
111IP address:
112
113![IP Address Help
114Box](https://img.cleberg.net/blog/20200325-session-private-messenger/session_ip.png)
115
116## iOS
117
118The mobile app is quite simple and effective, giving you all the
119standard mobile messaging options you'd expect.
120
121![iOS
122App](https://img.cleberg.net/blog/20200325-session-private-messenger/session_ios.png)
123
124# Potential Issues
125
126I've discovered one annoying issue that would prevent from using this
127app regularly. On a mobile device, there have been issues with receiving
128messages on time. Even with battery optimization disabled and no network
129restrictions, Session notifications sometimes do not display until I
130open the app or the conversation itself and wait a few moments. This is
131actually one of the reasons I stopped using Signal (this seems fixed as
132of my updates in 2021/2022, so I wouldn't worry about this issue
133anymore).
134
135Looking for another messenger instead of Session? I recommend Signal,
136Matrix, and IRC.
content/blog/2020-05-03-homelab.md added +165
@@ -0,0 +1,165 @@
1+++
2date = 2020-05-03
3title = "An Inside Look at My Homelab"
4description = "A retrospective on the first iteration of my home lab."
5+++
6
7# What is a Homelab?
8
9Starting as a developer, I have largely stayed away from hardware-based
10hobbies (other than building a gaming desktop). However, as the
11quarantine for COVID-19 stretches out further and further, I found
12myself bored and in search of new hobbies. After spending the last few
13months browsing the [r/homelab](https://www.reddit.com/r/homelab/)
14subreddit, I decided it was time to jump in and try things out for
15myself.
16
17Since I am a beginner and just recently graduated from college,
18everything I've done so far in my homelab is fairly low-budget.
19
20# Hardware
21
22![HomeLab
23Diagram](https://img.cleberg.net/blog/20200503-homelab/homelab-min.png)
24
25## Raspberry Pi 4
26
27Luckily, I had actually purchased a [Raspberry Pi
284](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) before
29the quarantine started so that I could try to keep Plex Media Center
30running, even while my desktop computer was turned off. I started here,
31using the Pi to hold Plex and Pi-hole until I grew tired with the slow
32performance.
33
34Here are the specifications for the Pi 4:
35
36- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
37- 4GB LPDDR4-3200 SDRAM
38- Gigabit Ethernet
39- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
40- 64 GB MicroSD Card
41
42## Dell Optiplex 5040
43
44Since I wasn't happy with the Pi as my main server, I turned to
45Craigslist. I know a lot of other homelabbers use Ebay, but I can't
46seem to ever trust it enough to purchase items on there. So I used
47Craigslist and found a Dell Optiplex 5040 desktop computer on sale for
48\$90. While this computer might be underpowered, it was one of the few
49computers under \$100 that was available during quarantine.
50
51Here are the specifications for the Dell Optiplex 5040:
52
53- Intel Core i3 6100
54- 8GB RAM DDR3
55- Intel HD Graphics
56- Gigabit Ethernet
57- 500GB Hard Drive
58
59While this hardware would be awful for a work computer or a gaming rig,
60it turned out to be wonderful for my server purposes. The only
61limitation I have found so far is the CPU. The i3-6100 only has enough
62power for a single 4k video transcode at a time. I haven't tested more
63than three 1080p streams at a time, but the maximum amount of streams
64I've ever actually used is two.
65
66## WD easystore 10TB & 8TB
67
68Application storage and temporary files are stored on the internal hard
69drive of the server, but all media files (movies, tv, games, books, etc)
70are stored externally on my WD easystore hard drive. Creating auto-boot
71configurations in the `/etc/fstab` file on my server allows
72the hard drives to automatically mount whenever I need to restart my
73server.
74
75> Update: In March 2022, I shucked the hard drives out of their external
76> cases, put some Kapton tape on the third power pin to prevent power
77> shutdowns, and stuck them inside my server tower using internal SATA
78> cables.
79
80## Netgear Unmanaged Switch
81
82To manage all the ethernet cords used by my homelab, my desktop, and my
83living room media center, I purchased an 8-port gigabit ethernet switch
84for \$50 at my local computer store. This is probably much more than I
85should have spent on an unmanaged switch, but I am comfortable with the
86choice.
87
88## TP-Link Managed Switch
89
90Since I use the unmanaged switch to group all living room devices
91together, I use the managed switch to configure VLANs and secure my
92network.
93
94## Arris TM1602A Modem & Sagecom Fast 5280 Router
95
96My default modem and router, provided by my ISP, are fairly standard.
97The Arris modem supports DOCSIS 3.0, which is something that I
98definitely wanted as a minimum. The Sagecom router is also standard, no
99fancy bells or whistles. However, it does support DHCP and DHCPv6, which
100is something you can use to route all household traffic through a
101pi-hole or firewall.
102
103## TP-Link EAP
104
105In order to gain better control over the network, I use my own wireless
106access point instead of the one included in the Sagecom router above.
107Now I can control and organize all of my ethernet connections through
108the VLANs on the managed switch and wireless connections through the
109VLANS on the EAP.
110
111## Generic Printer
112
113The last piece to my homelab is a standard wireless printer. Nothing
114special here.
115
116# Software
117
118## Ubuntu Server 20.04
119
120While the 20.04 version of Ubuntu was just released, I always like to
121experiment with new features (and I don't mind breaking my system - it
122just gives me more experience learning how to fix things). So, I have
123Ubuntu Server 20.04 installed on the Dell Optiplex server and Ubuntu
124Server 19.10 installed on the Raspberry Pi. Once I find an acceptable
125use for the Pi, I will most likely switch the operating system.
126
127## Docker
128
129I am *very* new to Docker, but I have had a lot of fun playing with it
130so far. Docker is used to create containers that can hold all the
131contents of a system without interfering with other software on the same
132system. So far, I have successfully installed pi-hole, GitLab, Gogs, and
133Nextcloud in containers. However, I opted to delete all of those so that
134I can reconfigure them more professionally at a later time.
135
136## Plex Media Server
137
138Plex is a media center software that allows you to organize your movies,
139TV shows, music, photos, and videos automatically. It will even download
140metadata for you so that you can easily browse these collections.
141
142## Pi-hole
143
144Pi-hole is an alternative ad-blocker that runs at the DNS level,
145allowing you to block traffic when it hits your network, so that you can
146reject any traffic you deem to be bad. Pi-hole uses blacklists and
147whitelists to decide which traffic block and, luckily, there are a lot
148of pre-made lists out there on Reddit, GitHub, etc.
149
150## Nextcloud
151
152While I had trouble with the Docker version of Nextcloud, I was very
153successful when setting up the snap version. Using this, I was able to
154map Nextcloud to a subdomain of a domain I own in Namecheap.
155Additionally, Nextcloud has an integration with Let's Encrypt that
156allows me to issue certificates automatically to any new domain I
157authorize.
158
159## Webmin
160
161To monitor my servers, and the processes running on them, I use the
162Webmin dashboard. This was fairly painless to set up, and I currently
163access it straight through the server's IP address. In the future, I
164will be looking to configure Webmin to use a custom domain just like
165Nextcloud.
content/blog/2020-05-19-customizing-ubuntu.md added +210
@@ -0,0 +1,210 @@
1+++
2date = 2020-05-19
3title = "Beginner's Guide: Customizing Ubuntu"
4description = "A beginner's guide to customizing the Ubuntu operating system."
5+++
6
7# More Information
8
9For inspiration on designing your *nix computer, check out the
10[r/unixporn](https://libredd.it/r/unixporn) subreddit!
11
12# Customizing Ubuntu
13
14New to Linux and want to add a personal touch to your machine? One of
15the best perks of Linux is that it is **extremely** customizable. You
16can change the styles of the windows, shell (status bars/docks), icons,
17fonts, terminals, and more.
18
19In this post, I'm going to go through customization on Ubuntu 20.04
20(GNOME) since most new users tend to choose Ubuntu-based distros. If
21you've found a way to install Arch with i3-gaps, I'm assuming you know
22how to find more advanced tutorials out there on customizations.
23
24## Required Tools
25
26![Gnome
27Tweaks](https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-tweaks-min.png)
28
29Ubuntu 20.04 ships with the default desktop environment
30[Gnome](https://www.gnome.org/), which includes the handy
31`gnome-tweaks` tool to quickly change designs. To install
32this, just open your terminal and enter the following command:
33
34```sh
35sudo apt install gnome-tweaks
36```
37
38After you've finished installing the tool, simply launch the Tweaks
39application, and you'll be able to access the various customization
40options available by default on Ubuntu. You might even like some of the
41pre-installed options.
42
43## GNOME Application Themes
44
45To change the themes applied to applications in GNOME, you will need to
46change the Applications dropdown in the Appearance section of Tweaks. To
47add more themes, you will have to find your preferred theme online and
48follow the steps below to have it show up in the Tweaks tool. While you
49may find themes anywhere, one of the most popular sites for GNOME themes
50is [gnome-look.org](https://www.gnome-look.org/). This website contains
51themes for applications, shells, icons, and cursors.
52
53Steps to import themes into Tweaks:
54
551. Download the theme.
562. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you
57 will need to extract the contents. This is easiest when opening the
58 file explorer, right-clicking the compressed file, and choosing
59 "Extract here."
603. Move the theme folder to `/usr/share/themes/`. You can do
61 so with the following command:
62 `sudo mv theme-folder/ /usr/share/themes/`.
63 - Icons and cursors will be moved to the
64 `/usr/share/icons/` folder.
65 - Fonts will be moved to the `/usr/share/fonts/` folder
66 Alternatively, you can move them to the
67 `/usr/share/fonts/opentype/` or
68 `/usr/share/fonts/opentype/` folders, if you have a
69 specific font type.
704. Close tweaks if it is open. Re-open Tweaks and your new theme will
71 be available in the Applications dropdown in the Appearance section
72 of Tweaks.
73
74If the theme is not showing up after you've moved it into the themes
75folder, you may have uncompressed the folder into a sub-folder. You can
76check this by entering the theme folder and listing the contents:
77
78```sh
79cd /usr/share/themes/Mojave-Dark && ls -la
80```
81
82This is an example of what the contents of your theme folder should look
83like. If you just see another folder there, you should move that folder
84up into the `/usr/share/themes/` folder.
85
86```sh
87cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
88```
89
90## GNOME Shell Themes
91
92To change the appearance of the title bar, default dock, app menu, and
93other parts of the GNOME shell, you'll need to install the [user
94themes](https://extensions.gnome.org/extension/19/user-themes/)
95extension on [Gnome Extensions](https://extensions.gnome.org/). To be
96able to install extensions, you will first need to install the browser
97extension that the website instructs you to. See this screenshot for the
98blue box with a link to the extension.
99
100![Gnome
101Extensions](https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-extensions-min.png)
102
103After the browser extension is installed, you will need to install the
104native host connector:
105
106```sh
107sudo apt install chrome-gnome-shell
108```
109
110Finally, you can go the [user
111themes](https://extensions.gnome.org/extension/19/user-themes/)
112extension page and click the installation button. This will enable the
113Shell option in Tweaks. Now you can move shell themes to the
114`/usr/share/themes` directory, using the same steps mentioned
115in the previous section, and enable the new theme in Tweaks.
116
117## Icons & Cursors
118
119Icons and cursors are installed exactly the same way, so I'm grouping
120these together in this post. Both of these items will need to follow the
121same process as installing themes, except you will want to move your
122font folders to the `/usr/share/icons/` directory instead.
123
124## Fonts
125
126Fonts are one of the overlooked parts of customization, but a good font
127can make the whole screen look different. For example, I have installed
128the [IBM Plex](https://github.com/IBM/plex/releases) fonts on my system.
129This follows the same process as installing themes, except you will want
130to move your font folders to the `/usr/share/fonts/`
131directory instead.
132
133## Terminal
134
135If you spend a lot of time typing commands, you know how important the
136style and functionality of the terminal is. After spending a lot of time
137using the default GNOME terminal with [unix
138shell](https://en.wikipedia.org/wiki/Bash_(Unix_shell)), I decided to
139try some different options. I ended up choosing
140[Terminator](https://terminator-gtk3.readthedocs.io/en/latest/) with
141[zsh](https://en.wikipedia.org/wiki/Z_shell).
142
143Terminator is great if you need to open multiple terminals at one time
144by simply right-clicking and splitting the screen into as many terminals
145as you want. While this project hasn't been updated in a while, [it is
146coming under new
147development](https://github.com/gnome-terminator/terminator/issues/1).
148However, this terminal is great and I haven't experienced any errors
149yet.
150
151For the shell choice, I decided to choose zsh after trying it out on a
152fresh Manjaro installation. Zsh is great if you like to change the
153themes of your terminal, include icons, or add plugins.
154
155The desktop uses the
156[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)
157to suggest past commands as you type. In addition, it suggests
158corrections if you misspell a command. Lastly, it uses the
159`af-magic` theme, which adds dashed lines between commands,
160moving the user@host tag to the right side of the terminal, and changes
161the colors. There are plenty of plugins and themes to choose from. Just
162figure out what you like and add it to your `~/.zshrc` file!
163
164### Steps to Replicate My Terminal
165
166To install zsh on Ubuntu, enter the following command into a terminal:
167
168```sh
169sudo apt install zsh
170```
171
172Then, enter the next command to activate zsh:
173
174```sh
175sudo chsh -s $(which zsh) $(whoami)
176```
177
178To install Terminator on Ubuntu:
179
180```sh
181sudo apt install terminator
182```
183
184To install Oh My Zsh on Ubuntu:
185
186```sh
187sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
188```
189
190To install zsh-autosuggestions via Oh My Zsh:
191
192```sh
193git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
194```
195
196Then, add the following plugin wording to your `~/.zshrc`
197file (the default config usually has the `git` plugin
198activated, so just add any other plugins to the parentheses separated by
199a space):
200
201```sh
202nano ~/.zshrc
203```
204
205```sh
206plugins=(git zsh-autosuggestions)
207```
208
209Finally, you need to log out of your computer and log back in so your
210user shell can refresh.
content/blog/2020-07-20-video-game-sales.md added +181
@@ -0,0 +1,181 @@
1+++
2date = 2020-07-20
3title = "Data Exploration: Video Game Sales"
4description = "Exploring and visualizing data with Python."
5+++
6
7# Background Information
8
9This dataset (obtained from
10[Kaggle](https://www.kaggle.com/gregorut/videogamesales/data)) contains
11a list of video games with sales greater than 100,000 copies. It was
12generated by a scrape of vgchartz.com.
13
14Fields include:
15
16- Rank: Ranking of overall sales
17- Name: The game name
18- Platform: Platform of the game release (i.e. PC,PS4, etc.)
19- Year: Year of the game's release
20- Genre: Genre of the game
21- Publisher: Publisher of the game
22- NA~Sales~: Sales in North America (in millions)
23- EU~Sales~: Sales in Europe (in millions)
24- JP~Sales~: Sales in Japan (in millions)
25- Other~Sales~: Sales in the rest of the world (in millions)
26- Global~Sales~: Total worldwide sales.
27
28There are 16,598 records. 2 records were dropped due to incomplete
29information.
30
31# Import the Data
32
33```python
34# Import the Python libraries we will be using
35import pandas as pd
36import numpy as np
37import seaborn as sns; sns.set()
38import matplotlib.pyplot as plt
39
40# Load the file using the path to the downloaded file
41file = r'video_game_sales.csv'
42df = pd.read_csv(file)
43df
44```
45
46![Dataframe
47Results](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/01_dataframe-min.png)
48
49# Explore the Data
50
51```python
52# With the description function, we can see the basic stats. For example, we can also see that the 'Year' column has some incomplete values.
53df.describe()
54```
55
56![df.describe()](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/02_describe-min.png)
57
58```python
59# This function shows the rows and columns of NaN values. For example, df[179,3] = nan
60np.where(pd.isnull(df))
61
62(array([179, ..., 16553], dtype=int64),
63 array([3, ..., 5], dtype=int64))
64```
65
66# Visualize the Data
67
68```python
69# This function plots the global sales by platform
70sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
71```
72
73![Plot of Global Sales by
74Platform](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/03_plot-min.png)
75
76```python
77# This function plots the global sales by genre
78sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
79```
80
81![Plot of Global Sales by
82Genre](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/04_plot-min.png)
83
84```python
85# This function plots the global sales by year
86sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
87```
88
89![Plot of Global Sales by
90Year](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/05_plot-min.png)
91
92```python
93# This function plots four different lines to show sales from different regions.
94# The global sales plot line is commented-out, but can be included for comparison
95df2 = df.groupby('Year').sum()
96years = range(1980,2019)
97
98a = df2['NA_Sales']
99b = df2['EU_Sales']
100c = df2['JP_Sales']
101d = df2['Other_Sales']
102# e = df2['Global_Sales']
103
104fig, ax = plt.subplots(figsize=(12,12))
105ax.set_ylabel('Region Sales (in Millions)')
106ax.set_xlabel('Year')
107
108ax.plot(years, a, label='NA_Sales')
109ax.plot(years, b, label='EU_Sales')
110ax.plot(years, c, label='JP_Sales')
111ax.plot(years, d, label='Other_Sales')
112# ax.plot(years, e, label='Global_Sales')
113
114ax.legend()
115plt.show()
116```
117
118![Plot of Regional Sales by
119Year](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/06_plot-min.png)
120
121# Investigate Outliers
122
123```python
124# Find the game with the highest sales in North America
125df.loc[df['NA_Sales'].idxmax()]
126
127Rank 1
128Name Wii Sports
129Platform Wii
130Year 2006
131Genre Sports
132Publisher Nintendo
133NA_Sales 41.49
134EU_Sales 29.02
135JP_Sales 3.77
136Other_Sales 8.46
137Global_Sales 82.74
138Name: 0, dtype: object
139
140# Explore statistics in the year 2006 (highest selling year)
141df3 = df[(df['Year'] == 2006)]
142df3.describe()
143```
144
145![Descriptive Statistics of 2006
146Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/07_2006_stats-min.png)
147
148```python
149# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
150sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
151```
152
153![Plot of 2006
154Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/08_plot-min.png)
155
156```python
157# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
158# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
159df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
160```
161
162![Outliers of 2006
163Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/09_outliers-min.png)
164
165# Discussion
166
167The purpose of exploring datasets is to ask questions, answer questions,
168and discover intelligence that can be used to inform decision-making.
169So, what have we found in this dataset?
170
171Today we simply explored a publicly-available dataset to see what kind
172of information it contained. During that exploration, we found that
173video game sales peaked in 2006. That peak was largely due to Nintendo,
174who sold the top 5 games in 2006 and has a number of games in the top-10
175list for the years 1980-2020. Additionally, the top four platforms by
176global sales (Wii, NES, GB, DS) are owned by Nintendo.
177
178We didn't explore everything this dataset has to offer, but we can tell
179from a brief analysis that Nintendo seems to rule sales in the video
180gaming world. Further analysis could provide insight into which genres,
181regions, publishers, or world events are correlated with sales.
blog/2020-07-26-business-analysis.org → content/blog/2020-07-26-business-analysis.md renamed +114 −103
@@ -1,28 +1,31 @@
1#+date: 2020-07-26 1+++
2#+title: Algorithmically Analyzing Local Businesses 2date = 2020-07-26
3title = "Algorithmically Analyzing Local Businesses"
4description = "Exploring and visualizing data with Python."
5+++
3 6
4* Background Information 7# Background Information
5 8
6This project aims to help investors learn more about a random city in order to 9This project aims to help investors learn more about a random city in
7determine optimal locations for business investments. The data used in this 10order to determine optimal locations for business investments. The data
8project was obtained using Foursquare's developer API. 11used in this project was obtained using Foursquare's developer API.
9 12
10Fields include: 13Fields include:
11 14
12- Venue Name 15- Venue Name
13- Venue Category 16- Venue Category
14- Venue Latitude 17- Venue Latitude
15- Venue Longitude 18- Venue Longitude
16 19
17There are 232 records found using the center of Lincoln as the area of interest 20There are 232 records found using the center of Lincoln as the area of
18with a radius of 10,000. 21interest with a radius of 10,000.
19 22
20* Import the Data 23# Import the Data
21 24
22The first step is the simplest: import the applicable libraries. We will be 25The first step is the simplest: import the applicable libraries. We will
23using the libraries below for this project. 26be using the libraries below for this project.
24 27
25#+BEGIN_SRC python 28```python
26# Import the Python libraries we will be using 29# Import the Python libraries we will be using
27import pandas as pd 30import pandas as pd
28import requests 31import requests
@@ -31,32 +34,32 @@ import math
31import json 34import json
32from pandas.io.json import json_normalize 35from pandas.io.json import json_normalize
33from sklearn.cluster import KMeans 36from sklearn.cluster import KMeans
34#+END_SRC 37```
35 38
36To begin our analysis, we need to import the data for this project. The data we 39To 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 40data 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 41API. The first step is to get the latitude and longitude of the city
39setting up the folium map. 42being studied (Lincoln, NE) and setting up the folium map.
40 43
41#+BEGIN_SRC python 44```python
42# Define the latitude and longitude, then map the results 45# Define the latitude and longitude, then map the results
43latitude = 40.806862 46latitude = 40.806862
44longitude = -96.681679 47longitude = -96.681679
45map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12) 48map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12)
46 49
47map_LNK 50map_LNK
48#+END_SRC 51```
49 52
50#+CAPTION: Blank Map 53![Blank
51[[https://img.cleberg.net/blog/20200726-ibm-data-science/01_blank_map-min.png]] 54Map](https://img.cleberg.net/blog/20200726-ibm-data-science/01_blank_map-min.png)
52 55
53Now that we have defined our city and created the map, we need to go get the 56Now 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 57the 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 58API 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 59results that Foursquare has found. Since the total results are 232, we
57three times (100 + 100 + 32 = 232). 60perform the API fetching process three times (100 + 100 + 32 = 232).
58 61
59#+BEGIN_SRC python 62```python
60# Foursquare API credentials 63# Foursquare API credentials
61CLIENT_ID = 'your-client-id' 64CLIENT_ID = 'your-client-id'
62CLIENT_SECRET = 'your-client-secret' 65CLIENT_SECRET = 'your-client-secret'
@@ -114,18 +117,19 @@ url3 = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret
114 117
115# Fetch the final results (201 - 232) 118# Fetch the final results (201 - 232)
116results3 = requests.get(url3).json() 119results3 = requests.get(url3).json()
117#+END_SRC 120```
118 121
119* Clean the Data 122# Clean the Data
120 123
121Now that we have our data in three separate dataframes, we need to combine them 124Now that we have our data in three separate dataframes, we need to
122into a single dataframe and make sure to reset the index so that we have a 125combine them into a single dataframe and make sure to reset the index so
123unique ID for each business. The `get_category_type` function below will pull 126that we have a unique ID for each business. The `get~categorytype~`
124the categories and name from each business's entry in the Foursquare data 127function below will pull the categories and name from each business's
125automatically. Once all the data has been labeled and combined, the results are 128entry in the Foursquare data automatically. Once all the data has been
126stored in the =nearby_venues= dataframe. 129labeled and combined, the results are stored in the
130`nearby_venues` dataframe.
127 131
128#+BEGIN_SRC python 132```python
129# This function will extract the category of the venue from the API dictionary 133# This function will extract the category of the venue from the API dictionary
130def get_category_type(row): 134def get_category_type(row):
131 try: 135 try:
@@ -188,18 +192,18 @@ nearby_venues3.columns = [col.split(".")[-1] for col in nearby_venues3.columns]
188nearby_venues = nearby_venues.append(nearby_venues3) 192nearby_venues = nearby_venues.append(nearby_venues3)
189nearby_venues = nearby_venues.reset_index(drop=True) 193nearby_venues = nearby_venues.reset_index(drop=True)
190nearby_venues 194nearby_venues
191#+END_SRC 195```
192 196
193#+CAPTION: Clean Data 197![Clean
194[[https://img.cleberg.net/blog/20200726-ibm-data-science/02_clean_data-min.png]] 198Data](https://img.cleberg.net/blog/20200726-ibm-data-science/02_clean_data-min.png)
195 199
196* Visualize the Data 200# Visualize the Data
197 201
198We now have a complete, clean data set. The next step is to visualize this data 202We now have a complete, clean data set. The next step is to visualize
199onto the map we created earlier. We will be using folium's =CircleMarker()= 203this data onto the map we created earlier. We will be using folium's
200function to do this. 204`CircleMarker()` function to do this.
201 205
202#+BEGIN_SRC python 206```python
203# add markers to map 207# add markers to map
204for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']): 208for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']):
205 label = '{} ({})'.format(name, categories) 209 label = '{} ({})'.format(name, categories)
@@ -215,22 +219,25 @@ for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng']
215 ).add_to(map_LNK) 219 ).add_to(map_LNK)
216 220
217map_LNK 221map_LNK
218#+END_SRC 222```
219 223
220![Initial data map](https://img.cleberg.net/blog/20200726-ibm-data-science/03_data_map-min.png "Initial data map") 224\![Initial data
225map](<https://img.cleberg.net/blog/20200726-ibm-data-science/03_data_map-min.png>
226"Initial data map")
221 227
222* Clustering: /k-means/ 228# Clustering: *k-means*
223 229
224To cluster the data, we will be using the /k-means/ algorithm. This algorithm is 230To cluster the data, we will be using the *k-means* algorithm. This
225iterative and will automatically make sure that data points in each cluster are 231algorithm is iterative and will automatically make sure that data points
226as close as possible to each other, while being as far as possible away from 232in each cluster are as close as possible to each other, while being as
227other clusters. 233far as possible away from other clusters.
228 234
229However, we first have to figure out how many clusters to use (defined as the 235However, we first have to figure out how many clusters to use (defined
230variable /'k'/). To do so, we will use the next two functions to calculate the 236as the variable *'k'*). To do so, we will use the next two functions
231sum of squares within clusters and then return the optimal number of clusters. 237to calculate the sum of squares within clusters and then return the
238optimal number of clusters.
232 239
233#+BEGIN_SRC python 240```python
234# 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
235def calculate_wcss(data): 242def calculate_wcss(data):
236 wcss = [] 243 wcss = []
@@ -264,13 +271,13 @@ def optimal_number_of_clusters(wcss):
264 271
265# calculating the optimal number of clusters 272# calculating the optimal number of clusters
266n = optimal_number_of_clusters(sum_of_squares) 273n = optimal_number_of_clusters(sum_of_squares)
267#+END_SRC 274```
268 275
269Now 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
270perform k-means clustering. When this clustering occurs, each business is 277need to perform k-means clustering. When this clustering occurs, each
271assigned a cluster number from 0 to 5 in the dataframe. 278business is assigned a cluster number from 0 to 5 in the dataframe.
272 279
273#+BEGIN_SRC python 280```python
274# set number of clusters equal to the optimal number 281# set number of clusters equal to the optimal number
275kclusters = n 282kclusters = n
276 283
@@ -279,12 +286,13 @@ kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(cluster_df)
279 286
280# add clustering labels to dataframe 287# add clustering labels to dataframe
281nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_) 288nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_)
282#+END_SRC 289```
283 290
284Success! 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
285number 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.
286 294
287#+BEGIN_SRC python 295```python
288# create map with clusters 296# create map with clusters
289map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12) 297map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12)
290colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717'] 298colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717']
@@ -303,36 +311,37 @@ for lat, lng, name, categories, cluster in zip(nearby_venues['lat'], nearby_venu
303 fill_opacity=0.7).add_to(map_clusters) 311 fill_opacity=0.7).add_to(map_clusters)
304 312
305map_clusters 313map_clusters
306#+END_SRC 314```
307 315
308#+CAPTION: Clustered Map 316![Clustered
309[[https://img.cleberg.net/blog/20200726-ibm-data-science/04_clusters-min.png]] 317Map](https://img.cleberg.net/blog/20200726-ibm-data-science/04_clusters-min.png)
310 318
311* Investigate Clusters 319# Investigate Clusters
312 320
313Now that we have figured out our clusters, let's do a little more analysis to 321Now that we have figured out our clusters, let's do a little more
314provide more insight into the clusters. With the information below, we can see 322analysis to provide more insight into the clusters. With the information
315which clusters are more popular for businesses and which are less popular. The 323below, we can see which clusters are more popular for businesses and
316results below show us that clusters 0 through 3 are popular, while clusters 4 324which are less popular. The results below show us that clusters 0
317and 5 are not very popular at all. 325through 3 are popular, while clusters 4 and 5 are not very popular at
326all.
318 327
319#+BEGIN_SRC python 328```python
320# Show how many venues are in each cluster 329# Show how many venues are in each cluster
321color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black'] 330color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black']
322for x in range(0,6): 331for x in range(0,6):
323 print("Color of Cluster", x, ":", color_names[x]) 332 print("Color of Cluster", x, ":", color_names[x])
324 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])
325 print("---") 334 print("---")
326#+END_SRC 335```
327 336
328#+CAPTION: Venues per Cluster 337![Venues per
329[[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)
330 339
331Our last piece of analysis is to summarize the categories of businesses within 340Our last piece of analysis is to summarize the categories of businesses
332each cluster. With these results, we can clearly see that restaurants, coffee 341within each cluster. With these results, we can clearly see that
333shops, and grocery stores are the most popular. 342restaurants, coffee shops, and grocery stores are the most popular.
334 343
335#+BEGIN_SRC python 344```python
336# Calculate how many venues there are in each category 345# Calculate how many venues there are in each category
337# Sort from largest to smallest 346# Sort from largest to smallest
338temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng']) 347temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng'])
@@ -352,27 +361,29 @@ with pd.option_context('display.max_rows', None, 'display.max_columns', None):
352 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])
353 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])
354 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])
355#+END_SRC 364```
356 365
357#+CAPTION: Venues per Cluster, pt. 1 366![Venues per Cluster, pt.
358[[https://img.cleberg.net/blog/20200726-ibm-data-science/06_categories_per_cluster_pt1-min.png]] 3671](https://img.cleberg.net/blog/20200726-ibm-data-science/06_categories_per_cluster_pt1-min.png)
359 368
360#+CAPTION: Venues per Cluster, pt. 2 369![Venues per Cluster, pt.
361[[https://img.cleberg.net/blog/20200726-ibm-data-science/07_categories_per_cluster_pt2-min.png]] 3702](https://img.cleberg.net/blog/20200726-ibm-data-science/07_categories_per_cluster_pt2-min.png)
362 371
363* Discussion 372# Discussion
364 373
365In this project, we gathered location data for Lincoln, Nebraska, USA and 374In this project, we gathered location data for Lincoln, Nebraska, USA
366clustered the data using the k-means algorithm in order to identify the unique 375and clustered the data using the k-means algorithm in order to identify
367clusters of businesses in Lincoln. Through these actions, we found that there 376the unique clusters of businesses in Lincoln. Through these actions, we
368are six unique business clusters in Lincoln and that two of the clusters are 377found that there are six unique business clusters in Lincoln and that
369likely unsuitable for investors. The remaining four clusters have a variety of 378two of the clusters are likely unsuitable for investors. The remaining
370businesses, but are largely dominated by restaurants and grocery stores. 379four clusters have a variety of businesses, but are largely dominated by
380restaurants and grocery stores.
371 381
372Using this project, investors can now make more informed decisions when deciding 382Using this project, investors can now make more informed decisions when
373the location and category of business in which to invest. 383deciding the location and category of business in which to invest.
374 384
375Further studies may involve other attributes for business locations, such as 385Further studies may involve other attributes for business locations,
376population density, average wealth across the city, or crime rates. In addition, 386such as population density, average wealth across the city, or crime
377further studies may include additional location data and businesses by utilizing 387rates. In addition, further studies may include additional location data
378multiple 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 +136
@@ -0,0 +1,136 @@
1+++
2date = 2020-08-22
3title = "Redirect GitHub Pages from Subdomain to the Top-Level Domain"
4description = "Learn how to redirect Github pages to the TLD."
5+++
6
7# Short answer
8
9## Step 1
10
11Add a new file CNAME to your GitHub Pages repository containing only one
12line: your top-level domain name. E.g.: `example.com`
13
14## Step 2
15
16[Optional] but highly recommended
17
182.1: Remove all other top-level records (prefixed with @) of type A from
19your DNS configuration.
20
212.2: Remove a CNAME record for the second-level domain www if it is
22present.
23
24## Step 3
25
26Add these 5 entries to the very top of your DNS configuration:
27
28```txt
29@ A 185.199.108.153
30@ A 185.199.109.153
31@ A 185.199.110.153
32@ A 185.199.111.153
33www CNAME your_github_username.github.io.
34```
35
36Replace `your_github_username` with your actual GitHub
37username.
38
39## Step 4
40
41Wait for your DNS changes to propagate. DNS changes aren't effective
42immediately. They can take up to a full day to propagate.
43
44# Long answer
45
46This issue has two sides. One is the DNS configuration itself. Another
47one is the way GitHub Pages will forward HTTP requests.
48
49We need to know a few things to understand what GitHub is trying to say
50in their documentation.
51
52## DNS Entry Types
53
54There are two types of DNS records which interest us: CNAME and A.
55
56`A` is also known as `Apex` or sometimes as
57`root entry`. It forwards requests to a specified fixed IP
58address. `CNAME` entry forwards requests to a specified URL
59(actual valid plain text URL, not an IP address).
60
61## DNS Load balancing
62
63GitHub has one central URL address which accepts all DNS requests for
64GitHub Pages: `http://username.github.io`. That URL is
65resolved to different IP addresses based on your geographical location.
66Website hosted on GitHub Pages is a simple collection of
67`HTML`, `CSS` and `JS` files. GitHub
68distributes these files to different servers across the globe. So that
69when your browser sends a request from Europe, it receives data from a
70server in Europe. The same is valid for the requests from Asia and the
71USA.
72
73## What GitHub is trying to say
74
75Since `A` records in DNS must contain IP addresses, and they
76must be either `185.199.108.153` or
77`185.199.109.153` or `185.199.110.153` or
78`185.199.111.153`, there is no way to forward requests to a
79server located somewhere in Europe or Asia. Your website hosted at
80GitHub Pages will be downloaded from a central GitHub Pages server.
81There is a minor risk that if GitHub Pages DNS servers
82(`x.x.x.153`) are down for some reason, all custom domains
83which use fixed GitHub Pages IP addresses will not be accessible (their
84DNS requests will not be resolvable).
85
86That is why GitHub strongly suggests to either use a second-level domain
87for your GitHub Pages (e.g. `blog.example.com`) or use a DNS
88service provider that supports a record type `ALIAS` that
89acts as `A` record but forwards request to a URL address
90(e.g. `username.github.io`) instead of a fixed IP address.
91
92## How GitHub Pages treats HTTP requests
93
94After a DNS request for `your_github_username.github.io` is
95resolved into an IP address, e.g. `185.199.108.153` your
96browser sends an HTTP request to that server with an HTTP header
97`Host`. Below are `curl` examples that load the
98same website (these examples might not work if you are behind a proxy
99server):
100
101```sh
102curl --header "Host: your_github_username.github.io" http://185.199.108.153/
103curl --header "Host: www.example.com" http://185.199.108.153/
104curl --header "Host: example.com" http://185.199.108.153/
105```
106
107This way GitHub Pages servers know which user website to serve.
108
109> GitHub Pages server will automatically redirect HTTP requests to the
110> top-level domain if your `CNAME` file contains
111> `example.com` but `www.example.com` is
112> requested.
113>
114> The same is valid if your `CNAME` file contains
115> `www.example.com` but the header `Host` in the
116> `HTTP` request contains `example.com`.
117
118## Why can't I add a `CNAME` record entry that accepts a top-level request (`@`) to my DNS configuration?
119
120Quote from the GitHub Pages documentation:
121
122> Warning: Do not create a CNAME record for your custom apex domain!
123> Doing so may cause issues with other services, such as email, on that
124> domain.
125
126# References:
127
1281. [Setting up a custom domain with GitHub
129 Pages](https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site)
1302. [My custom domain isn't
131 working](https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages)
1323. [Cannot access my GitHub Pages website by IP
133 Address](https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address)
1344. [How do I set up GitHub Pages to redirect DNS requests from a
135 subdomain (e.g. www) to the top-level domain (TLD, Apex
136 record)?](https://stackoverflow.com/questions/23375422/how-do-i-set-up-github-pages-to-redirect-dns-requests-from-a-subdomain-e-g-www)
blog/2020-08-29-php-auth-flow.org → content/blog/2020-08-29-php-auth-flow.md renamed +75 −65
@@ -1,19 +1,23 @@
1#+date: 2020-08-29 1+++
2#+title: PHP Authentication Flow 2date = 2020-08-29
3 3title = "PHP Authentication Flow"
4* Introduction 4description = "Learn how to establish and maintain a basic user authentication flow in PHP."
5 5+++
6When creating websites that will allow users to create accounts, the developer 6
7always needs to consider the proper authentication flow for their app. For 7# Introduction
8example, some developers will utilize an API for authentication, some will use 8
9OAuth, and some may just use their own simple database. 9When creating websites that will allow users to create accounts, the
10 10developer always needs to consider the proper authentication flow for
11For those using pre-built libraries, authentication may simply be a problem of 11their app. For example, some developers will utilize an API for
12copying and pasting the code from their library's documentation. For example, 12authentication, some will use OAuth, and some may just use their own
13here's the code I use to authenticate users with the Tumblr OAuth API for my 13simple database.
14Tumblr client, Vox Populi: 14
15 15For those using pre-built libraries, authentication may simply be a
16#+BEGIN_SRC php 16problem of copying and pasting the code from their library's
17documentation. For example, here's the code I use to authenticate users
18with the Tumblr OAuth API for my Tumblr client, Vox Populi:
19
20```php
17// Start the session 21// Start the session
18session_start(); 22session_start();
19 23
@@ -34,27 +38,28 @@ $client = new Tumblr\API\Client(
34 $token, 38 $token,
35 $token_secret 39 $token_secret
36); 40);
37#+END_SRC 41```
38 42
39However, developers creating authentication flows from scratch will need to 43However, developers creating authentication flows from scratch will need
40think carefully about when to make sure a web page will check the user's 44to think carefully about when to make sure a web page will check the
41authenticity. 45user's authenticity.
42 46
43In this article, we're going to look at a simple authentication flow using a 47In this article, we're going to look at a simple authentication flow
44MySQL database and PHP. 48using a MySQL database and PHP.
45 49
46* Creating User Accounts 50# Creating User Accounts
47 51
48The beginning to any type of user authentication is to create a user account. 52The beginning to any type of user authentication is to create a user
49This process can take many formats, but the simplest is to accept user input 53account. This process can take many formats, but the simplest is to
50from a form (e.g., username and password) and send it over to your database. 54accept user input from a form (e.g., username and password) and send it
51For example, here's a snippet that shows how to get username and password 55over to your database. For example, here's a snippet that shows how to
52parameters that would come when a user submits a form to your PHP script. 56get username and password parameters that would come when a user submits
57a form to your PHP script.
53 58
54*Note*: Ensure that your password column is large enough to hold the hashed 59**Note**: Ensure that your password column is large enough to hold the
55value (at least 60 characters or longer). 60hashed value (at least 60 characters or longer).
56 61
57#+BEGIN_SRC php 62```php
58// Get the values from the URL 63// Get the values from the URL
59$username = $_POST['username']; 64$username = $_POST['username'];
60$raw_password = $_POST['password']; 65$raw_password = $_POST['password'];
@@ -87,15 +92,15 @@ if ($conn->query($sql) === TRUE) {
87} 92}
88 93
89$conn->close(); 94$conn->close();
90#+END_SRC 95```
91 96
92* Validate Returning Users 97# Validate Returning Users
93 98
94To be able to verify that a returning user has a valid username and password in 99To be able to verify that a returning user has a valid username and
95your database is as simple as having users fill out a form and comparing their 100password in your database is as simple as having users fill out a form
96inputs to your database. 101and comparing their inputs to your database.
97 102
98#+BEGIN_SRC php 103```php
99// Query the database for username and password 104// Query the database for username and password
100// ... 105// ...
101 106
@@ -106,41 +111,42 @@ if(password_verify($password_input, $hashed_password)) {
106 111
107// Else, Redirect them back to the login page. 112// Else, Redirect them back to the login page.
108... 113...
109#+END_SRC 114```
110 115
111* Storing Authentication State 116# Storing Authentication State
112 117
113Once you've created the user's account, now you're ready to initialize the 118Once you've created the user's account, now you're ready to
114user's session. *You will need to do this on every page you load while the user 119initialize the user's session. **You will need to do this on every page
115is logged in.* To do so, simply enter the following code snippet: 120you load while the user is logged in.** To do so, simply enter the
121following code snippet:
116 122
117#+BEGIN_SRC php 123```php
118session_start(); 124session_start();
119#+END_SRC 125```
120 126
121Once you've initialized the session, the next step is to store the session in a 127Once you've initialized the session, the next step is to store the
122cookie so that you can access it later. 128session in a cookie so that you can access it later.
123 129
124#+BEGIN_SRC php 130```php
125setcookie(session_name()); 131setcookie(session_name());
126#+END_SRC 132```
127 133
128Now that the session name has been stored, you'll be able to check if there's an 134Now that the session name has been stored, you'll be able to check if
129active session whenever you load a page. 135there's an active session whenever you load a page.
130 136
131#+BEGIN_SRC php 137```php
132if(isset(session_name())) { 138if(isset(session_name())) {
133 // The session is active 139 // The session is active
134} 140}
135#+END_SRC 141```
136 142
137* Removing User Authentication 143# Removing User Authentication
138 144
139The next logical step is to give your users the option to log out once they are 145The next logical step is to give your users the option to log out once
140done using your application. This can be tricky in PHP since a few of the 146they are done using your application. This can be tricky in PHP since a
141standard ways do not always work. 147few of the standard ways do not always work.
142 148
143#+BEGIN_SRC php 149```php
144// Initialize the session. 150// Initialize the session.
145// If you are using session_name("something"), don't forget it now! 151// If you are using session_name("something"), don't forget it now!
146session_start(); 152session_start();
@@ -172,14 +178,18 @@ session_write_close();
172// Go back to sign-in page 178// Go back to sign-in page
173header('Location: https://example.com/logged-out/'); 179header('Location: https://example.com/logged-out/');
174die(); 180die();
175#+END_SRC 181```
176 182
177* Wrapping Up 183# Wrapping Up
178 184
179Now you should be ready to begin your authentication programming with PHP. You 185Now you should be ready to begin your authentication programming with
180can create user accounts, create sessions for users across different pages of 186PHP. You can create user accounts, create sessions for users across
181your site, and then destroy the user data when they're ready to leave. 187different pages of your site, and then destroy the user data when
188they're ready to leave.
182 189
183For more information on this subject, I recommend reading the [[https://www.php.net/][PHP 190For more information on this subject, I recommend reading the [PHP
184Documentation]]. Specifically, you may want to look at [[https://www.php.net/manual/en/features.http-auth.php][HTTP Authentication with 191Documentation](https://www.php.net/). Specifically, you may want to look
185PHP]], [[https://www.php.net/manual/en/book.session.php][session handling]], and [[https://www.php.net/manual/en/function.hash.php][hash]]. 192at [HTTP Authentication with
193PHP](https://www.php.net/manual/en/features.http-auth.php), [session
194handling](https://www.php.net/manual/en/book.session.php), and
195[hash](https://www.php.net/manual/en/function.hash.php).
content/blog/2020-09-01-visual-recognition.md added +204
@@ -0,0 +1,204 @@
1+++
2date = 2020-09-01
3title = "IBM Watson Visual Recognition"
4description = "Exploring and visualizing data with Python."
5+++
6
7# What is IBM Watson?
8
9If you've never heard of [Watson](https://www.ibm.com/watson), this
10service is a suite of enterprise-ready AI services, applications, and
11tooling provided by IBM. Watson contains quite a few useful tools for
12data scientists and students, including the subject of this post today:
13visual recognition.
14
15If you'd like to view the official documentation for the Visual
16Recognition API, visit the [API
17Docs](https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python).
18
19# Prerequisites
20
21To be able to use Watson Visual Recognition, you'll need the following:
22
231. Create a free account on [IBM Watson
24 Studio](https://www.ibm.com/cloud/watson-studio).
252. Add the [Watson Visual
26 Recognition](https://www.ibm.com/cloud/watson-visual-recognition)
27 service to your IBM Watson account.
283. Get your API key and URL. To do this, first go to the [profile
29 dashboard](https://dataplatform.cloud.ibm.com/home2?context=cpdaas)
30 for your IBM account and click on the Watson Visual Recognition
31 service you created. This will be listed in the section titled
32 **Your services**. Then click the **Credentials** tab and open the
33 **Auto-generated credentials** dropdown. Copy your API key and URL
34 so that you can use them in the Python script later.
354. **[Optional]** While not required, you can also create the Jupyter
36 Notebook for this project right inside [Watson
37 Studio](https://www.ibm.com/cloud/watson-studio). Watson Studio will
38 save your notebooks inside an organized project and allow you to use
39 their other integrated products, such as storage containers, AI
40 models, documentation, external sharing, etc.
41
42# Calling the IBM Watson Visual Recognition API
43
44Okay, now let's get started.
45
46To begin, we need to install the proper Python package for IBM Watson.
47
48```sh
49pip install --upgrade --user "ibm-watson>=4.5.0"
50```
51
52Next, we need to specify the API key, version, and URL given to us when
53we created the Watson Visual Recognition service.
54
55```python
56apikey = "<your-apikey>"
57version = "2018-03-19"
58url = "<your-url>"
59```
60
61Now, let's import the necessary libraries and authenticate our service.
62
63```python
64import json
65from ibm_watson import VisualRecognitionV3
66from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
67
68authenticator = IAMAuthenticator(apikey)
69visual_recognition = VisualRecognitionV3(
70 version=version,
71 authenticator=authenticator
72)
73
74visual_recognition.set_service_url(url)
75```
76
77**[Optional]** If you'd like to tell the API not to use any data to
78improve their products, set the following header.
79
80```python
81visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
82```
83
84Now we have our API all set and ready to go. For this example, I'm
85going to include a `dict` of photos to load as we test out
86the API.
87
88```python
89data = [
90 {
91 "title": "Grizzly Bear",
92 "url": "https://example.com/photos/image1.jpg"
93 },
94 {
95 "title": "Nature Lake",
96 "url": "https://example.com/photos/image2.jpg"
97 },
98 {
99 "title": "Welcome Sign",
100 "url": "https://example.com/photos/image3.jpg"
101 },
102 {
103 "title": "Honey Badger",
104 "url": "https://example.com/photos/image4.jpg"
105 },
106 {
107 "title": "Grand Canyon Lizard",
108 "url": "https://example.com/photos/image5.jpg"
109 },
110 {
111 "title": "Castle",
112 "url": "https://example.com/photos/image6.jpg"
113 }
114]
115```
116
117Now that we've set up our libraries and have the photos ready, let's
118create a loop to call the API for each image. The code below shows a
119loop that calls the URL of each image and sends it to the API,
120requesting results with at least 60% confidence. The results are output
121to the console with dotted lines separating each section.
122
123In the case of an API error, the codes and explanations are output to
124the console.
125
126```python
127from ibm_watson import ApiException
128
129for x in range(len(data)):
130try:
131 url = data[x]["url"]
132 images_filename = data[x]["title"]
133 classes = visual_recognition.classify(
134 url=url,
135 images_filename=images_filename,
136 threshold='0.6',
137 owners=["IBM"]).get_result()
138 print("-----------------------------------------------")
139 print("Image Title: ", data[x]["title"], "\n")
140 print("Image URL: ", data[x]["url"], "\n")
141 classification_results = classes["images"][0]["classifiers"][0]["classes"]
142 for result in classification_results:
143 print(result["class"], "(", result["score"], ")")
144 print("-----------------------------------------------")
145except ApiException as ex:
146 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
147```
148
149# The Results
150
151Here we can see the full result set of our function above. If you view
152each of the URLs that we sent to the API, you'll be able to see that it
153was remarkably accurate. To be fair, these are clear high-resolution,
154clear photos shot with a professional camera. In reality, you will most
155likely be processing images that are lower quality and may have a lot of
156noise in the photo.
157
158However, we can clearly see the benefit of being able to call this API
159instead of attempting to write our own image recognition function. Each
160of the classifications returned was a fair description of the image.
161
162If you wanted to restrict the results to those that are at least 90%
163confident or greater, you would simply adjust the `threshold`
164in the `visual_recognition.classify()` function.
165
166When your program runs, it should show the output below for each photo
167you provide.
168
169```txt
170----------------------------------------------------------------
171Image Title: Grizzly Bear
172Image URL: https://example.com/photos/image1.jpg
173
174brown bear ( 0.944 )
175bear ( 1 )
176carnivore ( 1 )
177mammal ( 1 )
178animal ( 1 )
179Alaskan brown bear ( 0.759 )
180greenishness color ( 0.975 )
181----------------------------------------------------------------
182```
183
184# Discussion
185
186Now, this was a very minimal implementation of the API. We simply
187supplied some images and looked to see how accurate the results were.
188However, you could implement this type of API into many machine learning
189(ML) models.
190
191For example, you could be working for a company that scans their
192warehouses or inventory using drones. Would you want to pay employees to
193sit there and watch drone footage all day in order to identify or count
194things in the video? Probably not. Instead, you could use a
195classification system similar to this one in order to train your machine
196learning model to correctly identify items that the drones show through
197video. More specifically, you could have your machine learning model
198watch a drone fly over a field of sheep in order to count how many sheep
199are living in that field.
200
201There are many ways to implement machine learning functionality, but
202hopefully this post helped inspire some deeper thought about the tools
203that can help propel us further into the future of machine learning and
204AI.
content/blog/2020-09-22-internal-audit.md added +263
@@ -0,0 +1,263 @@
1+++
2date = 2020-09-22
3title = "What is Internal Audit?"
4description = "Learn about the Internal Audit function and their purpose."
5+++
6
7![Internal Audit
8Overview](https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-overview.jpg)
9
10# Definitions
11
12One of the many reasons that Internal Audit needs such thorough
13explaining to non-auditors is that Internal Audit can serve many
14purposes, depending on the organization's size and needs. However, the
15Institute of Internal Auditors (IIA) defines Internal Auditing as:
16
17> Internal auditing is an independent, objective assurance and
18> consulting activity designed to add value and improve an
19> organization's operations. It helps an organization accomplish its
20> objectives by bringing a systematic, disciplined approach to evaluate
21> and improve the effectiveness of risk management, control, and
22> governance processes.
23
24However, this definition uses quite a few terms that aren't clear
25unless the reader already has a solid understanding of the auditing
26profession. To further explain, the following is a list of definitions
27that can help supplement understanding of internal auditing.
28
29## Independent
30
31Independence is the freedom from conditions that threaten the ability of
32the internal audit activity to carry out internal audit responsibilities
33in an unbiased manner. To achieve the degree of independence necessary
34to effectively carry out the responsibilities of the internal audit
35activity, the chief audit executive has direct and unrestricted access
36to senior management and the board. This can be achieved through a
37dual-reporting relationship. Threats to independence must be managed at
38the individual auditor, engagement, functional, and organizational
39levels.
40
41## Objective
42
43Objectivity is an unbiased mental attitude that allows internal auditors
44to perform engagements in such a manner that they believe in their work
45product and that no quality compromises are made. Objectivity requires
46that internal auditors do not subordinate their judgment on audit
47matters to others. Threats to objectivity must be managed at the
48individual auditor, engagement, functional, and organizational levels.
49
50## Assurance
51
52Assurance services involve the internal auditor's objective assessment
53of evidence to provide opinions or conclusions regarding an entity,
54operation, function, process, system, or other subject matters. The
55internal auditor determines the nature and scope of an assurance
56engagement. Generally, three parties are participants in assurance
57services: (1) the person or group directly involved with the entity,
58operation, function, process, system, or other subject - (the process
59owner), (2) the person or group making the assessment - (the internal
60auditor), and (3) the person or group using the assessment - (the user).
61
62## Consulting
63
64Consulting services are advisory in nature and are generally performed
65at the specific request of an engagement client. The nature and scope of
66the consulting engagement are subject to agreement with the engagement
67client. Consulting services generally involve two parties: (1) the
68person or group offering the advice (the internal auditor), and (2) the
69person or group seeking and receiving the advice (the engagement
70client). When performing consulting services, the internal auditor
71should maintain objectivity and not assume management responsibility.
72
73## Governance, Risk Management, & Compliance (GRC)
74
75The integrated collection of capabilities that enable an organization to
76reliably achieve objectives, address uncertainty and act with integrity.
77
78# Audit Charter & Standards
79
80First, it's important to note that not every organization needs
81internal auditors. In fact, it's unwise for an organization to hire
82internal auditors unless they have regulatory requirements for auditing
83and have the capital to support the department. Internal audit is a cost
84center that can only affect revenue indirectly.
85
86Once an organization determines the need for internal assurance
87services, they will hire a Chief Audit Executive and create the audit
88charter. This charter is a document, approved by the company's
89governing body, that will define internal audit's purpose, authority,
90responsibility, and position within the organization. Fortunately, the
91IIA has model charters available to IIA members for those developing or
92improving their charter.
93
94Beyond the charter and organizational documents, internal auditors
95follow a few different standards in order to perform their job. First is
96the International Professional Practices Framework (IPPF) by the IIA,
97which is the model of standards for internal auditing. In addition,
98ISACA's Information Technology Assurance Framework (ITAF) helps guide
99auditors in reference to information technology (IT) compliance and
100assurance. Finally, additional standards such as FASB, GAAP, and
101industry-specific standards are used when performing internal audit
102work.
103
104# Three Lines of Defense
105
106[The IIA](https://theiia.org) released the original Three Lines of
107Defense model in 2013, but have released an updated version in 2020.
108Here is what the Three Lines of Defense model has historically looked
109like:
110
111![2013 Three Lines of Defense
112Model](https://img.cleberg.net/blog/20200922-what-is-internal-audit/three_lines_model.png)
113
114I won't go into depth about the changes made to the model in this
115article. Instead, let's take a look at the most current model.
116
117![2020 Three Lines of Defense
118Model](https://img.cleberg.net/blog/20200922-what-is-internal-audit/updated_three_lines_model.png)
119
120The updated model forgets the strict idea of areas performing their own
121functions or line of defense. Instead of talking about management, risk,
122and internal audit as 1-2-3, the new model creates a more fluid and
123cooperative model.
124
125Looking at this model from an auditing perspective shows us that
126auditors will need to align, communicate, and collaborate with
127management, including business area managers and chief officers, as well
128as reporting to the governing body. The governing body will instruct
129internal audit *functionally* on their goals and track their progress
130periodically.
131
132However, the internal audit department will report *administratively* to
133a chief officer in the company for the purposes of collaboration,
134direction, and assistance with the business. Note that in most
135situations, the governing body is the audit committee on the company's
136board of directors.
137
138The result of this structure is that internal audit is an independent
139and objective function that can provide assurance over the topics they
140audit.
141
142# Audit Process
143
144A normal audit will generally follow the same process, regardless of the
145topic. However, certain special projects or abnormal business areas may
146call for changes to the audit process. The audit process is not set in
147stone, it's simply a set of best practices so that audits can be
148performed consistently.
149
150![The Internal Audit
151Process](https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-process.jpg)
152
153While different organizations may tweak the process, it will generally
154follow this flow:
155
156## 1. Risk Assessment
157
158The risk assessment part of the process has historically been performed
159annually, but many organizations have moved to performing this process
160much more frequently. In fact, some organizations are moving to an agile
161approach that can take new risks into the risk assessment and
162re-prioritize risk areas on-the-go. To perform a risk assessment,
163leaders in internal audit will research industry risks, consult with
164business leaders around the company, and perform analyses on company
165data.
166
167Once a risk assessment has been documented, the audit department has a
168prioritized list of risks that can be audited. This is usually in the
169form of auditable entities, such as business areas or departments.
170
171## 2. Planning
172
173During the planning phase of an audit, auditors will meet with the
174business area to discuss the various processes, controls, and risks
175applicable to the business. This helps the auditors determine the scope
176limits for the audit, as well as timing and subject-matter experts.
177Certain documents will be created in this phase that will be used to
178keep the audit on-track an in-scope as it goes forward.
179
180## 3. Testing
181
182The testing phase, also known as fieldwork or execution, is where
183internal auditors will take the information they've discovered and test
184it against regulations, industry standards, company rules, best
185practices, as well as validating that any processes are complete and
186accurate. For example, an audit of HR would most likely examine
187processes such as employee on-boarding, employee termination, security
188of personally identifiable information (PII), or the IT systems involved
189in these processes. Company standards would be examined and compared
190against how the processes are actually being performed day-to-day, as
191well as compared against regulations such as the Equal Employment
192Opportunity (EEO), American with Disabilities Act, and National Labor
193Relations Act.
194
195## 4. Reporting
196
197Once all the tests have been completed, the audit will enter the
198reporting phase. This is when the audit team will conclude on the
199evidence they've collected, interviews they've held, and any opinions
200they've formed on the controls in place. A summary of the audit
201findings, conclusions, and specific recommendations are officially
202communicated to the client through a draft report. Clients have the
203opportunity to respond to the report and submit an action plan and time
204frame. These responses become part of the final report which is
205distributed to the appropriate level of administration.
206
207## 5. Follow-Up
208
209After audits have been completed and management has formed action plans
210and time frames for audit issues, internal audit will follow up once
211that due date has arrived. In most cases, the follow-up will simply
212consist of a meeting to discuss how the action plan has been completed
213and to request documentation to prove it.
214
215# Audit Department Structure
216
217While an internal audit department is most often thought of as a team of
218full-time employees, there are actually many different ways in which a
219department can be structured. As the world becomes more digital and
220fast-paced, outsourcing has become a more attractive option for some
221organizations. Internal audit can be fully outsourced or partially
222outsourced, allowing for flexibility in cases where turnover is high.
223
224In addition, departments can implement a rotational model. This allows
225for interested employees around the organization to rotate into the
226internal audit department for a period of time, allowing them to obtain
227knowledge of risks and controls and allowing the internal audit team to
228obtain more business area knowledge. This program is popular in very
229large organizations, but organizations tend to rotate lower-level audit
230staff instead of managers. This helps prevent any significant knowledge
231loss as auditors rotate out to business areas.
232
233# Consulting
234
235Consulting is not an easy task at any organization, especially for a
236department that can have negative perceptions within the organization as
237the "compliance police." However, once an internal audit department
238has delivered value to organization, adding consulting to their suite of
239services is a smart move. In most cases, Internal Audit can insert
240themselves into a consulting role without affecting the process of
241project management at the company. This means that internal audit can
242add objective assurance and opinions to business areas as they develop
243new processes, instead of coming in periodically to audit an area and
244file issues that could have been fixed at the beginning.
245
246# Data Science & Data Analytics
247
248![Data Science Skill
249Set](https://img.cleberg.net/blog/20200922-what-is-internal-audit/data-science-skillset.png)
250
251One major piece of the internal audit function in the modern world is
252data science. While the process is data science, most auditors will
253refer to anything in this realm as data analytics. Hot topics such as
254robotic process automation (RPA), machine learning (ML), and data mining
255have taken over the auditing world in recent years. These technologies
256have been immensely helpful with increasing the effectiveness and
257efficiency of auditors.
258
259For example, mundane and repetitive tasks can be automated in order for
260auditors to make more room in their schedules for labor-intensive work.
261Further, auditors will need to adapt technologies like machine learning
262in order to extract more value from the data they're using to form
263conclusions.
blog/2020-09-25-happiness-map.org → content/blog/2020-09-25-happiness-map.md renamed +88 −80
@@ -1,88 +1,94 @@
1#+date: 2020-09-25 1+++
2#+title: Data Visualization: World Choropleth Map of Happiness 2date = 2020-09-25
3title = "Data Visualization: World Choropleth Map of Happiness"
4description = "Exploring and visualizing data with Python."
5+++
3 6
4* Background Information 7# Background Information
5 8
6The dataset (obtained from [[https://www.kaggle.com/unsdsn/world-happiness][Kaggle]]) used in this article contains a list of 9The dataset (obtained from
7countries around the world, their happiness rankings and scores, as well as 10[Kaggle](https://www.kaggle.com/unsdsn/world-happiness)) used in this
8other national scoring measures. 11article contains a list of countries around the world, their happiness
12rankings and scores, as well as other national scoring measures.
9 13
10Fields include: 14Fields include:
11 15
12- Overall rank 16- Overall rank
13- Country or region 17- Country or region
14- GDP per capita 18- GDP per capita
15- Social support 19- Social support
16- Healthy life expectancy 20- Healthy life expectancy
17- Freedom to make life choices 21- Freedom to make life choices
18- Generosity 22- Generosity
19- Perceptions of corruption 23- Perceptions of corruption
20 24
21There are 156 records. Since there are ~195 countries in the world, we can see 25There are 156 records. Since there are \~195 countries in the world, we
22that around 40 countries will be missing from this dataset. 26can see that around 40 countries will be missing from this dataset.
23 27
24* Install Packages 28# Install Packages
25 29
26As always, run the =install= command for all packages needed to perform 30As always, run the `install` command for all packages needed
27analysis. 31to perform analysis.
28 32
29#+BEGIN_SRC python 33```python
30!pip install folium geopandas matplotlib numpy pandas 34!pip install folium geopandas matplotlib numpy pandas
31#+END_SRC 35```
32 36
33* Import the Data 37# Import the Data
34 38
35We only need a couple packages to create a choropleth map. We will use [[https://python-visualization.github.io/folium/][Folium]], 39We only need a couple packages to create a choropleth map. We will use
36which provides map visualizations in Python. We will also use geopandas and 40[Folium](https://python-visualization.github.io/folium/), which provides
37pandas to wrangle our data before we put it on a map. 41map visualizations in Python. We will also use geopandas and pandas to
42wrangle our data before we put it on a map.
38 43
39#+BEGIN_SRC python 44```python
40# Import the necessary Python packages 45# Import the necessary Python packages
41import folium 46import folium
42import geopandas as gpd 47import geopandas as gpd
43import pandas as pd 48import pandas as pd
44#+END_SRC 49```
45 50
46To get anything to show up on a map, we need a file that will specify the 51To get anything to show up on a map, we need a file that will specify
47boundaries of each country. Luckily, GeoJSON files exist (for free!) on the 52the boundaries of each country. Luckily, GeoJSON files exist (for free!)
48internet. To get the boundaries of every country in the world, we will use the 53on the internet. To get the boundaries of every country in the world, we
49GeoJSON link shown below. 54will use the GeoJSON link shown below.
50 55
51GeoPandas will take this data and load it into a dataframe so that we can easily 56GeoPandas will take this data and load it into a dataframe so that we
52match it to the data we're trying to analyze. Let's look at the GeoJSON 57can easily match it to the data we're trying to analyze. Let's look at
53dataframe: 58the GeoJSON dataframe:
54 59
55#+BEGIN_SRC python 60```python
56# Load the GeoJSON data with geopandas 61# Load the GeoJSON data with geopandas
57geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson') 62geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson')
58geo_data.head() 63geo_data.head()
59#+END_SRC 64```
60 65
61#+CAPTION: GeoJSON Dataframe 66![GeoJSON
62[[https://img.cleberg.net/blog/20200925-world-choropleth-map/geojson_df.png]] 67Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/geojson_df.png)
63 68
64Next, let's load the data from the Kaggle dataset. I've downloaded this file, so 69Next, let's load the data from the Kaggle dataset. I've downloaded
65update the file path if you have it somewhere else. After loading, let's take a 70this file, so update the file path if you have it somewhere else. After
66look at this dataframe: 71loading, let's take a look at this dataframe:
67 72
68#+BEGIN_SRC python 73```python
69# Load the world happiness data with pandas 74# Load the world happiness data with pandas
70happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv') 75happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv')
71happy_data.head() 76happy_data.head()
72#+END_SRC 77```
73 78
74#+CAPTION: Happiness Dataframe 79![Happiness
75[[https://img.cleberg.net/blog/20200925-world-choropleth-map/happiness_df.png]] 80Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/happiness_df.png)
76 81
77* Clean the Data 82# Clean the Data
78 83
79Some countries need to be renamed, or they will be lost when you merge the 84Some countries need to be renamed, or they will be lost when you merge
80happiness and GeoJSON dataframes. This is something I discovered when the map 85the happiness and GeoJSON dataframes. This is something I discovered
81below showed empty countries. I searched both data frames for the missing 86when the map below showed empty countries. I searched both data frames
82countries to see the naming differences. Any countries that do not have records 87for the missing countries to see the naming differences. Any countries
83in the =happy_data= df will not show up on the map. 88that do not have records in the `happy_data` df will not show
89up on the map.
84 90
85#+BEGIN_SRC python 91```python
86# Rename some countries to match our GeoJSON data 92# Rename some countries to match our GeoJSON data
87 93
88# Rename USA 94# Rename USA
@@ -100,37 +106,38 @@ happy_data.at[republic_congo_index, 'Country or region'] = 'Republic of Congo'
100# Rename the DRC 106# Rename the DRC
101democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)'] 107democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)']
102happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo' 108happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo'
103#+END_SRC 109```
104 110
105* Merge the Data 111# Merge the Data
106 112
107Now that we have clean data, we need to merge the GeoJSON data with the 113Now that we have clean data, we need to merge the GeoJSON data with the
108happiness data. Since we've stored them both in dataframes, we just need to call 114happiness data. Since we've stored them both in dataframes, we just
109the =.merge()= function. 115need to call the `.merge()` function.
110 116
111We will also rename a couple columns, just so that they're a little easier to 117We will also rename a couple columns, just so that they're a little
112use when we create the map. 118easier to use when we create the map.
113 119
114#+BEGIN_SRC python 120```python
115# Merge the two previous dataframes into a single geopandas dataframe 121# Merge the two previous dataframes into a single geopandas dataframe
116merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region') 122merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region')
117 123
118# Rename columns for ease of use 124# Rename columns for ease of use
119merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'}) 125merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'})
120merged_df = merged_df.rename(columns = {'Country or region':'Country'}) 126merged_df = merged_df.rename(columns = {'Country or region':'Country'})
121#+END_SRC 127```
122 128
123#+CAPTION: Merged Dataframe 129![Merged
124[[https://img.cleberg.net/blog/20200925-world-choropleth-map/merged_df.png]] 130Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/merged_df.png)
125 131
126* Create the Map 132# Create the Map
127 133
128The data is finally ready to be added to a map. The code below shows the 134The data is finally ready to be added to a map. The code below shows the
129simplest way to find the center of the map and create a Folium map object. The 135simplest way to find the center of the map and create a Folium map
130important part is to remember to reference the merged dataframe for our GeoJSON 136object. The important part is to remember to reference the merged
131data and value data. The columns specify which geo data and value data to use. 137dataframe for our GeoJSON data and value data. The columns specify which
138geo data and value data to use.
132 139
133#+BEGIN_SRC python 140```python
134# Assign centroids to map 141# Assign centroids to map
135x_map = merged_df.centroid.x.mean() 142x_map = merged_df.centroid.x.mean()
136y_map = merged_df.centroid.y.mean() 143y_map = merged_df.centroid.y.mean()
@@ -154,20 +161,21 @@ folium.Choropleth(
154 smooth_factor=0, 161 smooth_factor=0,
155 highlight=True 162 highlight=True
156).add_to(world_map) 163).add_to(world_map)
157#+END_SRC 164```
158 165
159Let's look at the resulting map. 166Let's look at the resulting map.
160 167
161#+CAPTION: Choropleth Map 168![Choropleth
162[[https://img.cleberg.net/blog/20200925-world-choropleth-map/map.png]] 169Map](https://img.cleberg.net/blog/20200925-world-choropleth-map/map.png)
163 170
164* Create a Tooltip on Hover 171# Create a Tooltip on Hover
165 172
166Now that we have a map set up, we could stop. However, I want to add a tooltip 173Now that we have a map set up, we could stop. However, I want to add a
167so that I can see more information about each country. The =tooltip_data= code 174tooltip so that I can see more information about each country. The
168below will show a popup on hover with all the data fields shown. 175`tooltip_data` code below will show a popup on hover with all
176the data fields shown.
169 177
170#+BEGIN_SRC python 178```python
171 # Adding labels to map 179 # Adding labels to map
172 style_function = lambda x: {'fillColor': '#ffffff', 180 style_function = lambda x: {'fillColor': '#ffffff',
173 'color':'#000000', 181 'color':'#000000',
@@ -208,10 +216,10 @@ folium.LayerControl().add_to(world_map)
208 216
209# Display the map 217# Display the map
210world_map 218world_map
211#+END_SRC 219```
212 220
213The final image below will show you what the tooltip looks like whenever you 221The final image below will show you what the tooltip looks like whenever
214hover over a country. 222you hover over a country.
215 223
216#+CAPTION: Choropleth Map Tooltip 224![Choropleth Map
217[[https://img.cleberg.net/blog/20200925-world-choropleth-map/tooltip_map.png]] 225Tooltip](https://img.cleberg.net/blog/20200925-world-choropleth-map/tooltip_map.png)
content/blog/2020-10-12-mediocrity.md added +120
@@ -0,0 +1,120 @@
1+++
2date = 2020-10-12
3title = "On the Pursuit of Mediocrity"
4description = "Musings on mediocrity."
5+++
6
7# Perfect is the Enemy of Good
8
9As the saying goes, "the best is the enemy of the good." As we strive
10for perfection, we often fail to realize the implications of such an
11undertaking. Attempting to reach perfection is often unrealistic. Even
12worse, it can get in the way of achieving a good outcome. In certain
13situations, we try so hard to achieve the ideal solution that we have
14burned the bridges that would have allowed us to reach a lesser yet
15still superb solution.
16
17Philosophers throughout history have inspected this plight from many
18viewpoints. Greek mythology speaks of the [golden
19mean](https://en.wikipedia.org/wiki/Golden_mean_(philosophy)), which
20uses the story of Icarus to illustrate that sometimes "the middle
21course" is the best solution. In this story, Daedalus, a famous artist
22of his time, built feathered wings for himself and his son so that they
23might escape the clutches of King Minos. Daedalus warns his beloved son
24whom he loved so much to "fly the middle course", between the sea
25spray and the sun's heat. Icarus did not heed his father; he flew up
26and up until the sun melted the wax off his wings. For not heeding the
27middle course, he fell into the sea and drowned.
28
29More recently, management scholars have explored the [Pareto
30principle](https://en.wikipedia.org/wiki/Pareto_principle) and found
31that as we increase the frequency of something, or strive to perform
32actions to achieve some form of perfection, we run into [diminishing
33returns](https://en.wikipedia.org/wiki/Diminishing_returns).
34
35Even further, Harold Demsetz is noted as coining the term [the Nirvana
36fallacy](https://en.wikipedia.org/wiki/Nirvana_fallacy) in 1969, which
37shows the fallacy of comparing actual things with unrealistic, idealized
38alternatives. This is another trap that we may fall into, where we are
39constantly thinking of the ultimate solutions to problems, when
40something more realistic needs to be considered.
41
42Over and over throughout history, we've found that perfection is often
43unrealistic and unachievable. However, we push ourselves and our peers
44to "give 100%" or "go the extra mile," while it may be that the
45better course is to give a valuable level of effort while considering
46the effects of further effort on the outcome. Working harder does not
47always help us achieve loftier goals.
48
49This has presented itself to me most recently during my time studying at
50my university. I was anxious and feeling the stresses of my courses,
51career, and personal life for quite a while, which was greatly affecting
52how well I was doing at school and my level of effort at work. One day,
53I happened to be talking to my father when he said something simple that
54hit home:
55
56> All you can do is show up and do your best. Worrying about the
57> outcomes won't affect the outcome itself.
58
59The thought was extremely straightforward and uncomplicated, yet it was
60something that I had lost sight of during my stress-filled years at
61school. Ever since then, I've found myself pausing and remembering that
62quote every time I get anxious or stressed. It helps to stop and think
63"Can I do anything to affect the outcome, or am I simply worrying over
64something I can't change?"
65
66# When Mediocrity Isn't Enough
67
68One problem with the philosophies presented in this post is that they
69are implemented far too often in situations where mediocrity simply
70isn't adequate. For example, let's take a look at digital user data,
71specifically personally-identifiable information (PII). As a
72cybersecurity auditor in the United States, I have found that most
73companies are concerned more with compliance than any actual safeguards
74over the privacy or protection of user data. Other than companies who
75have built their reputation on privacy and security, most companies will
76use [satisficing](https://en.wikipedia.org/wiki/Satisficing) as their
77primary decision-making strategy around user data.
78
79> Satisficing is a decision-making strategy or cognitive heuristic that
80> entails searching through the available alternatives until an
81> acceptability threshold is met.
82
83This means that each decision will be met with certain possible
84solutions until one of the solutions meets their minimum acceptable
85standards. For companies that deal with user data, the
86minimum-acceptable standards come from three areas:
87
881. Laws and regulations
892. Competitive pressure
903. Risk of monetary or reputation loss
91
92Working with project management or auditing, the primary concern here is
93the risk of legal ramifications. Since the primary risk comes from laws
94and regulations, companies will require that any project that involves
95user data must follow all the rules of those laws so that the company
96can protect itself from fines or other penalties.
97
98Following this, companies will consider best practices in order to place
99itself in a competitive position (e.g. Google vs. Apple) and review any
100recent or ongoing litigation against companies regarding user data. In a
101perfect company, management would then consider the ethical
102responsibilities of their organization and discuss their
103responsibilities over things like personally-identifiable information.
104
105However, as we mentioned above, most companies follow the idea of
106satisficing, which states that they have met the minimum acceptable
107standards and can now move on to other decisions. Modern business
108culture in the United States dictates that profits are the golden
109measure of how well a company or manager is performing, so we often
110don't think about our responsibilities beyond these basic standards.
111
112Not all situations demand excellence, but I believe that applying any
113philosophy as a broad stroke across one's life can be a mistake. We
114must be able to think critically about what we are doing as we do it and
115ask ourselves a few questions. Have I done everything I can in this
116situation? Is mediocrity an acceptable outcome, or should we strive for
117perfection, even if we can't attain it?
118
119Taking a few moments to think critically throughout our day, as we make
120decisions, can have a tremendous effect on the outcomes we create.
content/blog/2020-12-27-website-redesign.md added +102
@@ -0,0 +1,102 @@
1+++
2date = 2020-12-27
3title = "Redesigning My Website: The 5 KB Result"
4description = "A retrospective on my recent website redesign."
5+++
6
7# A Brief History
8
9As a form of continuous learning and entertainment, I've been running a
10handful of websites since 2016 when I took my first programming courses
11in college. I maintain one main website, the place I consider the
12official website to represent me. Under this site, I have a handful of
13subdirectories and subdomains.
14
15One of the parts I've enjoyed the most about web development is the
16aspect of designing an identity for a web page and working to find
17exciting ways to display the site's content. Inevitably, this means
18I've changed the designs for my websites more times than I could
19possibly count. Since I don't really host anything on my main webpage
20that's vital, it allows me the freedom to change things as inspiration
21strikes.
22
23Historically, I've relied on core utilities for spacing, components,
24and layouts from [Bootstrap](https://getbootstrap.com) and added custom
25CSS for fonts, accents, colors, and other items. I also tend to create
26sites with no border radius on items, visible borders, and content that
27takes up the entire screen (using whitespace inside components instead
28of whitespace around my components).
29
30# The Redesign Process
31
32About a week ago, I found myself wishing for a new design yet again. The
33prior design was largely inspired by IBM's [Carbon Design
34System](https://www.carbondesignsystem.com) and relied on jQuery,
35Bootstrap, along with some compressed
36[.webp](https://en.wikipedia.org/wiki/WebP) images.
37
38To anyone who knows my preferences toward web design - and even in my
39personal life - it should be no surprise that I immediately started
40looking for inspiration on minimalism. While there are some decent
41minimalistic designs on sites like
42[Dribbble](https://dribbble.com/search/shots/popular/web-design?q=minimalism),
43people seem to mostly discuss [brutalist web
44design](https://brutalist-web.design) when you ask about minimalism.
45While brutalist web design doesn't have to be minimal, it often is.
46
47I suppose, in a way, I did create a brutalist website since my HTML is
48semantic and accessible, hyperlinks are colored and underlined, and all
49native browser functions like scrolling and the back button work as
50expected. However, I didn't think about brutalism while designing these
51sites.
52
53The new design followed a simple design process. I walked through the
54screens on my blog and asked myself: "Is this element necessary for a
55user?" This allowed me to first start by removing all javascript, which
56had the sole purpose of allowing users to open a collapsed navbar on
57mobile. Replacing the collapsible navbar allowed me to remove both
58jQuery and Bootstrap's javascript.
59
60Next, I removed things like author names (since I'm literally the only
61person who will ever write on this site), multiple ways to click on a
62blog post card, blog post descriptions, and the scroll-to-top button. It
63also helped to move all categories to a single page, rather than have
64each category on its own page.
65
66The final big piece to finish the
67"[KonMari](https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method)"-like
68part of my process was to remove Bootstrap CSS in its entirety. However,
69this meant pulling out a few very useful classes, such as `.img-fluid`
70and the default font stacks to keep in my custom CSS.
71
72After removing all the unnecessary pieces, I was finally able to
73reorganize my content and add a very small amount of custom CSS to make
74everything pretty. This took a brief amount of time, effectively just
75consisting of me converting `<div>` tags into things like
76`<ul>` lists and choosing accent colors.
77
78# The Results
79
80## Reflection
81
82So, what did all of this reorganizing do to my webpages? Well, first, my
83websites are now **ridiculously fast**. Since the prior designs were
84also minimal and didn't have many images, they measured up in
85Firefox's Network Monitor around 300 KB - 600KB. After making the
86changes, my main site is at 5 KB transferred (22 KB total), and my blog
87is at 6.5 KB transferred (13 KB total). **That means the redesigned
88pages are less than 2% the size of the old designs.**
89
90Google Lighthouse ranks the new webpage as 100 in performance,
91accessibility, and best practices, with SEO at 92 since they think tap
92targets are not sized appropriately for mobile users. First contextual
93paints of the pages are under 0.8 seconds with 0 ms of blocking time.
94However, the blog subdomain ranks at 100 for all four categories! First
95contextual paints of the blog homepage are under 1.0 seconds with 0 ms
96of blocking time, due to the fact that the CSS for my blog is within a
97separate CSS file, and the CSS for my main website is simply embedded in
98the HTML file.
99
100Now that everything is complete, I can confidently say I'm happy with
101the result and proud to look at the fastest set of websites I've
102created so far.
content/blog/2020-12-28-neon-drive.md added +94
@@ -0,0 +1,94 @@
1+++
2date = 2020-12-28
3title = "Neon Drive: A Nostalgic 80s Arcade Racing Game"
4description = "A video game review for Neon Drive."
5+++
6
7# Game Description
8
9[Neon Drive](https://store.steampowered.com/app/433910/Neon_Drive/)
10presents itself as a simple arcade-style game inspired by the arcade
11race games of the 1980s, yet it has managed to take up hours of my life
12without much effort. The game description, directly from the Steam page,
13is intriguing enough to entice anyone who's been looking for a good
14arcade racing game:
15
16> Neon Drive is a slick retro-futuristic arcade game that will make your
17> brain melt. You've been warned. From beautiful cityscapes and ocean
18> roads to exploding enemy spaceships, Neon Drive has it all.
19
20# Gameplay
21
22The game holds true to the
23[retro-futurism](https://en.wikipedia.org/wiki/Retrofuturism) style,
24including chrome female robots, pixelated arcade machines, and
25[outrun](https://teddit.net/r/outrun/) aesthetics.
26
27Each level of the game is shown as a separate arcade machine. Each
28arcade machine lets you play on Normal, Hard, Insane, Practice, and Free
29Run. To beat each arcade, you must reach the end of the level without
30crashing your car into the various obstacles on the course. Basic levels
31let you move left or right to avoid blocks in the road. Later levels put
32you through other tests, such as dodging traffic or blasting asteroids.
33
34The game uses synthwave music to keep you on track to make the correct
35moves by timing the beats of the songs to the correct moves on the
36screen. It reminds me of the early Guitar Hero games, as well as mobile
37apps like VOEZ - repetition and staying on-beat is the only way to win.
38
39# In-Game Screenshots
40
41Taking a look at the main menu, you can see that Neon Drive plays into
42every stereotype you can think of around retro-futuristic, synthwave
43arcades (in a good way).
44
45![Neon Drive
46Menu](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_menu.png)
47
48Once you get into the first level, we see that the choice of car fits
49right in with the stereotypical cars of the 80s, like the
50[DeLorean](https://en.wikipedia.org/wiki/DMC_DeLorean) or the [Ferrari
51F40](https://en.wikipedia.org/wiki/Ferrari_F40). Each new level comes
52with new color schemes and cars, so you should never get tired of the
53aesthetic.
54
55![Neon Drive
56Race](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_race.png)
57
58Personally, I love the orange and blue colors used in level 2:
59
60![Level
612](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_level_2.png)
62
63If you're the competitive type and getting 100% on all arcade machines
64isn't enough, there are leaderboards for the regular part of the game,
65and the endurance game mode.
66
67![Leaderboard](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_leaderboard.png)
68
69# Other Suggestions
70
71Neon Drive sits nicely within the well-founded cult genre of Outrun.
72Other games that I've enjoyed in this same spectrum are:
73
74- [Far Cry 3: Blood
75 Dragon](https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/)
76- [Retrowave](https://store.steampowered.com/app/1239690/Retrowave/)
77- [Slipstream](https://store.steampowered.com/app/732810/Slipstream/)
78
79Although these games aren't necessarily in the same genre, they do have
80aspects that place them close enough to interest gamers that enjoyed
81Neon Drive:
82
83- [Black Ice](https://store.steampowered.com/app/311800/Black_Ice/)
84- [Cloudpunk](https://store.steampowered.com/app/746850/Cloudpunk/)
85- [Need for Speed:
86 Heat](https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/)
87- [VirtuaVerse](https://store.steampowered.com/app/1019310/VirtuaVerse/)
88
89Of course, if all you really care about is the arcade aspect of these
90games, you can check out the [Atari
91Vault](https://store.steampowered.com/app/400020/Atari_Vault/) or any of
92the other classic games sold on Steam by companies like Namco, Atari.
93For something like Nintendo, you'd have to settle for buying used
94classic consoles or delve into the world of emulation.
content/blog/2020-12-29-zork.md added +95
@@ -0,0 +1,95 @@
1+++
2date = 2020-12-29
3title = "Zork: Let's Explore a Classic"
4description = "A video game review for Zork."
5+++
6
7# Download (Free)
8
9Before we get into the game itself, you should know that you can
10download Zork for free from Infocom's [download
11page](http://infocom-if.org/downloads/downloads.html). So feel free to
12boot it up and take a ride back to the 1980s with this masterpiece.
13
14# Game Description
15
16Zork is an interactive, text-based computer game originally released in
171980. This series, split into three separate games, introduced a robust
18and sophisticated text parser to gamers. People were largely used to the
19simple commands used in the popular game [Colossal Cave
20Adventure](https://en.wikipedia.org/wiki/Colossal_Cave_Adventure), but
21Zork allowed users to send more complex commands that included
22prepositions and conjunctions.
23
24Zork tracks your score as you explore the map, find tools, and collect
25trophy items (e.g., a jewel-encrusted egg). When you place your trophy
26items in the trophy case found in the Living Room area, you gain score
27points. Collecting the Twenty Treasures of Zork and placing them within
28the trophy case wins the game. However, you must explore the map, solve
29puzzles, and avoid being eaten by a grue to collect these treasures.
30
31# The Map
32
33Since Zork is a vast and complex game, it helps to have a map as you
34explore and collect your trophies. However, if you want to play the game
35as it was truly intended, you should try to play it without using the
36map.
37
38![Zork Map](https://img.cleberg.net/blog/20201229-zork/zork_map.png)
39
40*[Map Source](https://www.filfre.net/2012/01/exploring-zork-part-1/)*
41
42# In-Game Screenshots
43
44After playing the game (for the first time ever) for several weeks
45around 2014, I was finally able to beat the game with some online help
46to find the last couple items. As I was writing this post, I installed
47the game again to grab some screenshots to show off the true glory of
48this game. As noted in [Jimmy Maher's
49playthrough](https://www.filfre.net/2012/01/exploring-zork-part-1/), the
50original Zork games looked quite a bit different due to the older
51hardware of computers like the Apple II and multiple bug fixes that
52Infocom pushed out after the game's initial release. My play-through
53uses the [Zork
54Anthology](https://store.steampowered.com/app/570580/Zork_Anthology/)
55version, which utilizes DOSBox on Windows.
56
57The first screenshot here shows the introductory information, which
58doesn't include instructions of any kind for the player. If you
59haven't played text adventures before, try to use simple commands like
60"go west," "look around," or "hit troll with elvish sword."
61
62![Zork Screen, pt.
631](https://img.cleberg.net/blog/20201229-zork/zork_01.png)
64
65In this second screenshot, we see the player has entered the house and
66found the trophy case in the living room. The lantern and sword in this
67room allow the player to explore dark areas and attack enemies. If you
68don't use the lantern, you won't be able to see anything in dark
69areas, and you may be eaten by a grue.
70
71![Zork Screen, pt.
722](https://img.cleberg.net/blog/20201229-zork/zork_02.png)
73
74Finally, we see that the player has found the first treasure: a
75jewel-encrusted egg. These treasures can be taken back to the house and
76placed in the trophy case or carried until you feel like you want to put
77things away.
78
79![Zork Screen, pt
803.](https://img.cleberg.net/blog/20201229-zork/zork_03.png)
81
82# Conclusion
83
84It's been quite a few years since I first played Zork, but I clearly
85remember the late nights and bloodshot eyes that helped me find all the
86treasures. This game is well worth the time and effort, even though the
87text-based aspect may be off-putting to gamers who didn't have to grow
88up playing games without graphics. However, I believe that the strategy
89and skills learned in early video games like Zork can actually help you,
90even when playing newer games.
91
92If you do decide to play Zork, you can download Zork I, II, and III from
93Infocom's [download
94page](http://infocom-if.org/downloads/downloads.html) for free or search
95the internet for an online version.
blog/2021-01-01-seum.org → content/blog/2021-01-01-seum.md renamed +42 −57
@@ -1,32 +1,27 @@
1#+title: SEUM: Speedrunners from Hell 1+++
2#+date: 2021-01-01 2date = 2021-01-01
3 3title = "SEUM: Speedrunners from Hell"
4** Game Description 4description = "A video game review for SEUM: Speedrunners from Hell."
5:PROPERTIES: 5+++
6:CUSTOM_ID: game-description 6
7:ID: 809DA4E6-4E0D-4179-B8FF-6C9E767EC74D 7## Game Description {#game-description id="809DA4E6-4E0D-4179-B8FF-6C9E767EC74D"}
8:END: 8
9[[https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/][SEUM: 9[SEUM: Speedrunners from
10Speedrunners from Hell]] is an incredibly fast-paced mashup of a puzzle 10Hell](https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/)
11game and a free-running game. Created by [[https://pinestudio.co][Pine 11is an incredibly fast-paced mashup of a puzzle game and a free-running
12Studio]] in early 2016 and first released as a 12game. Created by [Pine Studio](https://pinestudio.co) in early 2016 and
13[[https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868][demo 13first released as a [demo on
14on GameJolt]], this game was successfully green-lit on Steam and has 14GameJolt](https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868),
15amassed a cult following on multiple consoles. 15this game was successfully green-lit on Steam and has amassed a cult
16following on multiple consoles.
16 17
17Here's the game description straight from the developers: 18Here's the game description straight from the developers:
18 19
19#+begin_quote 20> Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners
20Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners 21> from Hell is truly hardcore and focuses on speed and fast reaction.
21from Hell is truly hardcore and focuses on speed and fast reaction.
22 22
23#+end_quote 23## Story {#story id="BF401145-763D-4399-922B-7D73322B5B40"}
24 24
25** Story
26:PROPERTIES:
27:CUSTOM_ID: story
28:ID: BF401145-763D-4399-922B-7D73322B5B40
29:END:
30SEUM does a great job setting the stage when you open the game for the 25SEUM does a great job setting the stage when you open the game for the
31first time, playing an animated video in the form of comic book images. 26first time, playing an animated video in the form of comic book images.
32You see Marty, the protagonist, sitting around drinking his beer as 27You see Marty, the protagonist, sitting around drinking his beer as
@@ -38,14 +33,10 @@ use all the powers in the game.
38Check out the screenshot below for one of the first panels of the 33Check out the screenshot below for one of the first panels of the
39storyline: 34storyline:
40 35
41#+caption: SEUM Story 36![SEUM Story](https://img.cleberg.net/blog/20210101-seum/seum_story.png)
42[[https://img.cleberg.net/blog/20210101-seum/seum_story.png]] 37
38## Gameplay {#gameplay id="A55CB1AF-E818-4CA7-BF69-E8141369C269"}
43 39
44** Gameplay
45:PROPERTIES:
46:CUSTOM_ID: gameplay
47:ID: A55CB1AF-E818-4CA7-BF69-E8141369C269
48:END:
49To accomplish each level, you will need to get to the final blue portal 40To accomplish each level, you will need to get to the final blue portal
50under a certain time limit. You can beat a level by getting to the 41under a certain time limit. You can beat a level by getting to the
51portal before the time meter runs out or "Dominate" a level by beating 42portal before the time meter runs out or "Dominate" a level by beating
@@ -58,50 +49,44 @@ to go through any existing orange portals, light all yellow beacons,
58avoid things like fireballs and blades, or use any satanic power orbs 49avoid things like fireballs and blades, or use any satanic power orbs
59lying around. These special abilities include: 50lying around. These special abilities include:
60 51
61- Gravity 52- Gravity
62- Teleport 53- Teleport
63- Rewind 54- Rewind
64- Spawn platform 55- Spawn platform
65- Roar (DLC) 56- Roar (DLC)
66- Rocket (DLC) 57- Rocket (DLC)
67- Shadow world (DLC) 58- Shadow world (DLC)
68 59
69For the main storyline, there are nine floors to beat. Each floor 60For the main storyline, there are nine floors to beat. Each floor
70contains nine regular levels, one boss level, and one bonus level; 61contains nine regular levels, one boss level, and one bonus level;
71although you don't technically need to beat all levels to advance to the 62although you don't technically need to beat all levels to advance to
72next floor. 63the next floor.
64
65![SEUM Floor
66Menu](https://img.cleberg.net/blog/20210101-seum/seum_floor.png)
73 67
74#+caption: SEUM Floor Menu 68## In-Game Screenshots {#in-game-screenshots id="C779F008-4C8A-4BA4-AA31-60A1BF5A3EE3"}
75[[https://img.cleberg.net/blog/20210101-seum/seum_floor.png]]
76 69
77** In-Game Screenshots
78:PROPERTIES:
79:CUSTOM_ID: in-game-screenshots
80:ID: C779F008-4C8A-4BA4-AA31-60A1BF5A3EE3
81:END:
82The main menu gives you plenty of options for tuning your system, 70The main menu gives you plenty of options for tuning your system,
83playing main levels, playing the DLC, or exploring the other game modes. 71playing main levels, playing the DLC, or exploring the other game modes.
84 72
85#+caption: SEUM Main Menu 73![SEUM Main
86[[https://img.cleberg.net/blog/20210101-seum/seum_menu.png]] 74Menu](https://img.cleberg.net/blog/20210101-seum/seum_menu.png)
87 75
88Once you enter a level, you can look around and strategize before 76Once you enter a level, you can look around and strategize before
89starting. Clicking any button will start the menu, and you'll have to 77starting. Clicking any button will start the menu, and you'll have to
90restart if you die. One of the great things about SEUM is that it has 78restart if you die. One of the great things about SEUM is that it has
91great keyboard shortcuts. You can quickly smash =R= to restart the level 79great keyboard shortcuts. You can quickly smash `R` to
92or =M= to return to the level menu. 80restart the level or `M` to return to the level menu.
93 81
94#+caption: SEUM Level 82![SEUM Level](https://img.cleberg.net/blog/20210101-seum/seum_level.png)
95[[https://img.cleberg.net/blog/20210101-seum/seum_level.png]]
96 83
97Once you're able to win a level, you'll see the high scores and can 84Once you're able to win a level, you'll see the high scores and can
98watch replays of the best scores. 85watch replays of the best scores.
99 86
100#+caption: SEUM Win 87![SEUM Win](https://img.cleberg.net/blog/20210101-seum/seum_win.png)
101[[https://img.cleberg.net/blog/20210101-seum/seum_win.png]]
102 88
103Each regular level contains a beer in a disguised location that may take 89Each regular level contains a beer in a disguised location that may take
104some out-of-the-box thinking. 90some out-of-the-box thinking.
105 91
106#+caption: SEUM Beer 92![SEUM Beer](https://img.cleberg.net/blog/20210101-seum/seum_beer.png)
107[[https://img.cleberg.net/blog/20210101-seum/seum_beer.png]]
blog/2021-01-04-fediverse.org → content/blog/2021-01-04-fediverse.md renamed +58 −75
@@ -1,10 +1,11 @@
1#+title: A Simple Guide to the Fediverse 1+++
2#+date: 2021-01-04 2date = 2021-01-04
3title = "A Simple Guide to the Fediverse"
4description = "Learn about the basics of the Fediverse."
5+++
6
7## What is the Fediverse?
3 8
4** What is the Fediverse?
5:PROPERTIES:
6:CUSTOM_ID: what-is-the-fediverse
7:END:
8The fediverse is a federated universe of servers commonly used for 9The fediverse is a federated universe of servers commonly used for
9sharing content, like social media. So, instead of having to rely on a 10sharing content, like social media. So, instead of having to rely on a
10single organization to run the server (e.g. Facebook), the fediverse is 11single organization to run the server (e.g. Facebook), the fediverse is
@@ -21,8 +22,8 @@ and communicate back and forth without having to be on the same server.
21However, responsible email admins are there to set rules and control the 22However, responsible email admins are there to set rules and control the
22traffic going in/out of the server. 23traffic going in/out of the server.
23 24
24#+caption: Federated services diagram 25![Federated services
25[[https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg]] 26diagram](https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg)
26 27
27The main objective of this architecture is to decentralize the control 28The main objective of this architecture is to decentralize the control
28within the internet connections. For example, if you run your own 29within the internet connections. For example, if you run your own
@@ -35,12 +36,13 @@ This strategy is great for making sure control of the social web isn't
35controlled by a single organization, but it also has some downsides. If 36controlled by a single organization, but it also has some downsides. If
36I create a Mastodon instance and get a ton of users to sign up, I can 37I create a Mastodon instance and get a ton of users to sign up, I can
37shut the server down at any time. That means you're at risk of losing 38shut the server down at any time. That means you're at risk of losing
38the content you've created unless you back it up, or the server backs it 39the content you've created unless you back it up, or the server backs
39up for you. Also, depending on the software used (e.g. Mastodon, 40it up for you. Also, depending on the software used (e.g. Mastodon,
40Pixelfed, etc.), censorship may still be an issue if the server admins 41Pixelfed, etc.), censorship may still be an issue if the server admins
41decide they want to censor their users. Now, censorship isn't always a 42decide they want to censor their users. Now, censorship isn't always a
42bad thing and can even benefit the community as a whole, but you'll want 43bad thing and can even benefit the community as a whole, but you'll
43to determine which servers align with your idea of proper censorship. 44want to determine which servers align with your idea of proper
45censorship.
44 46
45However, these are risks that we take when we sign up for any online 47However, these are risks that we take when we sign up for any online
46platform. Whatever your reason is for trying out federated social 48platform. Whatever your reason is for trying out federated social
@@ -50,74 +52,55 @@ with the increased difficulty understanding and signing up for these
50platforms. Perhaps increased regulation and litigation against current 52platforms. Perhaps increased regulation and litigation against current
51social media sites will push more users into the fediverse. 53social media sites will push more users into the fediverse.
52 54
53** Federated Alternatives to Popular Sites 55## Federated Alternatives to Popular Sites
54:PROPERTIES: 56
55:CUSTOM_ID: federated-alternatives-to-popular-sites
56:END:
57The list below is a small guide that will show you federated 57The list below is a small guide that will show you federated
58alternatives to current popular websites. There are many more out there, 58alternatives to current popular websites. There are many more out there,
59so go and explore: you might just find the perfect home. 59so go and explore: you might just find the perfect home.
60 60
61*** Reddit 61### Reddit
62:PROPERTIES: 62
63:CUSTOM_ID: reddit 63- [Lemmy](https://lemmy.ml/instances)
64:END: 64
65- [[https://lemmy.ml/instances][Lemmy]] 65### Twitter/Facebook/Tumblr
66 66
67*** Twitter/Facebook/Tumblr 67- [Mastodon](https://joinmastodon.org)
68:PROPERTIES: 68- [Diaspora](https://diasporafoundation.org)
69:CUSTOM_ID: twitterfacebooktumblr 69- [Friendica](https://friendi.ca)
70:END: 70- [GNU Social](https://gnusocial.network)
71- [[https://joinmastodon.org][Mastodon]] 71- [Pleroma](https://pleroma.social)
72- [[https://diasporafoundation.org][Diaspora]] 72
73- [[https://friendi.ca][Friendica]] 73### Instagram
74- [[https://gnusocial.network][GNU Social]] 74
75- [[https://pleroma.social][Pleroma]] 75- [Pixelfed](https://pixelfed.org)
76 76
77*** Instagram 77### Slack/Discord
78:PROPERTIES: 78
79:CUSTOM_ID: instagram 79- [Matrix](https://element.io)
80:END: 80
81- [[https://pixelfed.org][Pixelfed]] 81### Youtube/Vimeo
82 82
83*** Slack/Discord 83- [Peertube](https://joinpeertube.org)
84:PROPERTIES: 84
85:CUSTOM_ID: slackdiscord 85### Spotify/Soundcloud
86:END: 86
87- [[https://element.io][Matrix]] 87- [Funkwhale](https://funkwhale.audio)
88 88
89*** Youtube/Vimeo 89### Podcasting
90:PROPERTIES: 90
91:CUSTOM_ID: youtubevimeo 91- [Pubcast](https://pubcast.pub)
92:END: 92
93- [[https://joinpeertube.org][Peertube]] 93### Medium/Blogger
94 94
95*** Spotify/Soundcloud 95- [WriteFreely](https://writefreely.org)
96:PROPERTIES: 96
97:CUSTOM_ID: spotifysoundcloud 97## Get Started
98:END: 98
99- [[https://funkwhale.audio][Funkwhale]]
100
101*** Podcasting
102:PROPERTIES:
103:CUSTOM_ID: podcasting
104:END:
105- [[https://pubcast.pub][Pubcast]]
106
107*** Medium/Blogger
108:PROPERTIES:
109:CUSTOM_ID: mediumblogger
110:END:
111- [[https://writefreely.org][WriteFreely]]
112
113** Get Started
114:PROPERTIES:
115:CUSTOM_ID: get-started
116:END:
117The best way to get started is to simply sign up and learn as you go. If 99The best way to get started is to simply sign up and learn as you go. If
118you're comfortable signing up through a Mastodon, Pleroma, or Friendica 100you're comfortable signing up through a Mastodon, Pleroma, or Friendica
119server, here is [[https://fediverse.party/en/portal/servers][a list of 101server, here is [a list of themed
120themed servers]] to choose from. If you're looking for something else, 102servers](https://fediverse.party/en/portal/servers) to choose from. If
121try a web search for a federated alternative to your favorite sites. 103you're looking for something else, try a web search for a federated
104alternative to your favorite sites.
122 105
123Find a server that focuses on your passions and start there! 106Find a server that focuses on your passions and start there!
blog/2021-01-07-ufw.org → content/blog/2021-01-07-ufw.md renamed +86 −100
@@ -1,10 +1,11 @@
1#+title: Secure Your Network with the Uncomplicated Firewall 1+++
2#+date: 2021-01-07 2date = 2021-01-07
3title = "Secure Your Network with the Uncomplicated Firewall"
4description = "A simple guide to the UFW."
5+++
6
7## Uncomplicated Firewall
3 8
4** Uncomplicated Firewall
5:PROPERTIES:
6:CUSTOM_ID: uncomplicated-firewall
7:END:
8Uncomplicated Firewall, also known as ufw, is a convenient and 9Uncomplicated Firewall, also known as ufw, is a convenient and
9beginner-friendly way to enforce OS-level firewall rules. For those who 10beginner-friendly way to enforce OS-level firewall rules. For those who
10are hosting servers or any device that is accessible to the world (i.e., 11are hosting servers or any device that is accessible to the world (i.e.,
@@ -14,160 +15,147 @@ implemented and active.
14Ufw is available by default in all Ubuntu installations after 8.04 LTS. 15Ufw is available by default in all Ubuntu installations after 8.04 LTS.
15For other distributions, you can look to install ufw or check if there 16For other distributions, you can look to install ufw or check if there
16are alternative firewalls installed already. There are usually 17are alternative firewalls installed already. There are usually
17alternatives available, such as Fedora's =firewall= and the package 18alternatives available, such as Fedora's `firewall` and the
18available on most distributions: =iptables=. Ufw is considered a 19package available on most distributions: `iptables`. Ufw is
19beginner-friendly front-end to iptables. 20considered a beginner-friendly front-end to iptables.
20 21
21[[https://gufw.org][Gufw]] is available as a graphical user interface 22[Gufw](https://gufw.org) is available as a graphical user interface
22(GUI) application for users who are uncomfortable setting up a firewall 23(GUI) application for users who are uncomfortable setting up a firewall
23through a terminal. 24through a terminal.
24 25
25[[https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png]] 26![](https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png)
27
28## Getting Help
26 29
27** Getting Help
28:PROPERTIES:
29:CUSTOM_ID: getting-help
30:END:
31If you need help figuring out commands, remember that you can run the 30If you need help figuring out commands, remember that you can run the
32=--help= flag to get a list of options. 31`--help` flag to get a list of options.
33 32
34#+begin_src sh 33```sh
35sudo ufw --help 34sudo ufw --help
36#+end_src 35```
36
37## Set Default State
37 38
38** Set Default State
39:PROPERTIES:
40:CUSTOM_ID: set-default-state
41:END:
42The proper way to run a firewall is to set a strict default state and 39The proper way to run a firewall is to set a strict default state and
43slowly open up ports that you want to allow. This helps prevent anything 40slowly open up ports that you want to allow. This helps prevent anything
44malicious from slipping through the cracks. The following command 41malicious from slipping through the cracks. The following command
45prevents all incoming traffic (other than the rules we specify later), 42prevents all incoming traffic (other than the rules we specify later),
46but you can also set this for outgoing connections, if necessary. 43but you can also set this for outgoing connections, if necessary.
47 44
48#+begin_src sh 45```sh
49sudo ufw default deny incoming 46sudo ufw default deny incoming
50#+end_src 47```
51 48
52You should also allow outgoing traffic if you want to allow the device 49You should also allow outgoing traffic if you want to allow the device
53to communicate back to you or other parties. For example, media servers 50to communicate back to you or other parties. For example, media servers
54like Plex need to be able to send out data related to streaming the 51like Plex need to be able to send out data related to streaming the
55media. 52media.
56 53
57#+begin_src sh 54```sh
58sudo ufw default allow outgoing 55sudo ufw default allow outgoing
59#+end_src 56```
60 57
61** Adding Port Rules 58## Adding Port Rules
62:PROPERTIES:
63:CUSTOM_ID: adding-port-rules
64:END:
65Now that we've disabled all incoming traffic by default, we need to open
66up some ports (or else no traffic would be able to come in). If you need
67to be able to =ssh= into the machine, you'll need to open up port 22.
68 59
69#+begin_src sh 60Now that we've disabled all incoming traffic by default, we need to
61open up some ports (or else no traffic would be able to come in). If you
62need to be able to `ssh` into the machine, you'll need to
63open up port 22.
64
65```sh
70sudo ufw allow 22 66sudo ufw allow 22
71#+end_src 67```
72 68
73You can also issue more restrictive rules. The following rule will allow 69You can also issue more restrictive rules. The following rule will allow
74=ssh= connections only from machines on the local subnet. 70`ssh` connections only from machines on the local subnet.
75 71
76#+begin_src sh 72```sh
77sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22 73sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22
78#+end_src 74```
79 75
80If you need to set a rule that isn't tcp, just append your connection 76If you need to set a rule that isn't tcp, just append your connection
81type to the end of the rule. 77type to the end of the rule.
82 78
83#+begin_src sh 79```sh
84sudo ufw allow 1900/udp 80sudo ufw allow 1900/udp
85#+end_src 81```
82
83## Enable ufw
86 84
87** Enable ufw
88:PROPERTIES:
89:CUSTOM_ID: enable-ufw
90:END:
91Now that the firewall is configured and ready to go, you can enable the 85Now that the firewall is configured and ready to go, you can enable the
92firewall. 86firewall.
93 87
94#+begin_src sh 88```sh
95sudo ufw enable 89sudo ufw enable
96#+end_src 90```
97 91
98A restart may be required for the firewall to begin operating. 92A restart may be required for the firewall to begin operating.
99 93
100#+begin_src sh 94```sh
101sudo reboot now 95sudo reboot now
102#+end_src 96```
97
98## Checking Status
103 99
104** Checking Status
105:PROPERTIES:
106:CUSTOM_ID: checking-status
107:END:
108Now that the firewall is enabled, let's check and see what the rules 100Now that the firewall is enabled, let's check and see what the rules
109look like. 101look like.
110 102
111#+begin_src sh 103```sh
112sudo ufw status numbered 104sudo ufw status numbered
113#+end_src 105```
114 106
115#+begin_src txt 107```txt
116Status: active 108Status: active
117 109
118 To Action From 110 To Action From
119 -- ------ ---- 111 -- ------ ----
120[ 1] 22 ALLOW IN Anywhere 112[ 1] 22 ALLOW IN Anywhere
121[ 2] 22 (v6) ALLOW IN Anywhere (v6) 113[ 2] 22 (v6) ALLOW IN Anywhere (v6)
122#+end_src 114```
115
116## Deleting Rules
123 117
124** Deleting Rules
125:PROPERTIES:
126:CUSTOM_ID: deleting-rules
127:END:
128If you need to delete a rule, you need to know the number associated 118If you need to delete a rule, you need to know the number associated
129with that rule. Let's delete the first rule in the table above. You'll 119with that rule. Let's delete the first rule in the table above. You'll
130be asked to confirm the deletion as part of this process. 120be asked to confirm the deletion as part of this process.
131 121
132#+begin_src sh 122```sh
133sudo ufw delete 1 123sudo ufw delete 1
134#+end_src 124```
125
126## Managing App Rules
135 127
136** Managing App Rules
137:PROPERTIES:
138:CUSTOM_ID: managing-app-rules
139:END:
140Luckily, there's a convenient way for installed applications to create 128Luckily, there's a convenient way for installed applications to create
141files that ufw can easily implement so that you don't have to search and 129files that ufw can easily implement so that you don't have to search
142find which ports your application requires. To see if your device has 130and find which ports your application requires. To see if your device
143any applications with pre-installed ufw rules, execute the following 131has any applications with pre-installed ufw rules, execute the following
144command: 132command:
145 133
146#+begin_src sh 134```sh
147sudo ufw app list 135sudo ufw app list
148#+end_src 136```
149 137
150The results should look something like this: 138The results should look something like this:
151 139
152#+begin_src txt 140```txt
153Available applications: 141Available applications:
154 OpenSSH 142 OpenSSH
155 Samba 143 Samba
156 plexmediaserver 144 plexmediaserver
157 plexmediaserver-all 145 plexmediaserver-all
158 plexmediaserver-dlna 146 plexmediaserver-dlna
159#+end_src 147```
160 148
161If you want to get more information on a specific app rule, use the 149If you want to get more information on a specific app rule, use the
162=info= command. 150`info` command.
163 151
164#+begin_src sh 152```sh
165sudo ufw app info plexmediaserver-dlna 153sudo ufw app info plexmediaserver-dlna
166#+end_src 154```
167 155
168You'll get a blurb of info back like this: 156You'll get a blurb of info back like this:
169 157
170#+begin_src txt 158```txt
171Profile: plexmediaserver-dlna 159Profile: plexmediaserver-dlna
172Title: Plex Media Server (DLNA) 160Title: Plex Media Server (DLNA)
173Description: The Plex Media Server (additional DLNA capability only) 161Description: The Plex Media Server (additional DLNA capability only)
@@ -175,31 +163,29 @@ Description: The Plex Media Server (additional DLNA capability only)
175Ports: 163Ports:
176 1900/udp 164 1900/udp
177 32469/tcp 165 32469/tcp
178#+end_src 166```
179 167
180You can add or delete app rules the same way that you'd add or delete 168You can add or delete app rules the same way that you'd add or delete
181specific port rules. 169specific port rules.
182 170
183#+begin_src sh 171```sh
184sudo ufw allow plexmediaserver-dlna 172sudo ufw allow plexmediaserver-dlna
185#+end_src 173```
186 174
187#+begin_src sh 175```sh
188sudo ufw delete RULE|NUM 176sudo ufw delete RULE|NUM
189#+end_src 177```
178
179## Creating App Rules
190 180
191** Creating App Rules
192:PROPERTIES:
193:CUSTOM_ID: creating-app-rules
194:END:
195If you'd like to create you own app rule, you'll need to create a file 181If you'd like to create you own app rule, you'll need to create a file
196in the =/etc/ufw/applications.d= directory. Within the file you create, 182in the `/etc/ufw/applications.d` directory. Within the file
197you need to make sure the content is properly formatted. 183you create, you need to make sure the content is properly formatted.
198 184
199For example, here are the contents my =plexmediaserver= file, which 185For example, here are the contents my `plexmediaserver` file,
200creates three distinct app rules for ufw: 186which creates three distinct app rules for ufw:
201 187
202#+begin_src config 188```config
203[plexmediaserver] 189[plexmediaserver]
204title=Plex Media Server (Standard) 190title=Plex Media Server (Standard)
205description=The Plex Media Server 191description=The Plex Media Server
@@ -214,24 +200,24 @@ ports=1900/udp|32469/tcp
214title=Plex Media Server (Standard + DLNA) 200title=Plex Media Server (Standard + DLNA)
215description=The Plex Media Server (with additional DLNA capability) 201description=The Plex Media Server (with additional DLNA capability)
216ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp 202ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp
217#+end_src 203```
218 204
219So, if I wanted to create a custom app rule called "mycustomrule," I'd 205So, if I wanted to create a custom app rule called "mycustomrule,"
220create a file and add my content like this: 206I'd create a file and add my content like this:
221 207
222#+begin_src sh 208```sh
223sudo nano /etc/ufw/applications.d/mycustomrule 209sudo nano /etc/ufw/applications.d/mycustomrule
224#+end_src 210```
225 211
226#+begin_src config 212```config
227[mycustomrule] 213[mycustomrule]
228title=My Custom Rule 214title=My Custom Rule
229description=This is a temporary ufw app rule. 215description=This is a temporary ufw app rule.
230ports=88/tcp|9100/udp 216ports=88/tcp|9100/udp
231#+end_src 217```
232 218
233Then, I would just enable this rule in ufw. 219Then, I would just enable this rule in ufw.
234 220
235#+begin_src sh 221```sh
236sudo ufw allow mycustomrule 222sudo ufw allow mycustomrule
237#+end_src 223```
blog/2021-02-19-macos.org → content/blog/2021-02-19-macos.md renamed +100 −111
@@ -1,10 +1,11 @@
1#+title: macOS: Testing Out A New OS 1+++
2#+date: 2021-02-19 2date = 2021-02-19
3title = "macOS: Testing Out A New OS"
4description = "A retrospective on my migration from Linux to macOS."
5+++
6
7## Diving into macOS
3 8
4** Diving into macOS
5:PROPERTIES:
6:CUSTOM_ID: diving-into-macos
7:END:
8After spending nearly 15 years working with Windows and 8 years on 9After spending nearly 15 years working with Windows and 8 years on
9Linux, I have experienced macOS for the first time. By chance, my spouse 10Linux, I have experienced macOS for the first time. By chance, my spouse
10happened to buy a new MacBook and gifted me their 2013 model. Of course, 11happened to buy a new MacBook and gifted me their 2013 model. Of course,
@@ -13,24 +14,22 @@ around for gaming needs, but over the past week I've found myself using
13the MacBook more and more for things that don't require gaming specs or 14the MacBook more and more for things that don't require gaming specs or
14advanced dev tools. 15advanced dev tools.
15 16
16** Initial Thoughts 17## Initial Thoughts
17:PROPERTIES: 18
18:CUSTOM_ID: initial-thoughts
19:END:
20Before I move on to the technical aspects of my set-up, I want to take 19Before 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. 20some time and express my thoughts on the overall OS.
22 21
23#+caption: macOS Desktop 22![macOS
24[[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png]] 23Desktop](https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png)
25 24
26As expected, the initial computer setup is a breeze with Mac's guided 25As expected, the initial computer setup is a breeze with Mac's guided
27GUI installer. 26GUI installer.
28 27
29The desktop itself reminds me of GNOME more than anything else I've 28The desktop itself reminds me of GNOME more than anything else I've
30seen: even Pantheon from [[https://elementary.io/][ElementaryOS]], which 29seen: even Pantheon from [ElementaryOS](https://elementary.io/), which
31people commonly refer to as the closest Linux distro to macOS. The 30people commonly refer to as the closest Linux distro to macOS. The
32desktop toolbar is great and far surpasses the utility of the GNOME 31desktop toolbar is great and far surpasses the utility of the GNOME
33toolbar due to the fact that the extensions and icons /actually work/. I 32toolbar due to the fact that the extensions and icons *actually work*. I
34launch macOS and immediately see my shortcuts for Tresorit, Bitwarden, 33launch macOS and immediately see my shortcuts for Tresorit, Bitwarden,
35and Mullvad pop up as the computer loads. 34and Mullvad pop up as the computer loads.
36 35
@@ -52,10 +51,8 @@ The 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 51environment to make me feel more at-home, similar to the environments I
53set up on Fedora, Ubuntu, etc. 52set up on Fedora, Ubuntu, etc.
54 53
55** Making it Feel Like Home 54## Making it Feel Like Home
56:PROPERTIES: 55
57:CUSTOM_ID: making-it-feel-like-home
58:END:
59If you're someone who uses Linux primarily, no doubt your first thought 56If you're someone who uses Linux primarily, no doubt your first thought
60when booting macOS will be the same as mine was: "Where is the terminal 57when booting macOS will be the same as mine was: "Where is the terminal
61and how do I set up my favorite utilities?" 58and how do I set up my favorite utilities?"
@@ -63,163 +60,155 @@ and how do I set up my favorite utilities?"
63Luckily, macOS hasn't completely hidden away the development tools from 60Luckily, macOS hasn't completely hidden away the development tools from
64the average user. You can easily find the Terminal app in the Launchpad 61the average user. You can easily find the Terminal app in the Launchpad
65area, but it's probably not what you're used to. I was surprised (and 62area, but it's probably not what you're used to. I was surprised (and
66happy) to see that the default shell is =zsh=, the shell I use on all of 63happy) to see that the default shell is `zsh`, the shell I
67my Linux distros. However, the commands are not the same - even the ones 64use on all of my Linux distros. However, the commands are not the same -
68you may think are native to the shell. Commands like =dir= do not exist, 65even the ones you may think are native to the shell. Commands like
69so other native commands like =ls -la= or =pwd= are more useful here. 66`dir` do not exist, so other native commands like
67`ls -la` or `pwd` are more useful here.
70 68
71With only a few minutes of installing and tweaking a few packages, I was 69With only a few minutes of installing and tweaking a few packages, I was
72able to recreate a terminal environment that I feel very comfortable 70able to recreate a terminal environment that I feel very comfortable
73using. See the image below for a preview of the iTerm2 app with a split 71using. See the image below for a preview of the iTerm2 app with a split
74view between my macOS desktop shell and an SSH session into my server. 72view between my macOS desktop shell and an SSH session into my server.
75 73
76#+caption: iTerm2 74![iTerm2](https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png)
77[[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png]] 75
78 76## Xcode
79** Xcode 77
80:PROPERTIES: 78My first step was to search the web for any hints on how to get
81:CUSTOM_ID: xcode 79`zsh` back up to the state I like, with extensions, themes,
82:END: 80etc. My first step was to install the CLI tools for
83My first step was to search the web for any hints on how to get =zsh= 81[Xcode](https://developer.apple.com/xcode/), Apple's suite of
84back up to the state I like, with extensions, themes, etc. My first step
85was to install the CLI tools for
86[[https://developer.apple.com/xcode/][Xcode]], Apple's suite of
87development tools. 82development tools.
88 83
89#+begin_src sh 84```sh
90sudo xcode-select -r 85sudo xcode-select -r
91#+end_src 86```
92 87
93#+begin_src sh 88```sh
94sudo xcode-select --install 89sudo xcode-select --install
95#+end_src 90```
96 91
97** Homebrew 92## Homebrew
98:PROPERTIES: 93
99:CUSTOM_ID: homebrew 94Next up is to install [Homebrew](https://brew.sh), a nifty package
100:END:
101Next up is to install [[https://brew.sh][Homebrew]], a nifty package
102manager for macOS. 95manager for macOS.
103 96
104#+begin_src sh 97```sh
105/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 98/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
106#+end_src 99```
107 100
108I ran into a permission error when installing Homebrew: 101I ran into a permission error when installing Homebrew:
109 102
110#+begin_src sh 103```sh
111Error: Failed to link all completions, docs and manpages: 104Error: Failed to link all completions, docs and manpages:
112 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew) 105 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
113Failed during: /usr/local/bin/brew update --force --quiet 106Failed during: /usr/local/bin/brew update --force --quiet
114#+end_src 107```
115 108
116I found that the following permission modification worked like a charm. 109I found that the following permission modification worked like a charm.
117However, I noted that some users online discussed the fact that this 110However, I noted that some users online discussed the fact that this
118solution may not work if your system has multiple users who use 111solution may not work if your system has multiple users who use
119Homebrew. 112Homebrew.
120 113
121#+begin_src sh 114```sh
122sudo chown -R $(whoami) $(brew --prefix)/* 115sudo chown -R $(whoami) $(brew --prefix)/*
123#+end_src 116```
124 117
125Next up is to ensure Homebrew is updated and cleaned. 118Next up is to ensure Homebrew is updated and cleaned.
126 119
127#+begin_src sh 120```sh
128brew update 121brew update
129#+end_src 122```
130 123
131#+begin_src sh 124```sh
132brew cleanup 125brew cleanup
133#+end_src 126```
127
128## iTerm2
134 129
135** iTerm2
136:PROPERTIES:
137:CUSTOM_ID: iterm2
138:END:
139Now that I've installed the basic utilities for development, I moved 130Now that I've installed the basic utilities for development, I moved
140onto installing iTerm2, a much better terminal than the default. 131onto installing iTerm2, a much better terminal than the default.
141 132
142#+begin_src sh 133```sh
143brew install --cask iterm2 134brew install --cask iterm2
144#+end_src 135```
145 136
146I also used the =Make iTerm2 Default Term= and 137I also used the `Make iTerm2 Default Term` and
147=Install Shell Integration= options in the iTerm2 application menu to 138`Install Shell Integration` options in the iTerm2 application
148make sure I don't run into any issues later on with different terminals. 139menu to make sure I don't run into any issues later on with different
140terminals.
149 141
150We will also install =zsh= so we can use it in iTerm2. 142We will also install `zsh` so we can use it in iTerm2.
151 143
152#+begin_src sh 144```sh
153brew install zsh 145brew install zsh
154#+end_src 146```
155 147
156** Oh-My-Zsh 148## Oh-My-Zsh
157:PROPERTIES:
158:CUSTOM_ID: oh-my-zsh
159:END:
160I've shown the great aspects of [[https://ohmyz.sh][Oh My Zsh]] in other
161blog posts, so I'll skip over that speech for now. Simply install it and
162run an update.
163 149
164#+begin_src sh 150I've shown the great aspects of [Oh My Zsh](https://ohmyz.sh) in other
151blog posts, so I'll skip over that speech for now. Simply install it
152and run an update.
153
154```sh
165sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" 155sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
166#+end_src 156```
167 157
168#+begin_src sh 158```sh
169omz update 159omz update
170#+end_src 160```
171 161
172Finally, restart the iTerm2 application to ensure all changes go into 162Finally, restart the iTerm2 application to ensure all changes go into
173effect. 163effect.
174 164
175** Oh-My-Zsh Themes 165## Oh-My-Zsh Themes
176:PROPERTIES: 166
177:CUSTOM_ID: oh-my-zsh-themes
178:END:
179Let's change the theme of the terminal to make it a little more 167Let's change the theme of the terminal to make it a little more
180friendly. 168friendly.
181 169
182#+begin_src sh 170```sh
183open ~/.zshrc 171open ~/.zshrc
184#+end_src 172```
185 173
186The third section of this file should contain a line like the code 174The third section of this file should contain a line like the code
187below. Change that theme to 175below. Change that theme to [any theme you
188[[https://github.com/ohmyzsh/ohmyzsh/wiki/Themes][any theme you want]], 176want](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes), save the file,
189save the file, and exit. 177and exit.
190 178
191#+begin_src sh 179```sh
192ZSH_THEME="af-magic" 180ZSH_THEME="af-magic"
193#+end_src 181```
194 182
195After changing the =.zshrc= file, you'll need to close your terminal and 183After changing the `.zshrc` file, you'll need to close your
196re-open it to see the changes. Optionally, just open a new tab if you're 184terminal and re-open it to see the changes. Optionally, just open a new
197using iTerm2, and you'll see the new shell config. 185tab if you're using iTerm2, and you'll see the new shell config.
198 186
199** Oh-My-Zsh Plugins 187## Oh-My-Zsh Plugins
200:PROPERTIES: 188
201:CUSTOM_ID: oh-my-zsh-plugins 189Of course, my customization of `zsh` would not be complete
202:END: 190without
203Of course, my customization of =zsh= would not be complete without 191[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions).
204[[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]]. 192This will bring up commands you've run in the past as you type them.
205This will bring up commands you've run in the past as you type them. For 193For example, if you've run `ssh user@192.168.1.99` before,
206example, if you've run =ssh user@192.168.1.99= before, the terminal will 194the terminal will show this command as soon as you start typing it
207show this command as soon as you start typing it (e.g. =zsh u=), and you 195(e.g. =zsh u=), and you can hit the right arrow to autocomplete the
208can hit the right arrow to autocomplete the command. 196command.
209 197
210#+begin_src sh 198```sh
211git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 199git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
212#+end_src 200```
213 201
214#+begin_src sh 202```sh
215open ~/.zshrc 203open ~/.zshrc
216#+end_src 204```
217 205
218#+begin_src sh 206```sh
219# Scroll down the script and edit this line to add zsh-autosuggestions 207# Scroll down the script and edit this line to add zsh-autosuggestions
220plugins=(git zsh-autosuggestions) 208plugins=(git zsh-autosuggestions)
221#+end_src 209```
222 210
223Remember: After changing the =.zshrc= file, you'll need to close your 211Remember: After changing the `.zshrc` file, you'll need to
224terminal and re-open it to see the changes. Optionally, just open a new 212close your terminal and re-open it to see the changes. Optionally, just
225tab if you're using iTerm2, and you'll see the new shell config. 213open a new tab if you're using iTerm2, and you'll see the new shell
214config.
blog/2021-03-19-clone-github-repos.org → content/blog/2021-03-19-clone-github-repos.md renamed +49 −49
@@ -1,10 +1,11 @@
1#+title: How to Clone All Repositories from a GitHub or Sourcehut Account 1+++
2#+date: 2021-03-19 2date = 2021-03-19
3title = "How to Clone All Repositories from a GitHub or Sourcehut Account"
4description = "Learn how to clone all GitHub or Sourcehut repositories."
5+++
6
7## Cloning from GitHub
3 8
4** Cloning from GitHub
5:PROPERTIES:
6:CUSTOM_ID: cloning-from-github
7:END:
8If you're like me and use a lot of different devices (and sometimes 9If you're like me and use a lot of different devices (and sometimes
9decide to just wipe your device and start with a new OS), you probably 10decide to just wipe your device and start with a new OS), you probably
10know the pain of cloning all your old code repositories down to your 11know the pain of cloning all your old code repositories down to your
@@ -12,48 +13,47 @@ local file system.
12 13
13If you're using GitHub, you can easily clone all of your code back down 14If you're using GitHub, you can easily clone all of your code back down
14in just seconds. First, create a bash script. I do so by opening a new 15in just seconds. First, create a bash script. I do so by opening a new
15file in =nano=, but you can use =gedit=, =vim=, or something else: 16file in `nano`, but you can use `gedit`,
17`vim`, or something else:
16 18
17#+begin_src sh 19```sh
18nano clone_github_repos.sh 20nano clone_github_repos.sh
19#+end_src 21```
20 22
21Next, paste in the following information. Note that you can replace the 23Next, paste in the following information. Note that you can replace the
22word =users= in the first line with =orgs= and type an organization's 24word `users` in the first line with `orgs` and
23name instead of a user's name. 25type an organization's name instead of a user's name.
24 26
25#+begin_src sh 27```sh
26CNTX=users; NAME=YOUR-USERNAME; PAGE=1 28CNTX=users; NAME=YOUR-USERNAME; PAGE=1
27curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" | 29curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" |
28 grep -e 'git_url*' | 30 grep -e 'git_url*' |
29 cut -d \" -f 4 | 31 cut -d " -f 4 |
30 xargs -L1 git clone 32 xargs -L1 git clone
31#+end_src 33```
32 34
33Finally, save the bash script and make it executable. 35Finally, save the bash script and make it executable.
34 36
35#+begin_src sh 37```sh
36chmod a+x clone_github_repos.sh 38chmod a+x clone_github_repos.sh
37#+end_src 39```
38 40
39Now you can run the script and should see the cloning process begin. 41Now you can run the script and should see the cloning process begin.
40 42
41#+begin_src sh 43```sh
42./clone_github_repos.sh 44./clone_github_repos.sh
43#+end_src 45```
46
47## Cloning from Sourcehut
44 48
45** Cloning from Sourcehut
46:PROPERTIES:
47:CUSTOM_ID: cloning-from-sourcehut
48:END:
49I haven't fully figured out how to directly incorporate Sourcehut's 49I haven't fully figured out how to directly incorporate Sourcehut's
50GraphQL API into a bash script yet, so this one will take two steps. 50GraphQL API into a bash script yet, so this one will take two steps.
51 51
52First, log-in to Sourcehut and go to their 52First, log-in to Sourcehut and go to their [GraphQL playground for
53[[https://git.sr.ht/graphql][GraphQL playground for Git]]. Next, paste 53Git](https://git.sr.ht/graphql). Next, paste the following query into
54the following query into the left box: 54the left box:
55 55
56#+begin_src sh 56```sh
57query { 57query {
58 me { 58 me {
59 canonicalName 59 canonicalName
@@ -65,7 +65,7 @@ query {
65 } 65 }
66 } 66 }
67} 67}
68#+end_src 68```
69 69
70The output on the right side will give you an object of all your 70The output on the right side will give you an object of all your
71repositories. Just grab that text and remove all the characters such as 71repositories. Just grab that text and remove all the characters such as
@@ -74,18 +74,19 @@ space-separated values for the next step.
74 74
75Now let's create the bash script: 75Now let's create the bash script:
76 76
77#+begin_src sh 77```sh
78nano clone_sourcehut_repos.sh 78nano clone_sourcehut_repos.sh
79#+end_src 79```
80 80
81Next, paste the following bash script in with the list of repositories 81Next, paste the following bash script in with the list of repositories
82you obtained above and replace =your-username= with your username. 82you obtained above and replace `your-username` with your
83username.
83 84
84Note that this uses the SSH-based Git cloning method 85Note that this uses the SSH-based Git cloning method
85(e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut 86(e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut
86with your SSH key. 87with your SSH key.
87 88
88#+begin_src sh 89```sh
89repos=(repo1 repo2 repo3) 90repos=(repo1 repo2 repo3)
90 91
91# List all sub-directories in the current directory 92# List all sub-directories in the current directory
@@ -94,38 +95,37 @@ do
94 # Clone 95 # Clone
95 git clone git@git.sr.ht:~your-username/$repo 96 git clone git@git.sr.ht:~your-username/$repo
96done 97done
97#+end_src 98```
98 99
99Finally, save the bash script and make it executable. 100Finally, save the bash script and make it executable.
100 101
101#+begin_src sh 102```sh
102chmod a+x clone_sourcehut_repos.sh 103chmod a+x clone_sourcehut_repos.sh
103#+end_src 104```
104 105
105Now you can run the script and should see the cloning process begin. 106Now you can run the script and should see the cloning process begin.
106 107
107#+begin_src sh 108```sh
108./clone_sourcehut_repos.sh 109./clone_sourcehut_repos.sh
109#+end_src 110```
111
112## Moving Repositories to a New Host
110 113
111** Moving Repositories to a New Host
112:PROPERTIES:
113:CUSTOM_ID: moving-repositories-to-a-new-host
114:END:
115Now that you have all of your code repositories cloned to your local 114Now that you have all of your code repositories cloned to your local
116computer, you may want to change the remote host (e.g., moving from 115computer, you may want to change the remote host (e.g., moving from
117GitHub to GitLab). To do this, let's create another bash script: 116GitHub to GitLab). To do this, let's create another bash script:
118 117
119#+begin_src sh 118```sh
120nano change_remote_urls.sh 119nano change_remote_urls.sh
121#+end_src 120```
122 121
123Past the following information and be sure to change the URL information 122Past the following information and be sure to change the URL information
124to whichever host you are moving to. For this example, I am looping 123to whichever host you are moving to. For this example, I am looping
125through all of my cloned GitHub directories and changing them to 124through all of my cloned GitHub directories and changing them to
126Sourcehut (e.g. =<YOUR_NEW_REMOTE_URL>= -> =git@git.sr.ht:~myusername=). 125Sourcehut (e.g. `<YOUR_NEW_REMOTE_URL>` -\>
126`git@git.sr.ht:~myusername`).
127 127
128#+begin_src sh 128```sh
129# List all sub-directories in the current directory 129# List all sub-directories in the current directory
130for dir in */ 130for dir in */
131do 131do
@@ -140,16 +140,16 @@ do
140 # Go back to main directory 140 # Go back to main directory
141 cd .. 141 cd ..
142done 142done
143#+end_src 143```
144 144
145Finally, save the bash script and make it executable. 145Finally, save the bash script and make it executable.
146 146
147#+begin_src sh 147```sh
148chmod a+x change_remote_urls.sh 148chmod a+x change_remote_urls.sh
149#+end_src 149```
150 150
151Now you can run the script and should see the cloning process begin. 151Now you can run the script and should see the cloning process begin.
152 152
153#+begin_src sh 153```sh
154./change_remote_urls.sh 154./change_remote_urls.sh
155#+end_src 155```
blog/2021-03-28-gemini-capsule.org → content/blog/2021-03-28-gemini-capsule.md renamed +70 −75
@@ -1,29 +1,30 @@
1#+title: Launching a Gemini Capsule 1+++
2#+date: 2021-03-28 2date = 2021-03-28
3 3title = "Launching a Gemini Capsule"
4** What is Gemini? 4description = "A guide to self-hosting a Gemini capsule on your own server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: what-is-gemini 6
7:END: 7## What is Gemini?
8[[https://gemini.circumlunar.space/][Gemini]] is an internet protocol 8
9[Gemini](https://gemini.circumlunar.space/) is an internet protocol
9introduced in June 2019 as an alternative to HTTP(S) or Gopher. In 10introduced in June 2019 as an alternative to HTTP(S) or Gopher. In
10layman's terms, it's an alternative way to browse sites (called 11layman's terms, it's an alternative way to browse sites (called
11capsules) that requires a special browser. Since Gemini is not 12capsules) that requires a special browser. Since Gemini is not
12standardized as an internet standard, normal web browsers won't be able 13standardized as an internet standard, normal web browsers won't be able
13to load a Gemini capsule. Instead, you'll need to use 14to load a Gemini capsule. Instead, you'll need to use [a
14[[https://gemini.%20circumlunar.space/clients.html][a Gemini-specific 15Gemini-specific
15browser]]. 16browser](https://gemini.%20circumlunar.space/clients.html).
16 17
17The content found within a Gemini page is called 18The content found within a Gemini page is called
18[[https://gemini.circumlunar.space/docs/cheatsheet.gmi][Gemtext]] and is 19[Gemtext](https://gemini.circumlunar.space/docs/cheatsheet.gmi) and is
19/extremely/ basic (on purpose). Gemini only processes the text, no media 20*extremely* basic (on purpose). Gemini only processes the text, no media
20content like images. However, you're able to style 3 levels of headings, 21content like images. However, you're able to style 3 levels of
21regular text, links (which will display on their own line), quotes, and 22headings, regular text, links (which will display on their own line),
22an unordered list. 23quotes, and an unordered list.
23 24
24Here's a complete listing of valid Gemtext: 25Here's a complete listing of valid Gemtext:
25 26
26#+begin_src txt 27```txt
27# Heading 1 28# Heading 1
28## Heading 2 29## Heading 2
29### Heading 3 30### Heading 3
@@ -40,12 +41,10 @@ My List:
40* Item 41* Item
41 42
42```Anything between three backticks will be rendered as code.``` 43```Anything between three backticks will be rendered as code.```
43#+end_src 44```
45
46## Free Option
44 47
45** Free Option
46:PROPERTIES:
47:CUSTOM_ID: free-option
48:END:
49There are probably numerous websites that allow you to create your 48There 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 49personal 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 50have personally tested. The first option below, Midnight Pub, allows you
@@ -55,20 +54,18 @@ second option below, Sourcehut, allows you to use a Git repository and
55automatic build process to deploy your personal Gemini capsule every 54automatic build process to deploy your personal Gemini capsule every
56time you push a commit. 55time you push a commit.
57 56
58*** Midnight Pub - Beginner Friendly 57### Midnight Pub - Beginner Friendly
59:PROPERTIES: 58
60:CUSTOM_ID: midnight-pub---beginner-friendly 59[Midnight Pub](https://midnight.pub/) is a small, virtual community
61:END:
62[[https://midnight.pub/][Midnight Pub]] is a small, virtual community
63meant to reflect the atmosphere of wandering into a small alley pub. The 60meant to reflect the atmosphere of wandering into a small alley pub. The
64site is built in Gemtext and has a server-side process to convert 61site is built in Gemtext and has a server-side process to convert
65Gemtext to HTML if someone loads the site in an HTTP(S) browser. 62Gemtext to HTML if someone loads the site in an HTTP(S) browser.
66 63
67To create an account, you'll need to email the owner of the website to 64To create an account, you'll need to email the owner of the website to
68obtain a key. You can find their email on the Midnight Pub homepage. 65obtain a key. You can find their email on the Midnight Pub homepage.
69Once registered, head to [[https://midnight.pub/account][your account]] 66Once registered, head to [your account](https://midnight.pub/account)
70and select [[https://midnight.pub/site][manage site]]. This is the 67and select [manage site](https://midnight.pub/site). This is the screen
71screen where you can upload or create any files to be displayed on the 68where you can upload or create any files to be displayed on the
72internet. 69internet.
73 70
74For example, I've created both an HTML file and a Gemini file. Remember 71For example, I've created both an HTML file and a Gemini file. Remember
@@ -76,25 +73,22 @@ that Gemini is automatically converted to HTML on the Pub, so you don't
76need an HTML version. For example, I created an HTML version to add in 73need an HTML version. For example, I created an HTML version to add in
77some extra styling. 74some extra styling.
78 75
79All you need to do is create a page like =index.gmi= and use your Gemini 76All you need to do is create a page like `index.gmi` and use
80browser to head over to your-username.midnight.pub to see the result. 77your Gemini browser to head over to your-username.midnight.pub to see
78the result.
81 79
82That's all there is to it! Easy enough, right? Let's check out a more 80That's all there is to it! Easy enough, right? Let's check out a more
83advanced version in the next section. 81advanced version in the next section.
84 82
85** Paid Option 83## Paid Option
86:PROPERTIES: 84
87:CUSTOM_ID: paid-option
88:END:
89As of 2021, Sourcehut has decided to require users to have a paid 85As of 2021, Sourcehut has decided to require users to have a paid
90account in order to utilize their automated build system. For now, paid 86account in order to utilize their automated build system. For now, paid
91accounts can be as low as $2/month. 87accounts can be as low as \$2/month.
88
89### Sourcehut
92 90
93*** Sourcehut 91[Sourcehut](https://sourcehut.org/) is a collection of software
94:PROPERTIES:
95:CUSTOM_ID: sourcehut
96:END:
97[[https://sourcehut.org/][Sourcehut]] is a collection of software
98development tools, but mostly surrounds their hosted Git repository 92development tools, but mostly surrounds their hosted Git repository
99service. Simply put, it's a minimal and more private alternative to 93service. Simply put, it's a minimal and more private alternative to
100services like GitHub. 94services like GitHub.
@@ -106,43 +100,43 @@ instead.
106 100
107The first thing you'll need to do is create an SSH key pair, if you 101The first thing you'll need to do is create an SSH key pair, if you
108don't already have one on your system. Once created, grab the contents 102don't already have one on your system. Once created, grab the contents
109of =id_rsa.pub= and add it to your Sourcehut account settings - this 103of `id_rsa.pub` and add it to your Sourcehut account
110will allow you to push and pull code changes without using a 104settings - this will allow you to push and pull code changes without
111username/password. 105using a username/password.
112 106
113#+begin_src sh 107```sh
114ssh keygen 108ssh keygen
115#+end_src 109```
116 110
117Next up, let's create a repository with the proper name so that the 111Next up, let's create a repository with the proper name so that the
118Sourcehut build system will know we want them to host a website for us. 112Sourcehut build system will know we want them to host a website for us.
119Use the following format exactly: 113Use the following format exactly:
120 114
121#+begin_src sh 115```sh
122mkdir your-username.srht.site && cd your-username.srht.site 116mkdir your-username.srht.site && cd your-username.srht.site
123#+end_src 117```
124 118
125Now that we've created the repo, let's initialize Git and add the proper 119Now that we've created the repo, let's initialize Git and add the
126remote URL. 120proper remote URL.
127 121
128#+begin_src sh 122```sh
129git init 123git init
130#+end_src 124```
131 125
132#+begin_src sh 126```sh
133git remote add origin git@git.sr.ht:~your-username/your-username.srht.site 127git remote add origin git@git.sr.ht:~your-username/your-username.srht.site
134#+end_src 128```
135 129
136Now that our repository is set up and configured, we will need to create 130Now that our repository is set up and configured, we will need to create
137at least two files: 131at least two files:
138 132
139- =index.gmi= 133- `index.gmi`
140- =.build.yml= 134- `.build.yml`
141 135
142For your =.build.yml= file, use the following content and be sure to 136For your `.build.yml` file, use the following content and be
143update the =site= line with your username! 137sure to update the `site` line with your username!
144 138
145#+begin_src yaml 139```yaml
146image: alpine/latest 140image: alpine/latest
147oauth: pages.sr.ht/PAGES:RW 141oauth: pages.sr.ht/PAGES:RW
148environment: 142environment:
@@ -153,38 +147,39 @@ tasks:
153 tar -cvz . > ../site.tar.gz 147 tar -cvz . > ../site.tar.gz
154 - upload: | 148 - upload: |
155 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI 149 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
156#+end_src 150```
157 151
158For the =index.gmi= file, put whatever you want in there and save it. 152For the `index.gmi` file, put whatever you want in there and
159You could even just copy and paste the Gemtext cheatsheet. 153save it. You could even just copy and paste the Gemtext cheatsheet.
160 154
161If you want to serve both HTML and Gemini files from this repository, 155If you want to serve both HTML and Gemini files from this repository,
162just add a second command to the =upload= section: 156just add a second command to the `upload` section:
163 157
164#+begin_src yaml 158```yaml
165- upload: | 159- upload: |
166 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI 160 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
167 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz 161 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
168#+end_src 162```
169 163
170Lastly, commit your changes and push them to the remote repo. 164Lastly, commit your changes and push them to the remote repo.
171 165
172#+begin_src sh 166```sh
173git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD 167git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD
174#+end_src 168```
175 169
176If you've successfully created the files with the proper format, you'll 170If you've successfully created the files with the proper format,
177see the terminal print a message that lets you know where the automatic 171you'll see the terminal print a message that lets you know where the
178build is taking place. For example, here's what the terminal tells me: 172automatic build is taking place. For example, here's what the terminal
173tells me:
179 174
180#+begin_src sh 175```sh
181remote: Build started: 176remote: Build started:
182remote: https://builds.sr.ht/~user/job/689803 [.build.yml] 177remote: https://builds.sr.ht/~user/job/689803 [.build.yml]
183#+end_src 178```
184 179
185Now that you've properly built your Sourcehut page, you can browse to 180Now that you've properly built your Sourcehut page, you can browse to
186your-username.srht.site in a Gemini browser and view the final results. 181your-username.srht.site in a Gemini browser and view the final results.
187Take a look at the image below for my Sourcehut Gemini capsule. 182Take a look at the image below for my Sourcehut Gemini capsule.
188 183
189#+caption: Gemini page on the amfora browser 184![Gemini page on the amfora
190[[https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png]] 185browser](https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png)
blog/2021-03-28-vaporwave-vs-outrun.org → content/blog/2021-03-28-vaporwave-vs-outrun.md renamed +58 −79
@@ -1,10 +1,11 @@
1#+title: Vaporwave vs Outrun 1+++
2#+date: 2021-03-28 2date = 2021-03-28
3title = "Vaporwave vs Outrun"
4description = "Learn the differences between the vaporwave and Outrun aesthetics."
5+++
6
7## Overview
3 8
4** Overview
5:PROPERTIES:
6:CUSTOM_ID: overview
7:END:
8When it comes to an aesthetic that originated primarily online, there 9When it comes to an aesthetic that originated primarily online, there
9tends to be a lot of confusion around what falls into each specific 10tends to be a lot of confusion around what falls into each specific
10genre. This post discusses Vaporwave and Outrun, which have become 11genre. This post discusses Vaporwave and Outrun, which have become
@@ -12,69 +13,58 @@ almost synonymous in the minds of those online who aren't hardcore into
12these genres of media. More specifically, Outrun is largely an unknown 13these genres of media. More specifically, Outrun is largely an unknown
13term while all aesthetics in these two genres are usually attributed to 14term while all aesthetics in these two genres are usually attributed to
14Vaporwave. For example, take a look at the image search results for 15Vaporwave. For example, take a look at the image search results for
15Vaporwave: the results include *a lot* of Outrun-themed images. You'll 16Vaporwave: the results include **a lot** of Outrun-themed images.
16find a similar trend almost everywhere. 17You'll find a similar trend almost everywhere.
18
19![](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png)
17 20
18[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png]] 21## Vaporwave
19 22
20** Vaporwave 23![](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png)
21:PROPERTIES:
22:CUSTOM_ID: vaporwave
23:END:
24[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png]]
25 24
26Okay, so what is Vaporwave? I'm going to just copy-and-paste some 25Okay, so what is Vaporwave? I'm going to just copy-and-paste some
27general info from the Wikipedia article on 26general info from the Wikipedia article on
28[[https://en.wikipedia.org/wiki/Vaporwave][Vaporwave]], so that I'm not 27[Vaporwave](https://en.wikipedia.org/wiki/Vaporwave), so that I'm not
29repeating everything you can already search for online: 28repeating everything you can already search for online:
30 29
31#+begin_quote 30> Vaporwave is a microgenre of electronic music, a visual art style, and
32Vaporwave is a microgenre of electronic music, a visual art style, and 31> an Internet meme that emerged in the early 2010s. It is defined partly
33an Internet meme that emerged in the early 2010s. It is defined partly 32> by its slowed-down, chopped and screwed samples of smooth jazz,
34by its slowed-down, chopped and screwed samples of smooth jazz, 33> elevator, R&B, and lounge music from the 1980s and 1990s. The
35elevator, R&B, and lounge music from the 1980s and 1990s. The 34> surrounding subculture is sometimes associated with an ambiguous or
36surrounding subculture is sometimes associated with an ambiguous or 35> satirical take on consumer capitalism and pop culture, and tends to be
37satirical take on consumer capitalism and pop culture, and tends to be 36> characterized by a nostalgic or surrealist engagement with the popular
38characterized by a nostalgic or surrealist engagement with the popular 37> entertainment, technology and advertising of previous decades.
39entertainment, technology and advertising of previous decades. Visually, 38> Visually, it incorporates early Internet imagery, late 1990s web
40it incorporates early Internet imagery, late 1990s web design, glitch 39> design, glitch art, anime, 3D-rendered objects, and cyberpunk tropes
41art, anime, 3D-rendered objects, and cyberpunk tropes in its cover 40> in its cover artwork and music videos.
42artwork and music videos.
43
44#+end_quote
45 41
46This is an excellent summary, and it helps address my point here: there 42This is an excellent summary, and it helps address my point here: there
47are specific aspects that make Vaporwave unique: 43are specific aspects that make Vaporwave unique:
48 44
49*** Time Frame 45### Time Frame
50:PROPERTIES: 46
51:CUSTOM_ID: time-frame
52:END:
53The time frame for references, logos, etc. focuses mostly on the 1990s 47The time frame for references, logos, etc. focuses mostly on the 1990s
54in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens, 48in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens,
55tropical plants, classic marble sculptures, and many references from 49tropical plants, classic marble sculptures, and many references from
56Japan's influence in the 90s. 50Japan's influence in the 90s.
57 51
58*** Art 52### Art
59:PROPERTIES: 53
60:CUSTOM_ID: art
61:END:
62The color scheme is generally a soft, light palette that uses pastel 54The color scheme is generally a soft, light palette that uses pastel
63colors. The backdrop will often be in a light-pink or teal blue. 55colors. The backdrop will often be in a light-pink or teal blue.
64 56
65*** Music 57### Music
66:PROPERTIES: 58
67:CUSTOM_ID: music
68:END:
69The musical genre of Vaporwave incorporates soft tunes, lounge music, 59The musical genre of Vaporwave incorporates soft tunes, lounge music,
70and sound effects that will make the listener reminisce of the 90s. The 60and sound effects that will make the listener reminisce of the 90s. The
71sounds of the genre are generally slower-paced and calming. The major 61sounds of the genre are generally slower-paced and calming. The major
72breakthrough artist for Vaporwave was 62breakthrough artist for Vaporwave was [Macintosh
73[[https://en.wikipedia.org/wiki/Vektroid][Macintosh Plus]], who released 63Plus](https://en.wikipedia.org/wiki/Vektroid), who released the album
74the album 64[Floral
75[[https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete][Floral 65Shoppe](https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete)
76Shoppe]] in 2011. Another more recent example is the artist 66in 2011. Another more recent example is the artist
77[[https://sadbert.bandcamp.com/][sadbert]], whose latest album 67[sadbert](https://sadbert.bandcamp.com/), whose latest album
78incorporates the upbeat tones of the 1999 Dilbert TV series. 68incorporates the upbeat tones of the 1999 Dilbert TV series.
79 69
80Notice that Vaporwave doesn't include things like racing cars, 70Notice that Vaporwave doesn't include things like racing cars,
@@ -84,30 +74,23 @@ was becoming common in households, a reality that we have already
84experienced. Focus on the most aesthetically-pleasing parts of that past 74experienced. Focus on the most aesthetically-pleasing parts of that past
85is a large part of Vaporwave. 75is a large part of Vaporwave.
86 76
87** Outrun 77## Outrun
88:PROPERTIES: 78
89:CUSTOM_ID: outrun 79![Outrun](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png)
90:END:
91#+caption: Outrun
92[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png]]
93 80
94Now, let's get to Outrun. This one is a little trickier since the genre 81Now, let's get to Outrun. This one is a little trickier since the genre
95has largely been lumped under the Vaporwave title for so long. However, 82has largely been lumped under the Vaporwave title for so long. However,
96it stems from the [[https://en.wikipedia.org/wiki/Synthwave][Synthwave]] 83it stems from the [Synthwave](https://en.wikipedia.org/wiki/Synthwave)
97music genre and is likely named after the 1986 racer game, 84music genre and is likely named after the 1986 racer game, [Out
98[[https://en.wikipedia.org/wiki/Out_Run][Out Run]]. 85Run](https://en.wikipedia.org/wiki/Out_Run).
99 86
100*** Time Frame 87### Time Frame
101:PROPERTIES: 88
102:CUSTOM_ID: time-frame-1
103:END:
104Outrun can be thought of as a retro-futuristic aesthetic born from the 89Outrun can be thought of as a retro-futuristic aesthetic born from the
1051980s. 901980s.
106 91
107*** Art 92### Art
108:PROPERTIES: 93
109:CUSTOM_ID: art-1
110:END:
111The color scheme uses a very dark color palette with the main colors 94The color scheme uses a very dark color palette with the main colors
112being deep oranges, blues, and purples. Red edges are common around 95being deep oranges, blues, and purples. Red edges are common around
113objects in Outrun art. The background of the Outrun aesthetic is almost 96objects in Outrun art. The background of the Outrun aesthetic is almost
@@ -118,10 +101,8 @@ Classic sports cars, chrome robots, computer generated graphics and
118fonts, and the occasional use of rain or palm trees can be found in 101fonts, and the occasional use of rain or palm trees can be found in
119Outrun art. 102Outrun art.
120 103
121*** Music 104### Music
122:PROPERTIES: 105
123:CUSTOM_ID: music-1
124:END:
125This aesthetic has a more aggressive and fast-paced style of music, 106This aesthetic has a more aggressive and fast-paced style of music,
126which tends to match the subject of the art in this aesthetic. 107which tends to match the subject of the art in this aesthetic.
127 108
@@ -129,22 +110,20 @@ Outrun enthusiasts love what people in the 1980s thought the future
129would look like. Take a look at a common video game discussed in Outrun 110would look like. Take a look at a common video game discussed in Outrun
130circles, Far Cry 3: Blood Dragon: 111circles, Far Cry 3: Blood Dragon:
131 112
132#+caption: Fry Cry 3: Blood Dragon 113![Fry Cry 3: Blood
133[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png]] 114Dragon](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png)
134 115
135Another example that doesn't force the color scheme as hard as some 116Another example that doesn't force the color scheme as hard as some
136online art does is Kung Fury: 117online art does is Kung Fury:
137 118
138#+caption: Kung Fury 119![Kung
139[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png]] 120Fury](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png)
121
122![Kung Fury Hacker
123Scene](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png)
140 124
141#+caption: Kung Fury Hacker Scene 125## Conclusion
142[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png]]
143 126
144** Conclusion
145:PROPERTIES:
146:CUSTOM_ID: conclusion
147:END:
148While Vaporwave and Outrun share similarities, they are two distinct 127While Vaporwave and Outrun share similarities, they are two distinct
149aesthetics with many important distinctions. Someone who enjoys one may 128aesthetics with many important distinctions. Someone who enjoys one may
150not necessarily enjoy the other, so it's important to make sure we 129not necessarily enjoy the other, so it's important to make sure we
blog/2021-03-30-vps-web-server.org → content/blog/2021-03-30-vps-web-server.md renamed +185 −201
@@ -1,35 +1,35 @@
1#+title: How to Set Up a VPS Web Server 1+++
2#+date: 2021-03-30 2date = 2021-03-30
3title = "How to Set Up a VPS Web Server"
4description = "A beginner's guide to setting up a virtual private server (VPS)."
5+++
6
7## Shared Hosting vs. VPS
3 8
4** Shared Hosting vs. VPS
5:PROPERTIES:
6:CUSTOM_ID: shared-hosting-vs.-vps
7:END:
8Choosing a place to host a website is one of the most confusing 9Choosing a place to host a website is one of the most confusing
9decisions for beginner web developers. Even for experienced web devs, 10decisions for beginner web developers. Even for experienced web devs,
10choosing between different forms of web hosting can be a daunting 11choosing between different forms of web hosting can be a daunting
11choice. 12choice.
12 13
13First, let's take a look at 14First, let's take a look at [shared web
14[[https://en.wikipedia.org/wiki/Shared_web_hosting_service][shared web 15hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service).
15hosting]]. Shared web hosting is a product where you are purchasing a 16Shared web hosting is a product where you are purchasing a small piece
16small piece of a web server that is being shared between many websites. 17of a web server that is being shared between many websites. As a result,
17As a result, the cost of shared hosting is extremely low. You won't have 18the cost of shared hosting is extremely low. You won't have access to
18access to the server itself, which means you can't install your own 19the server itself, which means you can't install your own software on
19software on the server, such as Docker. Usually, you are simply allowed 20the server, such as Docker. Usually, you are simply allowed to connect
20to connect your domains to the server, set up domain security, and other 21your domains to the server, set up domain security, and other small
21small utilities. 22utilities.
22 23
23In contrast, a 24In contrast, a [virtual private
24[[https://en.wikipedia.org/wiki/Virtual_private_server][virtual private 25server](https://en.wikipedia.org/wiki/Virtual_private_server) (VPS) is a
25server]] (VPS) is a virtual machine that replicates the environment of 26virtual machine that replicates the environment of having a dedicated
26having a dedicated server to yourself. You are able to control the 27server to yourself. You are able to control the virtual server's host
27virtual server's host names, base file system, package manager, etc. 28names, base file system, package manager, etc. Another great upside of a
28Another great upside of a VPS is that since it's virtual, the company 29VPS is that since it's virtual, the company providing the VPS can
29providing the VPS can dynamically increase the disk size, RAM size, or 30dynamically increase the disk size, RAM size, or number of CPUs at any
30number of CPUs at any time. However, the virtual server is still 31time. However, the virtual server is still physically located on a
31physically located on a server that is shared between multiple virtual 32server that is shared between multiple virtual servers.
32servers.
33 33
34The choice between shared hosting and VPS mostly depends on your skill 34The choice between shared hosting and VPS mostly depends on your skill
35level with system administration. If you're comforting working on a 35level with system administration. If you're comforting working on a
@@ -38,10 +38,8 @@ a VPS is usually a better option. However, shared hosting is a fantastic
38option for people who don't want to have to learn how to manage their 38option for people who don't want to have to learn how to manage their
39server. 39server.
40 40
41** My Situation 41## My Situation
42:PROPERTIES: 42
43:CUSTOM_ID: my-situation
44:END:
45I had used shared hosting for approximately 5 years before trying my 43I had used shared hosting for approximately 5 years before trying my
46first VPS. I manage a homelab and have had success running a server and 44first VPS. I manage a homelab and have had success running a server and
47performing typical sysadmin duties, but I was still hesitant to get a 45performing typical sysadmin duties, but I was still hesitant to get a
@@ -50,54 +48,49 @@ networking part of a server - DNS and hostname configurations were not
50my friend. 48my friend.
51 49
52As a little bit of background, I originally used 50As a little bit of background, I originally used
53[[https://www.siteground.com][Siteground]] for my initially shared 51[Siteground](https://www.siteground.com) for my initially shared hosting
54hosting and stayed on that platform for at least a year. However, the UI 52and stayed on that platform for at least a year. However, the UI was
55was clunky, and I didn't like how they handled certain technical 53clunky, and I didn't like how they handled certain technical aspects,
56aspects, so I switched to [[https://www.namecheap.com][Namecheap]]. 54so I switched to [Namecheap](https://www.namecheap.com). Namecheap was
57Namecheap was great because it is the service I primarily use for 55great because it is the service I primarily use for purchasing domain
58purchasing domain names, which made it incredibly easy to link them to 56names, which made it incredibly easy to link them to my hosting service.
59my hosting service. However, it was still mediocre shared hosting, and 57However, it was still mediocre shared hosting, and Namecheap is
60Namecheap is notorious for not letting you use 58notorious for not letting you use [Let's
61[[https://letsencrypt.org][Let's Encrypt]] to obtain free SSL/TLS 59Encrypt](https://letsencrypt.org) to obtain free SSL/TLS certificates;
62certificates; Namecheap wants to make you purchase certificates through 60Namecheap wants to make you purchase certificates through their store.
63their store. 61
64 62Finally, I settled down with [iWebFusion](https://www.iwebfusion.net)
65Finally, I settled down with [[https://www.iwebfusion.net][iWebFusion]]
66for about the last year of my shared hosting. This service was pretty 63for about the last year of my shared hosting. This service was pretty
67great, came with free SSL/TLS, and I never had any complaints. 64great, came with free SSL/TLS, and I never had any complaints.
68 65
69However, I finally grew tired of not being able to install software on 66However, I finally grew tired of not being able to install software on
70my own web server. I wanted to be able to try out things like 67my own web server. I wanted to be able to try out things like
71[[https://postmill.xyz][Postmill]] or [[https://matrix.org][Matrix]]. 68[Postmill](https://postmill.xyz) or [Matrix](https://matrix.org). This
72This is possible with a VPS, so I decided to grab a new domain name to 69is possible with a VPS, so I decided to grab a new domain name to try it
73try it out. 70out.
74 71
75** Getting Started: Buying a VPS 72## Getting Started: Buying a VPS
76:PROPERTIES: 73
77:CUSTOM_ID: getting-started-buying-a-vps
78:END:
79The first step to moving over to a VPS is (you guessed it): finding a 74The first step to moving over to a VPS is (you guessed it): finding a
80VPS provider. For my VPSs, I use [[https://1984hosting.com][1984]] and 75VPS provider. For my VPSs, I use [1984](https://1984hosting.com) and
81prefer their services much more than any alternative, due to their 76prefer their services much more than any alternative, due to their
82location (Iceland), their [[https://1984hosting.com/GDPR/][privacy 77location (Iceland), their [privacy
83policy]], their respect for GDPR, and the ability to remain anonymous if 78policy](https://1984hosting.com/GDPR/), their respect for GDPR, and the
84you pay in Bitcoin or Monero. 79ability to remain anonymous if you pay in Bitcoin or Monero.
85 80
86[[https://njal.la][Njalla]] is another good, privacy-oriented option for 81[Njalla](https://njal.la) is another good, privacy-oriented option for
87VPS services. 82VPS services.
88 83
89You'll have to decide what specifications you want on your VPS. For me, 84You'll have to decide what specifications you want on your VPS. For me,
90I only build and deploy low-resource HTML, PHP, and Python websites. 85I only build and deploy low-resource HTML, PHP, and Python websites.
91This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and 86This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and
9225GB SSD for $5.00 per month. 8725GB SSD for \$5.00 per month.
93 88
94As noted above, the great thing about a VPS is you can request your 89As noted above, the great thing about a VPS is you can request your
95provider to increase the resources at any time. 90provider to increase the resources at any time.
96 91
97** Configuring DNS Settings 92## Configuring DNS Settings
98:PROPERTIES: 93
99:CUSTOM_ID: configuring-dns-settings
100:END:
101Okay, so now let's get into some actual work that has to be done to get 94Okay, so now let's get into some actual work that has to be done to get
102content moved from a shared host to a VPS. At this point, I'm assuming 95content moved from a shared host to a VPS. At this point, I'm assuming
103you have a shared host with website content that you can still access, 96you have a shared host with website content that you can still access,
@@ -105,147 +98,143 @@ and you've purchased a new VPS and can SSH into that server.
105 98
106The first change is minor, but it should be done immediately in order to 99The first change is minor, but it should be done immediately in order to
107get things moving: DNS settings. Go to wherever your DNS settings are 100get things moving: DNS settings. Go to wherever your DNS settings are
108handled. If your shared host also managed your DNS settings, you'll need 101handled. If your shared host also managed your DNS settings, you'll
109to first move that DNS over to your new VPS provider. For me, I route my 102need to first move that DNS over to your new VPS provider. For me, I
110DNS through [[https://www.gandi.net][Gandi]]. 103route my DNS through [Gandi](https://www.gandi.net).
111 104
112Once you know where your DNS settings are, go ahead and update the =A= 105Once you know where your DNS settings are, go ahead and update the
113records to match the public IP address of your VPS. For example: 106`A` records to match the public IP address of your VPS. For
107example:
114 108
115#+begin_src txt 109```txt
116A example.com xxx.xxx.xxx.xxx 110A example.com xxx.xxx.xxx.xxx
117A subdomain xxx.xxx.xxx.xxx 111A subdomain xxx.xxx.xxx.xxx
118CNAME www example.com. 112CNAME www example.com.
119#+end_src 113```
120 114
121If you have any other records that require updates, such as MX or TXT 115If you have any other records that require updates, such as MX or TXT
122records for a mail server, be sure to update those accordingly. 116records for a mail server, be sure to update those accordingly.
123Personally, I don't host my own mail server. I route all mail on my 117Personally, I don't host my own mail server. I route all mail on my
124custom domains to [[https://www.migadu.com][Migadu]]. Hosting your own 118custom domains to [Migadu](https://www.migadu.com). Hosting your own
125email server can become complex quickly and is not for beginners. 119email server can become complex quickly and is not for beginners.
126 120
127DNS changes can take up to 48 hours to propagate, so be sure to give it 121DNS changes can take up to 48 hours to propagate, so be sure to give it
128some time before assuming you've made an error. 122some time before assuming you've made an error.
129 123
130** Server Updates and Packages 124## Server Updates and Packages
131:PROPERTIES: 125
132:CUSTOM_ID: server-updates-and-packages
133:END:
134Now that the DNS settings have been changed, let's set up our server 126Now that the DNS settings have been changed, let's set up our server
135while we wait for the DNS to propagate. First up is to ssh into your 127while we wait for the DNS to propagate. First up is to ssh into your
136server. If you've signed up with a service like DigitalOcean, you can 128server. If you've signed up with a service like DigitalOcean, you can
137add your SSH key to your account and to your VPS droplet so that you 129add your SSH key to your account and to your VPS droplet so that you
138don't need a password in order to SSH. 130don't need a password in order to SSH.
139 131
140#+begin_src sh 132```sh
141ssh root@xxx.xxx.xxx.xxx 133ssh root@xxx.xxx.xxx.xxx
142#+end_src 134```
143 135
144The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache 136The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache
145web server. If you're working on a different operating system (OS) or 137web server. If you're working on a different operating system (OS) or
146want a different web server, such as Nginx, you'll have to use different 138want a different web server, such as Nginx, you'll have to use
147commands to set it up. 139different commands to set it up.
148 140
149First, let's update and upgrade our server. 141First, let's update and upgrade our server.
150 142
151*NOTE:* Since we have logged in to the server as =root= for now, we 143**NOTE:** Since we have logged in to the server as `root` for
152don't need to use the =sudo= modifier before our commands. 144now, we don't need to use the `sudo` modifier before our
145commands.
153 146
154#+begin_src sh 147```sh
155apt update && apt upgrade -y 148apt update && apt upgrade -y
156#+end_src 149```
150
151## Create A User Account
157 152
158** Create A User Account 153While being able to use `root` can be beneficial at times,
159:PROPERTIES: 154you shouldn't use `root` unless you have to.
160:CUSTOM_ID: create-a-user-account
161:END:
162While being able to use =root= can be beneficial at times, you shouldn't
163use =root= unless you have to.
164 155
165So let's set up a new user in our system. The =-m= option below tells 156So let's set up a new user in our system. The `-m` option
166the OS to create a home directory for the new user. 157below tells the OS to create a home directory for the new user.
167 158
168#+begin_src sh 159```sh
169adduser USERNAME 160adduser USERNAME
170#+end_src 161```
171 162
172Now, create a password for that user. 163Now, create a password for that user.
173 164
174#+begin_src sh 165```sh
175passwd USERNAME 166passwd USERNAME
176#+end_src 167```
177 168
178Finally, add the user to the sudoers file, so they can perform 169Finally, add the user to the sudoers file, so they can perform
179priveleged commands. 170priveleged commands.
180 171
181#+begin_src sh 172```sh
182usermod -a -G sudo USERNAME 173usermod -a -G sudo USERNAME
183#+end_src 174```
184 175
185If you are using SSH keys and not passwords, you'll need to copy your 176If you are using SSH keys and not passwords, you'll need to copy your
186SSH key from your local machine to the VPS. If you haven't disabled 177SSH key from your local machine to the VPS. If you haven't disabled
187password-based SSH yet, the easiest way to do this is =ssh-copy-id= from 178password-based SSH yet, the easiest way to do this is
188your local computer (not from the VPS): 179`ssh-copy-id` from your local computer (not from the VPS):
189 180
190#+begin_src sh 181```sh
191ssh-copy-id testuser@xxx.xxx.xxx.xxx 182ssh-copy-id testuser@xxx.xxx.xxx.xxx
192#+end_src 183```
193 184
194If you've disabled password-based SSH, you'll need to manually copy your 185If you've disabled password-based SSH, you'll need to manually copy
195SSH key into the =~/.ssh/authorized_keys= file. 186your SSH key into the `~/.ssh/authorized_keys` file.
187
188## Install Software
196 189
197** Install Software
198:PROPERTIES:
199:CUSTOM_ID: install-software
200:END:
201Our goal here is to host a web server, so the next step is to install 190Our goal here is to host a web server, so the next step is to install
202the Apache web server and any other packages we need. 191the Apache web server and any other packages we need.
203 192
204From this point on, I will be logged in as a user (not =root=) and will 193From this point on, I will be logged in as a user (not
205need to use the =sudo= modifier for most commands. 194`root`) and will need to use the `sudo` modifier
195for most commands.
206 196
207#+begin_src sh 197```sh
208sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y 198sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
209sudo apt install apache2 199sudo apt install apache2
210#+end_src 200```
211 201
212If you need other language support, such as PHP, you'll need to install 202If you need other language support, such as PHP, you'll need to install
213that too. 203that too.
214 204
215#+begin_src sh 205```sh
216sudo apt install libapache2-mod-php php-dom 206sudo apt install libapache2-mod-php php-dom
217sudo a2enmod php 207sudo a2enmod php
218sudo systemctl restart apache2 208sudo systemctl restart apache2
219#+end_src 209```
210
211## Website Files & Folders
220 212
221** Website Files & Folders
222:PROPERTIES:
223:CUSTOM_ID: website-files-folders
224:END:
225Next up is to create the directories for the domain(s) we want to be 213Next up is to create the directories for the domain(s) we want to be
226hosted on this web server. 214hosted on this web server.
227 215
228#+begin_src sh 216```sh
229cd /var/www 217cd /var/www
230sudo mkdir example.com 218sudo mkdir example.com
231#+end_src 219```
232 220
233We have a folder for =example.com= now, so let's add an =index.html= 221We have a folder for `example.com` now, so let's add an
234file and put it within a specific =public_html= folder. You don't need 222`index.html` file and put it within a specific
235this =public_html= if you don't want it, but it helps with organizing 223`public_html` folder. You don't need this
236items related to =example.com= that you don't want to publish to the 224`public_html` if you don't want it, but it helps with
237internet. 225organizing items related to `example.com` that you don't
226want to publish to the internet.
238 227
239#+begin_src sh 228```sh
240cd example.com 229cd example.com
241sudo mkdir public_html && cd public_html 230sudo mkdir public_html && cd public_html
242sudo nano index.html 231sudo nano index.html
243#+end_src 232```
244 233
245You can put anything you want in this =index.html= file. If you can't 234You can put anything you want in this `index.html` file. If
246think of anything, paste this in there: 235you can't think of anything, paste this in there:
247 236
248#+begin_src html 237```html
249<!DOCTYPE html> 238<!DOCTYPE html>
250<html lang="en"> 239<html lang="en">
251 <head> 240 <head>
@@ -257,37 +246,36 @@ think of anything, paste this in there:
257 <h1>Hello, world!</h1> 246 <h1>Hello, world!</h1>
258 </body> 247 </body>
259</html> 248</html>
260#+end_src 249```
261 250
262If you want something to be served at =example.com/page01/file.txt=, 251If you want something to be served at
263you'll have to create the =page01= directory under the =example.com= 252`example.com/page01/file.txt`, you'll have to create the
253`page01` directory under the `example.com`
264directory. For example: 254directory. For example:
265 255
266#+begin_src sh 256```sh
267cd /var/www/example.com/public_html 257cd /var/www/example.com/public_html
268sudo mkdir page01 258sudo mkdir page01
269sudo nano file.txt 259sudo nano file.txt
270#+end_src 260```
261
262## Apache Configuration
271 263
272** Apache Configuration
273:PROPERTIES:
274:CUSTOM_ID: apache-configuration
275:END:
276Now, let's set up the files that will tell the server where to find the 264Now, let's set up the files that will tell the server where to find the
277files for =example.com=. We will copy the default configuration file and 265files for `example.com`. We will copy the default
278create our own. 266configuration file and create our own.
279 267
280#+begin_src sh 268```sh
281cd /etc/apache2/sites-available 269cd /etc/apache2/sites-available
282sudo cp 000-default.conf example.com.conf 270sudo cp 000-default.conf example.com.conf
283sudo nano example.com.conf 271sudo nano example.com.conf
284#+end_src 272```
285 273
286This configuration file will have a few default lines, but you'll need 274This configuration file will have a few default lines, but you'll need
287to edit it to look similar to this (settings may change based on your 275to edit it to look similar to this (settings may change based on your
288personal needs): 276personal needs):
289 277
290#+begin_src config 278```config
291<VirtualHost *:80> 279<VirtualHost *:80>
292 ServerAdmin your-email@email-provider.com 280 ServerAdmin your-email@email-provider.com
293 ServerName example.com 281 ServerName example.com
@@ -296,85 +284,85 @@ personal needs):
296 ErrorLog ${APACHE_LOG_DIR}/error.log 284 ErrorLog ${APACHE_LOG_DIR}/error.log
297 CustomLog ${APACHE_LOG_DIR}/access.log combined 285 CustomLog ${APACHE_LOG_DIR}/access.log combined
298</VirtualHost> 286</VirtualHost>
299#+end_src 287```
300 288
301Now, enable the configuration for your new site, disable the default 289Now, enable the configuration for your new site, disable the default
302configuration, and reload the web server. 290configuration, and reload the web server.
303 291
304#+begin_src sh 292```sh
305sudo a2ensite example.com.conf 293sudo a2ensite example.com.conf
306sudo a2dissite 000-default.conf 294sudo a2dissite 000-default.conf
307sudo systemctl reload apache2 295sudo systemctl reload apache2
308#+end_src 296```
309 297
310You can always run a test to make sure no errors or warnings are found 298You can always run a test to make sure no errors or warnings are found
311in your configuration files. 299in your configuration files.
312 300
313#+begin_src sh 301```sh
314sudo apache2ctl configtest 302sudo apache2ctl configtest
315#+end_src 303```
316 304
317Now, restart the web server entirely. After this, you should be able to 305Now, restart the web server entirely. After this, you should be able to
318browse to =http://example.com= and see the HTML content you provided 306browse to `http://example.com` and see the HTML content you
319earlier. Note that SSL/TLS has not been enabled yet, so you won't be 307provided earlier. Note that SSL/TLS has not been enabled yet, so you
320able to use the secure version yet (=https://example.com=). 308won't be able to use the secure version yet
309(`https://example.com`).
321 310
322#+begin_src sh 311```sh
323sudo systemctl restart apache2 312sudo systemctl restart apache2
324#+end_src 313```
325 314
326You can repeat this for as many websites as you need. Just create the 315You can repeat this for as many websites as you need. Just create the
327domain folders in =/var/www/=, add the configuration file, enable the 316domain folders in `/var/www/`, add the configuration file,
328configuration, and restart =apache2=. 317enable the configuration, and restart `apache2`.
318
319## SSL/TLS Certificates: Serve Websites Over HTTPS
329 320
330** SSL/TLS Certificates: Serve Websites Over HTTPS
331:PROPERTIES:
332:CUSTOM_ID: ssltls-certificates-serve-websites-over-https
333:END:
334In order to serve secure content, you'll need to obtain SSL/TLS 321In order to serve secure content, you'll need to obtain SSL/TLS
335certificates. Luckily, there's a free tool called 322certificates. Luckily, there's a free tool called
336[[https://certbot.eff.org][Certbot]] that helps us with the process. 323[Certbot](https://certbot.eff.org) that helps us with the process.
337 324
338The first step is to install =snapd= and =core= for Ubuntu. 325The first step is to install `snapd` and `core`
326for Ubuntu.
339 327
340#+begin_src sh 328```sh
341sudo apt install snapd 329sudo apt install snapd
342sudo snap install core 330sudo snap install core
343sudo snap refresh core 331sudo snap refresh core
344#+end_src 332```
345 333
346Next, install the =certbot= snap package. 334Next, install the `certbot` snap package.
347 335
348#+begin_src sh 336```sh
349sudo snap install --classic certbot 337sudo snap install --classic certbot
350#+end_src 338```
351 339
352Execute the following command to ensure that the =certbot= command can 340Execute the following command to ensure that the `certbot`
353be run. 341command can be run.
354 342
355#+begin_src sh 343```sh
356sudo ln -s /snap/bin/certbot /usr/bin/certbot 344sudo ln -s /snap/bin/certbot /usr/bin/certbot
357#+end_src 345```
358 346
359Finally, you can run =certbot= one of two ways: 347Finally, you can run `certbot` one of two ways:
360 348
3611. run it and let it alter your Apache configuration files automatically 3491. run it and let it alter your Apache configuration files
362 to enable HTTPS redirects. 350 automatically to enable HTTPS redirects.
3632. run it and only allow it to create certificates. You'll need to 3512. run it and only allow it to create certificates. You'll need to
364 manually alter the config files to enable HTTPS redirects. 352 manually alter the config files to enable HTTPS redirects.
365 353
366Run certbot and allow automatic config changes: 354Run certbot and allow automatic config changes:
367 355
368#+begin_src sh 356```sh
369sudo certbot --apache 357sudo certbot --apache
370#+end_src 358```
371 359
372Run certbot for certificates only and don't allow it to alter config 360Run certbot for certificates only and don't allow it to alter config
373files: 361files:
374 362
375#+begin_src sh 363```sh
376sudo certbot certonly --apache 364sudo certbot certonly --apache
377#+end_src 365```
378 366
379The Certbot packages on your system come with a cron job or systemd 367The Certbot packages on your system come with a cron job or systemd
380timer that will renew your certificates automatically before they 368timer that will renew your certificates automatically before they
@@ -382,52 +370,48 @@ expire. You will not need to run Certbot again unless you change your
382configuration. You can test automatic renewal for your certificates by 370configuration. You can test automatic renewal for your certificates by
383running this command: 371running this command:
384 372
385#+begin_src sh 373```sh
386sudo certbot renew --dry-run 374sudo certbot renew --dry-run
387#+end_src 375```
376
377Now, test your domains by going to `https://example.com`.
388 378
389Now, test your domains by going to =https://example.com=. 379## Firewall Security
390 380
391** Firewall Security
392:PROPERTIES:
393:CUSTOM_ID: firewall-security
394:END:
395To enable better security on your server, you'll need to enable a basic 381To enable better security on your server, you'll need to enable a basic
396firewall. For Ubuntu, we'll use 382firewall. For Ubuntu, we'll use [the uncomplicated
397[[https://cleberg.net/blog/secure-your-network-with-the-uncomplicated-firewall.html][the 383firewall](https://cleberg.net/blog/secure-your-network-with-the-uncomplicated-firewall.html).
398uncomplicated firewall]].
399 384
400Now, add the following rules to the firewall allow SSH, Apache, and 385Now, add the following rules to the firewall allow SSH, Apache, and
401HTTP(S) connections. If you need to, you can enable different ports for 386HTTP(S) connections. If you need to, you can enable different ports for
402specifics applications, SFTP, etc. 387specifics applications, SFTP, etc.
403 388
404#+begin_src sh 389```sh
405sudo ufw default deny incoming 390sudo ufw default deny incoming
406sudo ufw default allow outgoing 391sudo ufw default allow outgoing
407sudo ufw allow OpenSSH 392sudo ufw allow OpenSSH
408sudo ufw allow Apache 393sudo ufw allow Apache
409sudo ufw allow proto tcp from any to any port 80,443 394sudo ufw allow proto tcp from any to any port 80,443
410#+end_src 395```
411 396
412Once you've added all the rules you need, enable the firewall. 397Once you've added all the rules you need, enable the firewall.
413 398
414#+begin_src sh 399```sh
415sudo ufw enable 400sudo ufw enable
416#+end_src 401```
402
403## Troubleshooting
417 404
418** Troubleshooting
419:PROPERTIES:
420:CUSTOM_ID: troubleshooting
421:END:
422If you run into any issues during your VPS set-up, be sure to walk back 405If you run into any issues during your VPS set-up, be sure to walk back
423through your actions and make sure you didn't miss any steps. 406through your actions and make sure you didn't miss any steps.
424 407
425Many websites have fantastic guides to setting up various web servers. 408Many websites have fantastic guides to setting up various web servers.
426This is one of the areas 409This is one of the areas [where DigitalOcean
427[[https://www.digitalocean.%20com/community/tutorials][where 410shines](https://www.digitalocean.%20com/community/tutorials). For
428DigitalOcean shines]]. For simpler or more Linux-oriented questions, I 411simpler or more Linux-oriented questions, I suggest using [Linuxize]
429suggest using [Linuxize] (https://linuxize.com). 412(<https://linuxize.com>).
430 413
431If you're getting certain errors (e.g. =500 Internal Server Error=) and 414If you're getting certain errors (e.g. =500 Internal Server Error=) and
432need to debug locally, you can view the =access.log= and =error.log= 415need to debug locally, you can view the `access.log` and
433files in the =/var/log/apache/= directory. 416`error.log` files in the `/var/log/apache/`
417directory.
blog/2021-04-17-gemini-server.org → content/blog/2021-04-17-gemini-server.md renamed +78 −95
@@ -1,181 +1,164 @@
1#+title: Hosting a Gemini Server 1+++
2#+date: 2021-04-17 2date = 2021-04-17
3title = "Hosting a Gemini Server"
4description = "A guide to self-hosting a Gemini web server on your own server."
5+++
6
7## Similar Article Available
3 8
4** Similar Article Available
5:PROPERTIES:
6:CUSTOM_ID: similar-article-available
7:END:
8To read more about Gemini and ways to test out this new protocol without 9To read more about Gemini and ways to test out this new protocol without
9your own server, see my previous post 10your own server, see my previous post [Launching a Gemini
10[[/blog/launching-a-gemini-capsule/][Launching a Gemini Capsule]]. 11Capsule](/blog/launching-a-gemini-capsule/).
12
13## Preparation
11 14
12** Preparation
13:PROPERTIES:
14:CUSTOM_ID: preparation
15:END:
16This guide assumes you have access to a server accessible to the world 15This 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 16through a public IP address and that you own a domain name used for this
18Gemini capsule. 17Gemini capsule.
19 18
20** Getting Started with Agate 19## Getting Started with Agate
21:PROPERTIES: 20
22:CUSTOM_ID: getting-started-with-agate 21We are going to use [Agate](https://github.com/mbrubeck/agate) for this
23:END: 22tutorial. This is a basic Gemini server written in Rust. It takes very
24We are going to use [[https://github.com/mbrubeck/agate][Agate]] for 23little time and maintenance to get it running.
25this tutorial. This is a basic Gemini server written in Rust. It takes 24
26very little time and maintenance to get it running. 25## Install Dependencies
27 26
28** Install Dependencies
29:PROPERTIES:
30:CUSTOM_ID: install-dependencies
31:END:
32First, you will need to install the Rust package for your system. On 27First, you will need to install the Rust package for your system. On
33Ubuntu, use the following commands (remember to use =sudo= if you are 28Ubuntu, use the following commands (remember to use `sudo` if
34not the root user). The Rust installation will give you options to 29you are not the root user). The Rust installation will give you options
35customize the installation; I used the default installation options. 30to customize the installation; I used the default installation options.
36 31
37#+begin_src sh 32```sh
38sudo apt update && sudo apt upgrade -y 33sudo apt update && sudo apt upgrade -y
39curl https://sh.rustup.rs -sSf | sh 34curl https://sh.rustup.rs -sSf | sh
40#+end_src 35```
41 36
42Remember to configure your shell with the new configuration: 37Remember to configure your shell with the new configuration:
43 38
44#+begin_src sh 39```sh
45source $HOME/.cargo/env 40source $HOME/.cargo/env
46#+end_src 41```
47 42
48Before we install agate, make sure you have the =gcc= package installed: 43Before we install agate, make sure you have the `gcc` package
44installed:
49 45
50#+begin_src sh 46```sh
51sudo apt install gcc 47sudo apt install gcc
52#+end_src 48```
53 49
54Next, you'll need to install the agate executable with Rust's Cargo 50Next, you'll need to install the agate executable with Rust's Cargo
55package maintainer: 51package maintainer:
56 52
57#+begin_src sh 53```sh
58cargo install agate 54cargo install agate
59#+end_src 55```
56
57## Create Symlinks
60 58
61** Create Symlinks
62:PROPERTIES:
63:CUSTOM_ID: create-symlinks
64:END:
65Once Cargo has finished installing all the required packages, symlink 59Once Cargo has finished installing all the required packages, symlink
66the executable to your $PATH. 60the executable to your \$PATH.
67 61
68#+begin_src sh 62```sh
69sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate 63sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate
70#+end_src 64```
65
66## Using Agate's Built-In Installation Tool
71 67
72** Using Agate's Built-In Installation Tool
73:PROPERTIES:
74:CUSTOM_ID: using-agates-built-in-installation-tool
75:END:
76If you're running Ubuntu or Debian, use the Debian installation script 68If you're running Ubuntu or Debian, use the Debian installation script
77found in Agate's GitHub repository, under the =tools/debian= folder. 69found in Agate's GitHub repository, under the `tools/debian`
70folder.
78 71
79#+begin_src sh 72```sh
80git clone https://github.com/mbrubeck/agate 73git clone https://github.com/mbrubeck/agate
81cd agate/tools/debian 74cd agate/tools/debian
82sudo ./install.sh 75sudo ./install.sh
83#+end_src 76```
77
78## Configure the Gemini Service
84 79
85** Configure the Gemini Service
86:PROPERTIES:
87:CUSTOM_ID: configure-the-gemini-service
88:END:
89We have a little more to do, but since this script tries to immediately 80We have a little more to do, but since this script tries to immediately
90run the service, it will likely fail with an exit code. Let's add our 81run the service, it will likely fail with an exit code. Let's add our
91finishing touches. Edit the following file and replace the hostname with 82finishing touches. Edit the following file and replace the hostname with
92your desired URL. You can also change the directory where content will 83your desired URL. You can also change the directory where content will
93be served. 84be served.
94 85
95#+begin_src sh 86```sh
96sudo nano /etc/systemd/system/gemini.service 87sudo nano /etc/systemd/system/gemini.service
97#+end_src 88```
98 89
99#+begin_src sh 90```sh
100# Edit these lines to whatever you want - see the next code block for my personal configuration. 91# Edit these lines to whatever you want - see the next code block for my personal configuration.
101WorkingDirectory=/srv/gemini 92WorkingDirectory=/srv/gemini
102ExecStart=agate --hostname $(uname -n) --lang en 93ExecStart=agate --hostname $(uname -n) --lang en
103#+end_src 94```
104 95
105This is my personal config: 96This is my personal config:
106 97
107#+begin_src sh 98```sh
108WorkingDirectory=/var/gemini/ 99WorkingDirectory=/var/gemini/
109ExecStart=agate --hostname gemini.example.com --lang en 100ExecStart=agate --hostname gemini.example.com --lang en
110#+end_src 101```
111 102
112Since we've altered the systemd configuration files, we have to reload 103Since we've altered the systemd configuration files, we have to reload
113the daemon. Let's do that, restart our service, and check its status. 104the daemon. Let's do that, restart our service, and check its status.
114 105
115#+begin_src sh 106```sh
116sudo systemctl daemon-reload 107sudo systemctl daemon-reload
117sudo systemctl restart gemini.service 108sudo systemctl restart gemini.service
118sudo systemctl status gemini.service 109sudo systemctl status gemini.service
119#+end_src 110```
111
112## Fixing Systemd Errors
120 113
121** Fixing Systemd Errors
122:PROPERTIES:
123:CUSTOM_ID: fixing-systemd-errors
124:END:
125If you're still getting errors, the installation process may not have 114If you're still getting errors, the installation process may not have
126properly enabled the gemini service. Fix it with the following commands. 115properly enabled the gemini service. Fix it with the following commands.
127 116
128#+begin_src sh 117```sh
129sudo systemctl enable gemini.service 118sudo systemctl enable gemini.service
130sudo systemctl restart gemini.service 119sudo systemctl restart gemini.service
131sudo systemctl status gemini.service 120sudo systemctl status gemini.service
132#+end_src 121```
122
123## Firewall Rules
133 124
134** Firewall Rules
135:PROPERTIES:
136:CUSTOM_ID: firewall-rules
137:END:
138Great! Our server is now functional and running. The first consideration 125Great! Our server is now functional and running. The first consideration
139now is that you need to be able to access port 1965 on the server. If 126now is that you need to be able to access port 1965 on the server. If
140you have a firewall enabled, you'll need to open that port up. 127you have a firewall enabled, you'll need to open that port up.
141 128
142#+begin_src sh 129```sh
143sudo ufw allow 1965 130sudo ufw allow 1965
144sudo ufw reload 131sudo ufw reload
145#+end_src 132```
133
134## Creating Content
146 135
147** Creating Content
148:PROPERTIES:
149:CUSTOM_ID: creating-content
150:END:
151Let's create the Gemini capsule. Note that wherever you set the 136Let's create the Gemini capsule. Note that wherever you set the
152WorkingDirectory variable to earlier, Agate will expect you to put your 137WorkingDirectory variable to earlier, Agate will expect you to put your
153Gemini capsule contents in a sub-folder called "content." So, I place my 138Gemini capsule contents in a sub-folder called "content." So, I place
154files in "/var/gmi/content." I'm going to create that folder now and put 139my files in "/var/gmi/content." I'm going to create that folder now
155a file in there. 140and put a file in there.
156 141
157#+begin_src sh 142```sh
158sudo mkdir /var/gemini/content 143sudo mkdir /var/gemini/content
159sudo nano /var/gemini/content/index.gmi 144sudo nano /var/gemini/content/index.gmi
160#+end_src 145```
161 146
162You can put whatever you want in the "index.gmi" file, just make sure 147You can put whatever you want in the "index.gmi" file, just make sure
163it's valid Gemtext. 148it's valid Gemtext.
164 149
165** The Results 150## The Results
166:PROPERTIES: 151
167:CUSTOM_ID: the-results
168:END:
169Here are some screenshots of the Gemini page I just created in the 152Here are some screenshots of the Gemini page I just created in the
170[[https://gmi.skyjake.fi/lagrange/][Lagrange]] browser and the 153[Lagrange](https://gmi.skyjake.fi/lagrange/) browser and the
171[[https://github.com/makeworld-the-better-one/amfora][amfora]] browser. 154[amfora](https://github.com/makeworld-the-better-one/amfora) browser.
172 155
173#+caption: GUI Gemini browser 156![GUI Gemini
174[[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png]] 157browser](https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png)
175 158
176/Lagrange/ 159*Lagrange*
177 160
178#+caption: CLI Gemini browser 161![CLI Gemini
179[[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png]] 162browser](https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png)
180 163
181/Amfora/ 164*Amfora*
blog/2021-04-23-php-comment-system.org → content/blog/2021-04-23-php-comment-system.md renamed +95 −103
@@ -1,10 +1,11 @@
1#+title: Roll Your Own Static Commenting System in PHP 1+++
2#+date: 2021-04-23 2date = 2021-04-23
3title = "Roll Your Own Static Commenting System in PHP"
4description = "A simple guide to creating a commenting system in PHP."
5+++
6
7## The Terrible-ness of Commenting Systems
3 8
4** The Terrible-ness of Commenting Systems
5:PROPERTIES:
6:CUSTOM_ID: the-terrible-ness-of-commenting-systems
7:END:
8The current state of affairs regarding interactive comment systems is, 9The current state of affairs regarding interactive comment systems is,
9well, terrible. It is especially awful if you're a privacy conscious 10well, terrible. It is especially awful if you're a privacy conscious
10person who does not generally load third-party scripts or frames on the 11person who does not generally load third-party scripts or frames on the
@@ -15,26 +16,24 @@ something that should be standard.
15 16
16Of course, there are some really terrible options: 17Of course, there are some really terrible options:
17 18
18- Facebook Comments 19- Facebook Comments
19- Discourse 20- Discourse
20 21
21There are some options that are better but still use too many scripts, 22There are some options that are better but still use too many scripts,
22frames, or social integrations on your web page that could impact some 23frames, or social integrations on your web page that could impact some
23users: 24users:
24 25
25- Disqus 26- Disqus
26- Isso 27- Isso
27- Remark42 28- Remark42
28 29
29Lastly, I looked into a few unique ways of generating blog comments, 30Lastly, I looked into a few unique ways of generating blog comments,
30such as using Twitter threads or GitHub issues to automatically post 31such as using Twitter threads or GitHub issues to automatically post
31issues. However, these both rely on external third-party sites that I 32issues. However, these both rely on external third-party sites that I
32don't currently use. 33don't currently use.
33 34
34** Stay Static with Server-Side Comments 35## Stay Static with Server-Side Comments
35:PROPERTIES: 36
36:CUSTOM_ID: stay-static-with-server-side-comments
37:END:
38The main issue for my personal use-case is that my blog is completely, 37The main issue for my personal use-case is that my blog is completely,
39100% static. I use PHP on the back-end but website visitors only see 38100% static. I use PHP on the back-end but website visitors only see
40HTML and a single CSS file. No external javascript and no embedded 39HTML and a single CSS file. No external javascript and no embedded
@@ -42,14 +41,15 @@ frames.
42 41
43So, how do we keep a site static and still allow users to interact with 42So, how do we keep a site static and still allow users to interact with
44blog posts? The key actually pretty simple - I'm already using PHP, so 43blog posts? The key actually pretty simple - I'm already using PHP, so
45why not rely on the classic HTML =<form>= and a PHP script to save the 44why not rely on the classic HTML `<form>` and a PHP script to
46comments somewhere? As it turns out, this was a perfect solution for me. 45save the comments somewhere? As it turns out, this was a perfect
46solution for me.
47 47
48The second issue for my personal use-case is that I am trying to keep 48The second issue for my personal use-case is that I am trying to keep
49the contents of my website accessible over time, as described by 49the contents of my website accessible over time, as described by
50[cite/t:@brandur], in his post entitled 50@brandur, in his post entitled [Blog with Markdown + Git, and degrade
51[[https://brandur.org/fragments/graceful-degradation-time][Blog with 51gracefully through
52Markdown + Git, and degrade gracefully through time]] . 52time](https://brandur.org/fragments/graceful-degradation-time) .
53 53
54This means I cannot rely on a database for comments, since I do not rely 54This means I cannot rely on a database for comments, since I do not rely
55on a database for any other part of my websites. 55on a database for any other part of my websites.
@@ -59,37 +59,35 @@ that future readers will be able to see the source data long after I'm
59gone, or the website has gone offline. However, I still haven't 59gone, or the website has gone offline. However, I still haven't
60committed any images served on my blog to Git, as I'm not entirely sold 60committed any images served on my blog to Git, as I'm not entirely sold
61on Git LFS yet - for now, images can be found at 61on Git LFS yet - for now, images can be found at
62[[https://img.cleberg.net][img.cleberg.net]]. 62[img.cleberg.net](https://img.cleberg.net).
63 63
64Saving my comments back to the Git repository ensures that another 64Saving my comments back to the Git repository ensures that another
65aspect of my site will degrade gracefully. 65aspect of my site will degrade gracefully.
66 66
67** Create a Comment Form 67## Create a Comment Form
68:PROPERTIES: 68
69:CUSTOM_ID: create-a-comment-form
70:END:
71Okay, let's get started. The first step is to create an HTML form that 69Okay, let's get started. The first step is to create an HTML form that
72users can see and utilize to submit comments. This is fairly easy and 70users can see and utilize to submit comments. This is fairly easy and
73can be changed depending on your personal preferences. 71can be changed depending on your personal preferences.
74 72
75Take a look at the code block below for the form I currently use. Note 73Take a look at the code block below for the form I currently use. Note
76that =<current-url>= is replaced automatically in PHP with the current 74that `<current-url>` is replaced automatically in PHP with
77post's URL, so that my PHP script used later will know which blog post 75the current post's URL, so that my PHP script used later will know
78the comment is related to. 76which blog post the comment is related to.
79 77
80The form contains the following structure: 78The form contains the following structure:
81 79
821. =<form>= - This is the form and will determine which PHP script to 801. `<form>` - This is the form and will determine which PHP
83 send the comment to. 81 script to send the comment to.
842. =<section hidden>= - This section is hidden from the user and is used 822. `<section hidden>` - This section is hidden from the user
85 to ensure that we know which blog post sent the comment. 83 and is used to ensure that we know which blog post sent the comment.
863. =<section>= Display Name (Optional) - Used to accept a display name, 843. `<section>` Display Name (Optional) - Used to accept a
87 if entered. 85 display name, if entered.
884. =<section>= Comment (Required) - Used to accept the user's full 864. `<section>` Comment (Required) - Used to accept the
89 comment. Markdown is allowed. 87 user's full comment. Markdown is allowed.
905. =<button>= - A button to submit the form. 885. `<button>` - A button to submit the form.
91 89
92#+begin_src html 90```html
93<form action="/comment.php" method="POST"> 91<form action="/comment.php" method="POST">
94 <h3>Leave a Comment</h3> 92 <h3>Leave a Comment</h3>
95 <section hidden> 93 <section hidden>
@@ -129,33 +127,32 @@ The form contains the following structure:
129 </section> 127 </section>
130 <button type="submit">Submit</button> 128 <button type="submit">Submit</button>
131</form> 129</form>
132#+end_src 130```
131
132## Handle Comments via POST
133 133
134** Handle Comments via POST
135:PROPERTIES:
136:CUSTOM_ID: handle-comments-via-post
137:END:
138Now that we have a form and can submit comments, we need to create a PHP 134Now that we have a form and can submit comments, we need to create a PHP
139script so that the server can fetch the comment data and save it. Make 135script so that the server can fetch the comment data and save it. Make
140sure your PHP script name matches the name you entered in the =action= 136sure your PHP script name matches the name you entered in the
141field in your form. 137`action` field in your form.
142 138
143See the code block below for the contents of my =comment.php= script. We 139See the code block below for the contents of my `comment.php`
144perform the following tasks in this script: 140script. We perform the following tasks in this script:
145 141
1461. Grab the POST data from the HTML form. 1421. Grab the POST data from the HTML form.
1472. Sanitize the comment data with =htmlentities=. 1432. Sanitize the comment data with `htmlentities`.
1483. Set the display name to =Anonymous= if it was left blank. 1443. Set the display name to `Anonymous` if it was left blank.
1494. Create a PHP object that combines all of this data. 1454. Create a PHP object that combines all of this data.
1505. Check if our destination file =comments.json= exists. 1465. Check if our destination file `comments.json` exists.
1516. If so, convert the PHP object to JSON and save it to the file. 1476. If so, convert the PHP object to JSON and save it to the file.
1527. If the =comments.json= file does not exist, the script will exit with 1487. If the `comments.json` file does not exist, the script
153 an error. You can alter this to ensure it creates the script, but my 149 will exit with an error. You can alter this to ensure it creates the
154 source code includes the file by default, so it will always exist. 150 script, but my source code includes the file by default, so it will
1558. Finally, send the user back to the =#comments= section of the blog 151 always exist.
156 post they just read. 1528. Finally, send the user back to the `#comments` section of
157 153 the blog post they just read.
158#+begin_src php 154
155```php
159// Get the content sent from the comment form 156// Get the content sent from the comment form
160$comment = htmlentities($_POST['userContent']); 157$comment = htmlentities($_POST['userContent']);
161$post_url = $_POST['postURL']; 158$post_url = $_POST['postURL'];
@@ -190,35 +187,33 @@ if (file_exists($file_name)) {
190 187
191// Send the user back 188// Send the user back
192header('Location: ' . $post_url . '#comments'); 189header('Location: ' . $post_url . '#comments');
193#+end_src 190```
194 191
195If you're using Apache, make sure the =www-data= user on your server has 192If you're using Apache, make sure the `www-data` user on
196the correct permissions to your website directory or else it will not be 193your server has the correct permissions to your website directory or
197able to write to =comments.json=. 194else it will not be able to write to `comments.json`.
198 195
199#+begin_src sh 196```sh
200chgrp -R www-data /path/to/website/ 197chgrp -R www-data /path/to/website/
201chmod -R g+w comments.json 198chmod -R g+w comments.json
202#+end_src 199```
200
201## Display User Comments
203 202
204** Display User Comments
205:PROPERTIES:
206:CUSTOM_ID: display-user-comments
207:END:
208Now that we can submit comments, and they are saved to the 203Now that we can submit comments, and they are saved to the
209=comments.json= file, let's make sure we can show those comments in each 204`comments.json` file, let's make sure we can show those
210blog post. 205comments in each blog post.
211 206
212The code block below shows the function I use to decode my 207The code block below shows the function I use to decode my
213=comments.json= file, check if the comments apply to the current post, 208`comments.json` file, check if the comments apply to the
214and then display them. 209current post, and then display them.
215 210
216This piece of code should *really* be inside a function (or at least in 211This piece of code should **really** be inside a function (or at least
217an organized PHP workflow). Don't just copy-and-paste and expect it to 212in an organized PHP workflow). Don't just copy-and-paste and expect it
218work. You need to at least supply a =$query= variable depending on the 213to work. You need to at least supply a `$query` variable
219page visited. 214depending on the page visited.
220 215
221#+begin_src php 216```php
222$query = 'your-blog-post.html'; 217$query = 'your-blog-post.html';
223 218
224// Load saved comments 219// Load saved comments
@@ -250,35 +245,32 @@ foreach ($comments as $comment) {
250} 245}
251 246
252echo $comment_section; 247echo $comment_section;
253#+end_src 248```
249
250## Bonus: Create a 'Recent Comments' Page
254 251
255** Bonus: Create a 'Recent Comments' Page
256:PROPERTIES:
257:CUSTOM_ID: bonus-create-a-recent-comments-page
258:END:
259Finally, the last part of my current system is to create a Recent 252Finally, the last part of my current system is to create a Recent
260Comments page so that I can easily check-in on my blog and moderate any 253Comments page so that I can easily check-in on my blog and moderate any
261spam. As an alternative, you could use PHP's =mail()= function to email 254spam. As an alternative, you could use PHP's `mail()`
262you for each blog comment. 255function to email you for each blog comment.
263 256
264The code to do this is literally the same code as the previous section, 257The code to do this is literally the same code as the previous section,
265I just make sure it is printed when someone visits 258I just make sure it is printed when someone visits
266=https://example.com/comments/=. 259`https://example.com/comments/`.
260
261## Possible Enhancements
267 262
268** Possible Enhancements
269:PROPERTIES:
270:CUSTOM_ID: possible-enhancements
271:END:
272This comment system is by no means a fully-developed system. I have 263This comment system is by no means a fully-developed system. I have
273noted a few possible enhancements here that I may implement in the 264noted a few possible enhancements here that I may implement in the
274future: 265future:
275 266
276- Create a secure moderator page with user authentication at 267- Create a secure moderator page with user authentication at
277 =https://blog.example.com/mod/=. This page could have the option to 268 `https://blog.example.com/mod/`. This page could have the
278 edit or delete any comment found in =comments.json=. 269 option to edit or delete any comment found in
279- Create a temporary file, such as =pending_comments.json=, that will 270 `comments.json`.
280 store newly-submitted comments and won't display on blog posts until 271- Create a temporary file, such as `pending_comments.json`,
281 approved by a moderator. 272 that will store newly-submitted comments and won't display on blog
282- Create a =/modlog/= page with a chronological log, showing which 273 posts until approved by a moderator.
283 moderator approved which comments and why certain comments were 274- Create a `/modlog/` page with a chronological log,
284 rejected. 275 showing which moderator approved which comments and why certain
276 comments were rejected.
blog/2021-04-28-photography.org → content/blog/2021-04-28-photography.md renamed +34 −37
@@ -1,71 +1,68 @@
1#+title: Jumping Back Into Photography 1+++
2#+date: 2021-04-28 2date = 2021-04-28
3title = "Jumping Back Into Photography"
4description = "Some thoughts on photography."
5+++
3 6
4** Why Photography? 7## Why Photography?
5:PROPERTIES: 8
6:CUSTOM_ID: why-photography 9I've often wondered why photography is as enticing as it is. You can
7:END: 10see billions of people around the world taking photographs every single
8I've often wondered why photography is as enticing as it is. You can see
9billions of people around the world taking photographs every single
10moment of the day. New technology often boasts about their photographic 11moment of the day. New technology often boasts about their photographic
11capabilities, especially smartphones. I would even assume that we live 12capabilities, especially smartphones. I would even assume that we live
12in a world where there is never a moment in which a photograph is not 13in a world where there is never a moment in which a photograph is not
13being taken somewhere on Earth. 14being taken somewhere on Earth.
14 15
15As for myself, I would simply say that I enjoy preserving a memory in 16As for myself, I would simply say that I enjoy preserving a memory in
16physical (or digital) form. I've never had the best memory when it comes 17physical (or digital) form. I've never had the best memory when it
17to recalling details of places and people gone by, so it helps to have a 18comes to recalling details of places and people gone by, so it helps to
18frame of reference lying around. 19have a frame of reference lying around.
19 20
20Regardless of the reason, I think most people would agree that you 21Regardless of the reason, I think most people would agree that you
21simply cannot have too many hobbies. 22simply cannot have too many hobbies.
22 23
23** Older Cameras 24## Older Cameras
24:PROPERTIES: 25
25:CUSTOM_ID: older-cameras
26:END:
27I started playing around with the idea of photography when my family 26I started playing around with the idea of photography when my family
28purchased a Fujifilm camera for family-specific events. I don't recall 27purchased 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 28the specific model, but I do recall it was a point-and-shoot camera
30without an interchangeable lens. However, it was of great value to 29without an interchangeable lens. However, it was of great value to
31someone, like myself, who couldn't afford any other camera. I took about 30someone, like myself, who couldn't afford any other camera. I took
3210,000 shots with that camera over a 3-5 year span. Most notably, all of 31about 10,000 shots with that camera over a 3-5 year span. Most notably,
33my trips to California were documented through this camera. 32all of my trips to California were documented through this camera.
34 33
35When possible, I would borrow my sister's camera, which is a Sony 34When 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 35SLT-A58. This camera was great and allowed for some of my best early
37shots, especially those taken in Utah's and Nevada's parks. 36shots, especially those taken in Utah's and Nevada's parks.
38 37
39** My Current Kit 38## My Current Kit
40:PROPERTIES: 39
41:CUSTOM_ID: my-current-kit
42:END:
43I've finally come to a point in my life where I have the disposable 40I've finally come to a point in my life where I have the disposable
44income to invest in a solid photography kit. I played around with the 41income to invest in a solid photography kit. I played around with the
45idea of a lot of different cameras, different types, new vs used, etc. 42idea of a lot of different cameras, different types, new vs used, etc.
46Finally, I settled on the 43Finally, I settled on the [Sony α7
47[[https://en.wikipedia.org/wiki/Sony_%CE%B17_III][Sony α7 III]]. This 44III](https://en.wikipedia.org/wiki/Sony_%CE%B17_III). This camera is
48camera is mirror-less and uses a full-frame image sensor at 24 45mirror-less and uses a full-frame image sensor at 24 megapixels. I
49megapixels. I don't create large prints, and I am mostly focused on 46don't create large prints, and I am mostly focused on preserving
50preserving memories in high quality for the next 5-10 years with this 47memories in high quality for the next 5-10 years with this camera, so
51camera, so the specifications here are just perfect for me. 48the specifications here are just perfect for me.
52 49
53For lenses, I decided to buy two lenses that could carry me through most 50For lenses, I decided to buy two lenses that could carry me through most
54situations: 51situations:
55 52
56- [[https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z][Vario-Tessar 53- [Vario-Tessar T\* FE 24-70 mm F4 ZA
57 T* FE 24-70 mm F4 ZA OSS]] 54 OSS](https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z)
58- [[https://www.tamron-usa.com/product/lenses/a047.html][Tamron 70-300mm 55- [Tamron 70-300mm f4.5-6.3 Di III
59 f4.5-6.3 Di III RXD]] 56 RXD](https://www.tamron-usa.com/product/lenses/a047.html)
60 57
61In addition, I grabbed a couple 58In addition, I grabbed a couple [HGX Prime
62[[https://www.promaster.com/Product/6725][HGX Prime 67mm]] protection 5967mm](https://www.promaster.com/Product/6725) protection filters for the
63filters for the lenses. 60lenses.
64 61
65As I delve further into photography and pick up more skills, I will most 62As I delve further into photography and pick up more skills, I will most
66likely go back and grab a lens with a higher f-stop value, such as 63likely go back and grab a lens with a higher f-stop value, such as
67f/1.8. I toyed with the idea of grabbing a 50 mm at =f/1.8=, but decided 64f/1.8. I toyed with the idea of grabbing a 50 mm at `f/1.8`,
68to keep things in a reasonable price range instead. 65but decided to keep things in a reasonable price range instead.
69 66
70Finally, I made sure to buy a photography-specific backpack with a rain 67Finally, I made sure to buy a photography-specific backpack with a rain
71guard, and the zipper on the back panel, to protect the equipment while 68guard, and the zipper on the back panel, to protect the equipment while
blog/2021-05-30-changing-git-authors.org → content/blog/2021-05-30-changing-git-authors.md renamed +18 −17
@@ -1,10 +1,11 @@
1#+title: Changing Git Authors 1+++
2#+date: 2021-05-30 2date = 2021-05-30
3title = "Changing Git Authors"
4description = "A guide to change Git author names and emails in old commits."
5+++
6
7## Changing Git Author/Email Based on Previously Committed Email
3 8
4** Changing Git Author/Email Based on Previously Committed Email
5:PROPERTIES:
6:CUSTOM_ID: changing-git-authoremail-based-on-previously-committed-email
7:END:
8Here's the dilemma: You've been committing changes to your git 9Here's the dilemma: You've been committing changes to your git
9repository with an incorrect name or email (or multiple repositories), 10repository with an incorrect name or email (or multiple repositories),
10and now you want to fix it. Luckily, there's a semi-reliable way to fix 11and now you want to fix it. Luckily, there's a semi-reliable way to fix
@@ -14,18 +15,18 @@ of commits, so use this method only if you're okay accepting that risk.
14 15
15Okay, let's create the bash script: 16Okay, let's create the bash script:
16 17
17#+begin_src sh 18```sh
18nano change_git_authors.sh 19nano change_git_authors.sh
19#+end_src 20```
20 21
21The following information can be pasted directly into your bash script. 22The following information can be pasted directly into your bash script.
22The only changes you need to make are to the following variables: 23The only changes you need to make are to the following variables:
23 24
24- =OLD_EMAIL= 25- `OLD_EMAIL`
25- =CORRECT_NAME= 26- `CORRECT_NAME`
26- =CORRECT_EMAIL= 27- `CORRECT_EMAIL`
27 28
28#+begin_src sh 29```sh
29#!/bin/sh 30#!/bin/sh
30 31
31# List all sub-directories in the current directory 32# List all sub-directories in the current directory
@@ -58,16 +59,16 @@ do
58 59
59 cd .. 60 cd ..
60done 61done
61#+end_src 62```
62 63
63Finally, save the bash script and make it executable. 64Finally, save the bash script and make it executable.
64 65
65#+begin_src sh 66```sh
66chmod a+x change_git_authors.sh 67chmod a+x change_git_authors.sh
67#+end_src 68```
68 69
69Now you can run the script and should see the process begin. 70Now you can run the script and should see the process begin.
70 71
71#+begin_src sh 72```sh
72./change_git_authors.sh 73./change_git_authors.sh
73#+end_src 74```
blog/2021-07-15-delete-gitlab-repos.org → content/blog/2021-07-15-delete-gitlab-repos.md renamed +34 −39
@@ -1,10 +1,11 @@
1#+title: How to Delete All GitLab Repositories 1+++
2#+date: 2021-07-15 2date = 2021-07-15
3title = "How to Delete All GitLab Repositories"
4description = "Learn how to delete all GitLab repositories in your account."
5+++
6
7## Background
3 8
4** Background
5:PROPERTIES:
6:CUSTOM_ID: background
7:END:
8Have you ever used GitLab to host your source code, moved to a different 9Have you ever used GitLab to host your source code, moved to a different
9host, and wanted to delete everything from your GitLab account? Well, 10host, and wanted to delete everything from your GitLab account? Well,
10this post covers any scenario where you would want to delete all 11this post covers any scenario where you would want to delete all
@@ -15,43 +16,37 @@ manually delete them whenever I switch host. GitHub has a few different
15tools online to delete all repositories for you, but I have not found 16tools online to delete all repositories for you, but I have not found
16anything similar for GitLab, so I needed an alternative solution. 17anything similar for GitLab, so I needed an alternative solution.
17 18
18** Use a Python Script 19## Use a Python Script
19:PROPERTIES: 20
20:CUSTOM_ID: use-a-python-script 21### Requirements
21:END: 22
22*** Requirements
23:PROPERTIES:
24:CUSTOM_ID: requirements
25:END:
26Before we look at the script, make sure you know your GitLab username. 23Before we look at the script, make sure you know your GitLab username.
27Next, [[https://gitlab.com/-/profile/personal_access_tokens][create an 24Next, [create an authorization
28authorization token]] so that the Python script can delete your 25token](https://gitlab.com/-/profile/personal_access_tokens) so that the
29repositories. Don't lose this token or else you'll need to create a new 26Python script can delete your repositories. Don't lose this token or
30one. 27else you'll need to create a new one.
31 28
32*** Create the Script 29### Create the Script
33:PROPERTIES: 30
34:CUSTOM_ID: create-the-script
35:END:
36To run a Python script, you must first create it. Open a terminal and 31To run a Python script, you must first create it. Open a terminal and
37enter the following commands in whichever directory you prefer to store 32enter the following commands in whichever directory you prefer to store
38the script. You can do the same things in a file manager if you prefer. 33the script. You can do the same things in a file manager if you prefer.
39 34
40#+begin_src sh 35```sh
41mkdir delete-gitlab 36mkdir delete-gitlab
42#+end_src 37```
43 38
44#+begin_src sh 39```sh
45cd delete-gitlab 40cd delete-gitlab
46#+end_src 41```
47 42
48#+begin_src sh 43```sh
49nano main.py 44nano main.py
50#+end_src 45```
51 46
52Enter the following code into your =main.py= script. 47Enter the following code into your `main.py` script.
53 48
54#+begin_src python 49```python
55import request 50import request
56import json 51import json
57 52
@@ -105,16 +100,16 @@ def main():
105 100
106if __name__ == "__main__": 101if __name__ == "__main__":
107 main() 102 main()
108#+end_src 103```
109 104
110Now that you have the proper information, replace ={user-id}= with your 105Now that you have the proper information, replace `{user-id}`
111GitLab username and ={auth-token}= with the authorization token you 106with your GitLab username and `{auth-token}` with the
112created earlier. 107authorization token you created earlier.
113 108
114Finally, simply run the script and watch the output. You can also use 109Finally, simply run the script and watch the output. You can also use
115PyCharm Community Edition to edit and run the Python script if you don't 110PyCharm Community Edition to edit and run the Python script if you
116want to work in a terminal. 111don't want to work in a terminal.
117 112
118#+begin_src sh 113```sh
119python3 main.py 114python3 main.py
120#+end_src 115```
blog/2021-08-25-audit-sampling.org → content/blog/2021-08-25-audit-sampling.md renamed +87 −102
@@ -1,10 +1,11 @@
1#+title: Audit Sampling with Python 1+++
2#+date: 2021-08-25 2date = 2021-08-25
3title = "Audit Sampling with Python"
4description = "Learn how to sample populations with Python."
5+++
6
7## Introduction
3 8
4** Introduction
5:PROPERTIES:
6:CUSTOM_ID: introduction
7:END:
8For anyone who is familiar with internal auditing, external auditing, or 9For anyone who is familiar with internal auditing, external auditing, or
9consulting, you will understand how tedious audit testing can become 10consulting, you will understand how tedious audit testing can become
10when you are required to test large swaths of data. When we cannot 11when you are required to test large swaths of data. When we cannot
@@ -18,38 +19,34 @@ quite a lot of people. While some audit-focused tools have introduced
18sampling functionality (e.g. Wdesk), many audit departments and firms 19sampling functionality (e.g. Wdesk), many audit departments and firms
19cannot use software like this due to certain constraints, such as the 20cannot use software like this due to certain constraints, such as the
20team's budget or knowledge. Here is where this article comes in: we're 21team's budget or knowledge. Here is where this article comes in: we're
21going to use [[https://www.python.org][Python]], a free and open-source 22going to use [Python](https://www.python.org), a free and open-source
22programming language, to generate random samples from a dataset in order 23programming language, to generate random samples from a dataset in order
23to suffice numerous audit situations. 24to suffice numerous audit situations.
24 25
25** Audit Requirements for Sampling 26## Audit Requirements for Sampling
26:PROPERTIES: 27
27:CUSTOM_ID: audit-requirements-for-sampling
28:END:
29Before we get into the details of how to sample with Python, I want to 28Before we get into the details of how to sample with Python, I want to
30make sure I discuss the different requirements that auditors may have of 29make sure I discuss the different requirements that auditors may have of
31samples used within their projects. 30samples used within their projects.
32 31
33*** Randomness 32### Randomness
34:PROPERTIES: 33
35:CUSTOM_ID: randomness 34First, let's discuss randomness. When testing out new technology to
36:END: 35help assist with audit sampling, you need to understand exactly how your
37First, let's discuss randomness. When testing out new technology to help
38assist with audit sampling, you need to understand exactly how your
39samples are being generated. For example, if the underlying function is 36samples are being generated. For example, if the underlying function is
40just picking every 57th element from a list, that's not truly random; 37just picking every 57th element from a list, that's not truly random;
41it's a systematic form of sampling. Luckily, since Python is 38it's a systematic form of sampling. Luckily, since Python is
42open-source, we have access to its codebase. Through this blog post, I 39open-source, we have access to its codebase. Through this blog post, I
43will be using the [[https://pandas.pydata.org][pandas]] module in order 40will be using the [pandas](https://pandas.pydata.org) module in order to
44to generate the random samples. More specifically, I will be using the 41generate the random samples. More specifically, I will be using the
45[[https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html][pandas.DataFrame.sample]] 42[pandas.DataFrame.sample](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html)
46function provided by Pandas. 43function provided by Pandas.
47 44
48Now that you know what you're using, you can always check out the code 45Now that you know what you're using, you can always check out the code
49behind =pandas.DataFrame.sample=. This function does a lot of work, but 46behind `pandas.DataFrame.sample`. This function does a lot of
50we really only care about the following snippets of code: 47work, but we really only care about the following snippets of code:
51 48
52#+begin_src python 49```python
53# Process random_state argument 50# Process random_state argument
54rs = com.random_state(random_state) 51rs = com.random_state(random_state)
55 52
@@ -61,25 +58,24 @@ if ignore_index:
61result.index = ibase.default_index(len(result)) 58result.index = ibase.default_index(len(result))
62 59
63return result 60return result
64#+end_src 61```
65 62
66The block of code above shows you that if you assign a =random_state= 63The block of code above shows you that if you assign a
67argument when you run the function, that will be used as a seed number 64`random_state` argument when you run the function, that will
68in the random generation and will allow you to reproduce a sample, given 65be used as a seed number in the random generation and will allow you to
69that nothing else changes. This is critical to the posterity of audit 66reproduce a sample, given that nothing else changes. This is critical to
70work. After all, how can you say your audit process is adequately 67the posterity of audit work. After all, how can you say your audit
71documented if the next person can't run the code and get the same 68process is adequately documented if the next person can't run the code
72sample? The final piece here on randomness is to look at the 69and get the same sample? The final piece here on randomness is to look
73[[https://docs.%20python.org/3/library/random.html#random.choice][choice]] 70at the
71[choice](https://docs.%20python.org/3/library/random.html#random.choice)
74function used above. This is the crux of the generation and can also be 72function used above. This is the crux of the generation and can also be
75examined for more detailed analysis on its reliability. As far as 73examined for more detailed analysis on its reliability. As far as
76auditing goes, we will trust that these functions are mathematically 74auditing goes, we will trust that these functions are mathematically
77random. 75random.
78 76
79*** Sample Sizes 77### Sample Sizes
80:PROPERTIES: 78
81:CUSTOM_ID: sample-sizes
82:END:
83As mentioned in the intro, sampling is only an effective method of 79As mentioned in the intro, sampling is only an effective method of
84auditing when it truly represents the entire population. While some 80auditing when it truly represents the entire population. While some
85audit departments or firms may consider certain judgmental sample sizes 81audit departments or firms may consider certain judgmental sample sizes
@@ -98,38 +94,35 @@ proprietary, table that will instruct auditors which sample sizes to
98choose. This allows for uniform testing and reduces overall workload. 94choose. This allows for uniform testing and reduces overall workload.
99See the table below for a common implementation of sample sizes: 95See the table below for a common implementation of sample sizes:
100 96
101| Control Frequency | Sample Size - High Risk | Sample Size - Low Risk | 97 Control Frequency Sample Size - High Risk Sample Size - Low Risk
102|-------------------+-------------------------+------------------------| 98 ------------------- ------------------------- ------------------------
103| More Than Daily | 40 | 25 | 99 More Than Daily 40 25
104| Daily | 40 | 25 | 100 Daily 40 25
105| Weekly | 12 | 5 | 101 Weekly 12 5
106| Monthly | 5 | 3 | 102 Monthly 5 3
107| Quarterly | 2 | 2 | 103 Quarterly 2 2
108| Semi-Annually | 1 | 1 | 104 Semi-Annually 1 1
109| Annually | 1 | 1 | 105 Annually 1 1
110| Ad-hoc | 1 | 1 | 106 Ad-hoc 1 1
111 107
112** Sampling with Python & Pandas 108## Sampling with Python & Pandas
113:PROPERTIES: 109
114:CUSTOM_ID: sampling-with-python-pandas
115:END:
116In this section, I am going to cover a few basic audit situations that 110In this section, I am going to cover a few basic audit situations that
117require sampling. While some situations may require more effort, the 111require sampling. While some situations may require more effort, the
118syntax, organization, and intellect used remain largely the same. If 112syntax, organization, and intellect used remain largely the same. If
119you've never used Python before, note that lines starting with a '=#=' 113you've never used Python before, note that lines starting with a
120symbol are called comments, and they will be skipped by Python. I highly 114'`#`' symbol are called comments, and they will be skipped
121recommend taking a quick tutorial online to understand the basics of 115by Python. I highly recommend taking a quick tutorial online to
122Python if any of the code below is confusing to you. 116understand the basics of Python if any of the code below is confusing to
123 117you.
124*** Simple Random Sample 118
125:PROPERTIES: 119### Simple Random Sample
126:CUSTOM_ID: simple-random-sample 120
127:END:
128First, let's look at a simple, random sample. The code block below will 121First, let's look at a simple, random sample. The code block below will
129import the =pandas= module, load a data file, sample the data, and 122import the `pandas` module, load a data file, sample the
130export the sample to a file. 123data, and export the sample to a file.
131 124
132#+begin_src python 125```python
133# Import the Pandas module 126# Import the Pandas module
134import pandas 127import pandas
135 128
@@ -147,16 +140,14 @@ sample = df.sample(n=25, random_state=0)
147 140
148# Save the sample to Excel 141# Save the sample to Excel
149sample.to_excel(file_output) 142sample.to_excel(file_output)
150#+end_src 143```
144
145### Simple Random Sample: Using Multiple Input Files
151 146
152*** Simple Random Sample: Using Multiple Input Files
153:PROPERTIES:
154:CUSTOM_ID: simple-random-sample-using-multiple-input-files
155:END:
156Now that we've created a simple sample, let's create a sample from 147Now that we've created a simple sample, let's create a sample from
157multiple files. 148multiple files.
158 149
159#+begin_src python 150```python
160# Import the Pandas module 151# Import the Pandas module
161import pandas 152import pandas
162 153
@@ -183,17 +174,15 @@ sample = pandas.concat([sample_01, sample_02, sample_03], ignore_index=True)
183 174
184# Save the sample to Excel 175# Save the sample to Excel
185sample.to_excel(file_output) 176sample.to_excel(file_output)
186#+end_src 177```
178
179### Stratified Random Sample
187 180
188*** Stratified Random Sample
189:PROPERTIES:
190:CUSTOM_ID: stratified-random-sample
191:END:
192Well, what if you need to sample distinct parts of a single file? For 181Well, what if you need to sample distinct parts of a single file? For
193example, let's write some code to separate our data by "Region" and 182example, let's write some code to separate our data by "Region" and
194sample those regions independently. 183sample those regions independently.
195 184
196#+begin_src python 185```python
197# Import the Pandas module 186# Import the Pandas module
198import pandas 187import pandas
199 188
@@ -219,18 +208,16 @@ sample = pandas.concat([sample_east, sample_west], ignore_index=True)
219 208
220# Save the sample to Excel 209# Save the sample to Excel
221sample.to_excel(file_output) 210sample.to_excel(file_output)
222#+end_src 211```
212
213### Stratified Systematic Sample
223 214
224*** Stratified Systematic Sample
225:PROPERTIES:
226:CUSTOM_ID: stratified-systematic-sample
227:END:
228This next example is quite useful if you need audit coverage over a 215This next example is quite useful if you need audit coverage over a
229certain time period. This code will generate samples for each month in 216certain time period. This code will generate samples for each month in
230the data and combine them all together at the end. Obviously, this code 217the data and combine them all together at the end. Obviously, this code
231can be modified to stratify by something other than months, if needed. 218can be modified to stratify by something other than months, if needed.
232 219
233#+begin_src python 220```python
234# Import the Pandas module 221# Import the Pandas module
235import pandas 222import pandas
236 223
@@ -270,23 +257,21 @@ def monthly_stratified_sample(df: pandas.DataFrame, date_column: str, num_select
270sample_size = 3 257sample_size = 3
271sample = monthly_stratified_sample(df, 'Date of Sale', sample_size) 258sample = monthly_stratified_sample(df, 'Date of Sale', sample_size)
272sample.to_excel(file_output) 259sample.to_excel(file_output)
273#+end_src 260```
274 261
275** Documenting the Results 262## Documenting the Results
276:PROPERTIES: 263
277:CUSTOM_ID: documenting-the-results 264Once you've generated a proper sample, there are a few things left to
278:END: 265do in order to properly ensure your process is reproducible.
279Once you've generated a proper sample, there are a few things left to do 266
280in order to properly ensure your process is reproducible. 2671. Document the sample. Make sure the resulting file is readable and
281 268 includes the documentation listed in the next bullet.
2821. Document the sample. Make sure the resulting file is readable and 2692. Include documentation around the data source, extraction techniques,
283 includes the documentation listed in the next bullet. 270 any modifications made to the data, and be sure to include a copy of
2842. Include documentation around the data source, extraction techniques, 271 the script itself.
285 any modifications made to the data, and be sure to include a copy of 2723. Whenever possible, perform a completeness and accuracy test to
286 the script itself. 273 ensure your sample is coming from a complete and accurate
2873. Whenever possible, perform a completeness and accuracy test to ensure 274 population. To ensure completeness, compare the record count from
288 your sample is coming from a complete and accurate population. To 275 the data source to the record count loaded into Python. To ensure
289 ensure completeness, compare the record count from the data source to 276 accuracy, test a small sample against the source data (e.g., test 5
290 the record count loaded into Python. To ensure accuracy, test a small 277 sales against the database to see if the details are accurate).
291 sample against the source data (e.g., test 5 sales against the
292 database to see if the details are accurate).
content/blog/2021-10-09-apache-redirect.md added +46
@@ -0,0 +1,46 @@
1+++
2date = 2021-10-09
3title = "Apache Redirect HTML Files to a Directory"
4description = "A guide on redirecting HTML files to directory in Apache."
5+++
6
7## The Problem
8
9After recently switching static site generators (SSG), my blog URLs
10changed with no option to preserve the classic `.html`
11extension at the end of my blog post URLs.
12
13I really disliked using my old SSG ([Jekyll](https://jekyllrb.com)) and
14prefer my new tool ([Zola](https://www.getzola.org)) much more, so I was
15determined to figure out a way to get the proper redirect set up so that
16people who find my posts online aren't constantly met by 404 errors.
17
18## The Solution
19
20To solve this problem, I really needed to solve two pieces:
21
221. Redirect all blog post URL requests from
23 `/blog/some-post.html` to `/blog/some-post/`.
242. Ensure that no other `.html` files are redirected, such
25 as `index.html`.
26
27After *a lot* of tweaking and testing, I believe I have finally found
28the solution. The solution is shown below.
29
30```conf
31RewriteEngine On
32RewriteCond %{REQUEST_URI} !\index.html$ [NC]
33RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
34```
35
36This piece of code in the Apache `.conf` or
37`.htaccess` file will do the following:
38
391. Turn on the RewriteEngine so that we can modify URLs.
402. Ignore any `index.html` files from the rule we are about
41 to specify.
423. Find any `.html` files within the website directory and
43 redirect it to exclude the file extension.
444. The final piece is adding the trailing slash (`/`) at the
45 end of the URL - you'll notice that I don't have an Apache rule
46 for that since Apache handles that automatically.
blog/2021-12-04-cisa.org → content/blog/2021-12-04-cisa.md renamed +88 −98
@@ -1,19 +1,21 @@
1#+title: I Passed the CISA! 1+++
2#+date: 2021-12-04 2date = 2021-12-04
3title = "I Passed the CISA!"
4description = "A recap of the CISA certification exam and my results."
5+++
6
7## What is the CISA?
3 8
4** What is the CISA?
5:PROPERTIES:
6:CUSTOM_ID: what-is-the-cisa
7:END:
8For those of you lucky enough not to be knee-deep in the world of IT/IS 9For those of you lucky enough not to be knee-deep in the world of IT/IS
9Auditing, [[https://www.isaca.org/credentialing/cisa][CISA]] stands for 10Auditing, [CISA](https://www.isaca.org/credentialing/cisa) stands for
10Certified Information Systems Auditor. This certification and exam are 11Certified Information Systems Auditor. This certification and exam are
11part of ISACA's suite of certifications. As I often explain it to people 12part of ISACA's suite of certifications. As I often explain it to
12like my family, it basically means you're employed to use your knowledge 13people like my family, it basically means you're employed to use your
13of information systems, regulations, common threats, risks, etc. in 14knowledge of information systems, regulations, common threats, risks,
14order to assess an organization's current control of their risk. If a 15etc. in order to assess an organization's current control of their
15risk isn't controlled (and the company doesn't want to accept the risk), 16risk. If a risk isn't controlled (and the company doesn't want to
16an IS auditor will suggest implementing a control to address that risk. 17accept the risk), an IS auditor will suggest implementing a control to
18address that risk.
17 19
18Now, the CISA certification itself is, in my opinion, the main 20Now, the CISA certification itself is, in my opinion, the main
19certification for this career. While certifications such as the CPA or 21certification for this career. While certifications such as the CPA or
@@ -25,13 +27,13 @@ However, to be honest, I am a skeptic of most certifications. I
25understand the value they hold in terms of how much you need to commit 27understand the value they hold in terms of how much you need to commit
26to studying or learning on the job, as well as the market value for 28to studying or learning on the job, as well as the market value for
27certifications such as the CISA. But I also have known some very 29certifications such as the CISA. But I also have known some very
28+incompetent+ /less than stellar/ auditors who have CPAs, CISAs, CIAs, 30~~incompetent~~ *less than stellar* auditors who have CPAs, CISAs, CIAs,
29etc. 31etc.
30 32
31The same goes for most industries: if a person is good at studying, they 33The same goes for most industries: if a person is good at studying, they
32can earn the certification. However, that knowledge means nothing unless 34can earn the certification. However, that knowledge means nothing unless
33you're actually able to use it in real life and perform as expected of a 35you're actually able to use it in real life and perform as expected of
34certification holder. The challenge comes when people are hired or 36a certification holder. The challenge comes when people are hired or
35connected strictly because of their certifications or resume; you need 37connected strictly because of their certifications or resume; you need
36to see a person work before you can assume them having a CISA means 38to see a person work before you can assume them having a CISA means
37they're better than someone without the CISA. 39they're better than someone without the CISA.
@@ -40,56 +42,50 @@ Okay, rant over. Certifications are generally accepted as a measuring
40stick of commitment and quality of an employee, so I am accepting it 42stick of commitment and quality of an employee, so I am accepting it
41too. 43too.
42 44
43** Exam Content 45## Exam Content
44:PROPERTIES: 46
45:CUSTOM_ID: exam-content
46:END:
47The CISA is broken down into five sections, each weighted with a 47The CISA is broken down into five sections, each weighted with a
48percentage of test questions that may appear. 48percentage of test questions that may appear.
49 49
50#+caption: CISA exam sections 50![CISA exam
51[[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png]] 51sections](https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png)
52 52
53Since the exam contains 150 questions, here's how those sections break 53Since the exam contains 150 questions, here's how those sections break
54down: 54down:
55 55
56| Exam Section | Percentage of Exam | Questions | 56 Exam Section Percentage of Exam Questions
57|---------------+--------------------+-----------| 57 ----------------- -------------------- -----------
58| 1 | 21% | 32 | 58 1 21% 32
59| 2 | 17% | 26 | 59 2 17% 26
60| 3 | 12% | 18 | 60 3 12% 18
61| 4 | 23% | 34 | 61 4 23% 34
62| 5 | 27% | 40 | 62 5 27% 40
63| *Grand Total* | *100%* | *150* | 63 **Grand Total** **100%** **150**
64 64
65** My Studying Habits 65## My Studying Habits
66:PROPERTIES: 66
67:CUSTOM_ID: my-studying-habits
68:END:
69This part is a little hard for me to break down into specific detail due 67This part is a little hard for me to break down into specific detail due
70to the craziness of the last year. While I officially purchased my 68to the craziness of the last year. While I officially purchased my
71studying materials in December 2020 and opened them to "start studying" 69studying materials in December 2020 and opened them to "start
72in January 2021, I really wasn't able to study much due to the demands 70studying" in January 2021, I really wasn't able to study much due to
73of my job and personal life. 71the demands of my job and personal life.
74 72
75Let me approach this from a few different viewpoints. 73Let me approach this from a few different viewpoints.
76 74
77*** Study Materials 75### Study Materials
78:PROPERTIES: 76
79:CUSTOM_ID: study-materials
80:END:
81Let's start by discussing the study materials I purchased. I'll be 77Let's start by discussing the study materials I purchased. I'll be
82referring to #1 as the CRM and #2 as the QAE. 78referring to #1 as the CRM and #2 as the QAE.
83 79
841. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK][CISA 801. [CISA Review Manual, 27th Edition \|
85 Review Manual, 27th Edition | Print]] 81 Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK)
862. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK][CISA 822. [[<https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK>][CISA
87 Review Questions, Answers & Explanations Manual, 12th Edition | 83 Review Questions, Answers & Explanations Manual, 12th Edition \|
88 Print]] 84 Print]]
89 85
90The CRM is an excellent source of information and could honestly be used 86The CRM is an excellent source of information and could honestly be used
91as a reference for most IS auditors as a learning reference during their 87as a reference for most IS auditors as a learning reference during their
92daily audit responsibilities. However, it is *full* of information and 88daily audit responsibilities. However, it is **full** of information and
93can be overloading if you're not good at filtering out useless 89can be overloading if you're not good at filtering out useless
94information while studying. 90information while studying.
95 91
@@ -99,10 +95,10 @@ complaint about the QAE is that each question is immediately followed
99with the correct answer and explanations below it, which means I had to 95with the correct answer and explanations below it, which means I had to
100use something to constantly cover the answers while I was studying. 96use something to constantly cover the answers while I was studying.
101 97
102I didn't use the online database version of the QAE, but I've heard that 98I didn't use the online database version of the QAE, but I've heard
103it's easier to use than the printed book. However, it is more expensive 99that it's easier to use than the printed book. However, it is more
104($299 database vs $129 book) which might be important if you're paying 100expensive (\$299 database vs \$129 book) which might be important if
105for materials yourself. 101you're paying for materials yourself.
106 102
107In terms of question difficulty, I felt that the QAE was a good 103In terms of question difficulty, I felt that the QAE was a good
108representation of the actual exam. I've seen a lot of people online say 104representation of the actual exam. I've seen a lot of people online say
@@ -115,10 +111,8 @@ topics you struggle with), and use the QAE to continue practicing
115exam-like questions, you should be fine. I didn't use any online 111exam-like questions, you should be fine. I didn't use any online
116courses, videos, etc. - the ISACA materials are more than enough. 112courses, videos, etc. - the ISACA materials are more than enough.
117 113
118*** Studying Process 114### Studying Process
119:PROPERTIES: 115
120:CUSTOM_ID: studying-process
121:END:
122While I was able to briefly read through sections 1 and 2 in early 2021, 116While I was able to briefly read through sections 1 and 2 in early 2021,
123I had to stop and take a break from February/March to September. I 117I had to stop and take a break from February/March to September. I
124switched jobs in September, which allowed me a lot more free time to 118switched jobs in September, which allowed me a lot more free time to
@@ -130,62 +124,60 @@ practice exam from the QAE manual and scored 70% (105/150).
130 124
131Here's a breakdown of my initial practice exam: 125Here's a breakdown of my initial practice exam:
132 126
133| Exam Section | Incorrect | Correct | Grand Total | Percent | 127 Exam Section Incorrect Correct Grand Total Percent
134|---------------+-----------+---------+-------------+---------| 128 ----------------- ----------- --------- ------------- ---------
135| 1 | 8 | 25 | 33 | 76% | 129 1 8 25 33 76%
136| 2 | 5 | 20 | 25 | 80% | 130 2 5 20 25 80%
137| 3 | 6 | 12 | 18 | 67% | 131 3 6 12 18 67%
138| 4 | 10 | 23 | 33 | 70% | 132 4 10 23 33 70%
139| 5 | 16 | 25 | 41 | 61% | 133 5 16 25 41 61%
140| *Grand Total* | *45* | *105* | *150* | *70%* | 134 **Grand Total** **45** **105** **150** **70%**
141 135
142As I expected, my toughest sections were related to project management, 136As I expected, my toughest sections were related to project management,
143development, implementation, and security. 137development, implementation, and security.
144 138
145This just leaves October and November. For these months, I tried to 139This just leaves October and November. For these months, I tried to
146practice every few days, doing 10 questions for each section, until the 140practice every few days, doing 10 questions for each section, until the
147exam. This came out to 13 practice sessions, ~140 questions per section, 141exam. This came out to 13 practice sessions, \~140 questions per
148and ~700 questions total. 142section, and \~700 questions total.
149 143
150While some practice sessions were worse and some were better, the final 144While some practice sessions were worse and some were better, the final
151results were similar to my practice exam results. As you can see below, 145results were similar to my practice exam results. As you can see below,
152my averages were slightly worse than my practice exam. However, I got in 146my averages were slightly worse than my practice exam. However, I got in
153over 700 questions of practice and, most importantly, *I read through 147over 700 questions of practice and, most importantly, \*I read through
154the explanations every time I answered incorrectly and learned from my 148the explanations every time I answered incorrectly and learned from my
155mistakes*. 149mistakes\*.
156 150
157| Exam Section | Incorrect | Correct | Grand Total | Percent | 151 Exam Section Incorrect Correct Grand Total Percent
158|---------------+-----------+---------+-------------+---------| 152 ----------------- ----------- --------- ------------- ---------
159| 1 | 33 | 108 | 141 | 77% | 153 1 33 108 141 77%
160| 2 | 33 | 109 | 142 | 77% | 154 2 33 109 142 77%
161| 3 | 55 | 89 | 144 | 62% | 155 3 55 89 144 62%
162| 4 | 52 | 88 | 140 | 63% | 156 4 52 88 140 63%
163| 5 | 55 | 85 | 140 | 61% | 157 5 55 85 140 61%
164| *Grand Total* | *228* | *479* | *707* | *68%* | 158 **Grand Total** **228** **479** **707** **68%**
165 159
166#+caption: CISA practice question results 160![CISA practice question
167[[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png]] 161results](https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png)
168 162
169** Results 163## Results
170:PROPERTIES: 164
171:CUSTOM_ID: results
172:END:
173Now, how do the practice scores reflect my actual results? After all, 165Now, how do the practice scores reflect my actual results? After all,
174it's hard to tell how good a practice regimen is unless you see how it 166it's hard to tell how good a practice regimen is unless you see how it
175turns out. 167turns out.
176 168
177| Exam Section | Section Name | Score | 169 Exam Section Section Name Score
178|--------------+------------------------------------------------------------------+-------| 170 -------------- ------------------------------------------------------------------ ---------
179| 1 | Information Systems Auditing Process | 678 | 171 1 Information Systems Auditing Process 678
180| 2 | Governance and Management of IT | 590 | 172 2 Governance and Management of IT 590
181| 3 | Information Systems Acquisition, Development, and Implementation | 721 | 173 3 Information Systems Acquisition, Development, and Implementation 721
182| 4 | Information Systems Operations and Business Resilience | 643 | 174 4 Information Systems Operations and Business Resilience 643
183| 5 | Protection of Information Assets | 511 | 175 5 Protection of Information Assets 511
184| *TOTAL* | | *616* | 176 **TOTAL** **616**
185 177
186Now, in order to pass the CISA, you need at least 450 on a sliding scale 178Now, in order to pass the CISA, you need at least 450 on a sliding scale
187of 200-800. Personally, I really have no clue what an average CISA score 179of 200-800. Personally, I really have no clue what an average CISA score
188is. After a /very/ brief look online, I can see that the high end is 180is. After a *very* brief look online, I can see that the high end is
189usually in the low 700s. In addition, only about 50-60% of people pass 181usually in the low 700s. In addition, only about 50-60% of people pass
190the exam. 182the exam.
191 183
@@ -204,10 +196,8 @@ CISA topics at all, the most important part of studying is doing
204practice questions. You really need to understand how to read the 196practice questions. You really need to understand how to read the
205questions critically and pick the best answer. 197questions critically and pick the best answer.
206 198
207** Looking Forward 199## Looking Forward
208:PROPERTIES: 200
209:CUSTOM_ID: looking-forward
210:END:
211I am extremely happy that I was finally able to pass the CISA. Looking 201I am extremely happy that I was finally able to pass the CISA. Looking
212to the future, I'm not sure what's next in terms of professional 202to the future, I'm not sure what's next in terms of professional
213learning. My current company offers internal learning courses, so I will 203learning. My current company offers internal learning courses, so I will
blog/2022-02-10-leaving-the-office.org → content/blog/2022-02-10-leaving-the-office.md renamed +56 −88
@@ -1,10 +1,11 @@
1#+title: Leaving Office-Based Work In the Past 1+++
2#+date: 2022-02-10 2date = 2022-02-10
3title = "Leaving Office-Based Work in the Past"
4description = "My thoughts on the current surge of remote work and what that means for full-time office-based roles."
5+++
6
7## The Working World is Changing
3 8
4** The Working World is Changing
5:PROPERTIES:
6:CUSTOM_ID: the-working-world-is-changing
7:END:
8There has been a trend for the past few years of companies slowly 9There has been a trend for the past few years of companies slowly
9realizing that the pandemic is not just a temporary state that will go 10realizing that the pandemic is not just a temporary state that will go
10away eventually and let everything return to the way it was before. In 11away eventually and let everything return to the way it was before. In
@@ -22,14 +23,10 @@ I decided to take a look back at my relatively short career so far and
22compare the positive and negative effects of the different work 23compare the positive and negative effects of the different work
23environments I've been in. 24environments I've been in.
24 25
25** In-Person Offices 26## In-Person Offices
26:PROPERTIES: 27
27:CUSTOM_ID: in-person-offices 28### Retail Internship
28:END: 29
29*** Retail Internship
30:PROPERTIES:
31:CUSTOM_ID: retail-internship
32:END:
33I started my first job as a management intern at a busy retail pharmacy, 30I started my first job as a management intern at a busy retail pharmacy,
34working my 40-hour weeks on my feet. As these retail stores don't 31working my 40-hour weeks on my feet. As these retail stores don't
35believe in resting or sitting down, you can guarantee that you will 32believe in resting or sitting down, you can guarantee that you will
@@ -45,12 +42,10 @@ However, if you are able to operate a retail store with a limited crew
45and provide enough comfort and support, I believe these jobs could be 42and provide enough comfort and support, I believe these jobs could be
46both comfortable and efficient. 43both comfortable and efficient.
47 44
48*** Semi-Private Cubicles 45### Semi-Private Cubicles
49:PROPERTIES: 46
50:CUSTOM_ID: semi-private-cubicles 47![Semi-Private
51:END: 48Cubicles](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png)
52#+caption: Semi-Private Cubicles
53[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png]]
54 49
55After about a year, I was able to find another internship - this time, 50After about a year, I was able to find another internship - this time,
56it was in my field of interest: internal auditing. This was for a life 51it was in my field of interest: internal auditing. This was for a life
@@ -65,8 +60,8 @@ formed into cubicles that house 4 desks/employees. These "pods" of
654-person cubicles are linked throughout each floor of the headquarters 604-person cubicles are linked throughout each floor of the headquarters
66(except the sales people, who had that open-floor concept going on). The 61(except the sales people, who had that open-floor concept going on). The
67walls of the cubicle were tall and provided a lot of privacy and 62walls of the cubicle were tall and provided a lot of privacy and
68sound-proofing, except when I used the standing desk feature (I'm over 6 63sound-proofing, except when I used the standing desk feature (I'm over
69feet tall, so probably not an issue for most people). 646 feet tall, so probably not an issue for most people).
70 65
71I loved this environment, it allowed me to focus on my work with minimal 66I loved this environment, it allowed me to focus on my work with minimal
72distractions, but also allowed easy access, so I could spin around in my 67distractions, but also allowed easy access, so I could spin around in my
@@ -74,12 +69,10 @@ chair and chat with my friends without leaving my chair. This is the
74closest I've been to a home office environment (which is my personal 69closest I've been to a home office environment (which is my personal
75favorite, as I'll get to later in this post). 70favorite, as I'll get to later in this post).
76 71
77*** Semi-Open Floor Concept 72### Semi-Open Floor Concept
78:PROPERTIES: 73
79:CUSTOM_ID: semi-open-floor-concept 74![Semi-Open Floor
80:END: 75Concept](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png)
81#+caption: Semi-Open Floor Concept
82[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png]]
83 76
84When I shifted to my first full-time internal audit job out of college, 77When I shifted to my first full-time internal audit job out of college,
85I was working at a company that was headquartered on a floor in a 78I was working at a company that was headquartered on a floor in a
@@ -87,8 +80,8 @@ downtown high-rise building. The company was only about 20 years old
87when I worked there and were trying a lot of new things to attract young 80when I worked there and were trying a lot of new things to attract young
88talent, one of which was a semi-open floor concept for the office. My 81talent, one of which was a semi-open floor concept for the office. My
89department worked just around the hallway corner from the executive 82department worked just around the hallway corner from the executive
90offices and used that "modern" layout young tech companies started using 83offices and used that "modern" layout young tech companies started
91in the 2000s/2010s. 84using in the 2000s/2010s.
92 85
93Each desk was brief, and you could look most coworkers in the face 86Each desk was brief, and you could look most coworkers in the face
94without moving from your chair, I hated this so much. Directly to my 87without moving from your chair, I hated this so much. Directly to my
@@ -104,18 +97,17 @@ conversation would be constant throughout the day while you try to work.
104For someone like me, who needs silence to get work done, that was a 97For someone like me, who needs silence to get work done, that was a
105non-starter. 98non-starter.
106 99
107*** Hotel Office Concept 100### Hotel Office Concept
108:PROPERTIES: 101
109:CUSTOM_ID: hotel-office-concept 102![Hotel Office
110:END: 103Concept](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png)
111#+caption: Hotel Office Concept
112[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png]]
113 104
114I currently work for a company remotely (for now) and travel to the 105I currently work for a company remotely (for now) and travel to the
115office every once in a while for events and to help coach the staff 106office every once in a while for events and to help coach the staff
116underneath me. The office I visit uses the hotel desk concept, where you 107underneath me. The office I visit uses the hotel desk concept, where you
117need to check in at a touch screen when you enter the office and "rent" 108need to check in at a touch screen when you enter the office and
118a desk for the day. The same goes for offices and meeting rooms. 109"rent" a desk for the day. The same goes for offices and meeting
110rooms.
119 111
120These desks are flat-top only and do not have any walls at all. In 112These desks are flat-top only and do not have any walls at all. In
121addition, they're stacked with one row of 4 desks facing another row of 113addition, they're stacked with one row of 4 desks facing another row of
@@ -133,12 +125,10 @@ Luckily, you can rent offices with doors that offer quiet and privacy,
133which can be very nice if you have a lot of meetings or webinars on a 125which can be very nice if you have a lot of meetings or webinars on a
134certain day. 126certain day.
135 127
136** Home Office 128## Home Office
137:PROPERTIES: 129
138:CUSTOM_ID: home-office 130![Home
139:END: 131Office](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png)
140#+caption: Home Office
141[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png]]
142 132
143Okay, now let's finally get to the home office concept. I have worked 133Okay, now let's finally get to the home office concept. I have worked
144from home for a little over two years at this point, across three 134from home for a little over two years at this point, across three
@@ -150,16 +140,11 @@ These things might not apply to you, and that's fine. Everyone has a
150different situation, and I really don't think you'll know what works 140different situation, and I really don't think you'll know what works
151until you try. 141until you try.
152 142
153*** Tip #1 143### Tip #1
154:PROPERTIES:
155:CUSTOM_ID: tip-1
156:END:
157Let's start with my top rule for a home office:
158 144
159#+begin_quote 145Let's start with my top rule for a home office:
160If you live with others, working in a shared space is not effective.
161 146
162#+end_quote 147> If you live with others, working in a shared space is not effective.
163 148
164It just does not work. If you have another person sleeping in your 149It just does not work. If you have another person sleeping in your
165bedroom, it is difficult to manage your work schedule with their 150bedroom, it is difficult to manage your work schedule with their
@@ -172,17 +157,12 @@ likely won't work either. Distractions will come far more frequently:
172televisions, cooking, cleaning, deliveries, etc. If you're like me, 157televisions, cooking, cleaning, deliveries, etc. If you're like me,
173you'll end up playing a game instead of actually doing any work. 158you'll end up playing a game instead of actually doing any work.
174 159
175*** Tip #2 160### Tip #2
176:PROPERTIES:
177:CUSTOM_ID: tip-2
178:END:
179Okay, the second thing I've discovered that works for me:
180 161
181#+begin_quote 162Okay, the second thing I've discovered that works for me:
182Use the pomodoro method (or something similar) to balance work tasks
183with personal tasks.
184 163
185#+end_quote 164> Use the pomodoro method (or something similar) to balance work tasks
165> with personal tasks.
186 166
187I use a very casual version of the pomodoro method where I will work for 167I use a very casual version of the pomodoro method where I will work for
1881-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and 1681-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and
@@ -196,18 +176,13 @@ clothes in the washer, get the mail, etc. If you're in a convenient
196location, this usually gives time for things like getting groceries (as 176location, this usually gives time for things like getting groceries (as
197long as you're not a slow shopper). 177long as you're not a slow shopper).
198 178
199*** Tip #3 179### Tip #3
200:PROPERTIES: 180
201:CUSTOM_ID: tip-3
202:END:
203While I listed this one as number three, I don't think I'd accomplish 181While I listed this one as number three, I don't think I'd accomplish
204anything without it: 182anything without it:
205 183
206#+begin_quote 184> Document everything: even things you didn't before - such as task
207Document everything: even things you didn't before - such as task lists 185> lists and notes from casual calls or meetings.
208and notes from casual calls or meetings.
209
210#+end_quote
211 186
212I've noticed that staying in an office gave me more constant reminders 187I've noticed that staying in an office gave me more constant reminders
213of outstanding tasks or facts I had learned in a conversation. 188of outstanding tasks or facts I had learned in a conversation.
@@ -221,16 +196,11 @@ has improved my retention immensely. Beyond helping my mental
221recollection, it has saved me numerous times when I need to do a keyword 196recollection, it has saved me numerous times when I need to do a keyword
222search for some topic that was discussed 6+ months ago. 197search for some topic that was discussed 6+ months ago.
223 198
224*** Tip #4 199### Tip #4
225:PROPERTIES:
226:CUSTOM_ID: tip-4
227:END:
228Okay, last one for now.
229 200
230#+begin_quote 201Okay, last one for now.
231Keep your work area clean.
232 202
233#+end_quote 203> Keep your work area clean.
234 204
235This one is straightforward, but I know some people struggle with 205This one is straightforward, but I know some people struggle with
236cleanliness or may not believe it makes a difference. Trust me, keeping 206cleanliness or may not believe it makes a difference. Trust me, keeping
@@ -241,8 +211,8 @@ Just think about it, you walk into your home office and see a clean desk
241with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen 211with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen
242on top. 212on top.
243 213
244Now imagine the opposite, there's an office with the same equipment, but 214Now imagine the opposite, there's an office with the same equipment,
245there are clothes hanging on the chair, empty drink bottles, candy 215but there are clothes hanging on the chair, empty drink bottles, candy
246wrappers and dirty plates. This can take both a mental and emotional 216wrappers and dirty plates. This can take both a mental and emotional
247toll by bringing constant disarray and stress into your working 217toll by bringing constant disarray and stress into your working
248environment. 218environment.
@@ -250,14 +220,12 @@ environment.
250Just keep things clean each day, and you won't need to do any big 220Just keep things clean each day, and you won't need to do any big
251cleaning days to recover. 221cleaning days to recover.
252 222
253** My Preferences 223## My Preferences
254:PROPERTIES: 224
255:CUSTOM_ID: my-preferences
256:END:
257I've talked about the different environments I've worked in and 225I've talked about the different environments I've worked in and
258expressed some honest thoughts on pros or cons to each, but what do I 226expressed some honest thoughts on pros or cons to each, but what do I
259prefer? Well, if you're reading along, you should be able to tell that I 227prefer? Well, if you're reading along, you should be able to tell that
260much prefer a home office above all else. 228I much prefer a home office above all else.
261 229
262Being able to control my own day and allot my time as needed has brought 230Being able to control my own day and allot my time as needed has brought
263a calmness to my life and has allowed me to maximize each day. I feel 231a calmness to my life and has allowed me to maximize each day. I feel
@@ -270,5 +238,5 @@ my work done.
270 238
271Cubicles are good! I agree with Alice (from the comic Dilbert): 239Cubicles are good! I agree with Alice (from the comic Dilbert):
272 240
273#+caption: Dilbert comic strip 241![Dilbert comic
274[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png]] 242strip](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png)
blog/2022-02-16-debian-and-nginx.org → content/blog/2022-02-16-debian-and-nginx.md renamed +73 −75
@@ -1,12 +1,13 @@
1#+title: Migrating to a New Web Server Setup with Debian, Nginx, and Agate 1+++
2#+date: 2022-02-16 2date = 2022-02-16
3title = "Migrating to a New Web Server Setup with Debian, Nginx, and Agate"
4description = "A retrospective on my recent server migration."
5+++
3 6
4** Server OS: Debian 7## Server OS: Debian
5:PROPERTIES: 8
6:CUSTOM_ID: server-os-debian 9![Debian +
7:END: 10neofetch](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png)
8#+caption: Debian + neofetch
9[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png]]
10 11
11I've used various Linux distributions throughout the years, but I've 12I've used various Linux distributions throughout the years, but I've
12never used anything except Ubuntu for my servers. Why? I really have no 13never used anything except Ubuntu for my servers. Why? I really have no
@@ -15,64 +16,62 @@ idea, mostly just comfort around the commands and software availability.
15However, I have always wanted to try Debian as a server OS after testing 16However, I have always wanted to try Debian as a server OS after testing
16it out in a VM a few years ago (side-note: I'd love to try Alpine too, 17it out in a VM a few years ago (side-note: I'd love to try Alpine too,
17but I always struggle with compatibility). So, I decided to launch a new 18but I always struggle with compatibility). So, I decided to launch a new
18VPS and use [[https://www.debian.org][Debian]] 11 as the OS. Spoiler 19VPS and use [Debian](https://www.debian.org) 11 as the OS. Spoiler
19alert: it feels identical to Ubuntu for my purposes. 20alert: it feels identical to Ubuntu for my purposes.
20 21
21I did the normal things when first launching the VPS, such as adding a 22I did the normal things when first launching the VPS, such as adding a
22new user, locking down SSH, etc. If you want to see that level of 23new user, locking down SSH, etc. If you want to see that level of
23detail, read my other post about 24detail, read my other post about [How to Set Up a VPS Web
24[[https://cleberg.net/blog/how-to-set-up-a-vps-web-server/][How to Set 25Server](https://cleberg.net/blog/how-to-set-up-a-vps-web-server/).
25Up a VPS Web Server]].
26 26
27All of this has been similar, apart from small things such as the 27All of this has been similar, apart from small things such as the
28location of users' home folders. No complaints at all from me - Debian 28location of users' home folders. No complaints at all from me - Debian
29seems great. 29seems great.
30 30
31** Web Server: Nginx 31## Web Server: Nginx
32:PROPERTIES: 32
33:CUSTOM_ID: web-server-nginx 33![Nginx
34:END: 34status](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png)
35#+caption: Nginx status
36[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png]]
37 35
38Once I had the baseline server configuration set-up for Debian, I moved 36Once I had the baseline server configuration set-up for Debian, I moved
39on to trying out [[https://nginx.org][Nginx]] as my web server software. 37on to trying out [Nginx](https://nginx.org) as my web server software.
40This required me to install the =nginx= and =ufw= packages, as well as 38This required me to install the `nginx` and `ufw`
41setting up the initial UFW config: 39packages, as well as setting up the initial UFW config:
42 40
43#+begin_src sh 41```sh
44sudo apt install nginx ufw 42sudo apt install nginx ufw
45sudo ufw allow 'Nginx Full' 43sudo ufw allow 'Nginx Full'
46sudo ufw allow SSH 44sudo ufw allow SSH
47sudo ufw enable 45sudo ufw enable
48sudo ufw status 46sudo ufw status
49sudo systemctl status nginx 47sudo systemctl status nginx
50#+end_src 48```
51 49
52Once I had the firewall set, I moved on to creating the directories and 50Once I had the firewall set, I moved on to creating the directories and
53files for my website. This is very easy and is basically the same as 51files for my website. This is very easy and is basically the same as
54setting up an Apache server, so no struggles here. 52setting up an Apache server, so no struggles here.
55 53
56#+begin_src sh 54```sh
57sudo mkdir -p /var/www/your_domain/html 55sudo mkdir -p /var/www/your_domain/html
58sudo chown -R $USER:$USER /var/www/your_domain/html 56sudo chown -R $USER:$USER /var/www/your_domain/html
59sudo chmod -R 755 /var/www/your_domain 57sudo chmod -R 755 /var/www/your_domain
60nano /var/www/your_domain/html/index.html 58nano /var/www/your_domain/html/index.html
61#+end_src 59```
62 60
63The next part, creating the Nginx configuration files, is quite a bit 61The next part, creating the Nginx configuration files, is quite a bit
64different from Apache. First, you need to create the files in the 62different from Apache. First, you need to create the files in the
65=sites-available= folder and symlink it the =sites-enabled= folder. 63`sites-available` folder and symlink it the
64`sites-enabled` folder.
66 65
67Creating the config file for your domain: 66Creating the config file for your domain:
68 67
69#+begin_src sh 68```sh
70sudo nano /etc/nginx/sites-available/your_domain 69sudo nano /etc/nginx/sites-available/your_domain
71#+end_src 70```
72 71
73Default content for an Nginx config file: 72Default content for an Nginx config file:
74 73
75#+begin_src sh 74```sh
76server { 75server {
77 listen 80; 76 listen 80;
78 listen [::]:80; 77 listen [::]:80;
@@ -86,43 +85,45 @@ server {
86 try_files $uri $uri/ =404; 85 try_files $uri $uri/ =404;
87 } 86 }
88} 87}
89#+end_src 88```
90 89
91Finally, symlink it together: 90Finally, symlink it together:
92 91
93#+begin_src sh 92```sh
94sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/ 93sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
95#+end_src 94```
96 95
97This will make your site available to the public (as long as you have 96This will make your site available to the public (as long as you have
98=your_domain= DNS records pointed at the server's IP address)! 97`your_domain` DNS records pointed at the server's IP
98address)!
99 99
100Next, I used [[https://certbot.eff.org/][certbot]] to issue an HTTPS 100Next, I used [certbot](https://certbot.eff.org/) to issue an HTTPS
101certificate for my domains using the following commands: 101certificate for my domains using the following commands:
102 102
103#+begin_src sh 103```sh
104sudo apt install snapd; sudo snap install core; sudo snap refresh core 104sudo apt install snapd; sudo snap install core; sudo snap refresh core
105sudo snap install --classic certbot 105sudo snap install --classic certbot
106sudo ln -s /snap/bin/certbot /usr/bin/certbot 106sudo ln -s /snap/bin/certbot /usr/bin/certbot
107sudo certbot --nginx 107sudo certbot --nginx
108#+end_src 108```
109 109
110Now that certbot ran successfully and updated my Nginx config files to 110Now that certbot ran successfully and updated my Nginx config files to
111include a =443= server block of code, I went back in and edited the 111include a `443` server block of code, I went back in and
112config file to include security HTTP headers. This part is optional, but 112edited the config file to include security HTTP headers. This part is
113is recommended for security purposes; you can even test a website's HTTP 113optional, but is recommended for security purposes; you can even test a
114header security at [[https://securityheaders.com/][Security Headers]]. 114website's HTTP header security at [Security
115Headers](https://securityheaders.com/).
115 116
116The configuration below shows a set-up where you only want your website 117The configuration below shows a set-up where you only want your website
117to serve content from its own domain, except for images and scripts, 118to serve content from its own domain, except for images and scripts,
118which may come from =nullitics.com=. All other content would be blocked 119which may come from `nullitics.com`. All other content would
119from loading in a browser. 120be blocked from loading in a browser.
120 121
121#+begin_src sh 122```sh
122sudo nano /etc/nginx/sites-available/your_domain 123sudo nano /etc/nginx/sites-available/your_domain
123#+end_src 124```
124 125
125#+begin_src sh 126```sh
126server { 127server {
127 ... 128 ...
128 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 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'";
@@ -133,50 +134,47 @@ server {
133 add_header Referrer-Policy "no-referrer"; 134 add_header Referrer-Policy "no-referrer";
134 ... 135 ...
135} 136}
136#+end_src 137```
137 138
138#+begin_src sh 139```sh
139sudo systemctl restart nginx 140sudo systemctl restart nginx
140#+end_src 141```
142
143### Nginx vs. Apache
141 144
142*** Nginx vs. Apache
143:PROPERTIES:
144:CUSTOM_ID: nginx-vs.-apache
145:END:
146As I stated at the beginning, my historical hesitation with trying Nginx 145As I stated at the beginning, my historical hesitation with trying Nginx
147was that the differences in configuration formats scared me away from 146was that the differences in configuration formats scared me away from
148leaving Apache. However, I prefer Nginx to Apache for a few reasons: 147leaving Apache. However, I prefer Nginx to Apache for a few reasons:
149 148
1501. Nginx uses only one config file (=your_domain=) vs. Apache's two-file 1491. Nginx uses only one config file (`your_domain`)
151 approach for HTTP vs. HTTPS (=your_domain.conf= and 150 vs. Apache's two-file approach for HTTP vs. HTTPS
152 =your_domain-le-ssl.conf=). 151 (`your_domain.conf` and
1532. Symlinking new configurations files and reloading Nginx are way 152 `your_domain-le-ssl.conf`).
154 easier than Apache's process of having to enable headers with 1532. Symlinking new configurations files and reloading Nginx are way
155 =a2enmod mod_headers=, enable PHP with =a2enmod php= (plus any other 154 easier than Apache's process of having to enable headers with
156 mods you need), and then enabling sites with =a2ensite=, and THEN 155 `a2enmod mod_headers`, enable PHP with
157 reloading Apache. 156 `a2enmod php` (plus any other mods you need), and then
1583. The contents of the Nginx config files seem more organized and 157 enabling sites with `a2ensite`, and THEN reloading
159 logical with the curly-bracket approach. This is a minor reason, but 158 Apache.
160 everything just felt cleaner while I was installing my sites and that 1593. The contents of the Nginx config files seem more organized and
161 had a big quality of life impact on the installation for me. 160 logical with the curly-bracket approach. This is a minor reason, but
161 everything just felt cleaner while I was installing my sites and
162 that had a big quality of life impact on the installation for me.
162 163
163They're both great software packages, but Nginx just seems more 164They're both great software packages, but Nginx just seems more
164organized and easier to use these days. I will certainly be exploring 165organized and easier to use these days. I will certainly be exploring
165the Nginx docs to see what other fun things I can do with all of this. 166the Nginx docs to see what other fun things I can do with all of this.
166 167
167** Gemini Server: Agate 168## Gemini Server: Agate
168:PROPERTIES: 169
169:CUSTOM_ID: gemini-server-agate 170![Agate
170:END: 171status](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png)
171#+caption: Agate status
172[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png]]
173 172
174Finally, I set up the Agate software on this server again to host my 173Finally, I set up the Agate software on this server again to host my
175Gemini server content, using Rust as I have before. You can read my 174Gemini server content, using Rust as I have before. You can read my
176other post for more information on installing Agate: 175other post for more information on installing Agate: [Hosting a Gemini
177[[https://cleberg.net/blog/hosting-a-gemini-server/][Hosting a Gemini 176Server](https://cleberg.net/blog/hosting-a-gemini-server/).
178Server]].
179 177
180All in all, Debian + Nginx is very slick and I prefer it over my old 178All in all, Debian + Nginx is very slick and I prefer it over my old
181combination of Ubuntu + Apache (although it's really just Nginx > Apache 179combination of Ubuntu + Apache (although it's really just Nginx \>
182for me, since Debian seems mostly the same as Ubuntu is so far). 180Apache for me, since Debian seems mostly the same as Ubuntu is so far).
content/blog/2022-02-17-exiftool.md added +67
@@ -0,0 +1,67 @@
1+++
2date = 2022-02-17
3title = "Stripping Image Metadata with exiftool"
4description = "A simple guide to remove exif data with exiftool."
5+++
6
7## Why Strip Metadata?
8
9Okay, so you want to strip metadata from your photos. Perhaps you take
10pictures of very rare birds, and the location metadata is a gold mine
11for poachers, or perhaps you're just privacy-oriented like me and
12prefer to strip metadata from publicly-available images.
13
14There are various components of image metadata that you may want to
15delete before releasing a photo to the public. Here's an incomplete
16list of things I could easily see just by inspecting a photo on my
17laptop:
18
19- Location (Latitude & Longitude)
20- Dimensions
21- Device Make & Model
22- Color Space
23- Color Profile
24- Focal Length
25- Alpha Channel
26- Red Eye
27- Metering Mode
28- F Number
29
30Regardless of your reasoning, I'm going to explain how I used the
31`exiftool` package in Linux to automatically strip metadata
32from all images in a directory (+ subdirectories).
33
34## Installing `exiftool`
35
36First things first: we need to install the tool. I'm running Debian 11
37on my server (Ubuntu will work the same), so the command is as simple
38as:
39
40```sh
41sudo apt install exiftool
42```
43
44There are different tools that can accomplish the same thing across
45distributions, but I really only care to test out this one package.
46
47## Recursively Strip Data
48
49I actually use this tool extensively to strip any photos uploaded to the
50website that serves all the images for my blog
51(`img.cleberg.net`).
52
53The following command is incredibly useful and can be modified to
54include any image extensions that `exiftool` supports:
55
56```sh
57exiftool -r -all= -ext jpg -ext png /path/to/directory/
58```
59
60See below for the results of my most recent usage of
61`exiftool` after I uploaded the image for this blog post. You
62can see that the command will let you know how many directories were
63scanned, how many images were updated, and how many images were
64unchanged.
65
66![exiftool
67results](https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png)
content/blog/2022-02-20-nginx-caching.md added +72
@@ -0,0 +1,72 @@
1+++
2date = 2022-02-20
3title = "Caching Static Content with Nginx"
4description = "Learn how to enable the static content cache in Nginx."
5+++
6
7## Update Your Nginx Config to Cache Static Files
8
9If you run a website on Nginx that serves static content (i.e., content
10that is not dynamic and changing with interactions from the user), you
11would likely benefit from caching that content on the client-side. If
12you're used to Apache and looking for the Nginx equivalent, this post
13should help.
14
15Luckily, setting up the cache is as easy as identifying the file types
16you want to cache and determining the expiration length. To include more
17file types, simply use the bar separator (`|`) and type the
18new file extension you want to include.
19
20```config
21server {
22 ...
23
24 location ~* .(css|js|jpg|jpeg|gif|png|ico)$ {
25 expires 30d;
26 }
27
28 ...
29}
30```
31
32I have seen some people who prefer to set `expires` as
33`365d` or even `max`, but that is only for stable,
34infrequently changing websites. As my site often changes (i.e., I'm
35never content with my website), I need to know that my readers are
36seeing the new content without waiting too long.
37
38So, I went ahead and set the expiration date at `30d`, which
39is short enough to refresh for readers but long enough that
40clients/browsers won't be re-requesting the static files too often,
41hopefully resulting in faster loading times, as images should be the
42only thing slowing down my site.
43
44## Testing Results
45
46To test my changes to the Nginx configuration, I used the [HTTP Header
47Live](https://addons.mozilla.org/en-US/firefox/addon/http-header-live/)
48extension on my Gecko browser and used the sidebar to inspect the
49headers of a recent image from my blog.
50
51In the image below, you can see that the `Cache-Control`
52header is now present and set to 2592000, which is 30 days represented
53in seconds (30 days \_ 24 hours/day \_ 60 minutes/hour \* 60
54seconds/minute = 2,592,000 seconds).
55
56The `Expires` field is now showing 22 March 2022, which is 30
57days from the day of this post, 20 February 2022.
58
59![Image
60Headers](https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png)
61
62## Caveats
63
64Remember that this caching system is **client-side**, which means that
65content is only cached for as long as a client allows it. For example,
66my browser purges all caches, data, etc. upon exit, so this caching
67policy will only work as long as my browser remains open and running.
68
69If you need to test updates to your site, you'll need to clear the
70cache to see updates for any file extension you configured. This can
71often be done with the `Shift + F5` or `Ctrl + F5`
72key combinations in most browsers.
content/blog/2022-02-22-tuesday.md added +41
@@ -0,0 +1,41 @@
1+++
2date = 2022-02-22
3title = "Tuesday"
4description = "2-22-22 || 22-2-22"
5+++
6
7## Tuesday, Twosday
8
9I'm taking a break from my usual technology-related posts and writing
10about something a little more enjoyable today.
11
12Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
13Twosday.
14
15Let's look at everything that fell in place today:
16
171. Written in the `m-dd-yy` or `dd-m-yy` formats,
18 today is 2-22-22 or 22-2-22, which is a neat little palindrome in
19 either format. (The last ubiquitous six-digit palindrome was
20 1-11-11.)
212. Today is Tuesday, which is why everyone is using the nickname
22 Twosday to call out these similarities.
233. Falling on Tuesday means today is the 2nd day of the week (for most
24 cultures. For the US, it's the 3rd day of the week since we start
25 on Sunday).
264. The only culture I could find with a connection to a `2`
27 is that some Slavic languages derived their version of Tuesday from
28 the Old Church Slavonic word `въторъ`, meaning "the
29 second."
305. Written in the classic monospaced, digital font (think of digital
31 clocks from the 80s/90s), there is nice symmetry to the numbers
32 ([view the image
33 here](https://img.cleberg.net/blog/20220222-tuesday/digital_font.png)!).
346. This one isn't naturally-occurring, but it seems people around the
35 world are celebrating the day. For example, a group is putting
36 together
37 [[<https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597>][a
38 wedding of 222 couples at the California State Capitol in
39 Sacramento]], concluding at exactly 2:22 PM. These couples will
40 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
41 for \$222.22.
blog/2022-03-02-note-taking.org → content/blog/2022-03-02-note-taking.md renamed +49 −69
@@ -1,19 +1,16 @@
1#+title: Easy, Reliable Note-Taking 1+++
2#+date: 2022-03-02 2date = 2022-03-02
3title = "Easy, Reliable Note-Taking"
4description = "My personal preferences to ensure notes are clear, portable, and long-lasting."
5+++
3 6
4** Choosing Durable File Formats 7## Choosing Durable File Formats
5:PROPERTIES:
6:CUSTOM_ID: choosing-durable-file-formats
7:END:
8 8
9#+begin_quote 9> TL;DR: Write in a format that can be easily rendered and read in
10TL;DR: Write in a format that can be easily rendered and read in 10> plain-text mode (e.g., `.txt`, `.md`, etc.).
11plain-text mode (e.g., =.txt=, =.md=, etc.).
12 11
13#+end_quote 12As I've written more and more over the years, I've found that my love
14 13of note-taking is always growing. Everything I learn or need to remember
15As I've written more and more over the years, I've found that my love of
16note-taking is always growing. Everything I learn or need to remember
17can be written down in a note and saved digitally, with no cost to 14can be written down in a note and saved digitally, with no cost to
18myself. Unlike paper copies that need physical storage space, digital 15myself. Unlike paper copies that need physical storage space, digital
19files simply need space on your local disk or cloud storage, which is 16files simply need space on your local disk or cloud storage, which is
@@ -25,8 +22,8 @@ styling. This meant that I had to go through each note during migration
25and edit the file to look presentable again. 22and edit the file to look presentable again.
26 23
27For the last year or two, I have written everything exclusively in 24For the last year or two, I have written everything exclusively in
28[[https://en.wikipedia.org/wiki/Markdown][Markdown]] format. Small 25[Markdown](https://en.wikipedia.org/wiki/Markdown) format. Small notes,
29notes, long-form writings, and even these blog posts are all written in 26long-form writings, and even these blog posts are all written in
30Markdown. 27Markdown.
31 28
32Why Markdown? While I do appreciate the simplicity of plain-text files 29Why Markdown? While I do appreciate the simplicity of plain-text files
@@ -34,21 +31,19 @@ without any formatting, I often need visual cues such as heading and
34code blocks to keep my thoughts straight. Markdown provides a minimal 31code blocks to keep my thoughts straight. Markdown provides a minimal
35set of styling indicators for me to style my notes without adding any 32set of styling indicators for me to style my notes without adding any
36proprietary, embedded data into the files. If I want a top-level 33proprietary, embedded data into the files. If I want a top-level
37heading, I simply add a hash (=#=) before the line. An added bonus is 34heading, I simply add a hash (`#`) before the line. An added
38that even if a system doesn't understand Markdown, it will render it as 35bonus is that even if a system doesn't understand Markdown, it will
39plain-text and I can read it just as easily. 36render it as plain-text and I can read it just as easily.
40 37
41For example, here's how TextEdit on macOS will open and display a 38For example, here's how TextEdit on macOS will open and display a
42Markdown file in plain-text, since it does contain any features to 39Markdown file in plain-text, since it does contain any features to
43preview Markdown as HTML: 40preview Markdown as HTML:
44 41
45#+caption: Plain Text Markdown 42![Plain Text
46[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png]] 43Markdown](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png)
44
45## Saving & Syncing Files
47 46
48** Saving & Syncing Files
49:PROPERTIES:
50:CUSTOM_ID: saving-syncing-files
51:END:
52In order to read and edit my notes across platforms, I use my personal 47In order to read and edit my notes across platforms, I use my personal
53cloud storage through Tresorit due to its native integration with macOS 48cloud storage through Tresorit due to its native integration with macOS
54and iOS file managers. In addition, Tresorit works well on Debian-based 49and iOS file managers. In addition, Tresorit works well on Debian-based
@@ -64,23 +59,19 @@ me to sync anything manually or kick-off a sync job to update my files.
64This means that I can edit on mobile, and it takes about 5-10 seconds to 59This means that I can edit on mobile, and it takes about 5-10 seconds to
65see the changes on desktop. 60see the changes on desktop.
66 61
67*** Version Control with Git 62### Version Control with Git
68:PROPERTIES: 63
69:CUSTOM_ID: version-control-with-git
70:END:
71A different approach I've contemplated is storing my notes and 64A different approach I've contemplated is storing my notes and
72attachments is using a hosted Git repository to track changes to the 65attachments is using a hosted Git repository to track changes to the
73files. However, I don't want to rely on an external service that could 66files. However, I don't want to rely on an external service that could
74potentially see into my data, even if the repository is private. 67potentially see into my data, even if the repository is private.
75 68
76I might just do =git init= locally and then commit my changes each time 69I might just do `git init` locally and then commit my changes
77I write or update a note, but that seems to be a lot of work just for 70each time I write or update a note, but that seems to be a lot of work
78tracking changes - which I don't necessarily care to know. 71just for tracking changes - which I don't necessarily care to know.
72
73### Backups!
79 74
80*** Backups!
81:PROPERTIES:
82:CUSTOM_ID: backups
83:END:
84One small addition to the storage/sync conversation is the idea of 75One small addition to the storage/sync conversation is the idea of
85backups. Personally, I manually create periodic backups of my entire 76backups. Personally, I manually create periodic backups of my entire
86cloud storage, compress it into an archive, and store it on my home 77cloud storage, compress it into an archive, and store it on my home
@@ -90,18 +81,12 @@ To improve my workflow, I am going to be exploring options to
90automatically compress the mounted cloud directory and send it over to 81automatically compress the mounted cloud directory and send it over to
91my server on a set schedule. 82my server on a set schedule.
92 83
93** Writing on Desktop 84## Writing on Desktop
94:PROPERTIES:
95:CUSTOM_ID: writing-on-desktop
96:END:
97 85
98#+begin_quote 86> **Update (06.14.22)**: Since writing this post, I have reverted to
99*Update (06.14.22)*: Since writing this post, I have reverted to simply 87> simply keeping my `notes` folder open and opening notes
100keeping my =notes= folder open and opening notes individually in 88> individually in TextEdit for a more minimal and relaxing writing
101TextEdit for a more minimal and relaxing writing experience on the 89> experience on the desktop.
102desktop.
103
104#+end_quote
105 90
106The bulk of my writing occurs in a desktop environment, with a full 91The bulk of my writing occurs in a desktop environment, with a full
107keyboard layout and wide screen. I don't illustrate with a smart pen, I 92keyboard layout and wide screen. I don't illustrate with a smart pen, I
@@ -109,36 +94,31 @@ rarely use embedded images, and I love being able to see all of my
109notes/directories in a sidebar. 94notes/directories in a sidebar.
110 95
111With this simple set of requirements, I chose 96With this simple set of requirements, I chose
112[[https://obsidian.md][Obsidian]] as my desktop text editor. Obsidian 97[Obsidian](https://obsidian.md) as my desktop text editor. Obsidian has
113has some in-depth tools like a graph view, command palette, mentions, 98some in-depth tools like a graph view, command palette, mentions, etc.,
114etc., but I've found that using it as a simple Markdown editor is 99but I've found that using it as a simple Markdown editor is incredibly
115incredibly easy and straightforward. 100easy and straightforward.
116 101
117Here's an example of how my Markdown notes look when opened in 102Here's an example of how my Markdown notes look when opened in
118plain-text mode: 103plain-text mode:
119 104
120#+caption: Obsidian Markdown Source Mode 105![Obsidian Markdown Source
121[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png]] 106Mode](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png)
107
108Here's the "live preview" version, where the Markdown is rendered
109into its HTML format:
122 110
123Here's the "live preview" version, where the Markdown is rendered into 111![Obsidian Markdown Live
124its HTML format: 112Preview](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png)
125 113
126#+caption: Obsidian Markdown Live Preview 114### Programming on Desktop
127[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png]]
128 115
129*** Programming on Desktop
130:PROPERTIES:
131:CUSTOM_ID: programming-on-desktop
132:END:
133While 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
134the same editor for writing notes and for writing code. For programming 117the same editor for writing notes and for writing code. For programming
135purposes, I use [[https://vscodium.com][VSCodium]] as my development 118purposes, I use [VSCodium](https://vscodium.com) as my development IDE.
136IDE. 119
120## Writing on Mobile
137 121
138** Writing on Mobile
139:PROPERTIES:
140:CUSTOM_ID: writing-on-mobile
141:END:
142Personally, I write very little on mobile, except when needing to take 122Personally, I write very little on mobile, except when needing to take
143important notes on-the-go. Any long-form writing, journals, etc. are 123important notes on-the-go. Any long-form writing, journals, etc. are
144done at home, where I always have my laptop available. 124done at home, where I always have my laptop available.
@@ -146,9 +126,9 @@ done at home, where I always have my laptop available.
146I wanted a simple and foolproof editor for iOS, preferably open-source. 126I wanted a simple and foolproof editor for iOS, preferably open-source.
147After a long journey of testing the few (& terrible) open-source iOS 127After a long journey of testing the few (& terrible) open-source iOS
148note-taking apps, I finally found a phenomenal one: 128note-taking apps, I finally found a phenomenal one:
149[[https://github.com/simonbs/runestone][Runestone]]. This app is 129[Runestone](https://github.com/simonbs/runestone). This app is fantastic
150fantastic for note-taking, has plenty of optional features, and 130for note-taking, has plenty of optional features, and integrates
151integrates natively with the iOS file manager. 131natively with the iOS file manager.
152 132
153This app opens the iOS file manager and allows you to click any file you 133This app opens the iOS file manager and allows you to click any file you
154want, opens it up in an editor, and lets me save and close out of that 134want, opens it up in an editor, and lets me save and close out of that
content/blog/2022-03-03-financial-database.md added +271
@@ -0,0 +1,271 @@
1+++
2date = 2022-03-03
3title = "Maintaining a Personal Financial Database"
4description = "An example project showing to build and maintain a simple financial database."
5+++
6
7## Personal Financial Tracking
8
9For the last 6-ish years, I've tracked my finances in a spreadsheet.
10This is common practice in the business world, but any good dev will
11cringe at the thought of storing long-term data in a spreadsheet. A
12spreadsheet is not for long-term storage or as a source of data to pull
13data/reports.
14
15As I wanted to expand the functionality of my financial data (e.g.,
16adding more reports), I decided to migrate the data into a database. To
17run reports, I would query the database and use a language like Python
18or Javascript to process the data, perform calculations, and visualize
19the data.
20
21## SQLite
22
23When choosing the type of database I wanted to use for this project, I
24was split between three options:
25
261. MySQL: The database I have the most experience with and have used
27 for years.
282. PostgreSQL: A database I'm new to, but want to learn.
293. SQLite: A database that I've used for a couple projects and have
30 moderate experience.
31
32I ended up choosing SQLite since it can be maintained within a single
33`.sqlite` file, which allows me more flexibility for storage
34and backup. I keep this file in my cloud storage and pull it up whenever
35needed.
36
37### GUI Editing
38
39Since I didn't want to try and import 1000--1500 records into my new
40database via the command line, I opted to use [DB Browser for SQLite
41(DB4S)](https://sqlitebrowser.org/) as a GUI tool. This application is
42excellent, and I don't see myself going back to the CLI when working in
43this database.
44
45DB4S allows you to copy a range of cells from a spreadsheet and paste it
46straight into the SQL table. I used this process for all 36 accounts,
471290 account statements, and 126 pay statements. Overall, I'm guessing
48this took anywhere between 4--8 hours. In comparison, it probably took
49me 2-3 days to initially create the spreadsheet.
50
51![DB4S](https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png)
52
53### Schema
54
55The schema for this database is actually extremely simple and involves
56only three tables (for now):
57
581. Accounts
592. Statements
603. Payroll
61
62**Accounts**
63
64The Accounts table contains summary information about an account, such
65as a car loan or a credit card. By viewing this table, you can find
66high-level data, such as interest rate, credit line, or owner.
67
68```sql
69CREATE TABLE "Accounts" (
70 "AccountID" INTEGER NOT NULL UNIQUE,
71 "AccountType" TEXT,
72 "AccountName" TEXT,
73 "InterestRate" NUMERIC,
74 "CreditLine" NUMERIC,
75 "State" TEXT,
76 "Owner" TEXT,
77 "Co-Owner" TEXT,
78 PRIMARY KEY("AccountID" AUTOINCREMENT)
79)
80```
81
82**Statements**
83
84The Statements table uses the same unique identifier as the Accounts
85table, meaning you can join the tables to find a monthly statement for
86any of the accounts listed in the Accounts table. Each statement has an
87account ID, statement date, and total balance.
88
89```sql
90CREATE TABLE "Statements" (
91 "StatementID" INTEGER NOT NULL UNIQUE,
92 "AccountID" INTEGER,
93 "StatementDate" INTEGER,
94 "Balance" NUMERIC,
95 PRIMARY KEY("StatementID" AUTOINCREMENT),
96 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
97)
98```
99
100**Payroll**
101
102The Payroll table is a separate entity, unrelated to the Accounts or
103Statements tables. This table contains all information you would find on
104a pay statement from an employer. As you change employers or obtain new
105perks/benefits, just add new columns to adapt to the new data.
106
107```sql
108CREATE TABLE "Payroll" (
109 "PaycheckID" INTEGER NOT NULL UNIQUE,
110 "PayDate" TEXT,
111 "Payee" TEXT,
112 "Employer" TEXT,
113 "JobTitle" TEXT,
114 "IncomeRegular" NUMERIC,
115 "IncomePTO" NUMERIC,
116 "IncomeHoliday" NUMERIC,
117 "IncomeBonus" NUMERIC,
118 "IncomePTOPayout" NUMERIC,
119 "IncomeReimbursements" NUMERIC,
120 "FringeHSA" NUMERIC,
121 "FringeStudentLoan" NUMERIC,
122 "Fringe401k" NUMERIC,
123 "PreTaxMedical" NUMERIC,
124 "PreTaxDental" NUMERIC,
125 "PreTaxVision" NUMERIC,
126 "PreTaxLifeInsurance" NUMERIC,
127 "PreTax401k" NUMERIC,
128 "PreTaxParking" NUMERIC,
129 "PreTaxStudentLoan" NUMERIC,
130 "PreTaxOther" NUMERIC,
131 "TaxFederal" NUMERIC,
132 "TaxSocial" NUMERIC,
133 "TaxMedicare" NUMERIC,
134 "TaxState" NUMERIC,
135 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
136)
137```
138
139### Python Reporting
140
141Once I created the database tables and imported all my data, the only
142step left was to create a process to report and visualize on various
143aspects of the data.
144
145In order to explore and create the reports I'm interested in, I
146utilized a two-part process involving Jupyter Notebooks and Python
147scripts.
148
1491. Step 1: Jupyter Notebooks
150
151 When I need to explore data, try different things, and re-run my
152 code cell-by-cell, I use Jupyter Notebooks. For example, I explored
153 the `Accounts` table until I found the following useful
154 information:
155
156 ```python
157 import sqlite3
158 import pandas as pd
159 import matplotlib
160
161 # Set up database filename and connect
162 db = "finances.sqlite"
163 connection = sqlite3.connect(db)
164 df = pd.read_sql_query("SELECT * FROM Accounts", connection)
165
166 # Set global matplotlib variables
167 %matplotlib inline
168 matplotlib.rcParams['text.color'] = 'white'
169 matplotlib.rcParams['axes.labelcolor'] = 'white'
170 matplotlib.rcParams['xtick.color'] = 'white'
171 matplotlib.rcParams['ytick.color'] = 'white'
172 matplotlib.rcParams['legend.labelcolor'] = 'black'
173
174 # Display graph
175 df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
176 ```
177
1782. Step 2: Python Scripts
179
180 Once I explored enough through the notebooks and had a list of
181 reports I wanted, I moved on to create a Python project with the
182 following structure:
183
184 ```example
185 finance/
186 ├── notebooks/
187 │ │ ├── account_summary.ipynb
188 │ │ ├── account_details.ipynb
189 │ │ └── payroll.ipynb
190 ├── public/
191 │ │ ├── image-01.png
192 │ │ └── image-0X.png
193 ├── src/
194 │ └── finance.sqlite
195 ├── venv/
196 ├── _init.py
197 ├── database.py
198 ├── process.py
199 ├── requirements.txt
200 └── README.md
201 ```
202
203 This structure allows me to:
204
205 1. Compile all required python packages into
206 `requirements.txt` for easy installation if I move to
207 a new machine.
208 2. Activate a virtual environment in `venv/` so I don't
209 need to maintain a system-wide Python environment just for this
210 project.
211 3. Keep my `notebooks/` folder to continuously explore
212 the data as I see fit.
213 4. Maintain a local copy of the database in `src/` for
214 easy access.
215 5. Export reports, images, HTML files, etc. to
216 `public/`.
217
218 Now, onto the differences between the code in a Jupyter Notebook and
219 the actual Python files. To create the report in the Notebook
220 snippet above, I created the following function inside
221 `process.py`:
222
223 ```python
224 # Create summary pie chart
225 def summary_data(accounts: pandas.DataFrame) -> None:
226 accounts_01 = accounts[accounts["Owner"] == "Person01"]
227 accounts_02 = accounts[accounts["Owner"] == "Person02"]
228 for x in range(1, 4):
229 if x == 1:
230 df = accounts
231 account_string = "All Accounts"
232 elif x == 2:
233 df = accounts_01
234 account_string = "Person01's Accounts"
235 elif x == 3:
236 df = accounts_02
237 account_string = "Person02's Accounts"
238 print(f"Generating pie chart summary image for {account_string}...")
239 summary_chart = (
240 df.groupby(["AccountType"])
241 .sum()
242 .plot.pie(
243 title=f"Credit Line by Type for {account_string}",
244 y="CreditLine",
245 autopct="%1.1f%%",
246 )
247 )
248 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
249 ```
250
251 The result? A high-quality pie chart that is read directly by the
252 `public/index.html` template I use.
253
254 ![Summary Pie
255 Chart](https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png)
256
257 Other charts generated by this project include:
258
259 - Charts of account balances over time.
260 - Line chart of effective tax rate (taxes divided by taxable
261 income).
262 - Salary projections and error limits using past income and
263 inflation rates.
264 - Multi-line chart of gross income, taxable income, and net
265 income.
266
267 The best thing about this project? I can improve it at any given
268 time, shaping it into whatever helps me the most for that time. I
269 imagine that I will be introducing an asset tracking table soon to
270 track the depreciating value of cars, houses, etc. Who knows what's
271 next?
content/blog/2022-03-08-plex-migration.md added +253
@@ -0,0 +1,253 @@
1+++
2date = 2022-03-08
3title = "Migrating Plex to a New Server (& Nvidia Transcoding)"
4description = "A retrospective on migrating Plex between servers and enabling Nvidia GPU transcoding."
5+++
6
7## Migration Phases
8
9I recently decided to migrate my server from an old OptiPlex desktop
10machine to a custom-built tower with better hardware in every category.
11In order to do this, I would need to properly migrate a full Plex
12installation.
13
14The second part of this migration is that the new server uses an Nvidia
15GPU and does not have any integrated graphics, which requires extra work
16for installation, but provides much better hardware transcoding options
17for Plex.
18
19Therefore, I have broken this migration down into three phases:
20
211. [Configure the New Server](#phase-1-configure-the-new-server)
222. [Migrate Plex Data & Devices](#phase-2-migrate-plex-data-devices)
233. [Configure GPU Transcoding](#phase-3-configure-gpu-transcoding)
24
25------------------------------------------------------------------------
26
27## Phase 1: Configure the New Server
28
29### Choosing an OS
30
31In order to migrate Plex to my new server, I first needed to choose an
32appropriate operating system (OS) and install it on the machine. Given
33that I have encountered numerous issues installing other Linux
34distributions properly with Nvidia graphics, I chose [Ubuntu
35Server](https://ubuntu.com/download/server).
36
37The first step is to create a bootable USB with Ubuntu Server. This is
38easy with [Etcher](https://www.balena.io/etcher/), an app that runs on
39many different platforms. Just download the Ubuntu Server
40`.iso` image, launch Etcher, and install the
41`.iso` on the USB.
42
43Once the USB is created, insert it into my server, reboot, and click
44`Esc` (or any of the `F1-12` keys) until the BIOS
45menu appears. Finally, launch the USB boot drive.
46
47### Booting with Nvidia
48
49In order to install Ubuntu Server with an Nvidia Graphics card (and no
50integrated graphics on this device for some reason), you'll have to
51configure the boot menu to allow different graphics drivers to be
52loaded.
53
54When booting from the USB, the machine will launch the initial
55installation menu. From this menu, type `e` to view the
56default command options that come with the device - it's a good idea to
57take a photo of this screen, so you can enter these commands on the next
58screen (along with adding support for Nvidia).
59
60Finally, type `Ctrl + C` to enter the command line. From this
61command line, enter the commands found on the `e` screen.
62\*Remember to add `nomodeset` to the `linux ...`
63line so that your Nvidia device will display the installation screens
64properly!\*
65
66Here's an example of the commands I pulled from the `e`
67screen and entered on the command line.
68
69```sh
70setparams 'Install Ubuntu Server'
71setgfxpayload=keep
72linux /casper/vmlinuz quiet nomodeset ---
73initrd /casper/initrd
74boot
75```
76
77![Ubuntu Server Installation
7801](https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png)
79
80![Ubuntu Server Installation
8102](https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png)
82
83Once the machine is rebooted, enter the `e` screen again and
84add `nomodeset` to the `linux ...` line again and
85press `Ctrl + X` to save the boot options.
86
87The machine is now fully installed and can properly display on an
88external display using the Nvidia GPU.
89
90Always remember to update and upgrade on a new installation:
91
92```sh
93sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
94```
95
96------------------------------------------------------------------------
97
98## Phase 2: Migrate Plex Data & Devices
99
100This phase uses the great Plex article on migrations ([Move an
101Installation to Another
102System](https://support.plex.tv/articles/201370363-move-an-install-to-another-system/))
103and adds a bit more information to help with commands and context.
104
105### Terminology
106
107**Source:** The original server that is being replaced.\
108**Destination:** The new server.\
109**Client:** Any application that can be used to modify settings for both
110source/destination.
111
112### Step 01: [Client] Update Settings
113
114Open up a Plex app and *disable* the `Account` \>
115`Library` \>
116`Empty trash automatically after every scan` preference for
117the source server.
118
119### Step 02: [Destination] Install Plex
120
121Open up the [Plex Media Server download
122page](https://www.plex.tv/media-server-downloads/) and copy the link for
123the appropriate platform.
124
125Execute the following commands on the destination server to install
126Plex:
127
128```sh
129wget <url>
130sudo dpkg -i <filename>
131sudo systemctl stop plexmediaserver.service
132```
133
134### Step 03: [Source] Stop Plex & Migrate Data
135
136First, stop the Plex service so that no data is created or modified
137during the migration.
138
139```sh
140sudo systemctl stop plexmediaserver.service
141```
142
143Next, copy the data to the new server. To find where the Plex data
144directory is located, Plex has another excellent article available:
145[Where is the Plex Media Server data directory
146located?](https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/).
147
148There are many ways to copy the data to the new server and will largely
149depend on the size of the folder being copied. Personally, my data
150folder was \~23GB and I opted to simply use the `scp` command
151to copy the files over SSH.
152
153This process was throttled by the old server's slow HDD and ports and
154took approximately 90 minutes to complete. In comparison, moving the
155data from the new server's `home/user/` directory to the
156`/var/.../Plex Media Server` directory took 2-3 minutes.
157
158```sh
159scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
160```
161
162### Step 04: [Destination] Update File Permissions
163
164In case you move the data directory to a common area on the new server,
165it will have to be moved to the proper location before Plex can function
166properly:
167
168```sh
169mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
170```
171
172To ensure permissions were retained properly, the server will need to
173show that all files and folders in the data directory are owned by
174`plex:plex` (or whichever user is running the Plex
175application).
176
177```sh
178sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
179```
180
181Finally, start the service and check the status.
182
183```sh
184sudo systemctl start plexmediaserver.service
185sudo systemctl status plexmediaserver.service
186```
187
188### Step 05: [Client] Update Libraries & Metadata
189
190The first step - now that the new server is up and running - is to sign
191out of the client and sign back in. Once this is done, update any
192library locations, if necessary. This was unnecessary in my case since I
193simply moved my storage drives from the source server to the destination
194server.
195
196Next, perform the following actions in the client:
197
1981. On the left sidebar, click `More` \> Three-Dot Menu \>
199 `Scan Library Files`
2002. *Enable* the `Account` \> `Library` \>
201 `Empty trash automatically after every scan` preference
202 for the source server.
2033. On the left sidebar, click `More` \> Three-Dot Menu \>
204 `Manage Server` \> `Empty Trash`
2054. On the left sidebar, click `More` \> Three-Dot Menu \>
206 `Manage Server` \> `Clean Bundles`
2075. On the left sidebar, click `More` \> Three-Dot Menu \>
208 `Manage Server` \> `Optimize Database`
209
210Finally, double-check the Remote Access settings to make sure no changes
211have caused issues with accessing the server from outside the network.
212
213In my case, I use a single port forwarding rule in my router and needed
214to update the Local LAN IP Address to the new server IP address.
215
216![Router port
217forwarding](https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png)
218
219------------------------------------------------------------------------
220
221## Phase 3: Configure GPU Transcoding
222
223The final piece to the migration is enabling hardware transcoding so
224that Plex can fully utilize the new Nvidia GPU available in the server.
225The first step is to install Nvidia graphics drivers. This process may
226take a few minutes, but the commands are pretty simple:
227
228```sh
229sudo add-apt-repository ppa:graphics-drivers/ppa
230sudo apt update
231sudo apt-get install ubuntu-drivers-common
232sudo ubuntu-drivers autoinstall
233```
234
235Finally, reboot so that the changes are loaded:
236
237```sh
238sudo reboot now
239```
240
241To ensure that the Nvidia graphics drivers are working properly, run the
242following command to view the available GPUs, statistics, and processes:
243
244```sh
245sudo nvidia-smi
246```
247
248![nvidia-smi](https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png)
249
250Finally, enable hardware transcoding settings in the Plex application:
251
252![Plex transcoding
253settings](https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png)
blog/2022-03-23-cloudflare-dns-api.org → content/blog/2022-03-23-cloudflare-dns-api.md renamed +50 −46
@@ -1,40 +1,41 @@
1#+title: Dynamic DNS with Cloudflare API 1+++
2#+date: 2022-03-23 2date = 2022-03-23
3 3title = "Dynamic DNS with Cloudflare API"
4** DDNS: Dynamic DNS 4description = "Learn how to dynamically update DNS records for changing IPs with Cloudflare."
5:PROPERTIES: 5+++
6:CUSTOM_ID: ddns-dynamic-dns 6
7:END: 7## DDNS: Dynamic DNS
8If you're hosting a service from a location with dynamic DNS (where your 8
9IP may change at any time), you must have a solution to update the DNS 9If you're hosting a service from a location with dynamic DNS (where
10so that you can access your service even when the IP of the server 10your IP may change at any time), you must have a solution to update the
11DNS so that you can access your service even when the IP of the server
11changes. 12changes.
12 13
13The process below uses the [[https://api.cloudflare.com/][Cloudflare 14The process below uses the [Cloudflare API](https://api.cloudflare.com/)
14API]] to update DNS =A= and =AAAA= records with the server's current IP. 15to update DNS `A` and `AAAA` records with the
15If you use another DNS provider, you will have to find a way to update 16server's current IP. If you use another DNS provider, you will have to
16your DNS (or find a way to get a static IP). 17find a way to update your DNS (or find a way to get a static IP).
17 18
18First, install =jq= since we will use it in the next script: 19First, install `jq` since we will use it in the next script:
19 20
20#+begin_src sh 21```sh
21sudo apt install jq 22sudo apt install jq
22#+end_src 23```
23 24
24Next, create a location for your DDNS update scripts and open the first 25Next, create a location for your DDNS update scripts and open the first
25script: 26script:
26 27
27#+begin_src sh 28```sh
28mkdir ~/ddns 29mkdir ~/ddns
29nano ~/ddns/update.sh 30nano ~/ddns/update.sh
30#+end_src 31```
31 32
32The following =update.sh= script will take all of your domains and 33The following `update.sh` script will take all of your
33subdomains and check Cloudflare to see if the current =A= and =AAAA= 34domains and subdomains and check Cloudflare to see if the current
34records match your server's IP address. If not, it will update the 35`A` and `AAAA` records match your server's IP
35records. 36address. If not, it will update the records.
36 37
37#+begin_src sh 38```sh
38# file: update.sh 39# file: update.sh
39#!/bin/bash 40#!/bin/bash
40 41
@@ -56,20 +57,22 @@ do
56 echo -e "\nUpdating $subdomain..." 57 echo -e "\nUpdating $subdomain..."
57 zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh 58 zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh
58done 59done
59#+end_src 60```
60 61
61Next, open up the =ddns.sh= script. Paste the following into the script 62Next, open up the `ddns.sh` script. Paste the following into
62and update the =api_token= and =email= variables. 63the script and update the `api_token` and `email`
64variables.
63 65
64#+begin_src sh 66```sh
65nano ~/ddns/ddns.sh 67nano ~/ddns/ddns.sh
66#+end_src 68```
67 69
68:warning: *Note*: If you want your DNS records to be proxied through 70:warning: **Note**: If you want your DNS records to be proxied through
69Cloudflare, find and update the following snippet: =\"proxied\":false}"= 71Cloudflare, find and update the following snippet:
70to say =true= instead of =false=. 72`"proxied":false}"` to say `true` instead of
73`false`.
71 74
72#+begin_src sh 75```sh
73# file: ddns.sh 76# file: ddns.sh
74#!/bin/bash 77#!/bin/bash
75# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a 78# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
@@ -121,7 +124,7 @@ then
121 -H "Content-Type: application/json" \ 124 -H "Content-Type: application/json" \
122 -H "X-Auth-Email: $email" \ 125 -H "X-Auth-Email: $email" \
123 -H "Authorization: Bearer $api_token" \ 126 -H "Authorization: Bearer $api_token" \
124 --data "{\"type\":\"A\",\"name\":\"$dns_record\",\"content\":\"$ipv4\",\"ttl\":1,\"proxied\":false}" \ 127 --data "{"type":"A","name":"$dns_record","content":"$ipv4","ttl":1,"proxied":false}" \
125 | jq -r '.errors' 128 | jq -r '.errors'
126 else 129 else
127 echo "The current IPv4 and DNS record IPv4 are the same." 130 echo "The current IPv4 and DNS record IPv4 are the same."
@@ -148,7 +151,7 @@ then
148 -H "Content-Type: application/json" \ 151 -H "Content-Type: application/json" \
149 -H "X-Auth-Email: $email" \ 152 -H "X-Auth-Email: $email" \
150 -H "Authorization: Bearer $api_token" \ 153 -H "Authorization: Bearer $api_token" \
151 --data "{\"type\":\"AAAA\",\"name\":\"$dns_record\",\"content\":\"$ipv6\",\"ttl\":1,\"proxied\":false}" \ 154 --data "{"type":"AAAA","name":"$dns_record","content":"$ipv6","ttl":1,"proxied":false}" \
152 | jq -r '.errors' 155 | jq -r '.errors'
153 else 156 else
154 echo "The current IPv6 and DNS record IPv6 are the same." 157 echo "The current IPv6 and DNS record IPv6 are the same."
@@ -162,30 +165,31 @@ then
162else 165else
163 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"
164fi 167fi
165#+end_src 168```
166 169
167Once the script is saved and closed, make the scripts executable: 170Once the script is saved and closed, make the scripts executable:
168 171
169#+begin_src sh 172```sh
170chmod +x ~/ddns/ddns.sh 173chmod +x ~/ddns/ddns.sh
171chmod +x ~/ddns/update.sh 174chmod +x ~/ddns/update.sh
172#+end_src 175```
173 176
174You can test the script by running it manually: 177You can test the script by running it manually:
175 178
176#+begin_src sh 179```sh
177./update.sh 180./update.sh
178#+end_src 181```
179 182
180To make sure the scripts run automatically, add it to the =cron= file so 183To make sure the scripts run automatically, add it to the
181that 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:
182 186
183#+begin_src sh 187```sh
184crontab -e 188crontab -e
185#+end_src 189```
186 190
187In 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:
188 192
189#+begin_src sh 193```sh
190*/5 * * * * bash /home/<your_username>/ddns/update.sh 194*/5 * * * * bash /home/<your_username>/ddns/update.sh
191#+end_src 195```
blog/2022-03-23-nextcloud-on-ubuntu.org → content/blog/2022-03-23-nextcloud-on-ubuntu.md renamed +68 −80
@@ -1,90 +1,83 @@
1#+title: Installing Nextcloud on Ubuntu 1+++
2#+date: 2022-03-23 2date = 2022-03-23
3 3title = "Installing Nextcloud on Ubuntu"
4** What is Nextcloud? 4description = "A guide to self-hosting the NextCloud application on your own server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: what-is-nextcloud 6
7:END: 7## What is Nextcloud?
8[[https://nextcloud.com/][Nextcloud]] is a self-hosted solution for 8
9[Nextcloud](https://nextcloud.com/) is a self-hosted solution for
9storage, communications, editing, calendar, contacts, and more. 10storage, communications, editing, calendar, contacts, and more.
10 11
11This tutorial assumes that you have an Ubuntu server and a domain name 12This tutorial assumes that you have an Ubuntu server and a domain name
12configured to point toward the server. 13configured to point toward the server.
13 14
14** Install Dependencies 15## Install Dependencies
15:PROPERTIES: 16
16:CUSTOM_ID: install-dependencies
17:END:
18To start, you will need to install the packages that Nextcloud requires: 17To start, you will need to install the packages that Nextcloud requires:
19 18
20#+begin_src sh 19```sh
21sudo apt install apache2 mariadb-server libapache2-mod-php7.4 20sudo apt install apache2 mariadb-server libapache2-mod-php7.4
22sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl 21sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl
23sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip 22sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip
24#+end_src 23```
25 24
26** Set Up MySQL 25## Set Up MySQL
27:PROPERTIES:
28:CUSTOM_ID: set-up-mysql
29:END:
30Next, you will need to log in to MySQL as the =root= user of the
31machine.
32 26
33#+begin_src sh 27Next, you will need to log in to MySQL as the `root` user of
28the machine.
29
30```sh
34sudo mysql -uroot -p 31sudo mysql -uroot -p
35#+end_src 32```
36 33
37Once you've logged in, you must create a new user so that Nextcloud can 34Once you've logged in, you must create a new user so that Nextcloud can
38manage the database. You will also create a =nextcloud= database and 35manage the database. You will also create a `nextcloud`
39assign privileges: 36database and assign privileges:
40 37
41#+begin_src sql 38```sql
42CREATE USER 'username'@'localhost' IDENTIFIED BY 'password'; 39CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
43CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 40CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
44GRANT ALL PRIVILEGES ON nextcloud.* TO 'username'@'localhost'; 41GRANT ALL PRIVILEGES ON nextcloud.* TO 'username'@'localhost';
45FLUSH PRIVILEGES; 42FLUSH PRIVILEGES;
46quit; 43quit;
47#+end_src 44```
45
46## Download & Install Nextcloud
48 47
49** Download & Install Nextcloud 48To download Nextcloud, go the [Nextcloud downloads
50:PROPERTIES: 49page](https://nextcloud.com/install/#instructions-server), click on
51:CUSTOM_ID: download-install-nextcloud 50`Archive File` and right-click the big blue button to copy
52:END: 51the link.
53To download Nextcloud, go the
54[[https://nextcloud.com/install/#instructions-server][Nextcloud
55downloads page]], click on =Archive File= and right-click the big blue
56button to copy the link.
57 52
58Then, go to your server and enter the following commands to download, 53Then, go to your server and enter the following commands to download,
59unzip, and move the files to your destination directory. This example 54unzip, and move the files to your destination directory. This example
60uses =example.com= as the destination, but you can put it wherever you 55uses `example.com` as the destination, but you can put it
61want to server your files from. 56wherever you want to server your files from.
62 57
63#+begin_src sh 58```sh
64wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip 59wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip
65sudo apt install unzip 60sudo apt install unzip
66unzip nextcloud-23.0.3.zip 61unzip nextcloud-23.0.3.zip
67sudo cp -r nextcloud /var/www/example.com 62sudo cp -r nextcloud /var/www/example.com
68#+end_src 63```
64
65## Configure the Apache Web Server
69 66
70** Configure the Apache Web Server
71:PROPERTIES:
72:CUSTOM_ID: configure-the-apache-web-server
73:END:
74Now that the database is set up and Nextcloud is installed, you need to 67Now that the database is set up and Nextcloud is installed, you need to
75set up the Apache configuration files to tell the server how to handle 68set up the Apache configuration files to tell the server how to handle
76requests for =example.com/nextcloud=. 69requests for `example.com/nextcloud`.
77 70
78First, open the following file in the editor: 71First, open the following file in the editor:
79 72
80#+begin_src sh 73```sh
81sudo nano /etc/apache2/sites-available/nextcloud.conf 74sudo nano /etc/apache2/sites-available/nextcloud.conf
82#+end_src 75```
83 76
84Once the editor is open, paste the following information in. Then, save 77Once the editor is open, paste the following information in. Then, save
85and close the file. 78and close the file.
86 79
87#+begin_src config 80```config
88<VirtualHost *:80> 81<VirtualHost *:80>
89 DocumentRoot /var/www/example.com 82 DocumentRoot /var/www/example.com
90 ServerName example.com 83 ServerName example.com
@@ -103,79 +96,74 @@ and close the file.
103 </IfModule> 96 </IfModule>
104 </Directory> 97 </Directory>
105</VirtualHost> 98</VirtualHost>
106#+end_src 99```
107 100
108Once the file is saved, enable it with Apache: 101Once the file is saved, enable it with Apache:
109 102
110#+begin_src sh 103```sh
111sudo a2ensite nextcloud.conf 104sudo a2ensite nextcloud.conf
112#+end_src 105```
113 106
114Next, enable the Apache mods required by Nextcloud: 107Next, enable the Apache mods required by Nextcloud:
115 108
116#+begin_src sh 109```sh
117sudo a2enmod rewrite headers env dir mime 110sudo a2enmod rewrite headers env dir mime
118#+end_src 111```
119 112
120Finally, restart Apache. If any errors arise, you must solve those 113Finally, restart Apache. If any errors arise, you must solve those
121before continuing. 114before continuing.
122 115
123#+begin_src sh 116```sh
124sudo systemctl restart apache2 117sudo systemctl restart apache2
125#+end_src 118```
126 119
127For the app to work, you must have the correct file permissions on your 120For the app to work, you must have the correct file permissions on your
128=nextcloud= directory. Set the owner to be =www-data=: 121`nextcloud` directory. Set the owner to be
122`www-data`:
129 123
130#+begin_src sh 124```sh
131sudo chown -R www-data:www-data /var/www/example.com/nextcloud/ 125sudo chown -R www-data:www-data /var/www/example.com/nextcloud/
132#+end_src 126```
127
128## DNS
133 129
134** DNS
135:PROPERTIES:
136:CUSTOM_ID: dns
137:END:
138If you do not have a static IP address, you will need to update your DNS 130If you do not have a static IP address, you will need to update your DNS
139settings (at your DNS provider) whenever your dynamic IP address 131settings (at your DNS provider) whenever your dynamic IP address
140changes. 132changes.
141 133
142For an example on how I do that with Cloudflare, see my other post: 134For an example on how I do that with Cloudflare, see my other post:
143[[/blog/updating-dynamic-dns-with-cloudflare-api/][Updating Dynamic DNS 135[Updating Dynamic DNS with Cloudflare
144with Cloudflare API]] 136API](/blog/updating-dynamic-dns-with-cloudflare-api/)
137
138## Certbot
145 139
146** Certbot
147:PROPERTIES:
148:CUSTOM_ID: certbot
149:END:
150If you want to serve Nextcloud from HTTPS rather than plain HTTP, use 140If you want to serve Nextcloud from HTTPS rather than plain HTTP, use
151the following commands to issue Let's Encrypt SSL certificates: 141the following commands to issue Let's Encrypt SSL certificates:
152 142
153#+begin_src sh 143```sh
154sudo apt install snapd 144sudo apt install snapd
155sudo snap install core 145sudo snap install core
156sudo snap refresh core 146sudo snap refresh core
157sudo snap install --classic certbot 147sudo snap install --classic certbot
158sudo ln -s /snap/bin/certbot /usr/bin/certbot 148sudo ln -s /snap/bin/certbot /usr/bin/certbot
159sudo certbot --apache 149sudo certbot --apache
160#+end_src 150```
151
152## Results
161 153
162** Results
163:PROPERTIES:
164:CUSTOM_ID: results
165:END:
166Voilà! You're all done and should be able to access Nextcloud from your 154Voilà! You're all done and should be able to access Nextcloud from your
167domain or IP address. 155domain or IP address.
168 156
169See the screenshots below for the dashboard and a settings page on my 157See the screenshots below for the dashboard and a settings page on my
170instance of Nextcloud, using the =Breeze Dark= theme I installed from 158instance of Nextcloud, using the `Breeze Dark` theme I
171the Apps page. 159installed from the Apps page.
172 160
173#+caption: Nextcloud Dashboard 161![Nextcloud
174[[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png]] 162Dashboard](https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png)
175 163
176/Figure 01: Nextcloud Dashboard/ 164*Figure 01: Nextcloud Dashboard*
177 165
178#+caption: Nextcloud Settings 166![Nextcloud
179[[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png]] 167Settings](https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png)
180 168
181/Figure 02: Nextcloud Security Settings/ 169*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 = "Learn some basic techniques to harden a home server and network."
5+++
6
7## Post Updates
8
9> After reviewing this post today (2022-10-04), I noticed quite a few
10> gaps in my write-up and wanted to add a few things, even though this
11> blog is really just a retrospective and knowledge dump for myself. I
12> left things intact and simply crossed them out (~~like this~~) for
13> posterity.
14
15## Planning Data Flows & Security
16
17### My Personal Data Flow
18
19```txt
20 ┌───────┐ ┌─────────────────┐
21 ┌──► VLAN1 ├───► Private Devices │
22 │ └───────┘ └─────────────────┘
23┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
24│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
25└──────────┘ └────────┘ └──────────┘ └────────┘ │
26 │ ┌───────┐ ┌───────────────┐
27 └──► VLAN2 ├───► Public Server │
28 └───────┘ └───────────────┘
29```
30
31### Thought Process
32
33To serve content from your home server and harden your security posture,
34you have to think about the transport of data from `server`
35to `client`.
36
37Let's start with the actual server itself. Think about the following:
38
39- Do I have a firewall enabled? Do I need to update this to allow new
40 ports or IPs?
41- Do I have an IPS/IDS that may prevent outside traffic?
42- Do I have any other security software installed?
43- Are the services hosted inside Docker containers, behind a reverse
44 proxy, or virtualized? If so, are they configured to allow outside
45 traffic?
46
47Once the data leaves the server, where does it go? In my case, it goes
48to a managed switch. In this case, I asked the following:
49
50- What configurations is the switch using?
51- Am I using VLANs?
52 - Yes, I am using 802.1Q VLANs.
53- Are the VLANs configured properly?
54 - Yes, as shown in the [Switch](#switch) section below, I have a
55 separate VLAN to allow outside traffic to and from the server
56 alone. No other devices, except for a service port, and in that
57 VLAN.
58
59At this point, the data has been processed through the switch. Where
60does it go next? In my case, it's pretty simple: it goes to the
61router/modem device.
62
63- Does my ISP block any ports that I need?
64 - This is an important step that a lot of people run into when
65 self-hosting at home. Use an online port-checker tool for your
66 IP or call your ISP if you think ports are blocked.
67- Is there a router firewall?
68 - Yes, I checked that it's configured to allow the ports I need
69 to run my services publicly. Common web servers and reverse
70 proxies require ports 80 and 443, but other services like media
71 servers or games can require unique ports, so be sure to check
72 the documentation for your service(s).
73- Are there any other settings affecting inbound/outbound traffic?
74 - Schedules or access blocks
75 - Static Routing
76 - QoS
77 - Port Forwarding
78 - DMZ Hosting
79 - Remote Management (this can sometimes mess with services that
80 also require the use of ports 80 and 443)
81
82Once the data leaves my router, it goes to the upstream ISP and can be
83accessed publicly.
84
85## Server
86
87+The services I run on my server are installed straight into the OS,
88without any use of Docker or VMs, so I don't need any extra application
89configuration to make them accessible to the outside world.+
90
91As of 2022-10-04, the paragraph above is no longer true as I now run a
92reverse proxy with Nginx and host many services inside Docker. However,
93it doesn't change anything regarding this post as I still just need to
94open ports 80 & 443 and create the necessary website configuration
95files.
96
97When creating new services - either installed directly on bare metal or
98within something like Docker - I ensure that I read through the
99documentation thoroughly to understand a few key things: - What network
100activities should this app perform (if any)? Using which ports and
101protocols? - Does this app require any commands/services to be run as
102`root`? - Does this app log errors, authentication
103failures/successes, or anything else that would be useful for an
104investigation?
105
106For extra security, I use limit all incoming connections to SSH
107connections through my server firewall (`ufw`) and disable
108common SSH settings. After all of that, I use `fail2ban` as a
109preventative measure against brute-force login attempts.
110
111As another piece of security, you can randomize your SSH port to ensure
112that random scanners or attackers can't easily try to force their way
113into your network. For example, you can edit the port rules in your
114server to block all connection requests to port `22` but
115forward all remote connections from port `12345` to your
116server's port `22`. Then you just need to SSH to your
117network via your randomized port.
118
119### `ufw`
120
121To see how to configure `ufw`, see my other post: [Secure
122Your Network with the Uncomplicated
123Firewall](/blog/secure-your-network-with-the-uncomplicated-firewall/).
124
125The general notion with an on-device firewall is that you want to deny
126all incoming connections by default and then selectively open certain
127ports for services or users that you know need access.
128
129If you know that you will only be logging into this server from a
130certain set or list of IPs, you can always set the firewall to only
131allow connections to port 22 from those IPs.
132
133For a quick start to only allow SSH connections to the server, use this:
134
135```sh
136sudo ufw default deny incoming
137sudo ufw default allow outgoing
138sudo ufw allow 22
139sudo ufw enable
140```
141
142![ufw](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png)
143
144### `ssh`
145
1461. Using SSH Keys
147
148 First, make sure you have an SSH keypair generated on the device(s)
149 that you'll be using to log in to the server. If you don't have an
150 SSH key, run this command:
151
152 ```sh
153 ssh-keygen
154 ```
155
156 Now that we have an SSH key, copy it to the server with the
157 following command, which will ask for the user's password before
158 accepting the key:
159
160 ```sh
161 ssh-copy-id my_user@my_server
162 ```
163
164 If you have multiple keys, you'll need to specify which to use.
165 After it's complete, `ssh` back into the server as that
166 user and make sure it doesn't ask for a password.
167
1682. Disable Password & Root Authentication
169
170 Now that we can access the server without a password, we will
171 disable password authentication and disable anyone from using
172 `ssh` to login as `root`.
173
174 To do this, open the `sshd_config` file:
175
176 ```sh
177 sudo nano /etc/ssh/sshd_config
178 ```
179
180 You'll need to update the parameters to the values below. If one of
181 these rules is commented-out or doesn't exist, create the rule at
182 the bottom of the file.
183
184 ```config
185 PermitRootLogin no
186 PasswordAuthentication no
187 PubkeyAuthentication yes
188 ```
189
190 Finally, restart the `ssh` service:
191
192 ```sh
193 sudo systemctl restart sshd.service
194 ```
195
196 To test that everything's working so far, open ANOTHER terminal and
197 try logging in as `root` over SSH. It is very important
198 that you keep your current SSH session open and test with an
199 additional session, or you will lock yourself out at some point and
200 will need to use a recovery method (e.g., hooking monitor up to home
201 server) to get yourself back in.
202
2033. Enable MFA for `ssh`
204
205 This part is optional, but I highly recommend it. So far, we've
206 ensured that no one can log into our user on the server without
207 using our secret key, and we've ensured that no one can log in
208 remotely as `root`. Next, you can enable MFA
209 authentication for `ssh` connections.
210
211 This process involves editing a couple files and installing an MFA
212 package, so I will not include all the details in this post. To see
213 how to configure MFA for `ssh`, see my other post:
214 [Enabling MFA for SSH](/blog/enable-totp-mfa-for-ssh/).
215
216 ![SSH
217 MFA](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png)
218
219### `fail2ban`
220
221I haven't written a post on how I use `fail2ban`, but it's
222quite simple. I use the default `sshd` jail, but you can
223always create new jails for respective applications or ports. For
224example, if you use Nginx as your web server, you can use the
225`nginx-http-auth` jail.
226
227In order to get it up and running, use the following commands:
228
229```sh
230sudo apt install fail2ban
231sudo fail2ban-client start sshd
232sudo fail2ban-client status sshd
233```
234
235This should be used as a last-resort defense and shouldn't be a
236replacement for the security measures mentioned above.
237
238![fail2ban](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png)
239
240## Switch
241
242Between the router and any local devices is my managed switch, which is
243used to create VLANs. The example below shows how I would isolate the
244VLANs if I were starting to host a single service at home.
245
246### 802.1Q VLAN Configuration
247
248In this configuration, port 8 is the public server that needs to be
249accessed from the outside. Port 23 is my 'dedicated service port' for
250this server. In order to SSH to this server, I need to plug my laptop
251into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a
252monitor and keyboard directly to the server to manage it.
253
254 VLAN ID VLAN Name Member Ports Tagged Ports Untagged Ports
255 --------- ----------- -------------- -------------- ----------------
256 1 Default 1-24 1-24
257 2 Server 1,8,23 1,8,23
258
259### 802.1Q VLAN PVID Setting
260
261Once the VLAN is created, I simply add the `VLAN ID` of
262`2` as the `PVID` for any related ports (in this
263case, see that ports `8` and `23` have a PVID of
264`2`).
265
266 Port PVID
267 ------ ------
268 1 1
269 2 1
270 3 1
271 4 1
272 5 1
273 6 1
274 7 1
275 8 2
276 9 1
277 10 1
278 11 1
279 12 1
280 13 1
281 14 1
282 15 1
283 16 1
284 17 1
285 18 1
286 19 1
287 20 1
288 21 1
289 22 1
290 23 2
291 24 1
292
293## Router
294
295On my router, the configuration was as easy as opening the firewall
296settings and unblocking the ports I needed for my services (e.g.,
297HTTP/S, Plex, SSH, MySQL, etc.).
298
299+Since I'm relying on an ISP-provided modem/router combo for now (not
300by choice), I do not use any other advanced settings on my router that
301would inhibit any valid traffic to these services.+
302
303The paragraph above regarding the ISP-owned router is no longer accurate
304as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
305this router, I enabled port forwarding/firewall rules, segregate the
306network based on the device, and enable traffic restrictions (e.g.,
307silently drop traffic from certain countries and threat categories).
308
309If you have the option with your ISP, I recommend using a personal
310router with software that you are familiar with so that you can explore
311all the options available to you.
312
313## Physical Security
314
315One large piece of self-hosting that people generally don't discuss
316online is physical security. However, physical security is very
317important for everyone who hosts a server like this. Exactly *how*
318important it is depends on the server use/purpose.
319
320If you self-host customer applications that hold protected data (HIPAA,
321GDPR, COPPA, etc.), then physical security is extremely important and
322cannot be ignored. If you simply host a blog and some hobby sites, then
323it's a relatively minor consideration, but one you still need to think
324about.
325
326### Location
327
328The first consideration is quite simple: location. - Is the server
329within a property you own or housed on someone else's property? - Is it
330nearby (in your house, in your work office, in your neighbor's garage,
331in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
332there climate considerations, such as humidity, fires, tornadoes,
333monsoons? - Do you have emergency equipment nearby in case of emergency?
334
335### Hardware Ownership
336
337Secondly, consider the hardware itself: - Do you own the server in its
338entirety? - Are any other users able to access the server, even if your
339data/space is segregated? - If you're utilizing a third party, do they
340have any documentation to show responsibility? This could be a SOC 1/2/3
341report, ISO compliance report, internal security/safety documentation.
342
343### Physical Controls
344
345Regardless of who owns the hardware, ensure that there are adequate
346safeguards in place, if necessary. These usually don't apply to small
347home servers and are usually covered already if you're utilizing a
348third party.
349
350These can include: - Server bezel locks - Server room locks - physical,
351digital, or biometric authentication - Security cameras - Raised
352floors/lowered ceilings with proper guards/gates in-place within the
353floors or ceilings - Security personnel - Log sheets and/or guest badges
blog/2022-03-26-ssh-mfa.org → content/blog/2022-03-26-ssh-mfa.md renamed +72 −82
@@ -1,88 +1,83 @@
1#+title: Enable TOTP MFA for SSH 1+++
2#+date: 2022-03-26 2date = 2022-03-26
3title = "Enable TOTP MFA for SSH"
4description = "Learn how to enable timed one-time passcodes for SSH."
5+++
6
7## Why Do I Need MFA for SSH?
3 8
4** Why Do I Need MFA for SSH?
5:PROPERTIES:
6:CUSTOM_ID: why-do-i-need-mfa-for-ssh
7:END:
8If you are a sysadmin of a server anywhere (that includes at home!), you 9If you are a sysadmin of a server anywhere (that includes at home!), you
9may want an added layer of protection against intruders. This is not a 10may want an added layer of protection against intruders. This is not a
10replacement for other security measures, such as: 11replacement for other security measures, such as:
11 12
12- Disable root SSH 13- Disable root SSH
13- Disable SSH password authentication 14- Disable SSH password authentication
14- Allow only certain users to login via SSH 15- Allow only certain users to login via SSH
15- Allow SSH only from certain IPs 16- Allow SSH only from certain IPs
16 17
17However, MFA can be added as an additional security measure to ensure 18However, MFA can be added as an additional security measure to ensure
18that your server is protected. This is especially important if you need 19that your server is protected. This is especially important if you need
19to allow password authentication for SSH. 20to allow password authentication for SSH.
20 21
21For more guidance on server security measures, see my other post: 22For more guidance on server security measures, see my other post:
22[[/blog/hardening-a-public-facing-home-server/][Hardening a 23[Hardening a Public-Facing Home
23Public-Facing Home Server]]. 24Server](/blog/hardening-a-public-facing-home-server/).
25
26## Install MFA PAM Module
24 27
25** Install MFA PAM Module
26:PROPERTIES:
27:CUSTOM_ID: install-mfa-pam-module
28:END:
29PAM, which stands for Pluggable Authentication Module, is an 28PAM, which stands for Pluggable Authentication Module, is an
30authentication infrastructure used on Linux systems to authenticate a 29authentication infrastructure used on Linux systems to authenticate a
31user. In order to use this technology, let's install the 30user. In order to use this technology, let's install the
32=libpam-google-authenticator= package: 31`libpam-google-authenticator` package:
33 32
34#+begin_src sh 33```sh
35sudo apt-get update 34sudo apt-get update
36#+end_src 35```
37 36
38#+begin_src sh 37```sh
39sudo apt-get install libpam-google-authenticator 38sudo apt-get install libpam-google-authenticator
40#+end_src 39```
41 40
42** Initialize the PAM Module 41## Initialize the PAM Module
43:PROPERTIES: 42
44:CUSTOM_ID: initialize-the-pam-module 43### Interactive Method
45:END: 44
46*** Interactive Method
47:PROPERTIES:
48:CUSTOM_ID: interactive-method
49:END:
50Once the package is installed, initialize it and following the 45Once the package is installed, initialize it and following the
51interactive prompts to generate your OTP or TOTP: 46interactive prompts to generate your OTP or TOTP:
52 47
53#+begin_src sh 48```sh
54google-authenticator 49google-authenticator
55#+end_src 50```
56 51
57If you are not sure how to answer, read the prompts carefully and think 52If you are not sure how to answer, read the prompts carefully and think
58about having to how each situation would affect your normal login 53about having to how each situation would affect your normal login
59attempts. If you are still not sure, use my default responses below. 54attempts. If you are still not sure, use my default responses below.
60 55
61#+begin_src txt 56```txt
62OUTPUT 57OUTPUT
63 58
64Do you want authentication tokens to be time-based (y/n) y 59Do you want authentication tokens to be time-based (y/n) y
65#+end_src 60```
66 61
67At this point, use an authenticator app somewhere one of your devices to 62At this point, use an authenticator app somewhere one of your devices to
68scan the QR code. Any future login attempts after our upcoming 63scan the QR code. Any future login attempts after our upcoming
69configuration changes will require that TOTP. 64configuration changes will require that TOTP.
70 65
71#+begin_src txt 66```txt
72OUTPUT 67OUTPUT
73 68
74Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y 69Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y
75#+end_src 70```
76 71
77#+begin_src txt 72```txt
78OUTPUT 73OUTPUT
79 74
80Do you want to disallow multiple uses of the same authentication 75Do you want to disallow multiple uses of the same authentication
81token? This restricts you to one login about every 30s, but it increases 76token? This restricts you to one login about every 30s, but it increases
82your chances to notice or even prevent man-in-the-middle attacks (y/n) y 77your chances to notice or even prevent man-in-the-middle attacks (y/n) y
83#+end_src 78```
84 79
85#+begin_src txt 80```txt
86OUTPUT 81OUTPUT
87 82
88By default, a new token is generated every 30 seconds by the mobile app. 83By default, a new token is generated every 30 seconds by the mobile app.
@@ -95,32 +90,30 @@ code, the next code) to 17 permitted codes (the 8 previous codes, the current
95code, and the 8 next codes). This will permit for a time skew of up to 4 minutes 90code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
96between client and server. 91between client and server.
97Do you want to do so? (y/n) n 92Do you want to do so? (y/n) n
98#+end_src 93```
99 94
100#+begin_src txt 95```txt
101OUTPUT 96OUTPUT
102 97
103If the computer that you are logging into isn't hardened against brute-force 98If the computer that you are logging into isn't hardened against brute-force
104login attempts, you can enable rate-limiting for the authentication module. 99login attempts, you can enable rate-limiting for the authentication module.
105By default, this limits attackers to no more than 3 login attempts every 30s. 100By default, this limits attackers to no more than 3 login attempts every 30s.
106Do you want to enable rate-limiting? (y/n) y 101Do you want to enable rate-limiting? (y/n) y
107#+end_src 102```
103
104### Non-Interactive Method
108 105
109*** Non-Interactive Method
110:PROPERTIES:
111:CUSTOM_ID: non-interactive-method
112:END:
113If you need to do this quickly, know your responses to the prompts, or 106If you need to do this quickly, know your responses to the prompts, or
114are setting this up for numerous users, the non-interactive method can 107are setting this up for numerous users, the non-interactive method can
115be much faster: 108be much faster:
116 109
117#+begin_src sh 110```sh
118google-authenticator -t -d -f -r 3 -R 30 -w 3 111google-authenticator -t -d -f -r 3 -R 30 -w 3
119#+end_src 112```
120 113
121The options referenced above are as follows: 114The options referenced above are as follows:
122 115
123#+begin_src txt 116```txt
124google-authenticator [<options>] 117google-authenticator [<options>]
125 -h, --help Print this message 118 -h, --help Print this message
126 -c, --counter-based Set up counter-based (HOTP) verification 119 -c, --counter-based Set up counter-based (HOTP) verification
@@ -140,69 +133,66 @@ google-authenticator [<options>]
140 -w, --window-size=W Set window of concurrently valid codes 133 -w, --window-size=W Set window of concurrently valid codes
141 -W, --minimal-window Disable window of concurrently valid codes 134 -W, --minimal-window Disable window of concurrently valid codes
142 -e, --emergency-codes=N Number of emergency codes to generate 135 -e, --emergency-codes=N Number of emergency codes to generate
143#+end_src 136```
144 137
145This fully configures the authenticator, saves it to a file, and then 138This fully configures the authenticator, saves it to a file, and then
146outputs the secret key, QR code, and recovery codes. (If you add the 139outputs the secret key, QR code, and recovery codes. (If you add the
147flag =-q=, then there won't be any output). If you use this command in 140flag `-q`, then there won't be any output). If you use this
148an automated fashion, make sure your script captures the secret key 141command in an automated fashion, make sure your script captures the
149and/or recovery codes and makes them available to the user. 142secret key and/or recovery codes and makes them available to the user.
150 143
151** PAM Configuration Settings 144## PAM Configuration Settings
152:PROPERTIES: 145
153:CUSTOM_ID: pam-configuration-settings
154:END:
155Once you've enabled the T/OTP and have it saved to an MFA app on your 146Once you've enabled the T/OTP and have it saved to an MFA app on your
156phone or other device, open the PAM =sshd= file: 147phone or other device, open the PAM `sshd` file:
157 148
158#+begin_src sh 149```sh
159sudo nano /etc/pam.d/sshd 150sudo nano /etc/pam.d/sshd
160#+end_src 151```
161 152
162You need to do two things in this file. First, add the following lines 153You need to do two things in this file. First, add the following lines
163to the bottom of the file: 154to the bottom of the file:
164 155
165#+begin_src config 156```config
166auth required pam_google_authenticator.so nullok 157auth required pam_google_authenticator.so nullok
167auth required pam_permit.so 158auth required pam_permit.so
168#+end_src 159```
169 160
170Second, comment-out the following line near the top of the file. 161Second, comment-out the following line near the top of the file.
171 162
172If you leave this line uncommented, every SSH login attempt will ask for 163If you leave this line uncommented, every SSH login attempt will ask for
173the following three authentication factors: 164the following three authentication factors:
174 165
1751. Publickey 1661. Publickey
1762. Password 1672. Password
1773. T/OTP code 1683. T/OTP code
178 169
179#+begin_src config 170```config
180#@include common-auth 171#@include common-auth
181#+end_src 172```
173
174## SSH Configuration Settings
182 175
183** SSH Configuration Settings 176Finally, edit the `sshd_config` file again:
184:PROPERTIES:
185:CUSTOM_ID: ssh-configuration-settings
186:END:
187Finally, edit the =sshd_config= file again:
188 177
189#+begin_src sh 178```sh
190sudo nano /etc/ssh/sshd_config 179sudo nano /etc/ssh/sshd_config
191#+end_src 180```
192 181
193You'll need to change =ChallengeResponseAuthentication= to yes and add 182You'll need to change `ChallengeResponseAuthentication` to
194the =AuthenticationMethods= line to the bottom of the file. 183yes and add the `AuthenticationMethods` line to the bottom of
184the file.
195 185
196#+begin_src config 186```config
197ChallengeResponseAuthentication yes 187ChallengeResponseAuthentication yes
198AuthenticationMethods publickey,password publickey,keyboard-interactive 188AuthenticationMethods publickey,password publickey,keyboard-interactive
199#+end_src 189```
200 190
201Finally, restart the =ssh= service: 191Finally, restart the `ssh` service:
202 192
203#+begin_src sh 193```sh
204sudo systemctl restart sshd.service 194sudo systemctl restart sshd.service
205#+end_src 195```
206 196
207The next time you log in, you should be greeted with a verification code 197The next time you log in, you should be greeted with a verification code
208request! 198request!
content/blog/2022-04-02-nginx-reverse-proxy.md added +234
@@ -0,0 +1,234 @@
1+++
2date = 2022-04-02
3title = "Set-Up a Reverse Proxy with Nginx"
4description = "Learn how to set-up an Nginx reverse proxy from scratch."
5+++
6
7## What is a Reverse Proxy?
8
9A reverse proxy is a server that is placed between local servers or
10services and clients/users (e.g., the internet). The reverse proxy
11intercepts all requests from clients at the network edge and uses its
12configuration files to determine where each request should be sent.
13
14### A Brief Example
15
16For example, let's say that I run three servers in my home:
17
18- Server~01~ (`example.com`)
19- Server~02~ (`service01.example.com`)
20- Server~03~ (`service02.example.com`)
21
22I also run a reverse proxy in my home that intercepts all public
23traffic:
24
25- Reverse Proxy
26
27Assume that I have a domain name (`example.com`) that allows
28clients to request websites or services from my home servers.
29
30In this case, the reverse proxy will intercept all traffic from
31`example.com` that enters my network and determine if the
32client is requesting valid data, based on my configuration.
33
34If the user is requesting `example.com` and my configuration
35files say that Server~01~ holds that data, Nginx will send the user to
36Server~01~. If I were to change the configuration so that
37`example.com` is routed to Server~02~, that same user would
38be sent to Server~02~ instead.
39
40```txt
41┌──────┐ ┌───────────┐
42│ User │─┐ ┌──► Server_01 │
43└──────┘ │ │ └───────────┘
44 │ ┌──────────┐ ┌───────────────┐ │ ┌───────────┐
45 ├────► Internet ├───► Reverse Proxy ├─────├──► Server_02 │
46 │ └──────────┘ └───────────────┘ │ └───────────┘
47┌──────┐ │ │ ┌───────────┐
48│ User │─┘ └──► Server_03 │
49└──────┘ └───────────┘
50```
51
52## Reverse Proxy Options
53
54There are a lot of options when it comes to reverse proxy servers, so
55I'm just going to list a few of the options I've heard recommended
56over the last few years:
57
58- [Nginx](https://nginx.com)
59- [Caddy](https://caddyserver.com)
60- [Traefik](https://traefik.io/)
61- [HAProxy](https://www.haproxy.org/)
62- [Squid](https://ubuntu.com/server/docs/proxy-servers-squid)
63
64In this post, we will be using Nginx as our reverse proxy, running on
65Ubuntu Server 20.04.4 LTS.
66
67## Nginx Reverse Proxy Example
68
69### Local Applications
70
71You may be like me and have a lot of applications running on your local
72network that you'd like to expose publicly with a domain.
73
74In my case, I have services running in multiple Docker containers within
75a single server and want a way to visit those services from anywhere
76with a URL. For example, on my local network, [Dashy](https://dashy.to)
77runs through port 4000 (`localhost:4000`) and [Uptime
78Kuma](https://github.com/louislam/uptime-kuma) runs through port 3001
79(`localhost:3001`).
80
81In order to expose these services to the public, I will need to do the
82following:
83
841. Set up DNS records for a domain or subdomain (one per service) to
85 point toward the IP address of the server.
862. Open up the server network's HTTP and HTTPS ports (80 & 443) so
87 that the reverse proxy can accept traffic and determine where to
88 send it.
893. Install the reverse proxy software.
904. Configure the reverse proxy to recognize which service should get
91 traffic from any of the domains or subdomains.
92
93### Step 1: DNS Configuration
94
95To start, update your DNS configuration so that you have an
96`A` record for each domain or subdomain.
97
98The `A` records should point toward the public IP address of
99the server. If you don't know the public IP address, log in to the
100server and run the following command:
101
102```sh
103curl ifconfig.co
104```
105
106In the DNS example below, `xxx.xxx.xxx.xxx` is the public IP
107address of the server.
108
109```config
110example.com A xxx.xxx.xxx.xxx
111uptime.example.com A xxx.xxx.xxx.xxx
112dashy.example.com A xxx.xxx.xxx.xxx
113www CNAME example.com
114```
115
116Finally, ensure the DNS has propagated correctly with [DNS
117Checker](https://dnschecker.org) by entering your domains or subdomains
118in the search box and ensuring the results are showing the correct IP
119address.
120
121### Step 2: Open Network Ports
122
123This step will be different depending on which router you have in your
124home. If you're not sure, try to visit
125[192.168.1.1](http://192.168.1.1) in your browser. Login credentials are
126usually written on a sticker somewhere on your modem/router.
127
128Once you're able to log in to your router, find the Port Forwarding
129settings. You will need to forward ports `80` and
130`443` to whichever machine is running the reverse proxy.
131
132In my case, the table below shows the port-forwarding rules I've
133created. In this table, `xxx.xxx.xxx.xxx` is the local device
134IP of the reverse proxy server, it will probably be an IP between
135`192.168.1.1` and `192.168.1.255`.
136
137 NAME FROM PORT DEST PORT/IP ENABLED
138 ------- ------ ------ ----------------- ---------
139 HTTP ​\* 80 xxx.xxx.xxx.xxx TRUE
140 HTTPS ​\* 443 xxx.xxx.xxx.xxx TRUE
141
142Once configured, these rules will direct all web traffic to your reverse
143proxy.
144
145### Step 3: Nginx Installation
146
147To install Nginx, simply run the following command:
148
149```sh
150sudo apt install nginx
151```
152
153If you have a firewall enabled, open up ports `80` and
154`443` on your server so that Nginx can accept web traffic
155from the router.
156
157For example, if you want to use `ufw` for web traffic and
158SSH, run the following commands:
159
160```sh
161sudo ufw allow 'Nginx Full'
162sudo ufw allow SSH
163sudo ufw enable
164```
165
166### Step 4: Nginx Configuration
167
168Now that we have domains pointing toward the server, the only step left
169is to configure the reverse proxy to direct traffic from domains to
170local services.
171
172To start, you'll need to create a configuration file for each domain in
173`/etc/nginx/sites-available/`. They will look identical
174except for the `server_name` variable and the
175`proxy_pass` port.
176
177Dashy:
178
179```sh
180nano /etc/nginx/sites-available/dashy.example.com
181```
182
183```config
184server {
185 listen 80;
186 server_name dashy.example.com;
187
188 location / {
189 proxy_pass http://localhost:4000;
190 }
191}
192```
193
194Uptime:
195
196```sh
197nano /etc/nginx/sites-available/uptime.example.com
198```
199
200```config
201server {
202 listen 80;
203 server_name uptime.example.com;
204
205 location / {
206 proxy_pass http://localhost:3001;
207 }
208}
209```
210
211Once the configuration files are created, you will need to enable them
212with the `symlink` command:
213
214```sh
215sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
216```
217
218Voilà! Your local services should now be available through their URLs.
219
220## HTTPS with Certbot
221
222If you've followed along, you'll notice that your services are only
223available via HTTP (not HTTPS).
224
225If you want to enable HTTPS for your new domains, you will need to
226generate SSL/TLS certificates for them. The easiest way to generate
227certificates on Nginx is [Certbot](https://certbot.eff.org):
228
229```sh
230sudo apt install snapd; sudo snap install core; sudo snap refresh core
231sudo snap install --classic certbot
232sudo ln -s /snap/bin/certbot /usr/bin/certbot
233sudo certbot --nginx
234```
blog/2022-04-09-pinetime.org → content/blog/2022-04-09-pinetime.md renamed +77 −98
@@ -1,53 +1,44 @@
1#+title: PineTime: An Open-Source SmartWatch 1+++
2#+date: 2022-04-09 2date = 2022-04-09
3 3title = "PineTime: An Open-Source Smart Watch"
4** PineTime Product Information 4description = "Playing with the PineTime smart watch."
5:PROPERTIES: 5+++
6:CUSTOM_ID: pinetime-product-information 6
7:END: 7## PineTime Product Information
8*** Overview 8
9:PROPERTIES: 9### Overview
10:CUSTOM_ID: overview 10
11:END: 11The [PineTime](https://www.pine64.org/pinetime/) is an open-source
12The [[https://www.pine64.org/pinetime/][PineTime]] is an open-source 12smartwatch, created by [PINE64](https://www.pine64.org). Originally
13smartwatch, created by [[https://www.pine64.org][PINE64]]. Originally
14announced in September 2019, this ARM-based watch is a fantastic option 13announced in September 2019, this ARM-based watch is a fantastic option
15for users who want the benefits of a modern smartwatch with the backing 14for users who want the benefits of a modern smartwatch with the backing
16of open-source components and software. 15of open-source components and software.
17 16
18*** Product Specifications 17### Product Specifications
19:PROPERTIES: 18
20:CUSTOM_ID: product-specifications 19I won't dive into too many details that you can find on [the product
21:END: 20page](https://www.pine64.org/pinetime/), but I wanted to point out the
22I won't dive into too many details that you can find on 21prices for each watch and the primary functions:
23[[https://www.pine64.org/pinetime/][the product page]], but I wanted to 22
24point out the prices for each watch and the primary functions: 231. Price:
25 24
26**** Price: 25 - \$26.99 (Sealed)
27:PROPERTIES: 26 - \$24.99 (Dev Kit)
28:CUSTOM_ID: price 27 - \$51.98 (One Sealed + One Dev Kit)
29:END: 28
30- $26.99 (Sealed) 292. Primary Functionality:
31- $24.99 (Dev Kit) 30
32- $51.98 (One Sealed + One Dev Kit) 31 - Clock (+ Smartphone Sync)
33 32 - Pedometer
34**** Primary Functionality: 33 - Heart Rate Monitor
35:PROPERTIES: 34 - Sleep Monitor
36:CUSTOM_ID: primary-functionality 35 - Calories burned
37:END: 36 - Messaging
38- Clock (+ Smartphone Sync) 37 - Smartphone Notifications
39- Pedometer 38 - Media Controls
40- Heart Rate Monitor 39
41- Sleep Monitor 40## Unboxing
42- Calories burned 41
43- Messaging
44- Smartphone Notifications
45- Media Controls
46
47** Unboxing
48:PROPERTIES:
49:CUSTOM_ID: unboxing
50:END:
51Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and 42Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and
52was delivered on 2022-03-23. With the current delays on shipping times 43was delivered on 2022-03-23. With the current delays on shipping times
53around the world (and the semiconductor shortage), a month for delivery 44around the world (and the semiconductor shortage), a month for delivery
@@ -60,35 +51,31 @@ USB wall adapter).
60The watch itself was able to turn on immediately when I pulled it out of 51The watch itself was able to turn on immediately when I pulled it out of
61the box, but the battery was depleted and required charging right away. 52the box, but the battery was depleted and required charging right away.
62 53
63#+caption: PineTime Box Contents 54![PineTime Box
64[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png]] 55Contents](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png)
65 56
66** Software 57## Software
67:PROPERTIES: 58
68:CUSTOM_ID: software 59### Watch OS: InfiniTime
69:END: 60
70*** Watch OS: InfiniTime
71:PROPERTIES:
72:CUSTOM_ID: watch-os-infinitime
73:END:
74While turning on the watch for the first time, some of the main design 61While turning on the watch for the first time, some of the main design
75choices you can see in the watch OS, 62choices you can see in the watch OS,
76[[https://wiki.pine64.org/wiki/InfiniTime][InfiniTime]], are: 63[InfiniTime](https://wiki.pine64.org/wiki/InfiniTime), are:
77 64
78- A square bezel, not too thin against the sides of the watch. 65- A square bezel, not too thin against the sides of the watch.
79- A simple, rubber band. 66- A simple, rubber band.
80- Basic font and screen pixel design. 67- Basic font and screen pixel design.
81- Swipe gestures to access other screens. 68- Swipe gestures to access other screens.
82 69
83#+caption: PineTime Screens 70![PineTime
84[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png]] 71Screens](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png)
85 72
86The OS itself is fantastic in terms of functionality for me. It does 73The OS itself is fantastic in terms of functionality for me. It does
87exactly what a smartwatch should do - track time, steps, heart rates, 74exactly what a smartwatch should do - track time, steps, heart rates,
88and connect to another smart device, without being overly burdensome to 75and connect to another smart device, without being overly burdensome to
89the user. 76the user.
90 77
91My only gripe so far is that it's /really/ difficult to swipe to 78My only gripe so far is that it's *really* difficult to swipe to
92different screens, such as pulling down the notification tray. I'm not 79different screens, such as pulling down the notification tray. I'm not
93sure if this is an OS or hardware issue, but it makes it quite hard to 80sure if this is an OS or hardware issue, but it makes it quite hard to
94quickly move around the screens. 81quickly move around the screens.
@@ -99,52 +86,46 @@ accidentally turn on the screen. With other watches, I absolutely hated
99not being able to turn off the raise-to-wake or wave features (i.e., 86not being able to turn off the raise-to-wake or wave features (i.e.,
100blinding myself while wearing a watch at night because I moved my arm). 87blinding myself while wearing a watch at night because I moved my arm).
101 88
102*** iOS App: InfiniLink 89### iOS App: InfiniLink
103:PROPERTIES: 90
104:CUSTOM_ID: ios-app-infinilink
105:END:
106Since I am using iOS as my primary mobile device OS, I am using the 91Since I am using iOS as my primary mobile device OS, I am using the
107[[https://github.com/xan-m/InfiniLink][InfiniLink]] app to connect my 92[InfiniLink](https://github.com/xan-m/InfiniLink) app to connect my
108watch. 93watch.
109 94
110This app provides the following for PineTime owners: 95This app provides the following for PineTime owners:
111 96
112- Firmware updates 97- Firmware updates
113- Steps 98- Steps
114- Charts 99- Charts
115- Notifications 100- Notifications
116 101
117I mashed up a few screenshots to show off the home page, menu, firmware 102I mashed up a few screenshots to show off the home page, menu, firmware
118update, and notification test screens: 103update, and notification test screens:
119 104
120#+caption: InfiniLink Home 105![InfiniLink
121[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png]] 106Home](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png)
122 107
123Another big feature of InfiniLink is the ability to track pedometer 108Another big feature of InfiniLink is the ability to track pedometer
124steps in a collection of beautiful graphs, with the option to change 109steps in a collection of beautiful graphs, with the option to change
125your step goal and add in manual steps. 110your step goal and add in manual steps.
126 111
127#+caption: InfiniLink Steps 112![InfiniLink
128[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png]] 113Steps](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png)
129 114
130Finally, there are charts to display the battery percentage and heart 115Finally, there are charts to display the battery percentage and heart
131rates over time. This area also comes with an option to clear data. 116rates over time. This area also comes with an option to clear data.
132 117
133#+caption: InfiniLink Charts 118![InfiniLink
134[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png]] 119Charts](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png)
135 120
136** Final Thoughts 121## Final Thoughts
137:PROPERTIES: 122
138:CUSTOM_ID: final-thoughts 123### Pros
139:END: 124
140*** Pros
141:PROPERTIES:
142:CUSTOM_ID: pros
143:END:
144After wearing my watch for a few weeks, I have mostly positive thoughts 125After wearing my watch for a few weeks, I have mostly positive thoughts
145about the watch so far. In the past, I have owned smartwatches by 126about the watch so far. In the past, I have owned smartwatches by
146FitBit, Fossil, Apple, etc. - *but I prefer the PineTime over all of 127FitBit, Fossil, Apple, etc. - **but I prefer the PineTime over all of
147those watches*. 128those watches**.
148 129
149The PineTime strips out all the unnecessary features and performs the 130The PineTime strips out all the unnecessary features and performs the
150functions that it provides effectively and efficiently. 131functions that it provides effectively and efficiently.
@@ -152,14 +133,12 @@ functions that it provides effectively and efficiently.
152The battery life is amazing on this device. By default, the watch seems 133The battery life is amazing on this device. By default, the watch seems
153to last anywhere from a few days to a week before dying. 134to last anywhere from a few days to a week before dying.
154 135
155And of course, it's open source and backed by some of the most dedicated 136And of course, it's open source and backed by some of the most
156enthusiasts and developers I've seen. Watching the Matrix channel, 137dedicated enthusiasts and developers I've seen. Watching the Matrix
157forums, and website have been exciting to see. 138channel, forums, and website have been exciting to see.
139
140### Cons
158 141
159*** Cons
160:PROPERTIES:
161:CUSTOM_ID: cons
162:END:
163If I had to complain about anything, it would simply be the small bugs 142If I had to complain about anything, it would simply be the small bugs
164in some features that can be contributed to the companion apps more than 143in some features that can be contributed to the companion apps more than
165the watch itself. 144the watch itself.
blog/2022-06-01-ditching-cloudflare.org → content/blog/2022-06-01-ditching-cloudflare.md renamed +44 −55
@@ -1,10 +1,11 @@
1#+title: Ditching Cloudflare for Njalla 1+++
2#+date: 2022-06-01 2date = 2022-06-01
3title = "Ditching Cloudflare for Njalla"
4description = "A retrospective on my decision to leave Cloudflare and move to Njalla for domain registration and DNS."
5+++
6
7## Registrar
3 8
4** Registrar
5:PROPERTIES:
6:CUSTOM_ID: registrar
7:END:
8After spending a year or so using Cloudflare for DNS only - no proxying 9After spending a year or so using Cloudflare for DNS only - no proxying
9or applications - I spent the last few months using Cloudflare Tunnels 10or applications - I spent the last few months using Cloudflare Tunnels
10and Cloudflare Access to protect my self-hosted websites and 11and Cloudflare Access to protect my self-hosted websites and
@@ -14,27 +15,22 @@ However, I have never liked using Cloudflare due to their increasingly
14large share of control over web traffic, as well as their business model 15large share of control over web traffic, as well as their business model
15of being a MITM for all of your traffic. 16of being a MITM for all of your traffic.
16 17
17So, as of today, I have switched over to [[https://njal.la][Njalla]] as 18So, as of today, I have switched over to [Njalla](https://njal.la) as my
18my registrar and DNS manager. I was able to easily transfer my domains 19registrar and DNS manager. I was able to easily transfer my domains over
19over rapidly, with only one domain taking more than 15-30 minutes to 20rapidly, with only one domain taking more than 15-30 minutes to
20propagate. 21propagate.
21 22
22+I do still have two domains sitting at Cloudflare for the moment while 23+I do still have two domains sitting at Cloudflare for the moment while
23I decide if they're worth the higher rates (one domain is 30€ and the 24I decide if they're worth the higher rates (one domain is 30€ and the
24other is 45€).+ 25other is 45€).+
25 26
26#+begin_quote 27> **Update (2022.06.03)**: I ended up transferring my final two domains
27*Update (2022.06.03)*: I ended up transferring my final two domains over 28> over to Njalla, clearing my Cloudflare account of personal data, and
28to Njalla, clearing my Cloudflare account of personal data, and deleting 29> deleting the Cloudflare account entirely. *I actually feel relieved to
29the Cloudflare account entirely. /I actually feel relieved to have moved 30> have moved on to a provider I trust.*
30on to a provider I trust./
31 31
32#+end_quote 32## DNS
33 33
34** DNS
35:PROPERTIES:
36:CUSTOM_ID: dns
37:END:
38As noted above, I'm using Njalla exclusively for DNS configurations on 34As noted above, I'm using Njalla exclusively for DNS configurations on
39my domains. 35my domains.
40 36
@@ -46,55 +42,48 @@ back in one-by-one.
46 42
47This would be much simpler if I were able to edit the plain-text format 43This would be much simpler if I were able to edit the plain-text format
48of the DNS configuration. I was able to do that at a past registrar 44of the DNS configuration. I was able to do that at a past registrar
49(perhaps it was [[https://gandi.net/][Gandi.net]]?) and it made life a 45(perhaps it was [Gandi.net](https://gandi.net/)?) and it made life a lot
50lot easier. 46easier.
51 47
52*** Dynamic DNS Updates 48### Dynamic DNS Updates
53:PROPERTIES: 49
54:CUSTOM_ID: dynamic-dns-updates 50I have built an easy Python script to run (or set-up in
55:END: 51`cron` to run automatically) that will check my server's
56I have built an easy Python script to run (or set-up in =cron= to run 52IPv4 and IPv6, compare it to Njalla, and update the DNS records if they
57automatically) that will check my server's IPv4 and IPv6, compare it to 53don't match. You can see the full script and process in my other post:
58Njalla, and update the DNS records if they don't match. You can see the 54[Updating Dynamic DNS with Njalla API](/blog/njalla-dns-api/).
59full script and process in my other post:
60[[/blog/njalla-dns-api/][Updating Dynamic DNS with Njalla API]].
61 55
62I haven't used this other method, but I do know that you can create 56I haven't used this other method, but I do know that you can create
63=Dynamic= DNS records with Njalla that 57`Dynamic` DNS records with Njalla that [work for updating
64[[https://njal.la/docs/ddns/][work for updating dynamic subdomains]]. 58dynamic subdomains](https://njal.la/docs/ddns/).
65 59
66*** Njalla's DNS Tool 60### Njalla's DNS Tool
67:PROPERTIES: 61
68:CUSTOM_ID: njallas-dns-tool 62One neat upside to Njalla is that they have a [DNS lookup
69:END: 63tool](https://check.njal.la/dns/) that provides a lot of great
70One neat upside to Njalla is that they have a 64information for those of you (AKA: me) who hate using the
71[[https://check.njal.la/dns/][DNS lookup tool]] that provides a lot of 65`dig` command.
72great information for those of you (AKA: me) who hate using the =dig=
73command.
74 66
75This was very useful for monitoring a couple of my transferred domains 67This was very useful for monitoring a couple of my transferred domains
76to see when the changes in nameservers, records, and DNSSEC went into 68to see when the changes in nameservers, records, and DNSSEC went into
77effect. 69effect.
78 70
79** Tunnel 71## Tunnel
80:PROPERTIES: 72
81:CUSTOM_ID: tunnel
82:END:
83Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on 73Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on
84Cloudflare's servers) and allowed me to mask the private IP address of 74Cloudflare's servers) and allowed me to mask the private IP address of
85the server hosting my various websites and apps. 75the server hosting my various websites and apps.
86 76
87However, as I was moving away from Cloudflare, I was not able to find a 77However, as I was moving away from Cloudflare, I was not able to find a
88suitable replacement that was both inexpensive and simple. So, I simply 78suitable replacement that was both inexpensive and simple. So, I simply
89went back to hosting [[/blog/set-up-nginx-reverse-proxy/][my own reverse 79went back to hosting [my own reverse proxy with
90proxy with Nginx]]. With the recent additions of Unifi hardware in my 80Nginx](/blog/set-up-nginx-reverse-proxy/). With the recent
91server/network rack, I am much more protected against spam and malicious 81additions of Unifi hardware in my server/network rack, I am much more
92attacks at the network edge than I was before I switched to Cloudflare. 82protected against spam and malicious attacks at the network edge than I
93 83was before I switched to Cloudflare.
94** Access 84
95:PROPERTIES: 85## Access
96:CUSTOM_ID: access 86
97:END:
98Cloudflare Access, another app I used in combination with Cloudflare 87Cloudflare Access, another app I used in combination with Cloudflare
99Tunnel, provided an authentication screen that required you to enter 88Tunnel, provided an authentication screen that required you to enter
100valid credentials before Cloudflare would forward you to the actual 89valid credentials before Cloudflare would forward you to the actual
blog/2022-06-04-njalla-dns-api.org → content/blog/2022-06-04-njalla-dns-api.md renamed +69 −76
@@ -1,14 +1,15 @@
1#+title: Dynamic DNS with Njalla API 1+++
2#+date: 2022-06-04 2date = 2022-02-10
3 3title = "Dynamic DNS with Njalla API"
4** Njalla's API 4description = "Learn how to dynamically update DNS records for changing IPs with Njalla."
5:PROPERTIES: 5+++
6:CUSTOM_ID: njallas-api 6
7:END: 7## Njalla's API
8As noted in my recent post about [[/blog/ditching-cloudflare/][switching 8
9to Njalla from Cloudflare]], I was searching for a way to replace my 9As noted in my recent post about [switching to Njalla from
10very easy-to-use bash script to [[/blog/cloudflare-dns-api/][update 10Cloudflare](/blog/ditching-cloudflare/), I was searching for a
11Cloudflare's DNS via their API]]. 11way to replace my very easy-to-use bash script to [update Cloudflare's
12DNS via their API](/blog/cloudflare-dns-api/).
12 13
13To reiterate what I said in those posts, this is a common necessity for 14To reiterate what I said in those posts, this is a common necessity for
14those of us who have non-static IP addresses that can change at any 15those of us who have non-static IP addresses that can change at any
@@ -21,93 +22,88 @@ update their domain's DNS records if it changes.
21This post explains how to use Python to update Njalla's DNS records 22This post explains how to use Python to update Njalla's DNS records
22whenever a machine's public IP address changes. 23whenever a machine's public IP address changes.
23 24
24*** Creating a Token 25### Creating a Token
25:PROPERTIES: 26
26:CUSTOM_ID: creating-a-token
27:END:
28To use Njalla's API, you will first need to create a token that will be 27To use Njalla's API, you will first need to create a token that will be
29used to authenticate you every time you call the API. Luckily, this is 28used to authenticate you every time you call the API. Luckily, this is
30very easy to do if you have an account with Njalla. 29very easy to do if you have an account with Njalla.
31 30
32Simply go the [[https://njal.la/settings/api/][API Settings]] page and 31Simply go the [API Settings](https://njal.la/settings/api/) page and
33click the =Add Token= button. Next, enter a name for the token and click 32click the `Add Token` button. Next, enter a name for the
34=Add=. 33token and click `Add`.
35 34
36Finally, click the =Manage= button next to your newly created token and 35Finally, click the `Manage` button next to your newly created
37copy the =API Token= field. 36token and copy the `API Token` field.
37
38### Finding the Correct API Request
38 39
39*** Finding the Correct API Request
40:PROPERTIES:
41:CUSTOM_ID: finding-the-correct-api-request
42:END:
43Once you have a token, you're ready to call the Njalla API for any 40Once you have a token, you're ready to call the Njalla API for any
44number of requests. For a full listing of available requests, see the 41number of requests. For a full listing of available requests, see the
45[[https://njal.la/api/][Njalla API Documentation]]. 42[Njalla API Documentation](https://njal.la/api/).
46 43
47For this demo, we are using the =list-records= and =edit-record= 44For this demo, we are using the `list-records` and
48requests. 45`edit-record` requests.
49 46
50The =list-records= request requires the following payload to be sent 47The `list-records` request requires the following payload to
51when calling the API: 48be sent when calling the API:
52 49
53#+begin_src txt 50```txt
54params: { 51params: {
55 domain: string 52 domain: string
56} 53}
57#+end_src 54```
58 55
59The =edit-record= request requires the following payload to be sent when 56The `edit-record` request requires the following payload to
60calling the API: 57be sent when calling the API:
61 58
62#+begin_src txt 59```txt
63params: { 60params: {
64 domain: string 61 domain: string
65 id: int 62 id: int
66 content: string 63 content: string
67} 64}
68#+end_src 65```
66
67## Server Set-Up
69 68
70** Server Set-Up
71:PROPERTIES:
72:CUSTOM_ID: server-set-up
73:END:
74To create this script, we will be using Python. By default, I use Python 69To create this script, we will be using Python. By default, I use Python
753 on my servers, so please note that I did not test this in Python 2, 703 on my servers, so please note that I did not test this in Python 2,
76and I do not know if Python 2 will work for this. 71and I do not know if Python 2 will work for this.
77 72
78*** Creating the Script 73### Creating the Script
79:PROPERTIES: 74
80:CUSTOM_ID: creating-the-script
81:END:
82First, find a suitable place to create your script. Personally, I just 75First, find a suitable place to create your script. Personally, I just
83create a directory called =ddns= in my home directory: 76create a directory called `ddns` in my home directory:
84 77
85#+begin_src sh 78```sh
86mkdir ~/ddns 79mkdir ~/ddns
87#+end_src 80```
88 81
89Next, create a Python script file: 82Next, create a Python script file:
90 83
91#+begin_src sh 84```sh
92nano ~/ddns/ddns.py 85nano ~/ddns/ddns.py
93#+end_src 86```
94 87
95The following code snippet is quite long, so I won't go into depth on 88The following code snippet is quite long, so I won't go into depth on
96each part. However, I suggest you read through the entire script before 89each part. However, I suggest you read through the entire script before
97running it; it is quite simple and contains comments to help explain 90running it; it is quite simple and contains comments to help explain
98each code block. 91each code block.
99 92
100:warning: *Note*: You will need to update the following variables for 93:warning: **Note**: You will need to update the following variables for
101this to work: 94this to work:
102 95
103- =token=: This is the Njalla API token you created earlier. 96- `token`: This is the Njalla API token you created
104- =user_domain=: This is the top-level domain you want to modify. 97 earlier.
105- =include_subdomains=: Set this to =True= if you also want to modify 98- `user_domain`: This is the top-level domain you want to
106 subdomains found under the TLD. 99 modify.
107- =subdomains=: If =include_subdomains= = =True=, you can include your 100- `include_subdomains`: Set this to `True` if
108 list of subdomains to be modified here. 101 you also want to modify subdomains found under the TLD.
109 102- `subdomains`: If `include_subdomains` =
110#+begin_src python 103 `True`, you can include your list of subdomains to be
104 modified here.
105
106```python
111#!/usr/bin/python 107#!/usr/bin/python
112# -*- coding: utf-8 -*- 108# -*- coding: utf-8 -*-
113# Import Python modules 109# Import Python modules
@@ -173,7 +169,7 @@ for record in data['records']:
173 ) 169 )
174 else: 170 else:
175 print('IPv4 of', ipv4, 171 print('IPv4 of', ipv4,
176 'does not match Njalla\'s value of', 172 'does not match Njalla's value of',
177 record['content'], '. Updating...') 173 record['content'], '. Updating...')
178 update_record(user_domain, record['id'], ipv4) 174 update_record(user_domain, record['id'], ipv4)
179 elif record['type'] == 'AAAA': 175 elif record['type'] == 'AAAA':
@@ -183,36 +179,33 @@ for record in data['records']:
183 ) 179 )
184 else: 180 else:
185 print('IPv6 of', ipv6, 181 print('IPv6 of', ipv6,
186 'does not match Njalla\'s value of', 182 'does not match Njalla's value of',
187 record['content'], '. Updating...') 183 record['content'], '. Updating...')
188 update_record(user_domain, record['id'], ipv6) 184 update_record(user_domain, record['id'], ipv6)
189#+end_src 185```
186
187### Running the Script
190 188
191*** Running the Script
192:PROPERTIES:
193:CUSTOM_ID: running-the-script
194:END:
195Once you've created the script and are ready to test it, run the 189Once you've created the script and are ready to test it, run the
196following command: 190following command:
197 191
198#+begin_src sh 192```sh
199python3 ~/ddns/ddns.py 193python3 ~/ddns/ddns.py
200#+end_src 194```
195
196### Setting the Script to Run Automatically
201 197
202*** Setting the Script to Run Automatically 198To make sure the scripts run automatically, add it to the
203:PROPERTIES: 199`cron` file so that it will run on a schedule. To do this,
204:CUSTOM_ID: setting-the-script-to-run-automatically 200open the `cron` file:
205:END:
206To make sure the scripts run automatically, add it to the =cron= file so
207that it will run on a schedule. To do this, open the =cron= file:
208 201
209#+begin_src sh 202```sh
210crontab -e 203crontab -e
211#+end_src 204```
212 205
213In the cron file, paste the following at the bottom of the editor in 206In the cron file, paste the following at the bottom of the editor in
214order to check the IP every five minutes: 207order to check the IP every five minutes:
215 208
216#+begin_src sh 209```sh
217*/5 * * * * python3 /home/<your_username>/ddns/ddns.py 210*/5 * * * * python3 /home/<your_username>/ddns/ddns.py
218#+end_src 211```
blog/2022-06-07-freshrss.org → content/blog/2022-06-07-freshrss.md renamed +116 −125
@@ -1,10 +1,11 @@
1#+title: Self-Hosting FreshRSS 1+++
2#+date: 2022-06-07 2date = 2022-06-07
3title = "Self-Hosting FreshRSS"
4description = "A guide to self-hosting the FreshRSS application on your own server."
5+++
6
7## Why RSS?
3 8
4** Why RSS?
5:PROPERTIES:
6:CUSTOM_ID: why-rss
7:END:
8After noticing that I have collected 50+ blogs as bookmarks, I decided 9After noticing that I have collected 50+ blogs as bookmarks, I decided
9to migrate back to using RSS feeds to stay up-to-date with my favorite 10to migrate back to using RSS feeds to stay up-to-date with my favorite
10websites. Using RSS allows me to read all of these posts in a single app 11websites. Using RSS allows me to read all of these posts in a single app
@@ -15,61 +16,56 @@ However, I ran into one issue: syncing subscriptions and read/unread
15posts across devices. Since I want to be able to easily read on both 16posts across devices. Since I want to be able to easily read on both
16mobile and desktop, I decided to look for a self-hosted RSS solution. 17mobile and desktop, I decided to look for a self-hosted RSS solution.
17 18
18Thus, I found [[https://www.freshrss.org/][FreshRSS]] and was able to 19Thus, I found [FreshRSS](https://www.freshrss.org/) and was able to
19successfully install it on my server in about 30 minutes. 20successfully install it on my server in about 30 minutes.
20 21
21** Documentation 22## Documentation
22:PROPERTIES: 23
23:CUSTOM_ID: documentation 24While it's certainly not robust, the [FreshRSS
24:END: 25documentation](https://freshrss.github.io/FreshRSS/) is helpful for
25While it's certainly not robust, the 26figuring out basic information about the service.
26[[https://freshrss.github.io/FreshRSS/][FreshRSS documentation]] is
27helpful for figuring out basic information about the service.
28 27
29However, I wanted to install this service as a Docker container and 28However, I wanted to install this service as a Docker container and
30stumbled across the 29stumbled across the [Docker
31[[https://github.com/FreshRSS/FreshRSS/tree/edge/Docker][Docker README]] 30README](https://github.com/FreshRSS/FreshRSS/tree/edge/Docker) within
32within the GitHub repository. 31the GitHub repository.
33 32
34This README was the documentation I actually needed. However, as you'll 33This README was the documentation I actually needed. However, as you'll
35see below, I still had to manually edit one file (=config.php=) to 34see below, I still had to manually edit one file
36access the API externally via my RSS apps. 35(`config.php`) to access the API externally via my RSS apps.
37 36
38** Installation 37## Installation
39:PROPERTIES: 38
40:CUSTOM_ID: installation 39### DNS
41:END: 40
42*** DNS
43:PROPERTIES:
44:CUSTOM_ID: dns
45:END:
46The first step, as required by any external web service, was assigning a 41The first step, as required by any external web service, was assigning a
47domain name to use. I chose to use a subdomain, like =rss.example.com=. 42domain name to use. I chose to use a subdomain, like
43`rss.example.com`.
48 44
49To assign this, I created an =A= record in my DNS settings with the IPv4 45To assign this, I created an `A` record in my DNS settings
50address of the server and an =AAAA= record with the IPv6 address of the 46with the IPv4 address of the server and an `AAAA` record with
51server. Note: assigning an IPv6 (=AAAA=) record is optional, but I like 47the IPv6 address of the server. Note: assigning an IPv6
52to enable IPV6 for my services. 48(`AAAA`) record is optional, but I like to enable IPV6 for my
49services.
53 50
54#+begin_src config 51```config
55rss.example.com A xxx.xxx.xxx.xxx 52rss.example.com A xxx.xxx.xxx.xxx
56rss.example.com AAAA xxxx:xxxx: ... :xxxx 53rss.example.com AAAA xxxx:xxxx: ... :xxxx
57#+end_src 54```
58 55
59*** Docker 56### Docker
60:PROPERTIES: 57
61:CUSTOM_ID: docker 58I initially tried to set up a `docker-compose.yml` file with
62:END: 59a `.env` file because I prefer to have a file I can look back
63I initially tried to set up a =docker-compose.yml= file with a =.env= 60at later to see how I initially started the container, but it simply
64file because I prefer to have a file I can look back at later to see how 61wouldn't work for me. I'm not sure why, but I assume I wasn't telling
65I initially started the container, but it simply wouldn't work for me. 62`docker-compose` where the `.env` file was.
66I'm not sure why, but I assume I wasn't telling =docker-compose= where 63
67the =.env= file was. 64Regardless, I chose to simply run the service with
68 65`docker run`. See the following command for my
69Regardless, I chose to simply run the service with =docker run=. See the 66`docker run` configuration:
70following command for my =docker run= configuration: 67
71 68```sh
72#+begin_src sh
73sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ 69sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
74 -p 8080:80 \ 70 -p 8080:80 \
75 -e TZ=America/Chicago \ 71 -e TZ=America/Chicago \
@@ -78,46 +74,44 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
78 -v freshrss_extensions:/var/www/FreshRSS/extensions \ 74 -v freshrss_extensions:/var/www/FreshRSS/extensions \
79 --name freshrss \ 75 --name freshrss \
80 freshrss/freshrss 76 freshrss/freshrss
81#+end_src 77```
82 78
83This started the container successfully and allowed me to visit the 79This started the container successfully and allowed me to visit the
84FreshRSS instance at =localhost:8080=. 80FreshRSS instance at `localhost:8080`.
81
82### Fresh RSS Set-Up
85 83
86*** Fresh RSS Set-Up 84I **HIGHLY** suggest that you set up your user account prior to exposing
87:PROPERTIES:
88:CUSTOM_ID: fresh-rss-set-up
89:END:
90I *HIGHLY* suggest that you set up your user account prior to exposing
91this service to the public. It's unlikely that someone is trying to 85this service to the public. It's unlikely that someone is trying to
92access the exact domain or IP/port you're assigning here, but as soon as 86access the exact domain or IP/port you're assigning here, but as soon
93you expose this service, the first person to open the URL will be able 87as you expose this service, the first person to open the URL will be
94to create the admin user. 88able to create the admin user.
95 89
96In order to set up your FreshRSS service, open the =localhost:8080= URL 90In order to set up your FreshRSS service, open the
97in your browser (you may need to use a local IP instead of =localhost= 91`localhost:8080` URL in your browser (you may need to use a
98if you're accessing the page from a different machine on the network - 92local IP instead of `localhost` if you're accessing the page
99e.g., =192.168.1.20:8080=). 93from a different machine on the network - e.g.,
94`192.168.1.20:8080`).
100 95
101Once the page loads, set up your default user with a strong username and 96Once the page loads, set up your default user with a strong username and
102password. You may also choose to configure other settings prior to 97password. You may also choose to configure other settings prior to
103exposing this service. 98exposing this service.
104 99
105*** Nginx Reverse-Proxy 100### Nginx Reverse-Proxy
106:PROPERTIES: 101
107:CUSTOM_ID: nginx-reverse-proxy
108:END:
109In order to access this service outside my home, I needed to set up a 102In order to access this service outside my home, I needed to set up a
110reverse-proxy to connect =localhost:8080= to =rss.example.com=. 103reverse-proxy to connect `localhost:8080` to
104`rss.example.com`.
111 105
112First, I created a new Nginx configuration file: 106First, I created a new Nginx configuration file:
113 107
114#+begin_src sh 108```sh
115sudo nano /etc/nginx/sites-available/rss.example.com 109sudo nano /etc/nginx/sites-available/rss.example.com
116#+end_src 110```
117 111
118Within the config file, I pasted the following code: 112Within the config file, I pasted the following code:
119 113
120#+begin_src config 114```config
121upstream freshrss { 115upstream freshrss {
122 server 127.0.0.1:8080; 116 server 127.0.0.1:8080;
123 keepalive 64; 117 keepalive 64;
@@ -146,91 +140,87 @@ server {
146 proxy_pass_header Authorization; 140 proxy_pass_header Authorization;
147 } 141 }
148} 142}
149#+end_src 143```
150 144
151Finally, restart Nginx and you will be able to access your service via 145Finally, restart Nginx and you will be able to access your service via
152HTTP: 146HTTP:
153 147
154#+begin_src sh 148```sh
155sudo systemctl restart nginx.service 149sudo systemctl restart nginx.service
156#+end_src 150```
151
152### HTTPS
157 153
158*** HTTPS
159:PROPERTIES:
160:CUSTOM_ID: https
161:END:
162However, I don't want to access my RSS feeds via HTTP. I want it 154However, I don't want to access my RSS feeds via HTTP. I want it
163available only via HTTPS. In order to do this, I ran the 155available only via HTTPS. In order to do this, I ran the
164[[https://certbot.eff.org/][certbot]] program to generate SSL 156[certbot](https://certbot.eff.org/) program to generate SSL certificates
165certificates for me: 157for me:
166 158
167#+begin_src sh 159```sh
168sudo certbot --nginx 160sudo certbot --nginx
169#+end_src 161```
170 162
171This process will automatically generate an SSL certificate for you and 163This process will automatically generate an SSL certificate for you and
172modify the Nginx configuration file to include a redirect from HTTP to 164modify the Nginx configuration file to include a redirect from HTTP to
173HTTPS. 165HTTPS.
174 166
175** Post-Installation Fixes 167## Post-Installation Fixes
176:PROPERTIES: 168
177:CUSTOM_ID: post-installation-fixes
178:END:
179At this point, we have a functional FreshRSS website, available from 169At this point, we have a functional FreshRSS website, available from
180anywhere and secured with HTTPS. However, attempting to connect this 170anywhere and secured with HTTPS. However, attempting to connect this
181service to an RSS app resulted in many errors regarding unavailable URLs 171service to an RSS app resulted in many errors regarding unavailable URLs
182and incorrect credentials. 172and incorrect credentials.
183 173
184*** API Set-Up 174### API Set-Up
185:PROPERTIES: 175
186:CUSTOM_ID: api-set-up 176First, you need to open your user profile in FreshRSS
187:END: 177(`Settings` > `Profile`) and set an API password
188First, you need to open your user profile in FreshRSS (=Settings= > 178in the field at the bottom. This is the password you will need to
189=Profile=) and set an API password in the field at the bottom. This is 179provide to your RSS apps.
190the password you will need to provide to your RSS apps.
191 180
192Once that is set and saved, click the link below the API password field 181Once that is set and saved, click the link below the API password field
193to open the API check tool. It should look something like 182to open the API check tool. It should look something like
194=https://localhost:8080/api/= or =https://rss.example.com/api/=. 183`https://localhost:8080/api/` or
184`https://rss.example.com/api/`.
195 185
196Within this page, you /should/ see your correct external URL and "PASS" 186Within this page, you *should* see your correct external URL and
197at the bottom of each API type. This would mean everything is set up 187"PASS" at the bottom of each API type. This would mean everything is
198correctly, and you can now move on and login to any RSS apps that 188set up correctly, and you can now move on and login to any RSS apps that
199support self-hosted options. 189support self-hosted options.
200 190
201In my case, the URL showed an internal URL and I had a warning that the 191In my case, the URL showed an internal URL and I had a warning that the
202=base_url= variable may be misconfigured. If this is the case, see the 192`base_url` variable may be misconfigured. If this is the
203next section for a fix. 193case, see the next section for a fix.
194
195### Base URL Fix
204 196
205*** Base URL Fix 197In order to fix the `base_url` for the API, I opened up my
206:PROPERTIES: 198docker container with the following command:
207:CUSTOM_ID: base-url-fix
208:END:
209In order to fix the =base_url= for the API, I opened up my docker
210container with the following command:
211 199
212#+begin_src sh 200```sh
213sudo docker exec -it freshrss bash 201sudo docker exec -it freshrss bash
214#+end_src 202```
215 203
216Within this container, update the packages and install an editor: 204Within this container, update the packages and install an editor:
217 205
218#+begin_src sh 206```sh
219apt-get update 207apt-get update
220apt-get install nano 208apt-get install nano
221#+end_src 209```
222 210
223Finally, open up =config.php= in the =data= directory: 211Finally, open up `config.php` in the `data`
212directory:
224 213
225#+begin_src sh 214```sh
226nano data/config.php 215nano data/config.php
227#+end_src 216```
228 217
229Within =config.php=, you will need to update the =base_url= variable and 218Within `config.php`, you will need to update the
230update it to match your external URL. In my case, I simply commented-out 219`base_url` variable and update it to match your external URL.
231the incorrect URL with =//= and added the correct one on a new line: 220In my case, I simply commented-out the incorrect URL with
221`//` and added the correct one on a new line:
232 222
233#+begin_src php 223```php
234<?php 224<?php
235 return array ( 225 return array (
236 ... 226 ...
@@ -239,25 +229,26 @@ the incorrect URL with =//= and added the correct one on a new line:
239 ... 229 ...
240 ) 230 )
241> 231>
242#+end_src 232```
243 233
244You can now exit the file with =Ctrl + x=, press =y= to save the file, 234You can now exit the file with `Ctrl + x`, press
245and then click =Enter= to keep the same file name. 235`y` to save the file, and then click `Enter` to
236keep the same file name.
246 237
247Finally, just exit out of the docker container: 238Finally, just exit out of the docker container:
248 239
249#+begin_src sh 240```sh
250exit 241exit
251#+end_src 242```
252 243
253Next, just restart the container: 244Next, just restart the container:
254 245
255#+begin_src sh 246```sh
256sudo docker restart freshrss 247sudo docker restart freshrss
257#+end_src 248```
258 249
259Voilà! Your API check should now "PASS" and you should be able to use 250Voilà! Your API check should now "PASS" and you should be able to use
260one of the API URLs in your RSS apps. 251one of the API URLs in your RSS apps.
261 252
262In my case, I use [[https://netnewswire.com][NetNewsWire]] on my desktop 253In my case, I use [NetNewsWire](https://netnewswire.com) on my desktop
263and phone. 254and phone.
blog/2022-06-16-terminal-lifestyle.org → content/blog/2022-06-16-terminal-lifestyle.md renamed +88 −103
@@ -1,24 +1,25 @@
1#+title: A Terminal Lifestyle 1+++
2#+date: 2022-06-16 2date = 2022-06-16
3title = "A Terminal Lifestyle"
4description = "Explaining how I live my digital life mostly in the console/terminal."
5+++
6
7## Text-Based Simplicity
3 8
4** Text-Based Simplicity
5:PROPERTIES:
6:CUSTOM_ID: text-based-simplicity
7:END:
8I've detailed my views on web-based minimalism and related topics in 9I've detailed my views on web-based minimalism and related topics in
9other posts throughout the years; e.g., JavaScript/CSS bloat slowing 10other posts throughout the years; e.g., JavaScript/CSS bloat slowing
10down websites that are essentially a text document. However, I have 11down websites that are essentially a text document. However, I have
11never really expanded beyond talking about the web and describing how I 12never really expanded beyond talking about the web and describing how I
12focus on minimizing distractions in other digital environments. 13focus on minimizing distractions in other digital environments.
13 14
14This post is going to set the baseline for how I /try/ to live my 15This post is going to set the baseline for how I *try* to live my
15digital life. It does not necessarily get into my physical life, which 16digital life. It does not necessarily get into my physical life, which
16is often harder to control and contain all the noise in our modern 17is often harder to control and contain all the noise in our modern
17world. 18world.
18 19
19While there are new things to do every day in our digital world, I find 20While there are new things to do every day in our digital world, I find
20that keeping a core set of values and interests can ground you and keep 21that keeping a core set of values and interests can ground you and keep
21you mindful of /why/ you are participating in the digital world. For 22you mindful of *why* you are participating in the digital world. For
22example, if - at your core - you have no interest in what strangers 23example, if - at your core - you have no interest in what strangers
23think about random topics, it would be unwise to start participating in 24think about random topics, it would be unwise to start participating in
24social media. However, I am someone who has been dragged in by effective 25social media. However, I am someone who has been dragged in by effective
@@ -29,14 +30,12 @@ I won't dive much further into explaining the philosophy of all this,
29but I will link a few helpful articles that may pique your interest if 30but I will link a few helpful articles that may pique your interest if
30you're in search of more meaningful experiences: 31you're in search of more meaningful experiences:
31 32
32- [[https://en.wikipedia.org/wiki/Mindfulness][Mindfulness]] 33- [Mindfulness](https://en.wikipedia.org/wiki/Mindfulness)
33- [[https://en.wikipedia.org/wiki/Minimalism][Minimalism]] 34- [Minimalism](https://en.wikipedia.org/wiki/Minimalism)
34- [[https://en.wikipedia.org/wiki/Stoicism][Stoicism]] 35- [Stoicism](https://en.wikipedia.org/wiki/Stoicism)
36
37## Living Life in the Terminal
35 38
36** Living Life in the Terminal
37:PROPERTIES:
38:CUSTOM_ID: living-life-in-the-terminal
39:END:
40My personal approach to reducing digital distractions and increasing my 39My personal approach to reducing digital distractions and increasing my
41focus on the task at hand is to use a terminal for as much as I possibly 40focus on the task at hand is to use a terminal for as much as I possibly
42can. 41can.
@@ -44,12 +43,12 @@ can.
44Most days, this means that I have a few tabs open constantly in my 43Most days, this means that I have a few tabs open constantly in my
45terminal: 44terminal:
46 45
471. A web browser 461. A web browser
482. A chat client 472. A chat client
493. An email client 483. An email client
504. An RSS feed reader 494. An RSS feed reader
515. A local shell for navigating my computer's files 505. A local shell for navigating my computer's files
526. A remote shell for managing servers and other machines 516. A remote shell for managing servers and other machines
53 52
54Beyond this, I rarely open other tabs or GUI applications, unless 53Beyond this, I rarely open other tabs or GUI applications, unless
55absolutely necessary. If you look, you may be surprised what can be 54absolutely necessary. If you look, you may be surprised what can be
@@ -58,61 +57,56 @@ accomplished in the terminal.
58For example, I have moved my music and entertainment downloads to the 57For example, I have moved my music and entertainment downloads to the
59terminal, along with my device VPN connections. I am exploring options 58terminal, along with my device VPN connections. I am exploring options
60for moving my RSS subscriptions to something like 59for moving my RSS subscriptions to something like
61[[https://newsboat.org/][Newsboat]], so that I can read my daily 60[Newsboat](https://newsboat.org/), so that I can read my daily articles
62articles without all the fuss. 61without all the fuss.
63 62
64Now that we have some examples out of the way, let's dive into the 63Now that we have some examples out of the way, let's dive into the
65specifics. 64specifics.
66 65
67*** Browsing the Web 66### Browsing the Web
68:PROPERTIES: 67
69:CUSTOM_ID: browsing-the-web 68I'm going to start off with a hard topic for those who prefer to live
70:END: 69in the terminal: web browsing. This task is made hard mostly by websites
71I'm going to start off with a hard topic for those who prefer to live in
72the terminal: web browsing. This task is made hard mostly by websites
73and web apps that require JavaScript to run. The other difficult part is 70and web apps that require JavaScript to run. The other difficult part is
74that if you're using a text-based browser, that means images won't load 71that if you're using a text-based browser, that means images won't
75(hopefully that's obvious). 72load (hopefully that's obvious).
76 73
77I am using [[https://lynx.invisible-island.net][Lynx]], a text-based 74I am using [Lynx](https://lynx.invisible-island.net), a text-based
78browser that runs quickly and easily in the terminal. Lynx allows me to 75browser that runs quickly and easily in the terminal. Lynx allows me to
79browser most websites by simply typing =g= and then typing in the URL I 76browser most websites by simply typing `g` and then typing in
80want. 77the URL I want.
81 78
82#+caption: Lynx 79![Lynx](https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png)
83[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png]]
84 80
85If you need a search engine while in Lynx, I recommend 81If you need a search engine while in Lynx, I recommend [DuckDuckGo
86[[https://lite.duckduckgo.com/lite/][DuckDuckGo (Lite)]], which allows 82(Lite)](https://lite.duckduckgo.com/lite/), which allows you to search
87you to search the web using their text-only interface. 83the web using their text-only interface.
88 84
89#+caption: DuckDuckGo !Lite 85![DuckDuckGo
90[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png]] 86!Lite](https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png)
91 87
92Eventually, you will run into websites that don't work (or are just too 88Eventually, you will run into websites that don't work (or are just too
93ugly and messy) in a text-only mode, and you'll be forced to switch over 89ugly and messy) in a text-only mode, and you'll be forced to switch
94to a GUI browser to look at that site. Personally, I don't mind this as 90over to a GUI browser to look at that site. Personally, I don't mind
95it doesn't happen as often as I thought it would. 91this as it doesn't happen as often as I thought it would.
96 92
97The only time I need to do this is when I want to browse an 93The only time I need to do this is when I want to browse an
98image/video-focused webpage or if I need to log in to a site, and it 94image/video-focused webpage or if I need to log in to a site, and it
99doesn't support a text-only login page. For example, I am able to easily 95doesn't support a text-only login page. For example, I am able to
100log in to [[https://sr.ht][Sourcehut]] in lynx. 96easily log in to [Sourcehut](https://sr.ht) in lynx.
97
98### Chatting with Friends
101 99
102*** Chatting with Friends
103:PROPERTIES:
104:CUSTOM_ID: chatting-with-friends
105:END:
106After web browsing activities, my main form of terminal communication is 100After web browsing activities, my main form of terminal communication is
107Matrix. I use the [[https://docs.mau.fi/gomuks/][gomuks]] client 101Matrix. I use the [gomuks](https://docs.mau.fi/gomuks/) client
108currently. 102currently.
109 103
110This was incredibly easy to install on macOS (but I will need to see if 104This was incredibly easy to install on macOS (but I will need to see if
111it'll be just as easy on Linux when my new laptop arrives): 105it'll be just as easy on Linux when my new laptop arrives):
112 106
113#+begin_src sh 107```sh
114brew install gomuks 108brew install gomuks
115#+end_src 109```
116 110
117Once you launch gomuks, it will sync and require your username and 111Once you launch gomuks, it will sync and require your username and
118password to login. After doing so, the only problem I ran into was 112password to login. After doing so, the only problem I ran into was
@@ -120,111 +114,102 @@ verifying my gomuks client so that I could participate in rooms with
120E2EE. 114E2EE.
121 115
122Finally, I was able to verify the session by opening the Element desktop 116Finally, I was able to verify the session by opening the Element desktop
123app (I assume you can do this in the browser and mobile app too, but I'm 117app (I assume you can do this in the browser and mobile app too, but
124not sure) and manually verifying myself with this process: 118I'm not sure) and manually verifying myself with this process:
125 119
1261. Open the Element desktop app 1201. Open the Element desktop app
1272. Open a room I was a member of 1212. Open a room I was a member of
1283. Open the =Room Info= pane 1223. Open the `Room Info` pane
1294. Open the =People= menu and search for myself 1234. Open the `People` menu and search for myself
1305. Click on my profile name 1245. Click on my profile name
1316. Click on the session link under the =Security= section and follow the 1256. Click on the session link under the `Security` section
132 prompts to manually verify the session 126 and follow the prompts to manually verify the session
133 127
134Overall, I like gomuks and am able to enjoy all the features I was using 128Overall, I like gomuks and am able to enjoy all the features I was using
135in Element. The only hiccup I have occurred is manually downloading 129in Element. The only hiccup I have occurred is manually downloading
136images to view them, which can be annoying. 130images to view them, which can be annoying.
137 131
138#+caption: gomuks 132![gomuks](https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png)
139[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png]] 133
134### Email
140 135
141*** Email
142:PROPERTIES:
143:CUSTOM_ID: email
144:END:
145Moving email to the terminal has been the hardest of the tasks for me. 136Moving email to the terminal has been the hardest of the tasks for me.
146Unlike web browsing, where I can simply decide to not look at a website 137Unlike web browsing, where I can simply decide to not look at a website
147that does not work in the terminal, I cannot simply ignore emails sent 138that does not work in the terminal, I cannot simply ignore emails sent
148to me. 139to me.
149 140
150Personally, I am experimenting with [[https://neomutt.org/][neomutt]] as 141Personally, I am experimenting with [neomutt](https://neomutt.org/) as a
151a potential email client. 142potential email client.
152 143
153However, this requires a *TON* of configuration and tweaking to get 144However, this requires a **TON** of configuration and tweaking to get
154right. Even when I was able to set up neomutt, configure my email 145right. Even when I was able to set up neomutt, configure my email
155account, and customize a few personal preferences, a lot of emails still 146account, and customize a few personal preferences, a lot of emails still
156do not display correctly (mostly due to HTML and images). 147do not display correctly (mostly due to HTML and images).
157 148
158I won't get into the details of configuring =neomutt=; I mostly followed 149I won't get into the details of configuring `neomutt`; I
159this blog post: 150mostly followed this blog post: [Email in the Terminal: Configuring
160[[https://gideonwolfe.com/posts/workflow/neomutt/intro/][Email in the 151Neomutt](https://gideonwolfe.com/posts/workflow/neomutt/intro/).
161Terminal: Configuring Neomutt]].
162 152
163Finally, I have yet to figure out how to connect my GPG keys to 153Finally, I have yet to figure out how to connect my GPG keys to
164=neomutt=, but that's a problem for another day. 154`neomutt`, but that's a problem for another day.
155
156### RSS Feed Reader
165 157
166*** RSS Feed Reader 158I have just started using [Newsboat](https://newsboat.org/) to read
167:PROPERTIES:
168:CUSTOM_ID: rss-feed-reader
169:END:
170I have just started using [[https://newsboat.org/][Newsboat]] to read
171articles in my terminal and have found quick success with it. 159articles in my terminal and have found quick success with it.
172 160
173I'll show you a quick screenshot first: 161I'll show you a quick screenshot first:
174 162
175#+caption: Newsboat 163![Newsboat](https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png)
176[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png]]
177 164
178The configuration was super easy for this app; I simply installed the 165The configuration was super easy for this app; I simply installed the
179app, created a file for URLs, and imported my OPML subscriptions that I 166app, created a file for URLs, and imported my OPML subscriptions that I
180had exported out of my old feed reader: 167had exported out of my old feed reader:
181 168
182#+begin_src sh 169```sh
183brew install newsboat 170brew install newsboat
184#+end_src 171```
185 172
186#+begin_src sh 173```sh
187touch ~/.newsboat/urls 174touch ~/.newsboat/urls
188#+end_src 175```
189 176
190#+begin_src sh 177```sh
191newsboat -i=my_subscriptions.opml 178newsboat -i=my_subscriptions.opml
192#+end_src 179```
180
181### Writing & Programming
193 182
194*** Writing & Programming
195:PROPERTIES:
196:CUSTOM_ID: writing-programming
197:END:
198Unfortunately, the weak link in my terminal-based environment right now 183Unfortunately, the weak link in my terminal-based environment right now
199is my grasp of the possibilities of editing files within a shell. 184is my grasp of the possibilities of editing files within a shell.
200 185
201I am used to the easy extensions found in VSCodium and Kate, so I am 186I am used to the easy extensions found in VSCodium and Kate, so I am
202slowly learning how to mold the default editing tools to my needs. 187slowly learning how to mold the default editing tools to my needs.
203Currently, this means I am using =nano= with the following 188Currently, this means I am using `nano` with the following
204configuration: 189configuration:
205 190
206#+begin_src config 191```config
207set breaklonglines 192set breaklonglines
208set autoindent 193set autoindent
209set linenumbers 194set linenumbers
210set tabstospaces 195set tabstospaces
211set tabsize 2 196set tabsize 2
212set fill 80 197set fill 80
213#+end_src 198```
214 199
215This configuration allows nano to automatically hard-wrap lines at 80 200This configuration allows nano to automatically hard-wrap lines at 80
216characters, autoindent the wrapped lines (if the previous line was 201characters, autoindent the wrapped lines (if the previous line was
217indented), use 2 spaces per tab, and display line numbers within each 202indented), use 2 spaces per tab, and display line numbers within each
218file I open. 203file I open.
219 204
220I am currently looking to see if =vim= or =emacs= would be more useful 205I am currently looking to see if `vim` or `emacs`
221for my current needs, but I'm not in any rush, so I don't expect to find 206would be more useful for my current needs, but I'm not in any rush, so
222an answer anytime soon. 207I don't expect to find an answer anytime soon.
223 208
224With my current life demands, I am not programming at the moment and 209With my current life demands, I am not programming at the moment and
225have not explored the best terminal set-up for programming. However, I 210have not explored the best terminal set-up for programming. However, I
226have seen many peers find success configuring =vim= and =emacs=, so 211have seen many peers find success configuring `vim` and
227that's where I will start when I pick my projects back up. 212`emacs`, so that's where I will start when I pick my
213projects back up.
228 214
229#+caption: nano 215![nano](https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png)
230[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png]]
blog/2022-06-22-daily-poetry.org → content/blog/2022-06-22-daily-poetry.md renamed +79 −99
@@ -1,18 +1,17 @@
1#+title: Daily Plaintext Poetry via Email 1+++
2#+date: 2022-06-22 2date = 2022-06-22
3title = "Daily Plaintext Poetry via Email"
4description = "A small project to automatically deliver poetry to your inbox daily."
5+++
6
7## Source Code
3 8
4** Source Code
5:PROPERTIES:
6:CUSTOM_ID: source-code
7:END:
8I don't want to bury the lede here, so if you'd like to see the full 9I don't want to bury the lede here, so if you'd like to see the full
9source code I use to email myself plaintext poems daily, visit the 10source code I use to email myself plaintext poems daily, visit the
10repository: [[https://git.sr.ht/~cmc/daily-poem/][daily-poem]]. 11repository: [daily-poem](https://git.sr.ht/~cmc/daily-poem/).
12
13## My Daily Dose of Poetry
11 14
12** My Daily Dose of Poetry
13:PROPERTIES:
14:CUSTOM_ID: my-daily-dose-of-poetry
15:END:
16Most of my programming projects are small, random projects that are made 15Most of my programming projects are small, random projects that are made
17strictly to fix some small problem I have or enhance my quality of life. 16strictly to fix some small problem I have or enhance my quality of life.
18 17
@@ -26,55 +25,47 @@ each morning, as I know I would simply give up and stop reading daily.
26Thus, I found a way to deliver poetry to myself in plain-text format, on 25Thus, I found a way to deliver poetry to myself in plain-text format, on
27a daily basis, and scheduled to deliver automatically. 26a daily basis, and scheduled to deliver automatically.
28 27
29** Prerequisites 28## Prerequisites
30:PROPERTIES: 29
31:CUSTOM_ID: prerequisites
32:END:
33This solution uses Python and email, so the following process requires 30This solution uses Python and email, so the following process requires
34the following to be installed: 31the following to be installed:
35 32
361. An SMTP server, which can be as easy as installing =mailutils= if 331. An SMTP server, which can be as easy as installing
37 you're on a Debian-based distro. 34 `mailutils` if you're on a Debian-based distro.
382. Python (& pip!) 352. Python (& pip!)
393. The following Python packages: =email=, =smtplib=, =json=, and 363. The following Python packages: `email`,
40 =requests= 37 `smtplib`, `json`, and `requests`
38
39## Breaking Down the Logic
41 40
42** Breaking Down the Logic
43:PROPERTIES:
44:CUSTOM_ID: breaking-down-the-logic
45:END:
46I want to break down the logic for this program, as it's quite simple 41I want to break down the logic for this program, as it's quite simple
47and informational. 42and informational.
48 43
49*** Required Packages 44### Required Packages
50:PROPERTIES: 45
51:CUSTOM_ID: required-packages
52:END:
53This program starts with a simple import of the required packages, so I 46This program starts with a simple import of the required packages, so I
54wanted to explain why each package is used: 47wanted to explain why each package is used:
55 48
56#+begin_src py 49```python
57from email.mime.text import MIMEText # Required for translating MIMEText 50from email.mime.text import MIMEText # Required for translating MIMEText
58import smtplib # Required to process the SMTP mail delivery 51import smtplib # Required to process the SMTP mail delivery
59import json # Required to parse the poetry API results 52import json # Required to parse the poetry API results
60import requests # Required to send out a request to the API 53import requests # Required to send out a request to the API
61#+end_src 54```
55
56### Sending the API Request
62 57
63*** Sending the API Request
64:PROPERTIES:
65:CUSTOM_ID: sending-the-api-request
66:END:
67Next, we need to actually send the API request. In my case, I'm calling 58Next, we need to actually send the API request. In my case, I'm calling
68a random poem from the entire API. If you want, you can call specific 59a random poem from the entire API. If you want, you can call specific
69poems or authors from this API. 60poems or authors from this API.
70 61
71#+begin_src py 62```python
72json_data = requests.get('https://poetrydb.org/random').json() 63json_data = requests.get('https://poetrydb.org/random').json()
73#+end_src 64```
74 65
75This gives us the following result in JSON: 66This gives us the following result in JSON:
76 67
77#+begin_src json 68```json
78[ 69[
79 { 70 {
80 "title": "Sonnet XXII: With Fools and Children", 71 "title": "Sonnet XXII: With Fools and Children",
@@ -100,46 +91,40 @@ This gives us the following result in JSON:
100 "linecount": "15" 91 "linecount": "15"
101 } 92 }
102] 93]
103#+end_src 94```
104 95
105*** Parsing the API Results 96### Parsing the API Results
106:PROPERTIES:
107:CUSTOM_ID: parsing-the-api-results
108:END:
109In order to parse this into a readable format, we need to use the =json=
110package and extract the fields we want. In the example below, I am
111grabbing every field presented by the API.
112
113For the actual poem content, we need to loop over each line in the
114=lines= variable since each line is a separate string by default.
115 97
116#+begin_quote 98In order to parse this into a readable format, we need to use the
117You /could/ also extract the title or author and make another call out 99`json` package and extract the fields we want. In the example
118to the API to avoid having to build the plaintext poem with a loop, but 100below, I am grabbing every field presented by the API.
119it just doesn't make sense to me to send multiple requests when we can
120create a simple loop on our local machine to work with the data we
121already have.
122 101
123For 102For the actual poem content, we need to loop over each line in the
124[[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]], 103`lines` variable since each line is a separate string by
125look at the raw data response of this link to see the poem's lines 104default.
126returned in plaintext. 105
127 106> You *could* also extract the title or author and make another call out
128#+end_quote 107> to the API to avoid having to build the plaintext poem with a loop,
129 108> but it just doesn't make sense to me to send multiple requests when
130#+begin_src py 109> we can create a simple loop on our local machine to work with the data
110> we already have.
111>
112> For
113> [example](https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text),
114> look at the raw data response of this link to see the poem's lines
115> returned in plaintext.
116
117```python
131title = json_data[0]['title'] 118title = json_data[0]['title']
132author = json_data[0]['author'] 119author = json_data[0]['author']
133line_count = json_data[0]['linecount'] 120line_count = json_data[0]['linecount']
134lines = '' 121lines = ''
135for line in json_data[0]['lines']: 122for line in json_data[0]['lines']:
136 lines = lines + line + "\n" 123 lines = lines + line + "\n"
137#+end_src 124```
125
126### Composing the Email
138 127
139*** Composing the Email
140:PROPERTIES:
141:CUSTOM_ID: composing-the-email
142:END:
143Now that I have all the data I need, I just need to compose it into a 128Now that I have all the data I need, I just need to compose it into a
144message and prepare the message metadata. 129message and prepare the message metadata.
145 130
@@ -148,47 +133,43 @@ by the author, then a blank line, and finally the full poem. This code
148snippet combines that data and packages it into a MIMEText container, 133snippet combines that data and packages it into a MIMEText container,
149ready to be emailed. 134ready to be emailed.
150 135
151#+begin_src py 136```python
152msg_body = title + "\n" + author + "\n\n" + lines 137msg_body = title + "\n" + author + "\n\n" + lines
153msg = MIMEText(msg_body) 138msg = MIMEText(msg_body)
154#+end_src 139```
155 140
156Before we send the email, we need to prepare the metadata (subject, 141Before we send the email, we need to prepare the metadata (subject,
157from, to, etc.): 142from, to, etc.):
158 143
159#+begin_src py 144```python
160sender_email = 'example@server.local' 145sender_email = 'example@server.local'
161recipient_emails = ['user@example.com'] 146recipient_emails = ['user@example.com']
162msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)' 147msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
163msg['From'] = sender_email 148msg['From'] = sender_email
164msg['To'] = recipient_email 149msg['To'] = recipient_email
165#+end_src 150```
151
152### Sending the Email
166 153
167*** Sending the Email
168:PROPERTIES:
169:CUSTOM_ID: sending-the-email
170:END:
171Now that I have everything ready to be emailed, the last step is to 154Now that I have everything ready to be emailed, the last step is to
172simply connect to an SMTP server and send the email out to the 155simply connect to an SMTP server and send the email out to the
173recipients. In my case, I installed =mailutils= on Ubuntu and let my 156recipients. In my case, I installed `mailutils` on Ubuntu and
174SMTP server be =localhost=. 157let my SMTP server be `localhost`.
175 158
176#+begin_src py 159```python
177smtp_server = 'localhost' 160smtp_server = 'localhost'
178s = smtplib.SMTP(smtp_server) 161s = smtplib.SMTP(smtp_server)
179s.sendmail(sender_email, recipient_emails, msg.as_string()) 162s.sendmail(sender_email, recipient_emails, msg.as_string())
180s.quit() 163s.quit()
181#+end_src 164```
165
166## The Result!
182 167
183** The Result! 168Instead of including a screenshot, I've copied the contents of the
184:PROPERTIES: 169email that was delivered to my inbox below since I set this process up
185:CUSTOM_ID: the-result 170in plaintext format.
186:END:
187Instead of including a screenshot, I've copied the contents of the email
188that was delivered to my inbox below since I set this process up in
189plaintext format.
190 171
191#+begin_src txt 172```txt
192Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC) 173Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
193From: REDACTED 174From: REDACTED
194To: REDACTED 175To: REDACTED
@@ -214,26 +195,25 @@ You that behold us, laugh us not to scorn;
214Give Nature thanks you are not such as we. 195Give Nature thanks you are not such as we.
215Yet fools and children sometimes tell in play 196Yet fools and children sometimes tell in play
216Some, wise in show, more fools indeed than they. 197Some, wise in show, more fools indeed than they.
217#+end_src 198```
199
200## Scheduling the Daily Email
218 201
219** Scheduling the Daily Email 202Last, but not least, is scheduling this Python script with
220:PROPERTIES: 203`crontab`. To schedule a script to run daily, you can add it
221:CUSTOM_ID: scheduling-the-daily-email 204to the `crontab` file. To do this, open `crontab`
222:END: 205in editing mode:
223Last, but not least, is scheduling this Python script with =crontab=. To
224schedule a script to run daily, you can add it to the =crontab= file. To
225do this, open =crontab= in editing mode:
226 206
227#+begin_src sh 207```sh
228crontab -e 208crontab -e
229#+end_src 209```
230 210
231In the file, simply paste the following snippet at the bottom of the 211In the file, simply paste the following snippet at the bottom of the
232file and ensure that the file path is correctly pointing to wherever you 212file and ensure that the file path is correctly pointing to wherever you
233saved your Python script: 213saved your Python script:
234 214
235#+begin_src config 215```config
2360 8 * * * python3 /home/<your_user>/dailypoem/main.py 2160 8 * * * python3 /home/<your_user>/dailypoem/main.py
237#+end_src 217```
238 218
239We have now set up the script and scheduled it to run daily at 08:00! 219We have now set up the script and scheduled it to run daily at 08:00!
content/blog/2022-06-24-fedora-i3.md added +158
@@ -0,0 +1,158 @@
1+++
2date = 2022-06-24
3title = "Rebooting My Love Affair with Linux"
4description = "A retrospective on moving from macOS to Linux."
5+++
6
7## Leaving macOS
8
9As I noted [in a recent post](/blog/foss-macos-apps), I have been
10planning on migrating from macOS back to a Linux-based OS. I am happy to
11say that I have finally completed my migration and am now stuck in the
12wonderful world of Linux again.
13
14My decision to leave macOS really came down to just a few important
15things:
16
17- Apple Security (Gatekeeper) restricting me from running any software
18 I want. Even if you disable Gatekeeper and allow software to bypass
19 the rest of the device installation security, you still have to
20 repeat that process every time the allowed software is updated.
21- macOS sends out nearly constant connections, pings, telemetry, etc.
22 to a myriad of mysterious Apple services. I'm not even going to
23 dive into how many macOS apps have constant telemetry on, as well.
24- Lastly, I just *really* missed the customization and freedom that
25 comes with Linux. Being able to switch to entirely new kernel, OS,
26 or desktop within minutes is a freedom I took for granted when I
27 switched to macOS.
28
29Now that I've covered macOS, I'm going to move on to more exciting
30topics: my personal choice of OS, DE, and various customizations I'm
31using.
32
33## Fedora
34
35After trying a ton of distros (I think I booted and tested around 20-25
36distros), I finally landed on [Fedora Linux](https://getfedora.org/). I
37have quite a bit of experience with Fedora and enjoy the
38`dnf` package manager. Fedora allows me to keep up-to-date
39with recent software (I'm looking at you, Debian), but still provides a
40level of stability you don't find in every distro.
41
42In a very close second place was Arch Linux, as well as its spin-off:
43Garuda Linux (Garuda w/ sway is *beautiful*). Arch is great for
44compatibility and the massive community it has, but I have just never
45had the time to properly sit down and learn the methodology behind their
46packaging systems.
47
48Basically, everything else I tested was unacceptable in at least one way
49or another. Void (`glibc`) was great, but doesn't support
50all the software I need. Slackware worked well as a tui, but I wasn't
51skilled enough to get a tiling window manager (WM) working on it.
52
53### i3
54
55One of the reasons I settled on Fedora is that it comes with an official
56i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
57the biggest things I wanted to do as soon as I adopted Linux again.
58
59I will probably set up a dotfile repository soon, so that I don't lose
60any of my configurations, but nothing big has been configured thus far.
61
62The two main things I have updated in i3wm are natural scrolling and
63binding my brightness keys to the `brightnessctl` program.
64
651. Natural Scrolling
66
67 You can enable natural scrolling by opening the following file:
68
69 ```sh
70 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
71 ```
72
73 Within the `40-libinput.conf` file, find the following
74 input sections and enable the natural scrolling option.
75
76 This is the `pointer` section:
77
78 ```conf
79 Section "InputClass"
80 Identifier "libinput pointer catchall"
81 MatchIsPointer "on"
82 MatchDevicePath "/dev/input/event*"
83 Driver "libinput"
84 Option "NaturalScrolling" "True"
85 EndSection
86 ```
87
88 This is the `touchpad` section:
89
90 ```conf
91 Section "InputClass"
92 Identifier "libinput touchpad catchall"
93 MatchIsTouchpad "on"
94 MatchDevicePath "/dev/input/event*"
95 Driver "libinput"
96 Option "NaturalScrolling" "True"
97 EndSection
98 ```
99
1002. Enabling Brightness Keys
101
102 Likewise, enabling brightness key functionality is as simple as
103 binding the keys to the `brightnessctl` program.
104
105 To do this, open up your i3 config file. Mine is located here:
106
107 ```sh
108 nano /home/<my-user>/.config/i3/config
109 ```
110
111 ```conf
112 # Use brightnessctl to adjust brightness.
113 bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
114 bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
115 ```
116
1173. `polybar`
118
119 Instead of using the default `i3status` bar, I have opted
120 to use `polybar` instead (as you can also see in the
121 screenshot above).
122
123 My config for this menu bar is basically just the default settings
124 with modified colors and an added battery block to quickly show me
125 the machine's battery info.
126
1274. `alacritty`
128
129 Not much to say on this part yet, as I haven't configured it much,
130 but I installed `alacritty` as my default terminal, and I
131 am using `zsh` and the shell.
132
133## Software Choices
134
135Again, I'm not going to say much that I haven't said yet in other blog
136posts, so I'll just do a quick rundown of the apps I installed
137immediately after I set up the environment.
138
139Flatpak Apps:
140
141- Cryptomator
142- pCloud
143- Signal
144
145Fedora Packages:
146
147- gomuks
148- neomutt
149- neofetch
150- Firefox
151 - uBlock Origin
152 - Bitwarden
153 - Stylus
154 - Privacy Redirect
155
156Other:
157
158- exiftool
content/blog/2022-07-01-git-server.md added +654
@@ -0,0 +1,654 @@
1+++
2date = 2022-07-01
3title = "Self-Hosting a Personal Git Server"
4description = "A guide to self-hosting a Git server on your own server."
5+++
6
7## My Approach to Self-Hosting Git
8
9I have often tried to self-host my Git repositories, but have always
10fallen short when I tried to find a suitable web interface to show on
11the front-end.
12
13After a few years, I have finally found a combination of methods that
14allow me to easily self-host my projects, view them on the web, and
15access them from anywhere.
16
17Before I dive into the details, I want to state a high-level summary of
18my self-hosted Git approach:
19
20- This method uses the `ssh://` (read & write) and
21 `git://` (read-only) protocols for push and pull access.
22 - For the `git://` protocol, I create a
23 `git-daemon-export-ok` file in any repository that I
24 want to be cloneable by anyone.
25 - The web interface I am using (`cgit`) allows simple
26 HTTP cloning by default. I do not disable this setting as I want
27 beginners to be able to clone one of my repositories even if
28 they don't know the proper method.
29- I am not enabling Smart HTTPS for any repositories. Updates to
30 repositories must be pushed via SSH.
31- Beyond the actual repository management, I am using
32 `cgit` for the front-end web interface.
33 - If you use the `scan-path=<path>` configuration in
34 the `cgitrc` configuration file to automatically find
35 repositories, you can't exclude a repository from
36 `cgit` if it's stored within the path that
37 `cgit` reads. To host private repositories, you'd
38 need to set up another directory that `cgit` can't
39 read.
40
41## Assumptions
42
43For the purposes of this walkthrough, I am assuming you have a URL
44(`git.example.com`) or IP address
45(`207.84.26.991`) addressed to the server that you will be
46using to host your git repositories.
47
48## Adding a Git User
49
50In order to use the SSH method associated with git, we will need to add
51a user named `git`. If you have used the SSH method for other
52git hosting sites, you are probably used to the following syntax:
53
54```sh
55git clone [user@]server:project.git
56```
57
58The syntax above is an `scp`-like syntax for using SSH on the
59`git` user on the server to access your repository.
60
61Let's delete any remnants of an old `git` user, if any, and
62create the new user account:
63
64```sh
65sudo deluser --remove-home git
66sudo adduser git
67```
68
69### Import Your SSH Keys to the Git User
70
71Once the `git` user is created, you will need to copy your
72public SSH key on your local development machine to the `git`
73user on the server.
74
75If you don't have an SSH key yet, create one with this command:
76
77```sh
78ssh-keygen
79```
80
81Once you create the key pair, the public should be saved to
82`~/.ssh/id_rsa.pub`.
83
84If your server still has password-based authentication available, you
85can copy it over to your user's home directory like this:
86
87```sh
88ssh-copy-id git@server
89```
90
91Otherwise, copy it over to any user that you can access.
92
93```sh
94scp ~/.ssh/id_rsa.pub your_user@your_server:
95```
96
97Once on the server, you will need to copy the contents into the
98`git` user's `authorized_keys` file:
99
100```sh
101cat id_rsa.pub > /home/git/.ssh/authorized_keys
102```
103
104### (Optional) Disable Password-Based SSH
105
106If you want to lock down your server and ensure that no one can
107authenticate in via SSH with a password, you will need to edit your SSH
108configuration.
109
110```sh
111sudo nano /etc/ssh/sshd_config
112```
113
114Within this file, find the following settings and set them to the values
115I am showing below:
116
117```conf
118PermitRootLogin no
119PasswordAuthentication no
120AuthenticationMethods publickey
121```
122
123You may have other Authentication Methods required in your personal
124set-up, so the key here is just to ensure that
125`AuthenticationMethods` does not allow passwords.
126
127## Setting up the Base Directory
128
129Now that we have set up a `git` user to handle all transport
130methods, we need to set up the directory that we will be using as our
131base of all repositories.
132
133In my case, I am using `/git` as my source folder. To create
134this folder and assign it to the user we created, execute the following
135commands:
136
137```sh
138sudo mkdir /git
139sudo chown -R git:git /git
140```
141
142## Creating a Test Repository
143
144On your server, switch over to the `git` user in order to
145start managing git files.
146
147```sh
148su git
149```
150
151Once logged-in as the `git` user, go to your base directory
152and create a test repository.
153
154```sh
155cd /git
156mkdir test.git && cd test.git
157git init --bare
158```
159
160If you want to make this repo viewable/cloneable to the public via the
161`git://` protocol, you need to create a
162`git-daemon-export-ok` file inside the repository.
163
164```sh
165touch git-daemon-export-ok
166```
167
168## Change the Login Shell for `git`
169
170To make sure that the `git` user is only used for git
171operations and nothing else, you need to change the user's login shell.
172To do this, simply use the `chsh` command:
173
174```sh
175sudo chsh git
176```
177
178The interactive prompt will ask which shell you want the
179`git` user to use. You must use the following value:
180
181```sh
182/usr/bin/git-shell
183```
184
185Once done, no one will be able to SSH to the `git` user or
186execute commands other than the standard git commands.
187
188## Opening the Firewall
189
190Don't forget to open up ports on the device firewall and network
191firewall if you want to access these repositories publicly. If you're
192using default ports, forward ports `22` (ssh) and
193`9418` (git) from your router to your server's IP address.
194
195If your server also has a firewall, ensure that the firewall allows the
196same ports that are forwarded from the router. For example, if you use
197`ufw`:
198
199```sh
200sudo ufw allow 22
201sudo ufw allow 9418
202```
203
204### Non-Standard SSH Ports
205
206If you use a non-standard port for SSH, such as `9876`, you
207will need to create an SSH configuration file on your local development
208machine in order to connect to your server's git repositories.
209
210To do this, you'll need to define your custom port on your client
211machine in your `~/.ssh/config` file:
212
213```sh
214nano ~/.ssh/config
215```
216
217```conf
218Host git.example.com
219 # HostName can be a URL or an IP address
220 HostName git.example.com
221 Port 9876
222 User git
223```
224
225### Testing SSH
226
227There are two main syntaxes you can use to manage git over SSH:
228
229- `git clone [user@]server:project.git`
230- `git clone ssh://[user@]server/project.git`
231
232I prefer the first, which is an `scp`-like syntax. To test
233it, try to clone the test repository you set up on the server:
234
235```sh
236git clone git@git.example.com:/git/test.git
237```
238
239## Enabling Read-Only Access
240
241If you want people to be able to clone any repository where you've
242placed a `git-daemon-export-ok` file, you will need to start
243the git daemon.
244
245To do this on a system with `systemd`, create a service file:
246
247```sh
248sudo nano /etc/systemd/system/git-daemon.service
249```
250
251Inside the `git-daemon.service` file, paste the following:
252
253```conf
254[Unit]
255Description=Start Git Daemon
256
257[Service]
258ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
259
260Restart=always
261RestartSec=500ms
262
263StandardOutput=syslog
264StandardError=syslog
265SyslogIdentifier=git-daemon
266
267User=git
268Group=git
269
270[Install]
271WantedBy=multi-user.target
272```
273
274Once created, enable and start the service:
275
276```sh
277sudo systemctl enable git-daemon.service
278sudo systemctl start git-daemon.service
279```
280
281To clone read-only via the `git://` protocol, you can use the
282following syntax:
283
284```sh
285git clone git://git.example.com/test.git
286```
287
288## Migrating Repositories
289
290At this point, we have a working git server that works with both SSH and
291read-only access.
292
293For each of the repositories I had hosted a different provider, I
294executed the following commands in order to place a copy on my server as
295my new source of truth:
296
297Server:
298
299```sh
300su git
301mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
302git init --bare
303
304# If you want to make this repo viewable/cloneable to the public
305touch git-daemon-export-ok
306```
307
308Client:
309
310```sh
311git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
312git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
313git push
314```
315
316## Optional Web View: `cgit`
317
318If you want a web viewer for your repositories, you can use various
319tools, such as `gitweb`, `cgit`, or
320`klaus`. I chose `cgit` due to its simple
321interface and fairly easy set-up (compared to others). Not to mention
322that the [Linux kernel uses `cgit`](https://git.kernel.org/).
323
324### Docker Compose
325
326Instead of using my previous method of using a `docker run`
327command, I've updated this section to use `docker-compose`
328instead for an easier installation and simpler management and
329configuration.
330
331In order to use Docker Compose, you will set up a
332`docker-compose.yml` file to automatically connect resources
333like the repositories, `cgitrc`, and various files or folders
334to the `cgit` container you're creating:
335
336```sh
337mkdir ~/cgit && cd ~/cgit
338nano docker-compose.yml
339```
340
341```conf
342# docker-compose.yml
343version: '3'
344
345services:
346 cgit:
347 image: invokr/cgit
348 volumes:
349 - /git:/git
350 - ./cgitrc:/etc/cgitrc
351 - ./logo.png:/var/www/htdocs/cgit/logo.png
352 - ./favicon.png:/var/www/htdocs/cgit/favicon.png
353 - ./filters:/var/www/htdocs/cgit/filters
354 ports:
355 - "8763:80"
356 restart: always
357```
358
359Then, just start the container:
360
361```sh
362sudo docker-compose up -d
363```
364
365Once it's finished installing, you can access the site at
366`<SERVER_IP>:8763` or use a reverse-proxy service to forward
367`cgit` to a URL, such as `git.example.com`. See
368the next section for more details on reverse proxying a URL to a local
369port.
370
371### Nginx Reverse Proxy
372
373I am using Nginx as my reverse proxy so that the `cgit`
374Docker container can use `git.example.com` as its URL. To do
375so, I simply created the following configuration file:
376
377```sh
378sudo nano /etc/nginx/sites-available/git.example.com
379```
380
381```conf
382server {
383 listen 80;
384 server_name git.example.com;
385
386 if ($host = git.example.com) {
387 return 301 https://$host$request_uri;
388 }
389
390 return 404;
391}
392
393server {
394 server_name git.example.com;
395 listen 443 ssl http2;
396
397 location / {
398 # The final `/` is important.
399 proxy_pass http://localhost:8763/;
400 add_header X-Frame-Options SAMEORIGIN;
401 add_header X-XSS-Protection "1; mode=block";
402 proxy_redirect off;
403 proxy_buffering off;
404 proxy_set_header Host $host;
405 proxy_set_header X-Real-IP $remote_addr;
406 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
407 proxy_set_header X-Forwarded-Proto $scheme;
408 proxy_set_header X-Forwarded-Port $server_port;
409 }
410
411 # INCLUDE ANY SSL CERTS HERE
412 include /etc/letsencrypt/options-ssl-nginx.conf;
413 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
414}
415```
416
417Once created, symlink it and restart the web server.
418
419```sh
420sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
421sudo systemctl restart nginx.service
422```
423
424As we can see below, my site at `git.example.com` is
425available and running:
426
427### Settings Up Git Details
428
429Once you have `cgit` running, you can add some small details,
430such as repository owners and descriptions by editing the following
431files within each repository.
432
433Alternatively, you can use the `cgitrc` file to edit these
434details if you only care to edit them for the purpose of seeing them on
435your website.
436
437The `description` file within the repository on your server
438will display the description online.
439
440```sh
441cd /git/example.git
442nano description
443```
444
445You can add a `[gitweb]` block to the `config`
446file in order to display the owner of the repository.
447
448```sh
449cd /git/example.git
450nano config
451```
452
453```conf
454[gitweb]
455 owner = "YourName"
456```
457
458Note that you can ignore the configuration within each repository and
459simply set up this information in the `cgitrc` file, if you
460want to do it that way.
461
462### Editing `cgit`
463
464In order to edit certain items within `cgit`, you need to
465edit the `cgitrc` file.
466
467```sh
468nano ~/cgit/cgitrc
469```
470
471Below is an example configuration for `cgitrc`. You can find
472all the configuration options within the [configuration manual]
473(<https://git.zx2c4.com/cgit/plain/cgitrc.5.txt>).
474
475```conf
476css=/cgit.css
477logo=/logo.png
478favicon=/favicon.png
479robots=noindex, nofollow
480
481enable-index-links=1
482enable-commit-graph=1
483enable-blame=1
484enable-log-filecount=1
485enable-log-linecount=1
486enable-git-config=1
487
488clone-url=git://git.example.com/$CGIT_REPO_URL ssh://git@git.example.com:/git/$CGIT_REPO_URL
489
490root-title=My Git Website
491root-desc=My personal git repositories.
492
493# Allow download of tar.gz, tar.bz2 and zip-files
494snapshots=tar.gz tar.bz2 zip
495
496##
497## List of common mimetypes
498##
499mimetype.gif=image/gif
500mimetype.html=text/html
501mimetype.jpg=image/jpeg
502mimetype.jpeg=image/jpeg
503mimetype.pdf=application/pdf
504mimetype.png=image/png
505mimetype.svg=image/svg+xml
506
507# Highlight source code
508# source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.sh
509source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
510
511# Format markdown, restructuredtext, manpages, text files, and html files
512# through the right converters
513about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
514
515##
516## Search for these files in the root of the default branch of repositories
517## for coming up with the about page:
518##
519readme=:README.md
520readme=:readme.md
521readme=:README.mkd
522readme=:readme.mkd
523readme=:README.rst
524readme=:readme.rst
525readme=:README.html
526readme=:readme.html
527readme=:README.htm
528readme=:readme.htm
529readme=:README.txt
530readme=:readme.txt
531readme=:README
532readme=:readme
533
534# Repositories
535
536# Uncomment the following line to scan a path instead of adding repositories manually
537# scan-path=/git
538
539## Test Section
540section=git/test-section
541
542repo.url=test.git
543repo.path=/git/test.git
544repo.readme=:README.md
545repo.owner=John Doe
546repo.desc=An example repository!
547```
548
549### Final Fixes: Syntax Highlighting & README Rendering
550
551After completing my initial install and playing around with it for a few
552days, I noticed two issues:
553
5541. Syntax highlighting did not work when viewing the source code within
555 a file.
5562. The `about` tab within a repository was not rendered to
557 HTML.
558
559The following process fixes these issues. To start, let's go to the
560`cgit` directory where we were editing our configuration file
561earlier.
562
563```sh
564cd ~/cgit
565```
566
567In here, create two folders that will hold our syntax files:
568
569```sh
570mkdir filters && mkdir filters/html-converters && cd filters
571```
572
573Next, download the default filters:
574
575```sh
576curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
577chmod 755 about-formatting.sh
578curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
579chmod 755 syntax-highlighting.py
580```
581
582Finally, download the HTML conversion files you need. The example below
583downloads the Markdown converter:
584
585```sh
586cd html-converters
587curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
588chmod 755 md2html
589```
590
591If you need other filters or html-converters found within [the cgit
592project files](https://git.zx2c4.com/cgit/tree/filters), repeat the
593`curl` and `chmod` process above for whichever
594files you need.
595
596However, formatting will not work quite yet since the Docker cgit
597container we're using doesn't have the formatting package installed.
598You can install this easily by install Python 3+ and the
599`pygments` package:
600
601```sh
602# Enter the container's command line
603sudo docker exec -it cgit bash
604```
605
606```sh
607# Install the necessary packages and then exit
608yum update -y && \
609yum upgrade -y && \
610yum install python3 python3-pip -y && \
611pip3 install markdown pygments && \
612exit
613```
614
615**You will need to enter the cgit docker container and re-run these
616`yum` commands every time you kill and restart the
617container!**
618
619If not done already, we need to add the following variables to our
620`cgitrc` file in order for `cgit` to know where
621our filtering files are:
622
623```conf
624# Highlight source code with python pygments-based highlighter
625source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
626
627# Format markdown, restructuredtext, manpages, text files, and html files
628# through the right converters
629about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
630```
631
632Now you should see that syntax highlighting and README rendering to the
633`about` tab is fixed.
634
635### Theming
636
637I won't go into much detail in this section, but you can fully theme
638your installation of `cgit` since you have access to the
639`cgit.css` file in your web root. This is another file you
640can add as a volume to the `docker-compose.yml` file if you
641want to edit this without entering the container's command line.
642
643## :warning: Remember to Back Up Your Data!
644
645The last thing to note is that running services on your own equipment
646means that you're assuming a level of risk that exists regarding data
647loss, catastrophes, etc. In order to reduce the impact of any such
648occurrence, I suggest backing up your data regularly.
649
650Backups can be automated via `cron`, by hooking your base
651directory up to a cloud provider, or even setting up hooks to push all
652repository info to git mirrors on other git hosts. Whatever the method,
653make sure that your data doesn't vanish in the event that your drives
654or servers fail.
blog/2022-07-14-gnupg.org → content/blog/2022-07-14-gnupg.md renamed +114 −129
@@ -1,31 +1,30 @@
1#+title: GNU Privacy Guard (GPG) 1+++
2#+date: 2022-07-14 2date = 2022-07-14
3 3title = "GNU Privacy Guard (GPG)"
4** The History of GPG 4description = "Learn how to create a PGP key with GNU Privacy Guard (GPG)."
5:PROPERTIES: 5+++
6:CUSTOM_ID: the-history-of-gpg 6
7:END: 7## The History of GPG
8[[https://gnupg.org/][GNU Privacy Guard]], also known as GnuPG and GPG, 8
9is a free ("free" as in both speech and beer) software that fully 9[GNU Privacy Guard](https://gnupg.org/), also known as GnuPG and GPG, is
10implements the OpenPGP Message Format documented in 10a free ("free" as in both speech and beer) software that fully
11[[https://www.rfc-editor.org/rfc/rfc4880][RFC 4880]]. 11implements the OpenPGP Message Format documented in [RFC
124880](https://www.rfc-editor.org/rfc/rfc4880).
12 13
13I won't go in-depth on the full history of the software in this post, 14I won't go in-depth on the full history of the software in this post,
14but it is important to understand that GPG is not the same as PGP 15but it is important to understand that GPG is not the same as PGP
15(Pretty Good Privacy), which is a different implementation of RFC 4880. 16(Pretty Good Privacy), which is a different implementation of RFC 4880.
16However, GPG was designed to interoperate with PGP. 17However, GPG was designed to interoperate with PGP.
17 18
18GPG was originally developed in the late 1990s by 19GPG was originally developed in the late 1990s by [Werner
19[[https://en.wikipedia.org/wiki/Werner_Koch][Werner Koch]] and has 20Koch](https://en.wikipedia.org/wiki/Werner_Koch) and has historically
20historically been funded generously by the German government. 21been funded generously by the German government.
22
23Now that we have all the high-level info out of the way, let's dive
24into the different aspects of GPG and its uses.
21 25
22Now that we have all the high-level info out of the way, let's dive into 26## Encryption Algorithms
23the different aspects of GPG and its uses.
24 27
25** Encryption Algorithms
26:PROPERTIES:
27:CUSTOM_ID: encryption-algorithms
28:END:
29GPG supports a wide range of different encryption algorithms, including 28GPG supports a wide range of different encryption algorithms, including
30public-key, cipher, hash, and compression algorithms. The support for 29public-key, cipher, hash, and compression algorithms. The support for
31these algorithms has grown since the adoption of the Libgcrypt library 30these algorithms has grown since the adoption of the Libgcrypt library
@@ -35,7 +34,7 @@ As you will be able to see below in an example of a full key generation
35with the GPG command line tool, GPG recommends the following algorithms 34with the GPG command line tool, GPG recommends the following algorithms
36to new users: 35to new users:
37 36
38#+begin_src sh 37```sh
39Please select what kind of key you want: 38Please select what kind of key you want:
40 (1) RSA and RSA 39 (1) RSA and RSA
41 (2) DSA and Elgamal 40 (2) DSA and Elgamal
@@ -43,75 +42,72 @@ Please select what kind of key you want:
43 (4) RSA (sign only) 42 (4) RSA (sign only)
44 (9) ECC (sign and encrypt) *default* 43 (9) ECC (sign and encrypt) *default*
45 (10) ECC (sign only) 44 (10) ECC (sign only)
46#+end_src 45```
47 46
48I am not doing an in-depth explanation here in order to keep the focus 47I am not doing an in-depth explanation here in order to keep the focus
49on GPG and not encryption algorithms. If you want a deep dive into 48on GPG and not encryption algorithms. If you want a deep dive into
50cryptography or encryption algorithms, please read my other posts: 49cryptography or encryption algorithms, please read my other posts:
51 50
52- [[/blog/aes-encryption/][AES Encryption]] (2018) 51- [AES Encryption](/blog/aes-encryption/) (2018)
53- [[/blog/cryptography-basics/][Cryptography Basics]] (2020) 52- [Cryptography Basics](/blog/cryptography-basics/) (2020)
53
54### Vulnerabilities
54 55
55*** Vulnerabilities
56:PROPERTIES:
57:CUSTOM_ID: vulnerabilities
58:END:
59As of 2022-07-14, there are a few different vulnerabilities associated 56As of 2022-07-14, there are a few different vulnerabilities associated
60with GPG or the libraries it uses: 57with GPG or the libraries it uses:
61 58
62- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one 59- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one
63 (GPG-generated) ElGamal signature of an arbitrary message is released, 60 (GPG-generated) ElGamal signature of an arbitrary message is
64 one can recover the signer's private key in less than a second on a 61 released, one can recover the signer's private key in less than a
65 PC." ([[https://www.di.ens.fr/~pnguyen/pub_Ng04.htm][Source]]) 62 second on a PC."
66- GPG versions prior to 1.4.2.1 contain a false positive signature 63 ([Source](https://www.di.ens.fr/~pnguyen/pub_Ng04.htm))
67 verification bug. 64- GPG versions prior to 1.4.2.1 contain a false positive signature
68 ([[https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html][Source]]) 65 verification bug.
69- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned 66 ([Source](https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html))
70 data. ( 67- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned
71 [[https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html][Source]]) 68 data. (
72- Libgcrypt, a library used by GPG, contained a bug which enabled full 69 [Source](https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html))
73 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved in 70- Libgcrypt, a library used by GPG, contained a bug which enabled full
74 a GPG update in 2017. ([[https://lwn.net/Articles/727179/][Source]]) 71 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved
75- The [[https://en.wikipedia.org/wiki/ROCA_vulnerability][ROCA 72 in a GPG update in 2017.
76 Vulnerability]] affects RSA keys generated by YubiKey 4 tokens. 73 ([Source](https://lwn.net/Articles/727179/))
77 ([[https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf][Source]]) 74- The [ROCA
78- The [[https://en.wikipedia.org/wiki/SigSpoof][SigSpoof Attack]] allows 75 Vulnerability](https://en.wikipedia.org/wiki/ROCA_vulnerability)
79 an attacker to spoof digital signatures. 76 affects RSA keys generated by YubiKey 4 tokens.
80 ([[https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/][Source]]) 77 ([Source](https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf))
81- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer 78- The [SigSpoof Attack](https://en.wikipedia.org/wiki/SigSpoof) allows
82 overflow, fixed in Libgcrypt 1.9.1 79 an attacker to spoof digital signatures.
83 ([[https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/][Source]]) 80 ([Source](https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/))
84 81- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer
85** Platforms 82 overflow, fixed in Libgcrypt 1.9.1
86:PROPERTIES: 83 ([Source](https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/))
87:CUSTOM_ID: platforms 84
88:END: 85## Platforms
89Originally developed as a command-line program for *nix systems, GPG now 86
90has a wealth of front-end applications and libraries available for 87Originally developed as a command-line program for *nix systems, GPG
88now has a wealth of front-end applications and libraries available for
91end-users. However, the most recommended programs remain the same: 89end-users. However, the most recommended programs remain the same:
92 90
93- [[https://gnupg.org][GnuPG]] for Linux (depending on distro) 91- [GnuPG](https://gnupg.org) for Linux (depending on distro)
94- [[https://gpg4win.org][Gpg4win]] for Windows 92- [Gpg4win](https://gpg4win.org) for Windows
95- [[https://gpgtools.org][GPGTools]] for macOS 93- [GPGTools](https://gpgtools.org) for macOS
94
95## Creating a Key Pair
96 96
97** Creating a Key Pair
98:PROPERTIES:
99:CUSTOM_ID: creating-a-key-pair
100:END:
101In order to create a GPG key pair, a user would first need to install 97In order to create a GPG key pair, a user would first need to install
102GPG on their system. If we're assuming that the user is on Fedora Linux, 98GPG on their system. If we're assuming that the user is on Fedora
103they would execute the following: 99Linux, they would execute the following:
104 100
105#+begin_src sh 101```sh
106sudo dnf install gpg 102sudo dnf install gpg
107#+end_src 103```
108 104
109Once installed, a user can create a new key pair with the following 105Once installed, a user can create a new key pair with the following
110command(s): 106command(s):
111 107
112#+begin_src sh 108```sh
113gpg --full-generate-key 109gpg --full-generate-key
114#+end_src 110```
115 111
116GPG will walk the user through an interactive setup that asks for an 112GPG will walk the user through an interactive setup that asks for an
117algorithm preference, expiration date, name, and email to associate with 113algorithm preference, expiration date, name, and email to associate with
@@ -120,7 +116,7 @@ this key.
120See the following example key set-up for a default key generation using 116See the following example key set-up for a default key generation using
121the GnuPG command-line interface: 117the GnuPG command-line interface:
122 118
123#+begin_src sh 119```sh
124gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc. 120gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc.
125This is free software: you are free to change and redistribute it. 121This is free software: you are free to change and redistribute it.
126There is NO WARRANTY, to the extent permitted by law. 122There is NO WARRANTY, to the extent permitted by law.
@@ -172,22 +168,20 @@ pub ed25519 2022-07-14 [SC]
172 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C 168 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C
173uid John Doe (test key) <johndoe@example.com> 169uid John Doe (test key) <johndoe@example.com>
174sub cv25519 2022-07-14 [E] 170sub cv25519 2022-07-14 [E]
175#+end_src 171```
176 172
177Please note that GUI apps may differ slightly from the GPG command-line 173Please note that GUI apps may differ slightly from the GPG command-line
178interface. 174interface.
179 175
180** Common Usage 176## Common Usage
181:PROPERTIES: 177
182:CUSTOM_ID: common-usage
183:END:
184As noted in RFC 4880, the general functions of OpenPGP are as follows: 178As noted in RFC 4880, the general functions of OpenPGP are as follows:
185 179
186- digital signatures 180- digital signatures
187- encryption 181- encryption
188- compression 182- compression
189- Radix-64 conversion 183- Radix-64 conversion
190- key management and certificate services 184- key management and certificate services
191 185
192From this, you can probably gather that the main use of GPG is for 186From this, you can probably gather that the main use of GPG is for
193encrypting data and/or signing the data with a key. The purpose of 187encrypting data and/or signing the data with a key. The purpose of
@@ -196,10 +190,8 @@ recipient(s) can access the data.
196 190
197Let's explore some specific GPG use-cases. 191Let's explore some specific GPG use-cases.
198 192
199*** Email 193### Email
200:PROPERTIES: 194
201:CUSTOM_ID: email
202:END:
203One of the more popular uses of GPG is to sign and/or encrypt emails. 195One of the more popular uses of GPG is to sign and/or encrypt emails.
204With the use of a GPG keypair, you can encrypt a message, its subject, 196With the use of a GPG keypair, you can encrypt a message, its subject,
205and even the attachments within. 197and even the attachments within.
@@ -214,8 +206,8 @@ The second process, regarding the actual encryption of the message and
214its contents, works by using a combination of the sender's keys and the 206its contents, works by using a combination of the sender's keys and the
215recipient's keys. This process may vary slightly by implementation, but 207recipient's keys. This process may vary slightly by implementation, but
216it most commonly uses asymmetric cryptography, also known as public-key 208it most commonly uses asymmetric cryptography, also known as public-key
217cryptography. In this version of encryption, the sender's private key to 209cryptography. In this version of encryption, the sender's private key
218sign the message and a combination of the sender's keys and the 210to sign the message and a combination of the sender's keys and the
219recipient's public key to encrypt the message. 211recipient's public key to encrypt the message.
220 212
221If two people each have their own private keys and exchange their public 213If two people each have their own private keys and exchange their public
@@ -227,66 +219,61 @@ Implementation of email encryption varies greatly between email clients,
227so you will need to reference your email client's documentation to 219so you will need to reference your email client's documentation to
228ensure you are setting it up correctly for that specific client. 220ensure you are setting it up correctly for that specific client.
229 221
230*** File Encryption 222### File Encryption
231:PROPERTIES: 223
232:CUSTOM_ID: file-encryption
233:END:
234As noted in the section above regarding emails, GPG enables users to be 224As noted in the section above regarding emails, GPG enables users to be
235able to send a message to each other if they are both set-up with GPG 225able to send a message to each other if they are both set-up with GPG
236keys. In this example, I am going to show how a user could send a file 226keys. In this example, I am going to show how a user could send a file
237called =example_file.txt= to another user via the recipient's email. 227called `example_file.txt` to another user via the
228recipient's email.
238 229
239The sender would find the file they want to send and execute the 230The sender would find the file they want to send and execute the
240following command: 231following command:
241 232
242#+begin_src sh 233```sh
243gpg --encrypt --output example_file.txt.gpg --recipient \ 234gpg --encrypt --output example_file.txt.gpg --recipient \
244recipient@example.com example_file.txt 235recipient@example.com example_file.txt
245#+end_src 236```
246 237
247Once received, the recipient can decrypt the file with the following 238Once received, the recipient can decrypt the file with the following
248command: 239command:
249 240
250#+begin_src sh 241```sh
251gpg --decrypt --output example_file.txt example_file.txt.gpg 242gpg --decrypt --output example_file.txt example_file.txt.gpg
252#+end_src 243```
244
245### Ownership Signatures
253 246
254*** Ownership Signatures
255:PROPERTIES:
256:CUSTOM_ID: ownership-signatures
257:END:
258One important aspect of GPG, especially for developers, is the ability 247One important aspect of GPG, especially for developers, is the ability
259to sign data without encrypting it. For example, developers often sign 248to sign data without encrypting it. For example, developers often sign
260code changes when they commit the changes back to a central repository, 249code changes when they commit the changes back to a central repository,
261in order to display ownership of who made the changes. This allows other 250in order to display ownership of who made the changes. This allows other
262users to look at a code change and determine that the change was valid. 251users to look at a code change and determine that the change was valid.
263 252
264In order to do this using [[https://git-scm.com][Git]], the developer 253In order to do this using [Git](https://git-scm.com), the developer
265simply needs to alter the =git commit= command to include the =-S= flag. 254simply needs to alter the `git commit` command to include the
266Here's an example: 255`-S` flag. Here's an example:
267 256
268#+begin_src sh 257```sh
269git commit -S -m "my commit message" 258git commit -S -m "my commit message"
270#+end_src 259```
271 260
272As an expansion of the example above, Git users can configure their 261As an expansion of the example above, Git users can configure their
273environment with a default key to use by adding their GPG signature: 262environment with a default key to use by adding their GPG signature:
274 263
275#+begin_src sh 264```sh
276git config --global user.signingkey XXXXXXXXXXXXXXXX 265git config --global user.signingkey XXXXXXXXXXXXXXXX
277#+end_src 266```
278 267
279If you're not sure what your signature is, you can find it titled =sig= 268If you're not sure what your signature is, you can find it titled
280in the output of this command: 269`sig` in the output of this command:
281 270
282#+begin_src sh 271```sh
283gpg --list-signatures 272gpg --list-signatures
284#+end_src 273```
274
275### File Integrity
285 276
286*** File Integrity
287:PROPERTIES:
288:CUSTOM_ID: file-integrity
289:END:
290When a person generates a signature for data, they are allowing users 277When a person generates a signature for data, they are allowing users
291the ability to verify the signature on that data in the future to ensure 278the ability to verify the signature on that data in the future to ensure
292the data has not been corrupted. This is most common with software 279the data has not been corrupted. This is most common with software
@@ -296,26 +283,24 @@ replaced with dangerous software.
296 283
297In order to verify signed data, a user needs to have: 284In order to verify signed data, a user needs to have:
298 285
2991. The signed data 2861. The signed data
3002. A signature file 2872. A signature file
3013. The public GPG key of the signer 2883. The public GPG key of the signer
302 289
303Once the signer's public key is imported on the user's system, and they 290Once the signer's public key is imported on the user's system, and
304have the data and signature, they can verify the data with the following 291they have the data and signature, they can verify the data with the
305commands: 292following commands:
306 293
307#+begin_src sh 294```sh
308# If the signature is attached to the data 295# If the signature is attached to the data
309gpg --verify [signature-file] 296gpg --verify [signature-file]
310 297
311# If the signature is detached as a separate file from the data 298# If the signature is detached as a separate file from the data
312gpg --verify [signature-file] [original-file] 299gpg --verify [signature-file] [original-file]
313#+end_src 300```
301
302## Finding Public Keys
314 303
315** Finding Public Keys
316:PROPERTIES:
317:CUSTOM_ID: finding-public-keys
318:END:
319In order to use GPG with others, a user needs to know the other user(s) 304In order to use GPG with others, a user needs to know the other user(s)
320keys. This is easy to do if the user knows the other user(s) in person, 305keys. This is easy to do if the user knows the other user(s) in person,
321but may be hard if the relationship is strictly digital. Luckily, there 306but may be hard if the relationship is strictly digital. Luckily, there
@@ -324,5 +309,5 @@ social pages if they have them.
324 309
325Otherwise, the best option is to use a keyserver, such as: 310Otherwise, the best option is to use a keyserver, such as:
326 311
327- [[https://pgp.mit.edu][pgp.mit.edu]] 312- [pgp.mit.edu](https://pgp.mit.edu)
328- [[https://keys.openpgp.org][keys.openpgp.org]] 313- [keys.openpgp.org](https://keys.openpgp.org)
content/blog/2022-07-25-curseradio.md added +106
@@ -0,0 +1,106 @@
1+++
2date = 2022-07-25
3title = "CurseRadio: Listening to the Radio on the Comand Line"
4description = "Use Curse Radio to listen to radio on the command-line."
5+++
6
7## Overview
8
9While exploring some interesting Linux applications, I stumbled across
10[curseradio](https://github.com/chronitis/curseradio), a command-line
11radio player based on Python.
12
13This application is fantastic and incredibly easy to install, so I
14wanted to dedicate a post today to this app. Let's look at the features
15within the app and then walk through the installation process I took to
16get `curseradio` working.
17
18## Features
19
20![curseradio](https://img.cleberg.net/blog/20220725-curseradio/curseradio.png)
21
22The radio player itself is quite minimal. As you can see in the
23screenshot above, it contains a simple plaintext list of all available
24categories, which can be broken down further and further. In addition,
25radio shows are available for listening, alongside regular radio
26stations.
27
28For example, the `Sports` > `Pro Basketball` >
29`Shows` category contains a number of specific shows related
30to Professional Basketball.
31
32Aside from being able to play any of the listed stations/shows, you can
33make a channel your favorite by pressing `f`. It will now
34show up at the top of the radio player in the `Favourites`
35category.
36
37### Commands/Shortcuts
38
39 Key(s) Command
40 ------------ ---------------------------------
41 ↑, ↓ navigate
42 PgUp, PgDn navigate quickly
43 Home, End to top/bottom
44 Enter open/close folders, play stream
45 k stop playing stream
46 q quit
47 f toggle favourite
48
49## Installation
50
51### Dependencies
52
53Before installing `curseradio`, a handful of system and
54Python packages are required. To get started, install
55`python3`, `pip3`, and `mpv` on your
56system. In this example, I'm using Fedora Linux, which uses the
57`dnf` package manager. You may need to adjust this if you're
58using a different system.
59
60```sh
61sudo dnf install python3 pip3 mpv
62```
63
64Next, use `pip3` to install `requests`,
65`xdg`, and `lxml`:
66
67```sh
68pip3 install requests xdg lxml
69```
70
71### Repository Source Installation
72
73Once all the dependencies are installed, we can clone the source code
74and enter that directory:
75
76```sh
77git clone https://github.com/chronitis/curseradio && cd curseradio
78```
79
80Once you're within the `curseradio` directory, you can
81install the application with the provided `setup.py` script.
82
83```sh
84sudo python3 setup.py install
85```
86
87In my case, I ran into a few errors and needed to create the folders
88that curseradio wanted to use for its installation. If you don't get
89any errors, you can skip this and run the app.
90
91```sh
92sudo mkdir /usr/local/lib/python3.10/
93sudo mkdir /usr/local/lib/python3.10/site-packages/
94```
95
96```sh
97sudo chown -R $USER:$USER /usr/local/lib/python3.10/
98```
99
100## Run the Application
101
102Once fully installed without errors, you can run the application!
103
104```sh
105python3 /usr/local/bin/curseradio
106```
blog/2022-07-30-flac-to-opus.org → content/blog/2022-07-30-flac-to-opus.md renamed +65 −70
@@ -1,72 +1,70 @@
1#+title: Recursive Command-Line FLAC to Opus Conversion 1+++
2#+date: 2022-07-30 2date = 2022-07-30
3title = "Recursive Command-Line FLAC to Opus Conversion"
4description = "Learn how to convert all FLAC files to Opus, including recursive files in subdirectories."
5+++
6
7## Converting FLAC to OPUS
3 8
4** Converting FLAC to OPUS
5:PROPERTIES:
6:CUSTOM_ID: converting-flac-to-opus
7:END:
8I am currently rebuilding my music library from scratch so that I can 9I am currently rebuilding my music library from scratch so that I can
9effectively archive all the music I own in the 10effectively archive all the music I own in the [FLAC file
10[[https://en.wikipedia.org/wiki/FLAC][FLAC file format]], a lossless 11format](https://en.wikipedia.org/wiki/FLAC), a lossless audio codec.
11audio codec.
12 12
13However, streaming FLAC files outside the home can be difficult due to 13However, streaming FLAC files outside the home can be difficult due to
14the size of the files, especially if you're using a weak connection. 14the size of the files, especially if you're using a weak connection.
15 15
16So, in order to archive the music in a lossless format and still be able 16So, in order to archive the music in a lossless format and still be able
17to stream it easily, I opted to create a copy of my FLAC files in the 17to stream it easily, I opted to create a copy of my FLAC files in the
18[[https://en.wikipedia.org/wiki/Opus_(audio_format)][Opus audio codec]]. 18[Opus audio codec](https://en.wikipedia.org/wiki/Opus_(audio_format)).
19This allows me to archive a quality, lossless version of the music and 19This allows me to archive a quality, lossless version of the music and
20then point my streaming service to the smaller, stream-ready version. 20then point my streaming service to the smaller, stream-ready version.
21 21
22*** Dependencies 22### Dependencies
23:PROPERTIES: 23
24:CUSTOM_ID: dependencies 24The process I follow utilizes the `opus-tools` package in
25:END: 25Ubuntu. Before proceeding, install the package:
26The process I follow utilizes the =opus-tools= package in Ubuntu. Before
27proceeding, install the package:
28 26
29#+begin_src sh 27```sh
30sudo apt install opus-tools 28sudo apt install opus-tools
31#+end_src 29```
32 30
33If you want to use a different conversion method, such as =ffmpeg= or 31If you want to use a different conversion method, such as
34=avconv=, simply install that package instead. 32`ffmpeg` or `avconv`, simply install that package
33instead.
34
35### Conversion Process
35 36
36*** Conversion Process
37:PROPERTIES:
38:CUSTOM_ID: conversion-process
39:END:
40The script I'm using is stored in my home directory, but feel free to 37The script I'm using is stored in my home directory, but feel free to
41create it wherever you want. It does not need to be in the same 38create it wherever you want. It does not need to be in the same
42directory as your music files. 39directory as your music files.
43 40
44#+begin_src sh 41```sh
45cd ~ && nano transform.sh 42cd ~ && nano transform.sh
46#+end_src 43```
47 44
48Once you have your new bash script opened in an editor, go ahead and 45Once you have your new bash script opened in an editor, go ahead and
49paste the following logic into the script. 46paste the following logic into the script.
50 47
51You *MUST* edit the following variables in order for it to work: 48You **MUST** edit the following variables in order for it to work:
52 49
53- =source=: The source directory where your FLAC files are stored. 50- `source`: The source directory where your FLAC files are
54- =dest=: The destination directory where you want the resulting Opus 51 stored.
55 files to be stored. 52- `dest`: The destination directory where you want the
53 resulting Opus files to be stored.
56 54
57You *MAY* want to edit the following variables to suit your needs: 55You **MAY** want to edit the following variables to suit your needs:
58 56
59- =filename=: If you are converting to a file format other than Opus, 57- `filename`: If you are converting to a file format other
60 you'll need to edit this so that your resulting files have the correct 58 than Opus, you'll need to edit this so that your resulting files
61 filename extension. 59 have the correct filename extension.
62- =reldir=: This variable can be edited to strip out more leading 60- `reldir`: This variable can be edited to strip out more
63 directories in the file path. As you'll see later, I ignore this for 61 leading directories in the file path. As you'll see later, I ignore
64 now and simply clean it up afterward. 62 this for now and simply clean it up afterward.
65- =opusenc=: This is the actual conversion process. You may want to edit 63- `opusenc`: This is the actual conversion process. You may
66 the bitrate to suit your needs. I set mine at 128 but some prefer 160 64 want to edit the bitrate to suit your needs. I set mine at 128 but
67 or higher. 65 some prefer 160 or higher.
68 66
69#+begin_src sh 67```sh
70#!/bin/bash 68#!/bin/bash
71## - The IFS takes care of spaces in file and dirnames 69## - The IFS takes care of spaces in file and dirnames
72## - your folders may vary 70## - your folders may vary
@@ -128,55 +126,52 @@ opusenc --vbr --bitrate 128 --date "$DATE" \
128## just for testing 126## just for testing
129# sleep 1 127# sleep 1
130done 128done
131#+end_src 129```
132 130
133Once you're done, simply save the file and exit your editor. Don't 131Once you're done, simply save the file and exit your editor. Don't
134forget to enable execution of the script: 132forget to enable execution of the script:
135 133
136#+begin_src sh 134```sh
137chmod +x transform.sh 135chmod +x transform.sh
138#+end_src 136```
139 137
140Finally, you may now run the script: 138Finally, you may now run the script:
141 139
142#+begin_src sh 140```sh
143./transform.sh 141./transform.sh
144#+end_src 142```
145 143
146If you used =opusenc=, you'll see the conversions happen within the 144If you used `opusenc`, you'll see the conversions happen
147terminal as it progresses. You will also see variables printed if you 145within the terminal as it progresses. You will also see variables
148uncommented any of the bash script's comments. 146printed if you uncommented any of the bash script's comments.
149 147
150*** Cleanup 148### Cleanup
151:PROPERTIES: 149
152:CUSTOM_ID: cleanup 150As I noted above, I didn't customize my `reldir` variable in
153:END: 151the script, which caused my output directory to be
154As I noted above, I didn't customize my =reldir= variable in the script, 152`/mnt/music/library/archives` instead of
155which caused my output directory to be =/mnt/music/library/archives= 153`/mnt/music/library`. So, I moved the output up one level and
156instead of =/mnt/music/library=. So, I moved the output up one level and
157deleted the accidental directory. 154deleted the accidental directory.
158 155
159#+begin_src sh 156```sh
160cd /mnt/music/library 157cd /mnt/music/library
161mv archives/* . 158mv archives/* .
162rm -rf archives 159rm -rf archives
163#+end_src 160```
161
162### Check the Resulting Size
164 163
165*** Check the Resulting Size 164If you want to see what kind of file size savings you've gained, you
166:PROPERTIES: 165can always use the `du` command to check:
167:CUSTOM_ID: check-the-resulting-size
168:END:
169If you want to see what kind of file size savings you've gained, you can
170always use the =du= command to check:
171 166
172#+begin_src sh 167```sh
173cd /mnt/music 168cd /mnt/music
174du -h --max-depth=1 . 169du -h --max-depth=1 .
175#+end_src 170```
176 171
177In my case, my small library went from 78GB to 6.3GB! 172In my case, my small library went from 78GB to 6.3GB!
178 173
179#+begin_src txt 174```txt
18078G ./archives 17578G ./archives
1816.3G ./library 1766.3G ./library
182#+end_src 177```
blog/2022-07-31-bash-it.org → content/blog/2022-07-31-bash-it.md renamed +106 −122
@@ -1,126 +1,117 @@
1#+title: Upgrade Bash with Bash-It & Ble.sh 1+++
2#+date: 2022-07-31 2date = 2022-07-31
3title = "Upgrade Bash with Bash-It & Ble.sh"
4description = "Learn how to increase the power of bash with Bash-It and Ble.sh."
5+++
6
7## Bash
3 8
4** Bash
5:PROPERTIES:
6:CUSTOM_ID: bash
7:END:
8For those who are not familiar, 9For those who are not familiar,
9[[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][Bash]] is a Unix 10[Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) is a Unix shell
10shell that is used as the default login shell for most Linux 11that is used as the default login shell for most Linux distributions.
11distributions. This shell and command processor should be familiar if 12This shell and command processor should be familiar if you've used
12you've used Linux (or older version of macOS) before. 13Linux (or older version of macOS) before.
13 14
14However, bash is not the only option. There are numerous other shells 15However, bash is not the only option. There are numerous other shells
15that exist. Here are some popular examples: 16that exist. Here are some popular examples:
16 17
17- [[https://en.wikipedia.org/wiki/Z_shell][zsh]] 18- [zsh](https://en.wikipedia.org/wiki/Z_shell)
18- [[https://en.wikipedia.org/wiki/Fish_(Unix_shell)][fish]] 19- [fish](https://en.wikipedia.org/wiki/Fish_(Unix_shell))
19- [[https://github.com/ibara/oksh][oksh]] 20- [oksh](https://github.com/ibara/oksh)
20- [[https://wiki.gentoo.org/wiki/Mksh][mksh]] 21- [mksh](https://wiki.gentoo.org/wiki/Mksh)
21- [[https://en.wikipedia.org/wiki/Debian_Almquist_shell][dash]] 22- [dash](https://en.wikipedia.org/wiki/Debian_Almquist_shell)
22 23
23While each shell has its differences, bash is POSIX compliant and the 24While each shell has its differences, bash is POSIX compliant and the
24default for many Linux users. Because of this, I am going to explore a 25default for many Linux users. Because of this, I am going to explore a
25program called =bash-it= below that helps bash users increase the 26program called `bash-it` below that helps bash users increase
26utility of their shell without installing a completely new shell. 27the utility of their shell without installing a completely new shell.
28
29### Installation
27 30
28*** Installation
29:PROPERTIES:
30:CUSTOM_ID: installation
31:END:
32First, if bash is not already installed on your system, you can 31First, if bash is not already installed on your system, you can
33[[https://www.gnu.org/software/bash/][download bash from GNU]] or use 32[download bash from GNU](https://www.gnu.org/software/bash/) or use your
34your package manager to install it. 33package manager to install it.
35 34
36For example, this is how you can install bash on Fedora Linux: 35For example, this is how you can install bash on Fedora Linux:
37 36
38#+begin_src sh 37```sh
39sudo dnf install bash 38sudo dnf install bash
40#+end_src 39```
41 40
42If you are not using bash as your default shell, use the =chsh= command 41If you are not using bash as your default shell, use the
43to change your shell: 42`chsh` command to change your shell:
44 43
45#+begin_src sh 44```sh
46chsh 45chsh
47#+end_src 46```
48 47
49You should see a prompt like the one below. If the brackets (=[]=) 48You should see a prompt like the one below. If the brackets
50contain =bash= already, you're done, and you can simply continue by 49(`[]`) contain `bash` already, you're done, and
51hitting the Enter key. 50you can simply continue by hitting the Enter key.
52 51
53If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter 52If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter
54the path to the bash program on your system (it's most likely located at 53the path to the bash program on your system (it's most likely located
55=/usr/bin/bash=). 54at `/usr/bin/bash`).
56 55
57#+begin_src sh 56```sh
58Changing shell for <user>. 57Changing shell for <user>.
59New shell [/usr/bin/bash]: 58New shell [/usr/bin/bash]:
60#+end_src 59```
61 60
62You must log out or restart the machine in order for the login shell to 61You must log out or restart the machine in order for the login shell to
63be refreshed. You can do it now or wait until you're finished 62be refreshed. You can do it now or wait until you're finished
64customizing the shell. 63customizing the shell.
65 64
66#+begin_src sh 65```sh
67sudo reboot now 66sudo reboot now
68#+end_src 67```
69 68
70** Bash-it 69## Bash-it
71:PROPERTIES:
72:CUSTOM_ID: bash-it
73:END:
74As noted on the [[https://github.com/Bash-it/bash-it][Bash-it]]
75repository:
76 70
77#+begin_quote 71As noted on the [Bash-it](https://github.com/Bash-it/bash-it)
78Bash-it is a collection of community Bash commands and scripts for Bash 72repository:
793.2+. (And a shameless ripoff of oh-my-zsh 😃)
80 73
81#+end_quote 74> Bash-it is a collection of community Bash commands and scripts for
75> Bash 3.2+. (And a shameless ripoff of oh-my-zsh 😃)
82 76
83Bash-it makes it easy to install plugins, set up aliases for common 77Bash-it makes it easy to install plugins, set up aliases for common
84commands, and easily change the visual theme of your shell. 78commands, and easily change the visual theme of your shell.
85 79
86*** Installation 80### Installation
87:PROPERTIES: 81
88:CUSTOM_ID: installation-1
89:END:
90To install the framework, simply copy the repository files and use the 82To install the framework, simply copy the repository files and use the
91=install.sh= script provided. If you want, you can (and should!) inspect 83`install.sh` script provided. If you want, you can (and
92the contents of the installation script before you run it. 84should!) inspect the contents of the installation script before you run
85it.
93 86
94#+begin_src sh 87```sh
95git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it 88git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it
96~/.bash_it/install.sh 89~/.bash_it/install.sh
97#+end_src 90```
98 91
99If you didn't restart your session after making bash the default, and 92If you didn't restart your session after making bash the default, and
100are currently working within another shell, be sure to enter a bash 93are currently working within another shell, be sure to enter a bash
101session before using =bash-it=: 94session before using `bash-it`:
102 95
103#+begin_src sh 96```sh
104bash 97bash
105#+end_src 98```
99
100### Aliases
106 101
107*** Aliases
108:PROPERTIES:
109:CUSTOM_ID: aliases
110:END:
111Bash-it contains a number of aliases for common commands to help improve 102Bash-it contains a number of aliases for common commands to help improve
112efficiency in the terminal. To list all available options, use the 103efficiency in the terminal. To list all available options, use the
113following command: 104following command:
114 105
115#+begin_src sh 106```sh
116bash-it show aliases 107bash-it show aliases
117#+end_src 108```
118 109
119This will provide you a list that looks like the following text block. 110This will provide you a list that looks like the following text block.
120Within this screen, you will be able to see all available options and 111Within this screen, you will be able to see all available options and
121which ones are currently enabled. 112which ones are currently enabled.
122 113
123#+begin_src txt 114```txt
124Alias Enabled? Description 115Alias Enabled? Description
125ag [ ] the silver searcher (ag) aliases 116ag [ ] the silver searcher (ag) aliases
126ansible [ ] ansible abbreviations 117ansible [ ] ansible abbreviations
@@ -133,35 +124,33 @@ clipboard [ ] xclip shortcuts
133composer [ ] common composer abbreviations 124composer [ ] common composer abbreviations
134curl [x] Curl aliases for convenience. 125curl [x] Curl aliases for convenience.
135... 126...
136#+end_src 127```
137 128
138To enable an alias, do: 129To enable an alias, do:
139 130
140#+begin_src sh 131```sh
141bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all 132bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all
142#+end_src 133```
143 134
144To disable an alias, do: 135To disable an alias, do:
145 136
146#+begin_src sh 137```sh
147bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all 138bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all
148#+end_src 139```
140
141### Plugins
149 142
150*** Plugins
151:PROPERTIES:
152:CUSTOM_ID: plugins
153:END:
154Similar to aliases, plugins are available with bash-it. You can find a 143Similar to aliases, plugins are available with bash-it. You can find a
155complete list of plugins in the same way as aliases. Simply execute the 144complete list of plugins in the same way as aliases. Simply execute the
156following: 145following:
157 146
158#+begin_src sh 147```sh
159bash-it show plugins 148bash-it show plugins
160#+end_src 149```
161 150
162You will see the following output showing enabled and disabled plugins: 151You will see the following output showing enabled and disabled plugins:
163 152
164#+begin_src txt 153```txt
165Plugin Enabled? Description 154Plugin Enabled? Description
166alias-completion [ ] 155alias-completion [ ]
167autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details 156autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details
@@ -172,87 +161,82 @@ battery [x] display info about your battery charge level
172blesh [ ] load ble.sh, the Bash line editor! 161blesh [ ] load ble.sh, the Bash line editor!
173boot2docker [ ] Helpers to get Docker setup correctly for boot2docker 162boot2docker [ ] Helpers to get Docker setup correctly for boot2docker
174browser [ ] render commandline output in your browser 163browser [ ] render commandline output in your browser
175#+end_src 164```
176 165
177To enable a plugin, do: 166To enable a plugin, do:
178 167
179#+begin_src sh 168```sh
180bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all 169bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all
181#+end_src 170```
182 171
183To disable a plugin, do: 172To disable a plugin, do:
184 173
185#+begin_src sh 174```sh
186bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all 175bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all
187#+end_src 176```
177
178### Themes
188 179
189*** Themes
190:PROPERTIES:
191:CUSTOM_ID: themes
192:END:
193There are quite a few pre-defined 180There are quite a few pre-defined
194[[https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes][themes]] 181[themes](https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes)
195available with bash-it. 182available with bash-it.
196 183
197To list all themes: 184To list all themes:
198 185
199#+begin_src sh 186```sh
200ls ~/.bash_it/themes/ 187ls ~/.bash_it/themes/
201#+end_src 188```
202 189
203To use a new theme, you'll need to edit =.bashrc= and alter the 190To use a new theme, you'll need to edit `.bashrc` and alter
204=BASH_IT_THEME= variable to your desired theme. For example, I am using 191the `BASH_IT_THEME` variable to your desired theme. For
205the =zork= theme. 192example, I am using the `zork` theme.
206 193
207#+begin_src sh 194```sh
208nano ~/.bashrc 195nano ~/.bashrc
209#+end_src 196```
210 197
211#+begin_src sh 198```sh
212export BASH_IT_THEME='zork' 199export BASH_IT_THEME='zork'
213#+end_src 200```
214 201
215Once you save your changes, you just need to exit your terminal and 202Once you save your changes, you just need to exit your terminal and
216create a new one in order to see your changes to the =.bashrc= file. You 203create a new one in order to see your changes to the
217can also =source= the file to see changes, but I recommend starting a 204`.bashrc` file. You can also `source` the file to
218completely new shell instead. 205see changes, but I recommend starting a completely new shell instead.
219 206
220** ble.sh 207## ble.sh
221:PROPERTIES: 208
222:CUSTOM_ID: ble.sh 209One big feature I was missing in Bash that both `zsh` and
223:END: 210`fish` have is an autosuggestion feature. To explain: as you
224One big feature I was missing in Bash that both =zsh= and =fish= have is 211type, an autosuggestion feature in the shell will offer suggestions in a
225an autosuggestion feature. To explain: as you type, an autosuggestion 212lighter font color beyond the characters already typed. Once you see the
226feature in the shell will offer suggestions in a lighter font color 213command you want, you can click the right arrow and have the shell
227beyond the characters already typed. Once you see the command you want, 214auto-complete that line for you.
228you can click the right arrow and have the shell auto-complete that line 215
229for you. 216Luckily, the [Bash Line Editor](https://github.com/akinomyoga/ble.sh)
230
231Luckily, the [[https://github.com/akinomyoga/ble.sh][Bash Line Editor]]
232(ble.sh) exists! This program provides a wonderful autosuggestions 217(ble.sh) exists! This program provides a wonderful autosuggestions
233feature perfectly, among other features that I haven't tested yet. 218feature perfectly, among other features that I haven't tested yet.
234 219
235In order to install ble.sh, execute the following: 220In order to install ble.sh, execute the following:
236 221
237#+begin_src sh 222```sh
238git clone --recursive https://github.com/akinomyoga/ble.sh.git 223git clone --recursive https://github.com/akinomyoga/ble.sh.git
239make -C ble.sh install PREFIX=~/.local 224make -C ble.sh install PREFIX=~/.local
240echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc 225echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc
241#+end_src 226```
242 227
243Again, exit the terminal and open a new one in order to see the 228Again, exit the terminal and open a new one in order to see the
244newly-configured shell. 229newly-configured shell.
245 230
246** Restart the Session 231## Restart the Session
247:PROPERTIES: 232
248:CUSTOM_ID: restart-the-session
249:END:
250Finally, as mentioned above, you'll need to restart the session to 233Finally, as mentioned above, you'll need to restart the session to
251ensure that your user is using bash by default. 234ensure that your user is using bash by default.
252 235
253You will also need to exit and re-open a shell (e.g., terminal or 236You will also need to exit and re-open a shell (e.g., terminal or
254terminal tab) any time you make changes to the =.bashrc= file. 237terminal tab) any time you make changes to the `.bashrc`
238file.
255 239
256#+begin_src sh 240```sh
257sudo reboot now 241sudo reboot now
258#+end_src 242```
blog/2022-08-31-privacy.com-changes.org → content/blog/2022-08-31-privacy.com-changes.md renamed +34 −46
@@ -1,25 +1,24 @@
1#+title: Concerning Changes on Privacy.com 1+++
2#+date: 2022-08-31 2date = 2022-08-31
3title = "Concering Changes on Privacy.com"
4description = "My thoughts on Privacy.com changing from a prepaid model to a credit charge model."
5+++
6
7## Privacy.com Changes Their Terms
3 8
4** Privacy.com Changes Their Terms
5:PROPERTIES:
6:CUSTOM_ID: privacy.com-changes-their-terms
7:END:
8Recently, Privacy.com reached out to their customers regarding a change 9Recently, Privacy.com reached out to their customers regarding a change
9in their terms of use. Further, all customers are required to agree to 10in their terms of use. Further, all customers are required to agree to
10the changes in order to continue using their accounts. 11the changes in order to continue using their accounts.
11 12
12[[https://privacy.com/commercial-cardholder-agreement][You can view the 13[You can view the new cardholder agreement
13new cardholder agreement here]]. 14here](https://privacy.com/commercial-cardholder-agreement).
14 15
15When you log in, you'll be greeted with a pop-up window asking you to 16When you log in, you'll be greeted with a pop-up window asking you to
16review and agree to the new terms of use. You will also not be able to 17review and agree to the new terms of use. You will also not be able to
17open any new cards until the terms are agreed to. 18open any new cards until the terms are agreed to.
18 19
19*** Changing from a "Prepaid Debit" Model to a "Charge Card" Model 20### Changing from a "Prepaid Debit" Model to a "Charge Card" Model
20:PROPERTIES: 21
21:CUSTOM_ID: changing-from-a-prepaid-debit-model-to-a-charge-card-model
22:END:
23The actual content of the changes is interesting. While the historical 22The actual content of the changes is interesting. While the historical
24model of using Privacy.com was akin to prepaid debit cards, the new 23model of using Privacy.com was akin to prepaid debit cards, the new
25model is very similar to a credit card (they use the term "charge 24model is very similar to a credit card (they use the term "charge
@@ -38,17 +37,15 @@ charges through Privacy.com and set the merchant as one of their pre-set
38options, such as "Smiley's Corner Store" or "NSA Gift Shop." 37options, such as "Smiley's Corner Store" or "NSA Gift Shop."
39 38
40The new model still works with a bank account as a funding source, but 39The new model still works with a bank account as a funding source, but
41the model is changed so that you get a "line of credit" set according to 40the model is changed so that you get a "line of credit" set according
42a 14-day billing cycle. It seems that Privacy.com will now allow charges 41to a 14-day billing cycle. It seems that Privacy.com will now allow
43to be incurred without being immediately paid. 42charges to be incurred without being immediately paid.
44 43
45*** Daily Payments and Available Credit 44### Daily Payments and Available Credit
46:PROPERTIES: 45
47:CUSTOM_ID: daily-payments-and-available-credit
48:END:
49Instead of paying as charges are incurred, you must make a "Daily 46Instead of paying as charges are incurred, you must make a "Daily
50Payment" and your "Available Credit" will be locked until you make that 47Payment" and your "Available Credit" will be locked until you make
51payment. There are also "End of Billing Cycle Payments" that are 48that payment. There are also "End of Billing Cycle Payments" that are
52assigned a due date. 49assigned a due date.
53 50
54Further, Privacy.com will decline charges that would cause you to exceed 51Further, Privacy.com will decline charges that would cause you to exceed
@@ -56,23 +53,18 @@ your Available Credit or Credit Limit.
56 53
57One particular interesting section states the following: 54One particular interesting section states the following:
58 55
59#+begin_quote 56> YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER
60YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER 57> OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT
61OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT 58> PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE
62PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE 59> WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL
63WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL 60> LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY
64LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT 61> AGREEMENT FOR MORE INFORMATION.
65FOR MORE INFORMATION.
66 62
67#+end_quote 63### Personal Information
68 64
69*** Personal Information
70:PROPERTIES:
71:CUSTOM_ID: personal-information
72:END:
73Now that Privacy.com is more of a financial institution, they are 65Now that Privacy.com is more of a financial institution, they are
74obligated to comply with the 66obligated to comply with the [know your
75[[https://en.wikipedia.org/wiki/Know_your_customer][know your customer]] 67customer](https://en.wikipedia.org/wiki/Know_your_customer)
76guidelines/laws. 68guidelines/laws.
77 69
78I did not proceed with the change to my Privacy.com account, but I have 70I did not proceed with the change to my Privacy.com account, but I have
@@ -81,17 +73,13 @@ to be submitted, such as SSN. I am not aware of all new personal
81information required or if the funding source is now required to only be 73information required or if the funding source is now required to only be
82a bank account. 74a bank account.
83 75
84*** Fees 76### Fees
85:PROPERTIES: 77
86:CUSTOM_ID: fees
87:END:
88Luckily, the fees section did not change much. The subscription fees for 78Luckily, the fees section did not change much. The subscription fees for
89a premium account are still the only fees. 79a premium account are still the only fees.
90 80
91** My Thoughts 81## My Thoughts
92:PROPERTIES: 82
93:CUSTOM_ID: my-thoughts
94:END:
95Personally, I wiped my personal information from my account and then 83Personally, I wiped my personal information from my account and then
96permanently deleted it when I heard about these changes. I have no 84permanently deleted it when I heard about these changes. I have no
97interest in yet another method of credit lending offered by private 85interest in yet another method of credit lending offered by private
@@ -105,6 +93,6 @@ This type of change toward a "buy it now, pay us later" model is
105concerning, and I will be watching Privacy.com to see if they further 93concerning, and I will be watching Privacy.com to see if they further
106their interests in the credit model as time goes on. 94their interests in the credit model as time goes on.
107 95
108Could we see them start charging interest, fees, etc.? I'm not sure, but 96Could we see them start charging interest, fees, etc.? I'm not sure,
109this change does not inspire confidence in their mission as a 97but this change does not inspire confidence in their mission as a
110privacy-focused company. 98privacy-focused company.
blog/2022-09-17-serenity-os.org → content/blog/2022-09-17-serenity-os.md renamed +55 −66
@@ -1,11 +1,12 @@
1#+title: Serenity OS: Testing Out a Unique System 1+++
2#+date: 2022-09-17 2date = 2022-09-17
3 3title = "Serenity OS: Testing Out a Unique System"
4** Overview 4description = "A quick look at the Serenity operating system."
5:PROPERTIES: 5+++
6:CUSTOM_ID: overview 6
7:END: 7## Overview
8[[https://serenityos.org][SerenityOS]] is a unique operating system (OS) 8
9[SerenityOS](https://serenityos.org) is a unique operating system (OS)
9that I have seen pop up in my news feed a few times over the last few 10that I have seen pop up in my news feed a few times over the last few
10years, but I have never had time to test it out until now. 11years, but I have never had time to test it out until now.
11 12
@@ -15,51 +16,42 @@ to my youth.
15 16
16Per their website: 17Per their website:
17 18
18#+begin_quote 19> A graphical Unix-like operating system for desktop computers!
19A graphical Unix-like operating system for desktop computers! 20>
20 21> SerenityOS is a love letter to '90s user interfaces with a custom
21SerenityOS is a love letter to '90s user interfaces with a custom 22> Unix-like core. It flatters with sincerity by stealing beautiful ideas
22Unix-like core. It flatters with sincerity by stealing beautiful ideas 23> from various other systems.
23from various other systems. 24>
24 25> Roughly speaking, the goal is a marriage between the aesthetic of
25Roughly speaking, the goal is a marriage between the aesthetic of 26> late-1990s productivity software and the power-user accessibility of
26late-1990s productivity software and the power-user accessibility of 27> late-2000s *nix.
27late-2000s *nix. 28>
29> This is a system by us, for us, based on the things we like.
28 30
29This is a system by us, for us, based on the things we like. 31## Building
30 32
31#+end_quote 33Your first question may be "Where's the iso?" and the answer is...
34there are none. SerenityOS does not provide pre-built images for
35testing. You must build the images yourself. This seems intentionally to
36limit participation to only those who are truly interested enough to
37learn how to build the OS.
32 38
33** Building 39### Clone
34:PROPERTIES:
35:CUSTOM_ID: building
36:END:
37Your first question may be "Where's the iso?" and the answer is... there
38are none. SerenityOS does not provide pre-built images for testing. You
39must build the images yourself. This seems intentionally to limit
40participation to only those who are truly interested enough to learn how
41to build the OS.
42 40
43*** Clone
44:PROPERTIES:
45:CUSTOM_ID: clone
46:END:
47In order to get started, you'll need to clone the source repository: 41In order to get started, you'll need to clone the source repository:
48 42
49#+begin_src sh 43```sh
50git clone https://github.com/SerenityOS/serenity && cd serenity 44git clone https://github.com/SerenityOS/serenity && cd serenity
51#+end_src 45```
52 46
53*** Build 47### Build
54:PROPERTIES: 48
55:CUSTOM_ID: build 49Note that I followed the [Build
56:END: 50Instructions](https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md)
57Note that I followed the 51in the SerenityOS repository as of commit
58[[https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md][Build 52`660d2b53b1206e868d5470eee80b5e62d7e30da7`. Things may have
59Instructions]] in the SerenityOS repository as of commit 53changed since my installation, and you should double-check the
60=660d2b53b1206e868d5470eee80b5e62d7e30da7=. Things may have changed 54instructions first.
61since my installation, and you should double-check the instructions
62first.
63 55
64Regardless, I want to repeat my steps here to illustrate any errors or 56Regardless, I want to repeat my steps here to illustrate any errors or
65differing commands I needed to run in order to build and run SerenityOS. 57differing commands I needed to run in order to build and run SerenityOS.
@@ -67,61 +59,58 @@ differing commands I needed to run in order to build and run SerenityOS.
67Since I am running Fedora, I needed to install these packages in order 59Since I am running Fedora, I needed to install these packages in order
68to build the OS images: 60to build the OS images:
69 61
70#+begin_src sh 62```sh
71sudo 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 63sudo dnf install texinfo binutils-devel curl cmake mpfr-devel libmpc-devel gmp-devel e2fsprogs ninja-build patch ccache rsync @"C Development Tools and Libraries" @Virtualization
72#+end_src 64```
73 65
74Next, make sure you're inside the =serenity= directory created earlier 66Next, make sure you're inside the `serenity` directory
75during the git cloning process and process to build the toolchain: 67created earlier during the git cloning process and process to build the
68toolchain:
76 69
77#+begin_src sh 70```sh
78Meta/serenity.sh rebuild-toolchain 71Meta/serenity.sh rebuild-toolchain
79#+end_src 72```
80 73
81Once the toolchain is built, you can Build and run the OS! 74Once the toolchain is built, you can Build and run the OS!
82 75
83#+begin_src sh 76```sh
84Meta/serenity.sh run 77Meta/serenity.sh run
85#+end_src 78```
86 79
87After this process is completed, the image should run automatically and 80After this process is completed, the image should run automatically and
88launch. 81launch.
89 82
90** Issues 83## Issues
91:PROPERTIES: 84
92:CUSTOM_ID: issues
93:END:
94I played around in SerenityOS for an hour or two in order to see what I 85I played around in SerenityOS for an hour or two in order to see what I
95could do and had a lot of fun with it. The only issue I ran into was a 86could do and had a lot of fun with it. The only issue I ran into was a
96lack of working internet. I didn't try very hard, but I could tell that 87lack of working internet. I didn't try very hard, but I could tell that
97the main network link wasn't connecting to my Fedora host properly. 88the main network link wasn't connecting to my Fedora host properly.
98 89
99** Screenshots 90## Screenshots
100:PROPERTIES: 91
101:CUSTOM_ID: screenshots
102:END:
103The initial launch of the image displays the SerenityOS desktop, with a 92The initial launch of the image displays the SerenityOS desktop, with a
104simple terminal already launched: 93simple terminal already launched:
105 94
106[[https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png]] 95![](https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png)
107 96
108Here you can see the Fire application (literally just shows fire 97Here you can see the Fire application (literally just shows fire
109burning), a browser with the local Serenity Browser page loaded, and a 98burning), a browser with the local Serenity Browser page loaded, and a
110text editor. 99text editor.
111 100
112[[https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png]] 101![](https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png)
113 102
114I also poked around the system utilities and found most tools you'd 103I also poked around the system utilities and found most tools you'd
115expect to find within a standard desktop. 104expect to find within a standard desktop.
116 105
117[[https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png]] 106![](https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png)
118 107
119Lastly, I noted that the default desktop contains numerous pre-defined 108Lastly, I noted that the default desktop contains numerous pre-defined
120themes to choose from. This is a small piece, but it's actually 109themes to choose from. This is a small piece, but it's actually
121wonderful to see desktop developers consider theming directly out of the 110wonderful to see desktop developers consider theming directly out of the
122box rather than using an addon-based mentality. 111box rather than using an addon-based mentality.
123 112
124[[https://img.cleberg.net/blog/20220917-serenityos/themes.png]] 113![](https://img.cleberg.net/blog/20220917-serenityos/themes.png)
125 114
126I didn't take a screenshot of the other pre-installed games, but I did 115I didn't take a screenshot of the other pre-installed games, but I did
127spend nearly 30 minutes playing Solitaire before remembering that I was 116spend nearly 30 minutes playing Solitaire before remembering that I was
content/blog/2022-09-21-graphene-os.md added +167
@@ -0,0 +1,167 @@
1+++
2date = 2022-09-21
3title = "Installing Graphene OS on the Pixel 6 Pro"
4description = "A retrospective on the successful command-line installation of Graphene OS on a Pixel 6 Pro."
5+++
6
7## Introduction
8
9After using iOS for a couple of years, I finally took the plunge and
10purchased a Pixel 6 Pro in order to test and use [GrapheneOS]
11(<https://grapheneos.org>).
12
13The installation process was rather quick once you have the tools and
14files you need. Overall, it can be done in just a few minutes.
15
16## Gathering Tools & Files
17
18### Android Tools
19
20First, in order to interact with the device, we will need the [Android
21platform
22tools](https://developer.android.com/studio/releases/platform-tools.html).
23Find the Linux download and save the ZIP folder to your preferred
24location.
25
26Once we've downloaded the files, we will need to unzip them, enter the
27directory, and move the necessary executables to a central location,
28such as `/usr/bin/`. For this installation, we only need the
29`fastboot` and `adb` executables.
30
31```sh
32cd ~/Downloads
33```
34
35```sh
36unzip platform-tools_r33.0.3-linux.zip
37cd platform-tools
38sudo mv fastboot /usr/bin/
39sudo mv adb /usr/bin
40```
41
42### GrapheneOS Files
43
44Next, we need the [GrapheneOS files](https://grapheneos.org/releases)
45for our device and model. For example, the Pixel 6 Pro is codenamed
46`raven` on the release page.
47
48Once we have the links, let's download them to our working directory:
49
50```sh
51curl -O https://releases.grapheneos.org/factory.pub
52curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
53curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
54```
55
561. Validate Integrity
57
58 In order to validate the integrity of the downloaded files, we will
59 need the `signify` package and Graphene's
60 `factory.pub` file.
61
62 ```sh
63 sudo dnf install signify
64 ```
65
66 ```sh
67 curl -O https://releases.grapheneos.org/factory.pub
68 ```
69
70 Then we can validate the files and ensure that no data was corrupted
71 or modified before it was saved to our device.
72
73 ```sh
74 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
75 ```
76
772. Unzip Files
78
79 Once the files are verified, we can unzip the Graphene image and
80 enter the directory:
81
82 ```sh
83 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
84 ```
85
86## Installation Process
87
88### Enable Developer Debugging & OEM Unlock
89
90Before we can actually flash anything to the phone, we will need to
91enable OEM Unlocking, as well as either USB Debugging or Wireless
92Debugging, depending on which method we will be using.
93
94To start, enable developer mode by going to `Settings` >
95`About` and tapping `Build Number` seven (7)
96times. You may need to enter your PIN to enable this mode.
97
98Once developer mode is enabled, go to `Settings` >
99`System` > `Devloper Options` and enable OEM
100Unlocking, as well as USB or Wireless Debugging. In my case, I chose USB
101Debugging and performed all actions via USB cable.
102
103Once these options are enabled, plug the phone into the computer and
104execute the following command:
105
106```sh
107adb devices
108```
109
110If an unauthorized error occurs, make sure the USB mode on the phone is
111changed from charging to something like "File Transfer" or "PTP."
112You can find the USB mode in the notification tray.
113
114### Reboot Device
115
116Once we have found the device via `adb`, we can either boot
117into the bootloader interface by holding the volume down button while
118the phone reboots or by executing the following command:
119
120```sh
121adb reboot bootloader
122```
123
124### Unlock the Bootloader
125
126The phone will reboot and load the bootloader screen upon startup. At
127this point, we are ready to start the actual flashing of GrapheneOS onto
128the device.
129
130**NOTE**: In my situation, I needed to use `sudo` with every
131`fastboot` command, but not with `adb` commands. I
132am not sure if this is standard or a Fedora quirk, but I'm documenting
133my commands verbatim in this post.
134
135First, we start by unlocking the bootloader so that we can load other
136ROMs:
137
138```sh
139sudo fastboot flashing unlock
140```
141
142### Flashing Factory Images
143
144Once the phone is unlocked, we can flash it with the
145`flash-all.sh` script found inside the
146`raven-factory-2022091400` folder we entered earlier:
147
148```sh
149sudo ./flash-all.sh
150```
151
152This process should take a few minutes and will print informational
153messages as things progress. Avoid doing anything on the phone while
154this process is operating.
155
156### Lock the Bootloader
157
158If everything was successful, the phone should reboot a few times and
159finally land back on the bootloader screen. At this point, we can
160re-lock the bootloader to enable full verified boot and protect the
161device from unwanted flashing or erasure of data.
162
163```sh
164sudo fastboot flashing lock
165```
166
167Once done, the device will be wiped and ready for a fresh set-up!
blog/2022-10-04-mtp-linux.org → content/blog/2022-10-04-mtp-linux.md renamed +36 −41
@@ -1,14 +1,16 @@
1#+title: How to Mount an MTP Mobile Device on Linux 1+++
2#+date: 2022-10-04 2date = 2022-10-04
3title = "How to Mount an MTP Mobile Device on Fedora Linux"
4description = "Learn how to mount an MTP mobile device on Fedora Linux."
5+++
3 6
4I recently ran into trouble attempting to mount my GrapheneOS phone to my laptop 7I recently ran into trouble attempting to mount my GrapheneOS phone to
5running Fedora Linux via the [[https://en.wikipedia.org/wiki/Media_transfer_protocol][Media Transfer Protocol]] (MTP) and discovered a 8my laptop running Fedora Linux via the [Media Transfer
6simple and effective solution. 9Protocol](https://en.wikipedia.org/wiki/Media_transfer_protocol) (MTP)
10and discovered a simple and effective solution.
11
12## Use a USB 3.0 Port
7 13
8** Use a USB 3.0 Port
9:PROPERTIES:
10:CUSTOM_ID: use-a-usb-3.0-port
11:END:
12First, ensure that the device was plugged in to the laptop through a USB 14First, 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 153.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 16ports may cause issues with dropped connections over MTP. This is purely
@@ -16,70 +18,63 @@ anecdotal 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 18causes issues, but I can confirm that switching to a USB 3.0 port seemed
17to cut out most of my issues. 19to cut out most of my issues.
18 20
19** Switch USB Preferences to MTP 21## Switch USB Preferences to MTP
20:PROPERTIES: 22
21:CUSTOM_ID: switch-usb-preferences-to-mtp
22:END:
23Secondly, you need to ensure that the phone's USB preferences/mode is 23Secondly, you need to ensure that the phone's USB preferences/mode is
24changed to MTP or File Transfer once the phone is plugged in. Other 24changed to MTP or File Transfer once the phone is plugged in. Other
25modes will not allow you to access the phone's file system. 25modes will not allow you to access the phone's file system.
26 26
27** Install =jmtpfs= 27## Install `jmtpfs`
28:PROPERTIES: 28
29:CUSTOM_ID: install-jmtpfs 29Next, I used the `jmtpfs` package to mount my phone to my
30:END: 30laptop. There are other packages that exist, but this one worked
31Next, I used the =jmtpfs= package to mount my phone to my laptop. There 31perfectly for me. On Fedora Linux, you can install it like this:
32are other packages that exist, but this one worked perfectly for me. On
33Fedora Linux, you can install it like this:
34 32
35#+begin_src sh 33```sh
36sudo dnf install jmtpfs -y 34sudo dnf install jmtpfs -y
37#+end_src 35```
36
37## Create a Mount Point
38 38
39** Create a Mount Point
40:PROPERTIES:
41:CUSTOM_ID: create-a-mount-point
42:END:
43Once you have the package installed, you just need to create a folder 39Once you have the package installed, you just need to create a folder
44for the device to use as a mount point. In my case, I used =/mnt/pixel=: 40for the device to use as a mount point. In my case, I used
41`/mnt/pixel`:
45 42
46#+begin_src sh 43```sh
47sudo mkdir /mnt/pixel 44sudo mkdir /mnt/pixel
48sudo chown -R $USER:$USER /mnt/pixel 45sudo chown -R $USER:$USER /mnt/pixel
49#+end_src 46```
47
48## Mount & Access the Phone's File System
50 49
51** Mount & Access the Phone's File System
52:PROPERTIES:
53:CUSTOM_ID: mount-access-the-phones-file-system
54:END:
55Finally, plug-in and mount the device, and you should be able to see all 50Finally, plug-in and mount the device, and you should be able to see all
56storage (internal and external) inside your new folder! 51storage (internal and external) inside your new folder!
57 52
58#+begin_src sh 53```sh
59jmtpfs /mnt/pixel 54jmtpfs /mnt/pixel
60#+end_src 55```
61 56
62The output should look something like this: 57The output should look something like this:
63 58
64#+begin_src sh 59```sh
65Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP). 60Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP).
66Android device detected, assigning default bug flags 61Android device detected, assigning default bug flags
67#+end_src 62```
68 63
69Now you are mounted and can do anything you'd like with the device's 64Now you are mounted and can do anything you'd like with the device's
70files: 65files:
71 66
72#+begin_src sh 67```sh
73cd /mnt/pixel 68cd /mnt/pixel
74ls -lha 69ls -lha
75#+end_src 70```
76 71
77From here, you will be able to see any internal or external storage 72From here, you will be able to see any internal or external storage
78available on the device: 73available on the device:
79 74
80#+begin_src sh 75```sh
81total 0 76total 0
82drwxr-xr-x. 3 user user 0 Jan 1 1970 . 77drwxr-xr-x. 3 user user 0 Jan 1 1970 .
83drwxr-xr-x. 1 root root 10 Oct 4 13:29 .. 78drwxr-xr-x. 1 root root 10 Oct 4 13:29 ..
84drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage' 79drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage'
85#+end_src 80```
blog/2022-10-20-syncthing.org → content/blog/2022-10-20-syncthing.md renamed +91 −111
@@ -1,83 +1,70 @@
1#+title: Syncthing: A Minimal Self-Hosted Cloud Storage Solution 1+++
2#+date: 2022-10-20 2date = 2022-10-04
3title = "Syncthing: A Minimal Self-Hosted Cloud Storage Solution"
4description = ""
5+++
6
7## An Overview of Syncthing
3 8
4** An Overview of Syncthing
5:PROPERTIES:
6:CUSTOM_ID: an-overview-of-syncthing
7:END:
8If you've been looking around the self-hosted cloud storage space for a 9If you've been looking around the self-hosted cloud storage space for a
9while, you've undoubtedly run into someone suggesting 10while, you've undoubtedly run into someone suggesting
10[[https://syncthing.net][Syncthing]] as an option. However, it is an 11[Syncthing](https://syncthing.net) as an option. However, it is an
11unusual alternative for those users out there who are used to having a 12unusual alternative for those users out there who are used to having a
12centralized cloud server that serves as the "controller" of the data and 13centralized cloud server that serves as the "controller" of the data
13interacts with clients on devices to fetch files. 14and interacts with clients on devices to fetch files.
14 15
15This post is a walkthrough of the Syncthing software, how I set up my 16This post is a walkthrough of the Syncthing software, how I set up my
16personal storage, and some pros and cons of using the software. 17personal storage, and some pros and cons of using the software.
17 18
18** Installing Syncthing 19## Installing Syncthing
19:PROPERTIES: 20
20:CUSTOM_ID: installing-syncthing
21:END:
22To install Syncthing, visit the 21To install Syncthing, visit the
23[[https://syncthing.net/downloads/][Downloads]] page or install via your 22[Downloads](https://syncthing.net/downloads/) page or install via your
24device's package manager. 23device's package manager.
25 24
26*** Server & Desktop 25### Server & Desktop
27:PROPERTIES: 26
28:CUSTOM_ID: server-desktop
29:END:
30You can install Syncthing on servers and desktops via the Downloads page 27You can install Syncthing on servers and desktops via the Downloads page
31linked above or via the command-line. 28linked above or via the command-line.
32 29
33For Debian-based distros: 30For Debian-based distros:
34 31
35#+begin_src sh 32```sh
36sudo apt install syncthing 33sudo apt install syncthing
37#+end_src 34```
38 35
39For Fedora-based distros: 36For Fedora-based distros:
40 37
41#+begin_src sh 38```sh
42sudo dnf install syncthing 39sudo dnf install syncthing
43#+end_src 40```
41
42### Mobile
44 43
45*** Mobile
46:PROPERTIES:
47:CUSTOM_ID: mobile
48:END:
49Syncthing for Android is available on 44Syncthing for Android is available on
50[[https://f-droid.org/packages/com.nutomic.syncthingandroid/][F-Droid]] 45[F-Droid](https://f-droid.org/packages/com.nutomic.syncthingandroid/)
51and 46and [Google
52[[https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid][Google 47Play](https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid).
53Play]]. Syncthing does not have an official iOS client, but there is a 48Syncthing does not have an official iOS client, but there is a
54third-party client called 49third-party client called [Möbius
55[[https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216][Möbius 50Sync](https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216).
56Sync]]. 51
57 52## How Does Syncthing Work?
58** How Does Syncthing Work? 53
59:PROPERTIES:
60:CUSTOM_ID: how-does-syncthing-work
61:END:
62To start, I wanted to include the main marketing blurb from their 54To start, I wanted to include the main marketing blurb from their
63website: 55website:
64 56
65#+begin_quote 57> Syncthing is a continuous file synchronization program. It
66Syncthing is a continuous file synchronization program. It synchronizes 58> synchronizes files between two or more computers in real time, safely
67files between two or more computers in real time, safely protected from 59> protected from prying eyes. Your data is your data alone, and you
68prying eyes. Your data is your data alone, and you deserve to choose 60> deserve to choose where it is stored, whether it is shared with some
69where it is stored, whether it is shared with some third party, and how 61> third party, and how it's transmitted over the internet.
70it's transmitted over the internet.
71
72#+end_quote
73 62
74Let's break this apart and add in some other details to help explain 63Let's break this apart and add in some other details to help explain
75what exactly Syncthing does in order to sync files between devices. 64what exactly Syncthing does in order to sync files between devices.
76 65
77*** Local Syncthing Server(s) 66### Local Syncthing Server(s)
78:PROPERTIES: 67
79:CUSTOM_ID: local-syncthing-servers
80:END:
81Syncthing syncs files between multiple devices by creating a local 68Syncthing syncs files between multiple devices by creating a local
82server on each device. These local servers handle a few different 69server on each device. These local servers handle a few different
83things, such as watching files and directories for changes, hosting an 70things, such as watching files and directories for changes, hosting an
@@ -85,43 +72,40 @@ administrative GUI website, and authenticating with connected devices.
85 72
86You can also start, stop, and restart the Syncthing server via the 73You can also start, stop, and restart the Syncthing server via the
87command-line or web dashboard. If you're running Syncthing on a device 74command-line or web dashboard. If you're running Syncthing on a device
88with =systemd=, you can use the following commands: 75with `systemd`, you can use the following commands:
89 76
90#+begin_src sh 77```sh
91sudo systemctl start syncthing@username.service 78sudo systemctl start syncthing@username.service
92sudo systemctl restart syncthing@username.service 79sudo systemctl restart syncthing@username.service
93sudo systemctl stop syncthing@username.service 80sudo systemctl stop syncthing@username.service
94#+end_src 81```
82
83### Syncthing Dashboard
95 84
96*** Syncthing Dashboard
97:PROPERTIES:
98:CUSTOM_ID: syncthing-dashboard
99:END:
100This biggest part of Syncthing is the admin GUI website that runs on 85This biggest part of Syncthing is the admin GUI website that runs on
101each device (note that mobile devices will use the Syncthing app rather 86each device (note that mobile devices will use the Syncthing app rather
102than the web GUI). The admin GUI is available through the web browser on 87than the web GUI). The admin GUI is available through the web browser on
103the local device that is running Syncthing - simply go to 88the local device that is running Syncthing - simply go to
104=http://localhost:8384= or =http://127.0.0.1:8384=. This web page is the 89`http://localhost:8384` or
105place where you will change settings, add/modify synced files, and 90`http://127.0.0.1:8384`. This web page is the place where you
106add/modify connected devices. 91will change settings, add/modify synced files, and add/modify connected
92devices.
107 93
108Here's an example web GUI dashboard: 94Here's an example web GUI dashboard:
109 95
110#+caption: Syncthing Dashboard 96![Syncthing
111[[https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png]] 97Dashboard](https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png)
98
99### Remote Devices
112 100
113*** Remote Devices
114:PROPERTIES:
115:CUSTOM_ID: remote-devices
116:END:
117A cloud storage solution wouldn't be very useful if you aren't able to 101A cloud storage solution wouldn't be very useful if you aren't able to
118share data among various devices. Syncthing does this by sharing Device 102share data among various devices. Syncthing does this by sharing Device
119IDs to connect servers, and then by manually sharing Folders with 103IDs to connect servers, and then by manually sharing Folders with
120devices that have been connected. 104devices that have been connected.
121 105
122For instance, if you have a laptop running Syncthing and then install 106For instance, if you have a laptop running Syncthing and then install
123the Syncthing mobile app on a phone, you could scan the laptop's QR code 107the Syncthing mobile app on a phone, you could scan the laptop's QR
124for Device ID and then accept the authentication on the laptop's 108code for Device ID and then accept the authentication on the laptop's
125dashboard. Next, you can use either device to select a folder for 109dashboard. Next, you can use either device to select a folder for
126sharing and dictating which device should send, receive, or both. 110sharing and dictating which device should send, receive, or both.
127 111
@@ -130,10 +114,8 @@ which can add devices from the introducer to the device list, for
130mutually shared folders. You can also configure Auto Accept, 114mutually shared folders. You can also configure Auto Accept,
131compression, rate limits, and more settings per device. 115compression, rate limits, and more settings per device.
132 116
133** My Personal Cloud Storage Set-up 117## My Personal Cloud Storage Set-up
134:PROPERTIES: 118
135:CUSTOM_ID: my-personal-cloud-storage-set-up
136:END:
137Personally, I use a model similar to a traditional cloud storage 119Personally, I use a model similar to a traditional cloud storage
138service. I have a "centralized" server running 24/7 that acts as an 120service. I have a "centralized" server running 24/7 that acts as an
139Introducer for my Syncthing network. I think of this as my main storage 121Introducer for my Syncthing network. I think of this as my main storage
@@ -147,52 +129,50 @@ network, both running intermittently as they are not powered-on 24/7.
147The initial set-up of the software was easy enough, but data transfer 129The initial set-up of the software was easy enough, but data transfer
148rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged 130rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged
149my laptop into the ethernet network that my server is on and manually 131my laptop into the ethernet network that my server is on and manually
150copied my folders over to the server with =scp=. Once complete, 132copied my folders over to the server with `scp`. Once
151Syncthing validated that all files were there and not missing, and it 133complete, Syncthing validated that all files were there and not missing,
152did not need to transfer any data through the WAN. 134and it did not need to transfer any data through the WAN.
153 135
154As slow as the transfer was going, this probably saved me a few days of 136As slow as the transfer was going, this probably saved me a few days of
155waiting for my ~100GB sync. 137waiting for my ~100GB sync.
156 138
157** Pros & Cons 139## Pros & Cons
158:PROPERTIES: 140
159:CUSTOM_ID: pros-cons 141I've put together a short list of pros and cons for Syncthing. I
160:END: 142thought about my experiences with Nextcloud, WebDAV, proprietary
161I've put together a short list of pros and cons for Syncthing. I thought 143services (Google Drive, iCloud, etc.), and privacy-focused cloud
162about my experiences with Nextcloud, WebDAV, proprietary services 144solutions (pCloud, Tresorit, etc.).
163(Google Drive, iCloud, etc.), and privacy-focused cloud solutions 145
164(pCloud, Tresorit, etc.). 146**Pros:**
165 147
166*Pros:* 148- I've faced no data loss at all through my two-month trial run.
167 149- No third-parties store your data on their servers.
168- I've faced no data loss at all through my two-month trial run. 150- You have full control over your data and can take your data and
169- No third-parties store your data on their servers. 151 leave at any time.
170- You have full control over your data and can take your data and leave 152- It's possible to encrypt client-side easily with software like
171 at any time. 153 Cryptomator.
172- It's possible to encrypt client-side easily with software like 154- No proprietary clients or mounted volumes, just plain files and
173 Cryptomator. 155 folders.
174- No proprietary clients or mounted volumes, just plain files and 156
175 folders. 157**Cons:**
176 158
177*Cons:* 159- The learning curve is steeper than traditional cloud services and is
178 160 focused on a technical audience.
179- The learning curve is steeper than traditional cloud services and is 161- If a device needs to modify files in a Folder, the devices will need
180 focused on a technical audience. 162 to sync ALL files from the folder, which may be large. To avoid size
181- If a device needs to modify files in a Folder, the devices will need 163 restraints, split large folders into smaller folders for syncing.
182 to sync ALL files from the folder, which may be large. To avoid size 164- Syncing can be slow due to the clients/servers initially connecting
183 restraints, split large folders into smaller folders for syncing. 165 or re-connecting after sleeping.
184- Syncing can be slow due to the clients/servers initially connecting or 166- Multiple personal devices are required and require the user to own
185 re-connecting after sleeping. 167 or rent them as no third-party servers are involved in the storage
186- Multiple personal devices are required and require the user to own or 168 of data.
187 rent them as no third-party servers are involved in the storage of 169
188 data. 170Overall, I've had a great experience with Syncthing so far. I've had
189 171no data loss, syncing has been quick and easy when changes are made to
190Overall, I've had a great experience with Syncthing so far. I've had no
191data loss, syncing has been quick and easy when changes are made to
192files, device connections are reliable, and I love the freedom of 172files, device connections are reliable, and I love the freedom of
193controlling the clients and servers as I choose. 173controlling the clients and servers as I choose.
194 174
195Not to mention that I appreciate that I - or someone else - could pull 175Not to mention that I appreciate that I - or someone else - could pull
196the Syncthing [[https://github.com/syncthing][source code]] and continue 176the Syncthing [source code](https://github.com/syncthing) and continue
197development/support if the Syncthing Foundation decides to stop 177development/support if the Syncthing Foundation decides to stop
198developing the software or sells the business. 178developing the software or sells the business.
content/blog/2022-10-22-alpine-linux.md added +280
@@ -0,0 +1,280 @@
1+++
2date = 2022-10-22
3title = "Alpine Linux: My New Server OS"
4description = "A retrospective on installing and configuring Alpine Linux as my new server operating system."
5+++
6
7## Alpine Linux
8
9[Alpine Linux](https://alpinelinux.org) is a very small distro, built on
10musl libc and busybox. It uses ash as the default shell, OpenRC as the
11init system, and apk as the package manager. According to their website,
12an Alpine container "requires no more than 8 MB and a minimal
13installation to disk requires around 130 MB of storage." An actual bare
14metal machine is recommended to have 100 MB of RAM and 0-700 MB of
15storage space.
16
17Historically, I've used Ubuntu's minimal installation image as my
18server OS for the last five years. Ubuntu worked well and helped as my
19original server contained an nVidia GPU and no onboard graphics, so
20quite a few distros won't boot or install without a lot of tinkering.
21
22Alpine has given me a huge increase in performance across my Docker apps
23and Nginx websites. CPU load for the new server I'm using to test
24Alpine hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100
25CPU @ 3.70GHz.
26
27The only services I haven't moved over to Alpine are Plex Media Server
28and Syncthing, which may increase CPU load quite a bit depending on how
29many streams are running.
30
31### Installation
32
33In terms of installation, Alpine has an incredibly useful
34[wiki](https://wiki.alpinelinux.org/wiki/Installation) that will guide a
35user throughout the installation and post-installation processes, as
36well as various other articles and guides.
37
38To install Alpine, find an appropriate [image to
39download](https://alpinelinux.org/downloads/) and flash it to a USB
40using software such as Rufus or Etcher. I opted to use the Standard
41image for my x86~64~ architecture.
42
43Once the USB is ready, plug it into the machine and reboot. Note that
44you may have to use a key such as `Esc` or `F1-12`
45to access the boot menu. The Alpine Linux terminal will load quickly and
46for a login.
47
48To log in to the installation image, use the `root` account;
49there is no password. Once logged-in, execute the setup command:
50
51```sh
52setup-alpine
53```
54
55The setup script will ask a series of questions to configure the system.
56Be sure to answer carefully or else you may have to re-configure the
57system after boot.
58
59- Keyboard Layout (Local keyboard language and usage mode, e.g., us
60 and variant of us-nodeadkeys.)
61- Hostname (The name for the computer.)
62- Network (For example, automatic IP address discovery with the
63 "DHCP" protocol.)
64- DNS Servers (Domain Name Servers to query. For privacy reasons, it
65 is NOT recommended to route every local request to servers like
66 Google's 8.8.8.8 .)
67- Timezone
68- Proxy (Proxy server to use for accessing the web. Use "none" for
69 direct connections to the internet.)
70- Mirror (From where to download packages. Choose the organization you
71 trust giving your usage patterns to.)
72- SSH (Secure SHell remote access server. "Openssh" is part of the
73 default install image. Use "none" to disable remote login, e.g. on
74 laptops.)
75- NTP (Network Time Protocol client used for keeping the system clock
76 in sync with a time-server. Package "chrony" is part of the
77 default install image.)
78- Disk Mode (Select between diskless (disk="none"), "data" or
79 "sys", as described above.)
80
81Once the setup script is finished, be sure to reboot the machine and
82remove the USB device.
83
84```sh
85reboot
86```
87
88### Post-Installation
89
90There are many things you can do once your Alpine Linux system is up and
91running, and it largely depends on what you'll use the machine for.
92I'm going to walk through my personal post-installation setup for my
93web server.
94
951. Upgrade the System
96
97 First, login as `root` in order to update and upgrade the
98 system:
99
100 ```sh
101 apk -U upgrade
102 ```
103
1042. Adding a User
105
106 I needed to add a user so that I don't need to log in as root. Note
107 that if you're used to using the `sudo` command, you
108 will now need to use the `doas` command on Alpine Linux.
109
110 ```sh
111 apk add doas
112 adduser <username>
113 adduser <username> wheel
114 ```
115
116 You can now log out and log back in using the newly-created user:
117
118 ```sh
119 exit
120 ```
121
1223. Enable Community Packages
123
124 In order to install more common packages that aren't found in the
125 `main` repository, you will need to enable the
126 `community` repository:
127
128 ```sh
129 doas nano /etc/apk/repositories
130 ```
131
132 Uncomment the community line for whichever version of Alpine you're
133 running:
134
135 ```sh
136 /media/usb/apks
137 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
138 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
139 #http://dl-cdn.alpinelinux.org/alpine/edge/main
140 #http://dl-cdn.alpinelinux.org/alpine/edge/community
141 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
142 ```
143
1444. Install Required Packages
145
146 Now that the community packages are available, you can install any
147 packages you need. In my case, I installed the web server packages I
148 need for my services:
149
150 ```sh
151 doas apk add nano nginx docker docker-compose ufw
152 ```
153
1545. SSH
155
156 If you didn't install OpenSSH as part of the installation, you can
157 do so now:
158
159 ```sh
160 doas apk add openssh
161 ```
162
163 Next, either create a new key or copy your SSH key to the server
164 from your current machines:
165
166 ```sh
167 # Create a new key
168 ssh-keygen
169 ```
170
171 If you need to copy an existing SSH key from a current machine:
172
173 ```sh
174 # Copy key from existing machines
175 ssh-copy-id <username>@<ip_address>
176 ```
177
1786. Firewall
179
180 Lastly, I installed `ufw` above as my firewall. To set
181 up, default to deny incoming and allow outgoing connections. Then
182 selectively allow other ports or apps as needed.
183
184 ```sh
185 doas ufw default deny incoming
186 doas ufw default allow outgoing
187 doas ufw allow SSH
188 doas ufw allow "WWW Full"
189 doas ufw allow 9418 # Git server port
190 ```
191
1927. Change Hostname
193
194 If you don't like the hostname set during installation, you just
195 need to edit two files. First, edit the simple hostname file:
196
197 ```sh
198 doas nano /etc/hostname
199 ```
200
201 ```sh
202 <hostname>
203 ```
204
205 Next, edit the `hosts` file:
206
207 ```sh
208 doas nano /etc/hosts
209 ```
210
211 ```sh
212 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
213 ::1 <hostname> <hostname>.local
214 ```
215
216## Nginx Web Server
217
218To set up my web server, I simply created the `www` user and
219created the necessary files.
220
221```sh
222doas adduser -D -g 'www' www
223mkdir /www
224doas mkdir /www
225doas chown -R www:www /var/lib/nginx/
226doas chown -R www:www /www
227```
228
229If you're running a simple webroot, you can alter the main
230`nginx.conf` file. Otherwise, you can drop configuration
231files in the following directory. You don't need to enable or symlink
232the configuration file like you do in other systems.
233
234```sh
235doas nano /etc/nginx/http.d/example_website.conf
236```
237
238Once the configuration is set and pointed at the `/www`
239directory to serve files, enable the Nginx service:
240
241```sh
242# Note that 'default' must be included or Nginx will not start on boot
243doas rc-update add nginx default
244```
245
246## Docker Containers
247
248Docker works exactly the same as other systems. Either execute a
249`docker run` command or create a
250`docker-compose.yml` file and do
251`docker-compose up -d`.
252
253## Git Server
254
255I went in-depth on how to self-host a git server in another post:
256[Self-Hosting a Personal Git Server](/blog/git-server/).
257
258However, there are a few differences with Alpine. First note that in
259order to change the `git` user's shell, you must do a few
260things a little different:
261
262```sh
263doas apk add libuser
264doas touch /etc/login.defs
265doas mkdir /etc/default
266doas touch /etc/default/useradd
267doas lchsh git
268```
269
270## Thoughts on Alpine
271
272So far, I love Alpine Linux. I have no complaints about anything at this
273point, but I'm not completely finished with the migration yet. Once
274I'm able to upgrade my hardware to a rack-mounted server, I will
275migrate Plex and Syncthing over to Alpine as well - possibly putting
276Plex into a container or VM.
277
278The performance is stellar, the `apk` package manager is
279seamless, and system administration tasks are effortless. My only regret
280is that I didn't install Alpine sooner.
content/blog/2022-10-30-linux-display-manager.md added +81
@@ -0,0 +1,81 @@
1+++
2date = 2022-10-30
3title = "How to Disable or Change the Display Manager on Void Linux"
4description = "Learn how to remove or modify the display manager on Void Linux."
5+++
6
7## Display Manager Services
8
9In order to change the [display
10manager](https://en.wikipedia.org/wiki/Display_manager) on Void Linux -
11or any other Linux distro - you need to identify the currently enabled
12display manager.
13
14### Disabling the Current Display Manager
15
16Void Linux only has one ISO available for download with a pre-built
17display manager at the time of this post: the XFCE ISO. If you've
18installed this version, the pre-assigned display manager is
19`lxdm`. If you installed another display manager, replace
20`lxdm` in the following command with the display manager you
21have installed.
22
23To disable `lxdm`, simply remove the service symlink:
24
25```sh
26sudo rm /var/service/lxdm
27```
28
29### Enabling a New Display Manager
30
31If you want to enable a new display manager, you can do so after
32`lxdm` is disabled. Make sure to replace
33`<new_display_manager>` with your new DM, such as
34`gdm`, `xdm`, etc.
35
36```sh
37sudo ln -s /etc/sv/<new_display_manager> /var/service
38```
39
40## Set Up `.xinitrc`
41
42Depending on your setup, you may need to create a few X files, such as
43`~/.xinitrc`. For my personal set-up, I created this file to
44launch the i3wm as my desktop.
45
46```sh
47nano ~/.xinitrc
48```
49
50```sh
51#!/bin/sh
52
53exec i3
54```
55
56If you run a desktop other than i3, simply replace `i3` with
57the shell command that launches that desktop.
58
59## Set Up Your Shell Profile
60
61Finally, in order to automatically launch an X session upon login, you
62will need to edit the `.bash_profile` (bash) or
63`.zprofile` (zsh) files for your shell:
64
65```sh
66nano ~/.zprofile
67```
68
69Add the following snippet to the end of the shell profile file. This
70will execute the `startx` command upon login.
71
72```sh
73if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
74 exec startx
75fi
76```
77
78Alternatively, you can ignore this step and simply choose to manually
79execute `startx` upon login. This can be useful if you have
80issues with your desktop or like to manually launch different desktops
81by choice.
blog/2022-11-07-matrix-synapse.org → content/blog/2022-11-07-matrix-synapse.md renamed +82 −92
@@ -1,114 +1,106 @@
1#+title: Self-Hosting Matrix Synapse on Alpine Linux 1+++
2#+date: 2022-11-07 2date = 2022-11-07
3title = "Self-Hosting Matrix Synapse on Alpine Linux"
4description = "A guide to self-hosting the Matrix Synapse application on your own server."
5+++
6
7## Synpase
3 8
4** Synpase
5:PROPERTIES:
6:CUSTOM_ID: synpase
7:END:
8If you're reading this, you likely know that 9If you're reading this, you likely know that
9[[https://github.com/matrix-org/synapse/][Synapse]] is a popular 10[Synapse](https://github.com/matrix-org/synapse/) is a popular
10[[https://matrix.org/][Matrix]] home server software that allows users 11[Matrix](https://matrix.org/) home server software that allows users to
11to run their own Matrix home server. 12run their own Matrix home server.
12 13
13This post is a short guide describing how I was able to get Synapse 14This post is a short guide describing how I was able to get Synapse
14working in a minimally-usable state on Alpine Linux. 15working in a minimally-usable state on Alpine Linux.
15 16
16** Installation Process 17## Installation Process
17:PROPERTIES: 18
18:CUSTOM_ID: installation-process 19### Dependencies
19:END: 20
20*** Dependencies
21:PROPERTIES:
22:CUSTOM_ID: dependencies
23:END:
24First, since there is no Alpine-specific package for Synapse, we need to 21First, since there is no Alpine-specific package for Synapse, we need to
25ensure that Alpine has the required dependencies for the Python-based 22ensure that Alpine has the required dependencies for the Python-based
26installation method. 23installation method.
27 24
28#+begin_src sh 25```sh
29doas apk -U update 26doas apk -U update
30doas apk add python3 py3-virtualenv 27doas apk add python3 py3-virtualenv
31#+end_src 28```
32 29
33Next, we need to set up a Python virtual environment for Synapse: 30Next, we need to set up a Python virtual environment for Synapse:
34 31
35#+begin_src sh 32```sh
36mkdir -p ~/synapse && cd ~/synapse 33mkdir -p ~/synapse && cd ~/synapse
37virtualenv -p python3 ~/synapse/env 34virtualenv -p python3 ~/synapse/env
38source ~/synapse/env/bin/activate 35source ~/synapse/env/bin/activate
39pip install --upgrade pip 36pip install --upgrade pip
40pip install --upgrade setuptools 37pip install --upgrade setuptools
41pip install matrix-synapse 38pip install matrix-synapse
42#+end_src 39```
40
41### Running Synapse
43 42
44*** Running Synapse
45:PROPERTIES:
46:CUSTOM_ID: running-synapse
47:END:
48Once installed, running Synapse is easy. Simply execute the following 43Once installed, running Synapse is easy. Simply execute the following
49command, replacing =example.com= with the domain name that will be used 44command, replacing `example.com` with the domain name that
50with this home server. This will generate the configuration files needed 45will be used with this home server. This will generate the configuration
51to run the server. 46files needed to run the server.
52 47
53#+begin_src sh 48```sh
54python -m synapse.app.homeserver \ 49python -m synapse.app.homeserver \
55 --server-name example.com \ 50 --server-name example.com \
56 --config-path homeserver.yaml \ 51 --config-path homeserver.yaml \
57 --generate-config \ 52 --generate-config \
58 --report-stats=no 53 --report-stats=no
59#+end_src 54```
60 55
61Once the configuration is generated, we can start up the Synapse server: 56Once the configuration is generated, we can start up the Synapse server:
62 57
63#+begin_src sh 58```sh
64synctl start 59synctl start
65#+end_src 60```
61
62### Configuring Synapse
66 63
67*** Configuring Synapse 64To make any change to Synapse, we need to edit the `YAML`
68:PROPERTIES: 65configuration file:
69:CUSTOM_ID: configuring-synapse
70:END:
71To make any change to Synapse, we need to edit the =YAML= configuration
72file:
73 66
74#+begin_src sh 67```sh
75nano ~/synapse/homeserver.yaml 68nano ~/synapse/homeserver.yaml
76#+end_src 69```
77 70
78For now, we just need to ensure the =server_name= is accurate. However, 71For now, we just need to ensure the `server_name` is
79there are a lot of other configuration options found in the 72accurate. However, there are a lot of other configuration options found
80[[https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html][Configuring 73in the [Configuring
81Synapse]] documentation that can be enabled/disabled at any point. 74Synapse](https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html)
75documentation that can be enabled/disabled at any point.
82 76
83#+begin_src yaml 77```yaml
84server_name: "example.com" 78server_name: "example.com"
85#+end_src 79```
86 80
87Make sure to restart Synapse when you make changes to the configuration: 81Make sure to restart Synapse when you make changes to the configuration:
88 82
89#+begin_src sh 83```sh
90synctl restart 84synctl restart
91#+end_src 85```
86
87### Nginx Reverse-Proxy
92 88
93*** Nginx Reverse-Proxy
94:PROPERTIES:
95:CUSTOM_ID: nginx-reverse-proxy
96:END:
97To ensure that Synapse is reachable from the public, we need to connect 89To ensure that Synapse is reachable from the public, we need to connect
98our domain to the Synapse server. In my case, I use a Nginx 90our domain to the Synapse server. In my case, I use a Nginx
99reverse-proxy for this purpose. 91reverse-proxy for this purpose.
100 92
101To use Nginx, we need to create a reverse-proxy configuration file: 93To use Nginx, we need to create a reverse-proxy configuration file:
102 94
103#+begin_src sh 95```sh
104doas nano /etc/nginx/http.d/example.com.conf 96doas nano /etc/nginx/http.d/example.com.conf
105#+end_src 97```
106 98
107If you already have TLS certificates for this domain (=example.com=), 99If you already have TLS certificates for this domain
108you can simply use the SSL configuration and point toward your TLS 100(`example.com`), you can simply use the SSL configuration and
109certificates. 101point toward your TLS certificates.
110 102
111#+begin_src conf 103```conf
112server { 104server {
113 listen 443 ssl http2; 105 listen 443 ssl http2;
114 listen [::]:443 ssl http2; 106 listen [::]:443 ssl http2;
@@ -147,15 +139,15 @@ server {
147 listen 80; 139 listen 80;
148 return 404; 140 return 404;
149} 141}
150#+end_src 142```
151 143
152If you need to generate TLS certificates (I recommend 144If you need to generate TLS certificates (I recommend
153[[https://certbot.eff.org/][Certbot]]), you'll need a more minimal Nginx 145[Certbot](https://certbot.eff.org/)), you'll need a more minimal Nginx
154conf file before you can use the TLS-enabled example above. Instead, use 146conf file before you can use the TLS-enabled example above. Instead, use
155this configuration file during the Certbot certificate generation 147this configuration file during the Certbot certificate generation
156process: 148process:
157 149
158#+begin_src conf 150```conf
159server { 151server {
160 server_name example.com; 152 server_name example.com;
161 location / { 153 location / {
@@ -163,66 +155,64 @@ server {
163 } 155 }
164 listen 80; 156 listen 80;
165} 157}
166#+end_src 158```
167 159
168Once you're done editing the Nginx conf file, restart Nginx: 160Once you're done editing the Nginx conf file, restart Nginx:
169 161
170#+begin_src sh 162```sh
171doas rc-service nginx restart 163doas rc-service nginx restart
172#+end_src 164```
173 165
174If you still need to generate TLS certificates, run =certbot= now and 166If you still need to generate TLS certificates, run `certbot`
175obtain the certificates. Certbot will ask if you want to use a webroot 167now and obtain the certificates. Certbot will ask if you want to use a
176or spin up a temporary web server. I *highly* recommend using the 168webroot or spin up a temporary web server. I **highly** recommend using
177temporary web server due to the many issues with using a webroot. 169the temporary web server due to the many issues with using a webroot.
178 170
179You will need to stop Nginx in order to user the temporary web server 171You will need to stop Nginx in order to user the temporary web server
180option with Certbot: 172option with Certbot:
181 173
182#+begin_src sh 174```sh
183# Stop Nginx so certbot can spin up a temp webserver for cert generation 175# Stop Nginx so certbot can spin up a temp webserver for cert generation
184doas rc-service nginx stop 176doas rc-service nginx stop
185doas certbot certonly -v 177doas certbot certonly -v
186doas rc-service nginx start 178doas rc-service nginx start
187#+end_src 179```
188 180
189*** Open Firewall & Router Ports 181### Open Firewall & Router Ports
190:PROPERTIES: 182
191:CUSTOM_ID: open-firewall-router-ports 183If you use a firewall on the server, open the `8448` port for
192:END: 184discovery and federation, as well as the normal web server ports if
193If you use a firewall on the server, open the =8448= port for discovery 185you're using a reverse proxy. If you want additional services, such as
194and federation, as well as the normal web server ports if you're using a 186voice calls, you will need to read the Synapse documentation to see
195reverse proxy. If you want additional services, such as voice calls, you 187which ports need to be opened for those features.
196will need to read the Synapse documentation to see which ports need to
197be opened for those features.
198 188
199Here's an example of the Universal Firewall (UFW) software: 189Here's an example of the Universal Firewall (UFW) software:
200 190
201#+begin_src sh 191```sh
202# Matrix port 192# Matrix port
203doas ufw allow 8448 193doas ufw allow 8448
204# Standard web server ports 194# Standard web server ports
205doas ufw allow "Nginx Full" 195doas ufw allow "Nginx Full"
206#+end_src 196```
197
198Remember to forward any Synapse ports, such as `8448`,
199`80`, and `443`, in your Router from the internet
200to your server's IP address.
207 201
208Remember to forward any Synapse ports, such as =8448=, =80=, and =443=, 202### Adding Matrix Users
209in your Router from the internet to your server's IP address.
210 203
211*** Adding Matrix Users
212:PROPERTIES:
213:CUSTOM_ID: adding-matrix-users
214:END:
215Finally, if you didn't enable public registration in the 204Finally, if you didn't enable public registration in the
216=homeserver.yaml= file, you can manually create users via the 205`homeserver.yaml` file, you can manually create users via the
217command-line: 206command-line:
218 207
219#+begin_src sh 208```sh
220cd ~/synapse 209cd ~/synapse
221register_new_matrix_user -c homeserver.yaml 210register_new_matrix_user -c homeserver.yaml
222#+end_src 211```
223 212
224Remember that the format for federated Matrix usernames is 213Remember that the format for federated Matrix usernames is
225=@username:example.com= when logging in to client applications. 214`@username:example.com` when logging in to client
215applications.
226 216
227Once Synapse is running, and you have a username, you are ready to log 217Once Synapse is running, and you have a username, you are ready to log
228in to a Matrix client and start sending messages, joining rooms, and 218in to a Matrix client and start sending messages, joining rooms, and
blog/2022-11-11-nginx-tmp-errors.org → content/blog/2022-11-11-nginx-tmp-errors.md renamed +35 −35
@@ -1,13 +1,14 @@
1#+title: Fixing Permission Errors in /var/lib/nginx" description 1+++
2#+date: 2022-11-11 2date = 2022-11-11
3title = "Fixing Permission Errors in /var/lib/nginx"
4description = "Learn how to fix permission errors related to the Nginx temporary file storage."
5+++
3 6
4/This is a brief post so that I personally remember the solution as it 7*This is a brief post so that I personally remember the solution as it
5has occurred multiple times for me./ 8has occurred multiple times for me.*
9
10## The Problem
6 11
7** The Problem
8:PROPERTIES:
9:CUSTOM_ID: the-problem
10:END:
11After migrating to a new server OS, I started receiving quite a few 12After migrating to a new server OS, I started receiving quite a few
12permission errors like the one below. These popped up for various 13permission errors like the one below. These popped up for various
13different websites I'm serving via Nginx on this server, but did not 14different websites I'm serving via Nginx on this server, but did not
@@ -15,47 +16,46 @@ prevent the website from loading.
15 16
16I found the errors in the standard log file: 17I found the errors in the standard log file:
17 18
18#+begin_src sh 19```sh
19cat /var/log/nginx/error.log 20cat /var/log/nginx/error.log
20#+end_src 21```
21 22
22#+begin_src sh 23```sh
232022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/" 242022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/"
24#+end_src 25```
25 26
26You can see that the error is =13: Permission denied= and it occurs in 27You can see that the error is `13: Permission denied` and it
27the =/var/lib/nginx/tmp/= directory. In my case, I had thousands of 28occurs in the `/var/lib/nginx/tmp/` directory. In my case, I
28errors where Nginx was denied permission to read/write files in this 29had thousands of errors where Nginx was denied permission to read/write
29directory. 30files in this directory.
30 31
31So how do I fix it? 32So how do I fix it?
32 33
33** The Solution 34## The Solution
34:PROPERTIES: 35
35:CUSTOM_ID: the-solution 36In order to resolve the issue, I had to ensure the
36:END: 37`/var/lib/nginx` directory is owned by Nginx. Mine was owned
37In order to resolve the issue, I had to ensure the =/var/lib/nginx= 38by the `www` user and Nginx was not able to read or write
38directory is owned by Nginx. Mine was owned by the =www= user and Nginx 39files within that directory. This prevented Nginx from caching temporary
39was not able to read or write files within that directory. This 40files.
40prevented Nginx from caching temporary files.
41 41
42#+begin_src sh 42```sh
43# Alpine Linux 43# Alpine Linux
44doas chown -R nginx:nginx /var/lib/nginx 44doas chown -R nginx:nginx /var/lib/nginx
45 45
46# Other Distros 46# Other Distros
47sudo chown -R nginx:nginx /var/lib/nginx 47sudo chown -R nginx:nginx /var/lib/nginx
48#+end_src 48```
49 49
50You /may/ also be able to change the =proxy_temp_path= in your Nginx 50You *may* also be able to change the `proxy_temp_path` in
51config, but I did not try this. Here's a suggestion I found online that 51your Nginx config, but I did not try this. Here's a suggestion I found
52may work if the above solution does not: 52online that may work if the above solution does not:
53 53
54#+begin_src sh 54```sh
55nano /etc/nginx/http.d/example.com.conf 55nano /etc/nginx/http.d/example.com.conf
56#+end_src 56```
57 57
58#+begin_src conf 58```conf
59server { 59server {
60 ... 60 ...
61 61
@@ -65,15 +65,15 @@ server {
65 65
66 ... 66 ...
67} 67}
68#+end_src 68```
69 69
70Finally, restart Nginx and your server should be able to cache temporary 70Finally, restart Nginx and your server should be able to cache temporary
71files again. 71files again.
72 72
73#+begin_src sh 73```sh
74# Alpine Linux (OpenRC) 74# Alpine Linux (OpenRC)
75doas rc-service nginx restart 75doas rc-service nginx restart
76 76
77# Other Distros (systemd) 77# Other Distros (systemd)
78sudo systemctl restart nginx 78sudo systemctl restart nginx
79#+end_src 79```
blog/2022-11-27-server-build.org → content/blog/2022-11-27-server-build.md renamed +51 −61
@@ -1,10 +1,11 @@
1#+title: Building a Custom Rack-Mount Server 1+++
2#+date: 2022-11-27 2date = 2022-11-27
3title = "Building a Custom Rack-Mounted Server"
4description = "A retrospective on building my own rack-mounted server."
5+++
6
7## The Dilemma
3 8
4** The Dilemma
5:PROPERTIES:
6:CUSTOM_ID: the-dilemma
7:END:
8For years, I have been using desktops and a Raspberry Pi as the backbone 9For years, I have been using desktops and a Raspberry Pi as the backbone
9of my homelab. I have always wanted to move toward a single dedicated 10of my homelab. I have always wanted to move toward a single dedicated
10server that could handle all of my tasks, but was often put off by the 11server that could handle all of my tasks, but was often put off by the
@@ -20,37 +21,32 @@ After returning the R720XD, I decided that I wanted to build my own
20server with modern, consumer-grade PC components. This time, I am very 21server with modern, consumer-grade PC components. This time, I am very
21happy with the results of my server. 22happy with the results of my server.
22 23
23** Components 24## Components
24:PROPERTIES: 25
25:CUSTOM_ID: components
26:END:
27I'll start by listing all the components I used for this server build: 26I'll start by listing all the components I used for this server build:
28 27
29- *Case*: 28- **Case**: [Rosewill RSV-R4100U 4U Server Chassis Rackmount
30 [[https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825][Rosewill 29 Case](https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825)
31 RSV-R4100U 4U Server Chassis Rackmount Case]] 30- **Motherboard**: [NZXT B550](https://nzxt.com/product/n7-b550)
32- *Motherboard*: [[https://nzxt.com/product/n7-b550][NZXT B550]] 31- **CPU**: AMD Ryzen 7 5700G with Radeon Graphics
33- *CPU*: AMD Ryzen 7 5700G with Radeon Graphics 32- **GPU**: N/A - I specifically chose one of the few AMD CPUs that
34- *GPU*: N/A - I specifically chose one of the few AMD CPUs that support 33 support onboard graphics.
35 onboard graphics. 34- **RAM**: 64GB RAM (2x32GB) *Max of 128GB RAM on this motherboard*
36- *RAM*: 64GB RAM (2x32GB) /Max of 128GB RAM on this motherboard/ 35- **Boot Drive**: Western Digital 500GB M.2 NVME SSD
37- *Boot Drive*: Western Digital 500GB M.2 NVME SSD 36- **HDD Bay**:
38- *HDD Bay*: 37 - 10TB WD White *(shucked, moved from previous server)*
39 - 10TB WD White /(shucked, moved from previous server)/ 38 - 8TB WD White *(shucked, moved from previous server)*
40 - 8TB WD White /(shucked, moved from previous server)/ 39 - 2 x 8TB WD Red Plus *(Black Friday lined up perfectly with this
41 - 2 x 8TB WD Red Plus /(Black Friday lined up perfectly with this 40 build, so I grabbed two of these)*
42 build, so I grabbed two of these)/ 41- **PSU**: Corsair RM850 PSU
43- *PSU*: Corsair RM850 PSU 42- **Extras**:
44- *Extras*: 43 - Corsair TM3Q Thermal Paste
45 - Corsair TM3Q Thermal Paste 44 - Noctua 120mm fan *(replacement for front case fan)*
46 - Noctua 120mm fan /(replacement for front case fan)/ 45 - 2 x Noctua 80mm fans *(replacement for rear case fans)*
47 - 2 x Noctua 80mm fans /(replacement for rear case fans)/ 46 - CableMatters 6Gbps SATA Cables
48 - CableMatters 6Gbps SATA Cables 47
49 48## Building the Server
50** Building the Server 49
51:PROPERTIES:
52:CUSTOM_ID: building-the-server
53:END:
54This took quite a while for me to build (in my opinion of time), 50This took quite a while for me to build (in my opinion of time),
55totaling around 3 hours from start to finish. The case has some peculiar 51totaling around 3 hours from start to finish. The case has some peculiar
56construction, so you have to completely remove the ODD & HDD cages to 52construction, so you have to completely remove the ODD & HDD cages to
@@ -59,8 +55,8 @@ install the motherboard and other components first.
59Now, I've never built a computer of any kind before, so I was quite 55Now, I've never built a computer of any kind before, so I was quite
60nervous. Personally, the only challenging part was getting the CPU 56nervous. Personally, the only challenging part was getting the CPU
61cooler to screw into the motherboard without sliding the thermal paste 57cooler to screw into the motherboard without sliding the thermal paste
62around too much underneath. I'm still not entirely sure if I did a great 58around too much underneath. I'm still not entirely sure if I did a
63job of it, but nothing's broken yet. 59great job of it, but nothing's broken yet.
64 60
65The main components were all fine and simple. However, installing the 61The main components were all fine and simple. However, installing the
66hard drives is slightly tedious as I need to power off the server and 62hard drives is slightly tedious as I need to power off the server and
@@ -72,19 +68,15 @@ used to in other machines.
72Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay 68Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay
73dealing with the tedium of removing the cage to install new drives. 69dealing with the tedium of removing the cage to install new drives.
74 70
75** Software 71## Software
76:PROPERTIES: 72
77:CUSTOM_ID: software
78:END:
79I'm not going to dive into the software as I have done so in other 73I'm not going to dive into the software as I have done so in other
80recent posts. However, I wanted to note that I am using Alpine Linux on 74recent posts. However, I wanted to note that I am using Alpine Linux on
81this server and hosting most services inside Docker. No virtual machines 75this server and hosting most services inside Docker. No virtual machines
82(VMs) and very few bare-metal services. 76(VMs) and very few bare-metal services.
83 77
84** The Results 78## The Results
85:PROPERTIES: 79
86:CUSTOM_ID: the-results
87:END:
88How did my build turn out? Well, after migrating my other servers and 80How did my build turn out? Well, after migrating my other servers and
89their services over, I found that my server is blazing fast. The 81their services over, I found that my server is blazing fast. The
90heaviest of my applications, Plex, is handled with ease. Even 4k 82heaviest of my applications, Plex, is handled with ease. Even 4k
@@ -94,10 +86,8 @@ I am very happy with the results and will likely continue to improve on
94this server as the years go by rather than buying another used server 86this server as the years go by rather than buying another used server
95online. 87online.
96 88
97*** Mistakes I Made 89### Mistakes I Made
98:PROPERTIES: 90
99:CUSTOM_ID: mistakes-i-made
100:END:
101This post wouldn't be complete unless I wrote about the mistakes I made 91This post wouldn't be complete unless I wrote about the mistakes I made
102while building. The only real mistake I made beyond a "whoops I dropped 92while building. The only real mistake I made beyond a "whoops I dropped
103a screw" related to airflow and fan direction. 93a screw" related to airflow and fan direction.
@@ -106,19 +96,19 @@ While installing the two new hard drives that showed up on 2022-11-30
106and getting ready to install the case in my rack, I noticed that the 96and getting ready to install the case in my rack, I noticed that the
107hard drive temperatures were quite high. 97hard drive temperatures were quite high.
108 98
109I used the =smartctl= command for each of my drives (=/dev/sda= through 99I used the `smartctl` command for each of my drives
110=/dev/sdd=): 100(`/dev/sda` through `/dev/sdd`):
111 101
112#+begin_src sh 102```sh
113doas smartctl -a /dev/sda | grep Temperature_Celsius 103doas smartctl -a /dev/sda | grep Temperature_Celsius
114#+end_src 104```
115 105
116The results were unusual - all four drives were idling at ~44-46 degrees 106The results were unusual - all four drives were idling at ~44-46
117Celsius. The only drive that was cooler was my 10TB drive, which was at 107degrees Celsius. The only drive that was cooler was my 10TB drive, which
11838 degrees Celsius. I noted that this 10TB drive was also closest to the 108was at 38 degrees Celsius. I noted that this 10TB drive was also closest
119case fan. 109to the case fan.
120 110
121#+begin_src sh 111```sh
122ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE 112ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
123# /dev/sda 113# /dev/sda
124194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46) 114194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46)
@@ -128,7 +118,7 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_
128194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61) 118194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61)
129# /dev/sdd 119# /dev/sdd
130194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56) 120194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56)
131#+end_src 121```
132 122
133After looking to see if I could fit more fans into the case, I noticed 123After looking to see if I could fit more fans into the case, I noticed
134that the 120mm fan used for intake from the front of the case was 124that the 120mm fan used for intake from the front of the case was
@@ -140,7 +130,7 @@ dropped immediately! They are now idling at ~31-33 degrees Celsius. A
140single fan spinning the wrong way caused my drives to idle 10-15 degrees 130single fan spinning the wrong way caused my drives to idle 10-15 degrees
141higher than they should have. 131higher than they should have.
142 132
143#+begin_src sh 133```sh
144ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE 134ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
145# /dev/sda 135# /dev/sda
146194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56) 136194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56)
@@ -150,7 +140,7 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_
150194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48) 140194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48)
151# /dev/sdd 141# /dev/sdd
152194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46) 142194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46)
153#+end_src 143```
154 144
155This was a silly error to make, but I'm glad I found it today before I 145This was a silly error to make, but I'm glad I found it today before I
156screwed the case into the rack and made things a lot more tedious to 146screwed the case into the rack and made things a lot more tedious to
blog/2022-11-29-nginx-referrer-ban-list.org → content/blog/2022-11-29-nginx-referrer-ban-list.md renamed +54 −60
@@ -1,24 +1,25 @@
1#+title: Creating a Referrer Ban List on Nginx 1+++
2#+date: 2022-11-29 2date = 2022-11-29
3title = "Creating a Referrer Ban List in Nginx"
4description = "Learn how to create a ban list for referring sites in Nginx."
5+++
6
7## Creating the Ban List
3 8
4** Creating the Ban List
5:PROPERTIES:
6:CUSTOM_ID: creating-the-ban-list
7:END:
8In order to ban list referral domains or websites with Nginx, you need 9In order to ban list referral domains or websites with Nginx, you need
9to create a ban list file. The file below will accept regexes for 10to create a ban list file. The file below will accept regexes for
10different domains or websites you wish to block. 11different domains or websites you wish to block.
11 12
12First, create the file in your nginx directory: 13First, create the file in your nginx directory:
13 14
14#+begin_src sh 15```sh
15doas nano /etc/nginx/banlist.conf 16doas nano /etc/nginx/banlist.conf
16#+end_src 17```
17 18
18Next, paste the following contents in and fill out the regexes with 19Next, paste the following contents in and fill out the regexes with
19whichever domains you're blocking. 20whichever domains you're blocking.
20 21
21#+begin_src conf 22```conf
22# /etc/nginx/banlist.conf 23# /etc/nginx/banlist.conf
23 24
24map $http_referer $bad_referer { 25map $http_referer $bad_referer {
@@ -29,23 +30,21 @@ map $http_referer $bad_referer {
29 # Put regexes for undesired referrers here 30 # Put regexes for undesired referrers here
30 "~news.ycombinator.com" 1; 31 "~news.ycombinator.com" 1;
31} 32}
32#+end_src 33```
34
35## Configuring Nginx
33 36
34** Configuring Nginx
35:PROPERTIES:
36:CUSTOM_ID: configuring-nginx
37:END:
38In order for the ban list to work, Nginx needs to know it exists and how 37In 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. 38to handle it. For this, edit the `nginx.conf` file.
40 39
41#+begin_src sh 40```sh
42doas nano /etc/nginx/nginx.conf 41doas nano /etc/nginx/nginx.conf
43#+end_src 42```
44 43
45Within this file, find the =http= block and add your ban list file 44Within this file, find the `http` block and add your ban list
46location to the end of the block. 45file location to the end of the block.
47 46
48#+begin_src conf 47```conf
49# /etc/nginx/nginx.conf 48# /etc/nginx/nginx.conf
50 49
51http { 50http {
@@ -54,31 +53,30 @@ http {
54 # Include ban list 53 # Include ban list
55 include /etc/nginx/banlist.conf; 54 include /etc/nginx/banlist.conf;
56} 55}
57#+end_src 56```
57
58## Enabling the Ban List
58 59
59** Enabling the Ban List
60:PROPERTIES:
61:CUSTOM_ID: enabling-the-ban-list
62:END:
63Finally, we need to take action when a bad referral site is found. To do 60Finally, we need to take action when a bad referral site is found. To do
64so, edit the configuration file for your website. For example, I have 61so, edit the configuration file for your website. For example, I have
65all website configuration files in the =http.d= directory. You may have 62all website configuration files in the `http.d` directory.
66them in the =sites-available= directory on some distributions. 63You may have them in the `sites-available` directory on some
64distributions.
67 65
68#+begin_src sh 66```sh
69doas nano /etc/nginx/http.d/example.com.conf 67doas nano /etc/nginx/http.d/example.com.conf
70#+end_src 68```
71 69
72Within each website's configuration file, edit the =server= blocks that 70Within each website's configuration file, edit the `server`
73are listening to ports 80 and 443 and create a check for the 71blocks that are listening to ports 80 and 443 and create a check for the
74=$bad_referrer= variable we created in the ban list file. 72`$bad_referrer` variable we created in the ban list file.
75 73
76If a matching site is found, you can return any 74If a matching site is found, you can return any [HTTP Status
77[[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes][HTTP Status 75Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) you want.
78Code]] you want. Code 403 (Forbidden) is logical in this case since you 76Code 403 (Forbidden) is logical in this case since you are preventing a
79are preventing a client connection due to a banned domain. 77client connection due to a banned domain.
80 78
81#+begin_src conf 79```conf
82server { 80server {
83 ... 81 ...
84 82
@@ -86,49 +84,45 @@ server {
86 if ($bad_referer) { 84 if ($bad_referer) {
87 return 403; 85 return 403;
88 } 86 }
89 87
90 ... 88 ...
91} 89}
92#+end_src 90```
91
92## Restart Nginx
93 93
94** Restart Nginx
95:PROPERTIES:
96:CUSTOM_ID: restart-nginx
97:END:
98Lastly, restart Nginx to enable all changes made. 94Lastly, restart Nginx to enable all changes made.
99 95
100#+begin_src sh 96```sh
101doas rc-service nginx restart 97doas rc-service nginx restart
102#+end_src 98```
99
100## Testing Results
103 101
104** Testing Results
105:PROPERTIES:
106:CUSTOM_ID: testing-results
107:END:
108In order to test the results, let's curl the contents of our site. To 102In order to test the results, let's curl the contents of our site. To
109start, I'll curl the site normally: 103start, I'll curl the site normally:
110 104
111#+begin_src sh 105```sh
112curl https://cleberg.net 106curl https://cleberg.net
113#+end_src 107```
114 108
115The HTML contents of the page come back successfully: 109The HTML contents of the page come back successfully:
116 110
117#+begin_src html 111```html
118<!doctype html>...</html> 112<!doctype html>...</html>
119#+end_src 113```
120 114
121Next, let's include a banned referrer: 115Next, let's include a banned referrer:
122 116
123#+begin_src sh 117```sh
124curl --referer https://news.ycombinator.com https://cleberg.net 118curl --referer https://news.ycombinator.com https://cleberg.net
125#+end_src 119```
126 120
127This time, I'm met with a 403 Forbidden response page. That means we are 121This time, I'm met with a 403 Forbidden response page. That means we
128successful and any clients being referred from a banned domain will be 122are successful and any clients being referred from a banned domain will
129met with this same response code. 123be met with this same response code.
130 124
131#+begin_src html 125```html
132<html> 126<html>
133<head><title>403 Forbidden</title></head> 127<head><title>403 Forbidden</title></head>
134<body> 128<body>
@@ -136,4 +130,4 @@ met with this same response code.
136<hr><center>nginx</center> 130<hr><center>nginx</center>
137</body> 131</body>
138</html> 132</html>
139#+end_src 133```
content/blog/2022-12-01-nginx-compression.md added +79
@@ -0,0 +1,79 @@
1+++
2date = 2022-12-01
3title = "Enable GZIP Compression in Nginx"
4description = "Learn how to enable compression in Nginx."
5+++
6
7## Text Compression
8
9Text compression allows a web server to serve text-based resources
10faster than uncompressed data. This can speed up things like First
11Contentful Paint, Tie to Interactive, and Speed Index.
12
13## Enable Nginx Compression with gzip
14
15In order to enable text compression on Nginx, we need to enable it
16within the configuration file:
17
18```sh
19nano /etc/nginx/nginx.conf
20```
21
22Within the `http` block, find the section that shows
23something like the block below. This is the default gzip configuration I
24found in my `nginx.conf` file on Alpine Linux 3.17. Yours may
25look slightly different, just make sure that you're not creating any
26duplicate gzip options.
27
28```conf
29# Enable gzipping of responses.
30#gzip on;
31
32# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
33gzip_vary on;
34```
35
36Remove the default gzip lines and replace them with the following:
37
38```conf
39# Enable gzipping of responses.
40gzip on;
41gzip_vary on;
42gzip_min_length 10240;
43gzip_proxied expired no-cache no-store private auth;
44gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
45gzip_disable "MSIE [1-6]";
46```
47
48## Explanations of ngx~httpgzipmodule~ Options
49
50Each of the lines above enables a different aspect of the gzip response
51for Nginx. Here are the full explanations:
52
53- `gzip` -- Enables or disables gzipping of responses.
54- `gzip_vary` -- Enables or disables inserting the "Vary:
55 Accept-Encoding" response header field if the directives gzip,
56 gzip~static~, or gunzip are active.
57- `gzip_min_length` -- Sets the minimum length of a
58 response that will be gzipped. The length is determined only from
59 the "Content-Length" response header field.
60- `gzip_proxied` -- Enables or disables gzipping of
61 responses for proxied requests depending on the request and
62 response. The fact that the request is proxied is determined by the
63 presence of the "Via" request header field.
64- `gzip_types` -- Enables gzipping of responses for the
65 specified MIME types in addition to "text/html". The special value
66 "*" matches any MIME type (0.8.29). Responses with the
67 "text/html" type are always compressed.
68- `gzip_disable` -- Disables gzipping of responses for
69 requests with "User-Agent" header fields matching any of the
70 specified regular expressions.
71 - The special mask "msie6" (0.7.12) corresponds to the regular
72 expression "MSIE [4-6].", but works faster. Starting from
73 version 0.8.11, "MSIE 6.0; ... SV1" is excluded from this
74 mask.
75
76More information on these directives and their options can be found on
77the [Module
78ngx~httpgzipmodule~](https://nginx.org/en/docs/http/ngx_http_gzip_module.html)
79page in Nginx's documentation.
blog/2022-12-07-nginx-wildcard-redirect.org → content/blog/2022-12-07-nginx-wildcard-redirect.md renamed +36 −37
@@ -1,10 +1,11 @@
1#+title: Redirect Nginx Subdomains & Trailing Content with Regex 1+++
2#+date: 2022-12-07 2date = 2022-12-07
3title = "Redirect Nginx Subdomains & Trailing Content with Regex"
4description = "A simple Nginx configuration to redirect all subdomains and trailing content."
5+++
6
7## Problem
3 8
4** Problem
5:PROPERTIES:
6:CUSTOM_ID: problem
7:END:
8I recently migrated domains and replaced the old webpage with a simple 9I recently migrated domains and replaced the old webpage with a simple
9info page with instructions to users on how to edit their bookmarks and 10info page with instructions to users on how to edit their bookmarks and
10URLs to get to the page they were seeking. 11URLs to get to the page they were seeking.
@@ -12,15 +13,13 @@ URLs to get to the page they were seeking.
12This was not ideal as it left the work up to the user and may have 13This was not ideal as it left the work up to the user and may have
13caused friction for users who accessed my RSS feed. 14caused friction for users who accessed my RSS feed.
14 15
15** Solution 16## Solution
16:PROPERTIES: 17
17:CUSTOM_ID: solution
18:END:
19Instead, I finally found a solution that allows me to redirect both 18Instead, I finally found a solution that allows me to redirect both
20subdomains AND trailing content. For example, both of these URLs now 19subdomains AND trailing content. For example, both of these URLs now
21redirect properly using the logic I'll explain below: 20redirect properly using the logic I'll explain below:
22 21
23#+begin_src txt 22```txt
24# Example 1 - Simple base domain redirect with trailing content 23# Example 1 - Simple base domain redirect with trailing content
25https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ 24https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
26 25
@@ -28,25 +27,23 @@ https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
28https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 27https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
29-> 28->
30https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 29https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
31#+end_src 30```
32 31
33Go ahead, try the URLs if you want to test them. 32Go ahead, try the URLs if you want to test them.
34 33
35*** Nginx Config 34### Nginx Config
36:PROPERTIES: 35
37:CUSTOM_ID: nginx-config
38:END:
39To make this possible. I needed to configure a proper redirect scheme in 36To make this possible. I needed to configure a proper redirect scheme in
40my Nginx configuration. 37my Nginx configuration.
41 38
42#+begin_src sh 39```sh
43doas nano /etc/nginx/http.d/domain1.conf 40doas nano /etc/nginx/http.d/domain1.conf
44#+end_src 41```
45 42
46Within this file, I had one block configured to redirect HTTP requests 43Within this file, I had one block configured to redirect HTTP requests
47to HTTPS for the base domain and all subdomains. 44to HTTPS for the base domain and all subdomains.
48 45
49#+begin_src conf 46```conf
50server { 47server {
51 listen [::]:80; 48 listen [::]:80;
52 listen 80; 49 listen 80;
@@ -62,14 +59,14 @@ server {
62 59
63 return 404; 60 return 404;
64} 61}
65#+end_src 62```
66 63
67For the base domain, I have another =server= block dedicated to 64For the base domain, I have another `server` block dedicated
68redirecting all base domain requests. You can see that the =rewrite= 65to redirecting all base domain requests. You can see that the
69line is instructing Nginx to gather all trailing content and append it 66`rewrite` line is instructing Nginx to gather all trailing
70to the new =domain2.com= URL. 67content and append it to the new `domain2.com` URL.
71 68
72#+begin_src conf 69```conf
73server { 70server {
74 listen [::]:443 ssl http2; 71 listen [::]:443 ssl http2;
75 listen 443 ssl http2; 72 listen 443 ssl http2;
@@ -81,21 +78,23 @@ server {
81 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 78 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
82 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 79 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
83} 80}
84#+end_src 81```
85 82
86Finally, the tricky part is figuring out how to tell Nginx to redirect 83Finally, the tricky part is figuring out how to tell Nginx to redirect
87while keeping both a subdomain and trailing content intact. I found that 84while keeping both a subdomain and trailing content intact. I found that
88the easiest way to do this is to give it a =server= block of its own. 85the easiest way to do this is to give it a `server` block of
86its own.
89 87
90Within this block, we need to do some regex on the =server_name= line 88Within this block, we need to do some regex on the
91before we can rewrite anything. This creates a variable called 89`server_name` line before we can rewrite anything. This
92=subdomain=. 90creates a variable called `subdomain`.
93 91
94Once the server gets to the =rewrite= line, it pulls the =subdomain= 92Once the server gets to the `rewrite` line, it pulls the
95variable from above and uses it on the new =domain2.com= domain before 93`subdomain` variable from above and uses it on the new
96appending the trailing content (=$request_uri=). 94`domain2.com` domain before appending the trailing content
95(`$request_uri`).
97 96
98#+begin_src conf 97```conf
99server { 98server {
100 listen [::]:443 ssl http2; 99 listen [::]:443 ssl http2;
101 listen 443 ssl http2; 100 listen 443 ssl http2;
@@ -107,14 +106,14 @@ server {
107 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 106 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
108 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 107 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
109} 108}
110#+end_src 109```
111 110
112That's all there is to it. With this, I simply restarted Nginx and 111That's all there is to it. With this, I simply restarted Nginx and
113watched the redirections work in-action. 112watched the redirections work in-action.
114 113
115#+begin_src sh 114```sh
116doas rc-service nginx restart 115doas rc-service nginx restart
117#+end_src 116```
118 117
119Looking back on it, I wish I had done this sooner. Who knows how many 118Looking back on it, I wish I had done this sooner. Who knows how many
120people went looking for my sites or bookmarks and gave up when they saw 119people went looking for my sites or bookmarks and gave up when they saw
content/blog/2022-12-17-st.md added +94
@@ -0,0 +1,94 @@
1+++
2date = 2022-12-17
3title = "Simple Terminal"
4description = "Instructions for building simple terminal on Fedora."
5+++
6
7## st
8
9[st](https://st.suckless.org) standards for Simple Terminal, a simple
10terminal implementation for X made by the
11[suckless](https://suckless.org) team.
12
13This post walks through the dependencies needed and process to build and
14install `st` on Fedora Workstation.
15
16### Obtain Files
17
18To start, obtain the source files for `st` via
19`git clone`.
20
21```sh
22mkdir ~/suckless && cd ~/suckless
23git clone https://git.suckless.org/st && cd st
24```
25
26### Dependencies
27
28Once you have the files and are in the `st` directory, ensure
29the following packages are installed.
30
31```sh
32sudo dnf update && sudo dnf upgrade
33sudo dnf install gcc patch libX11-devel libXft-devel
34```
35
36### Building
37
38Before building, ensure that you read the README file.
39
40```sh
41cat README
42```
43
44Once you've read the instructions, open the `config.mk` file
45and ensure it matches your setup. If you're not sure, leave the default
46options within the file.
47
48Finally, you can build `st` with the following command.
49Ensure you run as root (e.g., `sudo`) or else you may not end
50up with a usable application file.
51
52```sh
53sudo make clean install
54```
55
56### Customization (Patches)
57
58Note that customizing `st` requires you to modify the source
59files or to download one of the [available
60patches](https://st.suckless.org/patches/) for suckless.org.
61
62If you've already installed `st` and want to customize or
63install a patch, start by uninstalling the current program.
64
65```sh
66cd ~/suckless/st
67sudo make uninstall
68```
69
70Next, grab the `<path>.diff` file from the page of the patch
71you chose. For example, I will be using the
72[defaultfontsize](https://st.suckless.org/patches/defaultfontsize/)
73patch in the below example.
74
75```sh
76wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
77```
78
79Once the file is downloaded inside the `st` folder, apply the
80patch and re-install the program. You may need to install the
81`patch` command if you don't have it installed already (you
82should have installed it above).
83
84```sh
85patch -i st-defaultfontsize-20210225-4ef0cbd.diff
86sudo make clean install
87```
88
89Once installed, you can use the default font size patch to launch
90`st` with any font size you wish:
91
92```sh
93st -z 16
94```
blog/2022-12-23-alpine-desktop.org → content/blog/2022-12-23-alpine-desktop.md renamed +91 −102
@@ -1,10 +1,11 @@
1#+title: Alpine Linux as a Desktop OS 1+++
2#+date: 2022-12-23 2date = 2022-12-23
3title = "Alpine Linux as a Desktop OS"
4description = "Learn how to set up Alpine Linux with Sway to use as a desktop operating system."
5+++
6
7## Isn't Alpine Linux for Servers?
3 8
4** Isn't Alpine Linux for Servers?
5:PROPERTIES:
6:CUSTOM_ID: isnt-alpine-linux-for-servers
7:END:
8This is a question I see a lot when people are presented with an example 9This is a question I see a lot when people are presented with an example
9of Alpine Linux running as a desktop OS. 10of Alpine Linux running as a desktop OS.
10 11
@@ -14,82 +15,76 @@ functioning at a productive level for desktop users.
14This post is documentation of how I installed and modified Alpine Linux 15This post is documentation of how I installed and modified Alpine Linux
15to become my daily desktop OS. 16to become my daily desktop OS.
16 17
17** Installation 18## Installation
18:PROPERTIES: 19
19:CUSTOM_ID: installation
20:END:
21Note that I cover the installation of Alpine Linux in my other post, so 20Note that I cover the installation of Alpine Linux in my other post, so
22I won't repeat it here: [[/blog/alpine-linux/][Alpine Linux: My New 21I won't repeat it here: [Alpine Linux: My New Server
23Server OS]]. 22OS](/blog/alpine-linux/).
24 23
25Basically, get a bootable USB or whatever you prefer with Alpine on it, 24Basically, get a bootable USB or whatever you prefer with Alpine on it,
26boot the ISO, and run the setup script. 25boot the ISO, and run the setup script.
27 26
28#+begin_src sh 27```sh
29setup-alpine 28setup-alpine
30#+end_src 29```
31 30
32Once you have gone through all the options and installer finishes 31Once you have gone through all the options and installer finishes
33without errors, reboot. 32without errors, reboot.
34 33
35#+begin_src sh 34```sh
36reboot 35reboot
37#+end_src 36```
37
38## Initial Setup
38 39
39** Initial Setup
40:PROPERTIES:
41:CUSTOM_ID: initial-setup
42:END:
43Once Alpine is installed and the machine has rebooted, login is as root 40Once Alpine is installed and the machine has rebooted, login is as root
44initially or =su= to root once you log in as your user. From here, you 41initially or `su` to root once you log in as your user. From
45should start by updating and upgrading the system in case the ISO was 42here, you should start by updating and upgrading the system in case the
46not fully up-to-date. 43ISO was not fully up-to-date.
47 44
48#+begin_src sh 45```sh
49# Update and upgrade system 46# Update and upgrade system
50apk -U update && apk -U upgrade 47apk -U update && apk -U upgrade
51 48
52# Add an editor so we can enable the community repository 49# Add an editor so we can enable the community repository
53apk add nano 50apk add nano
54#+end_src 51```
55 52
56You need to uncomment the =community= repository for your version of 53You need to uncomment the `community` repository for your
57Alpine Linux. 54version of Alpine Linux.
58 55
59For v3.17, the =repositories= file should look like this: 56For v3.17, the `repositories` file should look like this:
60 57
61#+begin_src sh 58```sh
62nano /etc/apk/repositories 59nano /etc/apk/repositories
63#+end_src 60```
64 61
65#+begin_src conf 62```conf
66#/media/sda/apks 63#/media/sda/apks
67http://mirrors.gigenet.com/alpinelinux/v3.17/main 64http://mirrors.gigenet.com/alpinelinux/v3.17/main
68http://mirrors.gigenet.com/alpinelinux/v3.17/community 65http://mirrors.gigenet.com/alpinelinux/v3.17/community
69#http://mirrors.gigenet.com/alpinelinux/edge/main 66#http://mirrors.gigenet.com/alpinelinux/edge/main
70#http://mirrors.gigenet.com/alpinelinux/edge/community 67#http://mirrors.gigenet.com/alpinelinux/edge/community
71#http://mirrors.gigenet.com/alpinelinux/edge/testing 68#http://mirrors.gigenet.com/alpinelinux/edge/testing
72#+end_src 69```
73 70
74#+begin_src sh 71```sh
75# Add the rest of your packages 72# Add the rest of your packages
76apk add linux-firmware iwd doas git curl wget 73apk add linux-firmware iwd doas git curl wget
77 74
78# Add yourself to the wheel group so you can use the doas command 75# Add yourself to the wheel group so you can use the doas command
79adduser $USER wheel 76adduser $USER wheel
80#+end_src 77```
81 78
82** Window Manager (Desktop) 79## Window Manager (Desktop)
83:PROPERTIES: 80
84:CUSTOM_ID: window-manager-desktop 81The [Sway installation guide](https://wiki.alpinelinux.org/wiki/Sway)
85:END:
86The [[https://wiki.alpinelinux.org/wiki/Sway][Sway installation guide]]
87has everything you need to get Sway working on Alpine. 82has everything you need to get Sway working on Alpine.
88 83
89However, I'll include a brief list of the commands I ran and their 84However, I'll include a brief list of the commands I ran and their
90purpose for posterity here. 85purpose for posterity here.
91 86
92#+begin_src sh 87```sh
93# Add eudev and set it up 88# Add eudev and set it up
94apk add eudev 89apk add eudev
95setup-devd udev 90setup-devd udev
@@ -126,21 +121,22 @@ apk add \ # Install optional dependencies:
126 swaylock swaylockd \ # lockscreen tool 121 swaylock swaylockd \ # lockscreen tool
127 swaybg \ # wallpaper daemon 122 swaybg \ # wallpaper daemon
128 swayidle # idle management (DPMS) daemon 123 swayidle # idle management (DPMS) daemon
129#+end_src 124```
130 125
131Once you have the packages installed and set-up, you need to export the 126Once you have the packages installed and set-up, you need to export the
132=XDG_RUNTIME_DIR= upon login. To do this, edit your =.profile= file. 127`XDG_RUNTIME_DIR` upon login. To do this, edit your
128`.profile` file.
133 129
134If you use another shell, such as =zsh=, you need to edit that shell's 130If you use another shell, such as `zsh`, you need to edit
135profile (e.g., =~/.zprofile=)! 131that shell's profile (e.g., `~/.zprofile`)!
136 132
137#+begin_src sh 133```sh
138nano ~/.profile 134nano ~/.profile
139#+end_src 135```
140 136
141Within the file, paste this: 137Within the file, paste this:
142 138
143#+begin_src sh 139```sh
144if test -z "${XDG_RUNTIME_DIR}"; then 140if test -z "${XDG_RUNTIME_DIR}"; then
145 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir 141 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir
146 if ! test -d "${XDG_RUNTIME_DIR}"; then 142 if ! test -d "${XDG_RUNTIME_DIR}"; then
@@ -148,22 +144,20 @@ if test -z "${XDG_RUNTIME_DIR}"; then
148 chmod 0700 "${XDG_RUNTIME_DIR}" 144 chmod 0700 "${XDG_RUNTIME_DIR}"
149 fi 145 fi
150fi 146fi
151#+end_src 147```
152 148
153Once that's complete, you can launch Sway manually. 149Once that's complete, you can launch Sway manually.
154 150
155#+begin_src sh 151```sh
156dbus-run-session -- sway 152dbus-run-session -- sway
157#+end_src 153```
154
155## Personal Touches
158 156
159** Personal Touches
160:PROPERTIES:
161:CUSTOM_ID: personal-touches
162:END:
163I also added the following packages, per my personal preferences and 157I also added the following packages, per my personal preferences and
164situation. 158situation.
165 159
166#+begin_src sh 160```sh
167doas apk add brightnessctl \ # Brightness controller 161doas apk add brightnessctl \ # Brightness controller
168 zsh \ # Shell 162 zsh \ # Shell
169 firefox \ # Browser 163 firefox \ # Browser
@@ -173,98 +167,93 @@ doas apk add brightnessctl \ # Brightness controller
173 neomutt \ # CLI email client 167 neomutt \ # CLI email client
174 thunderbird \ # GUI email client 168 thunderbird \ # GUI email client
175 gnupg # GPG key manager 169 gnupg # GPG key manager
176#+end_src 170```
177 171
178From here, I use my Syncthing storage to pull all the configuration 172From here, I use my Syncthing storage to pull all the configuration
179files I stored from prior desktops, such as 173files I stored from prior desktops, such as [my
180[[https://git.sr.ht/~cmc/dotfiles][my dotfiles]]. 174dotfiles](https://git.sr.ht/~cmc/dotfiles).
181 175
182** Resolving Issues 176## Resolving Issues
183:PROPERTIES: 177
184:CUSTOM_ID: resolving-issues 178### WiFi Issues
185:END: 179
186*** WiFi Issues 180I initially tried to set up my Wi-Fi the standard way with
187:PROPERTIES: 181`iwd`, but it didn't work.
188:CUSTOM_ID: wifi-issues 182
189:END: 183Here is what I initially tried (I did all of this as `root`):
190I initially tried to set up my Wi-Fi the standard way with =iwd=, but it 184
191didn't work. 185```sh
192
193Here is what I initially tried (I did all of this as =root=):
194
195#+begin_src sh
196apk add iwd 186apk add iwd
197rc-service iwd start 187rc-service iwd start
198iwctl station wlan0 connect <SSID> # This will prompt for the password 188iwctl station wlan0 connect <SSID> # This will prompt for the password
199rc-update add iwd boot && rc-update add dbus boot 189rc-update add iwd boot && rc-update add dbus boot
200#+end_src 190```
201 191
202Then, I added the Wi-Fi entry to the bottom of the networking interface 192Then, I added the Wi-Fi entry to the bottom of the networking interface
203file: 193file:
204 194
205#+begin_src sh 195```sh
206nano /etc/network/interfaces 196nano /etc/network/interfaces
207#+end_src 197```
208 198
209#+begin_src conf 199```conf
210auto wlan0 200auto wlan0
211iface wlan0 inet dhcp 201iface wlan0 inet dhcp
212#+end_src 202```
213 203
214Finally, restart the networking service: 204Finally, restart the networking service:
215 205
216#+begin_src sh 206```sh
217rc-service networking restart 207rc-service networking restart
218#+end_src 208```
219 209
220My Wi-Fi interface would receive an IP address from the router, but it 210My Wi-Fi interface would receive an IP address from the router, but it
221could not ping anything in the network. To solve the Wi-Fi issues, I 211could not ping anything in the network. To solve the Wi-Fi issues, I
222originally upgraded to Alpine's =edge= repositories, which was 212originally upgraded to Alpine's `edge` repositories, which
223unnecessary. 213was unnecessary.
224 214
225Really, the solution was to enable the =NameResolvingService=resolvconf= 215Really, the solution was to enable the
226in =/etc/iwd/main.conf=. 216`NameResolvingService=resolvconf` in
217`/etc/iwd/main.conf`.
227 218
228#+begin_src sh 219```sh
229doas nano /etc/iwd/main.conf 220doas nano /etc/iwd/main.conf
230#+end_src 221```
231 222
232#+begin_src conf 223```conf
233[Network] 224[Network]
234 225
235NameResolvingService=resolvconf 226NameResolvingService=resolvconf
236#+end_src 227```
237 228
238Once I finished this process, my Wi-Fi is working flawlessly. 229Once I finished this process, my Wi-Fi is working flawlessly.
239 230
240*** Sound Issues 231### Sound Issues
241:PROPERTIES: 232
242:CUSTOM_ID: sound-issues
243:END:
244Same as with the Wi-Fi, I had no sound and could not control the 233Same as with the Wi-Fi, I had no sound and could not control the
245mute/unmute or volume buttons on my laptop. 234mute/unmute or volume buttons on my laptop.
246 235
247To resolve this, I installed 236To resolve this, I installed
248[[https://wiki.alpinelinux.org/wiki/PipeWire][pipewire]]. 237[pipewire](https://wiki.alpinelinux.org/wiki/PipeWire).
249 238
250#+begin_src sh 239```sh
251# Add your user to the following groups 240# Add your user to the following groups
252addgroup $USER audio 241addgroup $USER audio
253addgroup $USER video 242addgroup $USER video
254 243
255# Install pipewire and other useful packages 244# Install pipewire and other useful packages
256apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa 245apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa
257#+end_src 246```
258 247
259Finally, I needed to add =/usr/libexec/pipewire-launcher= to my 248Finally, I needed to add `/usr/libexec/pipewire-launcher` to
260=.config/sway/config= file so that Pipewire would run every time I 249my `.config/sway/config` file so that Pipewire would run
261launched sway. 250every time I launched sway.
262 251
263#+begin_src sh 252```sh
264nano ~/.config/sway/config 253nano ~/.config/sway/config
265#+end_src 254```
266 255
267#+begin_src conf 256```conf
268# Run pipewire audio server 257# Run pipewire audio server
269exec /usr/libexec/pipewire-launcher 258exec /usr/libexec/pipewire-launcher
270 259
@@ -273,7 +262,7 @@ bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT
273bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% 262bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5%
274bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle 263bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle
275bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle 264bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle
276#+end_src 265```
277 266
278Note that I do not use bluetooth or screen sharing, so I won't cover 267Note that I do not use bluetooth or screen sharing, so I won't cover
279those options in this post. 268those options in this post.
blog/2023-01-03-recent-website-changes.org → content/blog/2023-01-03-recent-website-changes.md renamed +42 −50
@@ -1,46 +1,43 @@
1#+title: Recent Website Changes 1+++
2#+date: 2023-01-03 2date = 2023-01-03
3title = "Recent Website Changes"
4description = "A retrospective of recent changes to my website."
5+++
6
7## The State of This Website
3 8
4** The State of This Website
5:PROPERTIES:
6:CUSTOM_ID: the-state-of-this-website
7:END:
8Over the years, this website has changed URLs, styles, content focus, 9Over the years, this website has changed URLs, styles, content focus,
9and much more. However, it seems that I am never done, as I am yet again 10and much more. However, it seems that I am never done, as I am yet again
10changing this page to fit my current needs and wants. 11changing this page to fit my current needs and wants.
11 12
12While this site was already minimal (~12kb), it contained a lot of 13While this site was already minimal (\~12kb), it contained a lot of
13disorganized content and some poorly chosen color schemes. 14disorganized content and some poorly chosen color schemes.
14 15
15The recent updates attempt to fix these items while focusing on what I 16The recent updates attempt to fix these items while focusing on what I
16truly care about here: the content within each page. 17truly care about here: the content within each page.
17 18
18** Recent Changes 19## Recent Changes
19:PROPERTIES: 20
20:CUSTOM_ID: recent-changes
21:END:
22I've made quite a few changes to the style of this website today, both 21I've made quite a few changes to the style of this website today, both
23in the CSS and in the HTML. 22in the CSS and in the HTML.
24 23
25Here are some of the key takeaways from today's changes: 24Here are some of the key takeaways from today's changes:
26 25
271. Reduce the width of the website from =60em= to =40em=. 261. Reduce the width of the website from `60em` to
282. Remove breadcrumb navigation and replaced with a simple "Return Home" 27 `40em`.
29 link when visiting anything other than the homepage. 282. Remove breadcrumb navigation and replaced with a simple "Return
303. Remove syntax highlighting from code blocks. CSS now loads from a 29 Home" link when visiting anything other than the homepage.
31 single file. 303. Remove syntax highlighting from code blocks. CSS now loads from a
324. Move blog posts on the homepage to the top, pushing tertiary content 31 single file.
33 down. 324. Move blog posts on the homepage to the top, pushing tertiary content
345. Update font-family from =monospace= to =sans-serif= for readability. 33 down.
35 345. Update font-family from `monospace` to
36** Future Focus 35 `sans-serif` for readability.
37:PROPERTIES: 36
38:CUSTOM_ID: future-focus 37## Future Focus
39:END: 38
40*** Accessibility 39### Accessibility
41:PROPERTIES: 40
42:CUSTOM_ID: accessibility
43:END:
44My main focus amidst all the changes this past year was accessibility. I 41My main focus amidst all the changes this past year was accessibility. I
45tried to improve the accessibility of all pages on this site as well as 42tried to improve the accessibility of all pages on this site as well as
46I know how. For example, I added aria labels to identify different 43I know how. For example, I added aria labels to identify different
@@ -49,10 +46,8 @@ navigation menus in November.
49I want to continue this in 2023 and learn more about the accessibility 46I want to continue this in 2023 and learn more about the accessibility
50features I can utilize to help those who may need them. 47features I can utilize to help those who may need them.
51 48
52*** Content First 49### Content First
53:PROPERTIES: 50
54:CUSTOM_ID: content-first
55:END:
56Beyond accessibility, I am making a concerted effort to bring the 51Beyond accessibility, I am making a concerted effort to bring the
57content of this site to the forefront and push the tertiary information 52content of this site to the forefront and push the tertiary information
58(e.g., About Me, Services, etc.) down below the content. 53(e.g., About Me, Services, etc.) down below the content.
@@ -61,33 +56,30 @@ Further, I want to review and edit previous blog posts for grammar,
61accuracy, dead links, and more. Where necessary, I may even add a blurb 56accuracy, dead links, and more. Where necessary, I may even add a blurb
62at the top of old posts that are no longer accurate or helpful. 57at the top of old posts that are no longer accurate or helpful.
63 58
64*** Style 59### Style
65:PROPERTIES: 60
66:CUSTOM_ID: style
67:END:
68As always, I will be searching for ways to reduce distractions and bring 61As always, I will be searching for ways to reduce distractions and bring
69a minimalistic approach to this site. While there are certainly more 62a minimalistic approach to this site. While there are certainly more
70drastic measures I could take, such as making this a 63drastic measures I could take, such as making this a [Shinobi
71[[https://shinobi.bt.ht][Shinobi Website]] or a [[https://nocss.club][No 64Website](https://shinobi.bt.ht) or a [No CSS
72CSS Website]], I prefer to keep some modern features here. 65Website](https://nocss.club), I prefer to keep some modern features
66here.
73 67
74Two ideas have been floating around in my head, but I am not sure how I 68Two ideas have been floating around in my head, but I am not sure how I
75feel about these approaches yet: 69feel about these approaches yet:
76 70
771. Going all-in with Emacs org-mode and creating a script to 711. Going all-in with Emacs org-mode and creating a script to
78 auto-publish all my blog posts from org-mode to plain HTML files and 72 auto-publish all my blog posts from org-mode to plain HTML files and
79 then letting my CSS style it after the fact. 73 then letting my CSS style it after the fact.
802. Focus on publishing for Gemini or Gopher and then utilize a 742. Focus on publishing for Gemini or Gopher and then utilize a
81 conversion program to translate the pages to HTML. 75 conversion program to translate the pages to HTML.
76
77## Looking Back
82 78
83** Looking Back
84:PROPERTIES:
85:CUSTOM_ID: looking-back
86:END:
87As I sit here and finalize the changes, as well as this post, I am happy 79As I sit here and finalize the changes, as well as this post, I am happy
88with the current state of this website. It's certainly a long way from 80with the current state of this website. It's certainly a long way from
89my first attempts (parallax background images, anyone?) and it's good to 81my first attempts (parallax background images, anyone?) and it's good
90see the progress. 82to see the progress.
91 83
92I can only hope and work hard to ensure that I make more meaningful 84I can only hope and work hard to ensure that I make more meaningful
93progress by this time next year. 85progress by this time next year.
blog/2023-01-05-mass-unlike-tumblr-posts.org → content/blog/2023-01-05-mass-unlike-tumblr-posts.md renamed +40 −46
@@ -1,19 +1,18 @@
1#+title: How to Easily Mass Unlike Tumblr Posts With Javascript 1+++
2#+date: 2023-01-05 2date = 2023-01-05
3title = "How to Easily Mass Unlike Tumblr Posts with Javascript"
4description = "Learn how to unlike Tumblr posts en masse in the browser."
5+++
6
7## The Dilemma
3 8
4** The Dilemma
5:PROPERTIES:
6:CUSTOM_ID: the-dilemma
7:END:
8The dilemma I had was pretty simple: I wanted to unlike all the posts I 9The dilemma I had was pretty simple: I wanted to unlike all the posts I
9have liked on Tumblr so that I could follow a new focus on blogs and 10have liked on Tumblr so that I could follow a new focus on blogs and
10start fresh. Otherwise, Tumblr will keep recommending content based on 11start fresh. Otherwise, Tumblr will keep recommending content based on
11your previous likes. 12your previous likes.
12 13
13** The Solution 14## The Solution
14:PROPERTIES: 15
15:CUSTOM_ID: the-solution
16:END:
17I searched the web for a while and noted that most solutions referenced 16I searched the web for a while and noted that most solutions referenced
18Tumblr setting and dashboard pages that no longer exist. Additionally, I 17Tumblr setting and dashboard pages that no longer exist. Additionally, I
19did not want to install a third party extension to do this, as some 18did not want to install a third party extension to do this, as some
@@ -23,15 +22,13 @@ Luckily, I used Javascript for a while a few years ago and figured it
23would be easy enough to script a solution, as long as Tumblr had a 22would be easy enough to script a solution, as long as Tumblr had a
24system for the unlike buttons. 23system for the unlike buttons.
25 24
26*** Identifying Unlike Buttons 25### Identifying Unlike Buttons
27:PROPERTIES: 26
28:CUSTOM_ID: identifying-unlike-buttons
29:END:
30Tumblr's unlike buttons are structured as you can see in the following 27Tumblr's unlike buttons are structured as you can see in the following
31code block. All unlike buttons have an =aria-label= with a value of 28code block. All unlike buttons have an `aria-label` with a
32=Unlike=. 29value of `Unlike`.
33 30
34#+begin_src html 31```html
35<button class="TRX6J" aria-label="Unlike"> 32<button class="TRX6J" aria-label="Unlike">
36 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1"> 33 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1">
37 <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23" 34 <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23"
@@ -40,36 +37,35 @@ code block. All unlike buttons have an =aria-label= with a value of
40 </svg> 37 </svg>
41 </span> 38 </span>
42</button> 39</button>
43#+end_src 40```
44 41
45*** Running a Script to Unlike All Likes 42### Running a Script to Unlike All Likes
46:PROPERTIES: 43
47:CUSTOM_ID: running-a-script-to-unlike-all-likes 44To run this script, you will need to load the [Likes \|
48:END: 45Tumblr](https://www.tumblr.com/likes) page while logged in to your
49To run this script, you will need to load the 46account.
50[[https://www.tumblr.com/likes][Likes | Tumblr]] page while logged in to
51your account.
52 47
53Further, be sure to scroll down to the bottom and force Tumblr to load 48Further, be sure to scroll down to the bottom and force Tumblr to load
54more posts so that this script unlikes more posts at a time. 49more posts so that this script unlikes more posts at a time.
55 50
56Once you are logged in and the page is loaded, open the Developer Tools 51Once you are logged in and the page is loaded, open the Developer Tools
57and be sure you're on the "Console" tab. It should look something like 52and be sure you're on the "Console" tab. It should look something
58this (this is in Firefox, Chromium should be similar): 53like this (this is in Firefox, Chromium should be similar):
59 54
60#+caption: Firefox Dev !Tools 55![Firefox Dev
61[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png]] 56!Tools](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png)
62 57
63All you need to do is paste the following snippet into the dev console. 58All you need to do is paste the following snippet into the dev console.
64This code will collect all unlike buttons (=elements=) and then click 59This code will collect all unlike buttons (`elements`) and
65each button to unlike it. 60then click each button to unlike it.
66 61
67Optionally, you can comment-out the line =elements[i].click();= and 62Optionally, you can comment-out the line
68uncomment the =console.log()= lines to simply print out information 63`elements[i].click();` and uncomment the
69without performing any actions. This can be useful to debug issues or 64`console.log()` lines to simply print out information without
70confirm that the code below isn't doing anything you don't want it to. 65performing any actions. This can be useful to debug issues or confirm
66that the code below isn't doing anything you don't want it to.
71 67
72#+begin_src javascript 68```javascript
73const elements = document.querySelectorAll('[aria-label="Unlike"]'); 69const elements = document.querySelectorAll('[aria-label="Unlike"]');
74// console.log(elements); // 👉 [button] 70// console.log(elements); // 👉 [button]
75  71 
@@ -77,22 +73,20 @@ for (let i=0; i < elements.length; i++) {
77 // console.log(elements[i]); 73 // console.log(elements[i]);
78 elements[i].click(); 74 elements[i].click();
79} 75}
80#+end_src 76```
77
78## Results
81 79
82** Results 80The results were quick for my situation, as it unliked \~200 posts
83:PROPERTIES: 81within 2-3 seconds. I am not sure how this will perform on larger sets
84:CUSTOM_ID: results 82of likes (or if Tumblr has a limit to unliking posts).
85:END:
86The results were quick for my situation, as it unliked ~200 posts within
872-3 seconds. I am not sure how this will perform on larger sets of likes
88(or if Tumblr has a limit to unliking posts).
89 83
90You can see the below screenshot showing that I pasted the snippet into 84You can see the below screenshot showing that I pasted the snippet into
91the console, pressed Enter, and then the posts are automatically 85the console, pressed Enter, and then the posts are automatically
92unliked. 86unliked.
93 87
94#+caption: Script !Results 88![Script
95[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png]] 89!Results](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png)
96 90
97Thinking about this further, I would bet that this would be fairly 91Thinking about this further, I would bet that this would be fairly
98simple to package into a browser add-on so that users could install the 92simple to package into a browser add-on so that users could install the
blog/2023-01-08-fedora-remove-login-manager.org → content/blog/2023-01-08-fedora-remove-login-manager.md renamed +21 −24
@@ -1,10 +1,11 @@
1#+title: Remove the Login Manager from Fedora i3 1+++
2#+date: 2023-01-08 2date = 2023-01-08
3title = "How to Remove the Login Manager from Fedora i3"
4description = "Learn how to completely remove the login manager from Fedora i3."
5+++
6
7## Fedora i3's Login Manager
3 8
4** Fedora i3's Login Manager
5:PROPERTIES:
6:CUSTOM_ID: fedora-i3s-login-manager
7:END:
8Since I use the i3 spin of Fedora Workstation, I don't like to have a 9Since I use the i3 spin of Fedora Workstation, I don't like to have a
9login manager installed by default. As of the current version of Fedora 10login manager installed by default. As of the current version of Fedora
10i3, the default login manager is LightDM. 11i3, the default login manager is LightDM.
@@ -13,35 +14,31 @@ If this is no longer the case, you can search for currently-installed
13packages with the following command and see if you can identify a 14packages with the following command and see if you can identify a
14different login manager. 15different login manager.
15 16
16#+begin_src sh 17```sh
17sudo dnf list installed 18sudo dnf list installed
18#+end_src 19```
20
21## Removing the Login Manager
19 22
20** Removing the Login Manager
21:PROPERTIES:
22:CUSTOM_ID: removing-the-login-manager
23:END:
24In order to remove the login manager, simply uninstall the package. 23In order to remove the login manager, simply uninstall the package.
25 24
26#+begin_src sh 25```sh
27sudo dnf remove lightdm 26sudo dnf remove lightdm
28#+end_src 27```
28
29## Launching i3 Manually
29 30
30** Launching i3 Manually
31:PROPERTIES:
32:CUSTOM_ID: launching-i3-manually
33:END:
34In order to launch i3 manually, you need to set up your X session 31In order to launch i3 manually, you need to set up your X session
35properly. To start, create or edit the =~/.xinitrc= file to include the 32properly. To start, create or edit the `~/.xinitrc` file to
36following at the bottom. 33include the following at the bottom.
37 34
38#+begin_src config 35```config
39exec i3 36exec i3
40#+end_src 37```
41 38
42Now, whenever you log in to the TTY, you can launch your desktop with 39Now, whenever you log in to the TTY, you can launch your desktop with
43the following command. 40the following command.
44 41
45#+begin_src sh 42```sh
46startx 43startx
47#+end_src 44```
blog/2023-01-21-running-flatpak-apps-easily.org → content/blog/2023-01-21-running-flatpak-apps-easily.md renamed +25 −25
@@ -1,50 +1,50 @@
1#+title: Running Flatpak Apps with Symlinks 1+++
2#+date: 2023-01-21 2date = 2023-01-21
3title = "Running Flatpak Apps with Symlinks"
4description = "Learn how to run Flatpak apps through menu launchers with symlinks."
5+++
6
7## Running Flatpak Apps Should Be Faster
3 8
4** Running Flatpak Apps Should Be Faster
5:PROPERTIES:
6:CUSTOM_ID: running-flatpak-apps-should-be-faster
7:END:
8If you're like me and use Flatpak for those pesky apps that cannot run 9If you're like me and use Flatpak for those pesky apps that cannot run
9on your system for one reason or another, you likely get annoyed with 10on your system for one reason or another, you likely get annoyed with
10opening a terminal and manually running the Flatpak app with the lengthy 11opening a terminal and manually running the Flatpak app with the lengthy
11=flatpak run ...= command. 12`flatpak run ...` command.
12 13
13In the past, I manually created aliases in my =.zshrc= file for certain 14In the past, I manually created aliases in my `.zshrc` file
14apps. For example, an alias would look like the example below. 15for certain apps. For example, an alias would look like the example
16below.
15 17
16This would allow me to run the command fast within the terminal, but it 18This would allow me to run the command fast within the terminal, but it
17wouldn't allow me to run it in an application launcher. 19wouldn't allow me to run it in an application launcher.
18 20
19#+begin_src sh 21```sh
20# ~/.zshrc 22# ~/.zshrc
21alias librewolf = "flatpak run io.gitlab.librewolf-community" 23alias librewolf = "flatpak run io.gitlab.librewolf-community"
22#+end_src 24```
23 25
24However, I now use a much faster and better method that integrates with 26However, I now use a much faster and better method that integrates with
25the tiling WMs I use and their application launchers - =dmenu= and 27the tiling WMs I use and their application launchers -
26=bemenu=. 28`dmenu` and `bemenu`.
29
30## Creating Symlinks for Flatpak Apps
27 31
28** Creating Symlinks for Flatpak Apps
29:PROPERTIES:
30:CUSTOM_ID: creating-symlinks-for-flatpak-apps
31:END:
32Let's use the example of Librewolf below. I can install the application 32Let's use the example of Librewolf below. I can install the application
33like so: 33like so:
34 34
35#+begin_src sh 35```sh
36flatpak install flathub io.gitlab.librewolf-community 36flatpak install flathub io.gitlab.librewolf-community
37#+end_src 37```
38 38
39Once installed, I can create a symlink to link the flatpak app to my new 39Once installed, I can create a symlink to link the flatpak app to my new
40symlink in a location commonly included in your PATH. In this case, I 40symlink in a location commonly included in your PATH. In this case, I
41chose =/usr/bin=. You may need to choose a different location if 41chose `/usr/bin`. You may need to choose a different location
42=/usr/bin= isn't in your PATH. 42if `/usr/bin` isn't in your PATH.
43 43
44#+begin_src sh 44```sh
45ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf 45ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf
46#+end_src 46```
47 47
48Once complete, you should be able to launch the app using the command 48Once complete, you should be able to launch the app using the command
49name you chose above in the symlink (=librewolf=) from a terminal or 49name you chose above in the symlink (`librewolf`) from a
50from your application launcher! 50terminal or from your application launcher!
blog/2023-01-23-random-mullvad-wireguard.org → content/blog/2023-01-23-random-mullvad-wireguard.md renamed +44 −47
@@ -1,17 +1,17 @@
1#+title: Connecting to a Random Mullvad Wireguard Host 1+++
2#+date: 2023-01-23 2date = 2023-01-23
3title = "Connecting to a Random Mullvad Wireguard Host on Boot"
4description = "Learn how to connect to a random Mullvad Wireguard host on boot."
5+++
6
7## Mullvad Wireguard
3 8
4** Mullvad Wireguard
5:PROPERTIES:
6:CUSTOM_ID: mullvad-wireguard
7:END:
8If you're using an OS that does not support one of Mullvad's apps, 9If you're using an OS that does not support one of Mullvad's apps,
9you're likely using the Wireguard configuration files instead. 10you're likely using the Wireguard configuration files instead.
10 11
11If not, the first step is to visit Mullvad's 12If not, the first step is to visit Mullvad's [Wireguard configuration
12[[https://mullvad.net/en/account/#/wireguard-config][Wireguard 13files](https://mullvad.net/en/account/#/wireguard-config) page and
13configuration files]] page and download a ZIP of the configuration files 14download a ZIP of the configuration files you want to use.
14you want to use.
15 15
16Personally, I downloaded all configuration files across the world and 16Personally, I downloaded all configuration files across the world and
17chose my connections using the script below. 17chose my connections using the script below.
@@ -19,31 +19,29 @@ chose my connections using the script below.
19Once the files are downloaded, unzip them and move them to your 19Once the files are downloaded, unzip them and move them to your
20preferred location: 20preferred location:
21 21
22#+begin_src sh 22```sh
23cd Downloads 23cd Downloads
24unzip mullvad_wireguard_linux_all_all.zip 24unzip mullvad_wireguard_linux_all_all.zip
25mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/ 25mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/
26#+end_src 26```
27
28## Creating a Script to Connect to a Random Host
27 29
28** Creating a Script to Connect to a Random Host
29:PROPERTIES:
30:CUSTOM_ID: creating-a-script-to-connect-to-a-random-host
31:END:
32Once you have a folder of Wireguard configuration files from Mullvad, 30Once you have a folder of Wireguard configuration files from Mullvad,
33you can create a script to randomly connect to any one of the locations. 31you can create a script to randomly connect to any one of the locations.
34 32
35Start by creating a shell script - mine is called =vpn.sh=. 33Start by creating a shell script - mine is called `vpn.sh`.
36 34
37#+begin_src sh 35```sh
38nano ~/vpn.sh 36nano ~/vpn.sh
39#+end_src 37```
40 38
41Within this script, you can paste the following info. Note that I 39Within this script, you can paste the following info. Note that I
42specify =us-*= in my script, which means that it will only consider 40specify `us-*` in my script, which means that it will only
43US-based VPN locations. You can alter this or simply change it =*= to 41consider US-based VPN locations. You can alter this or simply change it
44consider all locations. 42`*` to consider all locations.
45 43
46#+begin_src sh 44```sh
47#!/bin/sh 45#!/bin/sh
48 46
49ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do 47ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do
@@ -54,19 +52,19 @@ ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do
54 printf "\n\nPrinting new IP info:\n" 52 printf "\n\nPrinting new IP info:\n"
55 curl https://am.i.mullvad.net/connected 53 curl https://am.i.mullvad.net/connected
56done 54done
57#+end_src 55```
58 56
59Once you've modified the script to your liking, add executable 57Once you've modified the script to your liking, add executable
60permissions and run the script: 58permissions and run the script:
61 59
62#+begin_src sh 60```sh
63chmod +x ~/vpn.sh 61chmod +x ~/vpn.sh
64~/vpn.sh 62~/vpn.sh
65#+end_src 63```
66 64
67The output should look like the following: 65The output should look like the following:
68 66
69#+begin_src txt 67```txt
70doas (user@host) password: 68doas (user@host) password:
71 69
72# ... The script will process all of the iptables and wg commands here 70# ... The script will process all of the iptables and wg commands here
@@ -75,45 +73,44 @@ Created Mullvad wireguard connection with file: /home/user/mullvad/us-nyc-wg-210
75 73
76Printing new IP info: 74Printing new IP info:
77You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99 75You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99
78#+end_src 76```
79 77
80That's all there is to it. You can see your new location and IP via the 78That's all there is to it. You can see your new location and IP via the
81=printf= and =curl= commands included in the script. 79`printf` and `curl` commands included in the
80script.
81
82You can also go to the [Connection Check ​\|
83Mullvad](https://mullvad.net/en/check/) page to see if you are fully
84connected to Mullvad and if any leaks exist.
82 85
83You can also go to the [[https://mullvad.net/en/check/][Connection Check 86![Mullvad Connection
84​| Mullvad]] page to see if you are fully connected to Mullvad and if any 87Check](https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png)
85leaks exist.
86 88
87#+caption: Mullvad Connection Check 89## Disconnecting from the Wireguard Connection
88[[https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png]]
89 90
90** Disconnecting from the Wireguard Connection
91:PROPERTIES:
92:CUSTOM_ID: disconnecting-from-the-wireguard-connection
93:END:
94If you forget which connection you're using, you can execute the 91If you forget which connection you're using, you can execute the
95following command to see where Wireguard is currently connected: 92following command to see where Wireguard is currently connected:
96 93
97#+begin_src sh 94```sh
98wg show 95wg show
99#+end_src 96```
100 97
101This command will show you the Wireguard interfaces and should output a 98This command will show you the Wireguard interfaces and should output a
102connection like so: =interface: us-lax-wg-104=. 99connection like so: `interface: us-lax-wg-104`.
103 100
104Once you have this, just disconnect using that files' full path: 101Once you have this, just disconnect using that files' full path:
105 102
106#+begin_src sh 103```sh
107wg-quick down /home/user/mullvad/us-lax-wg-104.conf 104wg-quick down /home/user/mullvad/us-lax-wg-104.conf
108#+end_src 105```
109 106
110I have a TODO item on figuring out how to easily export an environment 107I have a TODO item on figuring out how to easily export an environment
111variable that contains the configuration file's full name, so that I can 108variable that contains the configuration file's full name, so that I
112just execute the following: 109can just execute the following:
113 110
114#+begin_src sh 111```sh
115# Ideal situation if I can export the $file variable to the environment 112# Ideal situation if I can export the $file variable to the environment
116wg-quick down $file 113wg-quick down $file
117#+end_src 114```
118 115
119If you have an idea on how to do this, email me! 116If you have an idea on how to do this, email me!
blog/2023-01-28-self-hosting-wger-workout-manager.org → content/blog/2023-01-28-self-hosting-wger-workout-manager.md renamed +79 −96
@@ -1,118 +1,106 @@
1#+title: Self-Hosting Wger Workout Manager 1+++
2#+date: 2023-01-28 2date = 2023-01-28
3 3title = "Self-Hosting Wger Workout Manager"
4** Wger: The Self-Hosted Workout Manager 4description = "A guide to self-hosting the Wger application on your own server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: wger-the-self-hosted-workout-manager 6
7:END: 7## Wger: The Self-Hosted Workout Manager
8[[https://wger.de][Wger Workout Manager]] is a fitness tracking tool for 8
9[Wger Workout Manager](https://wger.de) is a fitness tracking tool for
9those who enjoy self-hosting their data. You can also register an 10those who enjoy self-hosting their data. You can also register an
10account on their main website if you'd prefer to try without 11account on their main website if you'd prefer to try without
11self-hosting. 12self-hosting.
12 13
13*** Features 14### Features
14:PROPERTIES: 15
15:CUSTOM_ID: features
16:END:
17I didn't see a full listing of features anywhere, so I compiled this 16I didn't see a full listing of features anywhere, so I compiled this
18list of my own after installing wger: 17list of my own after installing wger:
19 18
20**** Dashboard 191. Dashboard
21:PROPERTIES: 20
22:CUSTOM_ID: dashboard 21 - Dashboard view of Workout Schedule, Nutrition Plan, Weight
23:END: 22 Graph, & last 5 Weight Logs
24- Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, & 23
25 last 5 Weight Logs 242. Training
26 25
27**** Training 26 - Workout Log
28:PROPERTIES: 27 - Workout Schedule
29:CUSTOM_ID: training 28 - Calendar (shows weight logs and Bad/Neutral/Good days)
30:END: 29 - Gallery (shows images you upload)
31- Workout Log 30 - Workout templates
32- Workout Schedule 31 - Public templates
33- Calendar (shows weight logs and Bad/Neutral/Good days) 32 - Exercises
34- Gallery (shows images you upload) 33
35- Workout templates 343. Nutrition
36- Public templates 35
37- Exercises 36 - Nutrition plans
38 37 - BMI calculator
39**** Nutrition 38 - Daily calories calculator
40:PROPERTIES: 39 - Ingredient overview
41:CUSTOM_ID: nutrition 40
42:END: 414. Body Weight
43- Nutrition plans 42
44- BMI calculator 43 - Weight overview
45- Daily calories calculator 44
46- Ingredient overview 45### Documentation
47 46
48**** Body Weight
49:PROPERTIES:
50:CUSTOM_ID: body-weight
51:END:
52- Weight overview
53
54*** Documentation
55:PROPERTIES:
56:CUSTOM_ID: documentation
57:END:
58In order to self-host wger, I opted to use the Docker version of the 47In order to self-host wger, I opted to use the Docker version of the
59application. You can read the README within the 48application. You can read the README within the
60[[https://github.com/wger-project/docker][wger-project/docker]] project 49[wger-project/docker](https://github.com/wger-project/docker) project on
61on GitHub for information and installation instructions. 50GitHub for information and installation instructions.
51
52### Installation
62 53
63*** Installation
64:PROPERTIES:
65:CUSTOM_ID: installation
66:END:
67To start the installation, I created a folder for wger and started 54To start the installation, I created a folder for wger and started
68creating the three necessary files: 55creating the three necessary files:
69 56
70#+begin_src sh 57```sh
71mkdir ~/wger && mkdir ~/wger/config 58mkdir ~/wger && mkdir ~/wger/config
72touch ~/wger/docker-compose.yml && \ 59touch ~/wger/docker-compose.yml && \
73touch ~/wger/config/prod.env && \ 60touch ~/wger/config/prod.env && \
74touch ~/wger/config/nginx.conf 61touch ~/wger/config/nginx.conf
75#+end_src 62```
76 63
77Once you have the folders and files created, you will need to copy the 64Once you have the folders and files created, you will need to copy the
78contents of the =docker-compose.yml=, =prod.env=, and =nginx.conf= from 65contents of the `docker-compose.yml`, `prod.env`,
79the GitHub link above. 66and `nginx.conf` from the GitHub link above.
80 67
81A few notes to explain the changes I made to the default files: 68A few notes to explain the changes I made to the default files:
82 69
83- I updated the =ALLOW_REGISTRAION= variable in =prod.env= to =False= 70- I updated the `ALLOW_REGISTRAION` variable in
84 after I created an account via my LAN connection, *before* I connected 71 `prod.env` to `False` after I created an
85 this app to a publicly-available domain. 72 account via my LAN connection, **before** I connected this app to a
86- I uncommented and updated =CSRF_TRUSTED_ORIGINS= to be equal to the 73 publicly-available domain.
87 public version of this app: =https://wger.example.com=. 74- I uncommented and updated `CSRF_TRUSTED_ORIGINS` to be
88- I updated the port within =docker-compose.yml=, within the =nginx= 75 equal to the public version of this app:
89 block. The port I updated this to will be reflected in my nginx 76 `https://wger.example.com`.
90 configuration file on the server (NOT the wger nginx.conf file). 77- I updated the port within `docker-compose.yml`, within
91 78 the `nginx` block. The port I updated this to will be
92*** Deploy 79 reflected in my nginx configuration file on the server (NOT the wger
93:PROPERTIES: 80 nginx.conf file).
94:CUSTOM_ID: deploy 81
95:END: 82### Deploy
83
96Once all files are created and modified to your needs, simply start the 84Once all files are created and modified to your needs, simply start the
97container. 85container.
98 86
99#+begin_src sh 87```sh
100docker-compose up -d 88docker-compose up -d
101#+end_src 89```
102 90
103You can now visit the website on your LAN by going to 91You can now visit the website on your LAN by going to
104=localhost:YOUR_PORT= or by the server's IP, if you're not on the same 92`localhost:YOUR_PORT` or by the server's IP, if you're not
105machine that is running the container. 93on the same machine that is running the container.
106 94
107If you wish to connect this app to a public domain name, you'll need to 95If you wish to connect this app to a public domain name, you'll need to
108point an =A= DNS record from the domain to your server's public IP. 96point an `A` DNS record from the domain to your server's
109You'll then need to create a configuration file for whichever web server 97public IP. You'll then need to create a configuration file for
110or reverse proxy you're using. 98whichever web server or reverse proxy you're using.
111 99
112Wger's README suggests the following reverse proxy configuration for 100Wger's README suggests the following reverse proxy configuration for
113Nginx: 101Nginx:
114 102
115#+begin_src conf 103```conf
116upstream wger { 104upstream wger {
117 # This port should match the port in the `nginx` block of docker-compose.yml 105 # This port should match the port in the `nginx` block of docker-compose.yml
118 # If the container is running on this same machine, replace this with 106 # If the container is running on this same machine, replace this with
@@ -139,18 +127,16 @@ server {
139 ssl_certificate /path/to/https/certificate.crt; 127 ssl_certificate /path/to/https/certificate.crt;
140 ssl_certificate_key /path/to/https/certificate.key; 128 ssl_certificate_key /path/to/https/certificate.key;
141} 129}
142#+end_src 130```
143 131
144** Thoughts on Wger 132## Thoughts on Wger
145:PROPERTIES: 133
146:CUSTOM_ID: thoughts-on-wger 134I'm still playing around with the app itself, but it seems to be a
147:END: 135solid all-around workout manager, weight log, and food log.
148I'm still playing around with the app itself, but it seems to be a solid
149all-around workout manager, weight log, and food log.
150 136
151I like that the weight log graph is fluid and updates quickly. You can 137I like that the weight log graph is fluid and updates quickly. You can
152also import or export data in CSV format if you'd like to move your data 138also import or export data in CSV format if you'd like to move your
153elsewhere. 139data elsewhere.
154 140
155The workout manager is slightly odd, as it requires you to enter sets 141The workout manager is slightly odd, as it requires you to enter sets
156and reps for each exercise when you enter it into the plan. Then, when 142and reps for each exercise when you enter it into the plan. Then, when
@@ -160,11 +146,8 @@ actually performed, in terms of reps and weight.
160I haven't tried the food log yet and I likely will not, at least for a 146I haven't tried the food log yet and I likely will not, at least for a
161while. I have no need for a food log or calorie tracker at the moment. 147while. I have no need for a food log or calorie tracker at the moment.
162 148
163*** Screenshot Example 149### Screenshot Example
164:PROPERTIES: 150
165:CUSTOM_ID: screenshot-example
166:END:
167You can see an example of a dashboard with placeholder data here: 151You can see an example of a dashboard with placeholder data here:
168 152
169#+caption: wger dashboard 153![wger dashboard](https://img.cleberg.net/blog/20230128-wger/wger.png)
170[[https://img.cleberg.net/blog/20230128-wger/wger.png]]
content/blog/2023-02-02-exploring-hare.md added +171
@@ -0,0 +1,171 @@
1+++
2date = 2023-02-02
3title = "Exploring the Hare Programming Language"
4description = "A retrospective on my first time using the Hare Programming Language."
5+++
6
7## A Quick Note
8
9By no means am I a professional developer, so this post will be rather
10short. I won't be going into depth on the specification or anything
11that technical.
12
13Instead, I will simply be talking about how I (a relatively basic
14hobbyist programmer) have been playing with Hare and what intrigues me
15about the language.
16
17## Hare
18
19The [Hare](https://harelang.org) programming language is a
20straightforward language that should look familiar if you've ever
21programmed with C, Rust, or other languages that aim to build software
22at the system-level.
23
24The Hare homepage states the following:
25
26> Hare is a systems programming language designed to be simple, stable,
27> and robust. Hare uses a static type system, manual memory management,
28> and minimal runtime. It is well-suited to writing operating systems,
29> system tools, compilers, networking software, and other low-level,
30> high performance tasks.
31
32I have found this all to be true while playing with it for the first
33time today. In the next few sections, I'm going to walk through my
34installation and first program.
35
36### Installation
37
38I'm currently running Alpine Linux on my Thinkpad, so the installation
39was quite easy as there is a package for Hare in the `apk`
40repositories.
41
42```sh
43doas apk add hare hare-doc
44```
45
46However, I was able to install Hare from scratch on Fedora Linux a short
47while ago, which was also very easy to do. If you need further
48instructions and Hare doesn't have a package on your system, take a
49look at the [Hare Installation](https://harelang.org/installation/)
50page.
51
52### Creating a Test Project
53
54In order to play with the language, I created
55[hare-test](https://git.sr.ht/~cmc/hare-projects) and will be putting
56any of my Hare-related adventures in here.
57
58> **Update:** I also created a simple Hare program for creating a file
59> from user input:
60> [files.ha](https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha)
61
62Luckily, Hare doesn't require any complex set-up tools or build
63environment. Once you have Hare installed, you simply need to create a
64file ending with `.ha` and you can run a Hare program.
65
66I created a file called `rgb.ha` in order to test out the
67random number generation and passing parameters between functions.
68
69```sh
70nano rgb.ha
71```
72
73Within this file, I was able to easily import a few of the [standard
74library modules](https://harelang.org/tutorials/stdlib/):
75`fmt`, `math::random`, and `datetime`.
76
77With these modules, I created two functions:
78
791. `main`: This function calls the `generate_rgb`
80 function and then prints out the returned values.
812. `generate_rgb`: This function uses the current Unix epoch
82 time to generate a pseudo-random value and uses this value to create
83 three more random values between 0 and 255. These three numbers
84 represent a color in RGB format.
85
86> **Note**: Some syntax coloring may look odd, as Zola currently
87> doesn't have a syntax highlighting theme for Hare. Instead, I'm
88> using the C theme, which may not be exactly accurate when coloring the
89> code below.
90
91```c
92use datetime;
93use fmt;
94use math::random;
95
96export fn main() void = {
97 const rgb = generate_rgb();
98 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
99};
100
101fn generate_rgb() []u64 = {
102 // Use the current Unix epoch time as the seed value
103 let datetime = datetime::epochunix(&datetime::now());
104
105 // Generate initial pseudo-random value
106 // You must cast the datetime from int to u64
107 let x = random::init(datetime: u64);
108
109 // Generate RGB values between (0, 255) using pseudo-random init value
110 let r = random::u64n(&x, 255);
111 let g = random::u64n(&x, 255);
112 let b = random::u64n(&x, 255);
113
114 // Structure data as array and return
115 let rgb_array: [3]u64 = [r, g, b];
116 return rgb_array;
117};
118```
119
120### Running a Program
121
122Once you have a Hare file written and ready to run, you simply need to
123run it:
124
125```sh
126hare run file.ha
127```
128
129You can also compile the program into an executable:
130
131```sh
132hare build -o example file.ha
133./example
134```
135
136### Initial Thoughts
137
1381. Documentation Improvements Would Help
139
140 While I was able to piece everything together eventually, the
141 biggest downfall right now in Hare's documentation. For such a new
142 project, the documentation is in a great spot. However, bare
143 specifications don't help as much as a brief examples section
144 would.
145
146 For example, it took me a while to figure out what the
147 `u64n` function was looking for. I could tell that it
148 took two parameters and the second was my max value (255), but
149 couldn't figure out what the first value should be. Eventually, I
150 inspected the `random.ha` file in the [Hare source
151 code](https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha)
152 and found the test suite that helped me discover that it needed an
153 `init()` value in the form of `&var`.
154
1552. More Basic Modules
156
157 This is another point that comes from Hare being new and awaiting
158 more contributions, but there are some basic functions that I would
159 personally enjoy seeing in Hare, such as one to convert decimal
160 (base 10) values to hexadecimal (base 16).
161
162 If I'm feeling comfortable with my math, I may work on the list of
163 functions I want and see if any can make it into the Hare source
164 code.
165
1663. Overall Thoughts
167
168 Overall, I actually really enjoy Hare. It's not as tedious to get a
169 project up and running as Rust, but it's also simpler and more
170 user-friendly than learning C. I am going to continue playing with
171 it and see if I can make anything of particular value.
blog/2023-05-22-burnout.org → content/blog/2023-05-22-burnout.md renamed +21 −25
@@ -1,21 +1,19 @@
1#+title: Burnout 1+++
2#+date: 2023-05-22 2date = 2023-05-22
3 3title = "RE: Burnout"
4** RE: Burnout 4description = "A response to Drew DeVault's burnout post."
5:PROPERTIES: 5+++
6:CUSTOM_ID: re-burnout 6
7:END: 7## RE: Burnout
8I recently read 8
9[[https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html][Drew 9I recently read [Drew DeVault's post on
10DeVault's post on burnout]] around the same time I was pulling out of a 10burnout](https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html)
11burnout rut myself earlier this month. Finally, seeing the light at the 11around the same time I was pulling out of a burnout rut myself earlier
12end of my burnout tunnel made me want to write my first post back on 12this month. Finally, seeing the light at the end of my burnout tunnel
13this topic. 13made me want to write my first post back on this topic.
14 14
15** Busy Seasons on Busy Seasons 15## Busy Seasons on Busy Seasons
16:PROPERTIES: 16
17:CUSTOM_ID: busy-seasons-on-busy-seasons
18:END:
19My career deals with busy seasons, generally driven by client demand. 17My career deals with busy seasons, generally driven by client demand.
20This last year, I dealt with a harsh busy season from Aug to Oct 2022 to 18This last year, I dealt with a harsh busy season from Aug to Oct 2022 to
21issue a few SOC reports for the period ending 2022-09-30. Immediately 19issue a few SOC reports for the period ending 2022-09-30. Immediately
@@ -30,14 +28,12 @@ I have a brief break and need to focus on the 2023-09-30 SOC reports
30again. While auditing and consulting always involve a busy season, this 28again. While auditing and consulting always involve a busy season, this
31is the first time I've had one last 9+ months without a break. 29is the first time I've had one last 9+ months without a break.
32 30
33While it's been tough, I have a handful of breaks pre-planned throughout 31While it's been tough, I have a handful of breaks pre-planned
34this next cycle and should be able to moderate the level of commitment 32throughout this next cycle and should be able to moderate the level of
35required for each client. 33commitment required for each client.
34
35## Refocusing
36 36
37** Refocusing
38:PROPERTIES:
39:CUSTOM_ID: refocusing
40:END:
41Outside of work, I finally have time to work on hobbies such as this 37Outside of work, I finally have time to work on hobbies such as this
42website, programming, athletics, games, etc. 38website, programming, athletics, games, etc.
43 39
blog/2023-06-08-goaccess-geoip.org → content/blog/2023-06-08-goaccess-geoip.md renamed +32 −37
@@ -1,51 +1,48 @@
1#+title: Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data 1+++
2#+date: 2023-06-08 2date = 2023-06-08
3 3title = "Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data"
4** Overview 4description = "Learn how to use GoAccess and MaxMind to evaluate visitors to your web server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: overview 6
7:END: 7## Overview
8[[https://goaccess.io/][GoAccess]] is an open source real-time web log 8
9analyzer and interactive viewer that runs in a terminal in *nix systems 9[GoAccess](https://goaccess.io/) is an open source real-time web log
10analyzer and interactive viewer that runs in a terminal in \*nix systems
10or through your browser. 11or through your browser.
11 12
12** Installation 13## Installation
13:PROPERTIES: 14
14:CUSTOM_ID: installation 15To start, you'll need to install GoAccess for your OS. Here's an
15:END: 16example for Debian-based distros:
16To start, you'll need to install GoAccess for your OS. Here's an example
17for Debian-based distros:
18 17
19#+begin_src sh 18```sh
20sudo apt install goaccess 19sudo apt install goaccess
21#+end_src 20```
22 21
23Next, find any number of the MaxMind GeoIP database files on GitHub or 22Next, 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 23another file hosting website. We're going to use P3TERX's version in
25this example: 24this example:
26 25
27#+begin_src sh 26```sh
28wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb 27wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
29#+end_src 28```
30 29
31Be sure to save this file in an easy to remember location! 30Be sure to save this file in an easy to remember location!
32 31
33** Usage 32## Usage
34:PROPERTIES: 33
35:CUSTOM_ID: usage
36:END:
37In order to utilize the full capabilities of GoAccess and MMDB, start 34In order to utilize the full capabilities of GoAccess and MMDB, start
38with the command template below and customize as necessary. This will 35with the command template below and customize as necessary. This will
39export an HTML view of the GoAccess dashboard, showing all relevant 36export an HTML view of the GoAccess dashboard, showing all relevant
40information related to that site's access log. You can also omit the 37information related to that site's access log. You can also omit the
41=-o output.html= parameter if you prefer to view the data within the CLI 38`-o output.html` parameter if you prefer to view the data
42instead of creating an HTML file. 39within the CLI instead of creating an HTML file.
43 40
44With the addition of the GeoIP Database parameter, section 41With the addition of the GeoIP Database parameter, section
45=16 - Geo Location= will be added with the various countries that are 42`16 - Geo Location` will be added with the various countries
46associated with the collected IP addresses. 43that are associated with the collected IP addresses.
47 44
48#+begin_src sh 45```sh
49zcat /var/log/nginx/example.access.log.*.gz | goaccess \ 46zcat /var/log/nginx/example.access.log.*.gz | goaccess \
50--geoip-database=/home/user/GeoLite2-City.mmdb \ 47--geoip-database=/home/user/GeoLite2-City.mmdb \
51--date-format=%d/%b/%Y \ 48--date-format=%d/%b/%Y \
@@ -53,22 +50,20 @@ zcat /var/log/nginx/example.access.log.*.gz | goaccess \
53--log-format=COMBINED \ 50--log-format=COMBINED \
54-o output.html \ 51-o output.html \
55/var/log/nginx/example.access.log - 52/var/log/nginx/example.access.log -
56#+end_src 53```
54
55### Example Output
57 56
58*** Example Output
59:PROPERTIES:
60:CUSTOM_ID: example-output
61:END:
62See below for an example of the HTML output: 57See below for an example of the HTML output:
63 58
64#+caption: GoAccess HTML 59![GoAccess
65[[https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png]] 60HTML](https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png)
66 61
67You can also see the GeoIP card created by the integration of the 62You can also see the GeoIP card created by the integration of the
68MaxMind database information. 63MaxMind database information.
69 64
70#+caption: GoAccess GeoIP 65![GoAccess
71[[https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png]] 66GeoIP](https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png)
72 67
73That's all there is to it! Informational data is provided in an 68That's all there is to it! Informational data is provided in an
74organized fashion with minimal effort. 69organized fashion with minimal effort.
blog/2023-06-08-self-hosting-baikal-server.org → content/blog/2023-06-08-self-hosting-baikal-server.md renamed +58 −65
@@ -1,34 +1,34 @@
1#+title: Self-Hosting Baïkal Server 1+++
2#+date: 2023-06-08 2date = 2023-06-08
3 3title = "Self-Hosting Baïkal Server (CalDAV & CardDAV)"
4** What is Baïkal? 4description = "A guide to self-hosting the Baïkal application on your own server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: what-is-baïkal 6
7:END: 7## What is Baïkal?
8[[https://sabre.io/baikal/][Baïkal]] is a lightweight CalDAV + CardDAV 8
9[Baïkal](https://sabre.io/baikal/) is a lightweight CalDAV + CardDAV
9server that you can self-host on your own machine. While I have tried (& 10server that you can self-host on your own machine. While I have tried (&
10failed) to get this CalDAV + CardDAV server running before, it was quite 11failed) to get this CalDAV + CardDAV server running before, it was quite
11easy this time. Not really sure what I did differently this time, but 12easy this time. Not really sure what I did differently this time, but
12I'm documenting my process here to ensure I don't forget. 13I'm documenting my process here to ensure I don't forget.
13 14
14** Installation 15## Installation
15:PROPERTIES:
16:CUSTOM_ID: installation
17:END:
18First, create a folder on your server and open a =docker-compose.yml=
19file for editing:
20 16
21#+begin_src sh 17First, create a folder on your server and open a
18`docker-compose.yml` file for editing:
19
20```sh
22mkdir baikal && cd baikal 21mkdir baikal && cd baikal
23nano docker-compose.yml 22nano docker-compose.yml
24#+end_src 23```
25 24
26Within this file, you'll need to paste the information below. You can 25Within this file, you'll need to paste the information below. You can
27customize the =ports= section to use any port on your server to pass 26customize the `ports` section to use any port on your server
28through to port 80 in the container. You can also edit the =volumes= 27to pass through to port 80 in the container. You can also edit the
29section to use docker volumes instead of local folders. 28`volumes` section to use docker volumes instead of local
29folders.
30 30
31#+begin_src conf 31```conf
32version: "2" 32version: "2"
33services: 33services:
34 baikal: 34 baikal:
@@ -39,50 +39,45 @@ services:
39 volumes: 39 volumes:
40 - ./config:/var/www/baikal/config 40 - ./config:/var/www/baikal/config
41 - ./data:/var/www/baikal/Specific 41 - ./data:/var/www/baikal/Specific
42#+end_src 42```
43 43
44Once finished with editing, save and close the file. Then, launch the 44Once finished with editing, save and close the file. Then, launch the
45docker container: 45docker container:
46 46
47#+begin_src sh 47```sh
48sudo docker-compose up -d 48sudo docker-compose up -d
49#+end_src 49```
50
51## Intial Setup
50 52
51** Intial Setup
52:PROPERTIES:
53:CUSTOM_ID: intial-setup
54:END:
55As long as no issues came up when starting the container, you should be 53As long as no issues came up when starting the container, you should be
56able to visit the server's set-up page at =http://<server_ip>:<port>=. 54able to visit the server's set-up page at
57The application will ask you to create an administrator account and 55`http://<server_ip>:<port>`. The application will ask you to
58choose the database type for your storage. Personally, I opted to use 56create an administrator account and choose the database type for your
59SQLite. 57storage. Personally, I opted to use SQLite.
60 58
61Make sure the administrator credentials are adequate to protect against 59Make sure the administrator credentials are adequate to protect against
62common attacks. 60common attacks.
63 61
64** Creating Users 62## Creating Users
65:PROPERTIES: 63
66:CUSTOM_ID: creating-users 64Once you've set up the application, you will be greeted by the
67:END: 65Dashboard page, which will show the version of the app, status of the
68Once you've set up the application, you will be greeted by the Dashboard
69page, which will show the version of the app, status of the
70admin/CalDAV/CardDAV services, and the number of users, calendars, 66admin/CalDAV/CardDAV services, and the number of users, calendars,
71events, address books, and contacts. 67events, address books, and contacts.
72 68
73#+caption: Baïkal Dashboard 69![Baïkal
74[[https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png]] 70Dashboard](https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png)
75 71
76To create a new user, navigate to the =Users and resources= page. This 72To create a new user, navigate to the `Users and resources`
77process is as simple as entering a username, password, and email. 73page. This process is as simple as entering a username, password, and
74email.
78 75
79Once a user has been created, you can create any number of calendars and 76Once a user has been created, you can create any number of calendars and
80address books for user, as well as inspect their information. 77address books for user, as well as inspect their information.
81 78
82** Setting Up a Public URL 79## Setting Up a Public URL
83:PROPERTIES: 80
84:CUSTOM_ID: setting-up-a-public-url
85:END:
86Once your application is working locally, you can open access remotely 81Once your application is working locally, you can open access remotely
87via a URL by using a reverse-proxy like Nginx. 82via a URL by using a reverse-proxy like Nginx.
88 83
@@ -92,15 +87,15 @@ point a domain name to the server hosting Baïkal.
92Start by navigating to your web server's configuration directory and 87Start by navigating to your web server's configuration directory and
93create a new file for this application. 88create a new file for this application.
94 89
95#+begin_src sh 90```sh
96cd /etc/nginx/sites-available/ 91cd /etc/nginx/sites-available/
97nano dav 92nano dav
98#+end_src 93```
99 94
100Within this file, paste in the configuration from below and change 95Within this file, paste in the configuration from below and change
101=dav.example.com= to match the URL you'll be using. 96`dav.example.com` to match the URL you'll be using.
102 97
103#+begin_src conf 98```conf
104server { 99server {
105 if ($host ~ ^[^.]+\.example\.com$) { 100 if ($host ~ ^[^.]+\.example\.com$) {
106 return 301 https://$host$request_uri; 101 return 301 https://$host$request_uri;
@@ -130,37 +125,35 @@ server {
130 include /etc/letsencrypt/options-ssl-nginx.conf; 125 include /etc/letsencrypt/options-ssl-nginx.conf;
131 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 126 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
132} 127}
133#+end_src 128```
134 129
135For Nginx on Ubuntu, you'll need to symlink the configuration file to 130For Nginx on Ubuntu, you'll need to symlink the configuration file to
136the =sites-enabled= directory and then restart Nginx. 131the `sites-enabled` directory and then restart Nginx.
137 132
138#+begin_src sh 133```sh
139sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav 134sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav
140sudo systemctl restart nginx.service 135sudo systemctl restart nginx.service
141#+end_src 136```
142 137
143At this point, the Baïkal server should be available over the internet 138At this point, the Baïkal server should be available over the internet
144at the URL configured above! 139at the URL configured above!
145 140
146** Configuring Clients 141## Configuring Clients
147:PROPERTIES: 142
148:CUSTOM_ID: configuring-clients
149:END:
150Lastly, configuring clients and applications is essential to making sure 143Lastly, configuring clients and applications is essential to making sure
151the DAV server is being fully utilized. 144the DAV server is being fully utilized.
152 145
153You can also use the =Users and resources= page to inspect a user's 146You can also use the `Users and resources` page to inspect a
154personal link to their calendars and address books by clicking the info 147user's personal link to their calendars and address books by clicking
155(i) button. It will show a URI like 148the info (i) button. It will show a URI like
156=/dav.php/calendars/your-user/default/=. 149`/dav.php/calendars/your-user/default/`.
157 150
158However, I found that the following URL works for most applications: 151However, I found that the following URL works for most applications:
159=/dav.php/principals/your-user/=. 152`/dav.php/principals/your-user/`.
160 153
161I used the =principals= URL above for Thunderbird (calendar, tasks, and 154I used the `principals` URL above for Thunderbird (calendar,
162contacts), as well as iOS (calendar, tasks, and contacts) and everything 155tasks, and contacts), as well as iOS (calendar, tasks, and contacts) and
163works flawlessly so far. 156everything works flawlessly so far.
164 157
165Syncing is quick between the server and clients, and I haven't seen any 158Syncing is quick between the server and clients, and I haven't seen any
166disruptions in the service or data integrity. 159disruptions in the service or data integrity.
content/blog/2023-06-18-unifi-ip-blocklist.md added +87
@@ -0,0 +1,87 @@
1+++
2date = 2023-06-18
3title = "Block IP Addresses and Subnets with Unifi Network Firewall"
4description = "Learn how to use the Unifi Network Firewall to block IP addresses and subnets."
5+++
6
7## Identifying Abusive IPs
8
9If you're like me and use Unifi network equipment at the edge of the
10network you manage, you may know that Unifi is only somewhat decent at
11identifying and blocking IPs that represent abusive or threat actors.
12
13While Unifi has a [threat
14management](https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management)
15tool inside their Network application, it can be lacking in
16functionality and identification. For example, I have my UDM Pro set to
17identify and block almost all categories of threats available within the
18Unifi settings. However, I regularly identify abusive actors on my web
19server via the server logs.
20
21In addition, I have identified IP addresses and subnets directly within
22Unifi's logs that the UDM did not block for whatever reason.
23
24This guide is meant to be another step in the process to manually block
25abusive IP addresses or subnets that you have identified but are not
26being automatically blocked yet.
27
28## Create an IP Group Profile
29
30To start, login to the Unifi machine's web GUI and navigate to the
31Network app \> Settings \> Profiles.
32
33Within this page, choose the `IP Groups` tab and click
34`Create New`.
35
36![Network
37Profiles](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png)
38
39Each IP Group profile can be used as one of three options:
40
411. Port Group
422. IPv4 Address/Subnet
433. IPv6 Address/Subnet
44
45In this example, I'm creating an IPv4 Address/Subnet group and adding a
46few different IP addresses and a subnet. Once you've added all IP
47addresses and subnets, click the `Apply` button that should
48appear at the bottom.
49
50![Network Profile
51IPs](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png)
52
53At this point, the IPv4 Address/Subnet has been created but not yet
54used.
55
56## Drop IP Group Profile via the Unifi Firewall
57
58To instruct the Unifi machine to block the profile we just created, we
59need to navigate to the Network app \> Settings \> Firewall & Security.
60
61Within this screen, find the Firewall Rules table and click
62`Create Entry`. This entry should contain the following
63settings:
64
65- Type: `Internet In`
66- Description: `<Your Custom Rule>`
67- Rule Applied: `Before Predefined Rules`
68- Action: `Drop`
69- Source Type: `Port/IP Group`
70- IPv4 Address Group:
71 `<Name of the Group Profile You Created Above>`
72
73Customize the remaining configurations to your liking, and then save and
74enable the firewall rule.
75
76![Firewall
77Rule](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png)
78
79Once enabled, the Unifi machine will be able to drop all incoming
80connections from the defined IP addresses and subnets within the created
81profile.
82
83> As a personal aside to this topic, I'm looking for a convenient way
84> to update the firewall rules or profiles remotely (within the LAN)
85> from the web server to accelerate this process. If you have an idea on
86> how to automatically update Unifi IP groups or firewall rules, let me
87> know!
content/blog/2023-06-20-audit-review-template.md added +80
@@ -0,0 +1,80 @@
1+++
2date = 2023-06-20
3title = "Audit Testing Review Template"
4description = "A handy reference template for audit reviews."
5+++
6
7## Overview
8
9This post is a *very* brief overview on the basic process to review
10audit test results, focusing on work done as part of a financial
11statement audit (FSA) or service organization controls (SOC) report.
12
13While there are numerous different things to review and look for - all
14varying wildly depending on the report, client, and tester - this list
15serves as a solid base foundation for a reviewer.
16
17I have used this throughout my career as a starting point to my reviews,
18and it has worked wonders for creating a consistent and objective
19template to my reviews. The goal is to keep this base high-level enough
20to be used on a wide variety of engagements, while still ensuring that
21all key areas are covered.
22
23## Review Template
24
251. [ ] Check all documents for spelling and grammar.
262. [ ] Ensure all acronyms are fully explained upon first use.
273. [ ] For all people referenced, use their full names and job titles
28 upon first use.
294. [ ] All supporting documents must cross-reference to the lead sheet
30 and vice-versa.
315. [ ] Verify that the control has been adequately tested:
32 - [ ] **Test of Design**: Did the tester obtain information
33 regarding how the control should perform normally and abnormally
34 (e.g., emergency scenarios)?
35 - [ ] **Test of Operating Effectiveness**: Did the tester inquire,
36 observe, inspect, or re-perform sufficient evidence to support
37 their conclusion over the control? Inquiry alone is not
38 adequate!
396. [ ] For any information used in the control, whether by the control
40 operator or by the tester, did the tester appropriately document the
41 source (system or person), extraction method, parameters, and
42 completeness and accuracy (C&A)?
43 - [ ] For any reports, queries, etc. used in the extraction, did
44 the tester include a copy and notate C&A considerations?
457. [ ] Did the tester document the specific criteria that the control
46 is being tested against?
478. [ ] Did the tester notate in the supporting documents where each
48 criterion was satisfied?
499. [ ] If testing specific policies or procedures, are the documents
50 adequate?
51 - [ ] e.g., a test to validate that a review of policy XYZ occurs
52 periodically should also evaluate the sufficiency of the policy
53 itself, if meant to cover the risk that such a policy does not
54 exist and is not reviewed.
5510. [ ] Does the test cover the appropriate period under review?
56 - [ ] If the test is meant to cover only a portion of the audit
57 period, do other controls exist to mitigate the risks that exist
58 for the remainder of the period?
5911. [ ] For any computer-aided audit tools (CAATs) or other automation
60 techniques used in the test, is the use of such tools explained and
61 appropriately documented?
6212. [ ] If prior-period documentation exists, are there any missing
63 pieces of evidence that would further enhance the quality of the
64 test?
6513. [ ] Was any information discovered during the walkthrough or inquiry
66 phase that was not incorporated into the test?
6714. [ ] Are there new rules or expectations from your company's
68 internal guidance or your regulatory bodies that would affect the
69 audit approach for this control?
7015. [ ] Was an exception, finding, or deficiency identified as a result
71 of this test?
72 - [ ] Was the control deficient in design, operation, or both?
73 - [ ] What was the root cause of the finding?
74 - [ ] Does the finding indicate other findings or potential fraud?
75 - [ ] What's the severity and scope of the finding?
76 - [ ] Do other controls exist as a form of compensation against
77 the finding's severity, and do they mitigate the risk within
78 the control objective?
79 - [ ] Does the finding exist at the end of the period, or was it
80 resolved within the audit period?
blog/2023-06-23-byobu.org → content/blog/2023-06-23-byobu.md renamed +20 −24
@@ -1,40 +1,36 @@
1#+title: Byobu 1+++
2#+date: 2023-06-23 2date = 2023-06-23
3title = "Byobu"
4description = "Learning about the Byobu application for terminals."
5+++
3 6
4** Byobu 7## Byobu
5:PROPERTIES: 8
6:CUSTOM_ID: byobu 9[byobu](https://www.byobu.org) is a command-line tool that allows you to
7:END: 10use numerous screens within a single terminal emulator instance. More
8[[https://www.byobu.org][byobu]] is a command-line tool that allows you 11specifically, it's a text based window manager, using either
9to use numerous screens within a single terminal emulator instance. More 12`screen` or `tmux`.
10specifically, it's a text based window manager, using either =screen= or
11=tmux=.
12 13
13This post is mostly just a self-reference as I explore byobu, so I may 14This post is mostly just a self-reference as I explore byobu, so I may
14come back later and update this post with more content. 15come back later and update this post with more content.
15 16
16*** Screenshot 17### Screenshot
17:PROPERTIES: 18
18:CUSTOM_ID: screenshot
19:END:
20Take a look below at my current multi-window set-up in byobu while I 19Take a look below at my current multi-window set-up in byobu while I
21write this blog post: 20write this blog post:
22 21
23#+caption: byobu 22![byobu](https://img.cleberg.net/blog/20230623-byobu/byobu.png)
24[[https://img.cleberg.net/blog/20230623-byobu/byobu.png]] 23
24## Keybindings
25 25
26** Keybindings
27:PROPERTIES:
28:CUSTOM_ID: keybindings
29:END:
30You can open the help menu with either of the following commands; they 26You can open the help menu with either of the following commands; they
31will both open the same manpage: 27will both open the same manpage:
32 28
33#+begin_src sh 29```sh
34byobu --help 30byobu --help
35# or 31# or
36man byobu 32man byobu
37#+end_src 33```
38 34
39While the manpage contains a ton of information about the functionality 35While the manpage contains a ton of information about the functionality
40of byobu (such as status notifications, sessions, and windows), the 36of byobu (such as status notifications, sessions, and windows), the
@@ -42,7 +38,7 @@ first location to explore should be the keybindings section.
42 38
43The keybindings are configured as follows: 39The keybindings are configured as follows:
44 40
45#+begin_src txt 41```txt
46byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or 42byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or
47within .screenrc if byobu-export was used). The common key bindings are: 43within .screenrc if byobu-export was used). The common key bindings are:
48 44
@@ -70,4 +66,4 @@ Ctrl-a R - Reload profile
70Ctrl-a ! - Toggle key bindings on and off 66Ctrl-a ! - Toggle key bindings on and off
71Ctrl-a k - Kill the current window 67Ctrl-a k - Kill the current window
72Ctrl-a ~ - Save the current window's scrollback buffer 68Ctrl-a ~ - Save the current window's scrollback buffer
73#+end_src 69```
blog/2023-06-23-self-hosting-convos-irc.org → content/blog/2023-06-23-self-hosting-convos-irc.md renamed +73 −74
@@ -1,43 +1,43 @@
1#+title: Self-Hosting Convos: IRC Web Client 1+++
2#+date: 2023-06-23 2date = 2023-06-23
3 3title = "Self-Hosting Convos IRC Web Client"
4** Convos 4description = "A guide to self-hosting the Convos application on your own server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: convos 6
7:END: 7## Convos
8[[https://convos.chat/][Convos]] is an always-online web client for IRC. 8
9[Convos](https://convos.chat/) is an always-online web client for IRC.
9It has a few features that made it attractive to me as a self-hosted 10It has a few features that made it attractive to me as a self-hosted
10option: 11option:
11 12
12- Extremely simple Docker Compose installation method. 13- Extremely simple Docker Compose installation method.
13- Runs in the background and monitors chats even while you're not logged 14- Runs in the background and monitors chats even while you're not
14 in. 15 logged in.
15- Neatly organized sidebar for conversation and client settings. 16- Neatly organized sidebar for conversation and client settings.
16- Ability to connect to different hosts and create profiles for hosts. 17- Ability to connect to different hosts and create profiles for hosts.
17- By default, registration is closed to the public. You can enable 18- By default, registration is closed to the public. You can enable
18 public registration on the Settings page or generate invitation links 19 public registration on the Settings page or generate invitation
19 on the Users page. 20 links on the Users page.
20- Customization of the client theme, organization name and URL, admin 21- Customization of the client theme, organization name and URL, admin
21 email, and video service. 22 email, and video service.
22 23
23** Docker Installation 24## Docker Installation
24:PROPERTIES: 25
25:CUSTOM_ID: docker-installation 26To install Convos, simply create a directory and a
26:END: 27`docker-compose.yml` file:
27To install Convos, simply create a directory and a =docker-compose.yml= 28
28file: 29```sh
29
30#+begin_src sh
31mkdir ~/convos && cd ~/convos 30mkdir ~/convos && cd ~/convos
32nano docker-compose.yml 31nano docker-compose.yml
33#+end_src 32```
34 33
35With the =docker-compose.yml= file open, paste the configuration below 34With the `docker-compose.yml` file open, paste the
36into the file. You can customize the host port to be something unique, 35configuration below into the file. You can customize the host port to be
37such as =21897:3000=. You can also change the =data= folder to be a 36something unique, such as `21897:3000`. You can also change
38docker volume instead, if you prefer. 37the `data` folder to be a docker volume instead, if you
38prefer.
39 39
40#+begin_src config 40```config
41version: '3' 41version: '3'
42 42
43services: 43services:
@@ -50,34 +50,33 @@ services:
50 environment: 50 environment:
51 - CONVOS_REVERSE_PROXY=1 51 - CONVOS_REVERSE_PROXY=1
52 restart: always 52 restart: always
53#+end_src 53```
54 54
55Save the =docker-compose.yml= file and bring the container up: 55Save the `docker-compose.yml` file and bring the container
56up:
56 57
57#+begin_src sh 58```sh
58sudo docker-compose up -d 59sudo docker-compose up -d
59#+end_src 60```
61
62At this point, Convos is available at `<server_ip>:3000` but
63not available to the public.
60 64
61At this point, Convos is available at =<server_ip>:3000= but not 65## Nginx Reverse Proxy
62available to the public.
63 66
64** Nginx Reverse Proxy
65:PROPERTIES:
66:CUSTOM_ID: nginx-reverse-proxy
67:END:
68If you're using Nginx, you can create a configuration file for the 67If you're using Nginx, you can create a configuration file for the
69convos application. Start by opening the file: 68convos application. Start by opening the file:
70 69
71#+begin_src sh 70```sh
72nano /etc/nginx/sites-available/convos 71nano /etc/nginx/sites-available/convos
73#+end_src 72```
74 73
75Within the nginx configuration file, paste the following content and be 74Within the nginx configuration file, paste the following content and be
76sure to update =convos.example.com= to match your domain and 75sure to update `convos.example.com` to match your domain and
77=127.0.0.1:3000= to match the port you opened in the 76`127.0.0.1:3000` to match the port you opened in the
78=docker-compose.yml= file. 77`docker-compose.yml` file.
79 78
80#+begin_src config 79```config
81# Host and port where convos is running 80# Host and port where convos is running
82upstream convos_upstream { server 127.0.0.1:3000; } 81upstream convos_upstream { server 127.0.0.1:3000; }
83 82
@@ -115,59 +114,59 @@ server {
115 include /etc/letsencrypt/options-ssl-nginx.conf; 114 include /etc/letsencrypt/options-ssl-nginx.conf;
116 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 115 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
117} 116}
118#+end_src 117```
119 118
120Once the file is saved, link it to the =sites-enabled= directory and 119Once the file is saved, link it to the `sites-enabled`
121restart Nginx. 120directory and restart Nginx.
122 121
123#+begin_src sh 122```sh
124sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos 123sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos
125sudo systemctl restart nginx.service 124sudo systemctl restart nginx.service
126#+end_src 125```
126
127## Screenshots
127 128
128** Screenshots
129:PROPERTIES:
130:CUSTOM_ID: screenshots
131:END:
132See the screenshots below for an example of the Convos homepage and an 129See the screenshots below for an example of the Convos homepage and an
133excerpt of the chat screen. 130excerpt of the chat screen.
134 131
135There are numerous themes to choose from; the theme shown in the images 132There are numerous themes to choose from; the theme shown in the images
136below is Dracula. 133below is Dracula.
137 134
135```{=org}
138#+caption: Convos Home 136#+caption: Convos Home
139[[https://img.cleberg.net/blog/20230623-convos/convos_home.png%20%22Convos%20Home%22]] 137```
138<https://img.cleberg.net/blog/20230623-convos/convos_home.png%20%22Convos%20Home%22>
140 139
140```{=org}
141#+caption: Convos Chat 141#+caption: Convos Chat
142[[https://img.cleberg.net/blog/20230623-convos/convos_chat.png%20%22Convos%20Chat%22]] 142```
143<https://img.cleberg.net/blog/20230623-convos/convos_chat.png%20%22Convos%20Chat%22>
144
145## Registering a Nickname
143 146
144** Registering a Nickname
145:PROPERTIES:
146:CUSTOM_ID: registering-a-nickname
147:END:
148Hop into the server channel so that you can message NickServ. In the 147Hop into the server channel so that you can message NickServ. In the
149case of Convos, the default sever is libera.chat. Simply click the 148case of Convos, the default sever is libera.chat. Simply click the
150=libera= conversation at the top of the sidebar to open it. Once the 149`libera` conversation at the top of the sidebar to open it.
151chat is open, you can claim a nickname by typing: 150Once the chat is open, you can claim a nickname by typing:
152 151
153#+begin_src txt 152```txt
154/nick <nick> 153/nick <nick>
155#+end_src 154```
156 155
157If the nickname is available, and you'd like to register the nickname to 156If the nickname is available, and you'd like to register the nickname
158yourself, you'll need to type another command: 157to yourself, you'll need to type another command:
159 158
160#+begin_src txt 159```txt
161/msg NickServ REGISTER 160/msg NickServ REGISTER
162<password> <email> 161<password> <email>
163#+end_src 162```
164 163
165On libera.chat, the server will send a confirmation email with a command 164On libera.chat, the server will send a confirmation email with a command
166that you must message in IRC to verify registration of the nickname: 165that you must message in IRC to verify registration of the nickname:
167 166
168#+begin_src txt 167```txt
169/msg NickServ VERIFY REGISTER <nick> <verification_code> 168/msg NickServ VERIFY REGISTER <nick> <verification_code>
170#+end_src 169```
171 170
172Once entered, the server should confirm registration of the nickname to 171Once entered, the server should confirm registration of the nickname to
173the supplied email with the password specified. 172the supplied email with the password specified.
content/blog/2023-06-28-backblaze-b2.md added +188
@@ -0,0 +1,188 @@
1+++
2date = 2023-06-28
3title = "Getting Started with Backblaze B2 Cloud Storage"
4description = "An introduction to the free ttier of Backblaze B2 Cloud Storage."
5+++
6
7## Overview
8
9Backblaze [B2 Cloud
10Storage](https://www.backblaze.com/b2/cloud-storage.html) is an
11inexpensive and reliable on-demand cloud storage and backup solution.
12
13The service starts at \$5/TB/month (\$0.005/GB/month) with a download
14rate of \$0.01/GB/month.
15
16However, there are free tiers:
17
18- The first 10 GB of storage is free.
19- The first 1 GB of data downloaded each day is free.
20- Class A transactions are free.
21- The first 2500 Class B transactions each day are free.
22- The first 2500 Class C transactions each day are free.
23
24You can see which API calls fall into categories A, B, or C here:
25[Pricing Organized by API
26Calls](https://www.backblaze.com/b2/b2-transactions-price.html).
27
28For someone like me, who wants an offsite backup of their server's
29`/home/` directory and various other server configs that fall
30under 10 GB total, Backblaze is a great solution from a financial
31perspective.
32
33## Create An Account
34
35To start with Backblaze, you'll need to [create a free
36account](https://www.backblaze.com/b2/sign-up.html) - no payment method
37is required to sign up.
38
39Once you have an account, you can test out the service with their web
40GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool
41below to test a file upload and then sync an entire directory to my
42Backblaze bucket.
43
44## Create a Bucket
45
46Before you can start uploading, you need to create a bucket. If you're
47familiar with other object storage services, this will feel familiar. If
48not, it's pretty simple to create one.
49
50As their webpage says:
51
52> A bucket is a container that holds files that are uploaded into B2
53> Cloud Storage. The bucket name must be globally unique and must have a
54> minimum of 6 characters. A limit of 100 buckets may be created per
55> account. An unlimited number of files may be uploaded into a bucket.
56
57Once you click the `Create a Bucket` button on their webpage
58or mobile app, you need to provide the following:
59
60- Bucket Unique Name
61- Files in Bucket are: `Private` or `Public`
62- Default Encryption: `Disable` or `Enable`
63- Object Lock: `Disable` or `Enable`
64
65For my bucket, I created a private bucket with encryption enabled and
66object lock disabled.
67
68Once your bucket is created, you can test the upload/download feature on
69their web GUI or mobile app! At this point, you have a fully functional
70bucket and account.
71
72## Linux CLI Tool
73
74### Installation
75
76To install the `b2` CLI tool, you'll need to download it
77from the [CLI
78Tools](https://www.backblaze.com/docs/cloud-storage-command-line-tools)
79page. I recommend copying the URL from the link that says
80`Linux` and using wget to download it, as shown below.
81
82Once downloaded, make the file executable and move it to a location on
83your `$PATH`, so that you can execute that command from
84anywhere on the machine.
85
86```sh
87wget <b2_cli_url>
88chmod +x b2_linux
89mv b2_linux /usr/bin/b2
90```
91
92### Log In
93
94The first step after installation is to log in. To do this, execute the
95following command and provide your `<applicationKeyId>` and
96`<applicationKey>`.
97
98If you don't want to provide these values in the command itself, you
99can simply execute the base command and it will request them in an
100interactive prompt.
101
102```sh
103# if you want to provide the keys directly:
104b2 authorize-account [<applicationKeyId>] [<applicationKey>]
105
106# or, if you don't want your keys in your shell history:
107b2 authorize-account
108```
109
110### Upload a Test File
111
112In order to test the functionality of the CLI tool, I'll start by
113uploading a single test file to the bucket I created above. We can do
114this with the `upload_file` function.
115
116The command is issued as follows:
117
118```sh
119b2 upload_file <bucket_name> <local_file> <remote_file>
120```
121
122In my situation, I executed the following command with my username.
123
124```sh
125b2 upload_file my_unique_bucket /home/<user>/test.md test.md
126```
127
128To confirm that the file was uploaded successfully, list the files in
129your bucket:
130
131```sh
132b2 ls <bucket_name>
133```
134
135```txt
136test.md
137```
138
139### Sync a Directory
140
141If you have numerous files, you can use the `sync` function
142to perform functionality similar to `rsync`, where you can
143check what's in your bucket and sync anything that is new or modified.
144
145The command is issued as follows:
146
147```sh
148b2 sync <source file location> <B2 bucket destination>
149```
150
151In my case, I can sync my user's entire home directory to my bucket
152without specifying any of the files directly:
153
154```sh
155b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
156```
157
158## Caveats
159
160### Timing of Updates to the Web GUI
161
162When performing actions over a bucket, there is a slight delay in the
163web GUI when inspecting a bucket or its file. Note that simple actions
164such as uploading or deleting files may have a delay of a few minutes up
165to 24 hours. In my experience (\<10 GB and \~20,000 files), any actions
166took only a few minutes to update across clients.
167
168### Symlinks
169
170Note that symlinks are resolved by b2, so if you have a link from
171`/home/<user>/nas-storage` that symlinks out to a
172`/mnt/nas-storage` folder that has 10TB of data,
173`b2` will resolve that link and start uploading all 10TB of
174data linked within the folder.
175
176If you're not sure if you have any symlinks, a symlink will look like
177this (note the `->` symbol):
178
179```sh
180> ls -lha
181lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
182```
183
184You can recursively find symlink in a path with the following command:
185
186```sh
187ls -lR /path/to/search | grep '^l'
188```
blog/2023-06-30-self-hosting-voyager-lemmy-client.org → content/blog/2023-06-30-self-hosting-voyager-lemmy-client.md renamed +48 −51
@@ -1,57 +1,55 @@
1#+title: Self-Hosting Voyager: A Lemmy Web Client 1+++
2#+date: 2023-06-30 2date = 2023-06-30
3title = "Self-Hosting Voyager - A Lemmy Web Client"
4description = "A guide to self-hosting the Voyager application on your own server."
5+++
3 6
4** Installation Guide 7## Installation Guide
5:PROPERTIES: 8
6:CUSTOM_ID: installation-guide 9[Voyager](https://github.com/aeharding/voyager) is a mobile-first Lemmy
7:END: 10web client, based on iOS design standards. It follows very closely to
8[[https://github.com/aeharding/voyager][Voyager]] is a mobile-first 11Apollo's design.
9Lemmy web client, based on iOS design standards. It follows very closely
10to Apollo's design.
11 12
12This post is a guide showing how I was able to build and launch my own 13This post is a guide showing how I was able to build and launch my own
13instance of Voyager via Docker Compose. 14instance of Voyager via Docker Compose.
14 15
15*** Clone the Repository 16### Clone the Repository
16:PROPERTIES: 17
17:CUSTOM_ID: clone-the-repository
18:END:
19Start by cloning the repository and entering it: 18Start by cloning the repository and entering it:
20 19
21#+begin_src sh 20```sh
22git clone https://github.com/aeharding/voyager 21git clone https://github.com/aeharding/voyager
23cd voyager 22cd voyager
24#+end_src 23```
24
25### Build the Image
25 26
26*** Build the Image
27:PROPERTIES:
28:CUSTOM_ID: build-the-image
29:END:
30With this repository, you can build the image yourself without any 27With this repository, you can build the image yourself without any
31further configuration. When complete, it'll give you the image ID for 28further configuration. When complete, it'll give you the image ID for
32you to run. 29you to run.
33 30
34#+begin_src sh 31```sh
35sudo docker build . 32sudo docker build .
36# Successfully built 5f00723cb5be 33# Successfully built 5f00723cb5be
37#+end_src 34```
38 35
39With the image ID above, you can run the container and pass the 36With the image ID above, you can run the container and pass the
40requested port =5314= through or use a custom port, if you wish. 37requested port `5314` through or use a custom port, if you
38wish.
41 39
42You can also set the =CUSTOM_LEMMY_SERVERS= environment variable if you 40You can also set the `CUSTOM_LEMMY_SERVERS` environment
43want to add to the default suggested login servers. This must be set 41variable if you want to add to the default suggested login servers. This
44with a comma separated list of suggested servers. The first instance in 42must be set with a comma separated list of suggested servers. The first
45the list will be the default view for logged-out users. 43instance in the list will be the default view for logged-out users.
46 44
47I will be using a =docker-compose.yml= file to run this container, 45I will be using a `docker-compose.yml` file to run this
48instead of a =docker run= command. 46container, instead of a `docker run` command.
49 47
50#+begin_src sh 48```sh
51nano docker-compose.yml 49nano docker-compose.yml
52#+end_src 50```
53 51
54#+begin_src conf 52```conf
55version: "2" 53version: "2"
56services: 54services:
57 voyager: 55 voyager:
@@ -61,32 +59,30 @@ services:
61 - "<custom_port>:5314" 59 - "<custom_port>:5314"
62 environment: 60 environment:
63 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org 61 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org
64#+end_src 62```
65 63
66#+begin_src sh 64```sh
67sudo docker-compose up -d 65sudo docker-compose up -d
68#+end_src 66```
69 67
70The web app will now be available at the following address: 68The web app will now be available at the following address:
71=<machine_ip>:<custom_port>=. If you are running it on your local 69`<machine_ip>:<custom_port>`. If you are running it on your
72device, try =localhost:<custom_port>=. 70local device, try `localhost:<custom_port>`.
71
72### Reverse Proxy
73 73
74*** Reverse Proxy 74If you want to visit this app via an external URL or domain name,
75:PROPERTIES: 75you'll need to set up a reverse proxy. The example below uses Nginx as
76:CUSTOM_ID: reverse-proxy 76a reverse proxy.
77:END:
78If you want to visit this app via an external URL or domain name, you'll
79need to set up a reverse proxy. The example below uses Nginx as a
80reverse proxy.
81 77
82Simply create the configuration file, paste the contents below, save the 78Simply create the configuration file, paste the contents below, save the
83file, symlink the file, and restart Nginx. 79file, symlink the file, and restart Nginx.
84 80
85#+begin_src sh 81```sh
86sudo nano /etc/nginx/sites-available/voyager 82sudo nano /etc/nginx/sites-available/voyager
87#+end_src 83```
88 84
89#+begin_src conf 85```conf
90server { 86server {
91 if ($host ~ ^[^.]+\.example\.com$) { 87 if ($host ~ ^[^.]+\.example\.com$) {
92 return 301 https://$host$request_uri; 88 return 301 https://$host$request_uri;
@@ -116,14 +112,15 @@ server {
116 include /etc/letsencrypt/options-ssl-nginx.conf; 112 include /etc/letsencrypt/options-ssl-nginx.conf;
117 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 113 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
118} 114}
119#+end_src 115```
120 116
121#+begin_src sh 117```sh
122sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager 118sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager
123sudo systemctl restart nginx.service 119sudo systemctl restart nginx.service
124#+end_src 120```
125 121
126The site will now be available at the =server_name= you specified above! 122The site will now be available at the `server_name` you
123specified above!
127 124
128You can visit my instance at 125You can visit my instance at
129[[https://voyager.cleberg.net][voyager.cleberg.net]] for an example. 126[voyager.cleberg.net](https://voyager.cleberg.net) for an example.
blog/2023-07-12-mullvad-wireguard-lan.org → content/blog/2023-07-12-mullvad-wireguard-lan.md renamed +54 −61
@@ -1,32 +1,30 @@
1#+title: Enabling LAN Access in Mullvad Wireguard Conf Files 1+++
2#+date: 2023-07-12 2date = 2023-07-12
3 3title = "Enable LAN Access in Mullvad Wireguard Conf Files"
4** Download Configuration Files from Mullvad 4description = "Learn how to enable LAN access manually in Mullvad configuration files."
5:PROPERTIES: 5+++
6:CUSTOM_ID: download-configuration-files-from-mullvad 6
7:END: 7## Download Configuration Files from Mullvad
8To begin, you'll need 8
9[[https://mullvad.net/account/wireguard-config][Wireguard configuration 9To begin, you'll need [Wireguard configuration files from
10files from Mullvad]]. You can choose any of the options as you download 10Mullvad](https://mullvad.net/account/wireguard-config). You can choose
11them. For example, I enabled the kill switch, selected all countries, 11any of the options as you download them. For example, I enabled the kill
12and selected a few content filters. 12switch, selected all countries, and selected a few content filters.
13 13
14Once downloaded, unzip the files and move them to the Wireguard folder 14Once downloaded, unzip the files and move them to the Wireguard folder
15on your system. 15on your system.
16 16
17#+begin_src sh 17```sh
18cd ~/Downloads 18cd ~/Downloads
19unzip mullvad_wireguard_linux_all_all.zip 19unzip mullvad_wireguard_linux_all_all.zip
20doas mv *.conf /etc/wireguard/ 20doas mv *.conf /etc/wireguard/
21#+end_src 21```
22
23### Configuration File Layout
22 24
23*** Configuration File Layout
24:PROPERTIES:
25:CUSTOM_ID: configuration-file-layout
26:END:
27The default configuration files will look something like this: 25The default configuration files will look something like this:
28 26
29#+begin_src conf 27```conf
30[Interface] 28[Interface]
31# Device: <redacted> 29# Device: <redacted>
32PrivateKey = <redacted> 30PrivateKey = <redacted>
@@ -39,50 +37,45 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
39PublicKey = <redacted> 37PublicKey = <redacted>
40AllowedIPs = <redacted> 38AllowedIPs = <redacted>
41Endpoint = <redacted> 39Endpoint = <redacted>
42#+end_src 40```
43 41
44#+begin_quote 42> Note: If you didn't select the kill switch option, you won't see the
45Note: If you didn't select the kill switch option, you won't see the 43> `PostUp` and `PreDown` lines. In this case,
46=PostUp= and =PreDown= lines. In this case, you'll need to modify the 44> you'll need to modify the script below to simply append those lines
47script below to simply append those lines to the =[Interface]= block. 45> to the `[Interface]` block.
48 46
49#+end_quote 47## Editing the Configuration Files
50 48
51** Editing the Configuration Files
52:PROPERTIES:
53:CUSTOM_ID: editing-the-configuration-files
54:END:
55Once you have the files, you'll need to edit them and replace the 49Once you have the files, you'll need to edit them and replace the
56=PostUp= and =PreDown= lines to enable LAN access. 50`PostUp` and `PreDown` lines to enable LAN access.
57 51
58I recommend that you do this process as root, since you'll need to be 52I recommend that you do this process as root, since you'll need to be
59able to access files in =/etc/wireguard=, which are generally owned by 53able to access files in `/etc/wireguard`, which are generally
60root. You can also try using =sudo= or =doas=, but I didn't test that 54owned by root. You can also try using `sudo` or
61scenario so you may need to adjust, as necessary. 55`doas`, but I didn't test that scenario so you may need to
56adjust, as necessary.
62 57
63#+begin_src sh 58```sh
64su 59su
65#+end_src 60```
66 61
67Create the Python file that we'll be using to update the Wireguard 62Create the Python file that we'll be using to update the Wireguard
68configuration files. 63configuration files.
69 64
70#+begin_src sh 65```sh
71nano replace.py 66nano replace.py
72#+end_src 67```
73 68
74Within the Python file, copy and paste the logic below. This script will 69Within the Python file, copy and paste the logic below. This script will
75open a directory, loop through every configuration file within the 70open a directory, loop through every configuration file within the
76directory, and replace the =PostUp= and =PreDown= lines with the new 71directory, and replace the `PostUp` and `PreDown`
77LAN-enabled iptables commands. 72lines with the new LAN-enabled iptables commands.
78
79#+begin_quote
80Note: If your LAN is on a subnet other than =192.168.1.0/24=, you'll
81need to update the Python script below appropriately.
82 73
83#+end_quote 74> Note: If your LAN is on a subnet other than
75> `192.168.1.0/24`, you'll need to update the Python script
76> below appropriately.
84 77
85#+begin_src python 78```python
86import os 79import os
87import fileinput 80import fileinput
88 81
@@ -101,25 +94,25 @@ for file in os.listdir(dir):
101 print(line, end="") 94 print(line, end="")
102 95
103print("--- done ---") 96print("--- done ---")
104#+end_src 97```
105 98
106Once you're done, save and close the file. You can now run the Python 99Once you're done, save and close the file. You can now run the Python
107script and watch as each file is updated. 100script and watch as each file is updated.
108 101
109#+begin_src sh 102```sh
110python3 replace.py 103python3 replace.py
111#+end_src 104```
112 105
113To confirm it worked, you can =cat= one of the configuration files to 106To confirm it worked, you can `cat` one of the configuration
114inspect the new logic and connect to one to test it out. 107files to inspect the new logic and connect to one to test it out.
115 108
116#+begin_src sh 109```sh
117cat /etc/wireguard/us-chi-wg-001.conf 110cat /etc/wireguard/us-chi-wg-001.conf
118#+end_src 111```
119 112
120The configuration files should now look like this: 113The configuration files should now look like this:
121 114
122#+begin_src conf 115```conf
123[Interface] 116[Interface]
124# Device: <redacted> 117# Device: <redacted>
125PrivateKey = <redacted> 118PrivateKey = <redacted>
@@ -132,20 +125,20 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
132PublicKey = <redacted> 125PublicKey = <redacted>
133AllowedIPs = <redacted> 126AllowedIPs = <redacted>
134Endpoint = <redacted> 127Endpoint = <redacted>
135#+end_src 128```
136 129
137If you connect to a Wireguard interface, such as =us-chi-wg-001=, you 130If you connect to a Wireguard interface, such as
138can test your SSH functionality and see that it works even while on the 131`us-chi-wg-001`, you can test your SSH functionality and see
139VPN. 132that it works even while on the VPN.
140 133
141#+begin_src sh 134```sh
142wg-quick up us-chi-wg-001 135wg-quick up us-chi-wg-001
143ssh user@lan-host 136ssh user@lan-host
144#+end_src 137```
145 138
146To confirm your VPN connection, you can curl Mullvad's connection API: 139To confirm your VPN connection, you can curl Mullvad's connection API:
147 140
148#+begin_src sh 141```sh
149curl https://am.i.mullvad.net/connected 142curl https://am.i.mullvad.net/connected
150# You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted> 143# You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted>
151#+end_src 144```
blog/2023-07-19-plex-transcoder-errors.org → content/blog/2023-07-19-plex-transcoder-errors.md renamed +32 −36
@@ -1,67 +1,63 @@
1#+title: How to Fix Plex Error: 'Conversion failed. The transcoder failed to start up.' 1+++
2#+date: 2023-07-19 2date = 2023-07-19
3title = "How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.'"
4description = "Learn how to avoid Plex conversion errors caused by subtitles."
5+++
6
7## Plex Transcoder Error
3 8
4** Plex Transcoder Error
5:PROPERTIES:
6:CUSTOM_ID: plex-transcoder-error
7:END:
8Occasionally, you may see an error in your Plex client that references a 9Occasionally, you may see an error in your Plex client that references a
9failure with the transcoder conversion process. The specific error 10failure with the transcoder conversion process. The specific error
10wording is: 11wording is:
11 12
12#+begin_src txt 13```txt
13Conversion failed. The transcoder failed to start up. 14Conversion failed. The transcoder failed to start up.
14#+end_src 15```
16
17## Debugging the Cause
15 18
16** Debugging the Cause 19In order to get a better look at what is causing the error, I'm going
17:PROPERTIES: 20to observe the Plex console while the error occurs. To do this, open the
18:CUSTOM_ID: debugging-the-cause 21Plex web client, go to `Settings` > `Manage` >
19:END: 22`Console`. Now, try to play the title again and watch to see
20In order to get a better look at what is causing the error, I'm going to 23which errors occur.
21observe the Plex console while the error occurs. To do this, open the
22Plex web client, go to =Settings= > =Manage= > =Console=. Now, try to
23play the title again and watch to see which errors occur.
24 24
25In my case, you can see the errors below are related to a subtitle file 25In my case, you can see the errors below are related to a subtitle file
26(=.srt=) causing the transcoder to crash. 26(`.srt`) causing the transcoder to crash.
27 27
28#+begin_src txt 28```txt
29Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt 29Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt
30Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file 30Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file
31Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID. 31Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID.
32Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available 32Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available
33Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt" 33Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt"
34#+end_src 34```
35
36## Solving the Error
35 37
36** Solving the Error
37:PROPERTIES:
38:CUSTOM_ID: solving-the-error
39:END:
40In my case, I simply removed the subtitle file because it was not 38In my case, I simply removed the subtitle file because it was not
41critical to keep. You may also avoid this by turning off subtitles if 39critical to keep. You may also avoid this by turning off subtitles if
42you don't want to delete the file. 40you don't want to delete the file.
43 41
44#+begin_src sh 42```sh
45rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt" 43rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt"
46#+end_src 44```
47 45
48Once the subtitle is removed from the directory or subtitles are turned 46Once the subtitle is removed from the directory or subtitles are turned
49off, try to play the title again. At this point, it should play without 47off, try to play the title again. At this point, it should play without
50error. If not, reopen or refresh your Plex client and the Plex server: 48error. If not, reopen or refresh your Plex client and the Plex server:
51 49
52#+begin_src sh 50```sh
53sudo systemctl restart plexmediaserver.service 51sudo systemctl restart plexmediaserver.service
54#+end_src 52```
53
54## Related Discussion
55 55
56** Related Discussion 56Looking at [a related Plex forum
57:PROPERTIES: 57post](https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2),
58:CUSTOM_ID: related-discussion 58it seems that `.srt` subtitles are the core issue here.
59:END: 59However, Plex does not seem to have a resolution that fixes these
60Looking at 60errors.
61[[https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2][a
62related Plex forum post]], it seems that =.srt= subtitles are the core
63issue here. However, Plex does not seem to have a resolution that fixes
64these errors.
65 61
66Unfortunately, I would suggest converting subtitle formats, burning the 62Unfortunately, I would suggest converting subtitle formats, burning the
67subtitles into the title, or simply removing subtitles when they cause 63subtitles into the title, or simply removing subtitles when they cause
content/blog/2023-08-18-agile-auditing.md added +158
@@ -0,0 +1,158 @@
1+++
2date = 2023-08-18
3title = "Agile Auditing: An Introduction"
4description = "A quick introduction to using the Agile methodology in an audit."
5+++
6
7## What is Agile Auditing?
8
9[Agile](https://en.wikipedia.org/wiki/Agile_software_development), the
10collaborative philosophy behind many software development methods, has
11been picking up steam as a beneficial tool to use in the external and
12internal auditing world.
13
14This blog post will walk through commonly used terms within Agile,
15Scrum, and Kanban in order to translate these terms and roles into
16audit-specific terms.
17
18Whether your team is in charge of a financial statement audit, an
19attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms
20used throughout this post should still apply.
21
22## Agile
23
24To start, I'll take a look at Agile.
25
26> The Agile methodology is a project management approach that involves
27> breaking the project into phases and emphasizes continuous
28> collaboration and improvement. Teams follow a cycle of planning,
29> executing, and evaluating.
30
31While this approach may seem familiar to what audit teams have
32historically done, an audit team must make distinct changes in their
33mentality and how they approach and manage a project.
34
35### Agile Values
36
37The Agile Manifesto, written in 2001 at a summit in Utah, contain a set
38of four main values that comprise the Agile approach:
39
401. Individuals and interactions over processes and tools.
412. Working software over comprehensive documentation.
423. Customer collaboration over contract negotiation.
434. Responding to change over following a plan.
44
45Beyond the four values, [twelve
46principles](https://agilemanifesto.org/principles.html) were also
47written as part of the summit.
48
49In order to relate these values to an audit or attestation engagement,
50we need to shift the focus from software development to the main goal of
51an engagement: completing sufficient audit testing to address to
52relevant risks over the processes and controls at hand.
53
54Audit Examples:
55
56- Engagement teams must value the team members, client contacts, and
57 their interactions over the historical processes and tools that have
58 been used.
59- Engagement teams must value a final report that contains sufficient
60 audit documentation over excessive documentation or scope creep.
61- Engagement teams must collaborate with the audit clients as much as
62 feasible to ensure that both sides are constantly updated with
63 current knowledge of the engagement's status and any potential
64 findings, rather than waiting for pre-set meetings or the end of the
65 engagement to communicate.
66- Engagement teams must be able to respond to change in an
67 engagement's schedule, scope, or environment to ensure that the
68 project is completed in a timely manner and that all relevant areas
69 are tested.
70 - In terms of an audit department's portfolio, they must be able
71 to respond to changes in their company's or client's
72 environment and be able to dynamically change their audit plan
73 accordingly.
74
75## Scrum
76
77The above section discusses the high-level details of the Agile
78philosophy and how an audit team can potentially mold that mindset into
79the audit world, but how does a team implement these ideas?
80
81There are many methods that use an Agile mindset, but I prefer
82[Scrum](https://en.wikipedia.org/wiki/Scrum_(software_development)).
83Scrum is a framework based on Agile that enables a team to work through
84a project through a series of roles, ceremonies, artifacts, and values.
85
86Let's dive into each of these individually.
87
88### Scrum Team
89
90A scrum project is only as good as the team running the project.
91Standard scrum teams are separated into three distinct areas:
92
931. **Product Owner (Client Contact)**: The client contact is the audit
94 equivalent of the product owner in Scrum. They are responsible for
95 partnering with the engagement or audit team to ensure progress is
96 being made, priorities are established, and clear guidance is given
97 when questions or findings arise within each sprint.
982. **Scrum Master (Engagement Lead)**: The engagement or audit team
99 lead is responsible for coaching the team and the client contact on
100 the scrum process, tracking team progress against plan, scheduling
101 necessary resources, and helping remove obstacles.
1023. **Scrum Developers (Engagement Members)**: The engagement or audit
103 team is the set of team members responsible for getting the work
104 done. These team members will work on each task, report progress,
105 resolve obstacles, and collaborate with other team members and the
106 client contact to ensure goals are being met.
107
108### Scrum Ceremonies
109
110Scrum ceremonies are events that are performed on a regular basis.
111
1121. **Sprint Planning**: The team works together to plan the upcoming
113 sprint goal and which user stories (tasks) will be added to the
114 sprint to achieve that goal.
1152. **Sprint**: The time period, typically at least one week and no more
116 than one month in length, where the team works on the stories and
117 anything in the backlog.
1183. **Daily Scrum**: A very short meeting held each day, typically 15
119 minutes, to quickly emphasize alignment on the sprint goal and plan
120 the next 24 hours. Each team member may share what they did the day
121 before, what they'll do today, and any obstacles to their work.
1224. **Sprint Review**: At the end of each sprint, the team will gather
123 and discuss the progress, obstacles, and backlog from the previous
124 sprint.
1255. **Sprint Retrospective**: More specific than the sprint review, the
126 retrospective is meant to discuss what worked and what did not work
127 during the sprint. This may be processes, tools, people, or even
128 things related to the Scrum ceremonies.
129
130One additional ceremony that may be applicable is organizing the
131backlog. This is typically the responsibility of the engagement leader
132and is meant to prioritize and clarify what needs to be done to complete
133items in the backlog.
134
135### Artifacts
136
137While artifacts are generally not customizable in the audit world (i.e.,
138each control test must include some kind of working paper with evidence
139supporting the test results), I wanted to include some quick notes on
140associating scrum artifact terms with an audit.
141
1421. **Product Backlog**: This is the overall backlog of unfinished audit
143 tasks from all prior sprints.
1442. **Sprint Backlog**: This is the backlog of unfinished audit tasks
145 from one individual sprint.
1463. **Increment**: This is the output of each sprint - generally this is
147 best thought of as any documentation prepared during the sprint,
148 such as risk assessments, control working papers, deficiency
149 analysis, etc.
150
151## Kanban
152
153Last but not least, Kanban is a methodology that relies on boards to
154categorize work into distinct, descriptive categories that allow an
155agile or scrum team to effectively plan the work of a sprint or project.
156
157See Atlassian's [Kanban](https://www.atlassian.com/agile/kanban) page
158for more information.
blog/2023-09-15-gitweb.org → content/blog/2023-09-15-gitweb.md renamed +29 −34
@@ -1,13 +1,15 @@
1#+title: GitWeb via Nginx 1+++
2#+date: 2023-09-16 2date = 2023-09-15
3 3title = "GitWeb via Nginx"
4** Overview 4description = "A guide to self-hosting GitWeb using the Nginx web server."
5:PROPERTIES: 5+++
6:CUSTOM_ID: overview 6
7:END: 7## Overview
8[[https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb][GitWeb]] is a 8
9[GitWeb](https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb) is a
9simple web-based visualizer for git repositories. By default, GitWeb 10simple web-based visualizer for git repositories. By default, GitWeb
10will only run with the =lighttpd= or =webrick= web servers. 11will only run with the `lighttpd` or `webrick` web
12servers.
11 13
12However, this guide will show you how to keep GitWeb running in the 14However, this guide will show you how to keep GitWeb running in the
13background and display information for all repositories in a chosen 15background and display information for all repositories in a chosen
@@ -15,30 +17,25 @@ directory.
15 17
16See below for the final result: 18See below for the final result:
17 19
18#+caption: Gitweb 20![Gitweb](https://img.cleberg.net/blog/20230915-gitweb/gitweb.png)
19[[https://img.cleberg.net/blog/20230915-gitweb/gitweb.png]] 21
22## Install Dependencies
20 23
21** Install Dependencies
22:PROPERTIES:
23:CUSTOM_ID: install-dependencies
24:END:
25To start, you'll need install the following packages: 24To start, you'll need install the following packages:
26 25
27#+begin_src sh 26```sh
28sudo apt install git gitweb fcgiwrap nginx 27sudo apt install git gitweb fcgiwrap nginx
29#+end_src 28```
29
30## Configure Nginx
30 31
31** Configure Nginx
32:PROPERTIES:
33:CUSTOM_ID: configure-nginx
34:END:
35Once installed, create an Nginx configuration file. 32Once installed, create an Nginx configuration file.
36 33
37#+begin_src sh 34```sh
38sudo nano /etc/nginx/sites-available/git.example.com 35sudo nano /etc/nginx/sites-available/git.example.com
39#+end_src 36```
40 37
41#+begin_src conf 38```conf
42server { 39server {
43 listen 80; 40 listen 80;
44 server_name example.com; 41 server_name example.com;
@@ -57,26 +54,24 @@ server {
57 index index.cgi; 54 index index.cgi;
58 } 55 }
59} 56}
60#+end_src 57```
61 58
62To make the configuration active, you need to symlink it and then 59To make the configuration active, you need to symlink it and then
63restart Nginx. 60restart Nginx.
64 61
65#+begin_src sh 62```sh
66sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com 63sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com
67sudo systemctl restart nginx.service 64sudo systemctl restart nginx.service
68#+end_src 65```
69 66
70The GitWeb application should now be available via the URL you set in 67The GitWeb application should now be available via the URL you set in
71the Nginx configuration above. 68the Nginx configuration above.
72 69
73** Customize GitWeb 70## Customize GitWeb
74:PROPERTIES: 71
75:CUSTOM_ID: customize-gitweb
76:END:
77If you need to, you can customize many things about Gitweb by editing 72If you need to, you can customize many things about Gitweb by editing
78the [[https://git-scm.com/docs/gitweb.conf][gitweb.conf]] file. 73the [gitweb.conf](https://git-scm.com/docs/gitweb.conf) file.
79 74
80#+begin_src sh 75```sh
81sudo nano /etc/gitweb.conf 76sudo nano /etc/gitweb.conf
82#+end_src 77```
blog/2023-09-19-audit-sql-scripts.org → content/blog/2023-09-19-audit-sql-scripts.md renamed +49 −55
@@ -1,25 +1,24 @@
1#+title: Useful SQL Scripts for Auditing Logical Access 1+++
2#+date: 2023-09-20 2date = 2023-09-19
3title = "Useful SQL Scripts for Auditing Logical Access"
4description = "A reference of SQL scripts for auditing logical access for common databases."
5+++
6
7## Overview
3 8
4** Overview
5:PROPERTIES:
6:CUSTOM_ID: overview
7:END:
8When you have to scope a database into your engagement, you may be 9When you have to scope a database into your engagement, you may be
9curious how to best extract the information from the database. While 10curious how to best extract the information from the database. While
10there are numerous different methods to extract this type of 11there are numerous different methods to extract this type of
11information, I'm going to show an example of how to gather all users and 12information, I'm going to show an example of how to gather all users
12privileges from three main database types: Oracle, Microsoft SQL, and 13and privileges from three main database types: Oracle, Microsoft SQL,
13MySQL. 14and MySQL.
15
16## Oracle
14 17
15** Oracle
16:PROPERTIES:
17:CUSTOM_ID: oracle
18:END:
19You can use the following SQL script to see all users and their 18You can use the following SQL script to see all users and their
20privileges in an Oracle database: 19privileges in an Oracle database:
21 20
22#+begin_src sql 21```sql
23SELECT 22SELECT
24 grantee AS "User", 23 grantee AS "User",
25 privilege AS "Privilege" 24 privilege AS "Privilege"
@@ -35,43 +34,40 @@ FROM
35 dba_tab_privs 34 dba_tab_privs
36WHERE 35WHERE
37 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs); 36 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs);
38#+end_src 37```
39 38
40This script queries the =dba_sys_privs= and =dba_tab_privs= views to 39This script queries the `dba_sys_privs` and
41retrieve system and table-level privileges respectively. It then 40`dba_tab_privs` views to retrieve system and table-level
42combines the results using =UNION ALL= to show all users and their 41privileges respectively. It then combines the results using
43associated privileges. Please note that this method does not extract 42`UNION ALL` to show all users and their associated
44information from the =dba_role_privs= table - use the method below for 43privileges. Please note that this method does not extract information
44from the `dba_role_privs` table - use the method below for
45that data. 45that data.
46 46
47Please note that you might need appropriate privileges (e.g., DBA 47Please note that you might need appropriate privileges (e.g., DBA
48privileges) to access these views, and you should exercise caution when 48privileges) to access these views, and you should exercise caution when
49querying system tables in a production Oracle database. 49querying system tables in a production Oracle database.
50 50
51*** Alternative Oracle Query 51### Alternative Oracle Query
52:PROPERTIES: 52
53:CUSTOM_ID: alternative-oracle-query
54:END:
55You can also extract each table's information separately and perform 53You can also extract each table's information separately and perform
56processing outside the database to explore and determine the information 54processing outside the database to explore and determine the information
57necessary for the audit: 55necessary for the audit:
58 56
59#+begin_src sql 57```sql
60SELECT * FROM sys.dba_role_privs; 58SELECT * FROM sys.dba_role_privs;
61SELECT * FROM sys.dba_sys_privs; 59SELECT * FROM sys.dba_sys_privs;
62SELECT * FROM sys.dba_tab_privs; 60SELECT * FROM sys.dba_tab_privs;
63SELECT * FROM sys.dba_users; 61SELECT * FROM sys.dba_users;
64#+end_src 62```
63
64## Microsoft SQL
65 65
66** Microsoft SQL
67:PROPERTIES:
68:CUSTOM_ID: microsoft-sql
69:END:
70You can use the following SQL script to see all users and their 66You can use the following SQL script to see all users and their
71privileges in a Microsoft SQL Server database 67privileges in a Microsoft SQL Server database
72([[https://stackoverflow.com/a/30040784][source]]): 68([source](https://stackoverflow.com/a/30040784)):
73 69
74#+begin_src sql 70```sql
75/* 71/*
76Security Audit Report 72Security Audit Report
771) List all access provisioned to a sql user or windows user/group directly 731) List all access provisioned to a sql user or windows user/group directly
@@ -216,47 +212,45 @@ ORDER BY
216 perm.[permission_name], 212 perm.[permission_name],
217 perm.[state_desc], 213 perm.[state_desc],
218 obj.type_desc--perm.[class_desc] 214 obj.type_desc--perm.[class_desc]
219#+end_src 215```
216
217## MySQL
220 218
221** MySQL
222:PROPERTIES:
223:CUSTOM_ID: mysql
224:END:
225You can use the following SQL script to see all users and their 219You can use the following SQL script to see all users and their
226privileges in a MySQL database: 220privileges in a MySQL database:
227 221
228#+begin_src shell 222```sh
229mysql -u root -p 223mysql -u root -p
230#+end_src 224```
231 225
232Find all users and hosts with access to the database: 226Find all users and hosts with access to the database:
233 227
234#+begin_src sql 228```sql
235SELECT * FROM information_schema.user_privileges; 229SELECT * FROM information_schema.user_privileges;
236#+end_src 230```
237 231
238This script retrieves user information and their associated 232This script retrieves user information and their associated
239database-level privileges from the =information_schema.user_privileges= 233database-level privileges from the
240table in MySQL. It lists various privileges such as SELECT, INSERT, 234`information_schema.user_privileges` table in MySQL. It lists
241UPDATE, DELETE, CREATE, and more for each user and database combination. 235various privileges such as SELECT, INSERT, UPDATE, DELETE, CREATE, and
236more for each user and database combination.
242 237
243Please note that you may need appropriate privileges (e.g., =SELECT= 238Please note that you may need appropriate privileges (e.g.,
244privileges on =information_schema.user_privileges=) to access this 239`SELECT` privileges on
240`information_schema.user_privileges`) to access this
245information in a MySQL database. Additionally, some privileges like 241information in a MySQL database. Additionally, some privileges like
246GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all 242GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all
247users and databases. 243users and databases.
248 244
249*** Alternative MySQL Query 245### Alternative MySQL Query
250:PROPERTIES: 246
251:CUSTOM_ID: alternative-mysql-query
252:END:
253You can also grab individual sets of data from MySQL if you prefer to 247You can also grab individual sets of data from MySQL if you prefer to
254join them after extraction. I have marked the queries below with 248join them after extraction. I have marked the queries below with
255=SELECT ...= and excluded most =WHERE= clauses for brevity. You should 249`SELECT ...` and excluded most `WHERE` clauses for
256determine the relevant privileges in-scope and query for those 250brevity. You should determine the relevant privileges in-scope and query
257privileges to reduce the length of time to query. 251for those privileges to reduce the length of time to query.
258 252
259#+begin_src sql 253```sql
260-- Global Permissions 254-- Global Permissions
261SELECT ... FROM mysql.user; 255SELECT ... FROM mysql.user;
262 256
@@ -275,4 +269,4 @@ WHERE db = @db_name;
275-- Password Configuration 269-- Password Configuration
276SHOW GLOBAL VARIABLES LIKE 'validate_password%'; 270SHOW GLOBAL VARIABLES LIKE 'validate_password%';
277SHOW VARIABLES LIKE 'validate_password%'; 271SHOW VARIABLES LIKE 'validate_password%';
278#+end_src 272```
content/blog/2023-10-04-digital-minimalism.md added +106
@@ -0,0 +1,106 @@
1+++
2date = 2023-10-04
3title = "Digital Minimalism"
4description = "My personal retrospective on digital minimalism."
5+++
6
7I've written [a note about minimalism](/wiki/#digital-garden)
8before, but I wanted to dedicate some time to reflect on digital
9minimalism and how I've been able to minimize the impact of digital
10devices in my life.
11
12> These changes crept up on us and happened fast, before we had a chance
13> to step back and ask what we really wanted out of the rapid advances
14> of the past decade. We added new technologies to the periphery of our
15> experience for minor reasons, then woke one morning to discover that
16> they had colonized the core of our daily life. We didn't, in other
17> words, sign up for the digital world in which we're currently
18> entrenched; we seem to have stumbled backward into it.
19>
20> *(Digital Minimalism, 2019)*
21
22## The Principles of Digital Minimalism
23
24As noted in Cal Newport's book, *Digital Minimalism*, there are three
25main principles to digital minimalism that I tend to agree with:
26
271. Clutter is costly.
28 - Digital minimalists recognize that cluttering their time and
29 attention with too many devices, apps, and services creates an
30 overall negative cost that can swamp the small benefits that
31 each individual item provides in isolation.
322. Optimization is important.
33 - Digital minimalists believe that deciding a particular
34 technology supports something they value is only the first step.
35 To truly extract its full potential benefit, it's necessary to
36 think carefully about how they'll use the technology.
373. Intentionality is satisfying.
38 - Digital minimalists derive significant satisfaction from their
39 general commitment to being more intentional about how they
40 engage with new technologies. This source of satisfaction is
41 independent of the specific decisions they make and is one of
42 the biggest reasons that minimalism tends to be immensely
43 meaningful to its practitioners.
44
45## Taking Action
46
47In order to put the logic into practice, I've created a few new habits
48and continued performing old habits that are working well:
49
50### Using Devices With Intention
51
52- I already rarely use "social media", mostly limited to forums such
53 as Hacker News and Tildes, so I've just tweaked my behavior to stop
54 looking for content in those places when I'm bored.
55- Use devices with intention. Each time I pick up a digital device,
56 there should be an intention to use the device to improve my current
57 situation. No more endless scrolling or searching for something to
58 interest me.
59
60### Prevent Distractions
61
62- Disable (most) notifications on all devices. I spent 15-30 minutes
63 going through the notifications on my phone, watch, and computer to
64 ensure that only a select few apps have the ability to interrupt me:
65 Calendar, Messages, Phone, Reminders, & Signal.
66- Disable badges for any apps except the ones mentioned in the bullet
67 above.
68- Set-up focus profiles across devices so that I can enable different
69 modes, such as Personal when I only want to see notifications from
70 people I care about or Do Not Disturb, where absolutely nothing can
71 interrupt me.
72- Clean up my home screens. This one was quite easy as I already
73 maintain a minimalist set-up, but I went extreme by limiting my
74 phone to just eight apps on the home screen and four in the dock. If
75 I need another app, I'll have to search or use the app library.
76- Remove the work profile from my phone. This was a tough decision as
77 having my work profile on my device definitely makes my life easier
78 at times, but it also has quite a negative effect when I'm "always
79 online" and can see the notifications and team activity 24/7. I
80 believe creating a distinct barrier between my work and personal
81 devices will be beneficial in the end.
82
83### Creating Alternative Activities
84
85This is the most difficult piece, as most of my hobbies and interests
86lie in the digital world. However, I'm making a concerted effort to put
87devices down unless necessary and force myself to perform other
88activities in the physical world instead.
89
90I've started with a few basics that are always readily available to me:
91
92- Do a chore, such as organizing or cleaning.
93- Read a book, study a piece of art, etc.
94- Exercise or get outdoors.
95- Participate in a hobby, such as photography, birding, disc golf,
96 etc.
97- Let yourself be bored and wander into creativity.
98
99## Making Progress
100
101I'll be taking notes as I continue down this journey and hope to see
102positive trends. I've always been a minimalist in the physical world
103and it feels refreshing to filter out the clutter that has come to
104dominate my digital life over the years.
105
106I'm excited to see where this journey leads.
blog/2023-10-11-self-hosting-authelia.org → content/blog/2023-10-11-self-hosting-authelia.md renamed +127 −133
@@ -1,59 +1,56 @@
1#+title: Self-Hosting Authelia 1+++
2#+date: 2023-10-11 2date = 2023-10-11
3title = "Self-Hosting Authelia"
4description = "A guide to self-hosting the Authelia application on your own server."
5+++
3 6
4** Overview 7## Overview
5:PROPERTIES:
6:CUSTOM_ID: overview
7:END:
8[[https://www.authelia.com/][Authelia]] is an open-source authentication service that allows you to place a
9portal between end users on the internet and self-hosted services on your
10server.
11 8
12You can require one factor (username+password) or two factor authentication for 9[Authelia](https://www.authelia.com/) is an open-source authentication
13any such user before allowing them to access a specific service on your domain. 10service that allows you to place a portal between end users on the
11internet and self-hosted services on your server.
12
13You can require one factor (username+password) or two factor
14authentication for any such user before allowing them to access a
15specific service on your domain.
14 16
15This guide will walk through a standard installation of Authelia for 17This guide will walk through a standard installation of Authelia for
16=example.com=, using =auth.example.com= as Authelia's authentication domain and 18`example.com`, using `auth.example.com` as
17=teddit.example.com= as the website we want to protect behind the authentication 19Authelia's authentication domain and `teddit.example.com` as
18portal. 20the website we want to protect behind the authentication portal.
19 21
20** Prerequisites 22## Prerequisites
21:PROPERTIES: 23
22:CUSTOM_ID: prerequisites
23:END:
24This guide assumes you have the following already set-up: 24This guide assumes you have the following already set-up:
25 25
26- A registered domain with DNS pointing to your server. 26- A registered domain with DNS pointing to your server.
27- A subdomain for Authelia (=auth.example.com=) and a subdomain to 27- A subdomain for Authelia (`auth.example.com`) and a
28 protect via Authelia (=app.example.com=). 28 subdomain to protect via Authelia (`app.example.com`).
29- A working Nginx web server. 29- A working Nginx web server.
30- Docker and docker-compose installed. 30- Docker and docker-compose installed.
31 31
32** Installation 32## Installation
33:PROPERTIES: 33
34:CUSTOM_ID: installation 34This guide will walk through each installation step one-by-one, starting
35:END: 35with the container and finishing by cleaning up external access via an
36This guide will walk through each installation step one-by-one, starting with 36Nginx reverse proxy.
37the container and finishing by cleaning up external access via an Nginx reverse 37
38proxy. 38### Docker-Compose
39 39
40*** Docker-Compose
41:PROPERTIES:
42:CUSTOM_ID: docker-compose
43:END:
44To start, create a directory for Authelia and create a 40To start, create a directory for Authelia and create a
45=docker-compose.yml= file. 41`docker-compose.yml` file.
46 42
47#+begin_src sh 43```sh
48mkdir ~/authelia 44mkdir ~/authelia
49nano ~/authelia/docker-compose.yml 45nano ~/authelia/docker-compose.yml
50#+end_src 46```
51 47
52Within this file, paste the following content. If you prefer a different local 48Within this file, paste the following content. If you prefer a different
53port, modify the port on the left side of the colon on the =9091:9091= line. Be 49local port, modify the port on the left side of the colon on the
54sure to modify the =TZ= variable to your timezone. 50`9091:9091` line. Be sure to modify the `TZ`
51variable to your timezone.
55 52
56#+begin_src yml 53```yml
57version: '3.3' 54version: '3.3'
58 55
59services: 56services:
@@ -66,52 +63,55 @@ services:
66 - 9091:9091 63 - 9091:9091
67 environment: 64 environment:
68 - TZ=America/Chicago 65 - TZ=America/Chicago
69#+end_src 66```
70 67
71Start the container with docker-compose: 68Start the container with docker-compose:
72 69
73#+begin_src sh 70```sh
74sudo docker-compose up -d 71sudo docker-compose up -d
75#+end_src 72```
73
74After the first start, the container will automatically exit and require
75you to modify the app's configuration files before continuing. Read on
76to learn more.
76 77
77After the first start, the container will automatically exit and require you to 78### Authelia Configuration
78modify the app's configuration files before continuing. Read on to learn more.
79 79
80*** Authelia Configuration 80To configure Authelia before we restart the container, we need to open
81:PROPERTIES: 81the `config` directory and modify the files. Start by editing
82:CUSTOM_ID: authelia-configuration 82the `configuration.yml` file, where all of Authelia's
83:END: 83settings are stored.
84To configure Authelia before we restart the container, we need to open the
85=config= directory and modify the files. Start by editing the
86=configuration.yml= file, where all of Authelia's settings are stored.
87 84
88My personal preference is to copy the original configuration file to a backup 85My personal preference is to copy the original configuration file to a
89file and edit a fresh copy. 86backup file and edit a fresh copy.
90 87
91#+begin_src sh 88```sh
92sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk 89sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk
93sudo nano ~/authelia/config/configuration.yml 90sudo nano ~/authelia/config/configuration.yml
94#+end_src 91```
95 92
96Within the blank =configuration.yml= file, paste the following information. You 93Within the blank `configuration.yml` file, paste the
97will need to make quite a few updates, so be sure to read each line carefully 94following information. You will need to make quite a few updates, so be
98and modify as necessary. 95sure to read each line carefully and modify as necessary.
99 96
100The major required changes are: 97The major required changes are:
101 98
102- Any instances of =example.com= should be replaced by your domain. 99- Any instances of `example.com` should be replaced by your
103- =jwt_secret= - Use the =pwgen 40 1= command to generate a secret for yourself. 100 domain.
104- =access_control= - Set the Authelia domain to bypass here, as well as any 101- `jwt_secret` - Use the `pwgen 40 1` command to
105 subdomains you want to protect. 102 generate a secret for yourself.
106- =session= > =secret= - Use the =pwgen 40 1= command to generate a secret for 103- `access_control` - Set the Authelia domain to bypass
107 yourself. 104 here, as well as any subdomains you want to protect.
108- =regulation= - Set the variables here to restrict login attempts and bans. 105- `session` > `secret` - Use the
109- =storage= > =encryption_key= - Use the =pwgen 40 1= command to generate a 106 `pwgen 40 1` command to generate a secret for yourself.
110 secret for yourself. 107- `regulation` - Set the variables here to restrict login
111- =smtp= - If you have access to an SMTP service, set up the information here to 108 attempts and bans.
112 active outgoing emails. 109- `storage` > `encryption_key` - Use the
113 110 `pwgen 40 1` command to generate a secret for yourself.
114#+begin_src yml 111- `smtp` - If you have access to an SMTP service, set up
112 the information here to active outgoing emails.
113
114```yml
115# yamllint disable rule:comments-indentation 115# yamllint disable rule:comments-indentation
116--- 116---
117############################################################################### 117###############################################################################
@@ -200,27 +200,26 @@ notifier:
200 skip_verify: false 200 skip_verify: false
201 minimum_version: TLS1.2 201 minimum_version: TLS1.2
202... 202...
203#+end_src 203```
204
205### Authelia Users
204 206
205*** Authelia Users
206:PROPERTIES:
207:CUSTOM_ID: authelia-users
208:END:
209Next, create the users file for authentication. 207Next, create the users file for authentication.
210 208
211#+begin_src sh 209```sh
212sudo nano ~/authelia/config/users_database.yml 210sudo nano ~/authelia/config/users_database.yml
213#+end_src 211```
214 212
215Within the file, you will need to create an entry for each user that needs 213Within the file, you will need to create an entry for each user that
216access to Authelia. The =my_username= entry will be the username used on the 214needs access to Authelia. The `my_username` entry will be the
217login page. 215username used on the login page.
218 216
219To generate the password, go to [[https://argon2.online][Argon2 Hash Generator]], generate a random salt, 217To generate the password, go to [Argon2 Hash
220and make sure the rest of the settings match the =authentication_backend= 218Generator](https://argon2.online), generate a random salt, and make sure
221section of =configuration.yml= file. 219the rest of the settings match the `authentication_backend`
220section of `configuration.yml` file.
222 221
223#+begin_src yml 222```yaml
224users: 223users:
225 my_username: 224 my_username:
226 displayname: "My User" 225 displayname: "My User"
@@ -231,33 +230,32 @@ users:
231 groups: 230 groups:
232 - admins 231 - admins
233 - dev 232 - dev
234#+end_src 233```
235 234
236Once the app is configured, restart the container from scratch. 235Once the app is configured, restart the container from scratch.
237 236
238#+begin_src sh 237```sh
239cd ~/authelia 238cd ~/authelia
240sudo docker-compose down && sudo docker-compose up -d 239sudo docker-compose down && sudo docker-compose up -d
241#+end_src 240```
242 241
243*** Nginx: Authelia Domain 242### Nginx: Authelia Domain
244:PROPERTIES: 243
245:CUSTOM_ID: nginx-authelia-domain 244Once the container is running and configured, the final step is to
246:END: 245configure external access to the server via Nginx reverse proxy.
247Once the container is running and configured, the final step is to configure
248external access to the server via Nginx reverse proxy.
249 246
250Start by creating the Authelia domain. 247Start by creating the Authelia domain.
251 248
252#+begin_src sh 249```sh
253sudo nano /etc/nginx/sites-available/auth 250sudo nano /etc/nginx/sites-available/auth
254#+end_src 251```
255 252
256Within this file, paste the following information and be sure to update 253Within this file, paste the following information and be sure to update
257=example.com= to your domain. Make sure the =$upstream_authelia= variable 254`example.com` to your domain. Make sure the
258matches the location of your Authelia container. 255`$upstream_authelia` variable matches the location of your
256Authelia container.
259 257
260#+begin_src conf 258```conf
261server { 259server {
262 if ($host ~ ^[^.]+\.example\.com$) { 260 if ($host ~ ^[^.]+\.example\.com$) {
263 return 301 https://$host$request_uri; 261 return 301 https://$host$request_uri;
@@ -309,32 +307,30 @@ server {
309 } 307 }
310 308
311} 309}
312#+end_src 310```
313 311
314Next, symlink the file and restart Nginx. If there are errors, be sure to 312Next, symlink the file and restart Nginx. If there are errors, be sure
315resolve those before moving on. 313to resolve those before moving on.
316 314
317#+begin_src sh 315```sh
318sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth 316sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth
319sudo systemctl restart nginx.service 317sudo systemctl restart nginx.service
320#+end_src 318```
319
320### Nginx: Protected Domain(s)
321 321
322*** Nginx: Protected Domain(s) 322Now that Authelia is accessible externally, you need to configure the
323:PROPERTIES: 323domain you intend to protect with Authelia. In this example, I'm
324:CUSTOM_ID: nginx-protected-domains 324protecting `teddit.example.com`.
325:END:
326Now that Authelia is accessible externally, you need to configure the domain you
327intend to protect with Authelia. In this example, I'm protecting
328=teddit.example.com=.
329 325
330Similar to the process above, paste the content and update the relevant 326Similar to the process above, paste the content and update the relevant
331variables. 327variables.
332 328
333#+begin_src sh 329```sh
334sudo nano /etc/nginx/sites-available/teddit 330sudo nano /etc/nginx/sites-available/teddit
335#+end_src 331```
336 332
337#+begin_src conf 333```conf
338server { 334server {
339 if ($host ~ ^[^.]+\.example\.com$) { 335 if ($host ~ ^[^.]+\.example\.com$) {
340 return 301 https://$host$request_uri; 336 return 301 https://$host$request_uri;
@@ -436,28 +432,26 @@ server {
436 proxy_buffers 64 256k; 432 proxy_buffers 64 256k;
437 } 433 }
438} 434}
439#+end_src 435```
440 436
441Same as before, symlink the file and restart Nginx. 437Same as before, symlink the file and restart Nginx.
442 438
443#+begin_src sh 439```sh
444sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit 440sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit
445sudo systemctl restart nginx.service 441sudo systemctl restart nginx.service
446#+end_src 442```
443
444## Results
447 445
448** Results
449:PROPERTIES:
450:CUSTOM_ID: results
451:END:
452When visiting the protected domain, you will now be redirected to your 446When visiting the protected domain, you will now be redirected to your
453authentication domain and presented with the Authelia login portal. 447authentication domain and presented with the Authelia login portal.
454 448
455#+caption: Authelia Portal 449![Authelia
456[[https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png]] 450Portal](https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png "Authelia Portal")
457 451
458Once you've successfully authenticated, you can visit your authentication domain 452Once you've successfully authenticated, you can visit your
459directly and see that you're currently authenticated to any domain protected by 453authentication domain directly and see that you're currently
460Authelia. 454authenticated to any domain protected by Authelia.
461 455
462#+caption: Authelia Success 456![Authelia
463[[https://img.cleberg.net/blog/20231010-authelia/authelia_success.png]] 457Success](https://img.cleberg.net/blog/20231010-authelia/authelia_success.png "Authelia Success")
blog/2023-10-15-alpine-ssh-hardening.org → content/blog/2023-10-15-alpine-ssh-hardening.md renamed +40 −40
@@ -1,74 +1,74 @@
1#+title: SSH Hardening for Alpine Linux 1+++
2#+date: 2023-10-16 2date = 2023-10-15
3title = "SSH Hardening for Alpine Linux"
4description = "A quick guide to harden SSH configuration on Alpine."
5+++
6
7## Overview
3 8
4** Overview
5:PROPERTIES:
6:CUSTOM_ID: overview
7:END:
8This guide follows the standard 9This guide follows the standard
9[[https://www.ssh-audit.com/hardening_guides.html][ssh-audit]] hardening 10[ssh-audit](https://www.ssh-audit.com/hardening_guides.html) hardening
10guide, tweaked for Alpine Linux. 11guide, tweaked for Alpine Linux.
11 12
12** Hardening Guide 13## Hardening Guide
13:PROPERTIES: 14
14:CUSTOM_ID: hardening-guide 15These steps must be performed as root. You can try to use
15:END: 16`doas` or `sudo`, but there may be issues.
16These steps must be performed as root. You can try to use =doas= or
17=sudo=, but there may be issues.
18 17
191. Re-generate the RSA and ED25519 keys 181. Re-generate the RSA and ED25519 keys
20 19
21#+begin_src sh 20```sh
22rm /etc/ssh/ssh_host_* 21rm /etc/ssh/ssh_host_*
23ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" 22ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
24ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" 23ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
25#+end_src 24```
26 25
272. [@2] Remove small Diffie-Hellman moduli 262. Remove small Diffie-Hellman moduli
28 27
29#+begin_src sh 28```sh
30awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe 29awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe
31mv /etc/ssh/moduli.safe /etc/ssh/moduli 30mv /etc/ssh/moduli.safe /etc/ssh/moduli
32#+end_src 31```
33 32
343. [@3] Enable the RSA and ED25519 HostKey directives in the 333. Enable the RSA and ED25519 HostKey directives in the
35 /etc/ssh/sshd_config file 34 /etc/ssh/sshd~config~ file
36 35
37#+begin_src sh 36```sh
38sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config 37sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config
39#+end_src 38```
40 39
414. [@4] Restrict supported key exchange, cipher, and MAC algorithms 404. Restrict supported key exchange, cipher, and MAC algorithms
42 41
43#+begin_src sh 42```sh
44echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf 43echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf
45#+end_src 44```
46 45
475. [@5] Include the /etc/ssh/sshd_config.d directory 465. Include the /etc/ssh/sshd~config~.d directory
48 47
49#+begin_src sh 48```sh
50echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config 49echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config
51#+end_src 50```
52 51
536. [@6] Restart OpenSSH server 526. Restart OpenSSH server
54 53
55#+begin_src sh 54```sh
56rc-service sshd restart 55rc-service sshd restart
57#+end_src 56```
58 57
59** Testing SSH 58## Testing SSH
60:PROPERTIES:
61:CUSTOM_ID: testing-ssh
62:END:
63You can test the results with the =ssh-audit= python script.
64 59
65#+begin_src sh 60You can test the results with the `ssh-audit` python script.
61
62```sh
66pip3 install ssh-audit 63pip3 install ssh-audit
67ssh-audit localhost 64ssh-audit localhost
68#+end_src 65```
69 66
70If everything succeeded, the results will show as all green. If anything 67If everything succeeded, the results will show as all green. If anything
71is yellow, orange, or red, you may need to tweak additional settings. 68is yellow, orange, or red, you may need to tweak additional settings.
72 69
70```txt
73#+caption: ssh audit 71#+caption: ssh audit
74[[https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png%20%22ssh-audit%22]] 72```
73
74![ssh-audit](https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png "ssh-audit")
blog/2023-10-17-self-hosting-anonymousoverflow.org → content/blog/2023-10-17-self-hosting-anonymousoverflow.md renamed +38 −41
@@ -1,37 +1,35 @@
1#+title: Self-Hosting AnonymousOverflow 1+++
2#+date: 2023-10-17 2date = 2023-10-17
3title = "Self-Hosting AnonymousOverflow"
4description = "A guide to self-hosting the AnonymousOverflow application on your own server."
5+++
3 6
4** Overview 7## Overview
5:PROPERTIES: 8
6:CUSTOM_ID: overview 9I recently launched an instance of AnonymousOverflow at
7:END: 10[ao.cleberg.net](https://ao.cleberg.net) and wanted to write a brief
8I recently launched an instance of AnonymousOverflow at [[https://ao.cleberg.net][ao.cleberg.net]] and 11post on how easy it is to install with Docker Compose and Nginx.
9wanted to write a brief post on how easy it is to install with Docker Compose
10and Nginx.
11 12
12This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse 13This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse
13proxy. 14proxy.
14 15
15** Installation 16## Installation
16:PROPERTIES: 17
17:CUSTOM_ID: installation 18### Docker Compose
18:END: 19
19*** Docker Compose
20:PROPERTIES:
21:CUSTOM_ID: docker-compose
22:END:
23To install AnonymousOverflow, start by creating a directory for the 20To install AnonymousOverflow, start by creating a directory for the
24application and create its =docker-compose.yml= file. 21application and create its `docker-compose.yml` file.
25 22
26#+begin_src sh 23```sh
27mkdir ~/anonymousoverflow && cd ~/anonymousoverflow 24mkdir ~/anonymousoverflow && cd ~/anonymousoverflow
28nano docker-compose.yml 25nano docker-compose.yml
29#+end_src 26```
30 27
31Within this file, paste the following information. Be sure to change the 28Within this file, paste the following information. Be sure to change the
32=APP_URL=, =JWT_SIGNING_SECRET=, and =ports= to match your needs. 29`APP_URL`, `JWT_SIGNING_SECRET`, and
30`ports` to match your needs.
33 31
34#+begin_src yaml 32```yaml
35version: '3' 33version: '3'
36 34
37services: 35services:
@@ -44,33 +42,32 @@ services:
44 ports: 42 ports:
45 - '9380:8080' 43 - '9380:8080'
46 restart: 'always' 44 restart: 'always'
47#+end_src 45```
48 46
49Save and exit the file when complete. You can now launch the container 47Save and exit the file when complete. You can now launch the container
50and access it via your local network. 48and access it via your local network.
51 49
52#+begin_src sh 50```sh
53sudo docker-compose up -d 51sudo docker-compose up -d
54#+end_src 52```
53
54### Nginx Reverse Proxy
55 55
56*** Nginx Reverse Proxy
57:PROPERTIES:
58:CUSTOM_ID: nginx-reverse-proxy
59:END:
60If you want to access this service outside the local network, I 56If you want to access this service outside the local network, I
61recommend using Nginx as a reverse proxy. 57recommend using Nginx as a reverse proxy.
62 58
63Let's start by creating a configuration file. 59Let's start by creating a configuration file.
64 60
65#+begin_src sh 61```sh
66sudo nano /etc/nginx/sites-available/ao 62sudo nano /etc/nginx/sites-available/ao
67#+end_src 63```
68 64
69Within this file, paste the following content and repace 65Within this file, paste the following content and repace
70=ao.example.com= with your URL. You may need to update the SSL 66`ao.example.com` with your URL. You may need to update the
71certificate statements if your certificates are in a different location. 67SSL certificate statements if your certificates are in a different
68location.
72 69
73#+begin_src conf 70```conf
74server { 71server {
75 if ($host ~ ^[^.]+\.cleberg\.net$) { 72 if ($host ~ ^[^.]+\.cleberg\.net$) {
76 return 301 https://$host$request_uri; 73 return 301 https://$host$request_uri;
@@ -122,15 +119,15 @@ server {
122 proxy_buffers 64 256k; 119 proxy_buffers 64 256k;
123 } 120 }
124} 121}
125#+end_src 122```
126 123
127Save and exit the file when complete. On Ubuntu, you will need to symlink the 124Save and exit the file when complete. On Ubuntu, you will need to
128configuration file before it will be recognized by Nginx. Once complete, simply 125symlink the configuration file before it will be recognized by Nginx.
129restart the web server. 126Once complete, simply restart the web server.
130 127
131#+begin_src sh 128```sh
132sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao 129sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao
133sudo systemctl restart nginx.service 130sudo systemctl restart nginx.service
134#+end_src 131```
135 132
136The website will now be available publicly. Visit [[https://ao.cleberg.net][my instance]] for an example. 133The website will now be available publicly. Visit [my instance](https://ao.cleberg.net) for an example.
content/blog/2023-11-08-scli.md added +157
@@ -0,0 +1,157 @@
1+++
2date = 2023-11-08
3title = "Installing scli on Alpine Linux (musl)"
4description = "Learn how to properly configure scli for musl-based systems."
5+++
6
7[scli](https://github.com/isamert/scli) is a command-line tool that
8allows you to connect to your Signal messenger account. This program
9utilizes a two-pane display that shows you chats on the left and the
10focused conversation on the right.
11
12This guide will show you how to install `scli` and its
13dependencies on Alpine Linux, which requires some extra work due to
14musl.
15
16If you're using a non-musl system, you can likely following the
17`scli` README and download the packaged binaries for an
18easier installation process.
19
20## Dependencies
21
22In order to use `scli`, you need a few dependencies:
23
24- `openjdk17-jre` - Used as a dependency for the
25 `signal-cli` tool. Version may vary.
26- `signal-cli` - Used as the backbone of the
27 `scli` tool.
28- `findutils` - Replaces the standard Busybox version of
29 `xargs`.
30- `urwid` - A console user interface library for Python.
31- `urwid-readline` - For GNU emacs-like keybinds on the
32 input line.
33- `qrencode` - Displays a QR code in the terminal to link
34 the device using your phone. Not necessary if you're only linking
35 on desktop and can copy/paste the connection URL.
36
37Let's start by installing the packages available via Alpine's
38repositories. Be sure to install the latest version of
39`openjdk`. If you run into Java-related issues, uninstall
40`openjdk` and install an older version.
41
42```sh
43doas apk add openjdk17-jre findutils qrencode
44```
45
46Next, let's install `signal-cli`. Be sure to export the
47version of `signal-cli` that you want. I use version
48`0.12.4` below, but that may be outdated by the time you're
49reading this.
50
51```sh
52export VERSION="0.12.4"
53wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
54doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
55doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
56```
57
58Finally, install the `urwid` packages using the Python
59packaging utility.
60
61```sh
62pip3 install urwid urwid-readline
63```
64
65## Installation
66
67Now that we have all of the dependencies we need, we can install
68`scli`. Start by simply cloning the repository.
69
70```sh
71git clone https://github.com/isamert/scli
72```
73
74When I cloned this repository on 2023-11-08, I found a bug in the logic
75that required a fix. You must edit the `scli` file and
76replace the one instance of `RLIMIT_OFILE` with
77`RLIMIT_NOFILE`.
78
79```sh
80cd scli
81nano scli
82```
83
84Once complete, you can move this program to anywhere on your
85`$PATH`. I chose the following directory.
86
87```sh
88doas mv scli /usr/local/bin/scli
89```
90
91## Initial Setup
92
93Now that everything is installed, we can login and configure the client.
94Start by generating a connection link.
95
96```sh
97signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
98```
99
100This will generate a connection link and related QR code for you to use
101to link the devices together. Once complete, **wait patiently** for the
102connection process to finish.
103
104Once it completes, it will exit and return you to the prompt. From here,
105you need to perform an initial `receive` command to start
106things off. The `USERNAME` variable should be your phone
107number, such as `+15551237890`.
108
109```sh
110signal-cli -u USERNAME receive
111```
112
113Also be sure to test the daemon to ensure it works properly. If no
114errors occur, it's working. If you run into errors because you're not
115running a DBUS session, see my notes below.
116
117```sh
118signal-cli -u USERNAME daemon
119```
120
121Once the initial reception is complete, you are ready to use
122`scli`.
123
124This process will differ depending on your desktop environment (DE). If
125you are running a DE, you likely have a DBUS session running already and
126can simply launch the program.
127
128However, if you're like me and running your computer straight on the
129TTY without a DE, you'll need to start a DBUS session for this program.
130
131```sh
132# If you're not running a DBUS session yet, you need to start one for scli
133dbus-run-session -- scli
134
135# OR - If you're already running a DBUS session, simply run scli
136scli
137```
138
139## Configuration
140
141Lastly, there are a number of configuration options that you can pass
142via the command or in the `~/.config/sclirc` file. See the
143Github README for more information on configuration options.
144
145```sh
146nano ~/.config/sclirc
147```
148
149```conf
150# ~/.config/sclirc
151
152wrap-at = 80
153enable-notifications = true
154```
155
156That's it! Following this guide, I have a functional `scli`
157program that successfully sends messages to my contacts and myself!
blog/2023-12-03-unifi-nextdns.org → content/blog/2023-12-03-unifi-nextdns.md renamed +66 −90
@@ -1,128 +1,104 @@
1#+title: How to Install NextDNS on the Unifi Dream Machine 1+++
2#+date: 2023-12-03 2date = 2023-12-03
3title = "How to Install NextDNS on the Unifi Dream Machine"
4description = "A guide to properly install the NextDNS client on the UDM Pro."
5+++
3 6
4* Overview 7# Overview
5:PROPERTIES:
6:ID: 711309AE-955D-4B2D-B716-CFD700079157
7:PUBDATE: 2023-12-03 Sun 22:32
8:END:
9 8
10I recently installed NextDNS on my Unifi Dream Machine router using the 9I recently installed NextDNS on my Unifi Dream Machine router using the
11[[https://github.com/nextdns/nextdns/wiki/UnifiOS][UnifiOS]] wiki page on 10[UnifiOS](https://github.com/nextdns/nextdns/wiki/UnifiOS) wiki page on
12NextDNS's GitHub repository. 11NextDNS's GitHub repository.
13 12
14As a result of this, I wanted to write down the process in case the wiki or 13As a result of this, I wanted to write down the process in case the wiki
15installer ever gets lost. 14or installer ever gets lost.
16 15
17* Wiki 16# Wiki
18:PROPERTIES:
19:ID: 8457B7A9-AE62-448D-B092-C04759F8D468
20:PUBDATE: 2023-12-03 Sun 22:32
21:END:
22 17
23The following is copied from the wiki page linked above, with one difference in 18The following is copied from the wiki page linked above, with one
24the =ssh= command. 19difference in the `ssh` command.
25 20
26Install instructions for Unifi Dream Machine (UDM) standard and pro routers. 21Install instructions for Unifi Dream Machine (UDM) standard and pro
22routers.
27 23
28** Install 24## Install
29:PROPERTIES:
30:ID: 907F595C-6D53-409D-AB97-2B830D09B62E
31:END:
32 25
33Enable SSH: 26Enable SSH:
34 27
35- Go to your unifi admin interface and select your device (not the controller 28- Go to your unifi admin interface and select your device (not the
36 settings, but the Dream Machine settings) 29 controller settings, but the Dream Machine settings)
37- Click on "Settings" at the bottom of the page 30- Click on "Settings" at the bottom of the page
38- Go to the "Advanced" section on the left pan 31- Go to the "Advanced" section on the left pan
39- Enable SSH 32- Enable SSH
40- Set a SSH password 33- Set a SSH password
41 34
42Connect to your router using =ssh root@xxx.xxx.xxx.xxx= with the password you 35Connect to your router using `ssh root@xxx.xxx.xxx.xxx` with
43configured. 36the password you configured.
44 37
45Run the following command and follow the instructions: 38Run the following command and follow the instructions:
46 39
47#+begin_src sh 40```sh
48sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"' 41sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
49#+end_src 42```
50 43
51Note: Queries from the UDM itself won’t be routed to NextDNS nor encrypted due 44Note: Queries from the UDM itself won't be routed to NextDNS nor
52to current system limitation. All traffic from other devices on then network 45encrypted due to current system limitation. All traffic from other
53will. 46devices on then network will.
54 47
55** Upgrade 48## Upgrade
56:PROPERTIES:
57:ID: 3836C070-2C7C-456D-AB3B-15DDE60D2F99
58:END:
59 49
60To upgrade to the last version, simply re-run the installer above. If a new 50To upgrade to the last version, simply re-run the installer above. If a
61version is available, the upgrade action will added to the list of possible 51new version is available, the upgrade action will added to the list of
62actions. 52possible actions.
63 53
64** Uninstall 54## Uninstall
65:PROPERTIES:
66:ID: DFD1BA2F-EC4E-4B5C-9CEA-098DFF0AD62F
67:END:
68 55
69To uninstall, re-run the installer above and select "Remove" in the menu. 56To uninstall, re-run the installer above and select "Remove" in the
57menu.
70 58
71** Troubleshooting 59## Troubleshooting
72:PROPERTIES:
73:ID: 0B0F4E70-FACC-4C7C-8EB9-55398AA8476F
74:END:
75 60
76If the installation fail, please the installer in debug mode and contact us at 61If the installation fail, please the installer in debug mode and contact
77team@nextdns.io with the transcript of the installation: 62us at team@nextdns.io with the transcript of the installation:
78 63
79#+begin_src sh 64```sh
80sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"' 65sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
81#+end_src 66```
82 67
83*** Content Filtering Conflict 68### Content Filtering Conflict
84:PROPERTIES:
85:ID: BCDD3267-4C04-45AC-BAAD-9D15F414ED4D
86:END:
87 69
88NextDNS CLI and the UDM Content Filtering or the Ad Blocking features are 70NextDNS CLI and the UDM Content Filtering or the Ad Blocking features
89incompatible. If you want to use NextDNS CLI, please make sure they are 71are incompatible. If you want to use NextDNS CLI, please make sure they
90disabled. 72are disabled.
91 73
92To disable Content Filtering, go to Settings > Network, then for each network, 74To disable Content Filtering, go to Settings > Network, then for each
93set the Content Filtering feature to None 75network, set the Content Filtering feature to None
94 76
95To disable Ad Blocking, go to Settings > Application Firewall. In the General 77To disable Ad Blocking, go to Settings > Application Firewall. In the
96tab, uncheck the Ad Blocking checkbox. 78General tab, uncheck the Ad Blocking checkbox.
97 79
98*** APT Error 80### APT Error
99:PROPERTIES:
100:ID: 83244A13-7154-475A-8193-064A1B81D3AE
101:END:
102 81
103If you get an apt error as follow: 82If you get an apt error as follow:
104 83
105#+begin_src sh 84```sh
106E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80] 85E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80]
107#+end_src 86```
108 87
109You may try to following: 88You may try to following:
110 89
111#+begin_src sh 90```sh
112sed -i -e 's/deb.debian.org/archive.debian.org/g' \ 91sed -i -e 's/deb.debian.org/archive.debian.org/g' \
113 -e 's|security.debian.org|archive.debian.org/|g' \ 92 -e 's|security.debian.org|archive.debian.org/|g' \
114 -e '/stretch-updates/d' /etc/apt/sources.list 93 -e '/stretch-updates/d' /etc/apt/sources.list
115#+end_src 94```
116 95
117* install.sh 96# install.sh
118:PROPERTIES:
119:ID: 91DE0DD0-8177-4594-B067-373ABF99C343
120:PUBDATE: 2023-12-03 Sun 22:34
121:END:
122 97
123Here are the contents of the =install.sh= file used above, as of 2023-12-03: 98Here are the contents of the `install.sh` file used above, as
99of 2023-12-03:
124 100
125#+begin_src sh 101```sh
126#!/bin/sh 102#!/bin/sh
127 103
128main() { 104main() {
@@ -226,13 +202,13 @@ uninstall() {
226precheck() { 202precheck() {
227 if [ -e "/data/unifi" ] && [ -f "/run/dnsfilter/dnsfilter" ]; then 203 if [ -e "/data/unifi" ] && [ -f "/run/dnsfilter/dnsfilter" ]; then
228 log_warn "UDM Content Filtering and/or Ad Blocking feature is enabled." 204 log_warn "UDM Content Filtering and/or Ad Blocking feature is enabled."
229 log_warn "Please disable it to use NextDNS." 205 log_warn "Please disable it to use NextDNS."
230 log_warn "" 206 log_warn ""
231 log_warn " To disable Content Filtering, go to Settings > Network." 207 log_warn " To disable Content Filtering, go to Settings > Network."
232 log_warn " For each network, set the Content Filtering feature to None." 208 log_warn " For each network, set the Content Filtering feature to None."
233 log_warn "" 209 log_warn ""
234 log_warn " To disable Ad Blocking, go to Settings > Application Firewall" 210 log_warn " To disable Ad Blocking, go to Settings > Application Firewall"
235 log_warn " In the General tab, uncheck the Ad Blocking checkbox." 211 log_warn " In the General tab, uncheck the Ad Blocking checkbox."
236 log_warn "" 212 log_warn ""
237 while [ -f "/run/dnsfilter/dnsfilter" ]; do 213 while [ -f "/run/dnsfilter/dnsfilter" ]; do
238 sleep 1 214 sleep 1
@@ -359,7 +335,7 @@ install_bin() {
359 esac 335 esac
360 log_debug "Downloading $url" 336 log_debug "Downloading $url"
361 asroot mkdir -p "$(dirname "$bin_path")" && 337 asroot mkdir -p "$(dirname "$bin_path")" &&
362 curl -sL "$url" | asroot sh -c "tar Ozxf - nextdns > \"$bin_path\"" && 338 curl -sL "$url" | asroot sh -c "tar Ozxf - nextdns > "$bin_path"" &&
363 asroot chmod 755 "$bin_path" 339 asroot chmod 755 "$bin_path"
364} 340}
365 341
@@ -1163,9 +1139,9 @@ bin_location() {
1163 ;; 1139 ;;
1164 synology) 1140 synology)
1165 echo "/usr/local/bin/nextdns" 1141 echo "/usr/local/bin/nextdns"
1166 ;; 1142 ;;
1167 darwin) 1143 darwin)
1168 echo "$(brew --prefix 2>/dev/null || echo /usr/local)/bin/nextdns" 1144 echo "$(brew --prefix 2>/dev/null || echo /usr/local)/bin/nextdns"
1169 ;; 1145 ;;
1170 asuswrt-merlin|ddwrt) 1146 asuswrt-merlin|ddwrt)
1171 echo "/jffs/nextdns/nextdns" 1147 echo "/jffs/nextdns/nextdns"
@@ -1272,4 +1248,4 @@ openssl_get() {
1272 1248
1273umask 0022 1249umask 0022
1274main 1250main
1275#+end_src 1251```
content/blog/_index.md added +4
@@ -0,0 +1,4 @@
1+++
2title = "Blog"
3sort_by = "date"
4+++\ No newline at end of file
content/salary/index.md added +54
@@ -0,0 +1,54 @@
1+++
2title = "Salary"
3description = "A summary of my salaries throughout my career."
4+++
5
6# Salary Transparency
7
8The data below details the base salary information for each job I've
9held. This information is posted publicly to ensure others in my
10position have a solid reference point when determining if their current
11or proposed salary is appropriate.
12
13While sites like Glassdoor are locking salary data behind a paywall,
14LinkedIn is discontinuing LinkedIn Salary, and helpful websites like Big
154 Transparency are extremely rare, I wanted to provide my personal data
16publicly and freely to those who need it.
17
18I have seen what can happen when great employees don't know the market
19values for their skills and I happily help those in my teams, so I'm
20happy to extend this information to the online community.
21
22As a final note, there are numerous reasons that people in the same role
23are paid differently (expertise, years of experience, certifications,
24education, etc.) and that the data in this table should only be used as
25a single point of reference, not the whole story.
26
27# Salary Data
28
29Note: When in a role that gives periodic raises, I will create a new
30record with the new base salary in the table below. See the KPMG records
31for an example of a raise while in the same role.
32
33| Title | Company | Location | Start Date | End Date | Base Salary |
34|---------------------------------------|------------------------|----------------|------------|----------|-------------|
35| Senior Technology Assurance Associate | KPMG | Omaha, NE | 2023-10 | Current | \$116,700 |
36| Senior Technology Assurance Associate | KPMG | Omaha, NE | 2022-06 | 2023-10 | \$110,000 |
37| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | \$89,500 |
38| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | \$72,000 |
39| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | \$65,000 |
40| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | \$58,000 |
41| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | \$20/hour |
42| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | \$16/hour |
43| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | \$7/hour |
44| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | \$14/hour |
45
46This page was inspired by [Xe](https://xeiaso.net/), and I'm quoting
47the following wording from them as I want to reiterate this piece:
48
49> Please consider publishing your salary data like this as well. By
50> open, voluntary transparency we can help to end stigmas around
51> discussing pay and help ensure that the next generations of people in
52> tech are treated fairly. Stigmas thrive in darkness but die in the
53> light of day. You can help end the stigma by playing your cards out in
54> the open like this.
content/services/index.md added +17
@@ -0,0 +1,17 @@
1+++
2title = "Services"
3description = "Publicly-available services hosted on cleberg.net."
4+++
5
6- [AnonymousOverflow](https://ao.cleberg.net) - A StackOverflow proxy
7- [CyberChef](https://cyberchef.cleberg.net) - The Cyber Swiss Army Knife
8- [FileArchive](https://files.cleberg.net) - An interesting file archive
9- [FlashPaper](https://paste.cleberg.net) - One-time encrypted password/secret sharing
10- [GotHub](https://gh.cleberg.net) - An alternative front-end for GitHub
11- [ifconfig.php](https://ip.cleberg.net) - A \"whatsmyip\" tool
12- [Invidious](https://invidious.cleberg.net) - A YouTube proxy
13- [Nitter](https://nitter.cleberg.net) - A Twitter proxy
14- [Office](https://office.cleberg.net) - The world\'s smallest office suite
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=services/index.org;h=71ed6bcae7e52a8103f980f1821ca291ef3010ba;hb=HEAD) for this page if you want to see changes that have been made.
wiki/index.org → content/wiki/index.md renamed +48 −50
@@ -1,79 +1,80 @@
1#+title: Wiki 1+++
2title = "Wiki"
3description = "An informal wiki of sorts."
4+++
2 5
3An informal wiki of sorts. 6An informal wiki of sorts.
4 7
5* Digital Garden 8# Digital Garden
6 9
7#+BEGIN_QUOTE 10> At times, wilderness is exactly what readers want: a rich collection
8At times, wilderness is exactly what readers want: a rich collection of 11> of resources and links. At times, rigid formality suits readers
9resources and links. At times, rigid formality suits readers perfectly, 12> perfectly, providing precisely the information they want, no more and
10providing precisely the information they want, no more and no less. Indeed, 13> no less. Indeed, individual hypertexts and Web sites may contain
11individual hypertexts and Web sites may contain sections that tend toward 14> sections that tend toward each extreme.
12each extreme.
13#+END_QUOTE
14 15
15#+BEGIN_QUOTE 16> Often, however, designers should strive for the comfort, interest, and
16Often, however, designers should strive for the comfort, interest, and 17> habitability of parks and gardens: places that invite visitors to
17habitability of parks and gardens: places that invite visitors to remain, and 18> remain, and that are designed to engage and delight them, to invite
18that are designed to engage and delight them, to invite them to linger, to 19> them to linger, to explore, and to reflect.
19explore, and to reflect.
20#+END_QUOTE
21 20
22[[https://www.eastgate.com/garden/][Hypertext Garden]] 21[Hypertext Garden](https://www.eastgate.com/garden/)
23 22
24* Git 23# Git
25 24
26I want to get rid of all local modifications and go back to the working tree 25I want to get rid of all local modifications and go back to the working
27of the most recent commit: 26tree of the most recent commit:
28 27
29#+BEGIN_SRC sh 28```sh
30git restore . 29git restore .
31#+END_SRC 30```
32 31
33Revert a specified commit: 32Revert a specified commit:
34 33
35#+BEGIN_SRC sh 34```sh
36git revert commit-id 35git revert commit-id
37#+END_SRC 36```
38 37
39Reset the repository to a specific commit in the git log: 38Reset the repository to a specific commit in the git log:
40 39
41#+BEGIN_SRC sh 40```sh
42git reset --mixed commit-id 41git reset --mixed commit-id
43#+END_SRC 42```
44 43
45I need to commit and push changes to a remote that has been changed since my 44I need to commit and push changes to a remote that has been changed
46most recent pull: 45since my most recent pull:
47 46
48#+BEGIN_SRC sh 47```sh
49git pull --rebase 48git pull --rebase
50#+END_SRC 49```
51 50
52* Hardware 51# Hardware
53 52
54** Laptops 53## Laptops
54
55### macOS
55 56
56*** macOS
57 57
58| Category | Details | 58| Category | Details |
59| -------- | --------------- |
59| Model | Macbook Pro 16" | 60| Model | Macbook Pro 16" |
60| CPU | Apple M2 Pro | 61| CPU | Apple M2 Pro |
61| RAM | 16GB | 62| RAM | 16GB |
62| Storage | 512GB SSD | 63| Storage | 512GB SSD |
63 64
64*** Linux 65### Linux
65 66
66| Category | Details | 67| Category | Details |
67|----------|---------------------------------------------| 68| -------- | ------------------------------------------- |
68| Model | Lenovo ThinkPad E15 Gen 4, model 21ED0048US | 69| Model | Lenovo ThinkPad E15 Gen 4, model 21ED0048US |
69| CPU | AMD Ryzen 5 5625U with Radeon Graphics | 70| CPU | AMD Ryzen 5 5625U with Radeon Graphics |
70| RAM | 16 GB | 71| RAM | 16 GB |
71| Storage | 256 GB SSD | 72| Storage | 256 GB SSD |
72 73
73** Servers 74## Servers
74 75
75| Category | Details | 76| Category | Details |
76|--------------------|----------------------------------------| 77| ------------------ | -------------------------------------- |
77| Case | Rosewill RSV-R4100U 4U | 78| Case | Rosewill RSV-R4100U 4U |
78| Motherboard | NZXT B550 | 79| Motherboard | NZXT B550 |
79| CPU | AMD Ryzen 7 5700G with Radeon Graphics | 80| CPU | AMD Ryzen 7 5700G with Radeon Graphics |
@@ -82,7 +83,7 @@ git pull --rebase
82| Storage (HDD Bay) | 48TB HDD | 83| Storage (HDD Bay) | 48TB HDD |
83| PSU | Corsair RM850 PSU | 84| PSU | Corsair RM850 PSU |
84 85
85** Networking Equipment 86## Networking Equipment
86 87
87- UDM-Pro 88- UDM-Pro
88- USW-24-PoE 89- USW-24-PoE
@@ -94,18 +95,18 @@ git pull --rebase
94- UVC G4 Doorbell Pro 95- UVC G4 Doorbell Pro
95- UP Chime 96- UP Chime
96- USW 24-Port Patch Panel 97- USW 24-Port Patch Panel
97- USW Switch Lite 8 PoE 98- USW Switch Lite 8 PoE
98 99
99* Software 100# Software
100 101
101** Laptop 102## Laptop
102 103
103Alpine 3.18.2; no DE. 104Alpine 3.18.2; no DE.
104 105
105I currently run my Alpine laptop via the default login shell - no desktop 106I currently run my Alpine laptop via the default login shell - no
106environment. From here, I use a mix of byobu and emacs to split my screen into 107desktop environment. From here, I use a mix of byobu and emacs to split
107tabs and panes. All programs run through the shell and do not use visual 108my screen into tabs and panes. All programs run through the shell and do
108libraries such as X or Wayland. 109not use visual libraries such as X or Wayland.
109 110
110I have Sway installed and configured, but only launch it when I must. 111I have Sway installed and configured, but only launch it when I must.
111 112
@@ -128,17 +129,14 @@ I have Sway installed and configured, but only launch it when I must.
128- zola 129- zola
129- zsh 130- zsh
130 131
131** Server 132## Server
132 133
133Ubuntu 22.04.1; no DE. 134Ubuntu 22.04.1; no DE.
134 135
135See my services page for a list of the publicly-available services running on 136See my services page for a list of the publicly-available services
136this server. 137running on this server.
137 138
138- certbot 139- certbot
139- [docker, docker-compose] 140- [docker, docker-compose]
140- nginx 141- nginx
141- zsh 142- zsh
142
143
144
elisp/ox-rss.el deleted −434
@@ -1,434 +0,0 @@
1;;; ox-rss.el --- RSS 2.0 Back-End for Org Export Engine
2
3;; Copyright (C) 2013-2021 Free Software Foundation, Inc.
4
5;; Author: Bastien Guerry <bzg@gnu.org>
6;; Maintainer: Benedict Wang <foss@bhw.name>
7;; Version: 0.0.1
8;; Package-Requires: ((emacs "26.1") (org "9.3"))
9;; Keywords: org, wp, blog, feed, rss
10;; Homepage: https://github.com/benedicthw/ox-rss.git
11
12;; This file is not part of GNU Emacs.
13
14;; This program is free software: you can redistribute it and/or modify
15;; it under the terms of the GNU General Public License as published by
16;; the Free Software Foundation, either version 3 of the License, or
17;; (at your option) any later version.
18
19;; This program is distributed in the hope that it will be useful,
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
23
24;; You should have received a copy of the GNU General Public License
25;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
26
27;;; Commentary:
28
29;; This library implements an RSS 2.0 back-end for Org exporter, based
30;; on the `html' back-end.
31;;
32;; It requires Emacs 24.1 at least.
33;;
34;; It provides two commands for export, depending on the desired output:
35;; `org-rss-export-as-rss' (temporary buffer) and `org-rss-export-to-rss'
36;; (as a ".xml" file).
37;;
38;; This backend understands three new option keywords:
39;;
40;; #+RSS_EXTENSION: xml
41;; #+RSS_IMAGE_URL: https://myblog.org/mypicture.jpg
42;; #+RSS_FEED_URL: https://myblog.org/feeds/blog.xml
43;;
44;; It uses #+HTML_LINK_HOME: to set the base url of the feed.
45;;
46;; Exporting an Org file to RSS modifies each top-level entry by adding a
47;; PUBDATE property. If `org-rss-use-entry-url-as-guid', it will also add
48;; an ID property, later used as the guid for the feed's item.
49;;
50;; The top-level headline is used as the title of each RSS item unless
51;; an RSS_TITLE property is set on the headline.
52;;
53;; You typically want to use it within a publishing project like this:
54;;
55;; (add-to-list
56;; 'org-publish-project-alist
57;; '("homepage_rss"
58;; :base-directory "~/myhomepage/"
59;; :base-extension "org"
60;; :rss-image-url "http://lumiere.ens.fr/~guerry/images/faces/15.png"
61;; :html-link-home "http://lumiere.ens.fr/~guerry/"
62;; :html-link-use-abs-url t
63;; :rss-extension "xml"
64;; :publishing-directory "/home/guerry/public_html/"
65;; :publishing-function (org-rss-publish-to-rss)
66;; :section-numbers nil
67;; :exclude ".*" ;; To exclude all files...
68;; :include ("index.org") ;; ... except index.org.
69;; :table-of-contents nil))
70;;
71;; ... then rsync /home/guerry/public_html/ with your server.
72;;
73;; By default, the permalink for a blog entry points to the headline.
74;; You can specify a different one by using the :RSS_PERMALINK:
75;; property within an entry.
76
77;;; Code:
78
79(require 'ox-html)
80(declare-function url-encode-url "url-util" (url))
81
82;;; Variables and options
83
84(defgroup org-export-rss nil
85 "Options specific to RSS export back-end."
86 :tag "Org RSS"
87 :group 'org-export
88 :version "24.4"
89 :package-version '(Org . "8.0"))
90
91(defcustom org-rss-image-url "https://orgmode.org/img/org-mode-unicorn-logo.png"
92 "The URL of the image for the RSS feed."
93 :group 'org-export-rss
94 :type 'string)
95
96(defcustom org-rss-extension "xml"
97 "File extension for the RSS 2.0 feed."
98 :group 'org-export-rss
99 :type 'string)
100
101(defcustom org-rss-categories 'from-tags
102 "Where to extract items category information from.
103The default is to extract categories from the tags of the
104headlines. When set to another value, extract the category
105from the :CATEGORY: property of the entry."
106 :group 'org-export-rss
107 :type '(choice
108 (const :tag "From tags" from-tags)
109 (const :tag "From the category property" from-category)))
110
111(defcustom org-rss-use-entry-url-as-guid t
112 "Use the URL for the <guid> metatag?
113When nil, Org will create ids using `org-icalendar-create-uid'."
114 :group 'org-export-rss
115 :type 'boolean)
116
117;;; Define backend
118
119(org-export-define-derived-backend 'rss 'html
120 :menu-entry
121 '(?r "Export to RSS"
122 ((?R "As RSS buffer"
123 (lambda (a s v b) (org-rss-export-as-rss a s v)))
124 (?r "As RSS file" (lambda (a s v b) (org-rss-export-to-rss a s v)))
125 (?o "As RSS file and open"
126 (lambda (a s v b)
127 (if a (org-rss-export-to-rss t s v)
128 (org-open-file (org-rss-export-to-rss nil s v)))))))
129 :options-alist
130 '((:description "DESCRIPTION" nil nil newline)
131 (:keywords "KEYWORDS" nil nil space)
132 (:with-toc nil nil nil) ;; Never include HTML's toc
133 (:rss-extension "RSS_EXTENSION" nil org-rss-extension)
134 (:rss-image-url "RSS_IMAGE_URL" nil org-rss-image-url)
135 (:rss-feed-url "RSS_FEED_URL" nil nil t)
136 (:rss-categories nil nil org-rss-categories))
137 :filters-alist '((:filter-final-output . org-rss-final-function))
138 :translate-alist '((headline . org-rss-headline)
139 (comment . (lambda (&rest args) ""))
140 (comment-block . (lambda (&rest args) ""))
141 (timestamp . (lambda (&rest args) ""))
142 (plain-text . org-rss-plain-text)
143 (section . org-rss-section)
144 (template . org-rss-template)))
145
146;;; Export functions
147
148;;;###autoload
149(defun org-rss-export-as-rss (&optional async subtreep visible-only)
150 "Export current buffer to an RSS buffer.
151
152If narrowing is active in the current buffer, only export its
153narrowed part.
154
155If a region is active, export that region.
156
157A non-nil optional argument ASYNC means the process should happen
158asynchronously. The resulting buffer should be accessible
159through the `org-export-stack' interface.
160
161When optional argument SUBTREEP is non-nil, export the sub-tree
162at point, extracting information from the headline properties
163first.
164
165When optional argument VISIBLE-ONLY is non-nil, don't export
166contents of hidden elements.
167
168Export is done in a buffer named \"*Org RSS Export*\", which will
169be displayed when `org-export-show-temporary-export-buffer' is
170non-nil."
171 (interactive)
172 (let ((file (buffer-file-name (buffer-base-buffer))))
173 (org-icalendar-create-uid file 'warn-user)
174 (org-rss-add-pubdate-property))
175 (org-export-to-buffer 'rss "*Org RSS Export*"
176 async subtreep visible-only nil nil (lambda () (text-mode))))
177
178;;;###autoload
179(defun org-rss-export-to-rss (&optional async subtreep visible-only)
180 "Export current buffer to an RSS file.
181
182If narrowing is active in the current buffer, only export its
183narrowed part.
184
185If a region is active, export that region.
186
187A non-nil optional argument ASYNC means the process should happen
188asynchronously. The resulting file should be accessible through
189the `org-export-stack' interface.
190
191When optional argument SUBTREEP is non-nil, export the sub-tree
192at point, extracting information from the headline properties
193first.
194
195When optional argument VISIBLE-ONLY is non-nil, don't export
196contents of hidden elements.
197
198Return output file's name."
199 (interactive)
200 (let ((file (buffer-file-name (buffer-base-buffer))))
201 (org-icalendar-create-uid file 'warn-user)
202 (org-rss-add-pubdate-property))
203 (let ((outfile (org-export-output-file-name
204 (concat "." org-rss-extension) subtreep)))
205 (org-export-to-file 'rss outfile async subtreep visible-only)))
206
207;;;###autoload
208(defun org-rss-publish-to-rss (plist filename pub-dir)
209 "Publish an org file to RSS.
210
211FILENAME is the filename of the Org file to be published. PLIST
212is the property list for the given project. PUB-DIR is the
213publishing directory.
214
215Return output file name."
216 (let ((bf (get-file-buffer filename)))
217 (if bf
218 (with-current-buffer bf
219 (org-icalendar-create-uid filename 'warn-user)
220 (org-rss-add-pubdate-property)
221 (write-file filename))
222 (find-file filename)
223 (org-icalendar-create-uid filename 'warn-user)
224 (org-rss-add-pubdate-property)
225 (write-file filename) (kill-buffer)))
226 (org-publish-org-to
227 'rss filename (concat "." org-rss-extension) plist pub-dir))
228
229;;; Main transcoding functions
230
231(defun org-rss-headline (headline contents info)
232 "Transcode HEADLINE element into RSS format.
233CONTENTS is the headline contents. INFO is a plist used as a
234communication channel."
235 (if (> (org-export-get-relative-level headline info) 1)
236 (org-export-data-with-backend headline 'html info)
237 (unless (org-element-property :footnote-section-p headline)
238 (let* ((email (org-export-data (plist-get info :email) info))
239 (author (and (plist-get info :with-author)
240 (let ((auth (plist-get info :author)))
241 (and auth (org-export-data auth info)))))
242 (htmlext (plist-get info :html-extension))
243 (hl-number (org-export-get-headline-number headline info))
244 (hl-home (file-name-as-directory (plist-get info :html-link-home)))
245 (hl-pdir (plist-get info :publishing-directory))
246 (hl-perm (org-element-property :RSS_PERMALINK headline))
247 (anchor
248 (or (org-element-property :CUSTOM_ID headline)
249 (org-export-get-reference headline info)))
250 (category (org-rss-plain-text
251 (or (org-element-property :CATEGORY headline) "") info))
252 (pubdate0 (org-element-property :PUBDATE headline))
253 (pubdate (let ((system-time-locale "C"))
254 (if (and pubdate0 (not (string-empty-p pubdate0)))
255 (format-time-string
256 "%a, %d %b %Y %T %z"
257 (org-time-string-to-time pubdate0)))))
258 (title (org-rss-plain-text
259 (or (org-element-property :RSS_TITLE headline)
260 (replace-regexp-in-string
261 org-link-bracket-re
262 (lambda (m) (or (match-string 3 m)
263 (match-string 1 m)))
264 (org-element-property :raw-value headline))) info))
265 (publink
266 (or (and hl-perm (concat (or hl-home hl-pdir) hl-perm))
267 (concat
268 (or hl-home hl-pdir)
269 (file-name-nondirectory
270 (file-name-sans-extension
271 (plist-get info :input-file))) "." htmlext "#" anchor)))
272 (guid (if org-rss-use-entry-url-as-guid
273 publink
274 (org-rss-plain-text
275 (or (org-element-property :ID headline)
276 (org-element-property :CUSTOM_ID headline)
277 publink)
278 info))))
279 (if (not pubdate) "" ;; Skip entries with no PUBDATE prop
280 (format
281 (concat
282 "<item>\n"
283 "<title>%s</title>\n"
284 "<link>%s</link>\n"
285 "<author>%s (%s)</author>\n"
286 "<guid isPermaLink=\"false\">%s</guid>\n"
287 "<pubDate>%s</pubDate>\n"
288 (org-rss-build-categories headline info) "\n"
289 "<description><![CDATA[%s]]></description>\n"
290 "</item>\n")
291 title publink email author guid pubdate contents))))))
292
293(defun org-rss-build-categories (headline info)
294 "Build categories for the RSS item from INFO.
295Fallback to the HEADLINE :CATEGORY property."
296 (if (eq (plist-get info :rss-categories) 'from-tags)
297 (mapconcat
298 (lambda (c) (format "<category><![CDATA[%s]]></category>" c))
299 (org-element-property :tags headline)
300 "\n")
301 (let ((c (org-element-property :CATEGORY headline)))
302 (format "<category><![CDATA[%s]]></category>" c))))
303
304(defun org-rss-template (contents info)
305 "Return complete document string after RSS conversion.
306CONTENTS is the transcoded contents string. INFO is a plist used
307as a communication channel."
308 (let ((style (plist-get info :rss-stylesheet)))
309 (concat
310 (format "<?xml version=\"1.0\" encoding=\"%s\"?>"
311 (symbol-name org-html-coding-system))
312 (if style
313 (format "\n<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>"
314 style))
315 "\n<rss version=\"2.0\"
316 xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"
317 xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"
318 xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
319 xmlns:atom=\"http://www.w3.org/2005/Atom\"
320 xmlns:sy=\"http://purl.org/rss/1.0/modules/syndication/\"
321 xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"
322 xmlns:georss=\"http://www.georss.org/georss\"
323 xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"
324 xmlns:media=\"http://search.yahoo.com/mrss/\">"
325 "<channel>"
326 (org-rss-build-channel-info info) "\n"
327 contents
328 "</channel>\n"
329 "</rss>")))
330
331(defun org-rss-build-channel-info (info)
332 "Given plist INFO build the RSS channel information."
333 (let* ((system-time-locale "C")
334 (title (org-export-data (plist-get info :title) info))
335 (email (org-export-data (plist-get info :email) info))
336 (author (and (plist-get info :with-author)
337 (let ((auth (plist-get info :author)))
338 (and auth (org-export-data auth info)))))
339 (date (format-time-string "%a, %d %b %Y %T %z")) ;; RFC 882
340 (description (org-export-data (plist-get info :description) info))
341 (lang (plist-get info :language))
342 (keywords (plist-get info :keywords))
343 (rssext (plist-get info :rss-extension))
344 (blogurl (or (plist-get info :html-link-home)
345 (plist-get info :publishing-directory)))
346 (image (url-encode-url (plist-get info :rss-image-url)))
347 (ifile (plist-get info :input-file))
348 (publink
349 (or (plist-get info :rss-feed-url)
350 (concat (file-name-as-directory blogurl)
351 (file-name-nondirectory
352 (file-name-sans-extension ifile))
353 "." rssext))))
354 (format
355 "\n<title>%s</title>
356<atom:link href=\"%s\" rel=\"self\" type=\"application/rss+xml\" />
357<link>%s</link>
358<description><![CDATA[%s]]></description>
359<language>%s</language>
360<pubDate>%s</pubDate>
361<lastBuildDate>%s</lastBuildDate>
362<generator>%s</generator>
363<webMaster>%s (%s)</webMaster>
364<image>
365<url>%s</url>
366<title>%s</title>
367<link>%s</link>
368</image>
369"
370 title publink blogurl description lang date date
371 (concat (format "Emacs %d.%d"
372 emacs-major-version
373 emacs-minor-version)
374 " Org-mode " (org-version))
375 email author image title blogurl)))
376
377(defun org-rss-section (section contents info)
378 "Transcode SECTION element into RSS format.
379CONTENTS is the section contents. INFO is a plist used as
380a communication channel."
381 contents)
382
383(defun org-rss-timestamp (timestamp contents info)
384 "Transcode a TIMESTAMP object from Org to RSS.
385CONTENTS is nil. INFO is a plist holding contextual
386information."
387 (org-html-encode-plain-text
388 (org-timestamp-translate timestamp)))
389
390(defun org-rss-plain-text (contents info)
391 "Convert plain text CONTENTS into RSS encoded text.
392INFO is a plist used as a communication channel"
393 (let (output)
394 (setq output (org-html-encode-plain-text contents)
395 output (org-export-activate-smart-quotes
396 output :html info))))
397
398;;; Filters
399
400(defun org-rss-final-function (contents backend info)
401 "Prettify the RSS output.
402CONTENTS is the headline contents as a transcoded string. BACKEND
403is a symbol representation of the backend used. INFO is a plist
404used as a communication channel."
405 (with-temp-buffer
406 (xml-mode)
407 (insert contents)
408 (indent-region (point-min) (point-max))
409 (buffer-substring-no-properties (point-min) (point-max))))
410
411;;; Miscellaneous
412
413(defun org-rss-add-pubdate-property ()
414 "Set the PUBDATE property for top-level headlines."
415 (let (msg)
416 (org-map-entries
417 (lambda ()
418 (let* ((entry (org-element-at-point))
419 (level (org-element-property :level entry)))
420 (when (= level 1)
421 (unless (org-entry-get (point) "PUBDATE")
422 (setq msg t)
423 (org-set-property
424 "PUBDATE" (format-time-string
425 (cdr org-time-stamp-formats)))))))
426 nil nil 'comment 'archive)
427 (when msg
428 (message "Property PUBDATE added to top-level entries in %s"
429 (buffer-file-name))
430 (sit-for 2))))
431
432(provide 'ox-rss)
433
434;;; ox-rss.el ends here
elisp/publish.el deleted −59
@@ -1,59 +0,0 @@
1;; Publish org files to HTML
2
3;; Functions
4(defun org-rss-publish-to-rss (plist filename pub-dir)
5 "Publish RSS with PLIST, only when FILENAME is 'rss.org'.
6PUB-DIR is when the output will be placed."
7 (if (equal "rss.org" (file-name-nondirectory filename))
8 (org-rss-export-to-rss plist filename pub-dir)))
9
10;; Project publishing settings
11(setq org-publish-project-alist
12 '(("cleberg.net"
13 :base-directory "~/Source/cleberg.net/"
14 :publishing-function org-html-publish-to-html
15 :publishing-directory "~/Source/cleberg.net/public/"
16 :auto-sitemap t
17 :recursive t
18 :section-numbers nil
19 :with-author nil
20 :html-validation-link nil
21 :with-tags t
22 :html-htmlize-output-type 'inline-css
23 :html-head-include-default-style nil
24 :html-head-include-scripts nil
25 :html-head "<link rel=stylesheet href=/static/styles.min.css>"
26 :html-preamble "<nav><a href=/>Home</a><a href=/blog/>Blog</a><a href=/services/>Services</a><a href=/wiki/>Wiki</a></nav>")
27
28 ("rss"
29 :base-directory "~/Source/cleberg.net/blog/"
30 :base-extension "org"
31 :html-link-home "https://cleberg.net/"
32 :html-link-use-abs-url t
33 :rss-extension "xml"
34 :publishing-directory "~/Source/cleberg.net/public/blog/"
35 :publishing-function org-rss-publish-to-rss
36 :section-numbers nil
37 :exclude "index.org"
38 :author "cmc"
39 :email ""
40 ;; test
41 :html-link-org-files-as-html t
42 :auto-sitemap t
43 :sitemap-filename "rss.org"
44 :sitemap-title "cleberg.net"
45 :sitemap-style list
46 :sitemap-sort-files anti-chronologically
47 ;;:sitemap-function 'rw/format-rss-feed
48 ;;:sitemap-format-entry 'rw/format-rss-feed-entry
49 ;; end test
50 )
51
52 ("static"
53 :base-directory "~/Source/cleberg.net/static/"
54 :base-extension "css\\|txt"
55 :publishing-directory "~/Source/cleberg.net/public/static/"
56 :publishing-function org-publish-attachment)
57 ("website" :components ("cleberg.net" "rss" "static"))))
58
59(provide 'publish)
index.org deleted −26
@@ -1,26 +0,0 @@
1#+title: cleberg.net
2#+OPTIONS: toc:nil
3
4#+BEGIN_QUOTE
5Do AIs dream in electric sheep?
6
7He hoped it had nanosecond nightmares.
8#+END_QUOTE
9
10** Pages
11:PROPERTIES:
12:ID: A3B9560A-66B5-4526-ACA3-BA766C75CDDE
13:END:
14
15- [[./blog/][Blog]]
16- [[./services/][Services]]
17- [[./wiki/][Wiki]]
18
19** Contact
20:PROPERTIES:
21:ID: A0D2C73E-264F-4DBC-BF87-E875EA36D6A5
22:END:
23
24- hello [at] cleberg.net ([[./static/gpg.txt][GPG]])
25- [[https://matrix.to/#/@cyborg:matrix.tchncs.de][@cyborg@matrix.tchncs.de]]
26- [[https://git.cleberg.net][Source Code]]
salary/index.org deleted −53
@@ -1,53 +0,0 @@
1#+title: Salary
2#+date: 2023-10-01
3
4* Salary Transparency
5
6The data below details the base salary information for each job I've held. This
7information is posted publicly to ensure others in my position have a solid
8reference point when determining if their current or proposed salary is
9appropriate.
10
11While sites like Glassdoor are locking salary data behind a paywall, LinkedIn is
12discontinuing LinkedIn Salary, and helpful websites like Big 4 Transparency are
13extremely rare, I wanted to provide my personal data publicly and freely to
14those who need it.
15
16I have seen what can happen when great employees don't know the market values
17for their skills and I happily help those in my teams, so I'm happy to extend
18this information to the online community.
19
20As a final note, there are numerous reasons that people in the same role are
21paid differently (expertise, years of experience, certifications, education,
22etc.) and that the data in this table should only be used as a single point of
23reference, not the whole story.
24
25* Salary Data
26
27Note: When in a role that gives periodic raises, I will create a new record with
28the new base salary in the table below. See the KPMG records for an example of a
29raise while in the same role.
30
31| Title | Company | Location | Start Date | End Date | Base Salary |
32|---------------------------------------+------------------------+----------------+------------+----------+-------------|
33| Senior Technology Assurance Associate | KPMG | Omaha, NE | 2023-10 | Current | $116,700 |
34| Senior Technology Assurance Associate | KPMG | Omaha, NE | 2022-06 | 2023-10 | $110,000 |
35| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | $89,500 |
36| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | $72,000 |
37| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | $65,000 |
38| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | $58,000 |
39| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | $20/hour |
40| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | $16/hour |
41| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | $7/hour |
42| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | $14/hour |
43
44This page was inspired by [[https://xeiaso.net/][Xe]], and I'm quoting the following wording from them as
45I want to reiterate this piece:
46
47#+BEGIN_QUOTE
48Please consider publishing your salary data like this as well. By open,
49voluntary transparency we can help to end stigmas around discussing pay and help
50ensure that the next generations of people in tech are treated fairly. Stigmas
51thrive in darkness but die in the light of day. You can help end the stigma by
52playing your cards out in the open like this.
53#+END_QUOTE
services/index.org deleted −16
@@ -1,16 +0,0 @@
1#+title: Services
2#+OPTIONS: toc:nil
3
4- [[https://ao.cleberg.net][AnonymousOverflow]] - A StackOverflow proxy
5- [[https://cyberchef.cleberg.net][CyberChef]] - The Cyber Swiss Army Knife
6- [[https://files.cleberg.net][FileArchive]] - An interesting file archive
7- [[https://paste.cleberg.net][FlashPaper]] - One-time encrypted password/secret sharing
8- [[https://gh.cleberg.net][GotHub]] - An alternative front-end for GitHub
9- [[https://ip.cleberg.net][ifconfig.php]] - A "whatsmyip" tool
10- [[https://invidious.cleberg.net][Invidious]] - A YouTube proxy
11- [[https://nitter.cleberg.net][Nitter]] - A Twitter proxy
12- [[https://office.cleberg.net][Office]] - The world's smallest office suite
13- [[https://search.cleberg.net][SearXNG]] - A privacy-respecting, open metasearch engine
14
15See the [[https://git.cleberg.net/?p=cleberg.net.git;a=history;f=services/index.org;h=71ed6bcae7e52a8103f980f1821ca291ef3010ba;hb=HEAD][git log]] for this page if you want to see changes that have been made.
16
static/styles.css +424 −72
@@ -1,3 +1,365 @@
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
11 html {
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
23 body {
24 margin: 0;
25 }
26
27 /**
28 * Render the `main` element consistently in IE.
29 */
30
31 main {
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
40 h1 {
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
53 hr {
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
64 pre {
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
76 a {
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
85 abbr[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
95 b,
96 strong {
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
105 code,
106 kbd,
107 samp {
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
116 small {
117 font-size: 80%;
118 }
119
120 /**
121 * Prevent `sub` and `sup` elements from affecting the line height in
122 * all browsers.
123 */
124
125 sub,
126 sup {
127 font-size: 75%;
128 line-height: 0;
129 position: relative;
130 vertical-align: baseline;
131 }
132
133 sub {
134 bottom: -0.25em;
135 }
136
137 sup {
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
148 img {
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
160 button,
161 input,
162 optgroup,
163 select,
164 textarea {
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
176 button,
177 input { /* 1 */
178 overflow: visible;
179 }
180
181 /**
182 * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 * 1. Remove the inheritance of text transform in Firefox.
184 */
185
186 button,
187 select { /* 1 */
188 text-transform: none;
189 }
190
191 /**
192 * Correct the inability to style clickable types in iOS and Safari.
193 */
194
195 button,
196 [type="button"],
197 [type="reset"],
198 [type="submit"] {
199 -webkit-appearance: button;
200 }
201
202 /**
203 * Remove the inner border and padding in Firefox.
204 */
205
206 button::-moz-focus-inner,
207 [type="button"]::-moz-focus-inner,
208 [type="reset"]::-moz-focus-inner,
209 [type="submit"]::-moz-focus-inner {
210 border-style: none;
211 padding: 0;
212 }
213
214 /**
215 * Restore the focus styles unset by the previous rule.
216 */
217
218 button:-moz-focusring,
219 [type="button"]:-moz-focusring,
220 [type="reset"]:-moz-focusring,
221 [type="submit"]:-moz-focusring {
222 outline: 1px dotted ButtonText;
223 }
224
225 /**
226 * Correct the padding in Firefox.
227 */
228
229 fieldset {
230 padding: 0.35em 0.75em 0.625em;
231 }
232
233 /**
234 * 1. Correct the text wrapping in Edge and IE.
235 * 2. Correct the color inheritance from `fieldset` elements in IE.
236 * 3. Remove the padding so developers are not caught out when they zero out
237 * `fieldset` elements in all browsers.
238 */
239
240 legend {
241 box-sizing: border-box; /* 1 */
242 color: inherit; /* 2 */
243 display: table; /* 1 */
244 max-width: 100%; /* 1 */
245 padding: 0; /* 3 */
246 white-space: normal; /* 1 */
247 }
248
249 /**
250 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 */
252
253 progress {
254 vertical-align: baseline;
255 }
256
257 /**
258 * Remove the default vertical scrollbar in IE 10+.
259 */
260
261 textarea {
262 overflow: auto;
263 }
264
265 /**
266 * 1. Add the correct box sizing in IE 10.
267 * 2. Remove the padding in IE 10.
268 */
269
270 [type="checkbox"],
271 [type="radio"] {
272 box-sizing: border-box; /* 1 */
273 padding: 0; /* 2 */
274 }
275
276 /**
277 * Correct the cursor style of increment and decrement buttons in Chrome.
278 */
279
280 [type="number"]::-webkit-inner-spin-button,
281 [type="number"]::-webkit-outer-spin-button {
282 height: auto;
283 }
284
285 /**
286 * 1. Correct the odd appearance in Chrome and Safari.
287 * 2. Correct the outline style in Safari.
288 */
289
290 [type="search"] {
291 -webkit-appearance: textfield; /* 1 */
292 outline-offset: -2px; /* 2 */
293 }
294
295 /**
296 * Remove the inner padding in Chrome and Safari on macOS.
297 */
298
299 [type="search"]::-webkit-search-decoration {
300 -webkit-appearance: none;
301 }
302
303 /**
304 * 1. Correct the inability to style clickable types in iOS and Safari.
305 * 2. Change font properties to `inherit` in Safari.
306 */
307
308 ::-webkit-file-upload-button {
309 -webkit-appearance: button; /* 1 */
310 font: inherit; /* 2 */
311 }
312
313 /* Interactive
314 ========================================================================== */
315
316 /*
317 * Add the correct display in Edge, IE 10+, and Firefox.
318 */
319
320 details {
321 display: block;
322 }
323
324 /*
325 * Add the correct display in all browsers.
326 */
327
328 summary {
329 display: list-item;
330 }
331
332 /* Misc
333 ========================================================================== */
334
335 /**
336 * Add the correct display in IE 10+.
337 */
338
339 template {
340 display: none;
341 }
342
343 /**
344 * Add the correct display in IE 10.
345 */
346
347 [hidden] {
348 display: none;
349 }
350
351
352/* CUSTOM CSS */
353:root {
354 --bg: #181a1b;
355 --bg-bright: #2f3132;
356 --fg: #c6c6c6;
357 --fg-bright: #fff;
358 --fg-dark: #575757;
359 --blue: #6eb6ff;
360 --pink: #ff91a5;
361}
362
1@media (prefers-reduced-motion: no-preference) { 363@media (prefers-reduced-motion: no-preference) {
2 :root { 364 :root {
3 scroll-behavior: smooth; 365 scroll-behavior: smooth;
@@ -5,9 +367,9 @@
5} 367}
6 368
7body { 369body {
8 background-color: #282828; 370 background-color: var(--bg);
9 color: #ebdbb2; 371 color: var(--fg);
10 font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; 372 font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
11 font-size: 1rem; 373 font-size: 1rem;
12 line-height: 1.5; 374 line-height: 1.5;
13 max-width: 45em; 375 max-width: 45em;
@@ -15,65 +377,51 @@ body {
15 padding: 0 1rem; 377 padding: 0 1rem;
16} 378}
17 379
18nav, 380.site-nav,
19footer { 381footer {
20 border-color: #ebdbb2; 382 border-color: var(--fg);
21} 383}
22 384
23h1 { 385footer {
24 color: #458588; 386 border-top: 1px dotted;
25} 387 padding-top: 1rem;
26 388 margin: 1rem 0;
27h2 {
28 color: #d79921;
29}
30
31h3 {
32 color: #b16286;
33} 389}
34 390
35h4 { 391footer p {
36 color: #cc241d; 392 margin: 0;
37} 393}
38 394
39h1::before { 395.site-nav {
40 content: "* "; 396 border-bottom: 1px dotted;
41} 397}
42 398
43h2::before { 399.site-nav ul {
44 content: "** "; 400 list-style-type: none;
401 display: flex;
402 align-items: center;
403 padding: 0;
45} 404}
46 405
47h3::before { 406.site-nav ul li {
48 content: "*** "; 407 margin-right: 0.5rem;
49} 408}
50 409
51h4::before { 410h1,
52 content: "**** "; 411h2,
412h3,
413h4 {
414 color: var(--fg-bright);
53} 415}
54 416
55a, 417a,
56a:visited { 418a:visited {
57 color: #689d6a; 419 color: var(--blue);
58 text-decoration: underline; 420 text-decoration: none;
59} 421}
60 422
61a:hover { 423a:hover {
62 background-color: #689d6a; 424 text-decoration: underline;
63 color: #282828;
64}
65
66#preamble {
67 padding: 1rem 0;
68}
69
70#preamble nav a {
71 margin-right: 0.5rem;
72}
73
74#postamble {
75 border-top: 1px solid #000;
76 margin-top: 1rem;
77} 425}
78 426
79img { 427img {
@@ -82,15 +430,15 @@ img {
82 430
83table { 431table {
84 border-collapse: collapse; 432 border-collapse: collapse;
85 color: #ebdbb2; 433 color: var(--fg);
86 border-color: #665c54; 434 border-color: var(--fg-dark);
87} 435}
88 436
89thead, 437thead,
90th, 438th,
91tr, 439tr,
92td { 440td {
93 border: 1px solid #665c54; 441 border: 1px solid var(--fg-dark);
94 padding: 0.25rem; 442 padding: 0.25rem;
95} 443}
96 444
@@ -102,56 +450,60 @@ code {
102} 450}
103 451
104pre { 452pre {
105 background-color: #3c3836; 453 background-color: var(--bg-bright) !important;
454 border-radius: 3px;
106 margin: 0.5rem 0; 455 margin: 0.5rem 0;
456 padding: 0.5rem;
107 overflow-x: auto; 457 overflow-x: auto;
108} 458}
109 459
110pre::before,
111pre::after {
112 background-color: #504945;
113 display: block;
114 white-space: pre;
115}
116
117pre::before {
118 content: "#+BEGIN_SRC";
119}
120
121pre::after {
122 content: "#+END_SRC";
123}
124
125:not(pre) > code { 460:not(pre) > code {
126 color: #d79921; 461 background-color: var(--bg-bright);
462 color: var(--pink);
463 padding: 0.15rem;
464 border-radius: 3px;
127} 465}
128 466
129aside { 467aside {
130 background-color: #504945; 468 background-color: var(--bg-bright);
131 padding: 1rem; 469 padding: 1rem;
132} 470}
133 471
134blockquote { 472blockquote {
473 background-color: var(--bg-bright);
474 border-left: 10px solid var(--blue);
135 margin: 1rem 0; 475 margin: 1rem 0;
476 padding: 0.5rem 1rem;
136} 477}
137 478
138blockquote p { 479blockquote p {
139 margin: 0; 480 margin: 0;
140} 481}
141 482
142blockquote::before, 483.post time {
143blockquote::after { 484 margin-right: 0.5rem;
144 background-color: #504945; 485}
145 display: block; 486
146 white-space: pre; 487.post-metadata {
488 border: 1px dotted var(--fg);
489 border-top: none;
490 padding: 1rem;
491}
492
493.post-metadata h1 {
494 margin: 0;
495}
496
497.post-metadata p {
498 margin: 0;
147} 499}
148 500
149blockquote::before { 501details {
150 content: "#+BEGIN_QUOTE"; 502 margin-top: 1rem;
151} 503}
152 504
153blockquote::after { 505summary {
154 content: "#+END_QUOTE"; 506 font-weight: bold;
155} 507}
156 508
157.footnote-definition { 509.footnote-definition {
templates/base.html added +44
@@ -0,0 +1,44 @@
1<!doctype html>
2<html lang="en">
3<head>
4 <title>cleberg.net</title>
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 {% if page %}
7 {% if page.description %}
8 <meta name="description" content="{{ page.description }}">
9 {% elif config.description %}
10 <meta name="description" content="{{ config.description }}">
11 {% endif %}
12 {% elif config.description %}
13 <meta name="description" content="{{ config.description }}" />
14 {% endif %}
15
16 {% block rss %}
17 <link rel="alternate" type="application/atom+xml" title="RSS" href="{{ get_url(path="atom.xml", trailing_slash=false) }}">
18 {% endblock %}
19
20 {% block css %}
21 <link rel="stylesheet" href="/styles.css">
22 {% endblock css %}
23
24 {% block extra_head %}{% endblock extra_head %}
25</head>
26<body>
27 <nav class="site-nav">
28 <ul>
29 <li><a href="/">Home</a></li>
30 <li><a href="/blog/">Blog</a></li>
31 <li><a href="/services/">Services</a></li>
32 <li><a href="/wiki/">Wiki</a></li>
33 </ul>
34 </nav>
35
36 <main>{% block content %}{% endblock content %}</main>
37
38 <footer>
39 <p>Last build: {{ now() | date(format="%Y-%m-%d %H:%M:%S") }}</p>
40 <p><a href="https://git.sr.ht/~cyborg/cleberg.net" target="_blank" rel="noopener">Source Code</a></p>
41 <p><a href="/atom.xml">Atom RSS Feed</a></p>
42 </footer>
43</body>
44</html>
templates/index.html added +40
@@ -0,0 +1,40 @@
1{% extends "base.html" %}
2
3{% block content %}
4<section>
5 <h1>cleberg.net</h1>
6 <blockquote>Do AIs dream in electric sheep?<br>He hoped it had nanosecond nightmares.</blockquote>
7</section>
8<section>
9 <h2>Recent Blog Posts</h2>
10 {% set section = get_section(path="blog/_index.md") %}
11 {% set i = 0 %}
12 {% for page in section.pages %}
13 {% if i < 3%}
14 {% set_global i = i + 1%}
15 <div class="post">
16 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time>
17 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
18 </div>
19 {% endif %}
20 {% endfor %}
21 <br>
22 <a href="/blog/">Older Posts &rarr;</a>
23</section>
24<section>
25 <h2>Everything Else</h2>
26 <ul>
27 <li><a href="/salary/">Salary</a></li>
28 <li><a href="/services/">Services</a></li>
29 <li><a href="/wiki/">Wiki</a></li>
30 </ul>
31</section>
32<section>
33 <h2>Contact</h2>
34 <ul>
35 <li>hello [at] cleberg.net (<a href="/gpg.txt">GPG</a>)</li>
36 <li><a href="https://matrix.to/#/@cyborg:matrix.tchncs.de" target="_blank" rel="noopener">@cyborg:@matrix.tchncs.de</a></li>
37 <li><a href="https://git.sr.ht/~cyborg/" target="_blank" rel="noopener">Sourcehut</a></li>
38 </ul>
39</section>
40{% endblock content %}
templates/page.html added +41
@@ -0,0 +1,41 @@
1{% extends "base.html" %}
2
3{% block css %}
4<link rel="stylesheet" href="/syntax-theme-dark.css">
5<link rel="stylesheet" href="/styles.css">
6{% endblock css %}
7
8{% block content %}
9<section class="post-metadata">
10 <h1>{{ page.title }}</h1>
11 {% if page.date %}
12 <p><time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time></p>
13 {% endif %}
14 <p>{{ page.word_count }} words; {{ page.reading_time }} minute(s)</p>
15{% if page.toc %}
16 <details open>
17 <summary>Table of Contents</summary>
18 <nav class="page-nav">
19 <ul>
20 {% for h1 in page.toc %}
21 <li>
22 <a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
23 {% if h1.children %}
24 <ul>
25 {% for h2 in h1.children %}
26 <li>
27 <a href="{{ h2.permalink | safe }}">{{ h2.title }}</a>
28 </li>
29 {% endfor %}
30 </ul>
31 {% endif %}
32 </li>
33 {% endfor %}
34 </ul>
35 </nav>
36 </details>
37{% endif %}
38</section>
39
40{{ page.content | safe }}
41{% endblock content %}
templates/section.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.
6<br>You can also add the <a href="/atom.xml">Atom RSS Feed</a> to your feed reader.</p>
7{% set post_date = "1970-01-01" | date(format="%Y") %}
8{% for page in section.pages %}
9 {% if post_date != page.date | date(format="%Y") %}
10 {% set_global post_date = page.date | date(format="%Y") %}
11 <h2>{{ post_date }}</h2>
12 {% endif %}
13 <div class="post">
14 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time>
15 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
16 </div>
17{% endfor %}
18{% endblock content %}