~cyborg/php-blog

9911e4da6db6da699cb45d387e674c61895a67e7 — cyborg 3 months ago 5a9966e
generalize the repository as a template
43 files changed, 184 insertions(+), 5202 deletions(-)

M README.md
M _data/comments.json
M _data/metadata.json
M _functions/loadRSS.php
M _templates/template.html
M index.php
D posts/001-cpp-compiler.md
A posts/001-lorem-ipsum.md
D posts/002-aes-encryption.md
A posts/002-example-syntax.md
D posts/003-useful-css-snippets.md
D posts/004-data-analysis-in-auditing.md
D posts/005-the-ansoff-matrix.md
D posts/006-password-security.md
D posts/007-the-best-linux-software.md
D posts/008-steam-on-ntfs-drives.md
D posts/009-cryptography-basics.md
D posts/010-session-private-messenger.md
D posts/011-homelab.md
D posts/012-customizing-ubuntu.md
D posts/013-data-exploration-video-game-sales.md
D posts/014-algorithmically-analyzing-local-businesses.md
D posts/015-github-pages-subdomain-to-tld.md
D posts/016-php-authentication-flow.md
D posts/017-ibm-watson-visual-recognition.md
D posts/018-what-is-internal-audit.md
D posts/019-data-visualization-world-choropleth-map-of-happiness.md
D posts/020-on-the-pursuit-of-mediocrity.md
D posts/021-minimal-website-redesign.md
D posts/022-neon-drive.md
D posts/023-zork.md
D posts/024-seum.md
D posts/025-a-simple-guide-to-the-fediverse.md
D posts/026-secure-your-network-with-the-uncomplicated-firewall.md
D posts/027-macos-testing-out-a-new-os.md
D posts/028-how-to-clone-all-github-repositories.md
D posts/029-launching-a-gemini-capsule.md
D posts/030-vaporwave-vs-outrun.md
D posts/031-how-to-create-a-vps-web-server.md
D posts/032-hosting-a-gemini-server.md
D posts/033-roll-your-own-static-commenting-system-in-php.md
D posts/034-jumping-back-into-photography.md
D posts/035-changing-git-authors.md
M README.md => README.md +29 -30
@@ 14,18 14,13 @@ use of Markdown files to store written articles.

## Example Site

I use this framework for my own blog and this repository currently hosts metadata for my blog. You can
view [the current blog](https://blog.cleberg.io) before attempting to implement anything. Keep in mind that the CSS is
extremely minimal on purpose and can be expanded easily.
I previously used this framework for my own blog before I switched back to a static HTML blog. Keep in mind that the CSS
is extremely minimal on purpose and can be expanded easily.

If you have any questions, feel free to email me at [hello@cleberg.io](mailto:hello@cleberg.io).

## Installation

This work is released under the [GNU GPL v3.0](https://git.sr.ht/~cyborg/blog/blob/main/LICENSE) license.

### Installation Setup

Steps:

```bash


@@ 33,7 28,7 @@ cd yourBlog.com
```

```bash
git clone https://git.sr.ht/~cyborg/blog
git clone https://github.com/christian-cleberg/blog/
```

Change the global variables at the top of the `index.php` script:


@@ 44,21 39,25 @@ $shortDomain = 'yourBlog.com';
```

Remove my personal data files:

```bash
cd _data/ && rm -rf *
```

Add blank data files:

```bash
touch comments.json && touch metadata.json
```

Remove my personal posts:

```bash
cd ../posts/ && rm -rf *
```

Add your first post:

```bash
touch 001-your-first-post.md
```


@@ 71,7 70,7 @@ Finally, enable `FallbackResource` in your Apache `.conf` file or in your `.htac
FallbackResource /index.php
```

### Project Structure
## Project Structure

This project uses the structure show below. Continue reading for a brief description of each file/folder.



@@ 112,7 111,7 @@ blog/
    └─── prism.js
```

#### File: `index.php`
### File: `index.php`

This file is the FallbackResource for the entire directory, so non-existent folders/files requested by users will be
sent to `index.php` for processing. This file uses a switch statement to read the request URL and direct the user to


@@ 122,13 121,13 @@ Each function in this file will call various other functions/classes and then se

---

#### Folder: `_classes`
### Folder: `_classes`

##### File: `Comment.php`
#### File: `Comment.php`

This class allows for the creation of user comments and provides the `saveComment()` function to do so.

##### File: `Parsedown.php`
#### File: `Parsedown.php`

This class was not created by me, it comes from [erusev/parsedown](https://github.com/erusev/parsedown). See
his [MIT License](https://raw.githubusercontent.com/erusev/parsedown/master/LICENSE.txt) for licensing questions on this


@@ 136,16 135,16 @@ code.

This class provides numerous capabilities to parse Markdown files and variables to HTML.

##### File: `Template.php`
#### File: `Template.php`

This class allows for the creation of a final HTML page to show the user. The `echoTemplate()` function will replace
the `{}` templates in the `_templates/template.html` file.

---

#### Folder: `_data`
### Folder: `_data`

##### File: `metadata.json`
#### File: `metadata.json`

Your `metadata.json` file will need to contain an object for each post in the `post` folder. If you don't add an object
for your new blog post, it won't be displayed in the browser. The structure will follow this exactly:


@@ 164,7 163,7 @@ for your new blog post, it won't be displayed in the browser. The structure will
}
```

##### File: `comments.json`
#### File: `comments.json`

This file is updated server-side as users submit comments on posts. You can commit these changes back to your repository
by making sure the server-side git repo can read & write. Then just create git commits back to your repo from there (you


@@ 180,50 179,50 @@ chmod -R g+w _data/comments.json

---

#### Folder: `_functions`
### Folder: `_functions`

##### File: `loadJSON.php`
#### File: `loadJSON.php`

This file contains four (4) functions that can be called to load JSON data from a local file in various ways.

##### File: `loadRSS.php`
#### File: `loadRSS.php`

This file contains a single function that will grab the `_data/metadata.json` file and generate a complete RSS file.

##### File: `parseMarkdown.php`
#### File: `parseMarkdown.php`

This file uses the `Parsedown` class to parse a posts or comments from Markdown to HTML. It also adds additional
attributes to any link found in the Markdown.

##### File: `submitComment.php`
#### File: `submitComment.php`

This file uses the `Comment` class to create a comment and then save it to `_data/comments.json`.

---

#### Folder: `_templates`
### Folder: `_templates`

##### File: `template.html`
#### File: `template.html`

This file is the end result template that will be shown to users, regardless of the URL they enter. The main content in
the `<body>` and various elements in the `<head>` will change depending which function calls this template.

---

#### Folder: `posts`
### Folder: `posts`

Your `posts` folder will contain the `.md` files that will comprise the body of your blog post. Formatting follows
standard Markdown guidelines.

---

#### Folder: `static`
### Folder: `static`

This folder contains the main CSS file (`app.css`), as well as a pair of optional static files from [PrismJS](https://prismjs.com) to enable syntax highlighting
in any code blocks found in posts or comments.
This folder contains the main CSS file (`app.css`), as well as a pair of optional static files
from [PrismJS](https://prismjs.com) to enable syntax highlighting in any code blocks found in posts or comments.

To remove the optional files, simply delete the unwanted files and remove the following from the `pageExtras` parameter string in
the `ShowComments()` and `ShowPost()` functions in `index.php`:
To remove the optional files, simply delete the unwanted files and remove the following from the `pageExtras` parameter
string in the `ShowComments()` and `ShowPost()` functions in `index.php`:

```html


M _data/comments.json => _data/comments.json +6 -18
@@ 1,26 1,14 @@
[
  {
    "timestamp": "2021-04-20 19:16:36",
    "username": "Christian Cleberg",
    "comment": "Feel free to also check out [gemlog.blue](https:\/\/gemlog.blue)!",
    "postURL": "https:\/\/blog.cleberg.io\/post\/launching-a-gemini-capsule.html"
    "username": "John Doe",
    "comment": "Lorem ipsum dolor sit amet!",
    "postURL": "https:\/\/blog.example.com\/post\/lorem-ipsum.html"
  },
  {
    "timestamp": "2021-04-21 02:32:39",
    "username": "Christian Cleberg",
    "comment": "After more research, it seems that DigitalOcean and Vultr are priced similarly. However, Vultr has some lower-cost options for those who can't afford $5.00 per month.",
    "postURL": "https:\/\/blog.cleberg.io\/post\/how-to-create-a-vps-web-server.html"
  },
  {
    "timestamp": "2021-04-21 02:41:22",
    "username": "Christian Cleberg",
    "comment": "FYI - There are similar API solutions available for GitLab and Sourcehut.",
    "postURL": "https:\/\/blog.cleberg.io\/post\/how-to-clone-all-github-repositories.html"
  },
  {
    "timestamp": "2021-04-23 14:11:53",
    "username": "Christian Cleberg",
    "comment": "Shout-out to Baty, who launched his gemlog successfully through this tutorial @ [gemlog.baty.net](gemini:\/\/gemlog.baty.net).",
    "postURL": "https:\/\/blog.cleberg.io\/post\/hosting-a-gemini-server.html"
    "username": "Jane Dow",
    "comment": "# Lorem Ipsum!",
    "postURL": "https:\/\/blog.example.com\/post\/example-syntax.html"
  }
]

M _data/metadata.json => _data/metadata.json +11 -374
@@ 1,387 1,24 @@
[
  {
    "id": "035",
    "title": "Changing Git Authors",
    "author": "Christian Cleberg",
    "description": "Learn how to change Git author names and emails with this quick bash script.",
    "tag": "personal",
    "created": "2021-05-30 00:00:00",
    "modified": "2021-05-30 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/changing-git-authors.html",
    "published": "Yes"
  },
  {
    "id": "034",
    "title": "Jumping Back Into Photography",
    "author": "Christian Cleberg",
    "description": "A quick description of my current photography kit.",
    "tag": "personal",
    "created": "2021-04-28 00:00:00",
    "modified": "2021-04-28 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/jumping-back-into-photography.html",
    "published": "Yes"
  },
  {
    "id": "033",
    "title": "Roll Your Own Static Commenting System in PHP",
    "author": "Christian Cleberg",
    "description": "Tired of comment systems forcing you to use third-party frames or scripts? Use PHP to create a static set of comments and increase your blog engagement.",
    "tag": "coding",
    "created": "2021-04-23 00:00:00",
    "modified": "2021-04-23 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/roll-your-own-static-commenting-system-in-php.html",
    "published": "Yes"
  },
  {
    "id": "032",
    "title": "Hosting a Gemini Server",
    "author": "Christian Cleberg",
    "description": "An easy-to-follow tutorial on hosting your own Gemini server with Ubuntu 20.04.",
    "tag": "technology",
    "created": "2021-04-17 00:00:00",
    "modified": "2021-04-17 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/hosting-a-gemini-server.html",
    "published": "Yes"
  },
  {
    "id": "031",
    "title": "How to Create a VPS Web Server",
    "author": "Christian Cleberg",
    "description": "Read this guide to transferring websites from a shared hosting provider to a virtual private server (VPS).",
    "tag": "technology",
    "created": "2021-03-30 00:00:00",
    "modified": "2021-03-30 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/how-to-create-a-vps-web-server.html",
    "published": "Yes"
  },
  {
    "id": "030",
    "title": "Vaporwave vs Outrun",
    "author": "Christian Cleberg",
    "description": "This post describes the differences between Vaporwave and Outrun, aesthetic forms of media often confused for each other.",
    "tag": "culture",
    "created": "2021-03-28 00:00:00",
    "modified": "2021-03-28 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/vaporwave-vs-outrun.html",
    "published": "Yes"
  },
  {
    "id": "029",
    "title": "Launching a Gemini Capsule",
    "author": "Christian Cleberg",
    "description": "One of the major alternatives to HTTP(S), Gemini is quickly becoming a cult favorite in the world of internet protocols.",
    "tag": "technology",
    "created": "2021-03-28 00:00:00",
    "modified": "2021-03-28 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/launching-a-gemini-capsule.html",
    "published": "Yes"
  },
  {
    "id": "028",
    "title": "How to Clone All Repositories from a GitHub Account",
    "author": "Christian Cleberg",
    "description": "If you ever need to clone all repositories from a user's or organization's account on GitHub, simply run this script.",
    "tag": "coding",
    "created": "2021-03-19 00:00:00",
    "modified": "2021-03-19 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/how-to-clone-all-github-repositories.html",
    "published": "Yes"
  },
  {
    "id": "027",
    "title": "macOS: Testing Out A New OS",
    "author": "Christian Cleberg",
    "description": "As a Linux user who grew up on Windows, MacOS is a whole new world for me. However, I've found that it's actually an incredibly useful OS.",
    "tag": "software",
    "created": "2021-02-19 00:00:00",
    "modified": "2021-02-19 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/macos-testing-out-a-new-os.html",
    "published": "Yes"
  },
  {
    "id": "026",
    "title": "Secure Your Network with the Uncomplicated Firewall",
    "author": "Christian Cleberg",
    "description": "Use the uncomplicated firewall (ufw) to secure your network with minimal effort.",
    "tag": "security",
    "created": "2021-01-07 00:00:00",
    "modified": "2021-01-07 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/secure-your-network-with-the-uncomplicated-firewall.html",
    "published": "Yes"
  },
  {
    "id": "025",
    "title": "A Simple Guide to the Fediverse",
    "author": "Christian Cleberg",
    "description": "Read this short guide to understand how federated universes exists and how we can use it to our advantage.",
    "tag": "technology",
    "created": "2021-01-04 00:00:00",
    "modified": "2021-01-04 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/a-simple-guide-to-the-fediverse.html",
    "published": "Yes"
  },
  {
    "id": "024",
    "title": "SEUM: Speedrunners from Hell",
    "author": "Christian Cleberg",
    "description": "SEUM: Spreedrunners from Hell makes gamers of all skill levels rage with fast-paced runs and impossible puzzles.",
    "tag": "gaming",
    "created": "2021-01-01 00:00:00",
    "modified": "2021-01-01 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/seum.html",
    "published": "Yes"
  },
  {
    "id": "023",
    "title": "Zork: Let's Explore a Classic",
    "author": "Christian Cleberg",
    "description": "Let's play Zork, one of the most iconic text-based adventure games of the 1980s.",
    "tag": "gaming",
    "created": "2020-12-29 00:00:00",
    "modified": "2020-12-29 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/zork.html",
    "published": "Yes"
  },
  {
    "id": "022",
    "title": "Neon Drive: A Nostalgic 80s Arcade Racing Game",
    "author": "Christian Cleberg",
    "description": "Neon Drive (released in 2016) gives users the nostalgic fun of an arcade racing game, while sticking to the core aesthetics of outrun and synthwave.",
    "tag": "gaming",
    "created": "2020-12-28 00:00:00",
    "modified": "2020-12-28 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/neon-drive.html",
    "published": "Yes"
  },
  {
    "id": "021",
    "title": "Redesigning My Website: The 5 KB Result",
    "author": "Christian Cleberg",
    "description": "After struggling with indecision around designs for my homepage and test, I decided to scrap it all and go minimal.",
    "tag": "coding",
    "created": "2020-12-27 00:00:00",
    "modified": "2020-12-27 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/minimal-website-redesign.html",
    "published": "Yes"
  },
  {
    "id": "020",
    "title": "On the Pursuit of Mediocrity",
    "author": "Christian Cleberg",
    "description": "As workers in the modern world, we often find ourselves searching for the perfect solutions to problems when there are better alternatives. However, we must consider when mediocrity isn't enough.",
    "tag": "personal",
    "created": "2020-10-12 00:00:00",
    "modified": "2020-10-12 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/on-the-pursuit-of-mediocrity.html",
    "published": "Yes"
  },
  {
    "id": "019",
    "title": "Data Visualization: World Choropleth Map of Happiness",
    "author": "Christian Cleberg",
    "description": "Utilize Python and Folium to generate a world choropleth map that can display world happiness rankings, as well as other country statistics.",
    "tag": "data-analysis",
    "created": "2020-09-25 00:00:00",
    "modified": "2020-09-25 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/data-visualization-world-choropleth-map-of-happiness.html",
    "published": "Yes"
  },
  {
    "id": "018",
    "title": "What is Internal Audit?",
    "author": "Christian Cleberg",
    "description": "Learn more about internal audit, a company's way of extracting more value from their processes, controlling risks, and satisfying government regulations.",
    "tag": "business",
    "created": "2020-09-22 00:00:00",
    "modified": "2020-09-22 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/what-is-internal-audit.html",
    "published": "Yes"
  },
  {
    "id": "017",
    "title": "IBM Watson Visual Recognition",
    "author": "Christian Cleberg",
    "description": "Jump into machine learning with a look at the IBM Watson Visual Recognition API. This post shows how to use Python to classify images automatically using IBM Watson.",
    "tag": "data-analysis",
    "created": "2020-09-01 00:00:00",
    "modified": "2020-09-01 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/ibm-watson-visual-recognition.html",
    "published": "Yes"
  },
  {
    "id": "016",
    "title": "PHP Authentication Flow",
    "author": "Christian Cleberg",
    "description": "Learn how to authenticate user accounts, hash passwords, and maintain valid authentication across your site with PHP.",
    "tag": "coding",
    "created": "2020-08-29 00:00:00",
    "modified": "2020-08-29 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/php-authentication-flow.html",
    "published": "Yes"
  },
  {
    "id": "015",
    "title": "Redirect GitHub Pages from Subdomain to the Top-Level Domain",
    "author": "Christian Cleberg",
    "description": "Follow this easy steps to configure your GitHub Pages project to redirect from a subdomain (e.g. www.example.com) to the top-level domain (e.g. example.com).",
    "tag": "coding",
    "created": "2020-08-22 00:00:00",
    "modified": "2020-08-22 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/github-pages-subdomain-to-tld.html",
    "published": "Yes"
  },
  {
    "id": "014",
    "title": "Algorithmically Analyzing Local Businesses",
    "author": "Christian Cleberg",
    "description": "In this project, we use the Foursquare API, Leaflet maps, and the k-means algorithm to cluster data points of location business geo-locations.",
    "tag": "data-analysis",
    "created": "2020-07-26 00:00:00",
    "modified": "2020-07-26 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/algorithmically-analyzing-local-businesses.html",
    "published": "Yes"
  },
  {
    "id": "013",
    "title": "Data Exploration: Video Game Sales",
    "author": "Christian Cleberg",
    "description": "Use Python to explore a dataset of video game sales from 1980 to 2020, including basic analyses and visualizations.",
    "tag": "data-analysis",
    "created": "2020-07-20 00:00:00",
    "modified": "2020-07-20 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/data-exploration-video-game-sales.html",
    "published": "Yes"
  },
  {
    "id": "012",
    "title": "Beginner's Guide: Customizing Ubuntu",
    "author": "Christian Cleberg",
    "description": "Learn how to customize your Ubuntu Desktop 20.04 installation: choose your own application theme, shell theme, icons, cursors, fonts, and terminals!",
    "tag": "software",
    "created": "2020-05-19 00:00:00",
    "modified": "2020-05-19 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/customizing-ubuntu.html",
    "published": "Yes"
  },
  {
    "id": "011",
    "title": "An Inside Look at My Homelab",
    "author": "Christian Cleberg",
    "description": "Walk through the beginning steps to creating my own homelab, including the hardware, tools, and services I am using to get it running.",
    "tag": "hardware",
    "created": "2020-05-03 00:00:00",
    "modified": "2020-05-03 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/homelab.html",
    "published": "Yes"
  },
  {
    "id": "010",
    "title": "Session Private Messenger",
    "author": "Christian Cleberg",
    "description": "Learn more about Session, a private and decentralized messaging service from the Loki Network. Read more to get a preview of the app's main functions.",
    "tag": "software",
    "created": "2020-03-25 00:00:00",
    "modified": "2020-03-25 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/session-private-messenger.html",
    "published": "Yes"
  },
  {
    "id": "009",
    "title": "Cryptography Basics",
    "author": "Christian Cleberg",
    "description": "Brush up on the basics of cryptography, encryption techniques, and intelligent IT controls to mitigate cryptographic risks.",
    "tag": "security",
    "created": "2020-02-09 00:00:00",
    "modified": "2020-02-09 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/cryptography-basics.html",
    "published": "Yes"
  },
  {
    "id": "008",
    "title": "Linux Gaming Tweak: Steam on NTFS Drives",
    "author": "Christian Cleberg",
    "description": "Having trouble launching Steam games after installing them on another drive? Read here how to fix it.",
    "tag": "software",
    "created": "2020-01-26 00:00:00",
    "modified": "2020-01-26 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/steam-on-ntfs-drives.html",
    "published": "Yes"
  },
  {
    "id": "007",
    "title": "The Best Linux Software",
    "author": "Christian Cleberg",
    "description": "A running-list of notable applications, packages, and other software available for use on Linux.",
    "tag": "software",
    "created": "2020-01-25 00:00:00",
    "modified": "2020-01-25 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/the-best-linux-software.html",
    "published": "Yes"
  },
  {
    "id": "006",
    "title": "Password Security",
    "author": "Christian Cleberg",
    "description": "In a world full of insecure data practices, it's our responsibility to ensure proper security and fidelity.",
    "tag": "security",
    "created": "2019-12-16 00:00:00",
    "modified": "2019-12-16 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/password-security.html",
    "published": "Yes"
  },
  {
    "id": "005",
    "title": "The Ansoff Matrix",
    "author": "Christian Cleberg",
    "description": "A quick dive into the power of the Ansoff Matrix, one of the most popular strategic planning frameworks.",
    "tag": "business",
    "created": "2019-12-03 00:00:00",
    "modified": "2019-12-03 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/the-ansoff-matrix.html",
    "published": "Yes"
  },
  {
    "id": "004",
    "title": "Data Analysis in Auditing",
    "author": "Christian Cleberg",
    "description": "My current take on incorporating data analysis into the internal auditing function.",
    "tag": "data-analysis",
    "created": "2019-09-09 00:00:00",
    "modified": "2019-09-09 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/data-analysis-in-auditing.html",
    "published": "Yes"
  },
  {
    "id": "003",
    "title": "Useful CSS Snippets",
    "author": "Christian Cleberg",
    "description": "Some quick and easy CSS snippets that can help you save time or learn something new.",
    "tag": "coding",
    "created": "2019-01-07 00:00:00",
    "modified": "2019-01-07 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/useful-css-snippets.html",
    "published": "Yes"
  },
  {
    "id": "002",
    "title": "AES Encryption",
    "author": "Christian Cleberg",
    "description": "A beginner's introduction to the AES encryption model.",
    "title": "Syntax Possiblities",
    "author": "John Doe",
    "description": "Praesent maximus erat vitae arcu semper, sit amet euismod risus porta.",
    "tag": "security",
    "created": "2018-12-08 00:00:00",
    "modified": "2018-12-08 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/aes-encryption.html",
    "created": "2020-12-08 00:00:00",
    "modified": "2020-12-08 00:00:00",
    "link": "https:\/\/blog.example.com\/post\/example-syntax.html",
    "published": "Yes"
  },
  {
    "id": "001",
    "title": "The C++ Compiler",
    "author": "Christian Cleberg",
    "description": "An easy, straight-forward way to understand the C++ compiler.",
    "tag": "coding",
    "title": "Lorem Ipsum",
    "author": "John Doe",
    "description": "Lorem ipsum dolor sit amet...",
    "tag": "writing",
    "created": "2018-11-28 00:00:00",
    "modified": "2018-11-28 00:00:00",
    "link": "https:\/\/blog.cleberg.io\/post\/cpp-compiler.html",
    "link": "https:\/\/blog.example.com\/post\/lorem-ipsum.html",
    "published": "Yes"
  }
]

M _functions/loadRSS.php => _functions/loadRSS.php +1 -1
@@ 9,7 9,7 @@ function loadRSS(string $fileName): string
    // Set-up RSS variables
    $rssCounter = 0;
    $rssContents = '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    ><channel><atom:link href="' . $GLOBALS['fullDomain'] . '/rss.xml" rel="self" type="application/rss+xml" /><title>Blog | Christian Cleberg</title><link>' . $GLOBALS['fullDomain'] . '</link><description>This is the personal blog of Christian Cleberg. There are no specific topics to be covered in any of my blog posts but you can find information on auditing, security, software development, and other oddities in my life.</description><copyright>Copyright 2017 - 2021, Christian Cleberg</copyright><language>en-us</language><docs>https://cyber.harvard.edu/rss/index.html</docs><lastBuildDate>Mon, 04 Jan 2021 00:00:00 CST</lastBuildDate><ttl>60</ttl><image><url>https://img.cleberg.io/share/profile.jpg</url><title>Blog | Christian Cleberg</title><link>'.$GLOBALS['fullDomain'].'</link></image>';
    ><channel><atom:link href="' . $GLOBALS['fullDomain'] . '/rss.xml" rel="self" type="application/rss+xml" /><title>Blog</title><link>' . $GLOBALS['fullDomain'] . '</link><description>Lorem ipsum dolor sit amet...</description><copyright>Copyright 20xx - 20xx, My Name</copyright><language>en-us</language><docs>https://cyber.harvard.edu/rss/index.html</docs><lastBuildDate>Mon, 04 Jan 2021 00:00:00 CST</lastBuildDate><ttl>60</ttl><image><url>https://img.cleberg.io/share/profile.jpg</url><title>Blog | Christian Cleberg</title><link>'.$GLOBALS['fullDomain'].'</link></image>';

    // Loop through the JSON object
    foreach ($data as $postObject) {

M _templates/template.html => _templates/template.html +5 -5
@@ 6,7 6,7 @@
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="author" content="Christian Cleberg">
    <meta name="author" content="My Name">
    <meta name="description" content="{Page_Description}">
    <link rel="canonical" href="{Canonical_URL}">
    <link rel="icon" href="/favicon.ico">


@@ 29,10 29,10 @@
<footer>
    <h2>Contact</h2>
    <p>Have questions? Email me <br>
        at <a href="mailto:hello@cleberg.io">hello@cleberg.io</a>,<br>
        message me on <a rel="me" href="https://floss.social/@cyborg">Mastodon</a>,<br>
        or open an issue on <a href="https://sr.ht/~cyborg/blog">Sourcehut</a>.</p>
    <p>Copyright &copy; 2017 - {Current_Year}</p>
        at <a href="mailto:hello@example.com">hello@example.com</a>,<br>
        message me on <a rel="me" href="https://floss.social/@example">Mastodon</a>,<br>
        or open an issue on <a href="https://github.com/WittsEnd2/Example_Repo">GitHub</a>.</p>
    <p>Copyright &copy; 20xx - {Current_Year}</p>
</footer>

</body>

M index.php => index.php +7 -7
@@ 3,7 3,7 @@
// Set global variables for this specific website
// Domains must match the URLs in the _data files
$websiteProtocol = 'https://';
$shortDomain = 'blog.cleberg.io';
$shortDomain = 'blog.example.com';
$fullDomain = $websiteProtocol . $shortDomain;

// Trim the leading slashes & split the path on slashes


@@ 47,7 47,7 @@ else {
function showHomepage()
{
    // Add Articles
    $contentCol = '<h1>Christian\'s Blog</h1>';
    $contentCol = '<h1>My Blog</h1>';

    // Get metadata on all posts from the metadata.json file
    include_once('_functions/loadJSON.php');


@@ 57,8 57,8 @@ function showHomepage()
    include_once('_classes/Template.php');
    $template = new Template(
        $GLOBALS['fullDomain'],
        'Explore the thoughts of Christian Cleberg - cybersecurity auditor, data analyst, software developer, and avid learner.',
        'Blog | Christian Cleberg',
        'Explore the thoughts of...',
        'Blog | YourName',
        '',
        $contentCol,
        ''


@@ 105,7 105,7 @@ function showPost($params)
    $template = new Template(
        $GLOBALS['fullDomain'] . '/post/' . $query,
        $headerData->description . ' Read more at ' . $GLOBALS['fullDomain'] . '!',
        $headerData->title . ' | Blog by Christian Cleberg',
        $headerData->title . ' | Blog',
        '<link rel="stylesheet" href="/static/prism.css"><script src="/static/prism.js"></script>',
        $contentCol,
        $commentSection


@@ 135,7 135,7 @@ function showComments()
    $template = new Template(
        $GLOBALS['fullDomain'] . '/comments/',
        'Read through some recent comments for blog posts at ' . $GLOBALS['fullDomain'] . '.',
        'Recent Comments | Blog by Christian Cleberg',
        'Recent Comments | Blog',
        '<link rel="stylesheet" href="/static/prism.css"><script src="/static/prism.js"></script><meta name="robots" content="noindex,nofollow">',
        '',
        $commentSection


@@ 159,7 159,7 @@ function showCategory()
    $template = new Template(
        $GLOBALS['fullDomain'] . '/categories/',
        'Browse the categories for blog posts at ' . $GLOBALS['fullDomain'] . '.',
        'Categories | Blog by Christian Cleberg',
        'Categories | Blog',
        '<meta name="robots" content="noindex,nofollow">',
        $contentCol,
        ''

D posts/001-cpp-compiler.md => posts/001-cpp-compiler.md +0 -40
@@ 1,40 0,0 @@
## A Brief Introduction

Compiling C++ projects is a frustrating task most days. Seemingly nonexistent errors keeping your program from
successfully compiling can be annoying, especially since you know you wrote it perfectly the first time, right?

Have no fear, Christian is here to explain the role of the C++ compiler for you. I'm learning more and more about C++
these days and decided to reteach this concept so that I can cement it even further in my own head. However, C++ is not
the only compiled language. Check
out [the Wikipedia entry for compiled languages](https://en.wikipedia.org/wiki/Compiled_language) for more examples of
compiled languages.

I'll start with a wonderful, graphical way to conceptualize the C++
compiler. [View this page on C++ compilation](http://faculty.cs.niu.edu/%7Emcmahon/CS241/Notes/compile.html) to see the
graphic and an explanation. Kurt MacMahon, a professor from NIU, explains the compiler in a succinct, but easy
explanation. The goal of the compilation process is to take the C++ code and produce a shared library, dynamic library,
or an executable file.

Let's break down the compilation process. There are four major steps to compiling C++ code. The first step is to expand
the source code file to meet all dependencies. The C++ preprocessor includes the code from all the header files, such
as `#include <iostream>`. Now, what does that mean? The previous example includes the `iostream` header. This tells the
computer that you want to use the `iostream` standard library, which contains classes and functions written in the core
language. This header, specifically, allows you to manipulate input/output streams. After all this, you'll end up which
a temporary file that contains the expanded source code.

After the code is expanded, the compiler comes into play. The compiler takes the C++ code and converts this code into
the assembly language, understood by the platform. You can see this in action if you head over to
the [Godbolt Compiler Explorer](https://godbolt.org), which shows C++ being converted into assembly dynamically.

Third, the assembly code generated by the compiler is assembled into the object code for the platform. Essentially, this
is when the compiler takes the assembly code and assembles it into machine code in a binary format. After researching
this online, I figured out that a lot of compilers will allow you to stop compilation at this step. This would be useful
for compiling each source code file separately. This saves time later if a single file changes - only that file needs to
be recompiled.

Finally, the object code file generated by the assembler is linked together with the object code files for any library
functions used to produce a shared library, dynamic library, or an executable file. It replaces all references to
undefined symbols with the correct addresses.

That's all I have so far, folks! Hopefully this helps explain a little about the C++ compilation process. If you notice
anything is incorrect, feel free to [send me an email](mailto:hello@cleberg.io)!

A posts/001-lorem-ipsum.md => posts/001-lorem-ipsum.md +42 -0
@@ 0,0 1,42 @@
## Lorem Ipsum

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam aliquet lectus et elit pretium placerat. Etiam dolor
nunc, varius vitae mattis eleifend, malesuada in eros. Suspendisse vulputate porta ex non ornare. Praesent lorem libero,
maximus consequat mattis vitae, egestas at tellus. Nam suscipit felis libero, facilisis tempus elit mattis eu. Maecenas
in luctus quam. Cras egestas, odio tincidunt bibendum tincidunt, enim dui feugiat eros, sed eleifend risus eros sed
eros. Quisque ullamcorper sollicitudin arcu in convallis. Donec leo ligula, rutrum at tempus vitae, viverra at mi. Nunc
diam odio, laoreet ut euismod nec, rhoncus ac sem. Phasellus consectetur in ante cursus consequat. Curabitur ac luctus
justo, at posuere ante. Sed tempus eros quis malesuada efficitur. Curabitur gravida placerat felis, non maximus tortor
mattis at.

## Praesent Maximus

Praesent maximus erat vitae arcu semper, sit amet euismod risus porta. Praesent vel enim cursus, tincidunt erat non,
hendrerit nisi. Maecenas id facilisis felis, scelerisque egestas erat. Sed volutpat, orci ac pulvinar volutpat, neque
turpis posuere ipsum, mattis hendrerit velit magna ac tellus. Vestibulum eget ullamcorper dui, ac dictum diam.
Pellentesque sed ex sapien. Sed ullamcorper aliquam fringilla. Pellentesque id tincidunt augue, aliquet porttitor arcu.
Nullam sed mi id arcu posuere varius. Morbi maximus ligula sit amet lorem blandit lobortis. Cras feugiat risus id magna
consequat, vitae mollis purus efficitur.

### Nullam Quis

Nullam quis tortor sem. Nunc dictum augue eget mattis molestie. Interdum et malesuada fames ac ante ipsum primis in
faucibus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec at imperdiet
ligula. Nulla rutrum in metus eu elementum. Morbi vehicula purus eget fermentum feugiat. Sed at metus nec ipsum sagittis
dictum. Praesent quam augue, mollis vel pellentesque eget, fermentum sit amet urna. Mauris eget erat vitae libero
hendrerit molestie. Duis sollicitudin sodales purus ac ornare. Proin ac ultrices ex. Pellentesque sollicitudin dolor non
arcu lobortis aliquam. Maecenas vehicula, urna eget pellentesque varius, sapien purus porta lectus, id maximus quam
sapien elementum mauris. Suspendisse et velit non purus feugiat malesuada ac a enim.

Maecenas egestas ante vitae dignissim volutpat. Vivamus non mollis odio. Curabitur magna ligula, egestas at finibus ac,
pretium at justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec bibendum ante nec suscipit
viverra. Sed in risus neque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sem nisl, eleifend id
euismod in, cursus sed dui. In luctus ipsum a ipsum imperdiet, ac eleifend magna fringilla. Integer commodo volutpat
pharetra. Quisque vulputate fringilla libero, a vestibulum ante mollis in. Quisque sodales purus ipsum, a mattis justo
rhoncus at.

#### Mauris Suscipit

Mauris suscipit rutrum ipsum quis maximus. Nulla facilisi. Etiam venenatis enim a ligula rutrum porta. Sed dictum nibh a
elit iaculis, quis volutpat tortor vehicula. Ut sit amet mi turpis. Aenean imperdiet ullamcorper velit, eu pharetra
magna rhoncus id. Curabitur id ornare ligula. Maecenas finibus dignissim nisl, eget feugiat lorem blandit nec.

D posts/002-aes-encryption.md => posts/002-aes-encryption.md +0 -86
@@ 1,86 0,0 @@
## Basic AES

In case this is your first time hearing about this, AES is known as the **Advanced Encryption Standard**. This
encryption was established by the National Institute of Standards and Technology, sub-selected from the Rijndael family
of ciphers (128, 192, and 256 bits) in 2001. Furthering its popularity and status, the US government chose AES as their
default encryption method for top-secret data, removing the previous standard which had been in place since 1977.

AES has proven to be an extremely safe encryption method, with 7-round and 8-round attacks making no material
improvements since the release of this encryption standard almost two decades ago.

> Though many papers have been published on the cryptanalysis of AES, the fastest single-key attacks on round-reduced AES variants [20, 33] so far are only slightly more powerful than those proposed 10 years ago [23,24].
> \- [Bogdonav, et al.](http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf)

## How Secure is AES?

In theory, AES-256 is uncrackable due to the massive number of combinations that can be produced. AES-128 is no longer
recommended if you're looking for real security to protect important data. A
semi-short [comic strip](http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html) from Moserware quickly
explains AES for the general public to understand. Basically AES encrypts the data by obscuring the relationship between
the data and the encrypted data. Additionally, this method spreads the message out. Lastly, the key produced by AES is
the secret to decrypting it. Someone may know the method of AES, but without the key, they are powerless.

To obscure and spread the data out, AES creates a substitution-permutation network. Wikipedia has a simple example of an
SP
network [in this image](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png)
. This network sends the data through a set of S boxes (using the unique key) to substitute the bits with another block
of bits. Then, a P box will permutate, or rearrange, the bits. This is done over and over, with the key being derived
from the last round. For AES, the key size specifies the number of transformation rounds: 10, 12, and 14 rounds for
128-bit, 192-bit, and 256-bit keys, respectively.

## The Process

1. KeyExpansion: Using [Rijndael's key schedule](https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard), the keys
   are dynamically generated.
2. AddRoundKey: Each byte of the data is combined with this key using bitwise xor.
3. SubBytes: This is followed by the substitution of each byte of data.
4. ShiftRows: Then, the final three rows are shifted a certain number of steps, dictated by the cipher.
5. MixColumns: After the rows have been shifted, the columns are mixed and combined.

After that happens - steps 2 through 5 repeat for the number of rounds specified by the key. However, the final round
excludes the MixColumns step. As you can see, this is a fairly complex process. One must have a solid understanding of
general mathematic principles to fully understand how the sequence works (and to even attempt to find a weakness).
According to research done by Bogdanov et al., it would take billions of years to brute force a 126-bit key with current
hardware. Additionally, this brute force attack would require storing 2^88 bits of data! However, there are a few
different attacks that have been used to show vulnerabilities with the use of this technology. Side-channel attacks use
inadvertent leaks of data from the hardware or software, which can allow attackers to obtain the key or run programs on
a user's hardware.

Warning: This is not something you should run out and try to implement in your Hello World app after only a few hours of
research. While AES is extremely efficient in what it does, it takes a lot of time and patience to understand. If you're
looking for something which currently implements AES, check
out [the Legion of the Bouncy Castle](https://www.bouncycastle.org/documentation.html). This Java package can help users
learn more about how to implement cryptography.

## Why Does Encryption Matter?

Why is encryption so important? A popular news story recently features the Australian parliament, where lawmakers are
going to let
officials [force companies build backdoors into their software](https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/)
. Most notably, messaging apps are expected to be ordered to do this. Now, we know that encrypted messages are encoded
and decoded using private keys, which are theoretically only known by the sender and receiver. How would a backdoor
allow the government to decrypt these messages when they issue a warrant? From what the law provides, *no one quite
knows how the government will be able to do this*.

Let's ignore the technical difficulties of creating backdoors into code that's been developed to be secure for a minute
here. How would this be possible on a day-to-day level? For most companies, the government would need to notify a
developer that they have a warrant and need the developer to implant a backdoor, so they can spy or do whatever they
want to do. Even if the developer does know how to do that, she would need to submit the code for review. Any reasonable
senior developer would see these code changes and would need to know why these were being made. More realistically, it
would probably be obvious that this code has malicious intent. Additionally, almost all code changes require change
tickets to be filed with the company before code changes can occur. Here's another stop on the paper trail that would
let the company know it's being hacked by its own government.

Supposedly, these warrants would ensure that the person who is being tapped to install the backdoor can not tell anyone
and will not face jail time for implementing this backdoor. What happens when your company finds out? You won't go to
jail, and you can't tell them why you did it. The only options are that they say "Hey, that's totally fine. Hack away."
or they fire you immediately. I'm going to let you decide which is more likely.

Apple, one of the biggest companies pushing for stronger encryption worldwide, has been known to outright refuse
governmental requests. With this new
law, [Apple may pull out the Australian market completely](https://tendaily.com.au/amp/news/australia/a181206zli/if-encryption-laws-go-through-australia-may-lose-apple-20181206)
. Hopefully, this law sees future amendments or complete reversal before people start losing their jobs or blackhats
take advantage of these forced exploits.

**Read More:**  
[Federal Information Processing Standards Publication 197 [.pdf]](http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf)

A posts/002-example-syntax.md => posts/002-example-syntax.md +83 -0
@@ 0,0 1,83 @@
## Syntax Possiblities

Please see the [GitHub Flavored Markdown specs](https://github.github.com/gfm/) and
the [parsedown](https://github.com/erusev/parsedown) repository for more specific details on Markdown possiblities.

---

Headings:

# Heading - Level 1

## Heading - Level 2

### Heading - Level 3

#### Heading - Level 4

##### Heading - Level 5

###### Heading - Level 6

---

Formatted Text:

**Bold text**  
*Italics*  
~~strikethrough~~  
[a simple link](https://example.com)

---

Blockquotes:

> # Foo
> bar
> baz

---

Lists:

1. one
2. two
3. three
    1. sub-bullet

- unordered one
- unordered two

---

Code Blocks:

```html
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
```

---

Tables:

| foo | bar |
| --- | --- |
| baz | bim |

---

Images:

![banner image](https://img.cleberg.io/share/github_banner.gif)

---
\ No newline at end of file

D posts/003-useful-css-snippets.md => posts/003-useful-css-snippets.md +0 -111
@@ 1,111 0,0 @@
## Introduction to CSS

CSS, the language used to markup HTML code and make it pretty, is one of the most effective ways to increase the
attractiveness of a website. It can also lead to increased customer retention and satisfaction. In fact, there are whole
career fields are dedicated to the improvement of user experiences, known as UI design and UX design.

Web developers are used to the common CSS code snippets, such as changing sizes, fonts, colors, etc. but are not as well
versed in less-used methods such as flex boxes (although I hope the flex properties are used everywhere before too long)
. This article will provide some insight into the less-used and unique CSS properties.

## CSS Variables

The first topic today is CSS variables. Variables are not often used by smaller developers. CSS variables allow you to
give your website a well-defined structure, where you can easily reuse CSS properties throughout the project. In
fact, [here's an example I posted to CodePen](https://codepen.io/christiancleberg/pen/wRXByZ) recently.

The example above uses variables to define my color palette. Then, I was able to use these colors for backgrounds
anywhere else in the HTML. This could be extended, where extra variables could be defined for "primary-text", "
quoted-text", etc. Variables can also be used to define spacing (e.g. 32px or 2rem), which can then be applied to
margins, padding, font sizes, and more.

## CSS Box Shadows

Box shadows were once my mortal enemy. No matter how hard I tried, I just couldn't get them to work how I wanted.
Because of this, my favorite discovery has been CSSMatic's [box shadow generator](https://www.cssmatic.com/box-shadow).
It provides an excellent tool to generate box shadows using their simple sliders. Surprisingly, this is the reason I
learned how box shadows work! You can use the sliders and watch how the CSS code changes in the image that is displayed.
Through this, you should understand that the basic structure for box shadows is:

```css
box-shadow: inset horizontal vertical blur spread color;
```

Now, let's look at some basic examples! You can copy and paste the following code into a site like CodePen or your own
HTML files. Feel free to play around with the code, experiment, and learn.

**Box Shadow #1**

```html
<div class="shadow-examples">
  <div class="box effect1">
    <h3>Effect 1</h3>
  </div>
</div>
```

```css
.box h3 { text-align: center; position: relative; top: 80px }
.box { width: 70%; height: 200px; background: #fff; margin: 40px auto }
.effect1 { box-shadow: 0 10px 6px -6px #777 }
```

**Box Shadow #2**

```html
<div class="shadow-examples">
  <div class="box effect2">
    <h3>Effect 2</h3>
  </div>
</div>
```

```css
.box h3 { text-align:center; position:relative; top:80px }
.box { width:70%; height:200px; background:#FFF; margin:40px auto }
.effect2 { box-shadow: 10px 10px 5px -5px rgba(0,0,0,0.75) }
```

## CSS Flexbox

Now, let's move on to the best part of this article: flexbox. The flexbox is by far my favorite new toy. I originally
stumbled across this solution after looking for more efficient ways of centering content horizontally AND vertically. I
had used a few hack-ish methods before, but flexbox throws those out the window. The best part of it all is that flexbox
is *dead simple*.

Flexbox pertains to the parent div of any element. You want the parent to be the flexbox in which items are arranged
using the flex methods. It's easier to see this in action that explained, so let's see an example.

**Flexbox**

```html
<div class="flex-examples">
  <div class="sm-box">
    <h3>1</h3>
  </div>
  <div class="sm-box">
    <h3>2</h3>
  </div>
</div>
```

```css
.flex-examples { display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; padding: 10px; background-color: #f2f2f2 }
.sm-box { display: flex; justify-content: center; align-items: center; width: 20%; height: 100px; background: #fff; margin: 40px 10px }
```

You may notice that we no longer need to use the `top` property for the `h3` elements in our code. This is because we
set the display box to be a flex container for the small boxes, AND we made the small boxes flex containers for their
elements (the h3 tags). Flex boxes can be nested like this to center content that is inside centered content.

For the example above, we designated the `justify-content` property to be `flex-start` so that the boxes stack from the
left side of the screen. This property can be changed to `center` to make the boxes appear in the center of the screen.

For an interactive example, [check out this CodePen](https://codepen.io/LandonSchropp/pen/KpzzGo)
from [LandonSchropp](https://codepen.io/LandonSchropp/). Resize the window with dice to see how they collapse and
re-align.

## Even More CSS

For more inspiration, you can [visit CodePen](https://www.codepen.io), [Dribbble](https://dribbble.com),
or [UI Movement](https://uimovement.com) to browse the collections of many amazing web designers.

D posts/004-data-analysis-in-auditing.md => posts/004-data-analysis-in-auditing.md +0 -150
@@ 1,150 0,0 @@
## What Are Data Analytics?

Put simply, data analysis is a process that utilizes statistics and other mathematical methods to discover useful
information within datasets. This involves examining, cleaning, transforming, and modeling data so that you can use the
data to support an opinion, create more useful viewpoints, and gain knowledge to implement into audit planning or risk
assessments.

One of the common mistakes that managers (and anyone new to the process) make is assuming that everything involved with
this process is "data analytics". In fact, data analytics is only a small part of the process. See **Figure 1** for a
more accurate representation of where data analysis sits within the full process. This means that data analytics do not
include querying or extracting data, selecting samples, or performing audit tests. These steps can be necessary for an
audit (and may even be performed by the same associates), but they are not data analytics.

![](https://img.cleberg.io/blog/004-data-analysis-in-auditing/intelligence_cycle-min.png)
*Figure 1: The Intelligence Cycle*

## Current Use of Analytics in Auditing

While data analysis has been an integral part of most businesses and departments for the better part of the last
century, only recently have internal audit functions been adopting this practice. The internal audit function works
exclusively to provide assurance and consulting services to the business areas within the firm (except for internal
auditing firms who are hired by different companies to perform their roles).

> Internal Auditing helps an organization accomplish its objectives by bringing a systematic, disciplined approach to evaluate and improve the effectiveness of risk management, control and governance processes.
> \- The IIA's Definition of Internal Audit

The fact that internal auditing is strongly based off tradition and following the precedents set by previous auditors is
partially to blame. However, there can be no progress without auditors who are willing to break the mold and test new
audit techniques. In fact, as of
2018 [only 63% of internal audit departments currently utilize data analytics](https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics)
in North America. This number should be as close as possible to 100%. I have never been part of an audit that could not
have benefited from data analytics.

So, how do internal audit functions remedy this? It's definitely not as easy and walking into work on Monday and telling
your Chief Audit Executive that you're going to start implementing analytics in the next audit. You need a plan and a
system to make the analytics process as effective as possible.

## The DELTA Model

One of the easiest ways to experiment with data analytics and gain an understanding of the processes is to implement
them within your own department. But how do we do this if we've never worked with analytics before? One of the most
common places to start is to research some data analysis models currently available. For this post, we'll take a look at
the DELTA model. You can take a look at **Figure 2** for a quick overview of the model.

The DELTA model sets a few guidelines for areas wanting to implement data analytics so that the results can be as
comprehensive as possible:

* **Data**: Must be clean, accessible, and (usually) unique.
* **Enterprise-Wide Focus**: Key data systems and analytical resources must be available for use (by the Internal Audit
  Function).
* **Leaders**: Must promote a data analytics approach and show the value of analytical results.
* **Targets**: Must be set for key areas and risks that the analytics can be compared against (KPIs).
* **Analysts**: There must be auditors willing and able to perform data analytics or else the system cannot be
  sustained.

![](https://img.cleberg.io/blog/004-data-analysis-in-auditing/delta-min.png)
*Figure 2: The DELTA Model*

## Finding the Proper KPIs

Once the Internal Audit Function has decided that they want to start using data analytics internally and have ensured
they're properly set up to do so, they need to figure out what they will be testing against. Key Performance
Indicators (KPIs) are qualitative or quantitative factors that can be evaluated and assessed to determine if the
department is performing well, usually compared to historical or industry benchmarks. Once KPIs have been agreed upon
and set, auditors can use data analytics to assess and report on these KPIs. This allows the person performing the
analytics the freedom to express opinions on the results, whereas the results are ambiguous if no KPIs exist.

It should be noted that tracking KPIs in the department can help ensure you have a rigorous Quality Assurance and
Improvement Program in accordance with some applicable standards, such as IPPF Standard 1300.

> The chief audit executive must develop and maintain a quality assurance and improvement program that covers all aspects of the internal audit activity.
> \- IPPF Standard 1300

Additionally, IPPF Standard 2060 discusses reporting:

> The chief audit executive must report periodically to senior management and the board on the internal audit activity's purpose, authority, responsibility, and performance relative to its plan and on its conformance with the Code of Ethics and the Standards. Reporting must also include significant risk and control issues, including fraud risks, governance issues, and other matters that require the attention of senior management and/or the board.
> \- IPPF Standard 2060

The hardest part of finding KPIs is to determine which KPIs are appropriate for your department. Since every department
is different and has different goals, KPIs will vary drastically between companies. To give you an idea of where to
look, here are some ideas I came up with when discussing the topic with a few colleagues.

* Efficiency/Budgeting:
    * Audit hours to staff utilization ratio (annual hours divided by total annual work hours).
    * Audit hours compared to the number of audits completed.
    * Time between audit steps or to complete the whole audit. E.g. time from fieldwork completion to audit report
      issuance.
* Reputation:
    * The frequency that management has requested the services of the IAF.
    * Management, audit committee, or external audit satisfaction survey results.
    * Education, experience, certifications, tenure, and training of the auditors on staff.
* Quality:
    * Number and frequency of audit findings. Assign monetary or numerical values, if possible.
    * Percentage of recommendations issued and implemented.
* Planning:
    * Percentage or number of key risks audited per year or per audit.
    * Proportion of audit universe audited per year.

## Data Analysis Tools

Finally, to be able to analyze and report on the data analysis, auditors need to evaluate the tools at their disposal.
There are many options available, but a few of the most common ones can easily get the job done. For example, almost
every auditor already has access to Microsoft Excel. Excel is more powerful than most people give it credit for and can
accomplish a lot of basic statistics without much work. If you don't know a lot about statistics but still want to see
some of the more basic results, Excel is a great option.

To perform more in-depth statistical analysis or to explore large datasets that Excel cannot handle, auditors will need
to explore other options. The big three that have had a lot of success in recent years are Python, R, and ACL. ACL can
be used as either a graphical tool (point and click) or as a scripting tool, where the auditor must write the scripts
manually. Python and the R-language are solely scripting languages. The trend in the data analytics environment is that
if the tool allows you to do everything by clicking buttons or dragging elements, you won't be able to fully utilize the
analytics you need. The most robust solutions are created by those who understand how to write the scripts manually. It
should be noted that as the utility of a tool increases, it usually means that the learning curve for that tool will
also be higher. It will take auditors longer to learn how to utilize Python, R, or ACL versus learning how to utilize
Excel.

## Visualization

Once an auditor has finally found the right data, KPIs, and tools, they must report these results so that actions can be
taken. Performing in-depth data analysis is only useful if the results are understood by the audiences of the data. The
best way to create this understanding is to visualize the results of the data. Let's take a look at some of the best
options to visualize and report the results you've found.

Some of the most popular commercial tools for visualization are Microsoft PowerBI and Tableau Desktop. However, other
tools exist such as JMP, Plotly, Qlikview, Alteryx, or D3. Some require commercial licenses while others are simply free
to use. For corporate data, you may want to make sure that the tool does not communicate any of the data outside the
company (such as cloud storage). I won't be going into depth on any of these tools since visualization is largely a
subjective and creative experience, but remember to constantly explore new options as you repeat the process.

Lastly, let's take a look at an example of data visualization. This example comes from
a [blog post written by Kushal Chakrabarti](https://talent.works/2018/03/28/the-science-of-the-job-search-part-iii-61-of-entry-level-jobs-require-3-years-of-experience/)
in 2018 about the percent of entry-level US jobs that require experience. **Figure 3** shows us an easy-to-digest
picture of the data. We can quickly tell that only about 12.5% of entry-level jobs don't require experience.

This is the kind of result that easily describes the data for you. However, make sure to include an explanation of what
the results mean. Don't let the reader assume what the data means, especially if it relates to a complex subject. *Tell
a story* about the data and why the results matter. For example, **Figure 4** shows a part of the explanation the author
gives to illustrate his point.

![](https://img.cleberg.io/blog/004-data-analysis-in-auditing/vis_example-min.png)
*Figure 3: Entry-Level Visualization*

![](https://img.cleberg.io/blog/004-data-analysis-in-auditing/vis_example_explanation-min.png)
*Figure 4: Visualization Explanation*

## The End

While this is not an all-encompassing program that you can just adopt into your department, it should be enough to get
anyone started on the process of understanding and implementing data analytics. Always remember to continue learning and
exploring new options as your processes grow and evolve.

D posts/005-the-ansoff-matrix.md => posts/005-the-ansoff-matrix.md +0 -101
@@ 1,101 0,0 @@
## Overview

As the world of business evolves, managers must approach business planning and strategy with a contemporary mindset.
According to Dess, McNamara, Eisner, and Lee, managers must be willing to adapt to the modern business environment by
going beyond "'incremental management', whereby they view their job as making a series of small, minor changes to
improve the efficiency of the firm's
operations" [\[1\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references). One reason that strategic
management is crucial is because most businesses that fail in the United States each year fail due to a lack of
strategic focus or direction [\[2\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references). The rate of
failure for businesses with poor strategies shows that strategic planning and management are crucial to a business's
strength and longevity, injecting the critical factors of growth and direction into a company's business plan.

One of the most significant strategic planning and management frameworks that companies can use is the Ansoff Matrix.
While this framework has unique purposes and use-cases, it can effectively help an organization grow and compete.
Specifically, the Ansoff matrix is one of the most effective frameworks for companies who want to focus on increasing
sales revenue or profitability [\[3\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references).

This framework uses a two-by-two figure to show the four strategic options for companies to use in this framework:
market penetration, market development, product development, and diversification (see **Figure 1**). The x-axis of the
matrix focuses on the firm's markets and also determines if the firm is looking to enter new markets or innovate in its
current markets. The y-axis of the matrix focuses on the firm's products and determines if the firm wants to pursue
strategies around their existing products or explore new products.

![](https://img.cleberg.io/blog/005-the-ansoff-matrix/ansoff_matrix-min.png)
*Figure 1: The Ansoff Matrix*

## Market Penetration

The most straightforward strategy in the Ansoff matrix is to focus on existing products in existing markets, also known
as market penetration [\[3\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references). Companies such as
Coca-Cola have used market penetration successfully by investing a lot of money to get further value out of their
current markets. Coca-Cola does this by introducing new features such as Christmas-themed bottles, personal names on the
bottles, and other marketing schemes.

## Market Development

market development extends existing products into new markets in an attempt to increase the number of buyers. One
interesting way that Coca-Cola used this strategy comes from the stigma that Diet Coke is a woman's
drink [\[4\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references). Coca-Cola introduced Coca-Cola Zero,
which contained the same nutritional content as Diet Coke, but was packaged in a dark black can to appear more "
manly" [\[4\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references).

## Product Development

Product development uses existing markets to introduce new products so that the firm can better meet customer
needs [\[4\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references). The extreme end of diversification is
home to companies such as Johnson & Johnson, a healthcare company that has developed a business portfolio of more than
60,000 different products [\[5\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references). Johnson &
Johnson's dedication to continuous diversification has led them to a balance sheet rating of "AAA", industry recognition
for diversification, and increases in their investor dividends for 57 consecutive
years [\[6\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references).

## Related Diversification

Diversification, the final strategy of the Ansoff Matrix, is more difficult than the others since it involves exploring
both new markets and new products. Related diversification is a diversification strategy that closely relates to the
firm's core business. Coca-Cola's best example of related diversification is its acquisition of Glaceau and Vitamin
Water, which expanded their drinking lines of
business [\[4\]](https://blog.cleberg.io/article/the-ansoff-matrix.html#references).

## Unrelated Diversification

Unrelated diversification is a diversification strategy that does not really relate to the firm's core business but
still diversifies their business portfolio. A good example of this would be a coffee company who has decided to enter
the market for bicycle sales. The main purpose of this strategy is to an extremely diverse company that will not go
bankrupt if one market goes through difficult times. However, this requires a lot of independent skill and heavy
investments since the company most likely cannot easily transfer knowledge between the markets they compete in.

## Requirements for Success

To use the Ansoff Matrix framework, managers need to formulate corporate goals and objectives. Without goals and
direction, management frameworks do not present much practical utility. Further, the Ansoff Matrix requires the managers
involved to make tactical decisions and create a path for the company to take toward their goals. Lastly, both the
Ansoff Matrix needs to consider both internal and external perspectives throughout the strategy formulation process.

One interesting probability is that companies will be using multiple strategic planning and management frameworks at the
same time. While this may sound like it could crowd the management process, there are numerous reasons to do so. For
example, the Ansoff Matrix and the Balanced Scorecard are relatively popular and they cover entirely different parts of
a company's strategy. Using the results from the Balanced Scorecard could inform a company of the potential product and
market demands, such as from customer or supplier survey results, to help the company determine which Ansoff Matrix
strategy to pursue. However, a combined approach at this level would require mature frameworks and focused managers who
are able to strategize at a high level.

Lastly, it should be noted that the author of the Ansoff matrix, Igor Ansoff, often used the
term [paralysis by analysis](https://en.wikipedia.org/wiki/Analysis_paralysis) to explain the mistake of companies who
overuse analysis and spend too much time planning. Companies need to understand the utility of a strategic management
framework while ensuring that the company is poised to execute as efficiently as they have planned.

### References

[1] Dess, G. G., McNamara, G., Eisner, A. B., Lee, S. H. (2019). Strategic management: Text & cases, ninth edition. New
York, NY: McGraw-Hill Education.  
[2] Juneja, P. (n.d.). Benefits of strategic management. Management Study Guide. Retrieved
from https://www.managementstudyguide.com/strategic-management-benefits.htm  
[3] Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing Concepts. London: Palgrave.  
[4] Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda. Retrieved
from https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/  
[5] Lemke, T. (2019). The most diversified companies in the stock market. The balance. Retrieved
from https://www.thebalance.com/the-most-diversified-companies-in-the-stock-market-4169730  
[6] Johnson & Johnson. (2018). 2018 Investor Fact Sheet. [PDF file]. Retrieved
from http://www.investor.jnj.com/_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000

D posts/006-password-security.md => posts/006-password-security.md +0 -75
@@ 1,75 0,0 @@
## Users

### Why does it matter?

Information security, including passwords and identities, has become one of the most important digital highlights of the
last decade.
With [billions of people affected by data breaches every year](https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/)
, there's a greater need to introduce strong information security systems. If you think you've been part of a breach, or
you want to check and see, you can use [Have I Been Pwned](https://haveibeenpwned.com/) to see if your email has been
involved in any public breaches. Remember that there's a possibility that a company experienced a breach and did not
report it to anyone.

### How do I protect myself?

The first place to start with any personal security check-up is to gather a list of all the different websites, apps, or
programs that require you to have login credentials. Optionally, once you know where your information is being stored,
you can sort the list from the most-important items such as banks or government logins to less important items such as
your favorite meme site. You will want to ensure that your critical logins are secure before getting to the others.

Once you think you have a good idea of all your different authentication methods, I recommend using a password manager
such as [Bitwarden](https://bitwarden.com/). Using a password manager allows you to automatically save your logins,
create randomized passwords, and transfer passwords across devices. However, you'll need to memorize your "master
password" that allows you to open the password manager. It's important to make this something hard to guess since it
would allow anyone who has it to access every password you've stored in there.

Once you've stored your passwords, make sure you continually check-up on your account and make sure you aren't following
bad password practices. Krebs on Security has a [great blog post](https://krebsonsecurity.com/password-dos-and-donts/)
on password recommendations. Any time that a data breach happens, make sure you check to see if you were included and if
you need to reset any account passwords.

## Developers

### What are the basic requirements?

When developing any password-protected application, there are a few basic rules that anyone should follow even if they
do not follow any official guidelines such as NIST. The foremost practice is to require users to use passwords that are
8 characters or long and cannot easily be guessed. This sounds extremely simple, but it requires quite a few different
strategies. First, the application should check the potential passwords against a dictionary of insecure passwords
such `password`, `1234abc`, or `application_name`.

Next, the application should offer guidance on the strength of passwords being entered during enrollment. Further, NIST
officially recommends **not** implementing any composition rules that make passwords hard to remember (e.g. passwords
with letters, numbers, and special characters) and instead encouraging the use of long phrases which can include spaces.
It should be noted that to be able to keep spaces within passwords, all unicode characters should be supported and
passwords should not be truncated.

### What does NIST recommend?

The National Institute of Standards and Technology ([NIST](https://www.nist.gov)) in the US Department of Commerce
regularly publishes information around information security and digital identity guidelines. Recently, NIST
published [Special Publication 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html): Digital Identity Guidelines and
Authentication and Lifecycle Management.

> A Memorized Secret authenticator ? commonly referred to as a password or, if numeric, a PIN ? is a secret value intended to be chosen and memorized by the user. Memorized secrets need to be of sufficient complexity and secrecy that it would be impractical for an attacker to guess or otherwise discover the correct secret value. A memorized secret is something you know.
> \- NIST Special Publication 800-63B

NIST offers a lot of guidance on passwords, but I'm going to highlight just a few of the important factors:

* Require passwords to be a minimum of 8 characters (6 characters if randomly generated and be generated using an
  approved random bit generator).
* Compare potential passwords against a list that contains values known to be commonly-used, expected, or compromised.
* Offer guidance on password strength, such as a strength meter.
* Implement a rate-limiting mechanism to limit the number of failed authentication attempts for each user account.
* Do not require composition rules for passwords and do not require passwords to be changed periodically (unless
  compromised).
* Allow pasting of user identification and passwords to facilitate the use of password managers.
* Allow users to view the password as it is being entered.
* Use secure forms of communication and storage, including salting and hashing passwords using a one-way key derivation
  function.

NIST offers further guidance on other devices that require specific security policies, querying for passwords, and more.
All the information discussed so far comes
from [NIST Special Publication 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) but NIST offers a lot of
information on digital identities, enrollment, identity proofing, authentication, lifecycle management, federation, and
assertions in the total [NIST SP 800-63: Digital Identity Guidelines](https://pages.nist.gov/800-63-3/).

D posts/007-the-best-linux-software.md => posts/007-the-best-linux-software.md +0 -246
@@ 1,246 0,0 @@
## GUI Applications

---

### [Etcher](https://github.com/balena-io/etcher)

![Etcher example](https://img.cleberg.io/blog/007-the-best-linux-software/etcher.png "Etcher example")

[Etcher](https://www.balena.io/etcher/) is a quick and easy way to burn ISO images to CDs and USB devices. There are two
different ways you can install this program. First, you can navigate to
the [official website](https://www.balena.io/etcher/) and download the AppImage file, which can run without
installation.

However, AppImage files are not executable by default, so you'll either need to right-click to open the properties of
the file and click the "Allow executing file as program" box in the Permissions tab or use the following command:

```bash
chmod u+x FILE_NAME
```

If you don't like AppImage files or just prefer repositories, you can use the following commands to add the author's
repository and install it through the command-line only.

First, you'll have to echo the repo and write it to a list file:

```bash
echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
```

Next, add the application keys to Ubuntu's keyring:

```bash
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
```

Finally, update the repositories and install the app.

```bash
sudo apt update && sudo apt install balena-etcher-electron
```

Using Arch, Manjaro, or another distro using the AUR? Use this command instead:

```bash
sudo pacman -S etcher
```

---

### [Atom](https://atom.io)

![Atom example](https://img.cleberg.io/blog/007-the-best-linux-software/atom.png "Atom example")

[Atom](https://atom.io) is the self-proclaimed "hackable text editor for the 21st century". This text editor is made by
GitHub, [now owned by Microsoft](https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/),
and has some of the best add-ons available to customize the layout and abilities of the app.

First, add the Atom repository to your sources.

```bash
sudo add-apt-repository ppa:webupd8team/atom
```

Next, update your package listings and install atom.

```bash
sudo apt update && sudo apt install atom
```

If you have issues updating your packages with the Atom repository, you'll need use the snap package described below
instead of the repository. To remove the repository we just added, use this command:

```bash
sudo add-apt-repository -r ppa:webupd8team/atom
```

You can also install Atom as a snap package, but it must be installed with the `--classic` flag. A full explanation
of [classic snaps is available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement), if you'd
like to read more about why you need the classic flag.

```bash
snap install atom --classic
```

Using Arch, Manjaro, or another distro using the AUR? Use this command instead:

```bash
sudo pacman -S atom
```

---

### [Visual Studio Code](https://code.visualstudio.com)

![Visual Studio Code example](https://img.cleberg.io/blog/007-the-best-linux-software/vscode.png "Visual Studio Code example")

[Visual Studio Code](https://code.visualstudio.com) is yet another fantastic choice for programming on Linux, especially
if you need those extra add-ons to spice up your late-night coding sessions. The theme used in the screenshot
is [Mars](https://marketplace.visualstudio.com/items?itemName=EliverLara.mars) by
themer [Eliver Lara](https://github.com/EliverLara), who makes a ton of great themes for VS Code, Atom, and various
Linux desktop environments.

To install VS Code, you'll need to download the `.deb` file from the [offical website](https://code.visualstudio.com).
Once you've downloaded the file, either double-click it to install through the Software Center or run the following
command:

```bash
sudo dpkg -i FILE_NAME.deb
```

You can also install VS Code as a snap package, but it must be installed with the `--classic` flag. A full explanation
of [classic snaps is available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement), if you'd
like to read more about why you need the classic flag.

```bash
snap install code --classic
```

Using Arch, Manjaro, or another distro using the AUR? Use these commands instead:

```bash
sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
```

---

### [GNOME Tweaks](https://gitlab.gnome.org/GNOME/gnome-tweaks)

![Gnome Tweaks example](https://img.cleberg.io/blog/007-the-best-linux-software/gnome-tweaks.png "Gnome Tweaks example")

[GNOME Tweaks](https://gitlab.gnome.org/GNOME/gnome-tweaks) is the ultimate tool to use if you want to customize your
GNOME desktop environment. This is how you can switch application themes (GTK), shell themes, icons, fonts, and more. To
install GNOME Tweaks on Ubuntu, you just need to install the official package.

```bash
sudo apt install gnome-tweaks
```

If you've installed Manjaro or Arch with Gnome, you should have the tweak tool pre-installed. If you're on Fedora, this
tool is available as an official package:

```bash
sudo dnf install gnome-tweaks
```

---

### [Steam](https://steampowered.com)

![Steam example](https://img.cleberg.io/blog/007-the-best-linux-software/steam.png "Steam example")

[Steam](https://steampowered.com) is one of the most popular gaming libraries for computers and is one of the main
reasons that many people have been able to switch to Linux in recent years, thanks to Steam Proton which makes it easier
to play games not officially created for Linux platforms.

To install Steam on Ubuntu, you just need to install the official package.

```bash
sudo apt install steam-installer
```

For Arch-based systems, you'll simply need to install the `steam` package. However, this requires that you enable
the `multilib` source. To do so, use the following command:

```bash
sudo nano /etc/pacman.conf
```

Now, scroll down and uncomment the `multilib` section.

```editorconfig
# Before:
#[multilib]
#Include = /etc/pacman.d/mirrorlist

# After:
[multilib]
Include = /etc/pacman.d/mirrorlist
```

Finally, install the program:

```bash
sudo pacman -S steam
```

[Problem Launching Steam Games? Click Here.](https://blog.cleberg.io/article/steam-on-ntfs-drives.html)

---

## Command-Line Packages

### [neofetch](https://github.com/dylanaraps/neofetch)

![neofetch example](https://img.cleberg.io/blog/007-the-best-linux-software/neofetch.png "neofetch example")

[Neofetch](https://github.com/dylanaraps/neofetch) is a customizable tool used in the command-line to show system
information. This is exceptionally useful if you want to see your system's information quickly without the clutter of
some resource-heavy GUI apps.

This is an official package if you're running Ubuntu 17.04 or later, so simply use the following command:

```bash
sudo apt install neofetch
```

If you're running Ubuntu 16.10 or earlier, you'll have to use a series of commands:

```bash
sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
```

Using Arch, Manjaro, or another distro using the AUR? Use this command instead:

```bash
sudo pacman -S neofetch
```

---

### [youtube-dl](https://github.com/ytdl-org/youtube-dl/)

![youtube-dl example](https://img.cleberg.io/blog/007-the-best-linux-software/youtube-dl.png "youtube-dl example")

[youtube-dl](https://github.com/ytdl-org/youtube-dl/) is an extremely handy command-line tool that allows you to
download video or audio files from various websites, such as YouTube. There are a ton of different options when running
this package, so be sure to run `youtube-dl --help` first to look through everything you can do.

While this shouldn't be a problem for most users, youtube-dl requires Python 2.6, 2.7, or 3.2+ to work correctly so
install Python if you don't have it already. You can check to see if you have Python installed by running:

```bash
python -V
```

To get the youtube-dl package, simply curl the URL and output the results.

```bash
sudo curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
```

Finally, make the file executable so that it can be run from the command-line.

```bash
sudo chmod a+rx /usr/local/bin/youtube-dl
```

D posts/008-steam-on-ntfs-drives.md => posts/008-steam-on-ntfs-drives.md +0 -75
@@ 1,75 0,0 @@
## Auto-Mount Steam Drives

![Steam example](https://img.cleberg.io/blog/007-the-best-linux-software/steam.png "Steam example")

If you want to see how to install Steam on Linux, see my other
post: [The Best Linux Software](https://blog.cleberg.io/article/the-best-linux-software.html).

Are you having trouble launching games, even though they've installed correctly? This may happen if you're storing your
games on an nfts-formatted drive. This shouldn't be an issue if you're storing your games on the same drive that Steam
is on, but some gamers prefer to put Steam on their main drive and game files on another SSD or HDD.

To fix this problem, you'll need to try a few things. First, you'll need to install the `ntfs-3g` package, which is
meant for better interoperability with Linux.

```bash
sudo apt install ntfs-3g
```

Next, you should set up the `/etc/fstab` file to automatically mount your drives on boot. To automatically mount your
drives when the computer boots up, you'll have to create the folders you want to mount your drive to first. I store mine
in the `/mnt` folder using names that I'll recognize, but you can create your folders wherever you want.

```bash
mkdir /path/to/folder
```

For example:

```bash
mkdir /mnt/SteamDrive
```

Next, open the `/etc/fstab` file:

```bash
sudo nano /etc/fstab
```

Each drive you want to mount on boot should have its own line in the `/etc/fstab` file that looks similar to this:

```bash
UUID=B64E53824E5339F7 /path/to/folder ntfs-3g uid=1000,gid=1000 0 0
```

The UUID is the identification number connected to whichever drive you're using to store Steam games. To find your UUID,
run this command:

```bash
sudo blkid | grep UUID=
```

Drives are usually labeled similar to `/dev/nvme0n1p1` or `/dev/sda1`, so you'll need to find the line in the output
that correlates to your drive and copy the UUID over to the `/etc/fstab` file. Finally, make sure you've added
your `uid` and `gid` to the end of the configuration line in the `/etc/fstab` file. To find these, run the following
command:

```bash
id -u && id -g
```

Now all you need to do is unmount your drive and re-mount it. You can unmount the drive by doing this (be sure to use
the correct drive name here):

```bash
sudo umount /dev/sdxX
```

You can re-mount all your drives by executing the following:

```bash
sudo mount -a
```

If you don't know what your drive name is, or you're nervous about un-mounting and re-mounting, simply reboot your
computer, and it will be done for you automatically.

D posts/009-cryptography-basics.md => posts/009-cryptography-basics.md +0 -121
@@ 1,121 0,0 @@
**Similar Article Available**

If you haven't already, read my previous article on [AES Encryption](https://blog.cleberg.io/post/aes-encryption.html)

## What is Cryptography?

In layman's terms, cryptography is a process that can change data from a readable format into an unreadable format (and
vice-versa) through a series of processes and secrets. More technically, this is the Internet Security Glossary's
definition:

> [Cryptography is] the mathematical science that deals with transforming data to render its meaning unintelligible (i.e., to hide its semantic content), prevent its undetected alteration, or prevent its unauthorized use. If the transformation is reversible, cryptography also deals with restoring encrypted data to intelligible form.
> \- [Internet Security Glossary (2000)](https://tools.ietf.org/html/rfc2828)

Cryptography cannot offer protection against the loss of data, it simply offers encryption methods to protect data
at-rest and data in-traffic. At a high-level, encrypted is when plaintext data is encrypted to ciphertext (a secure form
of text that cannot be understood unless decrypted back to plaintext). The encryption process is completed through the
use of a mathematical function that utilizes one or more values called keys to encrypt/decrypt the data.

## Key Elements of Cryptographic Systems

To create or evaluate a cryptographic system, you need to know the essential pieces to the system:

* **Encryption Algorithm (Primitive):** A mathematical process that encrypts and decrypts data.
* **Encryption Key:** A string of bits used within the encryption algorithm as the secret that allows successful
  encryption or decryption of data.
* **Key Length (Size):** The maximum number of bits within the encryption key. It's important to remember that key size
  is regulated in many countries.
* **Message Digest:** A smaller, fixed-size bit string version of the original message. This is practically infeasible
  to reverse, which is why it's commonly used to verify integrity.

## Symmetric Systems (Secret Key Cryptography)

Symmetric cryptography utilizes a secret, bidirectional key to perform both encryption and decryption of the data. The
most common implementation of symmetric cryptography is
the [Advanced Encryption Standard](https://blog.cleberg.io/article/aes-encryption.html), which uses keys that are 128
bits to 256 bits in size. This standard came after the National Institute of Standards and Technology (NIST) decided to
retire the Data Encryption Standard (DES) in 2001.

Since brute force attacks strongly correlate with key length, the 56-bit key length of DES was considered insecure after
it was publicly broken in under 24 hours. However, there is a modern implementation of DES called Triple DES where the
DES method is applied three times to each data block.

The main advantages to symmetric systems are the ease of use, since only one key is required for both encryption and
decryption, and the simplicity of the algorithms. This helps with bulk data encryption that may unnecessarily waste time
and power using asymmetric systems.

However, symmetric systems have disadvantages to keep in mind. Since the key is private, it can be difficult to safely
distribute keys to communication partners. Additionally, the key cannot be used to sign messages since it's necessary to
keep the key private.

## Asymmetric Systems (Public Key Cryptography)

Asymmetric cryptography utilizes two keys within the system: a secret key that is privately-held and a public key that
can be distributed freely. The interesting aspect of asymmetric cryptography is that either key can be used to encrypt
the data, there's no rule that dictates which key must be used for encryption. Once one key is used to encrypt the data,
only the other key can be used to decrypt the data. This means that if the private key encrypts the data, only the
public key can decrypt the data.

An advantage of this system is that if you successfully decrypt data using one of the keys, you can be sure of the
sender since only the other key could have encrypted the data.

One of the major implementations of an asymmetric system is a digital signature. A digital signature can be generated
using the sender's private key, or a one-way hash function and is used to provide assurance for integrity and
authenticity of the message. A couple common message digest algorithms are SHA-256 and SHA-512, which securely compress
data and produce a 128-bit message digest.

It should be noted that man-in-the-middle attacks are one of the risks with digital signatures and public keys. To
combat this, applications often use a public key infrastructure (PKI) to independently authenticate the validity of
signatures and keys.

Due to the large key size and [inefficient mathematical functions](https://crypto.stackexchange.com/a/591) of asymmetric
encryption, elliptical curve cryptography (ECC) is often used to increase security while using fewer resources.

## Applications of Cryptographic Systems

**Transport Layer Security (TLS):** One of the most famous cryptographic solutions created is TLS, a session-layered or
connection-layered internet protocol that allows for secure communications between browsers and servers. Using
handshakes, peer negotiation, and authentication allows TLS to prevent eavesdropping and malicious transformation of
data. The major reason for TLS popularity is that a major vulnerability was found in the SSL protocol in 2014. Instead
of SSL, TLS can be used with HTTP to form HTTPS and is the preferred method for modern web development due to its
increased security.

**Secure Hypertext Transfer Protocol (HTTPS):** An application layer protocol that allows for secure transport of data
between servers and web clients. One of the unique parts of HTTPS is that it uses a secured port number instead of the
default web port address.

**Virtual Private Network (VPN):** VPNs are made to securely extend a private network across public networks by
utilizing an encrypted layered tunneling protocol paired with an authentication method, such as usernames and passwords.
This technology originally allowed remote employees to access their company's data but have evolved into one of the top
choices for anyone who wishes to mask their sensitive personal data.

**Internet Protocol Security (IPSec):** This protocol suite facilitates communication between two or more hosts or
subnets by authenticating and encrypting the data packets. IPSec is used in a lot of VPNs to establish the VPN
connection through the transport and tunnel mode encryption methods. IPSec encrypts just the data portion of packets in
the transport methods, but it encrypts both the data and headers in the tunnel method (introducing an additional header
for authentication).

**Secure Shell (SSH):** SSH is another network protocol used to protect network services by authenticating users through
a secure channel. This protocol is often used for command-line (shell) functions such as remote shell commands, logins,
and file transfers.

**Kerberos:** Developed by MIT, Kerberos is a computer-network authentication protocol that works on the basis of
tickets to allow nodes communicating over a non-secure network to prove their identity to one another securely. This is
most commonly used in business environments when used as the authentication and encryption method for Windows Active
Directory (AD).

## Cybersecurity Controls

**Policies:** A policy on the use of cryptographic controls for protection of information is implemented and is in
accordance with organizational objectives.

**Key management:** A policy on the use, protection and lifetime of cryptographic keys is implemented through the entire
application lifecycle.

**Key size:** The organization has researched the optimal key size for their purposes, considering national laws,
required processing power, and longevity of the solution.

**Algorithm selection:** Implemented algorithms are sufficiently appropriate for the business of the organization,
robust, and align with recommended guidelines.

**Protocol configuration:** Protocols have been reviewed and configured suitable to the purpose of the business.

D posts/010-session-private-messenger.md => posts/010-session-private-messenger.md +0 -87
@@ 1,87 0,0 @@
**Privacy Warning**

The company behind Session (Loki Foundation) is from Australia. If you didn't know, Australia has
introduced [legislation](https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf)
mandating companies comply with government requests to build backdoor access into applications. For more information,
[see my article on AES Encryption](https://blog.cleberg.io/post/aes-encryption.html)

## About Session

[Session](https://getsession.org) is a private, cross-platform messaging app from
the [Loki Foundation](https://loki.foundation). As someone who has spent years looking for quality alternatives to major
messaging apps, I was excited when I first heard about Session. Reading
through [Session's whitepaper](https://arxiv.org/pdf/2002.04609.pdf), you can learn the technologies behind the Session
app. Part of the security of Session comes from the Signal protocol, which was forked as the origin of Session.

> Session is an end-to-end encrypted messenger that removes sensitive metadata collection, and is designed for people who want privacy and freedom from any forms of surveillance.

In general, this app promises security through end-to-end encryption, decentralized onion routing, and private
identities. The biggest change that the Loki Foundation has made to the Signal protocol is removing the need for a phone
number. Instead, a random identification string is generated for any session you create. This means you can create a new
session for each device, if you want to, or link new devices with your ID.

Since Session's website and whitepaper describe the details of Session's security, I'm going to focus on using the app
in this post.

## Features

Since most people are looking for an alternative to a popular chat app, I am going to list out the features that Session
has so that you are able to determine if the app would suit your needs:

* Multiple device linking (via QR code or ID)
* App locking via device screen lock, password, or fingerprint
* Screenshot blocking
* Incognito keyboard
* Read receipts and typing indicators
* Mobile notification customization
* Old message deletion and conversation limit
* Backups
* Recovery phrase
* Account deletion, including ID, messages, sessions, and contacts

## Downloads

I tested the Session app on Linux (Ubuntu 19.10) and Android 10. Below is a brief overview of the Session app on Linux.
To get this app, you'll need to [go to the Downloads pages](https://getsession.org/download/) and click to link to the
operating system you're on. For Linux, it will download an AppImage that you'll need to enable with the following
command:

```bash
sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
```

![](https://img.cleberg.io/blog/010-session/session-downloads.png)
*Fig.1 - Session Downloads*

## Creating an Account

Once you've installed the app, simply run the app and create your unique Session ID. It will look something like
this: `05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608`

You'll need to set a display name and, optionally, a password. If you set a password, you will need to enter it every
time you open the app.

![](https://img.cleberg.io/blog/010-session/session-login.png)
*Fig.2 - Session Login*

![](https://img.cleberg.io/blog/010-session/session-password.png)
*Fig.3 - Password Authentication*

## Start Messaging

Once you've created your account and set up your profile details, the next step is to start messaging other people. To
do so, you'll need to share your Session ID with other people.
![](https://img.cleberg.io/blog/010-session/session-friend-request.png)
*Fig.4 - Friend Requests*

![](https://img.cleberg.io/blog/010-session/session-conversation.png)
*Fig.5 - Conversations*

## Potential Issues

I've discovered one annoying issue that I believe is connected to the Signal Protocol. On a mobile device, there have
been issues with receiving messages on time. Even with battery optimization disabled and no network restrictions,
Session notifications sometimes do not display until I open the app or the conversation itself and wait a few moments.
This is actually one of the reasons I stopped using Signal.

Looking for another messenger instead of Session? I recommend Signal, Matrix, and IRC.

D posts/011-homelab.md => posts/011-homelab.md +0 -109
@@ 1,109 0,0 @@
## What is a homelab?

Starting as a developer, I have largely stayed away from hardware-based hobbies (other than building a gaming desktop).
However, as the quarantine for COVID-19 stretches out further and further, I found myself bored and in search of new
hobbies. After spending the last few months browsing the [r/homelab](https://www.reddit.com/r/homelab/) subreddit, I
decided it was time to jump in and try things out for myself.

Since I am a beginner and just recently graduated college, everything I've done so far in my homelab is fairly
low-budget.

## My Hardware

![My homelab](https://img.cleberg.io/blog/011-homelab/homelab-min.png "Mhttps://img.cleberg.io/blogy homelab")

#### Raspberry Pi 4

Luckily, I had actually purchased a [Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)
before the quarantine started so that I could try to keep Plex Media Center running, even while my desktop computer was
turned off. I started here, using the Pi to hold Plex and Pi-hole until I grew tired with the slow performance.

Here are the specifications for the Pi 4:

* Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
* 4GB LPDDR4-3200 SDRAM
* Gigabit Ethernet
* H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
* 64 GB MicroSD Card

#### Dell Optiplex 5040

Since I wasn't happy with the Pi as my main server, I turned to Craigslist. I know a lot of other homelabbers use Ebay,
but I can't seem to ever trust it enough to purchase items on there. So I used Craigslist and found a Dell Optiplex 5040
desktop computer on sale for $90. While this computer might be underpowered, it was one of the few computers under $100
that was available during quarantine.

Here are the specifications for the Dell Optiplex 5040:

* Intel Core i3 6100
* 8GB RAM DDR3
* Intel HD Graphics
* Gigabit Ethernet
* 500GB Hard Drive

While this hardware would be awful for a work computer or a gaming rig, it turned out to be wonderful for my server
purposes. The only limitation I have found so far is the CPU. The i3-6100 only has enough power for a single 4k video
transcode at a time. I haven't tested more than three 1080p streams at a time, but the maximum amount of streams I've
ever actually used is two.

#### WD easystore 10TB

Application storage and temporary files are stored on the internal hard drive of the server, but all media files (
movies, tv, games, books, etc) are stored externally on my WD easystore hard drive. Creating an auto-boot line in
the `/etc/fstab` file on my server allows the hard drive to automatically mount whenever I need to restart my server.

#### Netgear Unmanaged Switch

To manage all the ethernet cords used by my homelab, my desktop, and my living room media center, I purchased an 8-port
gigabit ethernet switch for $50 at my local computer store. This is probably much more than I should have spent on an
unmanaged switch, but I am comfortable with the choice.

#### Arris TM1602A Modem & Sagecom Fast 5280 Router

My default modem and router, provided by my ISP, are fairly standard. The Arris modem supports DOCSIS 3.0, which is
something that I definitely wanted as a minimum. The Sagecom router is also standard, no fancy bells or whistles.
However, it does support DHCP and DHCPv6, which is something you can use to route all household traffic through a
Pi-hole or firewall.

#### Brother MFC-J480DW Printer

The last piece to my homelab is a standard wireless printer. Nothing special here.

## Software

#### Ubuntu Server 20.04

While the 20.04 version of Ubuntu was just released, I always like to experiment with new features (and I don't mind
breaking my system - it just gives me more experience learning how to fix things). So, I have Ubuntu Server 20.04
installed on the Dell Optiplex server and Ubuntu Server 19.10 installed on the Raspberry Pi. Once I find an acceptable
use for the Pi, I will most likely switch the operating system.

#### Docker

I am *very* new to Docker, but I have had a lot of fun playing with it so far. Docker is used to create containers that
can hold all the contents of a system without interfering with other software on the same system. So far, I have
successfully installed Pi-hole, GitLab, Gogs, and Nextcloud in containers. However, I opted to delete all of those so
that I can reconfigure them more professionally at a later time.

#### Plex Media Server

Plex is a media center software that allows you to organize your movies, TV shows, music, photos, and videos
automatically. It will even download metadata for you so that you can easily browse these collections.

#### Pi-hole

Pi-hole is an alternative ad-blocker that runs at the DNS level, allowing you to block traffic when it hits your
network, so that you can reject any traffic you deem to be bad. Pi-hole uses blacklists and whitelists to decide which
traffic block and, luckily, there are a lot of pre-made lists out there on Reddit, GitHub, etc.

#### Nextcloud

While I had trouble with the Docker version of Nextcloud, I was very successful when setting up the snap version. Using
this, I was able to map Nextcloud to a subdomain of a domain I own in Namecheap. Additionally, Nextcloud has an
integration with Let's Encrypt that allows me to issue certificates automatically to any new domain I authorize.

#### Webmin

To monitor my servers, and the processes running on them, I use the Webmin dashboard. This was fairly painless to set
up, and I currently access it straight through the server's IP address. In the future, I will be looking to configure
Webmin to use a custom domain just like Nextcloud.

D posts/012-customizing-ubuntu.md => posts/012-customizing-ubuntu.md +0 -165
@@ 1,165 0,0 @@
**More Information**

For inspiration on designing your *nix computer, check out the [/r/unixporn](https://reddit.com/r/unixporn) subreddit!

## Customizing Ubuntu

New to Linux and want to add a personal touch to your machine? One of the best perks of Linux is that it is **insanely**
customizable. You can change the styles of the windows, shell (status bars/docks), icons, fonts, terminals, and more.

In this post, I'm going to go through customization on Ubuntu 20.04 (GNOME) since most new users tend to choose
Ubuntu-based distros. If you've found a way to install Arch with i3-gaps, I'm assuming you know how to find more
advanced tutorials out there on customizations.

### Required Tools

![GNOME Tweaks](https://img.cleberg.io/blog/012-customizing-ubuntu/gnome-tweaks-min.png "GNOME Tweaks")

Ubuntu 20.04 ships with the default desktop environment [GNOME](https://www.gnome.org/), which includes the
handy `gnome-tweaks` tool to quickly change designs. To install this, just open your terminal and enter the following
command:

```bash
sudo apt install gnome-tweaks
```

After you've finished installing the tool, simply launch the Tweaks application, and you'll be able to access the
various customization options available by default on Ubuntu. You might even like some of the pre-installed options.

### GNOME Application Themes

To change the themes applied to applications in GNOME, you will need to change the Applications dropdown in the
Appearance section of Tweaks. To add more themes, you will have to find your preferred theme online and follow the steps
below to have it show up in the Tweaks tool. While you may find themes anywhere, one of the most popular sites for GNOME
themes is [GNOME-LOOK.ORG](https://www.gnome-look.org/). This website contains themes for applications, shells, icons,
and cursors.

Steps to import themes into Tweaks:

* Download the theme.
* These files are usually compressed (.zip, .tar.gz, .tar.xz), so you will need to extract the contents. This is easiest
  when opening the file explorer, right-clicking the compressed file, and choosing "Extract here".
* Move the theme folder to `/usr/share/themes/`. You can do so with the following
  command: `sudo mv theme-folder/ /usr/share/themes/`.
    - Icons and cursors will be moved to the `/usr/share/icons/` folder.
    - Fonts will be moved to the `/usr/share/fonts/` folder Alternatively, you can move them to
      the `/usr/share/fonts/opentype/` or `/usr/share/fonts/opentype/` folders, if you have a specific font type.
* Close Tweaks if it is open. Re-open Tweaks and your new theme will be available in the Applications dropdown in the
  Appearance section of Tweaks.

If the theme is not showing up after you've moved it into the themes folder, you may have uncompressed the folder into a
sub-folder. You can check this by entering the theme folder and listing the contents:

```bash
cd /usr/share/themes/Mojave-Dark && ls -la
```

This is an example of what the contents of your theme folder should look like. If you just see another folder there, you
should move that folder up into the `/usr/share/themes/` folder.

```bash
cinnamon  COPYING  gnome-shell	gtk-2.0  gtk-3.0  index.theme  metacity-1  plank  xfwm4
```

### GNOME Shell Themes

To change the appearance of the title bar, default dock, app menu, and other parts of the GNOME shell, you'll need to
install the [User Themes](https://extensions.gnome.org/extension/19/user-themes/) extension
on [GNOME Extensions](https://extensions.gnome.org/). To be able to install extensions, you will first need to install
the browser extension that the website instructs you to. See this screenshot for the blue box with a link to the
extension.

![GNOME Extensions](https://img.cleberg.io/blog/012-customizing-ubuntu/gnome-extensions-min.png "GNOME Extensions")

After the browser extension in installed, you will need to install the native host connector:

```bash
sudo apt install chrome-gnome-shell
```

Finally, you can go the [User Themes](https://extensions.gnome.org/extension/19/user-themes/) extension page and click
the install button. This will enable the Shell option in Tweaks. Now you can move shell themes to
the `/usr/share/themes` directory, using the same steps mentioned in the previous section, and enable the new theme in
Tweaks.

### Icons & Cursors

Icons and cursors are installed exactly the same way, so I'm grouping these together in this post. Both of these items
will need to follow the same process as installing themes, except you will want to move your font folders to
the `/usr/share/icons/` directory instead.

### Fonts

Fonts are one of the overlooked parts of customization, but a good font can make the whole screen look different. For
example, I have installed the [IBM Plex](https://github.com/IBM/plex/releases) fonts on my system. This follows the same
process as installing themes, except you will want to move your font folders to the `/usr/share/fonts/` directory
instead.

### Terminal

If you spend a lot of time typing commands, you know how important the style and functionality of the terminal is. After
spending a lot of time using the default GNOME terminal with [bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)), I
decided to try some different options. I ended up
choosing [Terminator](https://terminator-gtk3.readthedocs.io/en/latest/)
with [zsh](https://en.wikipedia.org/wiki/Z_shell).

Terminator is great if you need to open multiple terminals at one time by simply right-clicking and splitting the screen
into as many terminals as you want. While this project hasn't been updated in a
while, [it is coming under new development](https://github.com/gnome-terminator/terminator/issues/1). However, this
terminal is great and I haven't experienced any errors yet.

For the shell choice, I decided to choose zsh after trying it out on a fresh Manjaro install. Zsh is great if you like
to change the themes of your terminal, include icons, or add plugins. For example, see my terminal below:

![Terminator with zsh](https://img.cleberg.io/blog/012-customizing-ubuntu/terminator-zsh-min.png "Terminator with zsh")

In this screenshot, you can see the differences between my Ubuntu desktop with zsh and my Ubuntu server with bash. The
desktop uses the [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions) to suggest past commands as you
type. In addition, it suggests corrections if you misspell a command. Lastly, it uses the `af-magic` theme, which adds
dashed lines between commands, moving the user@host tag to the right side of the terminal, and changes the colors. There
are plenty of plugins and themes to choose from - just figure out what you like and add it to your `~/.zshrc` file!

#### Steps to Replicate My Terminal

To install zsh on Ubuntu, enter the following command into a terminal:

```bash
sudo apt install zsh
```

Then, enter the next command to activate zsh:

```bash
sudo chsh -s $(which zsh) $(whoami)
```

To install Terminator on Ubuntu:

```bash
sudo apt install terminator
```

To install Oh My Zsh on Ubuntu:

```bash
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
```

To install zsh-autosuggestions via Oh My Zsh:

```bash
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
```

Then, add the following plugin wording to your `~/.zshrc` file (the default config usually has the `git` plugin
activated, so just add any other plugins to the parentheses separated by a space):

```bash
nano ~/.zshrc
```

```bash
plugins=(git zsh-autosuggestions)
```

Finally, you need to log out of your computer and log back in so your user shell can refresh.

D posts/013-data-exploration-video-game-sales.md => posts/013-data-exploration-video-game-sales.md +0 -225
@@ 1,225 0,0 @@
**Want to try it yourself?**

You can download the Jupyter Notebook containing this Python analysis
here: [video_game_sales.ipynb](https://github.com/christian-cleberg/data-science)

## Background Information

This dataset (obtained from <a href="https://www.kaggle.com/gregorut/videogamesales/data" target="_blank">Kaggle</a>)
contains a list of video games with sales greater than 100,000 copies. It was generated by a scrape of vgchartz.com.

Fields include:

* Rank - Ranking of overall sales
* Name - The games name
* Platform - Platform of the games release (i.e. PC,PS4, etc.)
* Year - Year of the game's release
* Genre - Genre of the game
* Publisher - Publisher of the game
* NA_Sales - Sales in North America (in millions)
* EU_Sales - Sales in Europe (in millions)
* JP_Sales - Sales in Japan (in millions)
* Other_Sales - Sales in the rest of the world (in millions)
* Global_Sales - Total worldwide sales.

There are 16,598 records. 2 records were dropped due to incomplete information.

## Import the Data

```python
# Import the Python libraries we will be using
import pandas as pd
import numpy as np
import seaborn as sns; sns.set()
import matplotlib.pyplot as plt
```

```python
# Download the file from this URL (https://gitlab.com/christian-cleberg/data-science/-/raw/master/data_files/video_game_sales.csv)
# Load the file using the path to the downloaded file
file = r'video_game_sales.csv'
df = pd.read_csv(file)
df
```

![DataFrame result](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/01_dataframe-min.png "DataFrame result")

## Explore the Data

```python
# With the description function, we can see the basic stats. For example, we can also see that the 'Year' column has some incomplete values.
df.describe()
```

![df.describe() result](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/02_describe-min.png "df.describe() result")

```python
# This function shows the rows and columns of NaN values. For example, df[179,3] = nan
np.where(pd.isnull(df))
```

```python
(array([  179,   377,   431,   470,   470,   607,   624,   649,   652,
          711,   782,  1126,  1133,  1303,  1303,  1433,  1498,  1513,
         1585,  1649,  1662,  1697,  1837,  1990,  2019,  2086,  2113,
         2114,  2134,  2145,  2173,  2222,  2286,  2295,  2415,  2422,
         2484,  2497,  2528,  2586,  2776,  2786,  2838,  2947,  3049,
         3141,  3159,  3166,  3194,  3217,  3238,  3346,  3421,  3468,
         3501,  3715,  3753,  3766,  3880,  3952,  4145,  4145,  4151,
         4199,  4232,  4330,  4378,  4380,  4469,  4472,  4526,  4536,
         4635,  4683,  4791,  4797,  4858,  4865,  4934,  5061,  5078,
         5162,  5171,  5195,  5196,  5240,  5302,  5302,  5508,  5618,
         5625,  5647,  5657,  5669,  5769,  5798,  5838,  5861,  5870,
         5901,  5913,  6042,  6125,  6153,  6195,  6211,  6272,  6272,
         6283,  6314,  6316,  6319,  6401,  6437,  6496,  6562,  6647,
         6648,  6777,  6806,  6832,  6849,  6919,  6926,  6937,  6966,
         6968,  7035,  7181,  7208,  7213,  7351,  7351,  7369,  7370,
         7382,  7406,  7423,  7470,  7615,  7648,  7720,  7908,  7946,
         7953,  8044,  8086,  8157,  8167,  8204,  8223,  8313,  8330,
         8334,  8341,  8368,  8376,  8439,  8503,  8553,  8620,  8707,
         8760,  8770,  8848,  8896,  8899,  8929,  9081,  9151,  9171,
         9251,  9310,  9392,  9475,  9517,  9522,  9667,  9692,  9742,
         9749,  9749,  9769,  9815,  9820,  9821,  9840,  9868,  9981,
        10045, 10272, 10362, 10382, 10465, 10494, 10494, 10559, 10690,
        10758, 10792, 10829, 10979, 10997, 11076, 11076, 11108, 11142,
        11323, 11331, 11376, 11409, 11455, 11526, 11543, 11573, 11755,
        11798, 11921, 11938, 11976, 12015, 12028, 12127, 12129, 12135,
        12184, 12212, 12238, 12248, 12487, 12487, 12517, 12517, 12531,
        12626, 12666, 12709, 12749, 12807, 12825, 12879, 12915, 12922,
        12929, 13055, 13156, 13278, 13278, 13477, 13665, 13672, 13672,
        13675, 13733, 13870, 13962, 13962, 14046, 14055, 14056, 14087,
        14087, 14098, 14160, 14273, 14296, 14296, 14311, 14312, 14377,
        14473, 14522, 14583, 14676, 14696, 14698, 14849, 14855, 14876,
        14925, 14942, 14942, 14997, 14998, 14999, 15056, 15197, 15208,
        15261, 15261, 15316, 15325, 15353, 15476, 15579, 15606, 15652,
        15697, 15717, 15739, 15788, 15811, 15865, 15876, 15878, 15900,
        15915, 15915, 16057, 16058, 16065, 16191, 16191, 16194, 16198,
        16198, 16208, 16229, 16229, 16246, 16307, 16327, 16366, 16367,
        16427, 16493, 16494, 16543, 16553], dtype=int64),
 array([3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 5, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 5, 3, 3, 3, 3,
        3, 3, 3, 3, 5, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5,
        3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3,
        3, 3, 3, 5, 3, 5, 3, 5, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3,
        5, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 5, 3,
        5, 5, 3, 3, 5, 3, 3, 3, 3, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5,
        3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 5, 3, 3,
        3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 5, 3, 3, 3, 5, 3, 3, 3, 3, 5, 3, 3, 3, 3, 5, 3, 3,
        3, 3, 5, 5, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 5, 3, 3, 3, 5,
        3, 3, 3, 5, 3, 5, 5, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 5,
        3, 3, 3, 3, 5, 3, 3, 5, 5, 3, 5, 3, 3, 3, 3, 5, 3, 3, 5, 5, 5],
       dtype=int64))
 ```

## Visualize the Data

```python
# This function plots the global sales by platform
sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
```

![plot of global sales by platform](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/03_plot-min.png "plot of global sales by platform")

```python
# This function plots the global sales by genre
sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
```

![plot of global sales by genre](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/04_plot-min.png "plot of global sales by genre")

```python
# This function plots the global sales by year
sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
```

![plot of global sales by year](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/05_plot-min.png "plot of global sales by year")

```python
# This function plots four different lines to show sales from different regions.
# The global sales plot line is commented-out, but can be included for comparison
df2 = df.groupby('Year').sum()
years = range(1980,2019)

a = df2['NA_Sales']
b = df2['EU_Sales']
c = df2['JP_Sales']
d = df2['Other_Sales']
# e = df2['Global_Sales']

fig, ax = plt.subplots(figsize=(12,12))
ax.set_ylabel('Region Sales (in Millions)')
ax.set_xlabel('Year')

ax.plot(years, a, label='NA_Sales')
ax.plot(years, b, label='EU_Sales')
ax.plot(years, c, label='JP_Sales')
ax.plot(years, d, label='Other_Sales')
# ax.plot(years, e, label='Global_Sales')

ax.legend()
plt.show()
```

![plot of regional sales by year](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/06_plot-min.png "plot of regional sales by year")

## Investigate Outliers

```python
# Find the game with the highest sales in North America
df.loc[df['NA_Sales'].idxmax()]
```

```python
Rank                     1
Name            Wii Sports
Platform               Wii
Year                  2006
Genre               Sports
Publisher         Nintendo
NA_Sales             41.49
EU_Sales             29.02
JP_Sales              3.77
Other_Sales           8.46
Global_Sales         82.74
Name: 0, dtype: object
```

```python
# Explore statistics in the year 2006 (highest selling year)
df3 = df[(df['Year'] == 2006)]
df3.describe()
```

![descriptive statistics of 2006 sales](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/07_2006_stats-min.png "descriptive statistics of 2006 sales")

```python
# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
```

![plot of 2006 sales](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/08_plot-min.png "plot of 2006 sales")

```python
# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
```

![outliers of 2006 sales](https://img.cleberg.io/blog/013-data-exploration-video-game-sales/09_outliers-min.png "outliers of 2006 sales")

## Discussion

The purpose of exploring datasets is to ask questions, answer questions, and discover intelligence that can be used to
inform decision-making. So, what have we found in this dataset?

Today, we simply explored a publicly-available dataset to see what kind of information it contained. During that
exploration, we found that video game sales peaked in 2006. That peak was largely due to Nintendo, who sold the top 5
games in 2006 and has a number of games in the top 10 list for the years 1980-2020. Additionally, the top four platforms
by global sales (Wii, NES, GB, DS) are owned by Nintendo.

We didn't explore everything this dataset has to offer, but we can tell from a brief analysis that Nintendo seems to
rule sales in the video gaming world. Further analysis could provide insight into which genres, regions, publishers, or
world events are correlated with sales.

D posts/014-algorithmically-analyzing-local-businesses.md => posts/014-algorithmically-analyzing-local-businesses.md +0 -374
@@ 1,374 0,0 @@
**Want to try it yourself?**

You can download the Jupyter Notebook containing this Python analysis
here: [video_game_sales.ipynb](https://github.com/christian-cleberg/data-science)

## Background Information

This project aims to help investors learn more about Lincoln, Nebraska, USA in order to determine optimal locations for
business investments in Lincoln. The data used in this project was obtained using Foursquare's developer API.

Fields include:

* Venue Name
* Venue Category
* Venue Latitude
* Venue Longitude

There are 232 records found using the center of Lincoln as the area of interest with a radius of 10,000.

## Import the Data

The first step is the simplest: import the applicable libraries. We will be using the libraries below for this project.

```python
# Import the Python libraries we will be using
import pandas as pd
import requests
import folium
import math
import json
from pandas.io.json import json_normalize
from sklearn.cluster import KMeans
```

To begin our analysis, we need to import the data for this project. The data we are using in this project comes directly
from the Foursquare API. The first step is to get the latitude and longitude of the city being studied (Lincoln, NE) and
setting up the folium map.

```python
# Define the latitude and longitude of Lincoln, then map the results
latitude = 40.806862
longitude = -96.681679
map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12)

map_LNK
```

![Blank map](https://img.cleberg.io/blog/014-ibm-data-science/01_blank_map-min.png "Blank map")

Now that we have defined our city and created the map, we need to go get the business data. The Foursquare API will
limit the results to 100 per API call, so we use our first API call below to determine the total results that Foursquare
has found. Since the total results is 232, we perform the API fetching process three times (100 + 100 + 32 = 232).

```python
# Foursquare API credentials
CLIENT_ID = 'your-client-id'
CLIENT_SECRET = 'your-client-secret'
VERSION = '20180604'
```

```python
# Set up the URL to fetch the first 100 results
LIMIT = 100
radius = 10000
url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}'.format(
    CLIENT_ID,
    CLIENT_SECRET,
    VERSION,
    latitude,
    longitude,
    radius,
    LIMIT)

# Fetch the first 100 results
results = requests.get(url).json()

# Determine the total number of results needed to fetch
totalResults = results['response']['totalResults']
totalResults
```

```python
# Set up the URL to fetch the second 100 results (101-200)
LIMIT = 100
offset = 100
radius = 10000
url2 = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}&offset={}'.format(
    CLIENT_ID,
    CLIENT_SECRET,
    VERSION,
    latitude,
    longitude,
    radius,
    LIMIT,
    offset)

# Fetch the second 100 results (101-200)
results2 = requests.get(url2).json()
```

```python
# Set up the URL to fetch the final results (201 - 232)
LIMIT = 100
offset = 200
radius = 10000
url3 = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}&offset={}'.format(
    CLIENT_ID,
    CLIENT_SECRET,
    VERSION,
    latitude,
    longitude,
    radius,
    LIMIT,
    offset)

# Fetch the final results (201 - 232)
results3 = requests.get(url3).json()
```

## Clean the Data

Now that we have our data in three separate dataframes, we need to combine them into a single dataframe and make sure to
reset the index so that we have a unique ID for each business. The `get_category_type` function below will pull the
categories and name from each business's entry in the Foursquare data automatically. Once all the data has been labelled
and combined, the results are stored in the `nearby_venues` dataframe.

```python
# This function will extract the category of the venue from the API dictionary
def get_category_type(row):
    try:
        categories_list = row['categories']
    except:
        categories_list = row['venue.categories']

    if len(categories_list) == 0:
        return None
    else:
        return categories_list[0]['name']
```

```python
# Get the first 100 venues
venues = results['response']['groups'][0]['items']
nearby_venues = json_normalize(venues)

# filter columns
filtered_columns = ['venue.name', 'venue.categories', 'venue.location.lat', 'venue.location.lng']
nearby_venues = nearby_venues.loc[:, filtered_columns]

# filter the category for each row
nearby_venues['venue.categories'] = nearby_venues.apply(get_category_type, axis=1)

# clean columns
nearby_venues.columns = [col.split(".")[-1] for col in nearby_venues.columns]

---

# Get the second 100 venues
venues2 = results2['response']['groups'][0]['items']
nearby_venues2 = json_normalize(venues2) # flatten JSON

# filter columns
filtered_columns2 = ['venue.name', 'venue.categories', 'venue.location.lat', 'venue.location.lng']
nearby_venues2 = nearby_venues2.loc[:, filtered_columns]

# filter the category for each row
nearby_venues2['venue.categories'] = nearby_venues2.apply(get_category_type, axis=1)

# clean columns
nearby_venues2.columns = [col.split(".")[-1] for col in nearby_venues.columns]
nearby_venues = nearby_venues.append(nearby_venues2)

---

# Get the rest of the venues
venues3 = results3['response']['groups'][0]['items']
nearby_venues3 = json_normalize(venues3) # flatten JSON

# filter columns
filtered_columns3 = ['venue.name', 'venue.categories', 'venue.location.lat', 'venue.location.lng']
nearby_venues3 = nearby_venues3.loc[:, filtered_columns]

# filter the category for each row
nearby_venues3['venue.categories'] = nearby_venues3.apply(get_category_type, axis=1)

# clean columns
nearby_venues3.columns = [col.split(".")[-1] for col in nearby_venues3.columns]

nearby_venues = nearby_venues.append(nearby_venues3)
nearby_venues = nearby_venues.reset_index(drop=True)
nearby_venues
```

![Clean data](https://img.cleberg.io/blog/014-ibm-data-science/02_clean_data-min.png "Clean data")

## Visualize the Data

We now have a complete, clean data set. The next step is to visualize this data onto the map we created earlier. We will
be using folium's `CircleMarker()` function to do this.

```python
# add markers to map
for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']):
    label = '{} ({})'.format(name, categories)
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=5,
        popup=label,
        color='blue',
        fill=True,
        fill_color='#3186cc',
        fill_opacity=0.7,
        ).add_to(map_LNK)

map_LNK
```

![Initial data map](https://img.cleberg.io/blog/014-ibm-data-science/03_data_map-min.png "Initial data map")

## Clustering: *k-means*

To cluster the data, we will be using the *k-means* algorithm. This algorithm is iterative and will automatically make
sure that data points in each cluster are as close as possible to each other, while being as far as possible away from
other clusters.

However, we first have to figure out how many clusters to use (defined as the variable '*k*'). To do so, we will use the
next two functions to calculate the sum of squares within clusters and then return the optimal number of clusters.

```python
# This function will return the sum of squares found in the data
def calculate_wcss(data):
    wcss = []
    for n in range(2, 21):
        kmeans = KMeans(n_clusters=n)
        kmeans.fit(X=data)
        wcss.append(kmeans.inertia_)

    return wcss
```

```python
# Drop 'str' cols so we can use k-means clustering
cluster_df = nearby_venues.drop(columns=['name', 'categories'])
```

```python
# calculating the within clusters sum-of-squares for 19 cluster amounts
sum_of_squares = calculate_wcss(cluster_df)
```

```python
# This function will return the optimal number of clusters
def optimal_number_of_clusters(wcss):
    x1, y1 = 2, wcss[0]
    x2, y2 = 20, wcss[len(wcss)-1]

    distances = []
    for i in range(len(wcss)):
        x0 = i+2
        y0 = wcss[i]
        numerator = abs((y2-y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)
        denominator = math.sqrt((y2 - y1)**2 + (x2 - x1)**2)
        distances.append(numerator/denominator)

    return distances.index(max(distances)) + 2
```

```python
# calculating the optimal number of clusters
n = optimal_number_of_clusters(sum_of_squares)
```

Now that we have found that our optimal number of clusters is six, we need to perform k-means clustering. When this
clustering occurs, each business is assigned a cluster number from 0 to 5 in the dataframe.

```python
# set number of clusters equal to the optimal number
kclusters = n

# run k-means clustering
kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(cluster_df)
```

```python
# add clustering labels to dataframe
nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_)
```

Success! We now have a dataframe with clean business data, along with a cluster number for each business. Now let's map
the data using six different colors.

```python
# create map with clusters
map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12)
colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717']

# add markers to the map
for lat, lng, name, categories, cluster in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories'], nearby_venues['Cluster Labels']):
    label = '[{}] {} ({})'.format(cluster, name, categories)
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=5,
        popup=label,
        color=colors[int(cluster)],
        fill=True,
        fill_color=colors[int(cluster)],
        fill_opacity=0.7).add_to(map_clusters)

map_clusters
```

![clustered map](https://img.cleberg.io/blog/014-ibm-data-science/04_clusters-min.png "clustered map")

## Investigate Clusters

Now that we have figured out our clusters, let's do a little more analysis to provide more insight into the clusters.
With the information below, we can see which clusters are more popular for businesses and which are less popular. The
results below show us that clusters 0 through 3 are popular, while clusters 4 and 5 are not very popular at all.

```python
# Show how many venues are in each cluster
color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black']
for x in range(0,6):
    print("Color of Cluster", x, ":", color_names[x])
    print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0])
    print("---")
```

![venues per cluster](https://img.cleberg.io/blog/014-ibm-data-science/05_venues_per_cluster-min.png "venues per cluster")

Our last piece of analysis is to summarize the categories of businesses within each cluster. With these results, we can
clearly see that restaurants, coffee shops, and grocery stores are the most popular.

```python
# Calculate how many venues there are in each category
# Sort from largest to smallest
temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng'])

cluster0_grouped = temp_df.loc[temp_df['Cluster Labels'] == 0].groupby(['categories']).count().sort_values(by='Cluster Labels', ascending=False)
cluster1_grouped = temp_df.loc[temp_df['Cluster Labels'] == 1].groupby(['categories']).count().sort_values(by='Cluster Labels', ascending=False)
cluster2_grouped = temp_df.loc[temp_df['Cluster Labels'] == 2].groupby(['categories']).count().sort_values(by='Cluster Labels', ascending=False)
cluster3_grouped = temp_df.loc[temp_df['Cluster Labels'] == 3].groupby(['categories']).count().sort_values(by='Cluster Labels', ascending=False)
cluster4_grouped = temp_df.loc[temp_df['Cluster Labels'] == 4].groupby(['categories']).count().sort_values(by='Cluster Labels', ascending=False)
cluster5_grouped = temp_df.loc[temp_df['Cluster Labels'] == 5].groupby(['categories']).count().sort_values(by='Cluster Labels', ascending=False)

# show how many venues there are in each cluster (> 1)
with pd.option_context('display.max_rows', None, 'display.max_columns', None):
    print("\n\n", "Cluster 0:", "\n", cluster0_grouped.loc[cluster0_grouped['Cluster Labels'] > 1])
    print("\n\n", "Cluster 1:", "\n", cluster1_grouped.loc[cluster1_grouped['Cluster Labels'] > 1])
    print("\n\n", "Cluster 2:", "\n", cluster2_grouped.loc[cluster2_grouped['Cluster Labels'] > 1])
    print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1])
    print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1])
    print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1])
```

![venue categories per cluster pt. 1](https://img.cleberg.io/blog/014-ibm-data-science/06_categories_per_cluster_pt1-min.png "venue categories per cluster pt. 1")

![venue categories per cluster pt. 2](https://img.cleberg.io/blog/014-ibm-data-science/07_categories_per_cluster_pt2-min.png "venue categories per cluster pt. 2")

## Discussion

In this project, we gathered location data for Lincoln, Nebraska, USA and clustered the data using the k-means algorithm
in order to identify the unique clusters of businesses in Lincoln. Through these actions, we found that there are six
unique business clusters in Lincoln and that two of the clusters are likely unsuitable for investors. The remaining four
clusters have a variety of businesses, but are largely dominated by restaurants and grocery stores.

Using this project, investors can now make more informed decisions when deciding the location and category of business
in which to invest.

Further studies may involve other attributes for business locations, such as population density, average wealth across
the city, or crime rates. In addition, further studies may include additional location data and businesses by utilizing
multiple sources, such as Google Maps and OpenStreetMap.

D posts/015-github-pages-subdomain-to-tld.md => posts/015-github-pages-subdomain-to-tld.md +0 -96
@@ 1,96 0,0 @@
## Short answer

**Step 1**: Add a new file CNAME to your GitHub Pages repository containing only one line: your top-level domain name.

E.g.:

```
example.com
```

**Step 2**: [Optional] but highly recommended

2.1: Remove all other top-level records (prefixed with @) of type A from your DNS configuration.

2.2: Remove a CNAME record for the second-level domain www if it is present.

**Step 3**: Add these 5 entries to the very top of your DNS configuration:

```
@        A        185.199.108.153
@        A        185.199.109.153
@        A        185.199.110.153
@        A        185.199.111.153
www      CNAME    your_github_username.github.io.
```

Replace `your_github_username` with your actual GitHub username.

**Step 4**: Wait for your DNS changes to propagate.

DNS changes aren't effective immediately. They can take up to a full day to propagate.

## Long answer

This issue has two sides. One is the DNS configuration itself. Another one is the way GitHub Pages forwards HTTP
requests.

We need to know a few things to understand what GitHub is trying to say in their documentation.

**DNS Entry Types**

There are two types of DNS records which interest us: CNAME and A.

`A` is also known as `Apex` or sometimes as `root entry`. It forwards requests to a specified fixed IP address. `CNAME`
entry forwards requests to a specified URL (actual valid plain text URL, not an IP address).

**DNS Load balancing**

GitHub has one central URL address which accepts all DNS requests for GitHub Pages: `http://username.github.io`. That
URL is resolved to different IP addresses based on your geographical location. Website hosted on GitHub Pages is a
simple collection of `HTML`, `CSS` and `JS` files. GitHub distributes these files to different servers across the globe.
So that when your browser sends a request from Europe it receives data from a server in Europe. The same is valid for
the requests from Asia and the USA.

**What GitHub is trying to say**

Since `A` records in DNS must contain IP addresses, and they must be either `185.199.108.153` or `185.199.109.153`
or `185.199.110.153` or `185.199.111.153`, there is no way to forward requests to a server located somewhere in Europe
or Asia. Your website hosted at GitHub Pages will be downloaded from a central GitHub Pages server. There is a minor
risk that if GitHub Pages DNS servers (`x.x.x.153`) are down for some reason, all custom domains which use fixed GitHub
Pages IP addresses will not be accessible (their DNS requests will not be resolvable).

That is why GitHub strongly suggests to either use a second-level domain for your GitHub Pages (e.g. `blog.example.com`)
or use a DNS service provider that supports a record type `ALIAS` that acts as `A` record but forwards request to a URL
address (e.g. `username.github.io`) instead of a fixed IP address.

**How GitHub Pages treats HTTP requests**

After a DNS request for `your_github_username.github.io`. is resolved into an IP address, e.g. `185.199.108.153` your
browser sends an HTTP request to that server with an HTTP header `Host`. Below are `curl` examples that load the same
website (these examples might not work if you are behind a proxy server):

```
curl --header "Host: your_github_username.github.io" http://185.199.108.153/
curl --header "Host: www.example.com" http://185.199.108.153/
curl --header "Host: example.com" http://185.199.108.153/
```

This way GitHub Pages servers know which user website to serve.

> GitHub Pages server will automatically redirect HTTP requests to the top-level domain if your `CNAME` file contains `example.com` but `www.example.com` is requested.

> The same is valid if your `CNAME` file contains `www.example.com` but the header `Host` in the `HTTP` request contains `example.com`.</blockquote>

**Why can't I add a `CNAME` record entry that accepts a top-level request (`@`) to my DNS configuration?**

Quote from the GitHub Pages documentation:

> Warning: Do not create a CNAME record for your custom apex domain! Doing so may cause issues with other services, such as email, on that domain.

## References:

[1] [Setting up a custom domain with GitHub Pages](https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site)  
[2] [My custom domain isn't working](https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages)  
[3] [Cannot access my GitHub Pages website by IP Address](https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address)  
[4] [How do I set up GitHub Pages to redirect DNS requests from a subdomain (e.g. www) to the top-level domain (TLD, Apex record)?](https://stackoverflow.com/questions/23375422/how-do-i-set-up-github-pages-to-redirect-dns-requests-from-a-subdomain-e-g-www)  

D posts/016-php-authentication-flow.md => posts/016-php-authentication-flow.md +0 -175
@@ 1,175 0,0 @@
## Introduction

When creating websites that will allow users to create accounts, the developer always needs to consider the proper
authentication flow for their app. For example, some developers will utilize an API for authentication, some will use
OAuth, and some may just use their own simple database.

For those using pre-built libraries, authentication may simply be a problem of copying and pasting the code from their
library's documentation. For example, here's the code I use to authenticate users with the Tumblr OAuth API for my
Tumblr client, [Vox Populi](https://cleberg.io/vox-populi/):

```php
// Start the session
session_start();

// Use my key/secret pair to create a new client connection
$consumer_key = getenv('CONSUMER_KEY');
$consumer_secret = getenv('CONSUMER_SECRET');
$client = new Tumblr\API\Client($consumer_key, $consumer_secret);
$requestHandler = $client->getRequestHandler();
$requestHandler->setBaseUrl('https://www.tumblr.com/');

// Check the session and cookies to see if the user is authenticated
// Otherwise, send user to Tumblr authentication page and set tokens from Tumblr's response

// Authenticate client
$client = new Tumblr\API\Client(
    $consumer_key,
    $consumer_secret,
    $token,
    $token_secret
);
```

However, developers creating authentication flows from scratch will need to think carefully about when to make sure a
web page will check the user's authenticity.

In this article, we're going to look at a simple authentication flow using a MySQL database and PHP.

## Creating User Accounts

The beginning to any type of user auth is to create a user account. This process can take many forms, but the simplest
is to accept user input from a form (e.g. username and password) and send it over to your database. For example, here's
a snippet that shows how to get username and password parameters that would come when a user submits a form to your PHP
script.

**Note**: Ensure that your password column is large enough to hold the hashed value (at least 60 characters or longer).

```php
// Get the values from the URL
$username = $_POST['username'];
$raw_password = $_POST['password'];

// Hash password
// password_hash() will create a random salt if one isn't provided, and this is generally the easiest and most secure approach.
$password = password_hash($raw_password, PASSWORD_DEFAULT);

// Save database details as variables
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

// Create connection to the database
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
  die("Connection failed: " . $conn->connect_error);
}

$sql = "INSERT INTO users (username, password)
VALUES ('$username', '$password')";

if ($conn->query($sql) === TRUE) {
  echo "New record created successfully";
} else {
  echo "Error: " . $sql . "<br>" . $conn->error;
}

$conn->close();
```

## Validate Returning Users

To be able to verify that a returning user has a valid username and password in your database is as simple as having
users fill out a form and comparing their inputs to your database.

```php
// Query the database for username and password
// ...

if(password_verify($password_input, $hashed_password)) {
    // If the input password matched the hashed password in the database
    // Do something, log the user in.
}

// Else, Redirect them back to the login page.
```

## Storing Authentication State

Once you've created the user's account, now you're ready to initialize the user's session. <b>You will need to do this
on every page you load while the user is logged in.</b> To do so, simply enter the following code snippet:

```php
session_start();
```

Once you've initialized the session, the next step is store the session in a cookie so that you can access it later.

```php
setcookie(session_name());
```

Now that the session name has been stored, you'll be able to check if there's an active session whenever you load a
page.

```php
if(isset(session_name())) {
    // The session is active
}
```

## Removing User Authentication

The next logical step is to give your users the option to log out once they are done using your application. This can be
tricky in PHP since a few of the standard ways do not always work.

```php
&lt;?php

// Initialize the session.
// If you are using session_name("something"), don't forget it now!
session_start();

// Delete authentication cookies
unset($_COOKIE[session_name()]);
setcookie(session_name(), "", time() - 3600, "/logged-in/");
unset($_COOKIE["PHPSESSID"]);
setcookie("PHPSESSID", "", time() - 3600, "/logged-in/");

// Unset all of the session variables.
$_SESSION = array();
session_unset();

// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

// Finally, destroy the session.
session_destroy();
session_write_close();

// Go back to sign-in page
header('Location: https://example.com/logged-out/');
die();

?&gt;
```

## Wrapping Up

Now you should be ready to begin your authentication programming with PHP. You can create user accounts, create sessions
for users across different pages of your site, and then destroy the user data when they're ready to leave.

For more information on this subject, I recommend reading the [PHP Documentation](https://www.php.net/). Specifically,
you may want to look at [HTTP authentication with PHP](https://www.php.net/manual/en/features.http-auth.php)
, [session handling](https://www.php.net/manual/en/book.session.php),
and [hash](https://www.php.net/manual/en/function.hash.php).

D posts/017-ibm-watson-visual-recognition.md => posts/017-ibm-watson-visual-recognition.md +0 -227
@@ 1,227 0,0 @@
**Want to try it yourself?**

You can download the Jupyter Notebook containing this Python analysis
here: [video_game_sales.ipynb](https://git.sr.ht/~cyborg/data-science)

## What is IBM Watson?

If you've never heard of [Watson](https://www.ibm.com/watson), this service is a suite of enterprise-ready AI services,
applications, and tooling provided by IBM. Watson contains quite a few useful tools for data scientists and students,
including the subject of this post today: visual recognition.

If you'd like to view the official documentation for the Visual Recognition API, visit
the [API Docs](https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python).

## Prerequisites

To be able to use Watson Visual Recognition, you'll need the following:

1. Create a free account on [IBM Watson Studio](https://www.ibm.com/cloud/watson-studio).
2. Add the [Watson Visual Recognition](https://www.ibm.com/cloud/watson-visual-recognition) service to your IBM Watson
   account.
3. Get your API key and URL. To do this, first go to
   the [profile dashboard](https://dataplatform.cloud.ibm.com/home2?context=cpdaas) for your IBM account and click on
   the Watson Visual Recognition service you created. This will be listed in the section titled **Your services**. Then
   click the **Credentials** tab and open the **Auto-generated credentials** dropdown. Copy your API key and URL so that
   you can use them in the Python script later.
4. **[Optional]** While not required, you can also create the Jupyter Notebook for this project right
   inside [Watson Studio](https://www.ibm.com/cloud/watson-studio). Watson Studio will save your notebooks inside an
   organized project and allow you to use their other integrated products, such as storage containers, AI models,
   documentation, external sharing, etc.

## Calling the IBM Watson Visual Recognition API

Okay, now let's get started.

To begin, we need to install the proper Python package for IBM Watson.

```python
pip install --upgrade --user "ibm-watson>=4.5.0"
```

Next, we need to specify the API key, version, and URL given to us when we created the Watson Visual Recognition
service.

```python
apikey = "&lt;your-apikey&gt;"
version = "2018-03-19"
url = "&lt;your-url&gt;"
```

Now, let's import the necessary libraries and authenticate our service.

```python
import json
from ibm_watson import VisualRecognitionV3
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator

authenticator = IAMAuthenticator(apikey)
visual_recognition = VisualRecognitionV3(
    version=version,
    authenticator=authenticator
)

visual_recognition.set_service_url(url)
```

**[Optional]** If you'd like to tell the API not to use any data to improve their products, set the following header.

```python
visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
```

Now we have our API all set and ready to go. For this example, I'm going to include a <code class="language-python">
dict</code> of photos to load as we test out the API.

```python
data = [
    {
      "title": "Bear Country, South Dakota",
      "url": "https://img.cleberg.io/photos/20140717.jpg"
    },
    {
      "title": "Pactola Lake",
      "url": "https://img.cleberg.io/photos/20140718.jpg"
    },
    {
      "title": "Welcome to Utah",
      "url": "https://img.cleberg.io/photos/20190608_02.jpg"
    },
    {
      "title": "Honey Badger",
      "url": "https://img.cleberg.io/photos/20190611_03.jpg"
    },
    {
      "title": "Grand Canyon Lizard",
      "url": "https://img.cleberg.io/photos/20190612.jpg"
    },
    {
      "title": "The Workhouse",
      "url": "https://img.cleberg.io/photos/20191116_01.jpg"
    }
]
```

Now that we've set up our libraries and have the photos ready, let's create a loop to call the API for each image. The
code below shows a loop that calls the URL of each image and sends it to the API, requesting results with at least 60%
confidence. The results are output to the console with dotted lines separating each section.

In the case of an API error, the codes and explanations are output to the console.

```python
from ibm_watson import ApiException

for x in range(len(data)):
    try:
        url = data[x]["url"]
        images_filename = data[x]["title"]
        classes = visual_recognition.classify(
            url=url,
            images_filename=images_filename,
            threshold='0.6',
            owners=["IBM"]).get_result()
        print("-----------------------------------------------")
        print("Image Title: ", data[x]["title"], "\n")
        print("Image URL: ", data[x]["url"], "\n")
        classification_results = classes["images"][0]["classifiers"][0]["classes"]
        for result in classification_results:
            print(result["class"], "(", result["score"], ")")
        print("-----------------------------------------------")
    except ApiException as ex:
        print("Method failed with status code " + str(ex.code) + ": " + ex.message)
```

## The Results

Here we can see the full result set of our function above. If you view each of the URLs that we sent to the API, you'll
be able to see that it was remarkably accurate. To be fair, these are clear high-resolution, clear photos shot with a
professional camera. In reality, you will most likely be processing images that are lower quality and may have a lot of
noise in the photo.

However, we can clearly see the benefit of being able to call this API instead of attempting to write our own image
recognition function. Each of the classifications returned were a fair description of the image.

If you wanted to restrict the results to those that are at least 90% confident or greater, you would simply adjust
the `threshold` in the `visual_recognition.classify()` function.

```python
----------------------------------------------------------------
Image Title:  Bear Country, South Dakota
Image URL: https://img.cleberg.io/photos/20140717.jpg

brown bear ( 0.944 )
bear ( 1 )
carnivore ( 1 )
mammal ( 1 )
animal ( 1 )
Alaskan brown bear ( 0.759 )
greenishness color ( 0.975 )
----------------------------------------------------------------
----------------------------------------------------------------
Image Title:  Pactola Lake
Image URL: https://img.cleberg.io/photos/20140718.jpg

ponderosa pine ( 0.763 )
pine tree ( 0.867 )
tree ( 0.867 )
plant ( 0.867 )
blue color ( 0.959 )
----------------------------------------------------------------
----------------------------------------------------------------
Image Title:  Welcome to Utah
Image URL: https://img.cleberg.io/photos/20190608_02.jpg

signboard ( 0.953 )
building ( 0.79 )
blue color ( 0.822 )
purplish blue color ( 0.619 )
----------------------------------------------------------------
----------------------------------------------------------------
Image Title:  Honey Badger
Image URL: https://img.cleberg.io/photos/20190611_03.jpg

American badger ( 0.689 )
carnivore ( 0.689 )
mammal ( 0.864 )
animal ( 0.864 )
armadillo ( 0.618 )
light brown color ( 0.9 )
reddish brown color ( 0.751 )
----------------------------------------------------------------
----------------------------------------------------------------
Image Title:  Grand Canyon Lizard
Image URL: https://img.cleberg.io/photos/20190612.jpg

western fence lizard ( 0.724 )
lizard ( 0.93 )
reptile ( 0.93 )
animal ( 0.93 )
ultramarine color ( 0.633 )
----------------------------------------------------------------
----------------------------------------------------------------
Image Title:  The Workhouse
Image URL: https://img.cleberg.io/photos/20191116_01.jpg

castle ( 0.896 )
fortification ( 0.905 )
defensive structure ( 0.96 )
stronghold ( 0.642 )
building ( 0.799 )
mound ( 0.793 )
blue color ( 0.745 )
----------------------------------------------------------------
```

## Discussion

Now, this was a very minimal implementation of the API. We simply supplied six images and looked to see how accurate the
results were. However, you could implement this type of API into many machine learning (ML) models.

For example, you could be working for a company that scans their warehouses or inventory using drones. Would you want to
pay employees to sit there and watch drone footage all day in order to identify or count things in the video? Probably
not. Instead, you could use a classification system similar to this one in order to train your machine learning model to
correctly identify items that the drones show through video. More specifically, you could have your machine learning
model watch a drone fly over a field of sheep in order to count how many sheep are living in that field.

There are many ways to implement machine learning functionality, but hopefully this post helped inspire some deeper
thought about the tools that can help propel us further into the future of machine learning and AI.

D posts/018-what-is-internal-audit.md => posts/018-what-is-internal-audit.md +0 -184
@@ 1,184 0,0 @@
![](https://img.cleberg.io/blog/018-what-is-internal-audit/internal-audit-overview.jpg)
*Fig. 1 - Internal Audit Overview*

## Definitions

One of the many reasons that Internal Audit needs such thorough explaining to non-auditors is that Internal Audit can
serve many purposes, depending on the organization's size and needs. However, the Institute of Internal Auditors (IIA)
defines Internal Auditing as:

> Internal auditing is an independent, objective assurance and consulting activity designed to add value and improve an organization's operations. It helps an organization accomplish its objectives by bringing a systematic, disciplined approach to evaluate and improve the effectiveness of risk management, control, and governance processes.

However, this definition uses quite a few terms that aren’t clear unless the reader already has a solid understanding of
the auditing profession. To further explain, the following is a list of definitions that can help supplement
understanding of internal auditing.

**Independent**

Independence is the freedom from conditions that threaten the ability of the internal audit activity to carry out
internal audit responsibilities in an unbiased manner. To achieve the degree of independence necessary to effectively
carry out the responsibilities of the internal audit activity, the chief audit executive has direct and unrestricted
access to senior management and the board. This can be achieved through a dual-reporting relationship. Threats to
independence must be managed at the individual auditor, engagement, functional, and organizational levels.

**Objective**

Objectivity is an unbiased mental attitude that allows internal auditors to perform engagements in such a manner that
they believe in their work product and that no quality compromises are made. Objectivity requires that internal auditors
do not subordinate their judgment on audit matters to others. Threats to objectivity must be managed at the individual
auditor, engagement, functional, and organizational levels.

**Assurance**

Assurance services involve the internal auditor’s objective assessment of evidence to provide opinions or conclusions
regarding an entity, operation, function, process, system, or other subject matters. The nature and scope of an
assurance engagement are determined by the internal auditor. Generally, three parties are participants in assurance
services: (1) the person or group directly involved with the entity, operation, function, process, system, or other
subject matter — the process owner, (2) the person or group making the assessment — the internal auditor, and (3) the
person or group using the assessment — the user.

**Consulting**

Consulting services are advisory in nature and are generally performed at the specific request of an engagement client.
The nature and scope of the consulting engagement are subject to agreement with the engagement client. Consulting
services generally involve two parties: (1) the person or group offering the advice — the internal auditor, and (2) the
person or group seeking and receiving the advice — the engagement client. When performing consulting services the
internal auditor should maintain objectivity and not assume management responsibility.

**Governance, Risk Management, & Compliance (GRC)**

The integrated collection of capabilities that enable an organization to reliably achieve objectives, address
uncertainty and act with integrity.

## Audit Charter & Standards

First, it’s important to note that not every organization needs internal auditors. In fact, it’s unwise for an
organization to hire internal auditors unless they have regulatory requirements for auditing and have the capital to
support the department. Internal audit is a cost center that can only affect revenue indirectly.

Once an organization determines the need for internal assurance services, they will hire a Chief Audit Executive and
create the audit charter. This charter is a document, approved by the company’s governing body, that will define
internal audit’s purpose, authority, responsibility, and position within the organization. Fortunately, the IIA has
model charters available to IIA members for those developing or improving their charter.

Beyond the charter and organizational documents, internal auditors follow a few different standards in order to perform
their job. First is the International Professional Practices Framework (IPPF) by the IIA, which is the model of
standards for internal auditing. In addition, ISACA’s Information Technology Assurance Framework (ITAF) helps guide
auditors in reference to information technology (IT) compliance and assurance. Finally, additional standards such as
FASB, GAAP, and industry-specific standards are used when performing internal audit work.

## Three Lines of Defense

[The IIA](https://theiia.org) released their original three lines of defense model in 2013, but have released an updated
version in 2020. Here is what the three lines of defense model has historically looked like:

![](https://img.cleberg.io/blog/018-what-is-internal-audit/three_lines_model.png)
*Fig. 2 - 2013 Three Lines of Defense Model*

I won't go into depth about the changes made to the model in this article. Instead, let's take a look at the most
current model.

![](https://img.cleberg.io/blog/018-what-is-internal-audit/updated_three_lines_model.png)
*Fig. 3 - 2020 Three Lines of Defense Model*

The updated model forgets the strict idea of areas performing their own functions or line of defense. Instead of talking
about management, risk, and internal audit as 1-2-3, the new model creates a more fluid and cooperative model.

Looking at this model from an auditing perspective shows us that auditors will need to align, communicate, and
collaborate with management - including business area managers and chief officers - as well as reporting to the
governing body. The governing body will instruct internal audit *functionally* on their goals and track their progress
periodically. However, the internal audit department will report *administratively* to a chief officer in the company
for the purposes of collaboration, direction, and assistance with the business. Note that in most situations, the
governing body is the audit committee on the company's board of directors.

The result of this structure is that internal audit is an independent and objective function that can provide assurance
over the topics they audit.

## Audit Process

A normal audit will generally follow the same process, regardless of the topic. However, certain special projects or
abnormal business areas may call for changes to the audit process. The audit process is not set in stone, it’s simply a
set of best practices so that audits can be performed consistently.

![](https://img.cleberg.io/blog/018-what-is-internal-audit/internal-audit-process.jpg)
*Fig. 4 - The Internal Audit Process*

While different organizations may tweak the process, it will generally follow this flow:

**1. Risk Assessment**

The risk assessment part of the process has historically been performed annually, but many organizations have moved to
performing this process much more frequently. In fact, some organizations are moving to an agile approach that can take
new risks into the risk assessment and re-prioritize risk areas on-the-go. To perform a risk assessment, leaders in
internal audit will research industry risks, consult with business leaders around the company, and perform analyses on
company data.

Once a risk assessment has been documented, the audit department has a prioritized list of risks that can be audited.
This is usually in the form of auditable entities, such as business areas or departments.

**2. Planning**

During the planning phase of an audit, auditors will meet with the business area to discuss the various processes,
controls, and risks applicable to the business. This helps the auditors determine the scope limits for the audit, as
well as timing and subject-matter experts. Certain documents will be created in this phase that will be used to keep the
audit on-track an in-scope as it goes forward.

**3. Testing**

The testing phase, also known as fieldwork or execution, is where internal auditors will take the information they’ve
discovered and test it against regulations, industry standards, company rules, best practices, as well as validating
that any processes are complete and accurate. For example, an audit of HR would most likely examine processes such as
employee on-boarding, employee termination, security of personally identifiable information (PII), or the IT systems
involved in these processes. Company standards would be examined and compared against how the processes are actually
being performed day-to-day, as well as compared against regulations such as the Equal Employment Opportunity (EEO),
American with Disabilities Act, and National Labor Relations Act.

**4. Reporting**

Once all the tests have been completed, the audit will enter the reporting phase. This is when the audit team will
conclude on the evidence they’ve collected, interviews they’ve held, and any opinions they’ve formed on the controls in
place. A summary of the audit findings, conclusions, and specific recommendations are officially communicated to the
client through a draft report. Clients have the opportunity to respond to the report and submit an action plan and time
frame. These responses become part of the final report which is distributed to the appropriate level of administration.

**5. Follow-Up**

After audits have been completed and management has formed action plans and time frames for audit issues, internal audit
will follow-up once that due date has arrived. In most cases, the follow-up will simply consist of a meeting to discuss
how the action plan has been completed and to request documentation to prove it.

## Audit Department Structure

While an internal audit department is most often thought of as a team of full-time employees, there are actually many
different ways in which a department can be structured. As the world becomes more digital and fast-paced, outsourcing
has become a more attractive option for some organizations. Internal audit can be fully outsourced or partially
outsourced, allowing for flexibility in cases where turnover is high.

In addition, departments can implement a rotational model. This allows for interested employees around the organization
to rotate into the internal audit department for a period of time, allowing them to obtain knowledge of risks and
controls and allowing the internal audit team to obtain more business area knowledge. This program is popular in very
large organizations, but organizations tend to rotate lower-level audit staff instead of managers. This helps prevent
any significant knowledge loss as auditors rotate out to business areas.

## Consulting

Consulting is not an easy task at any organization, especially for a department that can have negative perceptions
within the organization as the "compliance police". However, once an internal audit department has delivered value to
organization, adding consulting to their suite of services is a smart move. In most cases, internal audit can insert
themselves into a consulting role without affecting the process of project management at the company. This means that
internal audit can add objective assurance and opinions to business areas as they develop new processes, instead of
coming in periodically to audit an area and file issues that could have been fixed at the beginning.

## Data Science & Data Analytics

![](https://img.cleberg.io/blog/018-what-is-internal-audit/data-science-skillset.webp)
*Fig. 5 - Data Science Skill Set*

One major piece of the internal audit function in the modern world is data science. While the process is data science,
most auditors will refer to anything in this realm as data analytics. Hot topics such as robotic process automation (
RPA), machine learning (ML), and data mining have taken over the auditing world in recent years. These technologies have
been immensely helpful with increasing the effectiveness and efficiency of auditors.

For example, mundane and repetitive tasks can be automated in order for auditors to make more room in their schedules
for labor-intensive work. Further, auditors will need to adapt technologies like machine learning in order to extract
more value from the data they’re using to form conclusions.

D posts/019-data-visualization-world-choropleth-map-of-happiness.md => posts/019-data-visualization-world-choropleth-map-of-happiness.md +0 -213
@@ 1,213 0,0 @@
**Want to try it yourself?**

You can download the Jupyter Notebook containing this Python analysis
here: [video_game_sales.ipynb](https://git.sr.ht/~cyborg/data-science)

## Background Information

The dataset (obtained from <a href="https://www.kaggle.com/unsdsn/world-happiness" target="_blank">Kaggle</a>) used in
this article contains a list of countries around the world, their happiness rankings and scores, as well as other
national scoring measures.

Fields include:

* Overall rank
* Country or region
* GDP per capita
* Social support
* Healthy life expectancy
* Freedom to make life choices
* Generosity
* Perceptions of corruption

There are 156 records. Since there are ~195 countries in the world, we can see that around 40 countries will be missing
from this dataset.

## Install Packages

As always, run the `install` command for all packages needed to perform analysis.

```python
!pip install folium geopandas matplotlib numpy pandas
```

## Import the Data

We only need a couple packages to create a choropleth map. We will
use [Folium](https://python-visualization.github.io/folium/), which provides map visualizations in Python. We will also
use geopandas and pandas to wrangle our data before we put it on a map.

```python
# Import the necessary Python packages
import folium
import geopandas as gpd
import pandas as pd
```

To get anything to show up on a map, we need a file that will specify the boundaries of each country. Luckily, these
GeoJSON files exists for free on the internet. To get the boundaries of every country in the world, we will use the
GeoJSON link shown below.

GeoPandas will take this data and load it into a dataframe so that we can easily match it to the data we're trying to
analyze. Let's look at the GeoJSON dataframe:

```python
# Load the GeoJSON data with geopandas
geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson')
geo_data.head()
```

![](https://img.cleberg.io/blog/019-world-choropleth-map/geojson_df.png)
*Fig. 1 - GeoJSON Dataframe*

Next, let's load the data from the Kaggle dataset. I've downloaded this file, so update the file path if you have it
somewhere else. After loading, let's take a look at this dataframe:

```python
# Load the world happiness data with pandas
happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv')
happy_data.head()
```

![](https://img.cleberg.io/blog/019-world-choropleth-map/happiness_df.png)
*Fig. 2 - Happiness Dataframe*

## Clean the Data

Some countries need to be renamed, or they will be lost when you merge the happiness and GeoJSON dataframes. This is
something I discovered when the map below showed empty countries. I searched both data frames for the missing countries
to see the naming differences. Any countries that do not have records in the `happy_data` df will not show up on the
map.

```python
# Rename some countries to match our GeoJSON data

# Rename USA
usa_index = happy_data.index[happy_data['Country or region'] == 'United States']
happy_data.at[usa_index, 'Country or region'] = 'United States of America'

# Rename Tanzania
tanzania_index = happy_data.index[happy_data['Country or region'] == 'Tanzania']
happy_data.at[tanzania_index, 'Country or region'] = 'United Republic of Tanzania'

# Rename the Congo
republic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Brazzaville)']
happy_data.at[republic_congo_index, 'Country or region'] = 'Republic of Congo'

# Rename the DRC
democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)']
happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo'
```

## Merge the Data

Now that we have clean data, we need to merge the GeoJSON data with the happiness data. Since we've stored them both in
dataframes, we just need to call the `.merge()` function.

We will also rename a couple columns, just so that they're a little easier to use when we create the map.

```python
# Merge the two previous dataframes into a single geopandas dataframe
merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region')

# Rename columns for ease of use
merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'})
merged_df = merged_df.rename(columns = {'Country or region':'Country'})
```

!][](https://img.cleberg.io/blog/019-world-choropleth-map/merged_df.png)
*Fig. 3 - Merged Dataframe*

## Create the Map

The data is finally ready to be added to a map. The code below shows the simplest way to find the center of the map and
create a Folium map object. The important part is to remember to reference the merged dataframe for our GeoJSON data and
value data. The columns specify which geo data and value data to use.

```python
# Assign centroids to map
x_map = merged_df.centroid.x.mean()
y_map = merged_df.centroid.y.mean()
print(x_map,y_map)
```

```python
# Creating a map object
world_map = folium.Map(location=[y_map, x_map], zoom_start=2,tiles=None)
folium.TileLayer('CartoDB positron',name='Dark Map',control=False).add_to(world_map)
```

```python
# Creating choropleth map
folium.Choropleth(
    geo_data=merged_df,
    name='Choropleth',         
    data=merged_df,
    columns=['Country','Overall rank'],
    key_on='feature.properties.Country',
    fill_color='YlOrRd',
    fill_opacity=0.6,
    line_opacity=0.8,
    legend_name='Overall happiness rank',
    smooth_factor=0,     
    highlight=True
).add_to(world_map)
```

Let's look at the resulting map.

![](https://img.cleberg.io/blog/019-world-choropleth-map/map.png)
*Fig. 4 - Choropleth Map*

## Create a Tooltip on Hover

Now that we have a map set up, we could stop. However, I want to add a tooltip so that I can see more information about
each country. The `tooltip_data` code below will show a popup on hover with all the data fields shown.

```python
# Adding labels to map
style_function = lambda x: {'fillColor': '#ffffff',
                            'color':'#000000',
                            'fillOpacity': 0.1,
                            'weight': 0.1}

tooltip_data = folium.features.GeoJson(
    merged_df,
    style_function=style_function,
    control=False,
    tooltip=folium.features.GeoJsonTooltip(
        fields=['Country'
                ,'Overall rank'
                ,'Score'
                ,'GDP per capita'
                ,'Social support'
                ,'Healthy life expectancy'
                ,'Freedom to make life choices'
                ,'Generosity'
                ,'Perceptions of corruption'
               ],
        aliases=['Country: '
                ,'Happiness rank: '
                ,'Happiness score: '
                ,'GDP per capita: '
                ,'Social support: '
                ,'Healthy life expectancy: '
                ,'Freedom to make life choices: '
                ,'Generosity: '
                ,'Perceptions of corruption: '
                 ],
        style=('background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;')
    )
)
world_map.add_child(tooltip_data)
world_map.keep_in_front(tooltip_data)
folium.LayerControl().add_to(world_map)

# Display the map
world_map
```

The final image below will show you what the tooltip looks like whenever you hover on a country.

![](https://img.cleberg.io/blog/019-world-choropleth-map/tooltip_map.png)
*Fig. 5 - Choropleth Map Tooltip*

D posts/020-on-the-pursuit-of-mediocrity.md => posts/020-on-the-pursuit-of-mediocrity.md +0 -77
@@ 1,77 0,0 @@
## Perfect is the Enemy of Good

As the saying goes, "the best is the enemy of the good." As we strive for perfection, we often fail to realize the
implications of such an undertaking. Attempting to reach perfection is often unrealistic and even worse, it can get in
the way of achieving a good outcome. In certain situations, we try so hard to achieve the ideal solution that we have
burned the bridges that would have allowed us to reach a lesser yet still superb solution.

Philosophers throughout history have inspected this plight from many viewpoints. Greek mythology speaks of
the [golden mean](https://en.wikipedia.org/wiki/Golden_mean_(philosophy)), which uses the story of Icarus to illustrate
that sometimes "the middle course" is the best solution. In this story, Daedalus, a famous artist of his time, built
feathered wings for himself and his son so that they might escape the clutches of King Minos. Daedalus warns his beloved
son whom he loved so much to "fly the middle course", between the sea spray and the sun's heat. Icarus did not heed his
father; he flew up and up until the sun melted the wax off his wings. For not heeding the middle course, he fell into
the sea and drowned.

More recently, management scholars have explored the [Pareto principle](https://en.wikipedia.org/wiki/Pareto_principle)
and found that as we increase the frequency of something, or strive to perform actions to achieve some form of
perfection, we run into [diminishing returns](https://en.wikipedia.org/wiki/Diminishing_returns).

Even further, Harold Demsetz is noted as coining the
term [the Nirvana fallacy](https://en.wikipedia.org/wiki/Nirvana_fallacy) in 1969, which shows the fallacy of comparing
actual things with unrealistic, idealized alternatives. This is another trap that we may fall into, where we are
constantly thinking of the ultimate solutions to problems, when something more realistic needs to be considered.

Over and over throughout history, we've found that perfection is often unrealistic and unachievable. However, we push
ourselves, and our peers to "give 100%" or "go the extra mile", while it may be that the better course is to give a
valuable level of effort while considering the effects of further effort on the outcome. Working harder does not always
help us achieve loftier goals.

This has presented itself to me most recently during my time studying at my university. I was anxious and feeling the
stresses of my courses, career, and personal life for quite a while, which was greatly affecting how well I was doing at
school and my level of effort at work. One day, I happened to be talking to my father when he said something simple that
hit home:

> All you can do is show up and do your best. Worrying about the outcomes won't affect the outcome itself.

The thought was extremely straightforward and uncomplicated, yet it was something that I had lost sight of during my
stress-filled years at school. Ever since then, I've found myself pausing and remembering that quote every time I get
anxious or stressed. It helps to stop and think "Can I do anything to affect the outcome or am I simply worrying over
something I can't change?"

## When Mediocrity Isn't Enough

One problem with the philosophies presented in this post is that they are implemented far too often in situations where
mediocrity simply isn't adequate. For example, let's take a look at digital user data, specifically
personally-identifiable information (PII). As a cybersecurity auditor in the United States, I have found that most
companies are concerned more with compliance than any actual safeguards over the privacy or protection of user data.
Other than companies who have built their reputation on privacy and security, most companies will
use [satisficing](https://en.wikipedia.org/wiki/Satisficing) as their primary decision-making strategy around user data.

> Satisficing is a decision-making strategy or cognitive heuristic that entails searching through the available alternatives until an acceptability threshold is met.

This means that each decision will be met with certain possible solutions until one of the solutions meets their minimum
acceptable standards. For companies that deal with user data, the minimum acceptable standards come from three areas:

1. Laws and regulations
2. Competitive pressure
3. Risk of monetary or reputation loss

Working with project management or auditing, the primary concern here is the risk of legal ramifications. Since the
primary risk comes from laws and regulations, companies will require that any project that involves user data must
follow all the rules of those laws so that the company can protect itself from fines or other penalties. Following this,
companies will consider best practices in order to place itself in a competitive position (e.g. Google vs. Apple) and
review any recent or ongoing litigation against companies regarding user data. In a perfect company, management would
then consider the ethical responsibilities of their organization and discuss their responsibilities over things like
personally-identifiable information. However, as we mentioned above, most companies follow the idea of satisficing,
which states that they have met the minimum acceptable standards and can now move on to other decisions. Modern business
culture in the United States dictates that profits are the golden measure of how well a company or manager is
performing, so we often don't think about our responsibilities beyond these basic standard.

Not all situations demand excellence, but I believe that applying any philosophy as a broad stroke across one's life can
be a mistake. We must be able to think critically about what we are doing as we do it and ask ourselves a few questions.
Have I done everything I can in this situation? Is mediocrity an acceptable outcome or should we strive for perfection,
even if we can't attain it?

Taking a few moments to think critically throughout our day, as we make decisions, can have a tremendous effect on the
outcomes we create.

D posts/021-minimal-website-redesign.md => posts/021-minimal-website-redesign.md +0 -69
@@ 1,69 0,0 @@
## A Brief History

As a form of continuous learning and entertainment, I've been running a handful of websites since 2016 when I took my
first programming courses in college. One of the websites I maintain is [cleberg.io](https://cleberg.io), the place I
consider the official website to represent me. Under this site, I have a handful of subdomains, such
as [blog.cleberg.io](https://blog.cleberg.io).

One of the parts I've enjoyed the most about web development is the aspect of designing an identity for a web page and
working to find exciting ways to display the site's content. Inevitably, this means I've changed the designs for my
websites more times than I could possibly count. Since I don't really host anything on my main webpage that's vital, it
allows me the freedom to change things as inspiration strikes.

Historically, I've relied on core utilities for spacing, components, and layouts
from [Bootstrap](https://getbootstrap.com) and added custom CSS for fonts, accents, colors, and other items. I also tend
to create sites with no border radius on items, visible borders, and content that takes up the entire screen (using
whitespace inside components instead of whitespace around my components).

## The New Design

About a week ago, I found myself wishing for a new design yet again. The prior design was largely inspired by
IBM's [Carbon Design System](https://www.carbondesignsystem.com) and relied on jQuery, Bootstrap, along with some
compressed [.webp](https://en.wikipedia.org/wiki/WebP) images.

To anyone who knows my preferences toward web design - and even in my personal life - it should be no surprise that I
immediately started looking for inspiration on minimalism. While there are some decent minimalistic designs on sites
like [Dribbble](https://dribbble.com/search/shots/popular/web-design?q=minimalism), people seem to mostly
discuss [brutalist web design](https://brutalist-web.design) when you ask about minimalism. While brutalist web design
doesn't have to be minimal, it often is.

I suppose, in a way, I did create a brutalist website since my HTML is semantic and accessible, hyperlinks are colored
and underlined, and all native browser functions like scrolling and the back button work as expected. However, I didn't
think about brutalism while designing these sites.

The new design followed a simple design process. I walked through the screens on my blog and asked myself: "Is this
element necessary for a user?" This allowed me to first start by removing all javascript, which had the sole purpose of
allowing users to open a collapsed navbar on mobile. Replacing the collapsible navbar allowed me to remove both jQuery
and Bootstrap's javascript.

Next, I removed things like author names (since I'm literally the only person who will ever write on this site),
multiple ways to click on a blog post card, blog post descriptions, and the scroll-to-top button. It also helped to move
all categories to a single page (`https://blog.cleberg.io/categories/`), rather than have each category on its own
page (e.g. `https://blog.cleberg.io/category/software/`).

The final big piece to finish the "[KonMari](https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method)"-like part of my
process was to remove Bootstrap CSS in its entirety. However, this meant pulling out a few very useful classes, such
as `.img-fluid` and the default font stacks to keep in my custom CSS.

After removing all the unnecessary pieces, I was finally able to reorganize my content and add a very small amount of
custom CSS to make everything pretty. This took a very short amount of time, effectively just consisting of me
converting `&lt;div&gt;` tags into things like `&lt;ul&gt;` lists and choosing accent colors.

## The Results

So, what did all of this reorganizing do to [cleberg.io](https://cleberg.io)
and [blog.cleberg.io](https://blog.cleberg.io)? Well first, my websites are now **ridiculously fast**. Since the prior
designs were also minimal and didn't have many images, they measured up in Firefox's Network Monitor around 300 KB - 600
KB. After making the changes, my main site is at 5 KB transferred (22 KB total), and my blog is at 6.5 KB transferred (
13 KB total). **That means the redesigned pages are less than 2% the size of the old designs.**

Google Lighthouse ranks [cleberg.io](https://cleberg.io) as 100 in performance, accessibility, and best practices - with
SEO at 92 since they think tap targets are not sized appropriately for mobile users. First contextual paints of the
pages are under 0.8 seconds with 0 ms of blocking time. However, [blog.cleberg.io](https://blog.cleberg.io) ranks at 100
for all four categories! First contextual paints of the blog homepage are under 1.0 seconds with 0 ms of blocking time,
due to the fact that the CSS for my blog is within a separate CSS file and the CSS for my main website is simply
embedded in the HTML file.

Now that everything is complete, I can confidently say I'm happy with the result and proud to look at the fastest set of
websites I've created so far. If you have any more inspiration or ideas for minimal/brutalist web design,
please [send me an email](mailto:hello@cleberg.io) or [message me on Mastodon](https://floss.social/@cyborg)!

D posts/022-neon-drive.md => posts/022-neon-drive.md +0 -68
@@ 1,68 0,0 @@
## Game Description

[Neon Drive](https://store.steampowered.com/app/433910/Neon_Drive/) presents itself as a simple arcade-style game
inspired by the arcade race games of 1980s, yet it has managed to take up hours of my life without much effort. The game
description, directly from the Steam page, is intriguing enough to entice anyone who's been looking for a good arcade
racing game:

<blockquote>Neon Drive is a slick retro-futuristic arcade game that will make your brain melt. You've been warned. From beautiful cityscapes and ocean roads to exploding enemy spaceships, Neon Drive has it all.</blockquote>

## Gameplay

The game holds true to the [retrofuturism](https://en.wikipedia.org/wiki/Retrofuturism) style, including chrome female
robots, blocky arcade machines, and [outrun](https://old.reddit.com/r/outrun/) aesthetics.

Each level of the game is shown as a separate arcade machine. Each arcade machine lets you play on Normal, Hard, Insane,
Practice, and Free Run. To beat each arcade, you must reach the end of the level without crashing your car into the
various obstacles on the course. Basic levels let you move left or right to avoid blocks in the road. Later levels put
you through other tests, such as dodging traffic or blasting asteroids.

The game uses synthwave music to keep you on track to make the correct moves by timing the beats of the songs to the
correct moves on the screen. It reminds me of the early Guitar Hero games, as well as mobile apps like VOEZ - repetition
and staying on-beat is the only way to win.

## In-Game Screenshots

Taking a look at the main menu, you can see that Neon Drive plays into every stereotype you can think of around
retrofuturistic, synthwave arcades (in a good way).

![](https://img.cleberg.io/blog/022-neon-drive/neon_drive_menu.webp)

Once you get into the first level, we see that the choice of car fits right in with the stereotypical cars of the 80s,
like the [DeLorean](https://en.wikipedia.org/wiki/DMC_DeLorean) or
the [Ferrari F40](https://en.wikipedia.org/wiki/Ferrari_F40). Each new level comes with new color schemes and cars, so
you should never get tired of the aesthetic.

![](https://img.cleberg.io/blog/022-neon-drive/neon_drive_race.webp)

Personally, I love the orange and blue colors used in level 2:

![](https://img.cleberg.io/blog/022-neon-drive/neon_drive_level_2.webp)

If you're the competitive type and getting 100% on all arcade machines isn't enough, there are leaderboards for the
regular part of the game, and the endurance game mode.

![](https://img.cleberg.io/blog/022-neon-drive/neon_drive_leaderboard.webp)
![](https://img.cleberg.io/blog/022-neon-drive/neon_drive_leaderboard_endurance.webp)

## Other Suggestions

Neon Drive sits nicely within the well-founded cult genre of Outrun. Other games that I've enjoyed in this same spectrum
are:

* [Far Cry 3: Blood Dragon](https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/)
* [Retrowave](https://store.steampowered.com/app/1239690/Retrowave/)
* [Slipstream](https://store.steampowered.com/app/732810/Slipstream/)

Although these games aren't necessarily in the same genre, they do have aspects that place them close enough to interest
gamers that enjoyed Neon Drive:

* [Black Ice](https://store.steampowered.com/app/311800/Black_Ice/)
* [Cloudpunk](https://store.steampowered.com/app/746850/Cloudpunk/)
* [Need for Speed Heat](https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/)
* [VirtuaVerse](https://store.steampowered.com/app/1019310/VirtuaVerse/)

Of course, if all you really care about is the arcade aspect of these games, you can check out
the [Atari Vault](https://store.steampowered.com/app/400020/Atari_Vault/) or any of the other classic games sold on
Steam by companies like Namco, Atari. For something like Nintendo, you'd have to settle for buying used classic consoles
or delve into the world of emulation.

D posts/023-zork.md => posts/023-zork.md +0 -64
@@ 1,64 0,0 @@
## Download (Free)

Before we get into the game itself, you should know that you can download Zork for free
from [Infocom's download page](http://infocom-if.org/downloads/downloads.html). So feel free to boot it up and take a
ride back to the 1980s with this masterpiece.

## Game Description

Zork is an interactive, text-based computer game originally released in 1980. This series, split into three separate
games, introduced a robust and sophisticated text parser to gamers. People were largely used to the simple commands used
in the popular game [Colossal Cave Adventure](https://en.wikipedia.org/wiki/Colossal_Cave_Adventure), but Zork allowed
users to send more complex commands that included prepositions and conjunctions.

Zork tracks your score as you explore the map, find tools, and collect trophy items (e.g. a jewel-encrusted egg). When
you place your trophy items in the trophy case found in the Living Room area, you gain score points. Collecting the
Twenty Treasures of Zork and placing them within the trophy case wins the game. However, you must explore the map, solve
puzzles, and avoid being eaten by a grue to collect these treasures.

## The Map

Since Zork is a vast and complex game, it helps to have a map as you explore and collect your trophies. However, if you
want to play the game as it was truly intended, you should try to play it without using the map.

![](https://img.cleberg.io/blog/023-zork/zork_map.webp)
[Map Source](https://www.filfre.net/2012/01/exploring-zork-part-1/)

## In-Game Screenshots

After playing the game (for the first time ever) for several weeks around 2014, I was finally able to beat the game with
some online help to find the last couple items. As I was writing this post, I installed the game again to grab some
screenshots to show off the true glory of this game. As noted
in [Jimmy Maher's playthrough](https://www.filfre.net/2012/01/exploring-zork-part-1/), the original Zork games looked
quite a bit different due to the older hardware of computers like the Apple II and multiple bug fixes that Infocom
pushed out after the game's initial release. My playthrough uses
the [Zork Anthology](https://store.steampowered.com/app/570580/Zork_Anthology/) version, which utilizes DOSBox on
Windows.

The first screenshot here shows the introductory information, which doesn't include instructions of any kind for the
player. If you haven't played text adventures before, try to use simple commands like "go west", "look around", or "hit
troll with elvish sword".

![](https://img.cleberg.io/blog/023-zork/zork_01.webp)

In this second screenshot, we see the player has entered the house and found the trophy case in the living room. The
lantern and sword in this room allow the player to explore dark areas and attack enemies. If you don't use the lantern,
you won't be able to see anything in dark areas and you may be eaten by a grue.

![](https://img.cleberg.io/blog/023-zork/zork_02.webp)

Finally, we see that the player has found the first treasure: a jewel-encrusted egg. These treasures can be taken back
to the house and placed in the trophy case or carried until you feel like you want to put things away.

![](https://img.cleberg.io/blog/023-zork/zork_03.webp)

## Conclusion

It's been quite a few years since I first played Zork, but I clearly remember the late nights and bloodshot eyes that
helped me find all the treasures. This game is well worth the time and effort, even though the text-based aspect may be
off-putting to gamers who didn't have to grow up playing games without graphics. However, I believe that the strategy
and skills learned in early video games like Zork can actually help you even when playing newer games.

If you do decide to play Zork, you can download Zork I, II, and III
from [Infocom's download page](http://infocom-if.org/downloads/downloads.html) for free or search the internet for an
online version.

D posts/024-seum.md => posts/024-seum.md +0 -65
@@ 1,65 0,0 @@
## Game Description

[SEUM: Speedrunners from Hell](https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/) is an incredibly
fast-paced mashup of a puzzle game and a free-running game. Created by [Pine Studio](https://pinestudio.co) in early
2016 and first released as a [demo on GameJolt](https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868),
this game was successfully greenlit on Steam and has amassed a cult following on multiple consoles.

Here's the game description straight from the developers:

> Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners from Hell is truly hardcore and focuses on speed and fast reaction.

## Story

SEUM does a great job setting the stage when you open the game for the first time, playing an animated video in the form
of comic book images. You see Marty, the protagonist, sitting around drinking his beer as Satan busts down the door,
cuts his hand off, and steals all his beer and bacon. As Satan is leaving, Marty whips a vinyl record across the room
and cuts off one of Satan's hands. This hand is what allows you to use all the powers in the game.

Check out the screenshot below for one of the first panels of the storyline:

![](https://img.cleberg.io/blog/024-seum/seum_story.webp)

## Gameplay

To accomplish each level, you will need to get to final blue portal under a certain time limit. You can beat a level by
getting to the portal before the time meter runs out or "Dominate" a level by beating it within a shorter time limit (
noted by a bright red/orange color in the time meter).

The portal is usually set behind locked gates, protected by dangerous defenses, or in hard-to-reach places. To reach the
portal, you'll need to go through any existing orange portals, light all yellow beacons, avoid things like fireballs and
blades, or use any satanic power orbs lying around. These special abilities include:

* Gravity
* Teleport
* Rewind
* Spawn platform
* Roar (DLC)
* Rocket (DLC)
* Shadow world (DLC)

For the main storyline, there are nine floors to beat. Each floor contains nine regular levels, one boss level, and one
bonus level - although you don't technically need to beat all levels to advance to the next floor.

![](https://img.cleberg.io/blog/024-seum/seum_floor.webp)

## In-Game Screenshots

The main menu gives you plenty of options for tuning your system, playing main levels, playing the DLC, or exploring the
other game modes.

![](https://img.cleberg.io/blog/024-seum/seum_menu.webp)

Once you enter a level, you can look around and strategize before starting. Clicking any button will start the menu, and
you'll have to restart if you die. One of the great things about SEUM is that it has great keyboard shortcuts. You can
quickly smash `R` to restart the level or `M` to return to level menu.

![](https://img.cleberg.io/blog/024-seum/seum_level.webp)

Once you're able to win a level, you'll see the high scores and can watch replays of the best scores.

![](https://img.cleberg.io/blog/024-seum/seum_win.webp)

Each regular level contains a beer in a disguised location that may take some out-of-the-box thinking.

![](https://img.cleberg.io/blog/024-seum/seum_beer.webp)

D posts/025-a-simple-guide-to-the-fediverse.md => posts/025-a-simple-guide-to-the-fediverse.md +0 -81
@@ 1,81 0,0 @@
## What is the Fediverse?

The fediverse is a federated universe of servers commonly used for sharing content, like social media. So, instead of
having to rely on a single organization to run the server (e.g. Facebook), the fediverse is a giant collection of
servers across the world, owned by many people and organizations.

Take a look at this depiction of a federated network - each server in this photo is owned and run by different
administrators/owners. Federated networks are best explained as email servers - you have an email account that exists on
a server (e.g. Outlook), your friend has an account on a different server (e.g. GMail), and another friend has an
account on a third server (e.g. ProtonMail). All three of you can talk and communicate back and forth without having to
be on the same server. However, responsible email admins are there to set rules and control the traffic going in/out of
the server.

![](https://img.cleberg.io/blog/025-a-simple-guide-to-the-fediverse/federated-example.svg)

The main objective of this architecture is to decentralize the control within the internet connections. For example, if
you run your own Mastodon instance, you and your users can't be censored or impacted in any way by authorities of
another Mastodon instance. Some users have praised these features due to recent criticism of popular social media
websites that may be over-censoring their users.

This strategy is great for making sure control of the social web isn't controlled by a single organization, but it also
has some downsides. If I create a Mastodon instance and get a ton of users to sign up, I can shut the server down at any
time. That means you're at risk of losing the content you've created unless you back it up, or the server backs it up
for you. Also, depending on the software used (e.g. Mastodon, Pixelfed, etc.), censorship may still be an issue if the
server admins decide they want to censor their users. Now, censorship isn't always a bad thing and can even benefit the
community as a whole, but you'll want to determine which servers align with your idea of proper censorship.

However, these are risks that we take when we sign up for any online platform. Whatever your reason is for trying out
federated social networks, they are part of the future of the internet. However, the popularity of these services is
still yet to be determined, especially with the increased difficulty understanding and signing up for these platforms.
Perhaps increased regulation and litigation against current social media sites will push more users into the fediverse.

## Federated Alternatives to Popular Sites

The list below is a small guide that will show you federated alternatives to current popular websites. There are many
more out there, so feel free to [send me an email](mailto:hello@cleberg.io) if you have suggestions or want your website
included.

#### Reddit

* [Lemmy](https://lemmy.ml/instances)

#### Twitter/Facebook/Tumblr

* [Mastodon](https://joinmastodon.org)
* [Diaspora](https://diasporafoundation.org)
* [Friendica](https://friendi.ca)
* [GNU Social](https://gnusocial.network)
* [Pleroma](https://pleroma.social)

#### Instagram

* [Pixelfed](https://pixelfed.org)

#### Slack/Discord

* [Matrix](https://element.io)

#### Youtube/Vimeo

* [Peertube](https://joinpeertube.org)

#### Spotify/Soundcloud

* [Funkwhale](https://funkwhale.audio)

#### Podcasting

* [Pubcast](https://pubcast.pub)

#### Medium/Blogger

* [WriteFreely](https://writefreely.org)

## Get Started

The best way to get started is to simply sign up and learn as you go. If you're comfortable signing up through a
Mastodon, Pleroma, or Friendica server, here is [a list of themed servers](https://fediverse.party/en/portal/servers) to
choose from. If you're looking for something else, try a web search for a federated alternative to your favorite sites.

Find a server that focuses on your passions and start there!

D posts/026-secure-your-network-with-the-uncomplicated-firewall.md => posts/026-secure-your-network-with-the-uncomplicated-firewall.md +0 -194
@@ 1,194 0,0 @@
## Uncomplicated Firewall

Uncomplicated Firewall, also known as ufw, is a convenient and beginner-friendly way to enforce OS-level firewall rules.
For those who are hosting servers or any device that is accessible to the world (i.e. by public IP or domain name), it's
critical that a firewall is properly implemented and active.

Ufw is available by default in all Ubuntu installations after 8.04 LTS. For other distributions, you can look to install
ufw or check if there are alternative firewalls installed already. There are usually alternatives available, such as
Fedora's `firewall` and the package available on most distributions: `iptables`. ufw is considered a beginner-friendly
front-end to iptables.

[Gufw](https://gufw.org) is available as a graphical user interface (GUI) application for users who are uncomfortable
setting up a firewall through a terminal.

![](https://img.cleberg.io/blog/026-secure-your-network-with-the-uncomplicated-firewall/gufw.webp)

## Getting Help

If you need help figuring out commands, remember that you can run the `--help` flag to get a list of options.

```bash
sudo ufw --help
```

## Set Default State

The proper way to run a firewall is to set a strict default state and slowly open up ports that you want to allow. This
helps prevent anything malicious from slipping through the cracks. The following command prevents all incoming traffic (
other than the rules we specify later), but you can also set this for outgoing connections, if necessary.

```bash
sudo ufw default deny incoming
```

You should also allow outgoing traffic if you want to allow the device to communicate back to you or other parties. For
example, media servers like Plex need to be able to send out data related to streaming the media.

```bash
sudo ufw default allow outgoing
```

## Adding Port Rules

Now that we've disabled all incoming traffic by default, we need to open up some ports (or else no traffic would be able
to come in). If you need to be able to `ssh` into the machine, you'll need to open up port 22.