Commit 3d4da5ac60
Unsigned
Layout: unified · split
blog/2021-01-01-seum.org +104 −81
| @@ -1,84 +1,107 @@ | |||
| 1 | +++ | 1 | #+title: SEUM: Speedrunners from Hell |
| 2 | date = 2021-01-01 | 2 | #+date: 2021-01-01 |
| 3 | title = "SEUM: Speedrunners from Hell" | 3 | |
| 4 | description = "A video game review for SEUM: Speedrunners from Hell, an incredibly fast-paced mashup of a puzzle game and a free-running game." | 4 | ** Game Description |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: game-description | |
| 7 | ## Game Description | 7 | :ID: 809DA4E6-4E0D-4179-B8FF-6C9E767EC74D |
| 8 | 8 | :END: | |
| 9 | [SEUM: Speedrunners from Hell](https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/) | 9 | [[https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/][SEUM: |
| 10 | is an incredibly fast-paced mashup of a puzzle game and a free-running game. | 10 | Speedrunners from Hell]] is an incredibly fast-paced mashup of a puzzle |
| 11 | Created by [Pine Studio](https://pinestudio.co) in early 2016 and first released | 11 | game and a free-running game. Created by [[https://pinestudio.co][Pine |
| 12 | as a | 12 | Studio]] in early 2016 and first released as a |
| 13 | [demo on GameJolt](https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868), | 13 | [[https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868][demo |
| 14 | this game was successfully green-lit on Steam and has amassed a cult | 14 | on GameJolt]], this game was successfully green-lit on Steam and has |
| 15 | following on multiple consoles. | 15 | amassed a cult following on multiple consoles. |
| 16 | 16 | ||
| 17 | Here's the game description straight from the developers: | 17 | Here's the game description straight from the developers: |
| 18 | 18 | ||
| 19 | > Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners from | 19 | #+begin_quote |
| 20 | > Hell is truly hardcore and focuses on speed and fast reaction. | 20 | Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners |
| 21 | 21 | from Hell is truly hardcore and focuses on speed and fast reaction. | |
| 22 | ## Story | 22 | |
| 23 | 23 | #+end_quote | |
| 24 | SEUM does a great job setting the stage when you open the game for the first | 24 | |
| 25 | time, playing an animated video in the form of comic book images. You see Marty, | 25 | ** Story |
| 26 | the protagonist, sitting around drinking his beer as Satan busts down the door, | 26 | :PROPERTIES: |
| 27 | cuts his hand off, and steals all his beer and bacon. As Satan is leaving, Marty | 27 | :CUSTOM_ID: story |
| 28 | whips a vinyl record across the room and cuts off one of Satan's hands. This | 28 | :ID: BF401145-763D-4399-922B-7D73322B5B40 |
| 29 | hand is what allows you to use all the powers in the game. | 29 | :END: |
| 30 | 30 | SEUM does a great job setting the stage when you open the game for the | |
| 31 | Check out the screenshot below for one of the first panels of the storyline: | 31 | first time, playing an animated video in the form of comic book images. |
| 32 | 32 | You see Marty, the protagonist, sitting around drinking his beer as | |
| 33 |  | 33 | Satan busts down the door, cuts his hand off, and steals all his beer |
| 34 | 34 | and bacon. As Satan is leaving, Marty whips a vinyl record across the | |
| 35 | ## Gameplay | 35 | room and cuts off one of Satan's hands. This hand is what allows you to |
| 36 | 36 | use all the powers in the game. | |
| 37 | To accomplish each level, you will need to get to the final blue portal under a | 37 | |
| 38 | certain time limit. You can beat a level by getting to the portal before the | 38 | Check out the screenshot below for one of the first panels of the |
| 39 | time meter runs out or "Dominate" a level by beating it within a shorter time | 39 | storyline: |
| 40 | limit (noted by a bright red/orange color in the time meter). | 40 | |
| 41 | 41 | #+caption: SEUM Story | |
| 42 | The portal is usually set behind locked gates, protected by dangerous defenses, | 42 | [[https://img.cleberg.net/blog/20210101-seum/seum_story.png]] |
| 43 | or in hard-to-reach places. To reach the portal, you'll need to go through any | 43 | |
| 44 | existing orange portals, light all yellow beacons, avoid things like fireballs | 44 | ** Gameplay |
| 45 | and blades, or use any satanic power orbs lying around. These special abilities | 45 | :PROPERTIES: |
| 46 | include: | 46 | :CUSTOM_ID: gameplay |
| 47 | 47 | :ID: A55CB1AF-E818-4CA7-BF69-E8141369C269 | |
| 48 | - Gravity | 48 | :END: |
| 49 | - Teleport | 49 | To accomplish each level, you will need to get to the final blue portal |
| 50 | - Rewind | 50 | under a certain time limit. You can beat a level by getting to the |
| 51 | - Spawn platform | 51 | portal before the time meter runs out or "Dominate" a level by beating |
| 52 | - Roar (DLC) | 52 | it within a shorter time limit (noted by a bright red/orange color in |
| 53 | - Rocket (DLC) | 53 | the time meter). |
| 54 | - Shadow world (DLC) | 54 | |
| 55 | 55 | The portal is usually set behind locked gates, protected by dangerous | |
| 56 | For the main storyline, there are nine floors to beat. Each floor contains nine | 56 | defenses, or in hard-to-reach places. To reach the portal, you'll need |
| 57 | regular levels, one boss level, and one bonus level; although you don't | 57 | to go through any existing orange portals, light all yellow beacons, |
| 58 | technically need to beat all levels to advance to the next floor. | 58 | avoid things like fireballs and blades, or use any satanic power orbs |
| 59 | 59 | lying around. These special abilities include: | |
| 60 |  | 60 | |
| 61 | 61 | - Gravity | |
| 62 | ## In-Game Screenshots | 62 | - Teleport |
| 63 | 63 | - Rewind | |
| 64 | The main menu gives you plenty of options for tuning your system, playing main | 64 | - Spawn platform |
| 65 | levels, playing the DLC, or exploring the other game modes. | 65 | - Roar (DLC) |
| 66 | 66 | - Rocket (DLC) | |
| 67 |  | 67 | - Shadow world (DLC) |
| 68 | 68 | ||
| 69 | Once you enter a level, you can look around and strategize before starting. | 69 | For the main storyline, there are nine floors to beat. Each floor |
| 70 | Clicking any button will start the menu, and you'll have to restart if you die. | 70 | contains nine regular levels, one boss level, and one bonus level; |
| 71 | One of the great things about SEUM is that it has great keyboard shortcuts. You | 71 | although you don't technically need to beat all levels to advance to the |
| 72 | can quickly smash `R` to restart the level or `M` to return to the level menu. | 72 | next floor. |
| 73 | 73 | ||
| 74 |  | 74 | #+caption: SEUM Floor Menu |
| 75 | 75 | [[https://img.cleberg.net/blog/20210101-seum/seum_floor.png]] | |
| 76 | Once you're able to win a level, you'll see the high scores and can watch | 76 | |
| 77 | replays of the best scores. | 77 | ** In-Game Screenshots |
| 78 | 78 | :PROPERTIES: | |
| 79 |  | 79 | :CUSTOM_ID: in-game-screenshots |
| 80 | 80 | :ID: C779F008-4C8A-4BA4-AA31-60A1BF5A3EE3 | |
| 81 | Each regular level contains a beer in a disguised location that may take some | 81 | :END: |
| 82 | out-of-the-box thinking. | 82 | The main menu gives you plenty of options for tuning your system, |
| 83 | 83 | playing main levels, playing the DLC, or exploring the other game modes. | |
| 84 |  | 84 | |
| 85 | #+caption: SEUM Main Menu | ||
| 86 | [[https://img.cleberg.net/blog/20210101-seum/seum_menu.png]] | ||
| 87 | |||
| 88 | Once you enter a level, you can look around and strategize before | ||
| 89 | starting. Clicking any button will start the menu, and you'll have to | ||
| 90 | restart if you die. One of the great things about SEUM is that it has | ||
| 91 | great keyboard shortcuts. You can quickly smash =R= to restart the level | ||
| 92 | or =M= to return to the level menu. | ||
| 93 | |||
| 94 | #+caption: SEUM Level | ||
| 95 | [[https://img.cleberg.net/blog/20210101-seum/seum_level.png]] | ||
| 96 | |||
| 97 | Once you're able to win a level, you'll see the high scores and can | ||
| 98 | watch replays of the best scores. | ||
| 99 | |||
| 100 | #+caption: SEUM Win | ||
| 101 | [[https://img.cleberg.net/blog/20210101-seum/seum_win.png]] | ||
| 102 | |||
| 103 | Each regular level contains a beer in a disguised location that may take | ||
| 104 | some out-of-the-box thinking. | ||
| 105 | |||
| 106 | #+caption: SEUM Beer | ||
| 107 | [[https://img.cleberg.net/blog/20210101-seum/seum_beer.png]] | ||
blog/2021-01-04-fediverse.org +116 −99
| @@ -1,106 +1,123 @@ | |||
| 1 | +++ | 1 | #+title: A Simple Guide to the Fediverse |
| 2 | date = 2021-01-04 | 2 | #+date: 2021-01-04 |
| 3 | title = "A Simple Guide to the Fediverse" | 3 | |
| 4 | description = "A simple guide to the fediverse, a federated universe of servers commonly used for sharing content, like social media." | 4 | ** What is the Fediverse? |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: what-is-the-fediverse |
| 7 | 7 | :END: | |
| 8 | ## What is the Fediverse? | 8 | The fediverse is a federated universe of servers commonly used for |
| 9 | 9 | sharing content, like social media. So, instead of having to rely on a | |
| 10 | The fediverse is a federated universe of servers commonly used for sharing | 10 | single organization to run the server (e.g. Facebook), the fediverse is |
| 11 | content, like social media. So, instead of having to rely on a single | 11 | a giant collection of servers across the world, owned by many people and |
| 12 | organization to run the server (e.g. Facebook), the fediverse is a giant | 12 | organizations. |
| 13 | collection of servers across the world, owned by many people and organizations. | 13 | |
| 14 | 14 | Take a look at this depiction of a federated network. Each server in | |
| 15 | Take a look at this depiction of a federated network. | 15 | this photo is owned and run by different administrators/owners. |
| 16 | Each server in this photo is owned and run by different | 16 | Federated networks are best explained as email servers: you have an |
| 17 | administrators/owners. | 17 | email account that exists on a server (e.g. Outlook), your friend has an |
| 18 | Federated networks are best explained as email servers: you have an email | 18 | account on a different server (e.g. GMail), and another friend has an |
| 19 | account that exists on a server (e.g. Outlook), your friend has an account on | 19 | account on a third server (e.g. ProtonMail). All three of you can talk |
| 20 | a different server (e.g. GMail), and another friend has an account on a | 20 | and communicate back and forth without having to be on the same server. |
| 21 | third server (e.g. ProtonMail). | 21 | However, responsible email admins are there to set rules and control the |
| 22 | All three of you can talk and communicate back and forth without having to | ||
| 23 | be on the same server. | ||
| 24 | However, responsible email admins are there to set rules and control the | ||
| 25 | traffic going in/out of the server. | 22 | traffic going in/out of the server. |
| 26 | 23 | ||
| 27 |  | 24 | #+caption: Federated services diagram |
| 25 | [[https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg]] | ||
| 28 | 26 | ||
| 29 | The main objective of this architecture is to decentralize the control within | 27 | The main objective of this architecture is to decentralize the control |
| 30 | the internet connections. For example, if you run your own Mastodon instance, | 28 | within the internet connections. For example, if you run your own |
| 31 | you and your users can't be censored or impacted in any way by authorities of | 29 | Mastodon instance, you and your users can't be censored or impacted in |
| 32 | another Mastodon instance. Some users have praised these features due to recent | 30 | any way by authorities of another Mastodon instance. Some users have |
| 33 | criticism of popular social media websites that may be over-censoring their | 31 | praised these features due to recent criticism of popular social media |
| 34 | users. | 32 | websites that may be over-censoring their users. |
| 35 | 33 | ||
| 36 | This strategy is great for making sure control of the social web isn't | 34 | This strategy is great for making sure control of the social web isn't |
| 37 | controlled by a single organization, but it also has some downsides. If I create | 35 | controlled by a single organization, but it also has some downsides. If |
| 38 | a Mastodon instance and get a ton of users to sign up, I can shut the server | 36 | I create a Mastodon instance and get a ton of users to sign up, I can |
| 39 | down at any time. That means you're at risk of losing the content you've created | 37 | shut the server down at any time. That means you're at risk of losing |
| 40 | unless you back it up, or the server backs it up for you. Also, depending on the | 38 | the content you've created unless you back it up, or the server backs it |
| 41 | software used (e.g. Mastodon, Pixelfed, etc.), censorship may still be an issue | 39 | up for you. Also, depending on the software used (e.g. Mastodon, |
| 42 | if the server admins decide they want to censor their users. Now, censorship | 40 | Pixelfed, etc.), censorship may still be an issue if the server admins |
| 43 | isn't always a bad thing and can even benefit the community as a whole, but | 41 | decide they want to censor their users. Now, censorship isn't always a |
| 44 | you'll want to determine which servers align with your idea of proper | 42 | bad thing and can even benefit the community as a whole, but you'll want |
| 45 | censorship. | 43 | to determine which servers align with your idea of proper censorship. |
| 46 | 44 | ||
| 47 | However, these are risks that we take when we sign up for any online platform. | 45 | However, these are risks that we take when we sign up for any online |
| 48 | Whatever your reason is for trying out federated social networks, they are part | 46 | platform. Whatever your reason is for trying out federated social |
| 49 | of the future of the internet. However, the popularity of these services is | 47 | networks, they are part of the future of the internet. However, the |
| 50 | still yet to be determined, especially with the increased difficulty | 48 | popularity of these services is still yet to be determined, especially |
| 51 | understanding and signing up for these platforms. Perhaps increased regulation | 49 | with the increased difficulty understanding and signing up for these |
| 52 | and litigation against current social media sites will push more users into the | 50 | platforms. Perhaps increased regulation and litigation against current |
| 53 | fediverse. | 51 | social media sites will push more users into the fediverse. |
| 54 | 52 | ||
| 55 | ## Federated Alternatives to Popular Sites | 53 | ** Federated Alternatives to Popular Sites |
| 56 | 54 | :PROPERTIES: | |
| 57 | The list below is a small guide that will show you federated alternatives to | 55 | :CUSTOM_ID: federated-alternatives-to-popular-sites |
| 58 | current popular websites. | 56 | :END: |
| 59 | There are many more out there, so go and explore: you might just find the | 57 | The list below is a small guide that will show you federated |
| 60 | perfect home. | 58 | alternatives to current popular websites. There are many more out there, |
| 61 | 59 | so go and explore: you might just find the perfect home. | |
| 62 | 60 | ||
| 63 | 61 | ||
| 64 | - [Lemmy](https://lemmy.ml/instances) | 62 | :PROPERTIES: |
| 65 | 63 | :CUSTOM_ID: reddit | |
| 66 | ### Twitter/Facebook/Tumblr | 64 | :END: |
| 67 | 65 | - [[https://lemmy.ml/instances][Lemmy]] | |
| 68 | - [Mastodon](https://joinmastodon.org) | 66 | |
| 69 | - [Diaspora](https://diasporafoundation.org) | 67 | *** Twitter/Facebook/Tumblr |
| 70 | - [Friendica](https://friendi.ca) | 68 | :PROPERTIES: |
| 71 | - [GNU Social](https://gnusocial.network) | 69 | :CUSTOM_ID: twitterfacebooktumblr |
| 72 | - [Pleroma](https://pleroma.social) | 70 | :END: |
| 73 | 71 | - [[https://joinmastodon.org][Mastodon]] | |
| 74 | 72 | - [[https://diasporafoundation.org][Diaspora]] | |
| 75 | 73 | - [[https://friendi.ca][Friendica]] | |
| 76 | - [Pixelfed](https://pixelfed.org) | 74 | - [[https://gnusocial.network][GNU Social]] |
| 77 | 75 | - [[https://pleroma.social][Pleroma]] | |
| 78 | ### Slack/Discord | 76 | |
| 79 | 77 | ||
| 80 | - [Matrix](https://element.io) | 78 | :PROPERTIES: |
| 81 | 79 | :CUSTOM_ID: instagram | |
| 82 | ### Youtube/Vimeo | 80 | :END: |
| 83 | 81 | - [[https://pixelfed.org][Pixelfed]] | |
| 84 | - [Peertube](https://joinpeertube.org) | 82 | |
| 85 | 83 | *** Slack/Discord | |
| 86 | ### Spotify/Soundcloud | 84 | :PROPERTIES: |
| 87 | 85 | :CUSTOM_ID: slackdiscord | |
| 88 | - [Funkwhale](https://funkwhale.audio) | 86 | :END: |
| 89 | 87 | - [[https://element.io][Matrix]] | |
| 90 | ### Podcasting | 88 | |
| 91 | 89 | *** Youtube/Vimeo | |
| 92 | - [Pubcast](https://pubcast.pub) | 90 | :PROPERTIES: |
| 93 | 91 | :CUSTOM_ID: youtubevimeo | |
| 94 | ### Medium/Blogger | 92 | :END: |
| 95 | 93 | - [[https://joinpeertube.org][Peertube]] | |
| 96 | - [WriteFreely](https://writefreely.org) | 94 | |
| 97 | 95 | *** Spotify/Soundcloud | |
| 98 | ## Get Started | 96 | :PROPERTIES: |
| 99 | 97 | :CUSTOM_ID: spotifysoundcloud | |
| 100 | The best way to get started is to simply sign up and learn as you go. If you're | 98 | :END: |
| 101 | comfortable signing up through a Mastodon, Pleroma, or Friendica server, here is | 99 | - [[https://funkwhale.audio][Funkwhale]] |
| 102 | [a list of themed servers](https://fediverse.party/en/portal/servers) to choose | 100 | |
| 103 | from. If you're looking for something else, try a web search for a federated | 101 | *** Podcasting |
| 104 | alternative to your favorite sites. | 102 | :PROPERTIES: |
| 103 | :CUSTOM_ID: podcasting | ||
| 104 | :END: | ||
| 105 | - [[https://pubcast.pub][Pubcast]] | ||
| 106 | |||
| 107 | *** Medium/Blogger | ||
| 108 | :PROPERTIES: | ||
| 109 | :CUSTOM_ID: mediumblogger | ||
| 110 | :END: | ||
| 111 | - [[https://writefreely.org][WriteFreely]] | ||
| 112 | |||
| 113 | ** Get Started | ||
| 114 | :PROPERTIES: | ||
| 115 | :CUSTOM_ID: get-started | ||
| 116 | :END: | ||
| 117 | The best way to get started is to simply sign up and learn as you go. If | ||
| 118 | you're comfortable signing up through a Mastodon, Pleroma, or Friendica | ||
| 119 | server, here is [[https://fediverse.party/en/portal/servers][a list of | ||
| 120 | themed servers]] to choose from. If you're looking for something else, | ||
| 121 | try a web search for a federated alternative to your favorite sites. | ||
| 105 | 122 | ||
| 106 | Find a server that focuses on your passions and start there! | 123 | Find a server that focuses on your passions and start there! |
blog/2021-01-07-ufw.org +144 −127
| @@ -1,158 +1,173 @@ | |||
| 1 | +++ | 1 | #+title: Secure Your Network with the Uncomplicated Firewall |
| 2 | date = 2021-01-07 | 2 | #+date: 2021-01-07 |
| 3 | title = "Secure Your Network with the Uncomplicated Firewall" | 3 | |
| 4 | description = "Learn how to use the UFW, one of the simplest and most widely available firewalls on Linux." | 4 | ** Uncomplicated Firewall |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: uncomplicated-firewall |
| 7 | 7 | :END: | |
| 8 | ## Uncomplicated Firewall | 8 | Uncomplicated Firewall, also known as ufw, is a convenient and |
| 9 | 9 | beginner-friendly way to enforce OS-level firewall rules. For those who | |
| 10 | Uncomplicated Firewall, also known as ufw, is a convenient and beginner-friendly | 10 | are hosting servers or any device that is accessible to the world (i.e., |
| 11 | way to enforce OS-level firewall rules. For those who are hosting servers or any | 11 | by public IP or domain name), it's critical that a firewall is properly |
| 12 | device that is accessible to the world (i.e., by public IP or domain name), it's | 12 | implemented and active. |
| 13 | critical that a firewall is properly implemented and active. | 13 | |
| 14 | 14 | Ufw is available by default in all Ubuntu installations after 8.04 LTS. | |
| 15 | Ufw is available by default in all Ubuntu installations after 8.04 LTS. For | 15 | For other distributions, you can look to install ufw or check if there |
| 16 | other distributions, you can look to install ufw or check if there are | 16 | are alternative firewalls installed already. There are usually |
| 17 | alternative firewalls installed already. There are usually alternatives | 17 | alternatives available, such as Fedora's =firewall= and the package |
| 18 | available, such as Fedora's `firewall` and the package available on most | 18 | available on most distributions: =iptables=. Ufw is considered a |
| 19 | distributions: `iptables`. Ufw is considered a beginner-friendly front-end to | 19 | beginner-friendly front-end to iptables. |
| 20 | iptables. | 20 | |
| 21 | 21 | [[https://gufw.org][Gufw]] is available as a graphical user interface | |
| 22 | [Gufw](https://gufw.org) is available as a graphical user interface (GUI) | 22 | (GUI) application for users who are uncomfortable setting up a firewall |
| 23 | application for users who are uncomfortable setting up a firewall through a | 23 | through a terminal. |
| 24 | terminal. | 24 | |
| 25 | 25 | [[https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png]] | |
| 26 |  | 26 | |
| 27 | 27 | ** Getting Help | |
| 28 | ## Getting Help | 28 | :PROPERTIES: |
| 29 | 29 | :CUSTOM_ID: getting-help | |
| 30 | If you need help figuring out commands, remember that you can run the `--help` | 30 | :END: |
| 31 | flag to get a list of options. | 31 | If you need help figuring out commands, remember that you can run the |
| 32 | 32 | =--help= flag to get a list of options. | |
| 33 | ```sh | 33 | |
| 34 | #+begin_src sh | ||
| 34 | sudo ufw --help | 35 | sudo ufw --help |
| 35 | ``` | 36 | #+end_src |
| 36 | 37 | ||
| 37 | ## Set Default State | 38 | ** Set Default State |
| 38 | 39 | :PROPERTIES: | |
| 39 | The proper way to run a firewall is to set a strict default state and slowly | 40 | :CUSTOM_ID: set-default-state |
| 40 | open up ports that you want to allow. This helps prevent anything malicious from | 41 | :END: |
| 41 | slipping through the cracks. The following command prevents all incoming traffic | 42 | The proper way to run a firewall is to set a strict default state and |
| 42 | (other than the rules we specify later), but you can also set this for outgoing | 43 | slowly open up ports that you want to allow. This helps prevent anything |
| 43 | connections, if necessary. | 44 | malicious from slipping through the cracks. The following command |
| 44 | 45 | prevents all incoming traffic (other than the rules we specify later), | |
| 45 | ```sh | 46 | but you can also set this for outgoing connections, if necessary. |
| 47 | |||
| 48 | #+begin_src sh | ||
| 46 | sudo ufw default deny incoming | 49 | sudo ufw default deny incoming |
| 47 | ``` | 50 | #+end_src |
| 48 | 51 | ||
| 49 | You should also allow outgoing traffic if you want to allow the device to | 52 | You should also allow outgoing traffic if you want to allow the device |
| 50 | communicate back to you or other parties. For example, media servers like Plex | 53 | to communicate back to you or other parties. For example, media servers |
| 51 | need to be able to send out data related to streaming the media. | 54 | like Plex need to be able to send out data related to streaming the |
| 55 | media. | ||
| 52 | 56 | ||
| 53 | ```sh | 57 | #+begin_src sh |
| 54 | sudo ufw default allow outgoing | 58 | sudo ufw default allow outgoing |
| 55 | ``` | 59 | #+end_src |
| 56 | |||
| 57 | ## Adding Port Rules | ||
| 58 | 60 | ||
| 59 | Now that we've disabled all incoming traffic by default, we need to open up some | 61 | ** Adding Port Rules |
| 60 | ports (or else no traffic would be able to come in). If you need to be able to | 62 | :PROPERTIES: |
| 61 | `ssh` into the machine, you'll need to open up port 22. | 63 | :CUSTOM_ID: adding-port-rules |
| 64 | :END: | ||
| 65 | Now that we've disabled all incoming traffic by default, we need to open | ||
| 66 | up some ports (or else no traffic would be able to come in). If you need | ||
| 67 | to be able to =ssh= into the machine, you'll need to open up port 22. | ||
| 62 | 68 | ||
| 63 | ```sh | 69 | #+begin_src sh |
| 64 | sudo ufw allow 22 | 70 | sudo ufw allow 22 |
| 65 | ``` | 71 | #+end_src |
| 66 | 72 | ||
| 67 | You can also issue more restrictive rules. The following rule will allow `ssh` | 73 | You can also issue more restrictive rules. The following rule will allow |
| 68 | connections only from machines on the local subnet. | 74 | =ssh= connections only from machines on the local subnet. |
| 69 | 75 | ||
| 70 | ```sh | 76 | #+begin_src sh |
| 71 | sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22 | 77 | sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22 |
| 72 | ``` | 78 | #+end_src |
| 73 | 79 | ||
| 74 | If you need to set a rule that isn't tcp, just append your connection type to | 80 | If you need to set a rule that isn't tcp, just append your connection |
| 75 | the end of the rule. | 81 | type to the end of the rule. |
| 76 | 82 | ||
| 77 | ```sh | 83 | #+begin_src sh |
| 78 | sudo ufw allow 1900/udp | 84 | sudo ufw allow 1900/udp |
| 79 | ``` | 85 | #+end_src |
| 80 | |||
| 81 | ## Enable ufw | ||
| 82 | 86 | ||
| 87 | ** Enable ufw | ||
| 88 | :PROPERTIES: | ||
| 89 | :CUSTOM_ID: enable-ufw | ||
| 90 | :END: | ||
| 83 | Now that the firewall is configured and ready to go, you can enable the | 91 | Now that the firewall is configured and ready to go, you can enable the |
| 84 | firewall. | 92 | firewall. |
| 85 | 93 | ||
| 86 | ```sh | 94 | #+begin_src sh |
| 87 | sudo ufw enable | 95 | sudo ufw enable |
| 88 | ``` | 96 | #+end_src |
| 89 | 97 | ||
| 90 | A restart may be required for the firewall to begin operating. | 98 | A restart may be required for the firewall to begin operating. |
| 91 | 99 | ||
| 92 | ```sh | 100 | #+begin_src sh |
| 93 | sudo reboot now | 101 | sudo reboot now |
| 94 | ``` | 102 | #+end_src |
| 95 | 103 | ||
| 96 | ## Checking Status | 104 | ** Checking Status |
| 105 | :PROPERTIES: | ||
| 106 | :CUSTOM_ID: checking-status | ||
| 107 | :END: | ||
| 108 | Now that the firewall is enabled, let's check and see what the rules | ||
| 109 | look like. | ||
| 97 | 110 | ||
| 98 | Now that the firewall is enabled, let's check and see what the rules look like. | 111 | #+begin_src sh |
| 99 | |||
| 100 | ```sh | ||
| 101 | sudo ufw status numbered | 112 | sudo ufw status numbered |
| 102 | ``` | 113 | #+end_src |
| 103 | 114 | ||
| 104 | ```txt | 115 | #+begin_src txt |
| 105 | Status: active | 116 | Status: active |
| 106 | 117 | ||
| 107 | To Action From | 118 | To Action From |
| 108 | -- ------ ---- | 119 | -- ------ ---- |
| 109 | [ 1] 22 ALLOW IN Anywhere | 120 | [ 1] 22 ALLOW IN Anywhere |
| 110 | [ 2] 22 (v6) ALLOW IN Anywhere (v6) | 121 | [ 2] 22 (v6) ALLOW IN Anywhere (v6) |
| 111 | ``` | 122 | #+end_src |
| 112 | |||
| 113 | ## Deleting Rules | ||
| 114 | 123 | ||
| 115 | If you need to delete a rule, you need to know the number associated with that | 124 | ** Deleting Rules |
| 116 | rule. | 125 | :PROPERTIES: |
| 117 | Let's delete the first rule in the table above. | 126 | :CUSTOM_ID: deleting-rules |
| 118 | You'll be asked to confirm the deletion as part of this process. | 127 | :END: |
| 128 | If you need to delete a rule, you need to know the number associated | ||
| 129 | with that rule. Let's delete the first rule in the table above. You'll | ||
| 130 | be asked to confirm the deletion as part of this process. | ||
| 119 | 131 | ||
| 120 | ```sh | 132 | #+begin_src sh |
| 121 | sudo ufw delete 1 | 133 | sudo ufw delete 1 |
| 122 | ``` | 134 | #+end_src |
| 123 | 135 | ||
| 124 | ## Managing App Rules | 136 | ** Managing App Rules |
| 125 | 137 | :PROPERTIES: | |
| 126 | Luckily, there's a convenient way for installed applications to create files | 138 | :CUSTOM_ID: managing-app-rules |
| 127 | that ufw can easily implement so that you don't have to search and find which | 139 | :END: |
| 128 | ports your application requires. To see if your device has any applications with | 140 | Luckily, there's a convenient way for installed applications to create |
| 129 | pre-installed ufw rules, execute the following command: | 141 | files that ufw can easily implement so that you don't have to search and |
| 130 | 142 | find which ports your application requires. To see if your device has | |
| 131 | ```sh | 143 | any applications with pre-installed ufw rules, execute the following |
| 144 | command: | ||
| 145 | |||
| 146 | #+begin_src sh | ||
| 132 | sudo ufw app list | 147 | sudo ufw app list |
| 133 | ``` | 148 | #+end_src |
| 134 | 149 | ||
| 135 | The results should look something like this: | 150 | The results should look something like this: |
| 136 | 151 | ||
| 137 | ```txt | 152 | #+begin_src txt |
| 138 | Available applications: | 153 | Available applications: |
| 139 | OpenSSH | 154 | OpenSSH |
| 140 | Samba | 155 | Samba |
| 141 | plexmediaserver | 156 | plexmediaserver |
| 142 | plexmediaserver-all | 157 | plexmediaserver-all |
| 143 | plexmediaserver-dlna | 158 | plexmediaserver-dlna |
| 144 | ``` | 159 | #+end_src |
| 145 | 160 | ||
| 146 | If you want to get more information on a specific app rule, use the `info` | 161 | If you want to get more information on a specific app rule, use the |
| 147 | command. | 162 | =info= command. |
| 148 | 163 | ||
| 149 | ```sh | 164 | #+begin_src sh |
| 150 | sudo ufw app info plexmediaserver-dlna | 165 | sudo ufw app info plexmediaserver-dlna |
| 151 | ``` | 166 | #+end_src |
| 152 | 167 | ||
| 153 | You'll get a blurb of info back like this: | 168 | You'll get a blurb of info back like this: |
| 154 | 169 | ||
| 155 | ```txt | 170 | #+begin_src txt |
| 156 | Profile: plexmediaserver-dlna | 171 | Profile: plexmediaserver-dlna |
| 157 | Title: Plex Media Server (DLNA) | 172 | Title: Plex Media Server (DLNA) |
| 158 | Description: The Plex Media Server (additional DLNA capability only) | 173 | Description: The Plex Media Server (additional DLNA capability only) |
| @@ -160,29 +175,31 @@ Description: The Plex Media Server (additional DLNA capability only) | |||
| 160 | Ports: | 175 | Ports: |
| 161 | 1900/udp | 176 | 1900/udp |
| 162 | 32469/tcp | 177 | 32469/tcp |
| 163 | ``` | 178 | #+end_src |
| 164 | 179 | ||
| 165 | You can add or delete app rules the same way that you'd add or delete specific | 180 | You can add or delete app rules the same way that you'd add or delete |
| 166 | port rules. | 181 | specific port rules. |
| 167 | 182 | ||
| 168 | ```sh | 183 | #+begin_src sh |
| 169 | sudo ufw allow plexmediaserver-dlna | 184 | sudo ufw allow plexmediaserver-dlna |
| 170 | ``` | 185 | #+end_src |
| 171 | 186 | ||
| 172 | ```sh | 187 | #+begin_src sh |
| 173 | sudo ufw delete RULE|NUM | 188 | sudo ufw delete RULE|NUM |
| 174 | ``` | 189 | #+end_src |
| 175 | |||
| 176 | ## Creating App Rules | ||
| 177 | 190 | ||
| 178 | If you'd like to create you own app rule, you'll need to create a file in the | 191 | ** Creating App Rules |
| 179 | `/etc/ufw/applications.d` directory. Within the file you create, you need to | 192 | :PROPERTIES: |
| 180 | make sure the content is properly formatted. | 193 | :CUSTOM_ID: creating-app-rules |
| 194 | :END: | ||
| 195 | If you'd like to create you own app rule, you'll need to create a file | ||
| 196 | in the =/etc/ufw/applications.d= directory. Within the file you create, | ||
| 197 | you need to make sure the content is properly formatted. | ||
| 181 | 198 | ||
| 182 | For example, here are the contents my `plexmediaserver` file, which creates | 199 | For example, here are the contents my =plexmediaserver= file, which |
| 183 | three distinct app rules for ufw: | 200 | creates three distinct app rules for ufw: |
| 184 | 201 | ||
| 185 | ```config | 202 | #+begin_src config |
| 186 | [plexmediaserver] | 203 | [plexmediaserver] |
| 187 | title=Plex Media Server (Standard) | 204 | title=Plex Media Server (Standard) |
| 188 | description=The Plex Media Server | 205 | description=The Plex Media Server |
| @@ -197,24 +214,24 @@ ports=1900/udp|32469/tcp | |||
| 197 | title=Plex Media Server (Standard + DLNA) | 214 | title=Plex Media Server (Standard + DLNA) |
| 198 | description=The Plex Media Server (with additional DLNA capability) | 215 | description=The Plex Media Server (with additional DLNA capability) |
| 199 | ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp | 216 | ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp |
| 200 | ``` | 217 | #+end_src |
| 201 | 218 | ||
| 202 | So, if I wanted to create a custom app rule called "mycustomrule," I'd create a | 219 | So, if I wanted to create a custom app rule called "mycustomrule," I'd |
| 203 | file and add my content like this: | 220 | create a file and add my content like this: |
| 204 | 221 | ||
| 205 | ```sh | 222 | #+begin_src sh |
| 206 | sudo nano /etc/ufw/applications.d/mycustomrule | 223 | sudo nano /etc/ufw/applications.d/mycustomrule |
| 207 | ``` | 224 | #+end_src |
| 208 | 225 | ||
| 209 | ```config | 226 | #+begin_src config |
| 210 | [mycustomrule] | 227 | [mycustomrule] |
| 211 | title=My Custom Rule | 228 | title=My Custom Rule |
| 212 | description=This is a temporary ufw app rule. | 229 | description=This is a temporary ufw app rule. |
| 213 | ports=88/tcp|9100/udp | 230 | ports=88/tcp|9100/udp |
| 214 | ``` | 231 | #+end_src |
| 215 | 232 | ||
| 216 | Then, I would just enable this rule in ufw. | 233 | Then, I would just enable this rule in ufw. |
| 217 | 234 | ||
| 218 | ```sh | 235 | #+begin_src sh |
| 219 | sudo ufw allow mycustomrule | 236 | sudo ufw allow mycustomrule |
| 220 | ``` | 237 | #+end_src |
blog/2021-02-19-macos.org +172 −152
| @@ -1,205 +1,225 @@ | |||
| 1 | +++ | 1 | #+title: macOS: Testing Out A New OS |
| 2 | date = 2021-02-19 | 2 | #+date: 2021-02-19 |
| 3 | title = "macOS: Testing Out A New OS" | 3 | |
| 4 | description = "My personal experience with macOS, from the perspective of a Linux junkie." | 4 | ** Diving into macOS |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: diving-into-macos |
| 7 | 7 | :END: | |
| 8 | ## Diving into macOS | 8 | After spending nearly 15 years working with Windows and 8 years on |
| 9 | 9 | Linux, I have experienced macOS for the first time. By chance, my spouse | |
| 10 | After spending nearly 15 years working with Windows and 8 years on Linux, I | 10 | happened to buy a new MacBook and gifted me their 2013 model. Of course, |
| 11 | have experienced macOS for the first time. | 11 | I still consider my Linux desktop to be my daily driver and keep Windows |
| 12 | By chance, my spouse happened to buy a new MacBook and gifted me their 2013 | 12 | around for gaming needs, but over the past week I've found myself using |
| 13 | model. | 13 | the MacBook more and more for things that don't require gaming specs or |
| 14 | Of course, I still consider my Linux desktop to be my daily driver and keep | 14 | advanced dev tools. |
| 15 | Windows around for gaming needs, but over the past week I've found myself using | 15 | |
| 16 | the MacBook more and more for things that don't require gaming specs or advanced | 16 | ** Initial Thoughts |
| 17 | dev tools. | 17 | :PROPERTIES: |
| 18 | 18 | :CUSTOM_ID: initial-thoughts | |
| 19 | ## Initial Thoughts | 19 | :END: |
| 20 | 20 | Before I move on to the technical aspects of my set-up, I want to take | |
| 21 | Before I move on to the technical aspects of my set-up, I want to take some time | 21 | some time and express my thoughts on the overall OS. |
| 22 | and express my thoughts on the overall OS. | 22 | |
| 23 | 23 | #+caption: macOS Desktop | |
| 24 |  | 24 | [[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png]] |
| 25 | 25 | ||
| 26 | As expected, the initial computer setup is a breeze with Mac's guided GUI | 26 | As expected, the initial computer setup is a breeze with Mac's guided |
| 27 | installer. | 27 | GUI installer. |
| 28 | 28 | ||
| 29 | The desktop itself reminds me of GNOME more than anything else I've seen: even | 29 | The desktop itself reminds me of GNOME more than anything else I've |
| 30 | Pantheon from [ElementaryOS](https://elementary.io/), which people commonly | 30 | seen: even Pantheon from [[https://elementary.io/][ElementaryOS]], which |
| 31 | refer to as the closest Linux distro to macOS. The desktop toolbar is great and | 31 | people commonly refer to as the closest Linux distro to macOS. The |
| 32 | far surpasses the utility of the GNOME toolbar due to the fact that the | 32 | desktop toolbar is great and far surpasses the utility of the GNOME |
| 33 | extensions and icons _actually work_. I launch macOS and immediately see my | 33 | toolbar due to the fact that the extensions and icons /actually work/. I |
| 34 | shortcuts for Tresorit, Bitwarden, and Mullvad pop up as the computer loads. | 34 | launch macOS and immediately see my shortcuts for Tresorit, Bitwarden, |
| 35 | 35 | and Mullvad pop up as the computer loads. | |
| 36 | Even further, the app dock is very useful and will be yet another familiarity | 36 | |
| 37 | for GNOME users. I know many people like panels instead of docks, but I've | 37 | Even further, the app dock is very useful and will be yet another |
| 38 | always found docks to have a more pleasing UI. However, I had to disable the | 38 | familiarity for GNOME users. I know many people like panels instead of |
| 39 | "Show recent applications in Dock" preference; I can't stand items taking up | 39 | docks, but I've always found docks to have a more pleasing UI. However, |
| 40 | precious screen space if I'm not currently using them. On that same note, it's | 40 | I had to disable the "Show recent applications in Dock" preference; I |
| 41 | taking me some time to get use to the fact that I have to manually quit an app | 41 | can't stand items taking up precious screen space if I'm not currently |
| 42 | or else it will still stay open/active in the dock, even if I've closed out all | 42 | using them. On that same note, it's taking me some time to get use to |
| 43 | windows for that app (e.g. Firefox). | 43 | the fact that I have to manually quit an app or else it will still stay |
| 44 | 44 | open/active in the dock, even if I've closed out all windows for that | |
| 45 | Overall, I'm having a lot of fun and for users who spend a large majority of | 45 | app (e.g. Firefox). |
| 46 | their time performing basic tasks like web browsing, writing, watching media, | 46 | |
| 47 | etc., macOS is a fantastic option. | 47 | Overall, I'm having a lot of fun and for users who spend a large |
| 48 | majority of their time performing basic tasks like web browsing, | ||
| 49 | writing, watching media, etc., macOS is a fantastic option. | ||
| 48 | 50 | ||
| 49 | The rest of this post explains the technicalities of how I set up my CLI | 51 | The rest of this post explains the technicalities of how I set up my CLI |
| 50 | environment to make me feel more at-home, similar to the environments I set up | 52 | environment to make me feel more at-home, similar to the environments I |
| 51 | on Fedora, Ubuntu, etc. | 53 | set up on Fedora, Ubuntu, etc. |
| 52 | 54 | ||
| 53 | ## Making it Feel Like Home | 55 | ** Making it Feel Like Home |
| 54 | 56 | :PROPERTIES: | |
| 55 | If you're someone who uses Linux primarily, no doubt your first thought when | 57 | :CUSTOM_ID: making-it-feel-like-home |
| 56 | booting macOS will be the same as mine was: "Where is the terminal and how do I | 58 | :END: |
| 57 | set up my favorite utilities?" | 59 | If you're someone who uses Linux primarily, no doubt your first thought |
| 58 | 60 | when booting macOS will be the same as mine was: "Where is the terminal | |
| 59 | Luckily, macOS hasn't completely hidden away the development tools from the | 61 | and how do I set up my favorite utilities?" |
| 60 | average user. You can easily find the Terminal app in the Launchpad area, but | 62 | |
| 61 | it's probably not what you're used to. I was surprised (and happy) to see that | 63 | Luckily, macOS hasn't completely hidden away the development tools from |
| 62 | the default shell is `zsh`, the shell I use on all of my Linux distros. However, | 64 | the average user. You can easily find the Terminal app in the Launchpad |
| 63 | the commands are not the same - even the ones you may think are native to the | 65 | area, but it's probably not what you're used to. I was surprised (and |
| 64 | shell. Commands like `dir` do not exist, so other native commands like `ls -la` | 66 | happy) to see that the default shell is =zsh=, the shell I use on all of |
| 65 | or `pwd` are more useful here. | 67 | my Linux distros. However, the commands are not the same - even the ones |
| 66 | 68 | you may think are native to the shell. Commands like =dir= do not exist, | |
| 67 | With only a few minutes of installing and tweaking a few packages, I was able to | 69 | so other native commands like =ls -la= or =pwd= are more useful here. |
| 68 | recreate a terminal environment that I feel very comfortable using. See the | 70 | |
| 69 | image below for a preview of the iTerm2 app with a split view between my macOS | 71 | With only a few minutes of installing and tweaking a few packages, I was |
| 70 | desktop shell and an SSH session into my server. | 72 | able to recreate a terminal environment that I feel very comfortable |
| 71 | 73 | using. See the image below for a preview of the iTerm2 app with a split | |
| 72 |  | 74 | view between my macOS desktop shell and an SSH session into my server. |
| 73 | 75 | ||
| 74 | ## Xcode | 76 | #+caption: iTerm2 |
| 75 | 77 | [[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png]] | |
| 76 | My first step was to search the web for any hints on how to get `zsh` back up to | 78 | |
| 77 | the state I like, with extensions, themes, etc. My first step was to install the | 79 | ** Xcode |
| 78 | CLI tools for [Xcode](https://developer.apple.com/xcode/), Apple's suite of | 80 | :PROPERTIES: |
| 81 | :CUSTOM_ID: xcode | ||
| 82 | :END: | ||
| 83 | My first step was to search the web for any hints on how to get =zsh= | ||
| 84 | back up to the state I like, with extensions, themes, etc. My first step | ||
| 85 | was to install the CLI tools for | ||
| 86 | [[https://developer.apple.com/xcode/][Xcode]], Apple's suite of | ||
| 79 | development tools. | 87 | development tools. |
| 80 | 88 | ||
| 81 | ```sh | 89 | #+begin_src sh |
| 82 | sudo xcode-select -r | 90 | sudo xcode-select -r |
| 83 | ``` | 91 | #+end_src |
| 84 | 92 | ||
| 85 | ```sh | 93 | #+begin_src sh |
| 86 | sudo xcode-select --install | 94 | sudo xcode-select --install |
| 87 | ``` | 95 | #+end_src |
| 88 | |||
| 89 | ## Homebrew | ||
| 90 | 96 | ||
| 91 | Next up is to install [Homebrew](https://brew.sh), a nifty package manager for | 97 | ** Homebrew |
| 92 | macOS. | 98 | :PROPERTIES: |
| 99 | :CUSTOM_ID: homebrew | ||
| 100 | :END: | ||
| 101 | Next up is to install [[https://brew.sh][Homebrew]], a nifty package | ||
| 102 | manager for macOS. | ||
| 93 | 103 | ||
| 94 | ```sh | 104 | #+begin_src sh |
| 95 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | 105 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 96 | ``` | 106 | #+end_src |
| 97 | 107 | ||
| 98 | I ran into a permission error when installing Homebrew: | 108 | I ran into a permission error when installing Homebrew: |
| 99 | 109 | ||
| 100 | ```sh | 110 | #+begin_src sh |
| 101 | Error: Failed to link all completions, docs and manpages: | 111 | Error: Failed to link all completions, docs and manpages: |
| 102 | Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew) | 112 | Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew) |
| 103 | Failed during: /usr/local/bin/brew update --force --quiet | 113 | Failed during: /usr/local/bin/brew update --force --quiet |
| 104 | ``` | 114 | #+end_src |
| 105 | 115 | ||
| 106 | I found that the following permission modification worked like a charm. However, | 116 | I found that the following permission modification worked like a charm. |
| 107 | I noted that some users online discussed the fact that this solution may not | 117 | However, I noted that some users online discussed the fact that this |
| 108 | work if your system has multiple users who use Homebrew. | 118 | solution may not work if your system has multiple users who use |
| 119 | Homebrew. | ||
| 109 | 120 | ||
| 110 | ```sh | 121 | #+begin_src sh |
| 111 | sudo chown -R $(whoami) $(brew --prefix)/* | 122 | sudo chown -R $(whoami) $(brew --prefix)/* |
| 112 | ``` | 123 | #+end_src |
| 113 | 124 | ||
| 114 | Next up is to ensure Homebrew is updated and cleaned. | 125 | Next up is to ensure Homebrew is updated and cleaned. |
| 115 | 126 | ||
| 116 | ```sh | 127 | #+begin_src sh |
| 117 | brew update | 128 | brew update |
| 118 | ``` | 129 | #+end_src |
| 119 | 130 | ||
| 120 | ```sh | 131 | #+begin_src sh |
| 121 | brew cleanup | 132 | brew cleanup |
| 122 | ``` | 133 | #+end_src |
| 123 | 134 | ||
| 124 | ## iTerm2 | 135 | ** iTerm2 |
| 136 | :PROPERTIES: | ||
| 137 | :CUSTOM_ID: iterm2 | ||
| 138 | :END: | ||
| 139 | Now that I've installed the basic utilities for development, I moved | ||
| 140 | onto installing iTerm2, a much better terminal than the default. | ||
| 125 | 141 | ||
| 126 | Now that I've installed the basic utilities for development, I moved onto | 142 | #+begin_src sh |
| 127 | installing iTerm2, a much better terminal than the default. | ||
| 128 | |||
| 129 | ```sh | ||
| 130 | brew install --cask iterm2 | 143 | brew install --cask iterm2 |
| 131 | ``` | 144 | #+end_src |
| 132 | 145 | ||
| 133 | I also used the `Make iTerm2 Default Term` and `Install Shell Integration` | 146 | I also used the =Make iTerm2 Default Term= and |
| 134 | options in the iTerm2 application menu to make sure I don't run into any issues | 147 | =Install Shell Integration= options in the iTerm2 application menu to |
| 135 | later on with different terminals. | 148 | make sure I don't run into any issues later on with different terminals. |
| 136 | 149 | ||
| 137 | We will also install `zsh` so we can use it in iTerm2. | 150 | We will also install =zsh= so we can use it in iTerm2. |
| 138 | 151 | ||
| 139 | ```sh | 152 | #+begin_src sh |
| 140 | brew install zsh | 153 | brew install zsh |
| 141 | ``` | 154 | #+end_src |
| 142 | |||
| 143 | ## Oh-My-Zsh | ||
| 144 | 155 | ||
| 145 | I've shown the great aspects of [Oh My Zsh](https://ohmyz.sh) in other blog | 156 | ** Oh-My-Zsh |
| 146 | posts, so I'll skip over that speech for now. Simply install it and run an | 157 | :PROPERTIES: |
| 147 | update. | 158 | :CUSTOM_ID: oh-my-zsh |
| 159 | :END: | ||
| 160 | I've shown the great aspects of [[https://ohmyz.sh][Oh My Zsh]] in other | ||
| 161 | blog posts, so I'll skip over that speech for now. Simply install it and | ||
| 162 | run an update. | ||
| 148 | 163 | ||
| 149 | ```sh | 164 | #+begin_src sh |
| 150 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" | 165 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" |
| 151 | ``` | 166 | #+end_src |
| 152 | 167 | ||
| 153 | ```sh | 168 | #+begin_src sh |
| 154 | omz update | 169 | omz update |
| 155 | ``` | 170 | #+end_src |
| 156 | 171 | ||
| 157 | Finally, restart the iTerm2 application to ensure all changes go into effect. | 172 | Finally, restart the iTerm2 application to ensure all changes go into |
| 173 | effect. | ||
| 158 | 174 | ||
| 159 | ## Oh-My-Zsh Themes | 175 | ** Oh-My-Zsh Themes |
| 176 | :PROPERTIES: | ||
| 177 | :CUSTOM_ID: oh-my-zsh-themes | ||
| 178 | :END: | ||
| 179 | Let's change the theme of the terminal to make it a little more | ||
| 180 | friendly. | ||
| 160 | 181 | ||
| 161 | Let's change the theme of the terminal to make it a little more friendly. | 182 | #+begin_src sh |
| 162 | |||
| 163 | ```sh | ||
| 164 | open ~/.zshrc | 183 | open ~/.zshrc |
| 165 | ``` | 184 | #+end_src |
| 166 | 185 | ||
| 167 | The third section of this file should contain a line like the code below. Change | 186 | The third section of this file should contain a line like the code |
| 168 | that theme to | 187 | below. Change that theme to |
| 169 | [any theme you want](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes), save the | 188 | [[https://github.com/ohmyzsh/ohmyzsh/wiki/Themes][any theme you want]], |
| 170 | file, and exit. | 189 | save the file, and exit. |
| 171 | 190 | ||
| 172 | ```sh | 191 | #+begin_src sh |
| 173 | ZSH_THEME="af-magic" | 192 | ZSH_THEME="af-magic" |
| 174 | ``` | 193 | #+end_src |
| 175 | |||
| 176 | After changing the `.zshrc` file, you'll need to close your terminal and re-open | ||
| 177 | it to see the changes. | ||
| 178 | Optionally, just open a new tab if you're using iTerm2, and you'll see the | ||
| 179 | new shell config. | ||
| 180 | 194 | ||
| 181 | ## Oh-My-Zsh Plugins | 195 | After changing the =.zshrc= file, you'll need to close your terminal and |
| 182 | 196 | re-open it to see the changes. Optionally, just open a new tab if you're | |
| 183 | Of course, my customization of `zsh` would not be complete without | 197 | using iTerm2, and you'll see the new shell config. |
| 184 | [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions). This | ||
| 185 | will bring up commands you've run in the past as you type them. For example, if | ||
| 186 | you've run `ssh user@192.168.1.99` before, the terminal will show this command | ||
| 187 | as soon as you start typing it (e.g. `zsh u`), and you can hit the right arrow | ||
| 188 | to autocomplete the command. | ||
| 189 | 198 | ||
| 190 | ```sh | 199 | ** Oh-My-Zsh Plugins |
| 200 | :PROPERTIES: | ||
| 201 | :CUSTOM_ID: oh-my-zsh-plugins | ||
| 202 | :END: | ||
| 203 | Of course, my customization of =zsh= would not be complete without | ||
| 204 | [[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]]. | ||
| 205 | This will bring up commands you've run in the past as you type them. For | ||
| 206 | example, if you've run =ssh user@192.168.1.99= before, the terminal will | ||
| 207 | show this command as soon as you start typing it (e.g. =zsh u=), and you | ||
| 208 | can hit the right arrow to autocomplete the command. | ||
| 209 | |||
| 210 | #+begin_src sh | ||
| 191 | git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions | 211 | git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions |
| 192 | ``` | 212 | #+end_src |
| 193 | 213 | ||
| 194 | ```sh | 214 | #+begin_src sh |
| 195 | open ~/.zshrc | 215 | open ~/.zshrc |
| 196 | ``` | 216 | #+end_src |
| 197 | 217 | ||
| 198 | ```sh | 218 | #+begin_src sh |
| 199 | # Scroll down the script and edit this line to add zsh-autosuggestions | 219 | # Scroll down the script and edit this line to add zsh-autosuggestions |
| 200 | plugins=(git zsh-autosuggestions) | 220 | plugins=(git zsh-autosuggestions) |
| 201 | ``` | 221 | #+end_src |
| 202 | 222 | ||
| 203 | Remember: After changing the `.zshrc` file, you'll need to close your terminal | 223 | Remember: After changing the =.zshrc= file, you'll need to close your |
| 204 | and re-open it to see the changes. Optionally, just open a new tab if you're | 224 | terminal and re-open it to see the changes. Optionally, just open a new |
| 205 | using iTerm2, and you'll see the new shell config. | 225 | tab if you're using iTerm2, and you'll see the new shell config. |
blog/2021-03-19-clone-github-repos.org +73 −71
| @@ -1,60 +1,59 @@ | |||
| 1 | +++ | 1 | #+title: How to Clone All Repositories from a GitHub or Sourcehut Account |
| 2 | date = 2021-03-19 | 2 | #+date: 2021-03-19 |
| 3 | title = "How to Clone All Repositories from a GitHub or Sourcehut Account" | 3 | |
| 4 | description = "Learn how to properly clone all repositories from a GitHub or sourcehut account." | 4 | ** Cloning from GitHub |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: cloning-from-github |
| 7 | 7 | :END: | |
| 8 | ## Cloning from GitHub | 8 | If you're like me and use a lot of different devices (and sometimes |
| 9 | 9 | decide to just wipe your device and start with a new OS), you probably | |
| 10 | If you're like me and use a lot of different devices (and sometimes decide to | 10 | know the pain of cloning all your old code repositories down to your |
| 11 | just wipe your device and start with a new OS), you probably know the pain of | 11 | local file system. |
| 12 | cloning all your old code repositories down to your local file system. | 12 | |
| 13 | 13 | If you're using GitHub, you can easily clone all of your code back down | |
| 14 | If you're using GitHub, you can easily clone all of your code back down in just | 14 | in just seconds. First, create a bash script. I do so by opening a new |
| 15 | seconds. | 15 | file in =nano=, but you can use =gedit=, =vim=, or something else: |
| 16 | First, create a bash script. | 16 | |
| 17 | I do so by opening a new file in `nano`, but you can use `gedit`, `vim`, or | 17 | #+begin_src sh |
| 18 | something else: | ||
| 19 | |||
| 20 | ```sh | ||
| 21 | nano clone_github_repos.sh | 18 | nano clone_github_repos.sh |
| 22 | ``` | 19 | #+end_src |
| 23 | 20 | ||
| 24 | Next, paste in the following information. Note that you can replace the word | 21 | Next, paste in the following information. Note that you can replace the |
| 25 | `users` in the first line with `orgs` and type an organization's name instead of | 22 | word =users= in the first line with =orgs= and type an organization's |
| 26 | a user's name. | 23 | name instead of a user's name. |
| 27 | 24 | ||
| 28 | ```sh | 25 | #+begin_src sh |
| 29 | CNTX=users; NAME=YOUR-USERNAME; PAGE=1 | 26 | CNTX=users; NAME=YOUR-USERNAME; PAGE=1 |
| 30 | curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" | | 27 | curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" | |
| 31 | grep -e 'git_url*' | | 28 | grep -e 'git_url*' | |
| 32 | cut -d \" -f 4 | | 29 | cut -d \" -f 4 | |
| 33 | xargs -L1 git clone | 30 | xargs -L1 git clone |
| 34 | ``` | 31 | #+end_src |
| 35 | 32 | ||
| 36 | Finally, save the bash script and make it executable. | 33 | Finally, save the bash script and make it executable. |
| 37 | 34 | ||
| 38 | ```sh | 35 | #+begin_src sh |
| 39 | chmod a+x clone_github_repos.sh | 36 | chmod a+x clone_github_repos.sh |
| 40 | ``` | 37 | #+end_src |
| 41 | 38 | ||
| 42 | Now you can run the script and should see the cloning process begin. | 39 | Now you can run the script and should see the cloning process begin. |
| 43 | 40 | ||
| 44 | ```sh | 41 | #+begin_src sh |
| 45 | ./clone_github_repos.sh | 42 | ./clone_github_repos.sh |
| 46 | ``` | 43 | #+end_src |
| 47 | 44 | ||
| 48 | ## Cloning from Sourcehut | 45 | ** Cloning from Sourcehut |
| 49 | 46 | :PROPERTIES: | |
| 50 | I haven't fully figured out how to directly incorporate Sourcehut's GraphQL API | 47 | :CUSTOM_ID: cloning-from-sourcehut |
| 51 | into a bash script yet, so this one will take two steps. | 48 | :END: |
| 49 | I haven't fully figured out how to directly incorporate Sourcehut's | ||
| 50 | GraphQL API into a bash script yet, so this one will take two steps. | ||
| 52 | 51 | ||
| 53 | First, log-in to Sourcehut and go to their | 52 | First, log-in to Sourcehut and go to their |
| 54 | [GraphQL playground for Git](https://git.sr.ht/graphql). Next, paste the | 53 | [[https://git.sr.ht/graphql][GraphQL playground for Git]]. Next, paste |
| 55 | following query into the left box: | 54 | the following query into the left box: |
| 56 | 55 | ||
| 57 | ```sh | 56 | #+begin_src sh |
| 58 | query { | 57 | query { |
| 59 | me { | 58 | me { |
| 60 | canonicalName | 59 | canonicalName |
| @@ -66,26 +65,27 @@ query { | |||
| 66 | } | 65 | } |
| 67 | } | 66 | } |
| 68 | } | 67 | } |
| 69 | ``` | 68 | #+end_src |
| 70 | 69 | ||
| 71 | The output on the right side will give you an object of all your repositories. | 70 | The output on the right side will give you an object of all your |
| 72 | Just grab that text and remove all the characters such as quotation marks and | 71 | repositories. Just grab that text and remove all the characters such as |
| 73 | curly brackets. You will need a single-line list of space-separated values for | 72 | quotation marks and curly brackets. You will need a single-line list of |
| 74 | the next step. | 73 | space-separated values for the next step. |
| 75 | 74 | ||
| 76 | Now let's create the bash script: | 75 | Now let's create the bash script: |
| 77 | 76 | ||
| 78 | ```sh | 77 | #+begin_src sh |
| 79 | nano clone_sourcehut_repos.sh | 78 | nano clone_sourcehut_repos.sh |
| 80 | ``` | 79 | #+end_src |
| 81 | 80 | ||
| 82 | Next, paste the following bash script in with the list of repositories you | 81 | Next, paste the following bash script in with the list of repositories |
| 83 | obtained above and replace `your-username` with your username. | 82 | you obtained above and replace =your-username= with your username. |
| 84 | 83 | ||
| 85 | Note that this uses the SSH-based Git cloning method (e.g. `git@git...`), so | 84 | Note that this uses the SSH-based Git cloning method |
| 86 | you'll need to ensure you have set up Sourcehut with your SSH key. | 85 | (e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut |
| 86 | with your SSH key. | ||
| 87 | 87 | ||
| 88 | ```sh | 88 | #+begin_src sh |
| 89 | repos=(repo1 repo2 repo3) | 89 | repos=(repo1 repo2 repo3) |
| 90 | 90 | ||
| 91 | # List all sub-directories in the current directory | 91 | # List all sub-directories in the current directory |
| @@ -94,36 +94,38 @@ do | |||
| 94 | # Clone | 94 | # Clone |
| 95 | git clone git@git.sr.ht:~your-username/$repo | 95 | git clone git@git.sr.ht:~your-username/$repo |
| 96 | done | 96 | done |
| 97 | ``` | 97 | #+end_src |
| 98 | 98 | ||
| 99 | Finally, save the bash script and make it executable. | 99 | Finally, save the bash script and make it executable. |
| 100 | 100 | ||
| 101 | ```sh | 101 | #+begin_src sh |
| 102 | chmod a+x clone_sourcehut_repos.sh | 102 | chmod a+x clone_sourcehut_repos.sh |
| 103 | ``` | 103 | #+end_src |
| 104 | 104 | ||
| 105 | Now you can run the script and should see the cloning process begin. | 105 | Now you can run the script and should see the cloning process begin. |
| 106 | 106 | ||
| 107 | ```sh | 107 | #+begin_src sh |
| 108 | ./clone_sourcehut_repos.sh | 108 | ./clone_sourcehut_repos.sh |
| 109 | ``` | 109 | #+end_src |
| 110 | |||
| 111 | ## Moving Repositories to a New Host | ||
| 112 | 110 | ||
| 113 | Now that you have all of your code repositories cloned to your local computer, | 111 | ** Moving Repositories to a New Host |
| 114 | you may want to change the remote host (e.g., moving from GitHub to GitLab). | 112 | :PROPERTIES: |
| 115 | To do this, let's create another bash script: | 113 | :CUSTOM_ID: moving-repositories-to-a-new-host |
| 114 | :END: | ||
| 115 | Now that you have all of your code repositories cloned to your local | ||
| 116 | computer, you may want to change the remote host (e.g., moving from | ||
| 117 | GitHub to GitLab). To do this, let's create another bash script: | ||
| 116 | 118 | ||
| 117 | ```sh | 119 | #+begin_src sh |
| 118 | nano change_remote_urls.sh | 120 | nano change_remote_urls.sh |
| 119 | ``` | 121 | #+end_src |
| 120 | 122 | ||
| 121 | Past the following information and be sure to change the URL information to | 123 | Past the following information and be sure to change the URL information |
| 122 | whichever host you are moving to. For this example, I am looping through all of | 124 | to whichever host you are moving to. For this example, I am looping |
| 123 | my cloned GitHub directories and changing them to Sourcehut (e.g. | 125 | through all of my cloned GitHub directories and changing them to |
| 124 | `<YOUR_NEW_REMOTE_URL>` -\> `git@git.sr.ht:~myusername`). | 126 | Sourcehut (e.g. =<YOUR_NEW_REMOTE_URL>= -> =git@git.sr.ht:~myusername=). |
| 125 | 127 | ||
| 126 | ```sh | 128 | #+begin_src sh |
| 127 | # List all sub-directories in the current directory | 129 | # List all sub-directories in the current directory |
| 128 | for dir in */ | 130 | for dir in */ |
| 129 | do | 131 | do |
| @@ -138,16 +140,16 @@ do | |||
| 138 | # Go back to main directory | 140 | # Go back to main directory |
| 139 | cd .. | 141 | cd .. |
| 140 | done | 142 | done |
| 141 | ``` | 143 | #+end_src |
| 142 | 144 | ||
| 143 | Finally, save the bash script and make it executable. | 145 | Finally, save the bash script and make it executable. |
| 144 | 146 | ||
| 145 | ```sh | 147 | #+begin_src sh |
| 146 | chmod a+x change_remote_urls.sh | 148 | chmod a+x change_remote_urls.sh |
| 147 | ``` | 149 | #+end_src |
| 148 | 150 | ||
| 149 | Now you can run the script and should see the cloning process begin. | 151 | Now you can run the script and should see the cloning process begin. |
| 150 | 152 | ||
| 151 | ```sh | 153 | #+begin_src sh |
| 152 | ./change_remote_urls.sh | 154 | ./change_remote_urls.sh |
| 153 | ``` | 155 | #+end_src |
blog/2021-03-28-gemini-capsule.org +129 −117
| @@ -1,30 +1,29 @@ | |||
| 1 | +++ | 1 | #+title: Launching a Gemini Capsule |
| 2 | date = 2021-03-28 | 2 | #+date: 2021-03-28 |
| 3 | title = "Launching a Gemini Capsule" | 3 | |
| 4 | description = "Learn more about Gemini, an alternative internet protocol to the World Wide Web (WWW)" | 4 | ** What is Gemini? |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: what-is-gemini |
| 7 | 7 | :END: | |
| 8 | ## What is Gemini? | 8 | [[https://gemini.circumlunar.space/][Gemini]] is an internet protocol |
| 9 | 9 | introduced in June 2019 as an alternative to HTTP(S) or Gopher. In | |
| 10 | [Gemini](https://gemini.circumlunar.space/) is an internet protocol introduced | 10 | layman's terms, it's an alternative way to browse sites (called |
| 11 | in June 2019 as an alternative to HTTP(S) or Gopher. In layman's terms, it's an | 11 | capsules) that requires a special browser. Since Gemini is not |
| 12 | alternative way to browse sites (called capsules) that requires a special | 12 | standardized as an internet standard, normal web browsers won't be able |
| 13 | browser. Since Gemini is not standardized as an internet standard, normal web | 13 | to load a Gemini capsule. Instead, you'll need to use |
| 14 | browsers won't be able to load a Gemini capsule. | 14 | [[https://gemini.%20circumlunar.space/clients.html][a Gemini-specific |
| 15 | Instead, you'll need to use [a Gemini-specific browser](https://gemini. | 15 | browser]]. |
| 16 | circumlunar.space/clients.html). | ||
| 17 | 16 | ||
| 18 | The content found within a Gemini page is called | 17 | The content found within a Gemini page is called |
| 19 | [Gemtext](https://gemini.circumlunar.space/docs/cheatsheet.gmi) and is | 18 | [[https://gemini.circumlunar.space/docs/cheatsheet.gmi][Gemtext]] and is |
| 20 | _extremely_ basic (on purpose). | 19 | /extremely/ basic (on purpose). Gemini only processes the text, no media |
| 21 | Gemini only processes the text, no media content like images. However, | 20 | content like images. However, you're able to style 3 levels of headings, |
| 22 | you're able to style 3 levels of headings, regular text, links (which will | 21 | regular text, links (which will display on their own line), quotes, and |
| 23 | display on their own line), quotes, and an unordered list. | 22 | an unordered list. |
| 24 | 23 | ||
| 25 | Here's a complete listing of valid Gemtext: | 24 | Here's a complete listing of valid Gemtext: |
| 26 | 25 | ||
| 27 | ```txt | 26 | #+begin_src txt |
| 28 | # Heading 1 | 27 | # Heading 1 |
| 29 | ## Heading 2 | 28 | ## Heading 2 |
| 30 | ### Heading 3 | 29 | ### Heading 3 |
| @@ -41,97 +40,109 @@ My List: | |||
| 41 | * Item | 40 | * Item |
| 42 | 41 | ||
| 43 | ```Anything between three backticks will be rendered as code.``` | 42 | ```Anything between three backticks will be rendered as code.``` |
| 44 | ``` | 43 | #+end_src |
| 45 | 44 | ||
| 46 | ## Free Option | 45 | ** Free Option |
| 47 | 46 | :PROPERTIES: | |
| 48 | There are probably numerous websites that allow you to create your personal | 47 | :CUSTOM_ID: free-option |
| 49 | Gemini capsule, but I'm going to focus on the two sites that I have personally | 48 | :END: |
| 50 | tested. The first option below, Midnight Pub, allows you to create/edit any | 49 | There are probably numerous websites that allow you to create your |
| 51 | Gemini files you want in your account. This is essentially a GUI option with a | 50 | personal Gemini capsule, but I'm going to focus on the two sites that I |
| 52 | built-in text box for editing. The second option below, Sourcehut, allows you to | 51 | have personally tested. The first option below, Midnight Pub, allows you |
| 53 | use a Git repository and automatic build process to deploy your personal Gemini | 52 | to create/edit any Gemini files you want in your account. This is |
| 54 | capsule every time you push a commit. | 53 | essentially a GUI option with a built-in text box for editing. The |
| 55 | 54 | second option below, Sourcehut, allows you to use a Git repository and | |
| 56 | ### Midnight Pub - Beginner Friendly | 55 | automatic build process to deploy your personal Gemini capsule every |
| 57 | 56 | time you push a commit. | |
| 58 | [Midnight Pub](https://midnight.pub/) is a small, virtual community meant to | 57 | |
| 59 | reflect the atmosphere of wandering into a small alley pub. The site is built in | 58 | *** Midnight Pub - Beginner Friendly |
| 60 | Gemtext and has a server-side process to convert Gemtext to HTML if someone | 59 | :PROPERTIES: |
| 61 | loads the site in an HTTP(S) browser. | 60 | :CUSTOM_ID: midnight-pub---beginner-friendly |
| 62 | 61 | :END: | |
| 63 | To create an account, you'll need to email the owner of the website to obtain a | 62 | [[https://midnight.pub/][Midnight Pub]] is a small, virtual community |
| 64 | key. | 63 | meant to reflect the atmosphere of wandering into a small alley pub. The |
| 65 | You can find their email on the Midnight Pub homepage. | 64 | site is built in Gemtext and has a server-side process to convert |
| 66 | Once registered, head to [your account](https://midnight.pub/account) and select | 65 | Gemtext to HTML if someone loads the site in an HTTP(S) browser. |
| 67 | [manage site](https://midnight.pub/site). This is the screen where you can | 66 | |
| 68 | upload or create any files to be displayed on the internet. | 67 | To create an account, you'll need to email the owner of the website to |
| 69 | 68 | obtain a key. You can find their email on the Midnight Pub homepage. | |
| 70 | For example, I've created both an HTML file and a Gemini file. Remember that | 69 | Once registered, head to [[https://midnight.pub/account][your account]] |
| 71 | Gemini is automatically converted to HTML on the Pub, so you don't need an HTML | 70 | and select [[https://midnight.pub/site][manage site]]. This is the |
| 72 | version. | 71 | screen where you can upload or create any files to be displayed on the |
| 73 | For example, I created an HTML version to add in some extra styling. | 72 | internet. |
| 74 | 73 | ||
| 75 | All you need to do is create a page like `index.gmi` and use your Gemini browser | 74 | For example, I've created both an HTML file and a Gemini file. Remember |
| 76 | to head over to your-username.midnight.pub to see the result. | 75 | that Gemini is automatically converted to HTML on the Pub, so you don't |
| 77 | 76 | need an HTML version. For example, I created an HTML version to add in | |
| 78 | That's all there is to it! Easy enough, right? Let's check out a more advanced | 77 | some extra styling. |
| 79 | version in the next section. | 78 | |
| 80 | 79 | All you need to do is create a page like =index.gmi= and use your Gemini | |
| 81 | ## Paid Option | 80 | browser to head over to your-username.midnight.pub to see the result. |
| 82 | 81 | ||
| 83 | As of 2021, Sourcehut has decided to require users to have a paid account in | 82 | That's all there is to it! Easy enough, right? Let's check out a more |
| 84 | order to utilize their automated build system. For now, paid accounts can be as | 83 | advanced version in the next section. |
| 85 | low as $2/month. | 84 | |
| 86 | 85 | ** Paid Option | |
| 87 | ### Sourcehut | 86 | :PROPERTIES: |
| 88 | 87 | :CUSTOM_ID: paid-option | |
| 89 | [Sourcehut](https://sourcehut.org/) is a collection of software development | 88 | :END: |
| 90 | tools, but mostly surrounds their hosted Git repository service. Simply put, | 89 | As of 2021, Sourcehut has decided to require users to have a paid |
| 91 | it's a minimal and more private alternative to services like GitHub. | 90 | account in order to utilize their automated build system. For now, paid |
| 92 | 91 | accounts can be as low as $2/month. | |
| 93 | This walkthrough is more advanced and involves things like Git, SSH, the command | 92 | |
| 94 | line. If you don't think you know enough to do this, check out my walkthrough on | 93 | *** Sourcehut |
| 95 | creating a Gemini capsule for the Midnight Pub instead. | 94 | :PROPERTIES: |
| 96 | 95 | :CUSTOM_ID: sourcehut | |
| 97 | The first thing you'll need to do is create an SSH key pair, if you don't | 96 | :END: |
| 98 | already have one on your system. Once created, grab the contents of `id_rsa.pub` | 97 | [[https://sourcehut.org/][Sourcehut]] is a collection of software |
| 99 | and add it to your Sourcehut account settings - this will allow you to push and | 98 | development tools, but mostly surrounds their hosted Git repository |
| 100 | pull code changes without using a username/password. | 99 | service. Simply put, it's a minimal and more private alternative to |
| 101 | 100 | services like GitHub. | |
| 102 | ```sh | 101 | |
| 102 | This walkthrough is more advanced and involves things like Git, SSH, the | ||
| 103 | command line. If you don't think you know enough to do this, check out | ||
| 104 | my walkthrough on creating a Gemini capsule for the Midnight Pub | ||
| 105 | instead. | ||
| 106 | |||
| 107 | The first thing you'll need to do is create an SSH key pair, if you | ||
| 108 | don't already have one on your system. Once created, grab the contents | ||
| 109 | of =id_rsa.pub= and add it to your Sourcehut account settings - this | ||
| 110 | will allow you to push and pull code changes without using a | ||
| 111 | username/password. | ||
| 112 | |||
| 113 | #+begin_src sh | ||
| 103 | ssh keygen | 114 | ssh keygen |
| 104 | ``` | 115 | #+end_src |
| 105 | 116 | ||
| 106 | Next up, let's create a repository with the proper name so that the Sourcehut | 117 | Next up, let's create a repository with the proper name so that the |
| 107 | build system will know we want them to host a website for us. Use the following | 118 | Sourcehut build system will know we want them to host a website for us. |
| 108 | format exactly: | 119 | Use the following format exactly: |
| 109 | 120 | ||
| 110 | ```sh | 121 | #+begin_src sh |
| 111 | mkdir your-username.srht.site && cd your-username.srht.site | 122 | mkdir your-username.srht.site && cd your-username.srht.site |
| 112 | ``` | 123 | #+end_src |
| 113 | 124 | ||
| 114 | Now that we've created the repo, let's initialize Git and add the proper remote | 125 | Now that we've created the repo, let's initialize Git and add the proper |
| 115 | URL. | 126 | remote URL. |
| 116 | 127 | ||
| 117 | ```sh | 128 | #+begin_src sh |
| 118 | git init | 129 | git init |
| 119 | ``` | 130 | #+end_src |
| 120 | 131 | ||
| 121 | ```sh | 132 | #+begin_src sh |
| 122 | git remote add origin git@git.sr.ht:~your-username/your-username.srht.site | 133 | git remote add origin git@git.sr.ht:~your-username/your-username.srht.site |
| 123 | ``` | 134 | #+end_src |
| 124 | 135 | ||
| 125 | Now that our repository is set up and configured, we will need to create at | 136 | Now that our repository is set up and configured, we will need to create |
| 126 | least two files: | 137 | at least two files: |
| 127 | 138 | ||
| 128 | - `index.gmi` | 139 | - =index.gmi= |
| 129 | - `.build.yml` | 140 | - =.build.yml= |
| 130 | 141 | ||
| 131 | For your `.build.yml` file, use the following content and be sure to update the | 142 | For your =.build.yml= file, use the following content and be sure to |
| 132 | `site` line with your username! | 143 | update the =site= line with your username! |
| 133 | 144 | ||
| 134 | ```yaml | 145 | #+begin_src yaml |
| 135 | image: alpine/latest | 146 | image: alpine/latest |
| 136 | oauth: pages.sr.ht/PAGES:RW | 147 | oauth: pages.sr.ht/PAGES:RW |
| 137 | environment: | 148 | environment: |
| @@ -142,37 +153,38 @@ tasks: | |||
| 142 | tar -cvz . > ../site.tar.gz | 153 | tar -cvz . > ../site.tar.gz |
| 143 | - upload: | | 154 | - upload: | |
| 144 | acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI | 155 | acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI |
| 145 | ``` | 156 | #+end_src |
| 146 | 157 | ||
| 147 | For the `index.gmi` file, put whatever you want in there and save it. You could | 158 | For the =index.gmi= file, put whatever you want in there and save it. |
| 148 | even just copy and paste the Gemtext cheatsheet. | 159 | You could even just copy and paste the Gemtext cheatsheet. |
| 149 | 160 | ||
| 150 | If you want to serve both HTML and Gemini files from this repository, just add a | 161 | If you want to serve both HTML and Gemini files from this repository, |
| 151 | second command to the `upload` section: | 162 | just add a second command to the =upload= section: |
| 152 | 163 | ||
| 153 | ```yaml | 164 | #+begin_src yaml |
| 154 | - upload: | | 165 | - upload: | |
| 155 | acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI | 166 | acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI |
| 156 | acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz | 167 | acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz |
| 157 | ``` | 168 | #+end_src |
| 158 | 169 | ||
| 159 | Lastly, commit your changes and push them to the remote repo. | 170 | Lastly, commit your changes and push them to the remote repo. |
| 160 | 171 | ||
| 161 | ```sh | 172 | #+begin_src sh |
| 162 | git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD | 173 | git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD |
| 163 | ``` | 174 | #+end_src |
| 164 | 175 | ||
| 165 | If you've successfully created the files with the proper format, you'll see the | 176 | If you've successfully created the files with the proper format, you'll |
| 166 | terminal print a message that lets you know where the automatic build is taking | 177 | see the terminal print a message that lets you know where the automatic |
| 167 | place. For example, here's what the terminal tells me: | 178 | build is taking place. For example, here's what the terminal tells me: |
| 168 | 179 | ||
| 169 | ```sh | 180 | #+begin_src sh |
| 170 | remote: Build started: | 181 | remote: Build started: |
| 171 | remote: https://builds.sr.ht/~user/job/689803 [.build.yml] | 182 | remote: https://builds.sr.ht/~user/job/689803 [.build.yml] |
| 172 | ``` | 183 | #+end_src |
| 173 | 184 | ||
| 174 | Now that you've properly built your Sourcehut page, you can browse to | 185 | Now that you've properly built your Sourcehut page, you can browse to |
| 175 | your-username.srht.site in a Gemini browser and view the final results. Take a | 186 | your-username.srht.site in a Gemini browser and view the final results. |
| 176 | look at the image below for my Sourcehut Gemini capsule. | 187 | Take a look at the image below for my Sourcehut Gemini capsule. |
| 177 | 188 | ||
| 178 |  | 189 | #+caption: Gemini page on the amfora browser |
| 190 | [[https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png]] | ||
blog/2021-03-28-vaporwave-vs-outrun.org +151 −120
| @@ -1,120 +1,151 @@ | |||
| 1 | +++ | 1 | #+title: Vaporwave vs Outrun |
| 2 | date = 2021-03-28 | 2 | #+date: 2021-03-28 |
| 3 | title = "Vaporwave vs Outrun" | 3 | |
| 4 | description = "This post discusses Vaporwave and Outrun, which have become almost synonymous in the minds of those online who aren't hardcore into these genres of media." | 4 | ** Overview |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: overview | |
| 7 | ## Overview | 7 | :END: |
| 8 | 8 | When it comes to an aesthetic that originated primarily online, there | |
| 9 | When it comes to an aesthetic that originated primarily online, there tends to | 9 | tends to be a lot of confusion around what falls into each specific |
| 10 | be a lot of confusion around what falls into each specific genre. This post | 10 | genre. This post discusses Vaporwave and Outrun, which have become |
| 11 | discusses Vaporwave and Outrun, which have become almost synonymous in the minds | 11 | almost synonymous in the minds of those online who aren't hardcore into |
| 12 | of those online who aren't hardcore into these genres of media. More | 12 | these genres of media. More specifically, Outrun is largely an unknown |
| 13 | specifically, Outrun is largely an unknown term while all aesthetics in these | 13 | term while all aesthetics in these two genres are usually attributed to |
| 14 | two genres are usually attributed to Vaporwave. For example, take a look at the | 14 | Vaporwave. For example, take a look at the image search results for |
| 15 | image search results for Vaporwave: the results include **a lot** of | 15 | Vaporwave: the results include *a lot* of Outrun-themed images. You'll |
| 16 | Outrun-themed images. You'll find a similar trend almost everywhere. | 16 | find a similar trend almost everywhere. |
| 17 | 17 | ||
| 18 |  | 18 | [[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png]] |
| 19 | 19 | ||
| 20 | ## Vaporwave | 20 | ** Vaporwave |
| 21 | 21 | :PROPERTIES: | |
| 22 |  | 22 | :CUSTOM_ID: vaporwave |
| 23 | 23 | :END: | |
| 24 | Okay, so what is Vaporwave? I'm going to just copy-and-paste some general info | 24 | [[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png]] |
| 25 | from the Wikipedia article on | 25 | |
| 26 | [Vaporwave](https://en.wikipedia.org/wiki/Vaporwave), so that I'm not repeating | 26 | Okay, so what is Vaporwave? I'm going to just copy-and-paste some |
| 27 | everything you can already search for online: | 27 | general info from the Wikipedia article on |
| 28 | 28 | [[https://en.wikipedia.org/wiki/Vaporwave][Vaporwave]], so that I'm not | |
| 29 | > Vaporwave is a microgenre of electronic music, a visual art style, and an | 29 | repeating everything you can already search for online: |
| 30 | > Internet meme that emerged in the early 2010s. It is defined partly by its | 30 | |
| 31 | > slowed-down, chopped and screwed samples of smooth jazz, elevator, R&B, and | 31 | #+begin_quote |
| 32 | > lounge music from the 1980s and 1990s. The surrounding subculture is sometimes | 32 | Vaporwave is a microgenre of electronic music, a visual art style, and |
| 33 | > associated with an ambiguous or satirical take on consumer capitalism and pop | 33 | an Internet meme that emerged in the early 2010s. It is defined partly |
| 34 | > culture, and tends to be characterized by a nostalgic or surrealist engagement | 34 | by its slowed-down, chopped and screwed samples of smooth jazz, |
| 35 | > with the popular entertainment, technology and advertising of previous | 35 | elevator, R&B, and lounge music from the 1980s and 1990s. The |
| 36 | > decades. Visually, it incorporates early Internet imagery, late 1990s web | 36 | surrounding subculture is sometimes associated with an ambiguous or |
| 37 | > design, glitch art, anime, 3D-rendered objects, and cyberpunk tropes in its | 37 | satirical take on consumer capitalism and pop culture, and tends to be |
| 38 | > cover artwork and music videos. | 38 | characterized by a nostalgic or surrealist engagement with the popular |
| 39 | 39 | entertainment, technology and advertising of previous decades. Visually, | |
| 40 | This is an excellent summary, and it helps address my point here: there are | 40 | it incorporates early Internet imagery, late 1990s web design, glitch |
| 41 | specific aspects that make Vaporwave unique: | 41 | art, anime, 3D-rendered objects, and cyberpunk tropes in its cover |
| 42 | 42 | artwork and music videos. | |
| 43 | ### Time Frame | 43 | |
| 44 | 44 | #+end_quote | |
| 45 | The time frame for references, logos, etc. focuses mostly on the 1990s in | 45 | |
| 46 | Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens, tropical | 46 | This is an excellent summary, and it helps address my point here: there |
| 47 | plants, classic marble sculptures, and many references from Japan's influence in | 47 | are specific aspects that make Vaporwave unique: |
| 48 | the 90s. | 48 | |
| 49 | 49 | *** Time Frame | |
| 50 | ### Art | 50 | :PROPERTIES: |
| 51 | 51 | :CUSTOM_ID: time-frame | |
| 52 | The color scheme is generally a soft, light palette that uses pastel colors. The | 52 | :END: |
| 53 | backdrop will often be in a light-pink or teal blue. | 53 | The time frame for references, logos, etc. focuses mostly on the 1990s |
| 54 | 54 | in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens, | |
| 55 | ### Music | 55 | tropical plants, classic marble sculptures, and many references from |
| 56 | 56 | Japan's influence in the 90s. | |
| 57 | The musical genre of Vaporwave incorporates soft tunes, lounge music, and sound | 57 | |
| 58 | effects that will make the listener reminisce of the 90s. The sounds of the | 58 | *** Art |
| 59 | genre are generally slower-paced and calming. The major breakthrough artist for | 59 | :PROPERTIES: |
| 60 | Vaporwave was [Macintosh Plus](https://en.wikipedia.org/wiki/Vektroid), who | 60 | :CUSTOM_ID: art |
| 61 | released the album | 61 | :END: |
| 62 | [Floral Shoppe](https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete) | 62 | The color scheme is generally a soft, light palette that uses pastel |
| 63 | in 2011. Another more recent example is the artist | 63 | colors. The backdrop will often be in a light-pink or teal blue. |
| 64 | [sadbert](https://sadbert.bandcamp.com/), whose latest album incorporates the | 64 | |
| 65 | upbeat tones of the 1999 Dilbert TV series. | 65 | *** Music |
| 66 | 66 | :PROPERTIES: | |
| 67 | Notice that Vaporwave doesn't include things like racing cars, futuristic | 67 | :CUSTOM_ID: music |
| 68 | technology, chrome, or the deep orange/purple color scheme. Vaporwave is a focus | 68 | :END: |
| 69 | on the idyllic state of the world when technology was becoming common in | 69 | The musical genre of Vaporwave incorporates soft tunes, lounge music, |
| 70 | households, a reality that we have already experienced. Focus on the most | 70 | and sound effects that will make the listener reminisce of the 90s. The |
| 71 | aesthetically-pleasing parts of that past is a large part of Vaporwave. | 71 | sounds of the genre are generally slower-paced and calming. The major |
| 72 | 72 | breakthrough artist for Vaporwave was | |
| 73 | ## Outrun | 73 | [[https://en.wikipedia.org/wiki/Vektroid][Macintosh Plus]], who released |
| 74 | 74 | the album | |
| 75 |  | 75 | [[https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete][Floral |
| 76 | 76 | Shoppe]] in 2011. Another more recent example is the artist | |
| 77 | Now, let's get to Outrun. This one is a little trickier since the genre has | 77 | [[https://sadbert.bandcamp.com/][sadbert]], whose latest album |
| 78 | largely been lumped under the Vaporwave title for so long. However, it stems | 78 | incorporates the upbeat tones of the 1999 Dilbert TV series. |
| 79 | from the [Synthwave](https://en.wikipedia.org/wiki/Synthwave) music genre and is | 79 | |
| 80 | likely named after the 1986 racer game, | 80 | Notice that Vaporwave doesn't include things like racing cars, |
| 81 | [Out Run](https://en.wikipedia.org/wiki/Out_Run). | 81 | futuristic technology, chrome, or the deep orange/purple color scheme. |
| 82 | 82 | Vaporwave is a focus on the idyllic state of the world when technology | |
| 83 | ### Time Frame | 83 | was becoming common in households, a reality that we have already |
| 84 | 84 | experienced. Focus on the most aesthetically-pleasing parts of that past | |
| 85 | Outrun can be thought of as a retro-futuristic aesthetic born from the 1980s. | 85 | is a large part of Vaporwave. |
| 86 | 86 | ||
| 87 | ### Art | 87 | ** Outrun |
| 88 | 88 | :PROPERTIES: | |
| 89 | The color scheme uses a very dark color palette with the main colors being deep | 89 | :CUSTOM_ID: outrun |
| 90 | oranges, blues, and purples. Red edges are common around objects in Outrun art. | 90 | :END: |
| 91 | The background of the Outrun aesthetic is almost always a neon grid like you'd | 91 | #+caption: Outrun |
| 92 | expect to see in Tron or a 1980s arcade machine. | 92 | [[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png]] |
| 93 | 93 | ||
| 94 | Classic sports cars, chrome robots, computer generated graphics and fonts, and | 94 | Now, let's get to Outrun. This one is a little trickier since the genre |
| 95 | the occasional use of rain or palm trees can be found in Outrun art. | 95 | has largely been lumped under the Vaporwave title for so long. However, |
| 96 | 96 | it stems from the [[https://en.wikipedia.org/wiki/Synthwave][Synthwave]] | |
| 97 | ### Music | 97 | music genre and is likely named after the 1986 racer game, |
| 98 | 98 | [[https://en.wikipedia.org/wiki/Out_Run][Out Run]]. | |
| 99 | This aesthetic has a more aggressive and fast-paced style of music, which tends | 99 | |
| 100 | to match the subject of the art in this aesthetic. | 100 | *** Time Frame |
| 101 | 101 | :PROPERTIES: | |
| 102 | Outrun enthusiasts love what people in the 1980s thought the future would look | 102 | :CUSTOM_ID: time-frame-1 |
| 103 | like. Take a look at a common video game discussed in Outrun circles, Far Cry 3: | 103 | :END: |
| 104 | Blood Dragon: | 104 | Outrun can be thought of as a retro-futuristic aesthetic born from the |
| 105 | 105 | 1980s. | |
| 106 |  | 106 | |
| 107 | 107 | *** Art | |
| 108 | Another example that doesn't force the color scheme as hard as some online art | 108 | :PROPERTIES: |
| 109 | does is Kung Fury: | 109 | :CUSTOM_ID: art-1 |
| 110 | 110 | :END: | |
| 111 |  | 111 | The color scheme uses a very dark color palette with the main colors |
| 112 | 112 | being deep oranges, blues, and purples. Red edges are common around | |
| 113 |  | 113 | objects in Outrun art. The background of the Outrun aesthetic is almost |
| 114 | 114 | always a neon grid like you'd expect to see in Tron or a 1980s arcade | |
| 115 | ## Conclusion | 115 | machine. |
| 116 | 116 | ||
| 117 | While Vaporwave and Outrun share similarities, they are two distinct aesthetics | 117 | Classic sports cars, chrome robots, computer generated graphics and |
| 118 | with many important distinctions. Someone who enjoys one may not necessarily | 118 | fonts, and the occasional use of rain or palm trees can be found in |
| 119 | enjoy the other, so it's important to make sure we properly describe the | 119 | Outrun art. |
| 120 | aesthetic we're looking for. | 120 | |
| 121 | *** Music | ||
| 122 | :PROPERTIES: | ||
| 123 | :CUSTOM_ID: music-1 | ||
| 124 | :END: | ||
| 125 | This aesthetic has a more aggressive and fast-paced style of music, | ||
| 126 | which tends to match the subject of the art in this aesthetic. | ||
| 127 | |||
| 128 | Outrun enthusiasts love what people in the 1980s thought the future | ||
| 129 | would look like. Take a look at a common video game discussed in Outrun | ||
| 130 | circles, Far Cry 3: Blood Dragon: | ||
| 131 | |||
| 132 | #+caption: Fry Cry 3: Blood Dragon | ||
| 133 | [[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png]] | ||
| 134 | |||
| 135 | Another example that doesn't force the color scheme as hard as some | ||
| 136 | online art does is Kung Fury: | ||
| 137 | |||
| 138 | #+caption: Kung Fury | ||
| 139 | [[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png]] | ||
| 140 | |||
| 141 | #+caption: Kung Fury Hacker Scene | ||
| 142 | [[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png]] | ||
| 143 | |||
| 144 | ** Conclusion | ||
| 145 | :PROPERTIES: | ||
| 146 | :CUSTOM_ID: conclusion | ||
| 147 | :END: | ||
| 148 | While Vaporwave and Outrun share similarities, they are two distinct | ||
| 149 | aesthetics with many important distinctions. Someone who enjoys one may | ||
| 150 | not necessarily enjoy the other, so it's important to make sure we | ||
| 151 | properly describe the aesthetic we're looking for. | ||
blog/2021-03-30-vps-web-server.org +295 −262
| @@ -1,270 +1,293 @@ | |||
| 1 | +++ | 1 | #+title: How to Set Up a VPS Web Server |
| 2 | date = 2021-03-30 | 2 | #+date: 2021-03-30 |
| 3 | title = "How to Set Up a VPS Web Server" | 3 | |
| 4 | description = "Choosing a place to host a website is one of the most confusing decisions for beginner web developers. Even for experienced web devs, choosing between different forms of web hosting can be a daunting choice." | 4 | ** Shared Hosting vs. VPS |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: shared-hosting-vs.-vps |
| 7 | 7 | :END: | |
| 8 | ## Shared Hosting vs. VPS | 8 | Choosing a place to host a website is one of the most confusing |
| 9 | 9 | decisions for beginner web developers. Even for experienced web devs, | |
| 10 | Choosing a place to host a website is one of the most confusing decisions for | 10 | choosing between different forms of web hosting can be a daunting |
| 11 | beginner web developers. Even for experienced web devs, choosing between | 11 | choice. |
| 12 | different forms of web hosting can be a daunting choice. | ||
| 13 | 12 | ||
| 14 | First, let's take a look at | 13 | First, let's take a look at |
| 15 | [shared web hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service). | 14 | [[https://en.wikipedia.org/wiki/Shared_web_hosting_service][shared web |
| 16 | Shared web hosting is a product where you are purchasing a small piece of a web | 15 | hosting]]. Shared web hosting is a product where you are purchasing a |
| 17 | server that is being shared between many websites. As a result, the cost of | 16 | small piece of a web server that is being shared between many websites. |
| 18 | shared hosting is extremely low. You won't have access to the server itself, | 17 | As a result, the cost of shared hosting is extremely low. You won't have |
| 19 | which means you can't install your own software on the server, such as Docker. | 18 | access to the server itself, which means you can't install your own |
| 20 | Usually, you are simply allowed to connect your domains to the server, set up | 19 | software on the server, such as Docker. Usually, you are simply allowed |
| 21 | domain security, and other small utilities. | 20 | to connect your domains to the server, set up domain security, and other |
| 21 | small utilities. | ||
| 22 | 22 | ||
| 23 | In contrast, a | 23 | In contrast, a |
| 24 | [virtual private server](https://en.wikipedia.org/wiki/Virtual_private_server) | 24 | [[https://en.wikipedia.org/wiki/Virtual_private_server][virtual private |
| 25 | (VPS) is a virtual machine that replicates the environment of having a dedicated | 25 | server]] (VPS) is a virtual machine that replicates the environment of |
| 26 | server to yourself. You are able to control the virtual server's host names, | 26 | having a dedicated server to yourself. You are able to control the |
| 27 | base file system, package manager, etc. Another great upside of a VPS is that | 27 | virtual server's host names, base file system, package manager, etc. |
| 28 | since it's virtual, the company providing the VPS can dynamically increase the | 28 | Another great upside of a VPS is that since it's virtual, the company |
| 29 | disk size, RAM size, or number of CPUs at any time. However, the virtual server | 29 | providing the VPS can dynamically increase the disk size, RAM size, or |
| 30 | is still physically located on a server that is shared between multiple virtual | 30 | number of CPUs at any time. However, the virtual server is still |
| 31 | physically located on a server that is shared between multiple virtual | ||
| 31 | servers. | 32 | servers. |
| 32 | 33 | ||
| 33 | The choice between shared hosting and VPS mostly depends on your skill level | 34 | The choice between shared hosting and VPS mostly depends on your skill |
| 34 | with system administration. If you're comforting working on a server that is | 35 | level with system administration. If you're comforting working on a |
| 35 | mostly left up to you (or you're willing to learn), then a VPS is usually a | 36 | server that is mostly left up to you (or you're willing to learn), then |
| 36 | better option. However, shared hosting is a fantastic option for people who | 37 | a VPS is usually a better option. However, shared hosting is a fantastic |
| 37 | don't want to have to learn how to manage their server. | 38 | option for people who don't want to have to learn how to manage their |
| 38 | 39 | server. | |
| 39 | ## My Situation | ||
| 40 | 40 | ||
| 41 | I had used shared hosting for approximately 5 years before trying my first VPS. | 41 | ** My Situation |
| 42 | I manage a homelab and have had success running a server and performing typical | 42 | :PROPERTIES: |
| 43 | sysadmin duties, but I was still hesitant to get a VPS. One fear was that I | 43 | :CUSTOM_ID: my-situation |
| 44 | always struggled to properly set up the networking part of a server - DNS and | 44 | :END: |
| 45 | hostname configurations were not my friend. | 45 | I had used shared hosting for approximately 5 years before trying my |
| 46 | first VPS. I manage a homelab and have had success running a server and | ||
| 47 | performing typical sysadmin duties, but I was still hesitant to get a | ||
| 48 | VPS. One fear was that I always struggled to properly set up the | ||
| 49 | networking part of a server - DNS and hostname configurations were not | ||
| 50 | my friend. | ||
| 46 | 51 | ||
| 47 | As a little bit of background, I originally used | 52 | As a little bit of background, I originally used |
| 48 | [Siteground](https://www.siteground.com) for my initially shared hosting and | 53 | [[https://www.siteground.com][Siteground]] for my initially shared |
| 49 | stayed on that platform for at least a year. However, the UI was clunky, and I | 54 | hosting and stayed on that platform for at least a year. However, the UI |
| 50 | didn't like how they handled certain technical aspects, so I switched to | 55 | was clunky, and I didn't like how they handled certain technical |
| 51 | [Namecheap](https://www.namecheap.com). Namecheap was great because it is the | 56 | aspects, so I switched to [[https://www.namecheap.com][Namecheap]]. |
| 52 | service I primarily use for purchasing domain names, which made it incredibly | 57 | Namecheap was great because it is the service I primarily use for |
| 53 | easy to link them to my hosting service. However, it was still mediocre shared | 58 | purchasing domain names, which made it incredibly easy to link them to |
| 54 | hosting, and Namecheap is notorious for not letting you use | 59 | my hosting service. However, it was still mediocre shared hosting, and |
| 55 | [Let's Encrypt](https://letsencrypt.org) to obtain free SSL/TLS certificates; | 60 | Namecheap is notorious for not letting you use |
| 56 | Namecheap wants to make you purchase certificates through their store. | 61 | [[https://letsencrypt.org][Let's Encrypt]] to obtain free SSL/TLS |
| 57 | 62 | certificates; Namecheap wants to make you purchase certificates through | |
| 58 | Finally, I settled down with [iWebFusion](https://www.iwebfusion.net) for about | 63 | their store. |
| 59 | the last year of my shared hosting. This service was pretty great, came with | 64 | |
| 60 | free SSL/TLS, and I never had any complaints. | 65 | Finally, I settled down with [[https://www.iwebfusion.net][iWebFusion]] |
| 61 | 66 | for about the last year of my shared hosting. This service was pretty | |
| 62 | However, I finally grew tired of not being able to install software on my own | 67 | great, came with free SSL/TLS, and I never had any complaints. |
| 63 | web server. I wanted to be able to try out things like | 68 | |
| 64 | [Postmill](https://postmill.xyz) or [Matrix](https://matrix.org). This is | 69 | However, I finally grew tired of not being able to install software on |
| 65 | possible with a VPS, so I decided to grab a new domain name to try it out. | 70 | my own web server. I wanted to be able to try out things like |
| 66 | 71 | [[https://postmill.xyz][Postmill]] or [[https://matrix.org][Matrix]]. | |
| 67 | ## Getting Started: Buying a VPS | 72 | This is possible with a VPS, so I decided to grab a new domain name to |
| 68 | 73 | try it out. | |
| 69 | The first step to moving over to a VPS is (you guessed it): finding a VPS | 74 | |
| 70 | provider. For my VPSs, I use [1984](https://1984hosting.com) and prefer their | 75 | ** Getting Started: Buying a VPS |
| 71 | services much more than any alternative, due to their location (Iceland), their | 76 | :PROPERTIES: |
| 72 | [privacy policy](https://1984hosting.com/GDPR/), their respect for GDPR, and the | 77 | :CUSTOM_ID: getting-started-buying-a-vps |
| 73 | ability to remain anonymous if you pay in Bitcoin or Monero. | 78 | :END: |
| 74 | 79 | The first step to moving over to a VPS is (you guessed it): finding a | |
| 75 | [Njalla](https://njal.la) is another good, privacy-oriented option for VPS | 80 | VPS provider. For my VPSs, I use [[https://1984hosting.com][1984]] and |
| 76 | services. | 81 | prefer their services much more than any alternative, due to their |
| 77 | 82 | location (Iceland), their [[https://1984hosting.com/GDPR/][privacy | |
| 78 | You'll have to decide what specifications you want on your VPS. For me, I only | 83 | policy]], their respect for GDPR, and the ability to remain anonymous if |
| 79 | build and deploy low-resource HTML, PHP, and Python websites. This means I can | 84 | you pay in Bitcoin or Monero. |
| 80 | survive on the smallest VPS: 1 CPU, 1GB of RAM, and 25GB SSD for $5.00 per | 85 | |
| 81 | month. | 86 | [[https://njal.la][Njalla]] is another good, privacy-oriented option for |
| 82 | 87 | VPS services. | |
| 83 | As noted above, the great thing about a VPS is you can request your provider to | 88 | |
| 84 | increase the resources at any time. | 89 | You'll have to decide what specifications you want on your VPS. For me, |
| 85 | 90 | I only build and deploy low-resource HTML, PHP, and Python websites. | |
| 86 | ## Configuring DNS Settings | 91 | This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and |
| 87 | 92 | 25GB SSD for $5.00 per month. | |
| 88 | Okay, so now let's get into some actual work that has to be done to get content | 93 | |
| 89 | moved from a shared host to a VPS. At this point, I'm assuming you have a shared | 94 | As noted above, the great thing about a VPS is you can request your |
| 90 | host with website content that you can still access, and you've purchased a new | 95 | provider to increase the resources at any time. |
| 91 | VPS and can SSH into that server. | 96 | |
| 97 | ** Configuring DNS Settings | ||
| 98 | :PROPERTIES: | ||
| 99 | :CUSTOM_ID: configuring-dns-settings | ||
| 100 | :END: | ||
| 101 | Okay, so now let's get into some actual work that has to be done to get | ||
| 102 | content moved from a shared host to a VPS. At this point, I'm assuming | ||
| 103 | you have a shared host with website content that you can still access, | ||
| 104 | and you've purchased a new VPS and can SSH into that server. | ||
| 92 | 105 | ||
| 93 | The first change is minor, but it should be done immediately in order to | 106 | The first change is minor, but it should be done immediately in order to |
| 94 | get things moving: DNS settings. Go to wherever your DNS settings are handled. | 107 | get things moving: DNS settings. Go to wherever your DNS settings are |
| 95 | If your shared host also managed your DNS settings, you'll need to first move | 108 | handled. If your shared host also managed your DNS settings, you'll need |
| 96 | that DNS over to your new VPS provider. For me, I route my DNS through | 109 | to first move that DNS over to your new VPS provider. For me, I route my |
| 97 | [Gandi](https://www.gandi.net). | 110 | DNS through [[https://www.gandi.net][Gandi]]. |
| 98 | 111 | ||
| 99 | Once you know where your DNS settings are, go ahead and update the `A` records | 112 | Once you know where your DNS settings are, go ahead and update the =A= |
| 100 | to match the public IP address of your VPS. For example: | 113 | records to match the public IP address of your VPS. For example: |
| 101 | 114 | ||
| 102 | ```txt | 115 | #+begin_src txt |
| 103 | A example.com xxx.xxx.xxx.xxx | 116 | A example.com xxx.xxx.xxx.xxx |
| 104 | A subdomain xxx.xxx.xxx.xxx | 117 | A subdomain xxx.xxx.xxx.xxx |
| 105 | CNAME www example.com. | 118 | CNAME www example.com. |
| 106 | ``` | 119 | #+end_src |
| 107 | 120 | ||
| 108 | If you have any other records that require updates, such as MX or TXT records | 121 | If you have any other records that require updates, such as MX or TXT |
| 109 | for a mail server, be sure to update those accordingly. Personally, I don't host | 122 | records for a mail server, be sure to update those accordingly. |
| 110 | my own mail server. | 123 | Personally, I don't host my own mail server. I route all mail on my |
| 111 | I route all mail on my custom domains to [Migadu](https://www.migadu.com). | 124 | custom domains to [[https://www.migadu.com][Migadu]]. Hosting your own |
| 112 | Hosting your own email server can become complex quickly and is not for | 125 | email server can become complex quickly and is not for beginners. |
| 113 | beginners. | 126 | |
| 114 | 127 | DNS changes can take up to 48 hours to propagate, so be sure to give it | |
| 115 | DNS changes can take up to 48 hours to propagate, so be sure to give it some | 128 | some time before assuming you've made an error. |
| 116 | time before assuming you've made an error. | 129 | |
| 117 | 130 | ** Server Updates and Packages | |
| 118 | ## Server Updates and Packages | 131 | :PROPERTIES: |
| 119 | 132 | :CUSTOM_ID: server-updates-and-packages | |
| 120 | Now that the DNS settings have been changed, let's set up our server while we | 133 | :END: |
| 121 | wait for the DNS to propagate. | 134 | Now that the DNS settings have been changed, let's set up our server |
| 122 | First up is to ssh into your server. | 135 | while we wait for the DNS to propagate. First up is to ssh into your |
| 123 | If you've signed up with a service like DigitalOcean, you can add your SSH | 136 | server. If you've signed up with a service like DigitalOcean, you can |
| 124 | key to your account and to your VPS droplet so that you don't need a | 137 | add your SSH key to your account and to your VPS droplet so that you |
| 125 | password in order to SSH. | 138 | don't need a password in order to SSH. |
| 126 | 139 | ||
| 127 | ```sh | 140 | #+begin_src sh |
| 128 | ssh root@xxx.xxx.xxx.xxx | 141 | ssh root@xxx.xxx.xxx.xxx |
| 129 | ``` | 142 | #+end_src |
| 130 | 143 | ||
| 131 | The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache web | 144 | The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache |
| 132 | server. | 145 | web server. If you're working on a different operating system (OS) or |
| 133 | If you're working on a different operating system (OS) or want a | 146 | want a different web server, such as Nginx, you'll have to use different |
| 134 | different web server, such as Nginx, you'll have to use different commands | 147 | commands to set it up. |
| 135 | to set it up. | ||
| 136 | 148 | ||
| 137 | First, let's update and upgrade our server. | 149 | First, let's update and upgrade our server. |
| 138 | 150 | ||
| 139 | **NOTE:** Since we have logged in to the server as `root` for now, we don't need | 151 | *NOTE:* Since we have logged in to the server as =root= for now, we |
| 140 | to use the `sudo` modifier before our commands. | 152 | don't need to use the =sudo= modifier before our commands. |
| 141 | 153 | ||
| 142 | ```sh | 154 | #+begin_src sh |
| 143 | apt update && apt upgrade -y | 155 | apt update && apt upgrade -y |
| 144 | ``` | 156 | #+end_src |
| 145 | |||
| 146 | ## Create A User Account | ||
| 147 | 157 | ||
| 148 | While being able to use `root` can be beneficial at times, you shouldn't use | 158 | ** Create A User Account |
| 149 | `root` unless you have to. | 159 | :PROPERTIES: |
| 160 | :CUSTOM_ID: create-a-user-account | ||
| 161 | :END: | ||
| 162 | While being able to use =root= can be beneficial at times, you shouldn't | ||
| 163 | use =root= unless you have to. | ||
| 150 | 164 | ||
| 151 | So let's set up a new user in our system. The `-m` option below tells the OS to | 165 | So let's set up a new user in our system. The =-m= option below tells |
| 152 | create a home directory for the new user. | 166 | the OS to create a home directory for the new user. |
| 153 | 167 | ||
| 154 | ```sh | 168 | #+begin_src sh |
| 155 | adduser USERNAME | 169 | adduser USERNAME |
| 156 | ``` | 170 | #+end_src |
| 157 | 171 | ||
| 158 | Now, create a password for that user. | 172 | Now, create a password for that user. |
| 159 | 173 | ||
| 160 | ```sh | 174 | #+begin_src sh |
| 161 | passwd USERNAME | 175 | passwd USERNAME |
| 162 | ``` | 176 | #+end_src |
| 163 | 177 | ||
| 164 | Finally, add the user to the sudoers file, so they can perform priveleged | 178 | Finally, add the user to the sudoers file, so they can perform |
| 165 | commands. | 179 | priveleged commands. |
| 166 | 180 | ||
| 167 | ```sh | 181 | #+begin_src sh |
| 168 | usermod -a -G sudo USERNAME | 182 | usermod -a -G sudo USERNAME |
| 169 | ``` | 183 | #+end_src |
| 170 | 184 | ||
| 171 | If you are using SSH keys and not passwords, you'll need to copy your SSH key | 185 | If you are using SSH keys and not passwords, you'll need to copy your |
| 172 | from your local machine to the VPS. If you haven't disabled password-based SSH | 186 | SSH key from your local machine to the VPS. If you haven't disabled |
| 173 | yet, the easiest way to do this is `ssh-copy-id` from your local computer (not | 187 | password-based SSH yet, the easiest way to do this is =ssh-copy-id= from |
| 174 | from the VPS): | 188 | your local computer (not from the VPS): |
| 175 | 189 | ||
| 176 | ```sh | 190 | #+begin_src sh |
| 177 | ssh-copy-id testuser@xxx.xxx.xxx.xxx | 191 | ssh-copy-id testuser@xxx.xxx.xxx.xxx |
| 178 | ``` | 192 | #+end_src |
| 179 | 193 | ||
| 180 | If you've disabled password-based SSH, you'll need to manually copy your SSH key | 194 | If you've disabled password-based SSH, you'll need to manually copy your |
| 181 | into the `~/.ssh/authorized_keys` file. | 195 | SSH key into the =~/.ssh/authorized_keys= file. |
| 182 | 196 | ||
| 183 | ## Install Software | 197 | ** Install Software |
| 198 | :PROPERTIES: | ||
| 199 | :CUSTOM_ID: install-software | ||
| 200 | :END: | ||
| 201 | Our goal here is to host a web server, so the next step is to install | ||
| 202 | the Apache web server and any other packages we need. | ||
| 184 | 203 | ||
| 185 | Our goal here is to host a web server, so the next step is to install the Apache | 204 | From this point on, I will be logged in as a user (not =root=) and will |
| 186 | web server and any other packages we need. | 205 | need to use the =sudo= modifier for most commands. |
| 187 | 206 | ||
| 188 | From this point on, I will be logged in as a user (not `root`) and will need to | 207 | #+begin_src sh |
| 189 | use the `sudo` modifier for most commands. | ||
| 190 | |||
| 191 | ```sh | ||
| 192 | sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y | 208 | sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y |
| 193 | sudo apt install apache2 | 209 | sudo apt install apache2 |
| 194 | ``` | 210 | #+end_src |
| 195 | 211 | ||
| 196 | If you need other language support, such as PHP, you'll need to install that | 212 | If you need other language support, such as PHP, you'll need to install |
| 197 | too. | 213 | that too. |
| 198 | 214 | ||
| 199 | ```sh | 215 | #+begin_src sh |
| 200 | sudo apt install libapache2-mod-php php-dom | 216 | sudo apt install libapache2-mod-php php-dom |
| 201 | sudo a2enmod php | 217 | sudo a2enmod php |
| 202 | sudo systemctl restart apache2 | 218 | sudo systemctl restart apache2 |
| 203 | ``` | 219 | #+end_src |
| 204 | |||
| 205 | ## Website Files & Folders | ||
| 206 | 220 | ||
| 207 | Next up is to create the directories for the domain(s) we want to be hosted on | 221 | ** Website Files & Folders |
| 208 | this web server. | 222 | :PROPERTIES: |
| 223 | :CUSTOM_ID: website-files-folders | ||
| 224 | :END: | ||
| 225 | Next up is to create the directories for the domain(s) we want to be | ||
| 226 | hosted on this web server. | ||
| 209 | 227 | ||
| 210 | ```sh | 228 | #+begin_src sh |
| 211 | cd /var/www | 229 | cd /var/www |
| 212 | sudo mkdir example.com | 230 | sudo mkdir example.com |
| 213 | ``` | 231 | #+end_src |
| 214 | 232 | ||
| 215 | We have a folder for `example.com` now, so let's add an `index.html` file and | 233 | We have a folder for =example.com= now, so let's add an =index.html= |
| 216 | put it within a specific `public_html` folder. You don't need this `public_html` | 234 | file and put it within a specific =public_html= folder. You don't need |
| 217 | if you don't want it, but it helps with organizing items related to | 235 | this =public_html= if you don't want it, but it helps with organizing |
| 218 | `example.com` that you don't want to publish to the internet. | 236 | items related to =example.com= that you don't want to publish to the |
| 237 | internet. | ||
| 219 | 238 | ||
| 220 | ```sh | 239 | #+begin_src sh |
| 221 | cd example.com | 240 | cd example.com |
| 222 | sudo mkdir public_html && cd public_html | 241 | sudo mkdir public_html && cd public_html |
| 223 | sudo nano index.html | 242 | sudo nano index.html |
| 224 | ``` | 243 | #+end_src |
| 225 | 244 | ||
| 226 | You can put anything you want in this `index.html` file. If you can't think of | 245 | You can put anything you want in this =index.html= file. If you can't |
| 227 | anything, paste this in there: | 246 | think of anything, paste this in there: |
| 228 | 247 | ||
| 229 | ```html | 248 | #+begin_src html |
| 230 | <!DOCTYPE html> | 249 | <!DOCTYPE html> |
| 231 | <html lang="en"> | 250 | <html lang="en"> |
| 232 | <head> | 251 | <head> |
| 233 | <meta charset="utf-8" /> | 252 | <meta charset="utf-8" /> |
| 234 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | 253 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 235 | <title>Hello, world!</title> | 254 | <title>Hello, world!</title> |
| 236 | </head> | 255 | </head> |
| 237 | <body> | 256 | <body> |
| 238 | <h1>Hello, world!</h1> | 257 | <h1>Hello, world!</h1> |
| 239 | </body> | 258 | </body> |
| 240 | </html> | 259 | </html> |
| 241 | ``` | 260 | #+end_src |
| 242 | 261 | ||
| 243 | If you want something to be served at `example.com/page01/file.txt`, you'll have | 262 | If you want something to be served at =example.com/page01/file.txt=, |
| 244 | to create the `page01` directory under the `example.com` directory. For example: | 263 | you'll have to create the =page01= directory under the =example.com= |
| 264 | directory. For example: | ||
| 245 | 265 | ||
| 246 | ```sh | 266 | #+begin_src sh |
| 247 | cd /var/www/example.com/public_html | 267 | cd /var/www/example.com/public_html |
| 248 | sudo mkdir page01 | 268 | sudo mkdir page01 |
| 249 | sudo nano file.txt | 269 | sudo nano file.txt |
| 250 | ``` | 270 | #+end_src |
| 251 | 271 | ||
| 252 | ## Apache Configuration | 272 | ** Apache Configuration |
| 273 | :PROPERTIES: | ||
| 274 | :CUSTOM_ID: apache-configuration | ||
| 275 | :END: | ||
| 276 | Now, let's set up the files that will tell the server where to find the | ||
| 277 | files for =example.com=. We will copy the default configuration file and | ||
| 278 | create our own. | ||
| 253 | 279 | ||
| 254 | Now, let's set up the files that will tell the server where to find the files | 280 | #+begin_src sh |
| 255 | for `example.com`. We will copy the default configuration file and create our | ||
| 256 | own. | ||
| 257 | |||
| 258 | ```sh | ||
| 259 | cd /etc/apache2/sites-available | 281 | cd /etc/apache2/sites-available |
| 260 | sudo cp 000-default.conf example.com.conf | 282 | sudo cp 000-default.conf example.com.conf |
| 261 | sudo nano example.com.conf | 283 | sudo nano example.com.conf |
| 262 | ``` | 284 | #+end_src |
| 263 | 285 | ||
| 264 | This configuration file will have a few default lines, but you'll need to edit | 286 | This configuration file will have a few default lines, but you'll need |
| 265 | it to look similar to this (settings may change based on your personal needs): | 287 | to edit it to look similar to this (settings may change based on your |
| 288 | personal needs): | ||
| 266 | 289 | ||
| 267 | ```config | 290 | #+begin_src config |
| 268 | <VirtualHost *:80> | 291 | <VirtualHost *:80> |
| 269 | ServerAdmin your-email@email-provider.com | 292 | ServerAdmin your-email@email-provider.com |
| 270 | ServerName example.com | 293 | ServerName example.com |
| @@ -273,128 +296,138 @@ it to look similar to this (settings may change based on your personal needs): | |||
| 273 | ErrorLog ${APACHE_LOG_DIR}/error.log | 296 | ErrorLog ${APACHE_LOG_DIR}/error.log |
| 274 | CustomLog ${APACHE_LOG_DIR}/access.log combined | 297 | CustomLog ${APACHE_LOG_DIR}/access.log combined |
| 275 | </VirtualHost> | 298 | </VirtualHost> |
| 276 | ``` | 299 | #+end_src |
| 277 | 300 | ||
| 278 | Now, enable the configuration for your new site, disable the default | 301 | Now, enable the configuration for your new site, disable the default |
| 279 | configuration, and reload the web server. | 302 | configuration, and reload the web server. |
| 280 | 303 | ||
| 281 | ```sh | 304 | #+begin_src sh |
| 282 | sudo a2ensite example.com.conf | 305 | sudo a2ensite example.com.conf |
| 283 | sudo a2dissite 000-default.conf | 306 | sudo a2dissite 000-default.conf |
| 284 | sudo systemctl reload apache2 | 307 | sudo systemctl reload apache2 |
| 285 | ``` | 308 | #+end_src |
| 286 | 309 | ||
| 287 | You can always run a test to make sure no errors or warnings are found in your | 310 | You can always run a test to make sure no errors or warnings are found |
| 288 | configuration files. | 311 | in your configuration files. |
| 289 | 312 | ||
| 290 | ```sh | 313 | #+begin_src sh |
| 291 | sudo apache2ctl configtest | 314 | sudo apache2ctl configtest |
| 292 | ``` | 315 | #+end_src |
| 293 | 316 | ||
| 294 | Now, restart the web server entirely. After this, you should be able to browse | 317 | Now, restart the web server entirely. After this, you should be able to |
| 295 | to `http://example.com` and see the HTML content you provided earlier. Note that | 318 | browse to =http://example.com= and see the HTML content you provided |
| 296 | SSL/TLS has not been enabled yet, so you won't be able to use the secure version | 319 | earlier. Note that SSL/TLS has not been enabled yet, so you won't be |
| 297 | yet (`https://example.com`). | 320 | able to use the secure version yet (=https://example.com=). |
| 298 | 321 | ||
| 299 | ```sh | 322 | #+begin_src sh |
| 300 | sudo systemctl restart apache2 | 323 | sudo systemctl restart apache2 |
| 301 | ``` | 324 | #+end_src |
| 302 | |||
| 303 | You can repeat this for as many websites as you need. Just create the domain | ||
| 304 | folders in `/var/www/`, add the configuration file, enable the configuration, | ||
| 305 | and restart `apache2`. | ||
| 306 | 325 | ||
| 307 | ## SSL/TLS Certificates: Serve Websites Over HTTPS | 326 | You can repeat this for as many websites as you need. Just create the |
| 327 | domain folders in =/var/www/=, add the configuration file, enable the | ||
| 328 | configuration, and restart =apache2=. | ||
| 308 | 329 | ||
| 309 | In order to serve secure content, you'll need to obtain SSL/TLS certificates. | 330 | ** SSL/TLS Certificates: Serve Websites Over HTTPS |
| 310 | Luckily, there's a free tool called [Certbot](https://certbot.eff.org) that | 331 | :PROPERTIES: |
| 311 | helps us with the process. | 332 | :CUSTOM_ID: ssltls-certificates-serve-websites-over-https |
| 333 | :END: | ||
| 334 | In order to serve secure content, you'll need to obtain SSL/TLS | ||
| 335 | certificates. Luckily, there's a free tool called | ||
| 336 | [[https://certbot.eff.org][Certbot]] that helps us with the process. | ||
| 312 | 337 | ||
| 313 | The first step is to install `snapd` and `core` for Ubuntu. | 338 | The first step is to install =snapd= and =core= for Ubuntu. |
| 314 | 339 | ||
| 315 | ```sh | 340 | #+begin_src sh |
| 316 | sudo apt install snapd | 341 | sudo apt install snapd |
| 317 | sudo snap install core | 342 | sudo snap install core |
| 318 | sudo snap refresh core | 343 | sudo snap refresh core |
| 319 | ``` | 344 | #+end_src |
| 320 | 345 | ||
| 321 | Next, install the `certbot` snap package. | 346 | Next, install the =certbot= snap package. |
| 322 | 347 | ||
| 323 | ```sh | 348 | #+begin_src sh |
| 324 | sudo snap install --classic certbot | 349 | sudo snap install --classic certbot |
| 325 | ``` | 350 | #+end_src |
| 326 | 351 | ||
| 327 | Execute the following command to ensure that the `certbot` command can be run. | 352 | Execute the following command to ensure that the =certbot= command can |
| 353 | be run. | ||
| 328 | 354 | ||
| 329 | ```sh | 355 | #+begin_src sh |
| 330 | sudo ln -s /snap/bin/certbot /usr/bin/certbot | 356 | sudo ln -s /snap/bin/certbot /usr/bin/certbot |
| 331 | ``` | 357 | #+end_src |
| 332 | 358 | ||
| 333 | Finally, you can run `certbot` one of two ways: | 359 | Finally, you can run =certbot= one of two ways: |
| 334 | 360 | ||
| 335 | 1. run it and let it alter your Apache configuration files automatically to | 361 | 1. run it and let it alter your Apache configuration files automatically |
| 336 | enable HTTPS redirects. | 362 | to enable HTTPS redirects. |
| 337 | 2. run it and only allow it to create certificates. | 363 | 2. run it and only allow it to create certificates. You'll need to |
| 338 | You'll need to manually alter the config files to enable HTTPS redirects. | 364 | manually alter the config files to enable HTTPS redirects. |
| 339 | 365 | ||
| 340 | Run certbot and allow automatic config changes: | 366 | Run certbot and allow automatic config changes: |
| 341 | 367 | ||
| 342 | ```sh | 368 | #+begin_src sh |
| 343 | sudo certbot --apache | 369 | sudo certbot --apache |
| 344 | ``` | 370 | #+end_src |
| 345 | 371 | ||
| 346 | Run certbot for certificates only and don't allow it to alter config files: | 372 | Run certbot for certificates only and don't allow it to alter config |
| 373 | files: | ||
| 347 | 374 | ||
| 348 | ```sh | 375 | #+begin_src sh |
| 349 | sudo certbot certonly --apache | 376 | sudo certbot certonly --apache |
| 350 | ``` | 377 | #+end_src |
| 351 | 378 | ||
| 352 | The Certbot packages on your system come with a cron job or systemd timer that | 379 | The Certbot packages on your system come with a cron job or systemd |
| 353 | will renew your certificates automatically before they expire. You will not need | 380 | timer that will renew your certificates automatically before they |
| 354 | to run Certbot again unless you change your configuration. You can test | 381 | expire. You will not need to run Certbot again unless you change your |
| 355 | automatic renewal for your certificates by running this command: | 382 | configuration. You can test automatic renewal for your certificates by |
| 383 | running this command: | ||
| 356 | 384 | ||
| 357 | ```sh | 385 | #+begin_src sh |
| 358 | sudo certbot renew --dry-run | 386 | sudo certbot renew --dry-run |
| 359 | ``` | 387 | #+end_src |
| 360 | 388 | ||
| 361 | Now, test your domains by going to `https://example.com`. | 389 | Now, test your domains by going to =https://example.com=. |
| 362 | |||
| 363 | ## Firewall Security | ||
| 364 | 390 | ||
| 391 | ** Firewall Security | ||
| 392 | :PROPERTIES: | ||
| 393 | :CUSTOM_ID: firewall-security | ||
| 394 | :END: | ||
| 365 | To enable better security on your server, you'll need to enable a basic | 395 | To enable better security on your server, you'll need to enable a basic |
| 366 | firewall. For Ubuntu, we'll use | 396 | firewall. For Ubuntu, we'll use |
| 367 | [the uncomplicated firewall](https://cleberg.net/blog/secure-your-network-with-the-uncomplicated-firewall.html). | 397 | [[https://cleberg.net/blog/secure-your-network-with-the-uncomplicated-firewall.html][the |
| 398 | uncomplicated firewall]]. | ||
| 368 | 399 | ||
| 369 | Now, add the following rules to the firewall allow SSH, Apache, and HTTP(S) | 400 | Now, add the following rules to the firewall allow SSH, Apache, and |
| 370 | connections. If you need to, you can enable different ports for specifics | 401 | HTTP(S) connections. If you need to, you can enable different ports for |
| 371 | applications, SFTP, etc. | 402 | specifics applications, SFTP, etc. |
| 372 | 403 | ||
| 373 | ```sh | 404 | #+begin_src sh |
| 374 | sudo ufw default deny incoming | 405 | sudo ufw default deny incoming |
| 375 | sudo ufw default allow outgoing | 406 | sudo ufw default allow outgoing |
| 376 | sudo ufw allow OpenSSH | 407 | sudo ufw allow OpenSSH |
| 377 | sudo ufw allow Apache | 408 | sudo ufw allow Apache |
| 378 | sudo ufw allow proto tcp from any to any port 80,443 | 409 | sudo ufw allow proto tcp from any to any port 80,443 |
| 379 | ``` | 410 | #+end_src |
| 380 | 411 | ||
| 381 | Once you've added all the rules you need, enable the firewall. | 412 | Once you've added all the rules you need, enable the firewall. |
| 382 | 413 | ||
| 383 | ```sh | 414 | #+begin_src sh |
| 384 | sudo ufw enable | 415 | sudo ufw enable |
| 385 | ``` | 416 | #+end_src |
| 386 | |||
| 387 | ## Troubleshooting | ||
| 388 | 417 | ||
| 389 | If you run into any issues during your VPS set-up, be sure to walk back through | 418 | ** Troubleshooting |
| 390 | your actions and make sure you didn't miss any steps. | 419 | :PROPERTIES: |
| 420 | :CUSTOM_ID: troubleshooting | ||
| 421 | :END: | ||
| 422 | If you run into any issues during your VPS set-up, be sure to walk back | ||
| 423 | through your actions and make sure you didn't miss any steps. | ||
| 391 | 424 | ||
| 392 | Many websites have fantastic guides to setting up various web servers. | 425 | Many websites have fantastic guides to setting up various web servers. |
| 393 | This is one of the areas [where DigitalOcean shines](https://www.digitalocean. | 426 | This is one of the areas |
| 394 | com/community/tutorials). | 427 | [[https://www.digitalocean.%20com/community/tutorials][where |
| 395 | For simpler or more Linux-oriented questions, I suggest using [Linuxize] | 428 | DigitalOcean shines]]. For simpler or more Linux-oriented questions, I |
| 396 | (https://linuxize.com). | 429 | suggest using [Linuxize] (https://linuxize.com). |
| 397 | 430 | ||
| 398 | If you're getting certain errors (e.g. `500 Internal Server Error`) and need to | 431 | If you're getting certain errors (e.g. =500 Internal Server Error=) and |
| 399 | debug locally, you can view the `access.log` and `error.log` files in the | 432 | need to debug locally, you can view the =access.log= and =error.log= |
| 400 | `/var/log/apache/` directory. | 433 | files in the =/var/log/apache/= directory. |
blog/2021-04-17-gemini-server.org +128 −106
| @@ -1,159 +1,181 @@ | |||
| 1 | +++ | 1 | #+title: Hosting a Gemini Server |
| 2 | date = 2021-04-17 | 2 | #+date: 2021-04-17 |
| 3 | title = "Hosting a Gemini Server" | 3 | |
| 4 | description = "Dive deeper into Gemini by hosting your own Gemini web server." | 4 | ** Similar Article Available |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: similar-article-available |
| 7 | 7 | :END: | |
| 8 | ## Similar Article Available | 8 | To read more about Gemini and ways to test out this new protocol without |
| 9 | 9 | your own server, see my previous post | |
| 10 | To read more about Gemini and ways to test out this new protocol without your | 10 | [[/blog/launching-a-gemini-capsule/][Launching a Gemini Capsule]]. |
| 11 | own server, see my previous post | 11 | |
| 12 | [Launching a Gemini Capsule](/blog/launching-a-gemini-capsule/). | 12 | ** Preparation |
| 13 | 13 | :PROPERTIES: | |
| 14 | ## Preparation | 14 | :CUSTOM_ID: preparation |
| 15 | 15 | :END: | |
| 16 | This guide assumes you have access to a server accessible to the world through a | 16 | This guide assumes you have access to a server accessible to the world |
| 17 | public IP address and that you own a domain name used for this Gemini capsule. | 17 | through a public IP address and that you own a domain name used for this |
| 18 | 18 | Gemini capsule. | |
| 19 | ## Getting Started with Agate | 19 | |
| 20 | 20 | ** Getting Started with Agate | |
| 21 | We are going to use [Agate](https://github.com/mbrubeck/agate) for this | 21 | :PROPERTIES: |
| 22 | tutorial. | 22 | :CUSTOM_ID: getting-started-with-agate |
| 23 | This is a basic Gemini server written in Rust. | 23 | :END: |
| 24 | It takes very little time and maintenance to get it running. | 24 | We are going to use [[https://github.com/mbrubeck/agate][Agate]] for |
| 25 | 25 | this tutorial. This is a basic Gemini server written in Rust. It takes | |
| 26 | ## Install Dependencies | 26 | very little time and maintenance to get it running. |
| 27 | 27 | ||
| 28 | First, you will need to install the Rust package for your system. On Ubuntu, use | 28 | ** Install Dependencies |
| 29 | the following commands (remember to use `sudo` if you are not the root user). | 29 | :PROPERTIES: |
| 30 | The Rust installation will give you options to customize the installation; I | 30 | :CUSTOM_ID: install-dependencies |
| 31 | used the default installation options. | 31 | :END: |
| 32 | 32 | First, you will need to install the Rust package for your system. On | |
| 33 | ```sh | 33 | Ubuntu, use the following commands (remember to use =sudo= if you are |
| 34 | not the root user). The Rust installation will give you options to | ||
| 35 | customize the installation; I used the default installation options. | ||
| 36 | |||
| 37 | #+begin_src sh | ||
| 34 | sudo apt update && sudo apt upgrade -y | 38 | sudo apt update && sudo apt upgrade -y |
| 35 | curl https://sh.rustup.rs -sSf | sh | 39 | curl https://sh.rustup.rs -sSf | sh |
| 36 | ``` | 40 | #+end_src |
| 37 | 41 | ||
| 38 | Remember to configure your shell with the new configuration: | 42 | Remember to configure your shell with the new configuration: |
| 39 | 43 | ||
| 40 | ```sh | 44 | #+begin_src sh |
| 41 | source $HOME/.cargo/env | 45 | source $HOME/.cargo/env |
| 42 | ``` | 46 | #+end_src |
| 43 | 47 | ||
| 44 | Before we install agate, make sure you have the `gcc` package installed: | 48 | Before we install agate, make sure you have the =gcc= package installed: |
| 45 | 49 | ||
| 46 | ```sh | 50 | #+begin_src sh |
| 47 | sudo apt install gcc | 51 | sudo apt install gcc |
| 48 | ``` | 52 | #+end_src |
| 49 | 53 | ||
| 50 | Next, you'll need to install the agate executable with Rust's Cargo package | 54 | Next, you'll need to install the agate executable with Rust's Cargo |
| 51 | maintainer: | 55 | package maintainer: |
| 52 | 56 | ||
| 53 | ```sh | 57 | #+begin_src sh |
| 54 | cargo install agate | 58 | cargo install agate |
| 55 | ``` | 59 | #+end_src |
| 56 | |||
| 57 | ## Create Symlinks | ||
| 58 | 60 | ||
| 59 | Once Cargo has finished installing all the required packages, symlink the | 61 | ** Create Symlinks |
| 60 | executable to your \$PATH. | 62 | :PROPERTIES: |
| 63 | :CUSTOM_ID: create-symlinks | ||
| 64 | :END: | ||
| 65 | Once Cargo has finished installing all the required packages, symlink | ||
| 66 | the executable to your $PATH. | ||
| 61 | 67 | ||
| 62 | ```sh | 68 | #+begin_src sh |
| 63 | sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate | 69 | sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate |
| 64 | ``` | 70 | #+end_src |
| 65 | 71 | ||
| 66 | ## Using Agate's Built-In Installation Tool | 72 | ** Using Agate's Built-In Installation Tool |
| 73 | :PROPERTIES: | ||
| 74 | :CUSTOM_ID: using-agates-built-in-installation-tool | ||
| 75 | :END: | ||
| 76 | If you're running Ubuntu or Debian, use the Debian installation script | ||
| 77 | found in Agate's GitHub repository, under the =tools/debian= folder. | ||
| 67 | 78 | ||
| 68 | If you're running Ubuntu or Debian, use the Debian installation script found in | 79 | #+begin_src sh |
| 69 | Agate's GitHub repository, under the `tools/debian` folder. | ||
| 70 | |||
| 71 | ```sh | ||
| 72 | git clone https://github.com/mbrubeck/agate | 80 | git clone https://github.com/mbrubeck/agate |
| 73 | cd agate/tools/debian | 81 | cd agate/tools/debian |
| 74 | sudo ./install.sh | 82 | sudo ./install.sh |
| 75 | ``` | 83 | #+end_src |
| 76 | 84 | ||
| 77 | ## Configure the Gemini Service | 85 | ** Configure the Gemini Service |
| 78 | 86 | :PROPERTIES: | |
| 79 | We have a little more to do, but since this script tries to immediately run the | 87 | :CUSTOM_ID: configure-the-gemini-service |
| 80 | service, it will likely fail with an exit code. Let's add our finishing touches. | 88 | :END: |
| 81 | Edit the following file and replace the hostname with your desired URL. You can | 89 | We have a little more to do, but since this script tries to immediately |
| 82 | also change the directory where content will be served. | 90 | run the service, it will likely fail with an exit code. Let's add our |
| 83 | 91 | finishing touches. Edit the following file and replace the hostname with | |
| 84 | ```sh | 92 | your desired URL. You can also change the directory where content will |
| 93 | be served. | ||
| 94 | |||
| 95 | #+begin_src sh | ||
| 85 | sudo nano /etc/systemd/system/gemini.service | 96 | sudo nano /etc/systemd/system/gemini.service |
| 86 | ``` | 97 | #+end_src |
| 87 | 98 | ||
| 88 | ```sh | 99 | #+begin_src sh |
| 89 | # Edit these lines to whatever you want - see the next code block for my personal configuration. | 100 | # Edit these lines to whatever you want - see the next code block for my personal configuration. |
| 90 | WorkingDirectory=/srv/gemini | 101 | WorkingDirectory=/srv/gemini |
| 91 | ExecStart=agate --hostname $(uname -n) --lang en | 102 | ExecStart=agate --hostname $(uname -n) --lang en |
| 92 | ``` | 103 | #+end_src |
| 93 | 104 | ||
| 94 | This is my personal config: | 105 | This is my personal config: |
| 95 | 106 | ||
| 96 | ```sh | 107 | #+begin_src sh |
| 97 | WorkingDirectory=/var/gemini/ | 108 | WorkingDirectory=/var/gemini/ |
| 98 | ExecStart=agate --hostname gemini.example.com --lang en | 109 | ExecStart=agate --hostname gemini.example.com --lang en |
| 99 | ``` | 110 | #+end_src |
| 100 | 111 | ||
| 101 | Since we've altered the systemd configuration files, we have to reload the | 112 | Since we've altered the systemd configuration files, we have to reload |
| 102 | daemon. Let's do that, restart our service, and check its status. | 113 | the daemon. Let's do that, restart our service, and check its status. |
| 103 | 114 | ||
| 104 | ```sh | 115 | #+begin_src sh |
| 105 | sudo systemctl daemon-reload | 116 | sudo systemctl daemon-reload |
| 106 | sudo systemctl restart gemini.service | 117 | sudo systemctl restart gemini.service |
| 107 | sudo systemctl status gemini.service | 118 | sudo systemctl status gemini.service |
| 108 | ``` | 119 | #+end_src |
| 109 | |||
| 110 | ## Fixing Systemd Errors | ||
| 111 | 120 | ||
| 112 | If you're still getting errors, the installation process may not have properly | 121 | ** Fixing Systemd Errors |
| 113 | enabled the gemini service. Fix it with the following commands. | 122 | :PROPERTIES: |
| 123 | :CUSTOM_ID: fixing-systemd-errors | ||
| 124 | :END: | ||
| 125 | If you're still getting errors, the installation process may not have | ||
| 126 | properly enabled the gemini service. Fix it with the following commands. | ||
| 114 | 127 | ||
| 115 | ```sh | 128 | #+begin_src sh |
| 116 | sudo systemctl enable gemini.service | 129 | sudo systemctl enable gemini.service |
| 117 | sudo systemctl restart gemini.service | 130 | sudo systemctl restart gemini.service |
| 118 | sudo systemctl status gemini.service | 131 | sudo systemctl status gemini.service |
| 119 | ``` | 132 | #+end_src |
| 120 | 133 | ||
| 121 | ## Firewall Rules | 134 | ** Firewall Rules |
| 135 | :PROPERTIES: | ||
| 136 | :CUSTOM_ID: firewall-rules | ||
| 137 | :END: | ||
| 138 | Great! Our server is now functional and running. The first consideration | ||
| 139 | now is that you need to be able to access port 1965 on the server. If | ||
| 140 | you have a firewall enabled, you'll need to open that port up. | ||
| 122 | 141 | ||
| 123 | Great! Our server is now functional and running. The first consideration now is | 142 | #+begin_src sh |
| 124 | that you need to be able to access port 1965 on the server. If you have a | ||
| 125 | firewall enabled, you'll need to open that port up. | ||
| 126 | |||
| 127 | ```sh | ||
| 128 | sudo ufw allow 1965 | 143 | sudo ufw allow 1965 |
| 129 | sudo ufw reload | 144 | sudo ufw reload |
| 130 | ``` | 145 | #+end_src |
| 131 | 146 | ||
| 132 | ## Creating Content | 147 | ** Creating Content |
| 133 | 148 | :PROPERTIES: | |
| 134 | Let's create the Gemini capsule. Note that wherever you set the WorkingDirectory | 149 | :CUSTOM_ID: creating-content |
| 135 | variable to earlier, Agate will expect you to put your Gemini capsule contents | 150 | :END: |
| 136 | in a sub-folder called "content." So, I place my files in "/var/gmi/content." I'm | 151 | Let's create the Gemini capsule. Note that wherever you set the |
| 137 | going to create that folder now and put a file in there. | 152 | WorkingDirectory variable to earlier, Agate will expect you to put your |
| 138 | 153 | Gemini capsule contents in a sub-folder called "content." So, I place my | |
| 139 | ```sh | 154 | files in "/var/gmi/content." I'm going to create that folder now and put |
| 155 | a file in there. | ||
| 156 | |||
| 157 | #+begin_src sh | ||
| 140 | sudo mkdir /var/gemini/content | 158 | sudo mkdir /var/gemini/content |
| 141 | sudo nano /var/gemini/content/index.gmi | 159 | sudo nano /var/gemini/content/index.gmi |
| 142 | ``` | 160 | #+end_src |
| 143 | |||
| 144 | You can put whatever you want in the "index.gmi" file, just make sure it's valid | ||
| 145 | Gemtext. | ||
| 146 | 161 | ||
| 147 | ## The Results | 162 | You can put whatever you want in the "index.gmi" file, just make sure |
| 163 | it's valid Gemtext. | ||
| 148 | 164 | ||
| 165 | ** The Results | ||
| 166 | :PROPERTIES: | ||
| 167 | :CUSTOM_ID: the-results | ||
| 168 | :END: | ||
| 149 | Here are some screenshots of the Gemini page I just created in the | 169 | Here are some screenshots of the Gemini page I just created in the |
| 150 | [Lagrange](https://gmi.skyjake.fi/lagrange/) browser and the | 170 | [[https://gmi.skyjake.fi/lagrange/][Lagrange]] browser and the |
| 151 | [amfora](https://github.com/makeworld-the-better-one/amfora) browser. | 171 | [[https://github.com/makeworld-the-better-one/amfora][amfora]] browser. |
| 152 | 172 | ||
| 153 |  | 173 | #+caption: GUI Gemini browser |
| 174 | [[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png]] | ||
| 154 | 175 | ||
| 155 | _Lagrange_ | 176 | /Lagrange/ |
| 156 | 177 | ||
| 157 |  | 178 | #+caption: CLI Gemini browser |
| 179 | [[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png]] | ||
| 158 | 180 | ||
| 159 | _Amfora_ | 181 | /Amfora/ |
blog/2021-04-23-php-comment-system.org +198 −176
| @@ -1,151 +1,161 @@ | |||
| 1 | +++ | 1 | #+title: Roll Your Own Static Commenting System in PHP |
| 2 | date = 2021-04-23 | 2 | #+date: 2021-04-23 |
| 3 | title = "Roll Your Own Static Commenting System in PHP" | ||
| 4 | description = "Learn how to skip the tedium of finding the proper commenting system by simply rolling your own in PHP." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | 3 | ||
| 8 | ## The Terrible-ness of Commenting Systems | 4 | ** The Terrible-ness of Commenting Systems |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: the-terrible-ness-of-commenting-systems | ||
| 7 | :END: | ||
| 8 | The current state of affairs regarding interactive comment systems is, | ||
| 9 | well, terrible. It is especially awful if you're a privacy conscious | ||
| 10 | person who does not generally load third-party scripts or frames on the | ||
| 11 | websites you visit. | ||
| 9 | 12 | ||
| 10 | The current state of affairs regarding interactive comment systems is, well, | 13 | Even further, many comment systems are charging exorbitant fees for |
| 11 | terrible. It is especially awful if you're a privacy conscious person who does | 14 | something that should be standard. |
| 12 | not generally load third-party scripts or frames on the websites you visit. | ||
| 13 | |||
| 14 | Even further, many comment systems are charging exorbitant fees for something | ||
| 15 | that should be standard. | ||
| 16 | 15 | ||
| 17 | Of course, there are some really terrible options: | 16 | Of course, there are some really terrible options: |
| 18 | 17 | ||
| 19 | - Facebook Comments | 18 | - Facebook Comments |
| 20 | - Discourse | 19 | - Discourse |
| 21 | 20 | ||
| 22 | There are some options that are better but still use too many scripts, frames, | 21 | There are some options that are better but still use too many scripts, |
| 23 | or social integrations on your web page that could impact some users: | 22 | frames, or social integrations on your web page that could impact some |
| 24 | 23 | users: | |
| 25 | - Disqus | 24 | |
| 26 | - Isso | 25 | - Disqus |
| 27 | - Remark42 | 26 | - Isso |
| 28 | 27 | - Remark42 | |
| 29 | Lastly, I looked into a few unique ways of generating blog comments, such as | 28 | |
| 30 | using Twitter threads or GitHub issues to automatically post issues. However, | 29 | Lastly, I looked into a few unique ways of generating blog comments, |
| 31 | these both rely on external third-party sites that I don't currently use. | 30 | such as using Twitter threads or GitHub issues to automatically post |
| 32 | 31 | issues. However, these both rely on external third-party sites that I | |
| 33 | ## Stay Static with Server-Side Comments | 32 | don't currently use. |
| 34 | 33 | ||
| 35 | The main issue for my personal use-case is that my blog is completely, 100% | 34 | ** Stay Static with Server-Side Comments |
| 36 | static. I use PHP on the back-end but website visitors only see HTML and a | 35 | :PROPERTIES: |
| 37 | single CSS file. No external javascript and no embedded frames. | 36 | :CUSTOM_ID: stay-static-with-server-side-comments |
| 38 | 37 | :END: | |
| 39 | So, how do we keep a site static and still allow users to interact with blog | 38 | The main issue for my personal use-case is that my blog is completely, |
| 40 | posts? The key actually pretty simple - I'm already using PHP, so why not rely | 39 | 100% static. I use PHP on the back-end but website visitors only see |
| 41 | on the classic HTML `<form>` and a PHP script to save the comments somewhere? As | 40 | HTML and a single CSS file. No external javascript and no embedded |
| 42 | it turns out, this was a perfect solution for me. | 41 | frames. |
| 43 | 42 | ||
| 44 | The second issue for my personal use-case is that I am trying to keep the | 43 | So, how do we keep a site static and still allow users to interact with |
| 45 | contents of my website accessible over time, as described by @brandur, in his | 44 | blog posts? The key actually pretty simple - I'm already using PHP, so |
| 46 | post entitled | 45 | why not rely on the classic HTML =<form>= and a PHP script to save the |
| 47 | [Blog with Markdown + Git, and degrade gracefully through time](https://brandur.org/fragments/graceful-degradation-time) | 46 | comments somewhere? As it turns out, this was a perfect solution for me. |
| 48 | . | 47 | |
| 49 | 48 | The second issue for my personal use-case is that I am trying to keep | |
| 50 | This means I cannot rely on a database for comments, since I do not rely on a | 49 | the contents of my website accessible over time, as described by |
| 51 | database for any other part of my websites. | 50 | [cite/t:@brandur], in his post entitled |
| 52 | 51 | [[https://brandur.org/fragments/graceful-degradation-time][Blog with | |
| 53 | I blog in plain Markdown files, commit all articles to Git, and ensure that | 52 | Markdown + Git, and degrade gracefully through time]] . |
| 54 | future readers will be able to see the source data long after I'm gone, or the | 53 | |
| 55 | website has gone offline. However, I still haven't committed any images served | 54 | This means I cannot rely on a database for comments, since I do not rely |
| 56 | on my blog to Git, as I'm not entirely sold on Git LFS yet - for now, images can | 55 | on a database for any other part of my websites. |
| 57 | be found at [img.cleberg.net](https://img.cleberg.net). | 56 | |
| 58 | 57 | I blog in plain Markdown files, commit all articles to Git, and ensure | |
| 59 | Saving my comments back to the Git repository ensures that another aspect of my | 58 | that future readers will be able to see the source data long after I'm |
| 60 | site will degrade gracefully. | 59 | gone, or the website has gone offline. However, I still haven't |
| 61 | 60 | committed any images served on my blog to Git, as I'm not entirely sold | |
| 62 | ## Create a Comment Form | 61 | on Git LFS yet - for now, images can be found at |
| 63 | 62 | [[https://img.cleberg.net][img.cleberg.net]]. | |
| 64 | Okay, let's get started. The first step is to create an HTML form that users can | 63 | |
| 65 | see and utilize to submit comments. This is fairly easy and can be changed | 64 | Saving my comments back to the Git repository ensures that another |
| 66 | depending on your personal preferences. | 65 | aspect of my site will degrade gracefully. |
| 67 | 66 | ||
| 68 | Take a look at the code block below for the form I currently use. Note that | 67 | ** Create a Comment Form |
| 69 | `<current-url>` is replaced automatically in PHP with the current post's URL, so | 68 | :PROPERTIES: |
| 70 | that my PHP script used later will know which blog post the comment is related | 69 | :CUSTOM_ID: create-a-comment-form |
| 71 | to. | 70 | :END: |
| 71 | Okay, let's get started. The first step is to create an HTML form that | ||
| 72 | users can see and utilize to submit comments. This is fairly easy and | ||
| 73 | can be changed depending on your personal preferences. | ||
| 74 | |||
| 75 | Take a look at the code block below for the form I currently use. Note | ||
| 76 | that =<current-url>= is replaced automatically in PHP with the current | ||
| 77 | post's URL, so that my PHP script used later will know which blog post | ||
| 78 | the comment is related to. | ||
| 72 | 79 | ||
| 73 | The form contains the following structure: | 80 | The form contains the following structure: |
| 74 | 81 | ||
| 75 | 1. `<form>` - This is the form and will determine which PHP script to send the | 82 | 1. =<form>= - This is the form and will determine which PHP script to |
| 76 | comment to. | 83 | send the comment to. |
| 77 | 2. `<section hidden>` - This section is hidden from the user and is used to | 84 | 2. =<section hidden>= - This section is hidden from the user and is used |
| 78 | ensure that we know which blog post sent the comment. | 85 | to ensure that we know which blog post sent the comment. |
| 79 | 3. `<section>` Display Name (Optional) - Used to accept a display name, if | 86 | 3. =<section>= Display Name (Optional) - Used to accept a display name, |
| 80 | entered. | 87 | if entered. |
| 81 | 4. `<section>` Comment (Required) - Used to accept the user's full comment. | 88 | 4. =<section>= Comment (Required) - Used to accept the user's full |
| 82 | Markdown is allowed. | 89 | comment. Markdown is allowed. |
| 83 | 5. `<button>` - A button to submit the form. | 90 | 5. =<button>= - A button to submit the form. |
| 84 | 91 | ||
| 85 | ```html | 92 | #+begin_src html |
| 86 | <form action="/comment.php" method="POST"> | 93 | <form action="/comment.php" method="POST"> |
| 87 | <h3>Leave a Comment</h3> | 94 | <h3>Leave a Comment</h3> |
| 88 | <section hidden> | 95 | <section hidden> |
| 89 | <label class="form-label" for="postURL">Post URL</label> | 96 | <label class="form-label" for="postURL">Post URL</label> |
| 90 | <input | 97 | <input |
| 91 | class="form-control" | 98 | class="form-control" |
| 92 | id="postURL" | 99 | id="postURL" |
| 93 | name="postURL" | 100 | name="postURL" |
| 94 | type="text" | 101 | type="text" |
| 95 | value="<current-url>" | 102 | value="<current-url>" |
| 96 | /> | 103 | /> |
| 97 | </section> | 104 | </section> |
| 98 | <section> | 105 | <section> |
| 99 | <label class="form-label" for="userName">Display Name</label> | 106 | <label class="form-label" for="userName">Display Name</label> |
| 100 | <input | 107 | <input |
| 101 | class="form-control" | 108 | class="form-control" |
| 102 | id="userName" | 109 | id="userName" |
| 103 | name="userName" | 110 | name="userName" |
| 104 | placeholder="John Doe" | 111 | placeholder="John Doe" |
| 105 | type="text" | 112 | type="text" |
| 106 | /> | 113 | /> |
| 107 | </section> | 114 | </section> |
| 108 | <section> | 115 | <section> |
| 109 | <label class="form-label" for="userContent">Your Comment</label> | 116 | <label class="form-label" for="userContent">Your Comment</label> |
| 110 | <textarea | 117 | <textarea |
| 111 | class="form-control" | 118 | class="form-control" |
| 112 | id="userContent" | 119 | id="userContent" |
| 113 | name="userContent" | 120 | name="userContent" |
| 114 | rows="3" | 121 | rows="3" |
| 115 | placeholder="# Feel free to use Markdown" | 122 | placeholder="# Feel free to use Markdown" |
| 116 | aria-describedby="commentHelp" | 123 | aria-describedby="commentHelp" |
| 117 | required | 124 | required |
| 118 | ></textarea> | 125 | ></textarea> |
| 119 | <div id="commentHelp" class="form-text"> | 126 | <div id="commentHelp" class="form-text"> |
| 120 | Comments are saved as Markdown and cannot be edited or deleted. | 127 | Comments are saved as Markdown and cannot be edited or deleted. |
| 121 | </div> | 128 | </div> |
| 122 | </section> | 129 | </section> |
| 123 | <button type="submit">Submit</button> | 130 | <button type="submit">Submit</button> |
| 124 | </form> | 131 | </form> |
| 125 | ``` | 132 | #+end_src |
| 126 | |||
| 127 | ## Handle Comments via POST | ||
| 128 | 133 | ||
| 129 | Now that we have a form and can submit comments, we need to create a PHP script | 134 | ** Handle Comments via POST |
| 130 | so that the server can fetch the comment data and save it. Make sure your PHP | 135 | :PROPERTIES: |
| 131 | script name matches the name you entered in the `action` field in your form. | 136 | :CUSTOM_ID: handle-comments-via-post |
| 137 | :END: | ||
| 138 | Now that we have a form and can submit comments, we need to create a PHP | ||
| 139 | script so that the server can fetch the comment data and save it. Make | ||
| 140 | sure your PHP script name matches the name you entered in the =action= | ||
| 141 | field in your form. | ||
| 132 | 142 | ||
| 133 | See the code block below for the contents of my `comment.php` script. We perform | 143 | See the code block below for the contents of my =comment.php= script. We |
| 134 | the following tasks in this script: | 144 | perform the following tasks in this script: |
| 135 | 145 | ||
| 136 | 1. Grab the POST data from the HTML form. | 146 | 1. Grab the POST data from the HTML form. |
| 137 | 2. Sanitize the comment data with `htmlentities`. | 147 | 2. Sanitize the comment data with =htmlentities=. |
| 138 | 3. Set the display name to `Anonymous` if it was left blank. | 148 | 3. Set the display name to =Anonymous= if it was left blank. |
| 139 | 4. Create a PHP object that combines all of this data. | 149 | 4. Create a PHP object that combines all of this data. |
| 140 | 5. Check if our destination file `comments.json` exists. | 150 | 5. Check if our destination file =comments.json= exists. |
| 141 | 6. If so, convert the PHP object to JSON and save it to the file. | 151 | 6. If so, convert the PHP object to JSON and save it to the file. |
| 142 | 7. If the `comments.json` file does not exist, the script will exit with an | 152 | 7. If the =comments.json= file does not exist, the script will exit with |
| 143 | error. You can alter this to ensure it creates the script, but my source code | 153 | an error. You can alter this to ensure it creates the script, but my |
| 144 | includes the file by default, so it will always exist. | 154 | source code includes the file by default, so it will always exist. |
| 145 | 8. Finally, send the user back to the `#comments` section of the blog post they | 155 | 8. Finally, send the user back to the =#comments= section of the blog |
| 146 | just read. | 156 | post they just read. |
| 147 | 157 | ||
| 148 | ```php | 158 | #+begin_src php |
| 149 | // Get the content sent from the comment form | 159 | // Get the content sent from the comment form |
| 150 | $comment = htmlentities($_POST['userContent']); | 160 | $comment = htmlentities($_POST['userContent']); |
| 151 | $post_url = $_POST['postURL']; | 161 | $post_url = $_POST['postURL']; |
| @@ -180,30 +190,35 @@ if (file_exists($file_name)) { | |||
| 180 | 190 | ||
| 181 | // Send the user back | 191 | // Send the user back |
| 182 | header('Location: ' . $post_url . '#comments'); | 192 | header('Location: ' . $post_url . '#comments'); |
| 183 | ``` | 193 | #+end_src |
| 184 | 194 | ||
| 185 | If you're using Apache, make sure the `www-data` user on your server has the | 195 | If you're using Apache, make sure the =www-data= user on your server has |
| 186 | correct permissions to your website directory or else it will not be able to | 196 | the correct permissions to your website directory or else it will not be |
| 187 | write to `comments.json`. | 197 | able to write to =comments.json=. |
| 188 | 198 | ||
| 189 | ```sh | 199 | #+begin_src sh |
| 190 | chgrp -R www-data /path/to/website/ | 200 | chgrp -R www-data /path/to/website/ |
| 191 | chmod -R g+w comments.json | 201 | chmod -R g+w comments.json |
| 192 | ``` | 202 | #+end_src |
| 193 | 203 | ||
| 194 | ## Display User Comments | 204 | ** Display User Comments |
| 195 | 205 | :PROPERTIES: | |
| 196 | Now that we can submit comments, and they are saved to the `comments.json` file, | 206 | :CUSTOM_ID: display-user-comments |
| 197 | let's make sure we can show those comments in each blog post. | 207 | :END: |
| 198 | 208 | Now that we can submit comments, and they are saved to the | |
| 199 | The code block below shows the function I use to decode my `comments.json` file, | 209 | =comments.json= file, let's make sure we can show those comments in each |
| 200 | check if the comments apply to the current post, and then display them. | 210 | blog post. |
| 201 | 211 | ||
| 202 | This piece of code should **really** be inside a function (or at least in an | 212 | The code block below shows the function I use to decode my |
| 203 | organized PHP workflow). Don't just copy-and-paste and expect it to work. You | 213 | =comments.json= file, check if the comments apply to the current post, |
| 204 | need to at least supply a `$query` variable depending on the page visited. | 214 | and then display them. |
| 205 | 215 | ||
| 206 | ```php | 216 | This piece of code should *really* be inside a function (or at least in |
| 217 | an organized PHP workflow). Don't just copy-and-paste and expect it to | ||
| 218 | work. You need to at least supply a =$query= variable depending on the | ||
| 219 | page visited. | ||
| 220 | |||
| 221 | #+begin_src php | ||
| 207 | $query = 'your-blog-post.html'; | 222 | $query = 'your-blog-post.html'; |
| 208 | 223 | ||
| 209 | // Load saved comments | 224 | // Load saved comments |
| @@ -235,28 +250,35 @@ foreach ($comments as $comment) { | |||
| 235 | } | 250 | } |
| 236 | 251 | ||
| 237 | echo $comment_section; | 252 | echo $comment_section; |
| 238 | ``` | 253 | #+end_src |
| 239 | 254 | ||
| 240 | ## Bonus: Create a 'Recent Comments' Page | 255 | ** Bonus: Create a 'Recent Comments' Page |
| 241 | 256 | :PROPERTIES: | |
| 242 | Finally, the last part of my current system is to create a Recent Comments page | 257 | :CUSTOM_ID: bonus-create-a-recent-comments-page |
| 243 | so that I can easily check-in on my blog and moderate any spam. As an | 258 | :END: |
| 244 | alternative, you could use PHP's `mail()` function to email you for each blog | 259 | Finally, the last part of my current system is to create a Recent |
| 245 | comment. | 260 | Comments page so that I can easily check-in on my blog and moderate any |
| 246 | 261 | spam. As an alternative, you could use PHP's =mail()= function to email | |
| 247 | The code to do this is literally the same code as the previous section, I just | 262 | you for each blog comment. |
| 248 | make sure it is printed when someone visits `https://example.com/comments/`. | 263 | |
| 249 | 264 | The code to do this is literally the same code as the previous section, | |
| 250 | ## Possible Enhancements | 265 | I just make sure it is printed when someone visits |
| 251 | 266 | =https://example.com/comments/=. | |
| 252 | This comment system is by no means a fully-developed system. I have noted a few | 267 | |
| 253 | possible enhancements here that I may implement in the future: | 268 | ** Possible Enhancements |
| 254 | 269 | :PROPERTIES: | |
| 255 | - Create a secure moderator page with user authentication at | 270 | :CUSTOM_ID: possible-enhancements |
| 256 | `https://blog.example.com/mod/`. This page could have the option to edit or | 271 | :END: |
| 257 | delete any comment found in `comments.json`. | 272 | This comment system is by no means a fully-developed system. I have |
| 258 | - Create a temporary file, such as `pending_comments.json`, that will store | 273 | noted a few possible enhancements here that I may implement in the |
| 259 | newly-submitted comments and won't display on blog posts until approved by a | 274 | future: |
| 260 | moderator. | 275 | |
| 261 | - Create a `/modlog/` page with a chronological log, showing which moderator | 276 | - Create a secure moderator page with user authentication at |
| 262 | approved which comments and why certain comments were rejected. | 277 | =https://blog.example.com/mod/=. This page could have the option to |
| 278 | edit or delete any comment found in =comments.json=. | ||
| 279 | - Create a temporary file, such as =pending_comments.json=, that will | ||
| 280 | store newly-submitted comments and won't display on blog posts until | ||
| 281 | approved by a moderator. | ||
| 282 | - Create a =/modlog/= page with a chronological log, showing which | ||
| 283 | moderator approved which comments and why certain comments were | ||
| 284 | rejected. | ||
blog/2021-04-28-photography.org +60 −52
| @@ -1,67 +1,75 @@ | |||
| 1 | +++ | 1 | #+title: Jumping Back Into Photography |
| 2 | date = 2021-04-28 | 2 | #+date: 2021-04-28 |
| 3 | title = "Jumping Back Into Photography" | ||
| 4 | description = "After a long respite, I have returned to my hobby of photography with a brand-new camera." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | |||
| 8 | ## Why Photography? | ||
| 9 | 3 | ||
| 4 | ** Why Photography? | ||
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: why-photography | ||
| 7 | :END: | ||
| 10 | I've often wondered why photography is as enticing as it is. You can see | 8 | I've often wondered why photography is as enticing as it is. You can see |
| 11 | billions of people around the world taking photographs every single moment of | 9 | billions of people around the world taking photographs every single |
| 12 | the day. New technology often boasts about their photographic capabilities, | 10 | moment of the day. New technology often boasts about their photographic |
| 13 | especially smartphones. I would even assume that we live in a world where there | 11 | capabilities, especially smartphones. I would even assume that we live |
| 14 | is never a moment in which a photograph is not being taken somewhere on Earth. | 12 | in a world where there is never a moment in which a photograph is not |
| 15 | 13 | being taken somewhere on Earth. | |
| 16 | As for myself, I would simply say that I enjoy preserving a memory in physical | ||
| 17 | (or digital) form. I've never had the best memory when it comes to recalling | ||
| 18 | details of places and people gone by, so it helps to have a frame of reference | ||
| 19 | lying around. | ||
| 20 | |||
| 21 | Regardless of the reason, I think most people would agree that you simply cannot | ||
| 22 | have too many hobbies. | ||
| 23 | 14 | ||
| 24 | ## Older Cameras | 15 | As for myself, I would simply say that I enjoy preserving a memory in |
| 16 | physical (or digital) form. I've never had the best memory when it comes | ||
| 17 | to recalling details of places and people gone by, so it helps to have a | ||
| 18 | frame of reference lying around. | ||
| 25 | 19 | ||
| 26 | I started playing around with the idea of photography when my family purchased a | 20 | Regardless of the reason, I think most people would agree that you |
| 27 | Fujifilm camera for family-specific events. I don't recall the specific model, | 21 | simply cannot have too many hobbies. |
| 28 | but I do recall it was a point-and-shoot camera without an interchangeable lens. | ||
| 29 | However, it was of great value to someone, like myself, who couldn't afford any | ||
| 30 | other camera. I took about 10,000 shots with that camera over a 3-5 year span. | ||
| 31 | Most notably, all of my trips to California were documented through this camera. | ||
| 32 | 22 | ||
| 33 | When possible, I would borrow my sister's camera, which is a Sony SLT-A58. This | 23 | ** Older Cameras |
| 34 | camera was great and allowed for some of my best early shots, especially those | 24 | :PROPERTIES: |
| 35 | taken in Utah's and Nevada's parks. | 25 | :CUSTOM_ID: older-cameras |
| 26 | :END: | ||
| 27 | I started playing around with the idea of photography when my family | ||
| 28 | purchased a Fujifilm camera for family-specific events. I don't recall | ||
| 29 | the specific model, but I do recall it was a point-and-shoot camera | ||
| 30 | without an interchangeable lens. However, it was of great value to | ||
| 31 | someone, like myself, who couldn't afford any other camera. I took about | ||
| 32 | 10,000 shots with that camera over a 3-5 year span. Most notably, all of | ||
| 33 | my trips to California were documented through this camera. | ||
| 36 | 34 | ||
| 37 | ## My Current Kit | 35 | When possible, I would borrow my sister's camera, which is a Sony |
| 36 | SLT-A58. This camera was great and allowed for some of my best early | ||
| 37 | shots, especially those taken in Utah's and Nevada's parks. | ||
| 38 | 38 | ||
| 39 | I've finally come to a point in my life where I have the disposable income to | 39 | ** My Current Kit |
| 40 | invest in a solid photography kit. I played around with the idea of a lot of | 40 | :PROPERTIES: |
| 41 | different cameras, different types, new vs used, etc. Finally, I settled on the | 41 | :CUSTOM_ID: my-current-kit |
| 42 | [Sony α7 III](https://en.wikipedia.org/wiki/Sony_%CE%B17_III). This camera is | 42 | :END: |
| 43 | mirror-less and uses a full-frame image sensor at 24 megapixels. I don't create | 43 | I've finally come to a point in my life where I have the disposable |
| 44 | large prints, and I am mostly focused on preserving memories in high quality for | 44 | income to invest in a solid photography kit. I played around with the |
| 45 | the next 5-10 years with this camera, so the specifications here are just | 45 | idea of a lot of different cameras, different types, new vs used, etc. |
| 46 | perfect for me. | 46 | Finally, I settled on the |
| 47 | [[https://en.wikipedia.org/wiki/Sony_%CE%B17_III][Sony α7 III]]. This | ||
| 48 | camera is mirror-less and uses a full-frame image sensor at 24 | ||
| 49 | megapixels. I don't create large prints, and I am mostly focused on | ||
| 50 | preserving memories in high quality for the next 5-10 years with this | ||
| 51 | camera, so the specifications here are just perfect for me. | ||
| 47 | 52 | ||
| 48 | For lenses, I decided to buy two lenses that could carry me through most | 53 | For lenses, I decided to buy two lenses that could carry me through most |
| 49 | situations: | 54 | situations: |
| 50 | 55 | ||
| 51 | - [Vario-Tessar T\* FE 24-70 mm F4 ZA OSS](https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z) | 56 | - [[https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z][Vario-Tessar |
| 52 | - [Tamron 70-300mm f4.5-6.3 Di III RXD](https://www.tamron-usa.com/product/lenses/a047.html) | 57 | T* FE 24-70 mm F4 ZA OSS]] |
| 58 | - [[https://www.tamron-usa.com/product/lenses/a047.html][Tamron 70-300mm | ||
| 59 | f4.5-6.3 Di III RXD]] | ||
| 53 | 60 | ||
| 54 | In addition, I grabbed a couple | 61 | In addition, I grabbed a couple |
| 55 | [HGX Prime 67mm](https://www.promaster.com/Product/6725) protection filters for | 62 | [[https://www.promaster.com/Product/6725][HGX Prime 67mm]] protection |
| 56 | the lenses. | 63 | filters for the lenses. |
| 57 | 64 | ||
| 58 | As I delve further into photography and pick up more skills, I will most likely | 65 | As I delve further into photography and pick up more skills, I will most |
| 59 | go back and grab a lens with a higher f-stop value, such as f/1.8. I toyed with | 66 | likely go back and grab a lens with a higher f-stop value, such as |
| 60 | the idea of grabbing a 50 mm at `f/1.8`, but decided to keep things in a | 67 | f/1.8. I toyed with the idea of grabbing a 50 mm at =f/1.8=, but decided |
| 61 | reasonable price range instead. | 68 | to keep things in a reasonable price range instead. |
| 62 | 69 | ||
| 63 | Finally, I made sure to buy a photography-specific backpack with a rain guard, | 70 | Finally, I made sure to buy a photography-specific backpack with a rain |
| 64 | and the zipper on the back panel, to protect the equipment while wearing the | 71 | guard, and the zipper on the back panel, to protect the equipment while |
| 65 | bag. If you've ever had to haul around a DSLR (or camera of similar heft) in a | 72 | wearing the bag. If you've ever had to haul around a DSLR (or camera of |
| 66 | bag that only has a shoulder strap, you'll know the pain it can cause. Putting | 73 | similar heft) in a bag that only has a shoulder strap, you'll know the |
| 67 | all my equipment in a backpack was an easy decision. | 74 | pain it can cause. Putting all my equipment in a backpack was an easy |
| 75 | decision. | ||
blog/2021-05-30-changing-git-authors.org +26 −28
| @@ -1,33 +1,31 @@ | |||
| 1 | +++ | 1 | #+title: Changing Git Authors |
| 2 | date = 2021-05-30 | 2 | #+date: 2021-05-30 |
| 3 | title = "Changing Git Authors" | 3 | |
| 4 | description = "Learn how to haphazardly replace Git authors and/or emails within the Git history." | 4 | ** Changing Git Author/Email Based on Previously Committed Email |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: changing-git-authoremail-based-on-previously-committed-email | |
| 7 | ## Changing Git Author/Email Based on Previously Committed Email | 7 | :END: |
| 8 | 8 | Here's the dilemma: You've been committing changes to your git | |
| 9 | Here's the dilemma: You've been committing changes to your git repository with | 9 | repository with an incorrect name or email (or multiple repositories), |
| 10 | an incorrect name or email (or multiple repositories), and now you want to fix | 10 | and now you want to fix it. Luckily, there's a semi-reliable way to fix |
| 11 | it. | 11 | that. While I have never experienced issues with this method, some |
| 12 | Luckily, there's a semi-reliable way to fix that. | 12 | people have warned that it can mess with historical hashes and integrity |
| 13 | While I have never experienced issues with this method, some people have | 13 | of commits, so use this method only if you're okay accepting that risk. |
| 14 | warned that it can mess with historical hashes and integrity of commits, so | ||
| 15 | use this method only if you're okay accepting that risk. | ||
| 16 | 14 | ||
| 17 | Okay, let's create the bash script: | 15 | Okay, let's create the bash script: |
| 18 | 16 | ||
| 19 | ```sh | 17 | #+begin_src sh |
| 20 | nano change_git_authors.sh | 18 | nano change_git_authors.sh |
| 21 | ``` | 19 | #+end_src |
| 22 | 20 | ||
| 23 | The following information can be pasted directly into your bash script. The only | 21 | The following information can be pasted directly into your bash script. |
| 24 | changes you need to make are to the following variables: | 22 | The only changes you need to make are to the following variables: |
| 25 | 23 | ||
| 26 | - `OLD_EMAIL` | 24 | - =OLD_EMAIL= |
| 27 | - `CORRECT_NAME` | 25 | - =CORRECT_NAME= |
| 28 | - `CORRECT_EMAIL` | 26 | - =CORRECT_EMAIL= |
| 29 | 27 | ||
| 30 | ```sh | 28 | #+begin_src sh |
| 31 | #!/bin/sh | 29 | #!/bin/sh |
| 32 | 30 | ||
| 33 | # List all sub-directories in the current directory | 31 | # List all sub-directories in the current directory |
| @@ -60,16 +58,16 @@ do | |||
| 60 | 58 | ||
| 61 | cd .. | 59 | cd .. |
| 62 | done | 60 | done |
| 63 | ``` | 61 | #+end_src |
| 64 | 62 | ||
| 65 | Finally, save the bash script and make it executable. | 63 | Finally, save the bash script and make it executable. |
| 66 | 64 | ||
| 67 | ```sh | 65 | #+begin_src sh |
| 68 | chmod a+x change_git_authors.sh | 66 | chmod a+x change_git_authors.sh |
| 69 | ``` | 67 | #+end_src |
| 70 | 68 | ||
| 71 | Now you can run the script and should see the process begin. | 69 | Now you can run the script and should see the process begin. |
| 72 | 70 | ||
| 73 | ```sh | 71 | #+begin_src sh |
| 74 | ./change_git_authors.sh | 72 | ./change_git_authors.sh |
| 75 | ``` | 73 | #+end_src |
blog/2021-07-15-delete-gitlab-repos.org +54 −49
| @@ -1,53 +1,57 @@ | |||
| 1 | +++ | 1 | #+title: How to Delete All GitLab Repositories |
| 2 | date = 2021-07-15 | 2 | #+date: 2021-07-15 |
| 3 | title = "How to Delete All GitLab Repositories" | 3 | |
| 4 | description = "Learn how to delete all GitLab repositories in your user account." | 4 | ** Background |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: background |
| 7 | 7 | :END: | |
| 8 | ## Background | 8 | Have you ever used GitLab to host your source code, moved to a different |
| 9 | 9 | host, and wanted to delete everything from your GitLab account? Well, | |
| 10 | Have you ever used GitLab to host your source code, moved to a different host, | 10 | this post covers any scenario where you would want to delete all |
| 11 | and wanted to delete everything from your GitLab account? | ||
| 12 | Well, this post covers any scenario where you would want to delete all | ||
| 13 | repositories from your GitLab account. | 11 | repositories from your GitLab account. |
| 14 | 12 | ||
| 15 | For me, I currently maintain around 30 repositories and don't like to manually | 13 | For me, I currently maintain around 30 repositories and don't like to |
| 16 | delete them whenever I switch host. | 14 | manually delete them whenever I switch host. GitHub has a few different |
| 17 | GitHub has a few different tools online to delete all repositories for you, | 15 | tools online to delete all repositories for you, but I have not found |
| 18 | but I have not found anything similar for GitLab, so I needed an alternative | 16 | anything similar for GitLab, so I needed an alternative solution. |
| 19 | solution. | 17 | |
| 20 | 18 | ** Use a Python Script | |
| 21 | ## Use a Python Script | 19 | :PROPERTIES: |
| 22 | 20 | :CUSTOM_ID: use-a-python-script | |
| 23 | ### Requirements | 21 | :END: |
| 24 | 22 | *** Requirements | |
| 25 | Before we look at the script, make sure you know your GitLab username. Next, | 23 | :PROPERTIES: |
| 26 | [create an authorization token](https://gitlab.com/-/profile/personal_access_tokens) | 24 | :CUSTOM_ID: requirements |
| 27 | so that the Python script can delete your repositories. Don't lose this token or | 25 | :END: |
| 28 | else you'll need to create a new one. | 26 | Before we look at the script, make sure you know your GitLab username. |
| 29 | 27 | Next, [[https://gitlab.com/-/profile/personal_access_tokens][create an | |
| 30 | ### Create the Script | 28 | authorization token]] so that the Python script can delete your |
| 31 | 29 | repositories. Don't lose this token or else you'll need to create a new | |
| 32 | To run a Python script, you must first create it. Open a terminal and enter the | 30 | one. |
| 33 | following commands in whichever directory you prefer to store the script. You | 31 | |
| 34 | can do the same things in a file manager if you prefer. | 32 | *** Create the Script |
| 35 | 33 | :PROPERTIES: | |
| 36 | ```sh | 34 | :CUSTOM_ID: create-the-script |
| 35 | :END: | ||
| 36 | To run a Python script, you must first create it. Open a terminal and | ||
| 37 | enter the following commands in whichever directory you prefer to store | ||
| 38 | the script. You can do the same things in a file manager if you prefer. | ||
| 39 | |||
| 40 | #+begin_src sh | ||
| 37 | mkdir delete-gitlab | 41 | mkdir delete-gitlab |
| 38 | ``` | 42 | #+end_src |
| 39 | 43 | ||
| 40 | ```sh | 44 | #+begin_src sh |
| 41 | cd delete-gitlab | 45 | cd delete-gitlab |
| 42 | ``` | 46 | #+end_src |
| 43 | 47 | ||
| 44 | ```sh | 48 | #+begin_src sh |
| 45 | nano main.py | 49 | nano main.py |
| 46 | ``` | 50 | #+end_src |
| 47 | 51 | ||
| 48 | Enter the following code into your `main.py` script. | 52 | Enter the following code into your =main.py= script. |
| 49 | 53 | ||
| 50 | ```python | 54 | #+begin_src python |
| 51 | import request | 55 | import request |
| 52 | import json | 56 | import json |
| 53 | 57 | ||
| @@ -101,15 +105,16 @@ def main(): | |||
| 101 | 105 | ||
| 102 | if __name__ == "__main__": | 106 | if __name__ == "__main__": |
| 103 | main() | 107 | main() |
| 104 | ``` | 108 | #+end_src |
| 105 | 109 | ||
| 106 | Now that you have the proper information, replace `{user-id}` with your GitLab | 110 | Now that you have the proper information, replace ={user-id}= with your |
| 107 | username and `{auth-token}` with the authorization token you created earlier. | 111 | GitLab username and ={auth-token}= with the authorization token you |
| 112 | created earlier. | ||
| 108 | 113 | ||
| 109 | Finally, simply run the script and watch the output. You can also use PyCharm | 114 | Finally, simply run the script and watch the output. You can also use |
| 110 | Community Edition to edit and run the Python script if you don't want to work | 115 | PyCharm Community Edition to edit and run the Python script if you don't |
| 111 | in a terminal. | 116 | want to work in a terminal. |
| 112 | 117 | ||
| 113 | ```sh | 118 | #+begin_src sh |
| 114 | python3 main.py | 119 | python3 main.py |
| 115 | ``` | 120 | #+end_src |
blog/2021-08-25-audit-sampling.org +154 −139
| @@ -1,52 +1,55 @@ | |||
| 1 | +++ | 1 | #+title: Audit Sampling with Python |
| 2 | date = 2021-08-25 | 2 | #+date: 2021-08-25 |
| 3 | title = "Audit Sampling with Python" | ||
| 4 | description = "Learn how to use Python to automate the boring parts of audit sampling." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | |||
| 8 | ## Introduction | ||
| 9 | 3 | ||
| 4 | ** Introduction | ||
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: introduction | ||
| 7 | :END: | ||
| 10 | For anyone who is familiar with internal auditing, external auditing, or | 8 | For anyone who is familiar with internal auditing, external auditing, or |
| 11 | consulting, you will understand how tedious audit testing can become when you | 9 | consulting, you will understand how tedious audit testing can become |
| 12 | are required to test large swaths of data. When we cannot establish an automated | 10 | when you are required to test large swaths of data. When we cannot |
| 13 | means of testing an entire population, we generate samples to represent the | 11 | establish an automated means of testing an entire population, we |
| 14 | population of data. This helps ensure we can have a small enough data pool to | 12 | generate samples to represent the population of data. This helps ensure |
| 15 | test and that our results still represent the population. | 13 | we can have a small enough data pool to test and that our results still |
| 16 | 14 | represent the population. | |
| 17 | However, sampling data within the world of audit still seems to confuse quite a | 15 | |
| 18 | lot of people. While some audit-focused tools have introduced sampling | 16 | However, sampling data within the world of audit still seems to confuse |
| 19 | functionality (e.g. Wdesk), many audit departments and firms cannot use software | 17 | quite a lot of people. While some audit-focused tools have introduced |
| 20 | like this due to certain constraints, such as the team's budget or knowledge. | 18 | sampling functionality (e.g. Wdesk), many audit departments and firms |
| 21 | Here is where this article comes in: we're going to use | 19 | cannot use software like this due to certain constraints, such as the |
| 22 | [Python](https://www.python.org), a free and open-source programming language, | 20 | team's budget or knowledge. Here is where this article comes in: we're |
| 23 | to generate random samples from a dataset in order to suffice numerous audit | 21 | going to use [[https://www.python.org][Python]], a free and open-source |
| 24 | situations. | 22 | programming language, to generate random samples from a dataset in order |
| 25 | 23 | to suffice numerous audit situations. | |
| 26 | ## Audit Requirements for Sampling | 24 | |
| 27 | 25 | ** Audit Requirements for Sampling | |
| 28 | Before we get into the details of how to sample with Python, I want to make sure | 26 | :PROPERTIES: |
| 29 | I discuss the different requirements that auditors may have of samples used | 27 | :CUSTOM_ID: audit-requirements-for-sampling |
| 30 | within their projects. | 28 | :END: |
| 31 | 29 | Before we get into the details of how to sample with Python, I want to | |
| 32 | ### Randomness | 30 | make sure I discuss the different requirements that auditors may have of |
| 33 | 31 | samples used within their projects. | |
| 34 | First, let's discuss randomness. When testing out new technology to help assist | 32 | |
| 35 | with audit sampling, you need to understand exactly how your samples are being | 33 | *** Randomness |
| 36 | generated. For example, if the underlying function is just picking every 57th | 34 | :PROPERTIES: |
| 37 | element from a list, that's not truly random; it's a systematic form of | 35 | :CUSTOM_ID: randomness |
| 38 | sampling. Luckily, since Python is open-source, we have access to its codebase. | 36 | :END: |
| 39 | Through this blog post, I will be using the [pandas](https://pandas.pydata.org) | 37 | First, let's discuss randomness. When testing out new technology to help |
| 40 | module in order to generate the random samples. More specifically, I will be | 38 | assist with audit sampling, you need to understand exactly how your |
| 41 | using the | 39 | samples are being generated. For example, if the underlying function is |
| 42 | [pandas.DataFrame.sample](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html) | 40 | just picking every 57th element from a list, that's not truly random; |
| 41 | it's a systematic form of sampling. Luckily, since Python is | ||
| 42 | open-source, we have access to its codebase. Through this blog post, I | ||
| 43 | will be using the [[https://pandas.pydata.org][pandas]] module in order | ||
| 44 | to generate the random samples. More specifically, I will be using the | ||
| 45 | [[https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html][pandas.DataFrame.sample]] | ||
| 43 | function provided by Pandas. | 46 | function provided by Pandas. |
| 44 | 47 | ||
| 45 | Now that you know what you're using, you can always check out the code behind | 48 | Now that you know what you're using, you can always check out the code |
| 46 | `pandas.DataFrame.sample`. This function does a lot of work, but we really only | 49 | behind =pandas.DataFrame.sample=. This function does a lot of work, but |
| 47 | care about the following snippets of code: | 50 | we really only care about the following snippets of code: |
| 48 | 51 | ||
| 49 | ```python | 52 | #+begin_src python |
| 50 | # Process random_state argument | 53 | # Process random_state argument |
| 51 | rs = com.random_state(random_state) | 54 | rs = com.random_state(random_state) |
| 52 | 55 | ||
| @@ -58,45 +61,45 @@ if ignore_index: | |||
| 58 | result.index = ibase.default_index(len(result)) | 61 | result.index = ibase.default_index(len(result)) |
| 59 | 62 | ||
| 60 | return result | 63 | return result |
| 61 | ``` | 64 | #+end_src |
| 62 | 65 | ||
| 63 | The block of code above shows you that if you assign a `random_state` | 66 | The block of code above shows you that if you assign a =random_state= |
| 64 | argument when you run the function, that will be used as a seed number in | 67 | argument when you run the function, that will be used as a seed number |
| 65 | the random generation and will allow you to reproduce a sample, given that | 68 | in the random generation and will allow you to reproduce a sample, given |
| 66 | nothing else changes. | 69 | that nothing else changes. This is critical to the posterity of audit |
| 67 | This is critical to the posterity of audit work. | 70 | work. After all, how can you say your audit process is adequately |
| 68 | After all, how can you say your audit process is adequately documented if | 71 | documented if the next person can't run the code and get the same |
| 69 | the next person can't run the code and get the same sample? | 72 | sample? The final piece here on randomness is to look at the |
| 70 | The final piece here on randomness is to look at the [choice](https://docs. | 73 | [[https://docs.%20python.org/3/library/random.html#random.choice][choice]] |
| 71 | python.org/3/library/random.html#random.choice) function used above. | 74 | function used above. This is the crux of the generation and can also be |
| 72 | This is the crux of the generation and can also be examined for more | 75 | examined for more detailed analysis on its reliability. As far as |
| 73 | detailed analysis on its reliability. | 76 | auditing goes, we will trust that these functions are mathematically |
| 74 | As far as auditing goes, we will trust that these functions are | 77 | random. |
| 75 | mathematically random. | 78 | |
| 76 | 79 | *** Sample Sizes | |
| 77 | ### Sample Sizes | 80 | :PROPERTIES: |
| 78 | 81 | :CUSTOM_ID: sample-sizes | |
| 79 | As mentioned in the intro, sampling is only an effective method of auditing | 82 | :END: |
| 80 | when it truly represents the entire population. | 83 | As mentioned in the intro, sampling is only an effective method of |
| 81 | While some audit departments or firms may consider certain judgmental sample | 84 | auditing when it truly represents the entire population. While some |
| 82 | sizes to be adequate, you may need to rely on statistically-significant | 85 | audit departments or firms may consider certain judgmental sample sizes |
| 83 | confidence levels of sample testing at certain points. | 86 | to be adequate, you may need to rely on statistically-significant |
| 84 | I will demonstrate both here. | 87 | confidence levels of sample testing at certain points. I will |
| 85 | For statistically-significant confidence levels, most people will assume a | 88 | demonstrate both here. For statistically-significant confidence levels, |
| 86 | 90% - 99% confidence level. | 89 | most people will assume a 90% - 99% confidence level. In order to |
| 87 | In order to actually calculate the correct sample size, it is best to use | 90 | actually calculate the correct sample size, it is best to use |
| 88 | statistical tools due to the tedious math work required. | 91 | statistical tools due to the tedious math work required. For example, |
| 89 | For example, for a population of 1000, and a 90% confidence level that no | 92 | for a population of 1000, and a 90% confidence level that no more than |
| 90 | more than 5% of the items are nonconforming, you would sample 45 items. | 93 | 5% of the items are nonconforming, you would sample 45 items. |
| 91 | 94 | ||
| 92 | However, in my personal experience, many audit departments and firms do not use | 95 | However, in my personal experience, many audit departments and firms do |
| 93 | statistical sampling. Most people use a predetermined, often proprietary, table | 96 | not use statistical sampling. Most people use a predetermined, often |
| 94 | that will instruct auditors which sample sizes to choose. This allows for | 97 | proprietary, table that will instruct auditors which sample sizes to |
| 95 | uniform testing and reduces overall workload. See the table below for a common | 98 | choose. This allows for uniform testing and reduces overall workload. |
| 96 | implementation of sample sizes: | 99 | See the table below for a common implementation of sample sizes: |
| 97 | 100 | ||
| 98 | | Control Frequency | Sample Size - High Risk | Sample Size - Low Risk | | 101 | | Control Frequency | Sample Size - High Risk | Sample Size - Low Risk | |
| 99 | |-------------------|-------------------------|------------------------| | 102 | |-------------------+-------------------------+------------------------| |
| 100 | | More Than Daily | 40 | 25 | | 103 | | More Than Daily | 40 | 25 | |
| 101 | | Daily | 40 | 25 | | 104 | | Daily | 40 | 25 | |
| 102 | | Weekly | 12 | 5 | | 105 | | Weekly | 12 | 5 | |
| @@ -106,23 +109,27 @@ implementation of sample sizes: | |||
| 106 | | Annually | 1 | 1 | | 109 | | Annually | 1 | 1 | |
| 107 | | Ad-hoc | 1 | 1 | | 110 | | Ad-hoc | 1 | 1 | |
| 108 | 111 | ||
| 109 | ## Sampling with Python & Pandas | 112 | ** Sampling with Python & Pandas |
| 110 | 113 | :PROPERTIES: | |
| 111 | In this section, I am going to cover a few basic audit situations that require | 114 | :CUSTOM_ID: sampling-with-python-pandas |
| 112 | sampling. While some situations may require more effort, the syntax, | 115 | :END: |
| 113 | organization, and intellect used remain largely the same. If you've never used | 116 | In this section, I am going to cover a few basic audit situations that |
| 114 | Python before, note that lines starting with a '`#`' symbol are called comments, | 117 | require sampling. While some situations may require more effort, the |
| 115 | and they will be skipped by Python. I highly recommend taking a quick tutorial | 118 | syntax, organization, and intellect used remain largely the same. If |
| 116 | online to understand the basics of Python if any of the code below is confusing | 119 | you've never used Python before, note that lines starting with a '=#=' |
| 117 | to you. | 120 | symbol are called comments, and they will be skipped by Python. I highly |
| 118 | 121 | recommend taking a quick tutorial online to understand the basics of | |
| 119 | ### Simple Random Sample | 122 | Python if any of the code below is confusing to you. |
| 120 | 123 | ||
| 121 | First, let's look at a simple, random sample. The code block below will import | 124 | *** Simple Random Sample |
| 122 | the `pandas` module, load a data file, sample the data, and export the sample to | 125 | :PROPERTIES: |
| 123 | a file. | 126 | :CUSTOM_ID: simple-random-sample |
| 124 | 127 | :END: | |
| 125 | ```python | 128 | First, let's look at a simple, random sample. The code block below will |
| 129 | import the =pandas= module, load a data file, sample the data, and | ||
| 130 | export the sample to a file. | ||
| 131 | |||
| 132 | #+begin_src python | ||
| 126 | # Import the Pandas module | 133 | # Import the Pandas module |
| 127 | import pandas | 134 | import pandas |
| 128 | 135 | ||
| @@ -140,14 +147,16 @@ sample = df.sample(n=25, random_state=0) | |||
| 140 | 147 | ||
| 141 | # Save the sample to Excel | 148 | # Save the sample to Excel |
| 142 | sample.to_excel(file_output) | 149 | sample.to_excel(file_output) |
| 143 | ``` | 150 | #+end_src |
| 144 | 151 | ||
| 145 | ### Simple Random Sample: Using Multiple Input Files | 152 | *** Simple Random Sample: Using Multiple Input Files |
| 153 | :PROPERTIES: | ||
| 154 | :CUSTOM_ID: simple-random-sample-using-multiple-input-files | ||
| 155 | :END: | ||
| 156 | Now that we've created a simple sample, let's create a sample from | ||
| 157 | multiple files. | ||
| 146 | 158 | ||
| 147 | Now that we've created a simple sample, let's create a sample from multiple | 159 | #+begin_src python |
| 148 | files. | ||
| 149 | |||
| 150 | ```python | ||
| 151 | # Import the Pandas module | 160 | # Import the Pandas module |
| 152 | import pandas | 161 | import pandas |
| 153 | 162 | ||
| @@ -174,15 +183,17 @@ sample = pandas.concat([sample_01, sample_02, sample_03], ignore_index=True) | |||
| 174 | 183 | ||
| 175 | # Save the sample to Excel | 184 | # Save the sample to Excel |
| 176 | sample.to_excel(file_output) | 185 | sample.to_excel(file_output) |
| 177 | ``` | 186 | #+end_src |
| 178 | |||
| 179 | ### Stratified Random Sample | ||
| 180 | 187 | ||
| 181 | Well, what if you need to sample distinct parts of a single file? For example, | 188 | *** Stratified Random Sample |
| 182 | let's write some code to separate our data by "Region" and sample those regions | 189 | :PROPERTIES: |
| 183 | independently. | 190 | :CUSTOM_ID: stratified-random-sample |
| 191 | :END: | ||
| 192 | Well, what if you need to sample distinct parts of a single file? For | ||
| 193 | example, let's write some code to separate our data by "Region" and | ||
| 194 | sample those regions independently. | ||
| 184 | 195 | ||
| 185 | ```python | 196 | #+begin_src python |
| 186 | # Import the Pandas module | 197 | # Import the Pandas module |
| 187 | import pandas | 198 | import pandas |
| 188 | 199 | ||
| @@ -208,16 +219,18 @@ sample = pandas.concat([sample_east, sample_west], ignore_index=True) | |||
| 208 | 219 | ||
| 209 | # Save the sample to Excel | 220 | # Save the sample to Excel |
| 210 | sample.to_excel(file_output) | 221 | sample.to_excel(file_output) |
| 211 | ``` | 222 | #+end_src |
| 212 | 223 | ||
| 213 | ### Stratified Systematic Sample | 224 | *** Stratified Systematic Sample |
| 214 | 225 | :PROPERTIES: | |
| 215 | This next example is quite useful if you need audit coverage over a certain time | 226 | :CUSTOM_ID: stratified-systematic-sample |
| 216 | period. This code will generate samples for each month in the data and combine | 227 | :END: |
| 217 | them all together at the end. Obviously, this code can be modified to stratify | 228 | This next example is quite useful if you need audit coverage over a |
| 218 | by something other than months, if needed. | 229 | certain time period. This code will generate samples for each month in |
| 219 | 230 | the data and combine them all together at the end. Obviously, this code | |
| 220 | ```python | 231 | can be modified to stratify by something other than months, if needed. |
| 232 | |||
| 233 | #+begin_src python | ||
| 221 | # Import the Pandas module | 234 | # Import the Pandas module |
| 222 | import pandas | 235 | import pandas |
| 223 | 236 | ||
| @@ -257,21 +270,23 @@ def monthly_stratified_sample(df: pandas.DataFrame, date_column: str, num_select | |||
| 257 | sample_size = 3 | 270 | sample_size = 3 |
| 258 | sample = monthly_stratified_sample(df, 'Date of Sale', sample_size) | 271 | sample = monthly_stratified_sample(df, 'Date of Sale', sample_size) |
| 259 | sample.to_excel(file_output) | 272 | sample.to_excel(file_output) |
| 260 | ``` | 273 | #+end_src |
| 261 | 274 | ||
| 262 | ## Documenting the Results | 275 | ** Documenting the Results |
| 263 | 276 | :PROPERTIES: | |
| 264 | Once you've generated a proper sample, there are a few things left to do in | 277 | :CUSTOM_ID: documenting-the-results |
| 265 | order to properly ensure your process is reproducible. | 278 | :END: |
| 266 | 279 | Once you've generated a proper sample, there are a few things left to do | |
| 267 | 1. Document the sample. Make sure the resulting file is readable and includes | 280 | in order to properly ensure your process is reproducible. |
| 268 | the documentation listed in the next bullet. | 281 | |
| 269 | 2. Include documentation around the data source, extraction techniques, any | 282 | 1. Document the sample. Make sure the resulting file is readable and |
| 270 | modifications made to the data, and be sure to include a copy of the script | 283 | includes the documentation listed in the next bullet. |
| 271 | itself. | 284 | 2. Include documentation around the data source, extraction techniques, |
| 272 | 3. Whenever possible, perform a completeness and accuracy test to ensure your | 285 | any modifications made to the data, and be sure to include a copy of |
| 273 | sample is coming from a complete and accurate population. To ensure | 286 | the script itself. |
| 274 | completeness, compare the record count from the data source to the record | 287 | 3. Whenever possible, perform a completeness and accuracy test to ensure |
| 275 | count loaded into Python. To ensure accuracy, test a small sample against the | 288 | your sample is coming from a complete and accurate population. To |
| 276 | source data (e.g., test 5 sales against the database to see if the details are | 289 | ensure completeness, compare the record count from the data source to |
| 277 | accurate). | 290 | the record count loaded into Python. To ensure accuracy, test a small |
| 291 | sample against the source data (e.g., test 5 sales against the | ||
| 292 | database to see if the details are accurate). | ||
blog/2021-10-09-apache-redirect.org +36 −34
| @@ -1,45 +1,47 @@ | |||
| 1 | +++ | 1 | #+title: Apache Redirect HTML Files to a Directory |
| 2 | date = 2021-10-10 | 2 | #+date: 2021-10-10 |
| 3 | title = "Apache Redirect HTML Files to a Directory" | 3 | |
| 4 | description = "Learn how to conveniently rewrite certain Apache web file URLs to directories with the Apache web server." | 4 | ** The Problem |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: the-problem |
| 7 | 7 | :END: | |
| 8 | ## The Problem | 8 | After recently switching static site generators (SSG), my blog URLs |
| 9 | 9 | changed with no option to preserve the classic =.html= extension at the | |
| 10 | After recently switching static site generators (SSG), my blog URLs changed with | 10 | end of my blog post URLs. |
| 11 | no option to preserve the classic `.html` extension at the end of my blog | 11 | |
| 12 | post URLs. | 12 | I really disliked using my old SSG ([[https://jekyllrb.com][Jekyll]]) |
| 13 | 13 | and prefer my new tool ([[https://www.getzola.org][Zola]]) much more, so | |
| 14 | I really disliked using my old SSG ([Jekyll](https://jekyllrb.com)) and prefer | 14 | I was determined to figure out a way to get the proper redirect set up |
| 15 | my new tool ([Zola](https://www.getzola.org)) much more, so I was determined to | 15 | so that people who find my posts online aren't constantly met by 404 |
| 16 | figure out a way to get the proper redirect set up so that people who find my | 16 | errors. |
| 17 | posts online aren't constantly met by 404 errors. | 17 | |
| 18 | 18 | ** The Solution | |
| 19 | ## The Solution | 19 | :PROPERTIES: |
| 20 | 20 | :CUSTOM_ID: the-solution | |
| 21 | :END: | ||
| 21 | To solve this problem, I really needed to solve two pieces: | 22 | To solve this problem, I really needed to solve two pieces: |
| 22 | 23 | ||
| 23 | 1. Redirect all blog post URL requests from `/blog/some-post.html` to | 24 | 1. Redirect all blog post URL requests from =/blog/some-post.html= to |
| 24 | `/blog/some-post/`. | 25 | =/blog/some-post/=. |
| 25 | 2. Ensure that no other `.html` files are redirected, such as `index.html`. | 26 | 2. Ensure that no other =.html= files are redirected, such as |
| 27 | =index.html=. | ||
| 26 | 28 | ||
| 27 | After _a lot_ of tweaking and testing, I believe I have finally found the | 29 | After /a lot/ of tweaking and testing, I believe I have finally found |
| 28 | solution. The solution is shown below. | 30 | the solution. The solution is shown below. |
| 29 | 31 | ||
| 30 | ```config | 32 | #+begin_src config |
| 31 | RewriteEngine On | 33 | RewriteEngine On |
| 32 | RewriteCond %{REQUEST_URI} !\index.html$ [NC] | 34 | RewriteCond %{REQUEST_URI} !\index.html$ [NC] |
| 33 | RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L] | 35 | RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L] |
| 34 | ``` | 36 | #+end_src |
| 35 | 37 | ||
| 36 | This piece of code in the Apache `.conf` or `.htaccess` file will do the | 38 | This piece of code in the Apache =.conf= or =.htaccess= file will do the |
| 37 | following: | 39 | following: |
| 38 | 40 | ||
| 39 | 1. Turn on the RewriteEngine so that we can modify URLs. | 41 | 1. Turn on the RewriteEngine so that we can modify URLs. |
| 40 | 2. Ignore any `index.html` files from the rule we are about to specify. | 42 | 2. Ignore any =index.html= files from the rule we are about to specify. |
| 41 | 3. Find any `.html` files within the website directory and redirect it to | 43 | 3. Find any =.html= files within the website directory and redirect it |
| 42 | exclude the file extension. | 44 | to exclude the file extension. |
| 43 | 4. The final piece is adding the trailing slash (`/`) at the end of the URL - | 45 | 4. The final piece is adding the trailing slash (=/=) at the end of the |
| 44 | you'll notice that I don't have an Apache rule for that since Apache handles | 46 | URL - you'll notice that I don't have an Apache rule for that since |
| 45 | that automatically. | 47 | Apache handles that automatically. |
blog/2021-12-04-cisa.org +209 −182
| @@ -1,197 +1,224 @@ | |||
| 1 | +++ | 1 | #+title: I Passed the CISA! |
| 2 | date = 2021-12-04 | 2 | #+date: 2021-12-04 |
| 3 | title = "I Passed the CISA!" | ||
| 4 | description = "After nearly a year of on-and-off studying, I passed the CISA exam by ISACA." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | |||
| 8 | ## What is the CISA? | ||
| 9 | 3 | ||
| 4 | ** What is the CISA? | ||
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: what-is-the-cisa | ||
| 7 | :END: | ||
| 10 | For those of you lucky enough not to be knee-deep in the world of IT/IS | 8 | For those of you lucky enough not to be knee-deep in the world of IT/IS |
| 11 | Auditing, [CISA](https://www.isaca.org/credentialing/cisa) stands for Certified | 9 | Auditing, [[https://www.isaca.org/credentialing/cisa][CISA]] stands for |
| 12 | Information Systems Auditor. This certification and exam are part of ISACA's | 10 | Certified Information Systems Auditor. This certification and exam are |
| 13 | suite of certifications. As I often explain it to people like my family, it | 11 | part of ISACA's suite of certifications. As I often explain it to people |
| 14 | basically means you're employed to use your knowledge of information systems, | 12 | like my family, it basically means you're employed to use your knowledge |
| 15 | regulations, common threats, risks, etc. in order to assess an organization's | 13 | of information systems, regulations, common threats, risks, etc. in |
| 16 | current control of their risk. If a risk isn't controlled (and the company | 14 | order to assess an organization's current control of their risk. If a |
| 17 | doesn't want to accept the risk), an IS auditor will suggest implementing a | 15 | risk isn't controlled (and the company doesn't want to accept the risk), |
| 18 | control to address that risk. | 16 | an IS auditor will suggest implementing a control to address that risk. |
| 19 | 17 | ||
| 20 | Now, the CISA certification itself is, in my opinion, the main certification for | 18 | Now, the CISA certification itself is, in my opinion, the main |
| 21 | this career. While certifications such as the CPA or CISSP are beneficial, | 19 | certification for this career. While certifications such as the CPA or |
| 22 | nothing matches the power of the CISA for an IS auditor when it comes to getting | 20 | CISSP are beneficial, nothing matches the power of the CISA for an IS |
| 23 | hired, getting a raise/bonus, or earning respect in the field. | 21 | auditor when it comes to getting hired, getting a raise/bonus, or |
| 24 | 22 | earning respect in the field. | |
| 25 | However, to be honest, I am a skeptic of most certifications. I understand the | 23 | |
| 26 | value they hold in terms of how much you need to commit to studying or learning | 24 | However, to be honest, I am a skeptic of most certifications. I |
| 27 | on the job, as well as the market value for certifications such as the CISA. | 25 | understand the value they hold in terms of how much you need to commit |
| 28 | But I also have known some very ~~incompetent~~ _less than stellar_ auditors | 26 | to studying or learning on the job, as well as the market value for |
| 29 | who have CPAs, CISAs, CIAs, etc. | 27 | certifications such as the CISA. But I also have known some very |
| 30 | 28 | +incompetent+ /less than stellar/ auditors who have CPAs, CISAs, CIAs, | |
| 31 | The same goes for most industries: if a person is good at studying, they can | 29 | etc. |
| 32 | earn the certification. However, that knowledge means nothing unless you're | 30 | |
| 33 | actually able to use it in real life and perform as expected of a certification | 31 | The same goes for most industries: if a person is good at studying, they |
| 34 | holder. The challenge comes when people are hired or connected strictly because | 32 | can earn the certification. However, that knowledge means nothing unless |
| 35 | of their certifications or resume; you need to see a person work before you can | 33 | you're actually able to use it in real life and perform as expected of a |
| 36 | assume them having a CISA means they're better than someone without the CISA. | 34 | certification holder. The challenge comes when people are hired or |
| 37 | 35 | connected strictly because of their certifications or resume; you need | |
| 38 | Okay, rant over. Certifications are generally accepted as a measuring stick of | 36 | to see a person work before you can assume them having a CISA means |
| 39 | commitment and quality of an employee, so I am accepting it too. | 37 | they're better than someone without the CISA. |
| 40 | 38 | ||
| 41 | ## Exam Content | 39 | Okay, rant over. Certifications are generally accepted as a measuring |
| 42 | 40 | stick of commitment and quality of an employee, so I am accepting it | |
| 43 | The CISA is broken down into five sections, each weighted with a percentage of | 41 | too. |
| 44 | test questions that may appear. | 42 | |
| 45 | 43 | ** Exam Content | |
| 46 |  | 44 | :PROPERTIES: |
| 47 | 45 | :CUSTOM_ID: exam-content | |
| 48 | Since the exam contains 150 questions, here's how those sections break down: | 46 | :END: |
| 49 | 47 | The CISA is broken down into five sections, each weighted with a | |
| 50 | | Exam Section | Percentage of Exam | Questions | | 48 | percentage of test questions that may appear. |
| 51 | |:---------------:|:------------------:|:---------:| | 49 | |
| 52 | | 1 | 21% | 32 | | 50 | #+caption: CISA exam sections |
| 53 | | 2 | 17% | 26 | | 51 | [[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png]] |
| 54 | | 3 | 12% | 18 | | 52 | |
| 55 | | 4 | 23% | 34 | | 53 | Since the exam contains 150 questions, here's how those sections break |
| 56 | | 5 | 27% | 40 | | 54 | down: |
| 57 | | **Grand Total** | **100%** | **150** | | 55 | |
| 58 | 56 | | Exam Section | Percentage of Exam | Questions | | |
| 59 | ## My Studying Habits | 57 | |---------------+--------------------+-----------| |
| 60 | 58 | | 1 | 21% | 32 | | |
| 61 | This part is a little hard for me to break down into specific detail due to the | 59 | | 2 | 17% | 26 | |
| 62 | craziness of the last year. While I officially purchased my studying materials | 60 | | 3 | 12% | 18 | |
| 63 | in December 2020 and opened them to "start studying" in January 2021, I really | 61 | | 4 | 23% | 34 | |
| 64 | wasn't able to study much due to the demands of my job and personal life. | 62 | | 5 | 27% | 40 | |
| 63 | | *Grand Total* | *100%* | *150* | | ||
| 64 | |||
| 65 | ** My Studying Habits | ||
| 66 | :PROPERTIES: | ||
| 67 | :CUSTOM_ID: my-studying-habits | ||
| 68 | :END: | ||
| 69 | This part is a little hard for me to break down into specific detail due | ||
| 70 | to the craziness of the last year. While I officially purchased my | ||
| 71 | studying materials in December 2020 and opened them to "start studying" | ||
| 72 | in January 2021, I really wasn't able to study much due to the demands | ||
| 73 | of my job and personal life. | ||
| 65 | 74 | ||
| 66 | Let me approach this from a few different viewpoints. | 75 | Let me approach this from a few different viewpoints. |
| 67 | 76 | ||
| 68 | ### Study Materials | 77 | *** Study Materials |
| 69 | 78 | :PROPERTIES: | |
| 70 | Let's start by discussing the study materials I purchased. I'll be referring to | 79 | :CUSTOM_ID: study-materials |
| 71 | #1 as the CRM and #2 as the QAE. | 80 | :END: |
| 72 | 81 | Let's start by discussing the study materials I purchased. I'll be | |
| 73 | 1. [CISA Review Manual, 27th Edition | Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK) | 82 | referring to #1 as the CRM and #2 as the QAE. |
| 74 | 2. [CISA Review Questions, Answers & Explanations Manual, 12th Edition | Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK) | 83 | |
| 75 | 84 | 1. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK][CISA | |
| 76 | The CRM is an excellent source of information and could honestly be used as a | 85 | Review Manual, 27th Edition | Print]] |
| 77 | reference for most IS auditors as a learning reference during their daily audit | 86 | 2. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK][CISA |
| 78 | responsibilities. However, it is **full** of information and can be overloading | 87 | Review Questions, Answers & Explanations Manual, 12th Edition | |
| 79 | if you're not good at filtering out useless information while studying. | 88 | Print]] |
| 80 | 89 | ||
| 81 | The QAE is the real star of the show here. This book contains 1000 questions, | 90 | The CRM is an excellent source of information and could honestly be used |
| 82 | separated by exam section, and a practice exam. My only complaint about the QAE | 91 | as a reference for most IS auditors as a learning reference during their |
| 83 | is that each question is immediately followed with the correct answer and | 92 | daily audit responsibilities. However, it is *full* of information and |
| 84 | explanations below it, which means I had to use something to constantly cover | 93 | can be overloading if you're not good at filtering out useless |
| 85 | the answers while I was studying. | 94 | information while studying. |
| 86 | 95 | ||
| 87 | I didn't use the online database version of the QAE, but I've heard that it's | 96 | The QAE is the real star of the show here. This book contains 1000 |
| 88 | easier to use than the printed book. However, it is more expensive ($299 | 97 | questions, separated by exam section, and a practice exam. My only |
| 89 | database vs $129 book) which might be important if you're paying for materials | 98 | complaint about the QAE is that each question is immediately followed |
| 90 | yourself. | 99 | with the correct answer and explanations below it, which means I had to |
| 91 | 100 | use something to constantly cover the answers while I was studying. | |
| 92 | In terms of question difficulty, I felt that the QAE was a good representation | 101 | |
| 93 | of the actual exam. I've seen a lot of people online say it wasn't accurate to | 102 | I didn't use the online database version of the QAE, but I've heard that |
| 94 | the exam or that it was much easier/harder, but I disagree with all of those. | 103 | it's easier to use than the printed book. However, it is more expensive |
| 95 | The exam was fairly similar to the QAE, just focusing on whichever topics they | 104 | ($299 database vs $129 book) which might be important if you're paying |
| 96 | chose for my version of the exam. | 105 | for materials yourself. |
| 97 | 106 | ||
| 98 | If you understand the concepts, skim the CRM (and read in-depth on topics you | 107 | In terms of question difficulty, I felt that the QAE was a good |
| 99 | struggle with), and use the QAE to continue practicing exam-like questions, you | 108 | representation of the actual exam. I've seen a lot of people online say |
| 100 | should be fine. I didn't use any online courses, videos, etc. - the ISACA | 109 | it wasn't accurate to the exam or that it was much easier/harder, but I |
| 101 | materials are more than enough. | 110 | disagree with all of those. The exam was fairly similar to the QAE, just |
| 102 | 111 | focusing on whichever topics they chose for my version of the exam. | |
| 103 | ### Studying Process | 112 | |
| 104 | 113 | If you understand the concepts, skim the CRM (and read in-depth on | |
| 105 | While I was able to briefly read through sections 1 and 2 in early 2021, I had | 114 | topics you struggle with), and use the QAE to continue practicing |
| 106 | to stop and take a break from February/March to September. | 115 | exam-like questions, you should be fine. I didn't use any online |
| 107 | I switched jobs in September, which allowed me a lot more free time to study. | 116 | courses, videos, etc. - the ISACA materials are more than enough. |
| 108 | 117 | ||
| 109 | In September, I studied sections 3-5, took notes, and did a quick review of the | 118 | *** Studying Process |
| 110 | section topics. Once I felt comfortable with my notes, I took a practice exam | 119 | :PROPERTIES: |
| 111 | from the QAE manual and scored 70% (105/150). | 120 | :CUSTOM_ID: studying-process |
| 121 | :END: | ||
| 122 | While I was able to briefly read through sections 1 and 2 in early 2021, | ||
| 123 | I had to stop and take a break from February/March to September. I | ||
| 124 | switched jobs in September, which allowed me a lot more free time to | ||
| 125 | study. | ||
| 126 | |||
| 127 | In September, I studied sections 3-5, took notes, and did a quick review | ||
| 128 | of the section topics. Once I felt comfortable with my notes, I took a | ||
| 129 | practice exam from the QAE manual and scored 70% (105/150). | ||
| 112 | 130 | ||
| 113 | Here's a breakdown of my initial practice exam: | 131 | Here's a breakdown of my initial practice exam: |
| 114 | 132 | ||
| 115 | | Exam Section | Incorrect | Correct | Grand Total | Percent | | 133 | | Exam Section | Incorrect | Correct | Grand Total | Percent | |
| 116 | |:---------------:|:---------:|:-------:|:-----------:|:-------:| | 134 | |---------------+-----------+---------+-------------+---------| |
| 117 | | 1 | 8 | 25 | 33 | 76% | | 135 | | 1 | 8 | 25 | 33 | 76% | |
| 118 | | 2 | 5 | 20 | 25 | 80% | | 136 | | 2 | 5 | 20 | 25 | 80% | |
| 119 | | 3 | 6 | 12 | 18 | 67% | | 137 | | 3 | 6 | 12 | 18 | 67% | |
| 120 | | 4 | 10 | 23 | 33 | 70% | | 138 | | 4 | 10 | 23 | 33 | 70% | |
| 121 | | 5 | 16 | 25 | 41 | 61% | | 139 | | 5 | 16 | 25 | 41 | 61% | |
| 122 | | **Grand Total** | **45** | **105** | **150** | **70%** | | 140 | | *Grand Total* | *45* | *105* | *150* | *70%* | |
| 123 | 141 | ||
| 124 | As I expected, my toughest sections were related to project management, | 142 | As I expected, my toughest sections were related to project management, |
| 125 | development, implementation, and security. | 143 | development, implementation, and security. |
| 126 | 144 | ||
| 127 | This just leaves October and November. For these months, I tried to practice | 145 | This just leaves October and November. For these months, I tried to |
| 128 | every few days, doing 10 questions for each section, until the exam. This came | 146 | practice every few days, doing 10 questions for each section, until the |
| 129 | out to 13 practice sessions, ~140 questions per section, and ~700 questions | 147 | exam. This came out to 13 practice sessions, ~140 questions per section, |
| 130 | total. | 148 | and ~700 questions total. |
| 131 | 149 | ||
| 132 | While some practice sessions were worse and some were better, the final results | 150 | While some practice sessions were worse and some were better, the final |
| 133 | were similar to my practice exam results. As you can see below, my averages were | 151 | results were similar to my practice exam results. As you can see below, |
| 134 | slightly worse than my practice exam. However, I got in over 700 questions of | 152 | my averages were slightly worse than my practice exam. However, I got in |
| 135 | practice and, most importantly, **I read through the explanations every time I | 153 | over 700 questions of practice and, most importantly, *I read through |
| 136 | answered incorrectly and learned from my mistakes**. | 154 | the explanations every time I answered incorrectly and learned from my |
| 137 | 155 | mistakes*. | |
| 138 | | Exam Section | Incorrect | Correct | Grand Total | Percent | | 156 | |
| 139 | |:---------------:|:---------:|:-------:|:-----------:|:-------:| | 157 | | Exam Section | Incorrect | Correct | Grand Total | Percent | |
| 140 | | 1 | 33 | 108 | 141 | 77% | | 158 | |---------------+-----------+---------+-------------+---------| |
| 141 | | 2 | 33 | 109 | 142 | 77% | | 159 | | 1 | 33 | 108 | 141 | 77% | |
| 142 | | 3 | 55 | 89 | 144 | 62% | | 160 | | 2 | 33 | 109 | 142 | 77% | |
| 143 | | 4 | 52 | 88 | 140 | 63% | | 161 | | 3 | 55 | 89 | 144 | 62% | |
| 144 | | 5 | 55 | 85 | 140 | 61% | | 162 | | 4 | 52 | 88 | 140 | 63% | |
| 145 | | **Grand Total** | **228** | **479** | **707** | **68%** | | 163 | | 5 | 55 | 85 | 140 | 61% | |
| 146 | 164 | | *Grand Total* | *228* | *479* | *707* | *68%* | | |
| 147 |  | 165 | |
| 148 | 166 | #+caption: CISA practice question results | |
| 149 | ## Results | 167 | [[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png]] |
| 150 | 168 | ||
| 151 | Now, how do the practice scores reflect my actual results? | 169 | ** Results |
| 152 | After all, it's hard to tell how good a practice regimen is unless you see | 170 | :PROPERTIES: |
| 153 | how it turns out. | 171 | :CUSTOM_ID: results |
| 154 | 172 | :END: | |
| 155 | | Exam Section | Section Name | Score | | 173 | Now, how do the practice scores reflect my actual results? After all, |
| 156 | |:------------:|------------------------------------------------------------------|:-------:| | 174 | it's hard to tell how good a practice regimen is unless you see how it |
| 157 | | 1 | Information Systems Auditing Process | 678 | | 175 | turns out. |
| 158 | | 2 | Governance and Management of IT | 590 | | 176 | |
| 159 | | 3 | Information Systems Acquisition, Development, and Implementation | 721 | | 177 | | Exam Section | Section Name | Score | |
| 160 | | 4 | Information Systems Operations and Business Resilience | 643 | | 178 | |--------------+------------------------------------------------------------------+-------| |
| 161 | | 5 | Protection of Information Assets | 511 | | 179 | | 1 | Information Systems Auditing Process | 678 | |
| 162 | | **TOTAL** | | **616** | | 180 | | 2 | Governance and Management of IT | 590 | |
| 163 | 181 | | 3 | Information Systems Acquisition, Development, and Implementation | 721 | | |
| 164 | Now, in order to pass the CISA, you need at least 450 on a sliding scale of | 182 | | 4 | Information Systems Operations and Business Resilience | 643 | |
| 165 | 200-800. Personally, I really have no clue what an average CISA score is. After | 183 | | 5 | Protection of Information Assets | 511 | |
| 166 | a _very_ brief look online, I can see that the high end is usually in the low | 184 | | *TOTAL* | | *616* | |
| 167 | 700s. In addition, only about 50-60% of people pass the exam. | 185 | |
| 168 | 186 | Now, in order to pass the CISA, you need at least 450 on a sliding scale | |
| 169 | Given this information, I feel great about my scores. | 187 | of 200-800. Personally, I really have no clue what an average CISA score |
| 170 | 616 may not be phenomenal, and I wish I had done better on sections 2 & 5, | 188 | is. After a /very/ brief look online, I can see that the high end is |
| 171 | but my practicing seems to have worked very well overall. | 189 | usually in the low 700s. In addition, only about 50-60% of people pass |
| 172 | 190 | the exam. | |
| 173 | However, the practice results do not conform to the actual results. Section 2 | 191 | |
| 174 | was one of my highest practice sections and was my second-lowest score in the | 192 | Given this information, I feel great about my scores. 616 may not be |
| 175 | exam. Conversely, section 3 was my second-lowest practice section and turned out | 193 | phenomenal, and I wish I had done better on sections 2 & 5, but my |
| 176 | to be my highest actual score! | 194 | practicing seems to have worked very well overall. |
| 177 | 195 | ||
| 178 | After reflecting, it is obvious that if you have any background on the CISA | 196 | However, the practice results do not conform to the actual results. |
| 179 | topics at all, the most important part of studying is doing practice questions. | 197 | Section 2 was one of my highest practice sections and was my |
| 180 | You really need to understand how to read the questions critically and pick the | 198 | second-lowest score in the exam. Conversely, section 3 was my |
| 181 | best answer. | 199 | second-lowest practice section and turned out to be my highest actual |
| 182 | 200 | score! | |
| 183 | ## Looking Forward | 201 | |
| 184 | 202 | After reflecting, it is obvious that if you have any background on the | |
| 185 | I am extremely happy that I was finally able to pass the CISA. Looking to the | 203 | CISA topics at all, the most important part of studying is doing |
| 186 | future, I'm not sure what's next in terms of professional learning. My current | 204 | practice questions. You really need to understand how to read the |
| 187 | company offers internal learning courses, so I will most likely focus on that if | 205 | questions critically and pick the best answer. |
| 188 | I need to gain more knowledge in certain areas. | 206 | |
| 189 | 207 | ** Looking Forward | |
| 190 | To be fair, even if you pass the CISA, it's hard to become an expert on any | 208 | :PROPERTIES: |
| 191 | specific topic found within. | 209 | :CUSTOM_ID: looking-forward |
| 192 | My career may take me in a different direction, and I might need to focus | 210 | :END: |
| 193 | more on security or networking certifications (or possibly building a better | 211 | I am extremely happy that I was finally able to pass the CISA. Looking |
| 194 | analysis/visualization portfolio if I want to go into data analysis/science). | 212 | to the future, I'm not sure what's next in terms of professional |
| 213 | learning. My current company offers internal learning courses, so I will | ||
| 214 | most likely focus on that if I need to gain more knowledge in certain | ||
| 215 | areas. | ||
| 216 | |||
| 217 | To be fair, even if you pass the CISA, it's hard to become an expert on | ||
| 218 | any specific topic found within. My career may take me in a different | ||
| 219 | direction, and I might need to focus more on security or networking | ||
| 220 | certifications (or possibly building a better analysis/visualization | ||
| 221 | portfolio if I want to go into data analysis/science). | ||
| 195 | 222 | ||
| 196 | All I know is that I am content at the moment and extremely proud of my | 223 | All I know is that I am content at the moment and extremely proud of my |
| 197 | accomplishment. | 224 | accomplishment. |
blog/2022-02-10-leaving-the-office.org +249 −202
| @@ -1,227 +1,274 @@ | |||
| 1 | +++ | 1 | #+title: Leaving Office-Based Work In the Past |
| 2 | date = 2022-02-10 | 2 | #+date: 2022-02-10 |
| 3 | title = "Leaving Office-Based Work In the Past" | 3 | |
| 4 | description = "As the world moves on from the before-times, I'm taking a look back on how working from different office environments affected my effectiveness and comfort." | 4 | ** The Working World is Changing |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: the-working-world-is-changing |
| 7 | 7 | :END: | |
| 8 | ## The Working World is Changing | 8 | There has been a trend for the past few years of companies slowly |
| 9 | 9 | realizing that the pandemic is not just a temporary state that will go | |
| 10 | There has been a trend for the past few years of companies slowly realizing that | 10 | away eventually and let everything return to the way it was before. In |
| 11 | the pandemic is not just a temporary state that will go away eventually and let | 11 | terms of business and employment, this means that more and more jobs are |
| 12 | everything return to the way it was before. In terms of business and employment, | 12 | being offered as permanently remote roles. |
| 13 | this means that more and more jobs are being offered as permanently remote | ||
| 14 | roles. | ||
| 15 | 13 | ||
| 16 | I had always dreamt of working from home but thought of it as a fantasy, | 14 | I had always dreamt of working from home but thought of it as a fantasy, |
| 17 | especially since I did not want to move over into the software development | 15 | especially since I did not want to move over into the software |
| 18 | field. However, I have found that almost all roles being sent to me via | 16 | development field. However, I have found that almost all roles being |
| 19 | recruiters are permanently remote (although most are limited to US citizens or | 17 | sent to me via recruiters are permanently remote (although most are |
| 20 | even region-locked for companies who only operate in select states). | 18 | limited to US citizens or even region-locked for companies who only |
| 21 | 19 | operate in select states). | |
| 22 | I decided to take a look back at my relatively short career so far and compare | 20 | |
| 23 | the positive and negative effects of the different work environments I've been | 21 | I decided to take a look back at my relatively short career so far and |
| 24 | in. | 22 | compare the positive and negative effects of the different work |
| 25 | 23 | environments I've been in. | |
| 26 | ## In-Person Offices | 24 | |
| 27 | 25 | ** In-Person Offices | |
| 28 | ### Retail Internship | 26 | :PROPERTIES: |
| 29 | 27 | :CUSTOM_ID: in-person-offices | |
| 30 | I started my first job as a management intern at a busy retail pharmacy, working | 28 | :END: |
| 31 | my 40-hour weeks on my feet. | 29 | *** Retail Internship |
| 32 | As these retail stores don't believe in resting or sitting down, you can | 30 | :PROPERTIES: |
| 33 | guarantee that you will spend entire shifts standing, walking, or running | 31 | :CUSTOM_ID: retail-internship |
| 34 | around the store. | 32 | :END: |
| 35 | Unfortunately, I worked at a time when our store didn't have enough managers, | 33 | I started my first job as a management intern at a busy retail pharmacy, |
| 36 | so I spent the majority of my tenure at the store running and breaking a sweat. | 34 | working my 40-hour weeks on my feet. As these retail stores don't |
| 37 | 35 | believe in resting or sitting down, you can guarantee that you will | |
| 38 | Now, things aren't all bad in retail stores like this. It is definitely tiring | 36 | spend entire shifts standing, walking, or running around the store. |
| 39 | and inefficient to force employees to work constantly, or pretend to work if | 37 | Unfortunately, I worked at a time when our store didn't have enough |
| 40 | there's nothing to do, and not allow anyone to sit down. However, if you are | 38 | managers, so I spent the majority of my tenure at the store running and |
| 41 | able to operate a retail store with a limited crew and provide enough comfort | 39 | breaking a sweat. |
| 42 | and support, I believe these jobs could be both comfortable and efficient. | 40 | |
| 43 | 41 | Now, things aren't all bad in retail stores like this. It is definitely | |
| 44 | ### Semi-Private Cubicles | 42 | tiring and inefficient to force employees to work constantly, or pretend |
| 45 | 43 | to work if there's nothing to do, and not allow anyone to sit down. | |
| 46 |  | 44 | However, if you are able to operate a retail store with a limited crew |
| 47 | 45 | and provide enough comfort and support, I believe these jobs could be | |
| 48 | After about a year, I was able to find another internship - this time, it was in | 46 | both comfortable and efficient. |
| 49 | my field of interest: internal auditing. This was for a life insurance company | 47 | |
| 50 | that was well over 100 years old. The age of the company shows if you work | 48 | *** Semi-Private Cubicles |
| 51 | there, as most people in management are well into their 40s-60s with little to | 49 | :PROPERTIES: |
| 52 | no youthful leadership in the company. | 50 | :CUSTOM_ID: semi-private-cubicles |
| 53 | Likewise, they owned a large headquarters in a nice area of town with plenty | 51 | :END: |
| 54 | of space, parking, etc. | 52 | #+caption: Semi-Private Cubicles |
| 55 | 53 | [[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png]] | |
| 56 | One upside is that each person gets their own large L-shaped desk, formed into | 54 | |
| 57 | cubicles that house 4 desks/employees. These "pods" of 4-person cubicles are | 55 | After about a year, I was able to find another internship - this time, |
| 58 | linked throughout each floor of the headquarters (except the sales | 56 | it was in my field of interest: internal auditing. This was for a life |
| 59 | people, who had that open-floor concept going on). The walls of the cubicle were | 57 | insurance company that was well over 100 years old. The age of the |
| 60 | tall and provided a lot of privacy and sound-proofing, except when I used the | 58 | company shows if you work there, as most people in management are well |
| 61 | standing desk feature (I'm over 6 feet tall, so probably not an issue for most | 59 | into their 40s-60s with little to no youthful leadership in the company. |
| 62 | people). | 60 | Likewise, they owned a large headquarters in a nice area of town with |
| 61 | plenty of space, parking, etc. | ||
| 62 | |||
| 63 | One upside is that each person gets their own large L-shaped desk, | ||
| 64 | formed into cubicles that house 4 desks/employees. These "pods" of | ||
| 65 | 4-person cubicles are linked throughout each floor of the headquarters | ||
| 66 | (except the sales people, who had that open-floor concept going on). The | ||
| 67 | walls of the cubicle were tall and provided a lot of privacy and | ||
| 68 | sound-proofing, except when I used the standing desk feature (I'm over 6 | ||
| 69 | feet tall, so probably not an issue for most people). | ||
| 63 | 70 | ||
| 64 | I loved this environment, it allowed me to focus on my work with minimal | 71 | I loved this environment, it allowed me to focus on my work with minimal |
| 65 | distractions, but also allowed easy access, so I could spin around in my chair | 72 | distractions, but also allowed easy access, so I could spin around in my |
| 66 | and chat with my friends without leaving my chair. This is the closest I've been | 73 | chair and chat with my friends without leaving my chair. This is the |
| 67 | to a home office environment (which is my personal favorite, as I'll get to | 74 | closest I've been to a home office environment (which is my personal |
| 68 | later in this post). | 75 | favorite, as I'll get to later in this post). |
| 69 | 76 | ||
| 70 | ### Semi-Open Floor Concept | 77 | *** Semi-Open Floor Concept |
| 71 | 78 | :PROPERTIES: | |
| 72 |  | 79 | :CUSTOM_ID: semi-open-floor-concept |
| 73 | 80 | :END: | |
| 74 | When I shifted to my first full-time internal audit job out of college, I was | 81 | #+caption: Semi-Open Floor Concept |
| 75 | working at a company that was headquartered on a floor in a downtown high-rise | 82 | [[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png]] |
| 76 | building. The company was only about 20 years old when I worked there and were | 83 | |
| 77 | trying a lot of new things to attract young talent, one of which was a | 84 | When I shifted to my first full-time internal audit job out of college, |
| 78 | semi-open floor concept for the office. My department worked just around the | 85 | I was working at a company that was headquartered on a floor in a |
| 79 | hallway corner from the executive offices and used that "modern" layout young | 86 | downtown high-rise building. The company was only about 20 years old |
| 80 | tech companies started using in the 2000s/2010s. | 87 | when I worked there and were trying a lot of new things to attract young |
| 81 | 88 | talent, one of which was a semi-open floor concept for the office. My | |
| 82 | Each desk was brief, and you could look most coworkers in the face without | 89 | department worked just around the hallway corner from the executive |
| 83 | moving from your chair, I hated this so much. | 90 | offices and used that "modern" layout young tech companies started using |
| 84 | Directly to my left was the Chief Audit Executive (our department's leading | 91 | in the 2000s/2010s. |
| 85 | boss), and his desk was pointed so that his face would stare straight at my | 92 | |
| 86 | desk all day. | 93 | Each desk was brief, and you could look most coworkers in the face |
| 87 | I spent more time thinking about who was looking at me or checking on me | 94 | without moving from your chair, I hated this so much. Directly to my |
| 88 | than actually working. | 95 | left was the Chief Audit Executive (our department's leading boss), and |
| 89 | 96 | his desk was pointed so that his face would stare straight at my desk | |
| 90 | The other annoying part of the open concept they used was that the kitchen area | 97 | all day. I spent more time thinking about who was looking at me or |
| 91 | and pathways were too close to everyone's desks (since the desks were spread | 98 | checking on me than actually working. |
| 92 | out, to provide space or something), so noise and conversation would be constant | 99 | |
| 93 | throughout the day while you try to work. For someone like me, who needs silence | 100 | The other annoying part of the open concept they used was that the |
| 94 | to get work done, that was a non-starter. | 101 | kitchen area and pathways were too close to everyone's desks (since the |
| 95 | 102 | desks were spread out, to provide space or something), so noise and | |
| 96 | ### Hotel Office Concept | 103 | conversation would be constant throughout the day while you try to work. |
| 97 | 104 | For someone like me, who needs silence to get work done, that was a | |
| 98 |  | 105 | non-starter. |
| 99 | 106 | ||
| 100 | I currently work for a company remotely (for now) and travel to the office every | 107 | *** Hotel Office Concept |
| 101 | once in a while for events and to help coach the staff underneath me. The office | 108 | :PROPERTIES: |
| 102 | I visit uses the hotel desk concept, where you need to check in at a touch | 109 | :CUSTOM_ID: hotel-office-concept |
| 103 | screen when you enter the office and "rent" a desk for the day. The same goes | 110 | :END: |
| 104 | for offices and meeting rooms. | 111 | #+caption: Hotel Office Concept |
| 105 | 112 | [[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png]] | |
| 106 | These desks are flat-top only and do not have any walls at all. In addition, | 113 | |
| 107 | they're stacked with one row of 4 desks facing another row of 4 desks. These | 114 | I currently work for a company remotely (for now) and travel to the |
| 108 | pairs of desk rows are repeated through the office. | 115 | office every once in a while for events and to help coach the staff |
| 109 | 116 | underneath me. The office I visit uses the hotel desk concept, where you | |
| 110 | This means that when I go, I need to rent a random desk or try to remember the | 117 | need to check in at a touch screen when you enter the office and "rent" |
| 111 | unique ID numbers on desks I like. Once I rent it, I have to make sure no one | 118 | a desk for the day. The same goes for offices and meeting rooms. |
| 112 | sat down in that desk without renting it. Then, I can sit down and work, but | 119 | |
| 113 | will probably need to adjust the monitors so that I'm not staring in the face of | 120 | These desks are flat-top only and do not have any walls at all. In |
| 114 | the person across from me all day. Finally, I need to wear headphones as this | 121 | addition, they're stacked with one row of 4 desks facing another row of |
| 115 | environment does nothing to provide you with peace or quiet. | 122 | 4 desks. These pairs of desk rows are repeated through the office. |
| 116 | 123 | ||
| 117 | Luckily, you can rent offices with doors that offer quiet and privacy, which can | 124 | This means that when I go, I need to rent a random desk or try to |
| 118 | be very nice if you have a lot of meetings or webinars on a certain day. | 125 | remember the unique ID numbers on desks I like. Once I rent it, I have |
| 119 | 126 | to make sure no one sat down in that desk without renting it. Then, I | |
| 120 | ## Home Office | 127 | can sit down and work, but will probably need to adjust the monitors so |
| 121 | 128 | that I'm not staring in the face of the person across from me all day. | |
| 122 |  | 129 | Finally, I need to wear headphones as this environment does nothing to |
| 123 | 130 | provide you with peace or quiet. | |
| 124 | Okay, now let's finally get to the home office concept. I have worked from home | 131 | |
| 125 | for a little over two years at this point, across three different | 132 | Luckily, you can rent offices with doors that offer quiet and privacy, |
| 126 | jobs/employers. Over this time, I have experimented with a plethora of different | 133 | which can be very nice if you have a lot of meetings or webinars on a |
| 127 | organizational ideas, desks, and room layouts to find what works best for me. | 134 | certain day. |
| 128 | 135 | ||
| 129 | These things might not apply to you, and that's fine. | 136 | ** Home Office |
| 130 | Everyone has a different situation, and I really don't think you'll know | 137 | :PROPERTIES: |
| 131 | what works until you try. | 138 | :CUSTOM_ID: home-office |
| 132 | 139 | :END: | |
| 133 | ### Tip #1 | 140 | #+caption: Home Office |
| 134 | 141 | [[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png]] | |
| 142 | |||
| 143 | Okay, now let's finally get to the home office concept. I have worked | ||
| 144 | from home for a little over two years at this point, across three | ||
| 145 | different jobs/employers. Over this time, I have experimented with a | ||
| 146 | plethora of different organizational ideas, desks, and room layouts to | ||
| 147 | find what works best for me. | ||
| 148 | |||
| 149 | These things might not apply to you, and that's fine. Everyone has a | ||
| 150 | different situation, and I really don't think you'll know what works | ||
| 151 | until you try. | ||
| 152 | |||
| 153 | *** Tip #1 | ||
| 154 | :PROPERTIES: | ||
| 155 | :CUSTOM_ID: tip-1 | ||
| 156 | :END: | ||
| 135 | Let's start with my top rule for a home office: | 157 | Let's start with my top rule for a home office: |
| 136 | 158 | ||
| 137 | > If you live with others, working in a shared space is not effective. | 159 | #+begin_quote |
| 160 | If you live with others, working in a shared space is not effective. | ||
| 138 | 161 | ||
| 139 | It just does not work. | 162 | #+end_quote |
| 140 | If you have another person sleeping in your bedroom, it is difficult to | ||
| 141 | manage your work schedule with their sleeping/work/school schedule. | ||
| 142 | If they wake up after you need to start work, you might wake them up | ||
| 143 | or have to suffer the agony of staring at bright screens in a dark room. | ||
| 144 | 163 | ||
| 145 | In a similar vein, working from a location such as the living room likely won't | 164 | It just does not work. If you have another person sleeping in your |
| 146 | work either. | 165 | bedroom, it is difficult to manage your work schedule with their |
| 147 | Distractions will come far more frequently: televisions, cooking, cleaning, | 166 | sleeping/work/school schedule. If they wake up after you need to start |
| 148 | deliveries, etc. If you're like me, you'll end up playing a game instead of | 167 | work, you might wake them up or have to suffer the agony of staring at |
| 149 | actually doing any work. | 168 | bright screens in a dark room. |
| 150 | 169 | ||
| 151 | ### Tip #2 | 170 | In a similar vein, working from a location such as the living room |
| 171 | likely won't work either. Distractions will come far more frequently: | ||
| 172 | televisions, cooking, cleaning, deliveries, etc. If you're like me, | ||
| 173 | you'll end up playing a game instead of actually doing any work. | ||
| 152 | 174 | ||
| 175 | *** Tip #2 | ||
| 176 | :PROPERTIES: | ||
| 177 | :CUSTOM_ID: tip-2 | ||
| 178 | :END: | ||
| 153 | Okay, the second thing I've discovered that works for me: | 179 | Okay, the second thing I've discovered that works for me: |
| 154 | 180 | ||
| 155 | > Use the pomodoro method (or something similar) to balance work tasks with | 181 | #+begin_quote |
| 156 | > personal tasks. | 182 | Use the pomodoro method (or something similar) to balance work tasks |
| 157 | 183 | with personal tasks. | |
| 158 | I use a very casual version of the pomodoro method where I will work for 1-2 | 184 | |
| 159 | hours (usually set in strict intervals like 1, 1.5, 2 hours) and then will allow | 185 | #+end_quote |
| 160 | myself 30-60 minutes for personal tasks. This schedule works for me, since my | 186 | |
| 161 | work schedule really only comes to 3-6 hours of work per day. | 187 | I use a very casual version of the pomodoro method where I will work for |
| 162 | 188 | 1-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and | |
| 163 | In this case, I'll work through my list of tasks for an hour or two and then | 189 | then will allow myself 30-60 minutes for personal tasks. This schedule |
| 164 | give myself personal time to get drinks and food, wash dishes, put clothes in | 190 | works for me, since my work schedule really only comes to 3-6 hours of |
| 165 | the washer, get the mail, etc. If you're in a convenient location, this usually | 191 | work per day. |
| 166 | gives time for things like getting groceries (as long as you're not a slow | 192 | |
| 167 | shopper). | 193 | In this case, I'll work through my list of tasks for an hour or two and |
| 168 | 194 | then give myself personal time to get drinks and food, wash dishes, put | |
| 169 | ### Tip #3 | 195 | clothes in the washer, get the mail, etc. If you're in a convenient |
| 170 | 196 | location, this usually gives time for things like getting groceries (as | |
| 171 | While I listed this one as number three, I don't think I'd accomplish anything | 197 | long as you're not a slow shopper). |
| 172 | without it: | 198 | |
| 173 | 199 | *** Tip #3 | |
| 174 | > Document everything: even things you didn't before - such as task lists and | 200 | :PROPERTIES: |
| 175 | > notes from casual calls or meetings. | 201 | :CUSTOM_ID: tip-3 |
| 176 | 202 | :END: | |
| 177 | I've noticed that staying in an office gave me more constant reminders of | 203 | While I listed this one as number three, I don't think I'd accomplish |
| 178 | outstanding tasks or facts I had learned in a conversation. Translating | 204 | anything without it: |
| 179 | everything to a digital world has made me lose a bit of that focus (perhaps | 205 | |
| 180 | since I don't have visual reminders?). | 206 | #+begin_quote |
| 181 | 207 | Document everything: even things you didn't before - such as task lists | |
| 182 | Keeping a running task list of all things I have to do - even potential tasks! - | 208 | and notes from casual calls or meetings. |
| 183 | has helped me keep up without missing anything small. Likewise, keeping notes | 209 | |
| 184 | for ALL meetings and calls, no matter how casual/quick, has improved my | 210 | #+end_quote |
| 185 | retention immensely. Beyond helping my mental recollection, it has saved me | 211 | |
| 186 | numerous times when I need to do a keyword search for some topic that was discussed | 212 | I've noticed that staying in an office gave me more constant reminders |
| 187 | 6+ months ago. | 213 | of outstanding tasks or facts I had learned in a conversation. |
| 188 | 214 | Translating everything to a digital world has made me lose a bit of that | |
| 189 | ### Tip #4 | 215 | focus (perhaps since I don't have visual reminders?). |
| 190 | 216 | ||
| 217 | Keeping a running task list of all things I have to do - even potential | ||
| 218 | tasks! - has helped me keep up without missing anything small. Likewise, | ||
| 219 | keeping notes for ALL meetings and calls, no matter how casual/quick, | ||
| 220 | has improved my retention immensely. Beyond helping my mental | ||
| 221 | recollection, it has saved me numerous times when I need to do a keyword | ||
| 222 | search for some topic that was discussed 6+ months ago. | ||
| 223 | |||
| 224 | *** Tip #4 | ||
| 225 | :PROPERTIES: | ||
| 226 | :CUSTOM_ID: tip-4 | ||
| 227 | :END: | ||
| 191 | Okay, last one for now. | 228 | Okay, last one for now. |
| 192 | 229 | ||
| 193 | > Keep your work area clean. | 230 | #+begin_quote |
| 231 | Keep your work area clean. | ||
| 194 | 232 | ||
| 195 | This one is straightforward, but I know some people struggle with cleanliness or | 233 | #+end_quote |
| 196 | may not believe it makes a difference. Trust me, keeping your desk area clean | ||
| 197 | and organized makes a huge difference, both mentally and emotionally. | ||
| 198 | 234 | ||
| 199 | Just think about it, you walk into your home office and see a clean desk with a | 235 | This one is straightforward, but I know some people struggle with |
| 200 | laptop, dock, monitors, keyboard, mouse, and a notepad with a pen on top. | 236 | cleanliness or may not believe it makes a difference. Trust me, keeping |
| 237 | your desk area clean and organized makes a huge difference, both | ||
| 238 | mentally and emotionally. | ||
| 201 | 239 | ||
| 202 | Now imagine the opposite, there's an office with the same equipment, but there | 240 | Just think about it, you walk into your home office and see a clean desk |
| 203 | are clothes hanging on the chair, empty drink bottles, candy wrappers and dirty | 241 | with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen |
| 204 | plates. This can take both a mental and emotional toll by bringing constant | 242 | on top. |
| 205 | disarray and stress into your working environment. | ||
| 206 | 243 | ||
| 207 | Just keep things clean each day, and you won't need to do any big cleaning days | 244 | Now imagine the opposite, there's an office with the same equipment, but |
| 208 | to recover. | 245 | there are clothes hanging on the chair, empty drink bottles, candy |
| 246 | wrappers and dirty plates. This can take both a mental and emotional | ||
| 247 | toll by bringing constant disarray and stress into your working | ||
| 248 | environment. | ||
| 209 | 249 | ||
| 210 | ## My Preferences | 250 | Just keep things clean each day, and you won't need to do any big |
| 251 | cleaning days to recover. | ||
| 211 | 252 | ||
| 212 | I've talked about the different environments I've worked in and expressed some | 253 | ** My Preferences |
| 213 | honest thoughts on pros or cons to each, but what do I prefer? Well, if you're | 254 | :PROPERTIES: |
| 214 | reading along, you should be able to tell that I much prefer a home office above | 255 | :CUSTOM_ID: my-preferences |
| 215 | all else. | 256 | :END: |
| 257 | I've talked about the different environments I've worked in and | ||
| 258 | expressed some honest thoughts on pros or cons to each, but what do I | ||
| 259 | prefer? Well, if you're reading along, you should be able to tell that I | ||
| 260 | much prefer a home office above all else. | ||
| 216 | 261 | ||
| 217 | Being able to control my own day and allot my time as needed has brought a | 262 | Being able to control my own day and allot my time as needed has brought |
| 218 | calmness to my life and has allowed me to maximize each day. I feel far more | 263 | a calmness to my life and has allowed me to maximize each day. I feel |
| 219 | effective and efficient in a home office than any other office, especially | 264 | far more effective and efficient in a home office than any other office, |
| 220 | open-office layouts. | 265 | especially open-office layouts. |
| 221 | 266 | ||
| 222 | If I do need to return to an office part-time in the future, I really hope the | 267 | If I do need to return to an office part-time in the future, I really |
| 223 | office will have privacy and quietness in order for me to get my work done. | 268 | hope the office will have privacy and quietness in order for me to get |
| 269 | my work done. | ||
| 224 | 270 | ||
| 225 | Cubicles are good! I agree with Alice (from the comic Dilbert): | 271 | Cubicles are good! I agree with Alice (from the comic Dilbert): |
| 226 | 272 | ||
| 227 |  | 273 | #+caption: Dilbert comic strip |
| 274 | [[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png]] | ||
blog/2022-02-16-debian-and-nginx.org +120 −113
| @@ -1,76 +1,78 @@ | |||
| 1 | +++ | 1 | #+title: Migrating to a New Web Server Setup with Debian, Nginx, and Agate |
| 2 | date = 2022-02-16 | 2 | #+date: 2022-02-16 |
| 3 | title = "Migrating to a New Web Server Setup with Debian, Nginx, and Agate" | 3 | |
| 4 | description = "I've finally taken the leap of faith on two different technologies for the first time: trying out Debian as my main server OS and Nginx as my web server software." | 4 | ** Server OS: Debian |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: server-os-debian |
| 7 | 7 | :END: | |
| 8 | ## Server OS: Debian | 8 | #+caption: Debian + neofetch |
| 9 | 9 | [[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png]] | |
| 10 |  | 10 | |
| 11 | 11 | I've used various Linux distributions throughout the years, but I've | |
| 12 | I've used various Linux distributions throughout the years, but I've never used | 12 | never used anything except Ubuntu for my servers. Why? I really have no |
| 13 | anything except Ubuntu for my servers. | 13 | idea, mostly just comfort around the commands and software availability. |
| 14 | Why? | 14 | |
| 15 | I really have no idea, mostly just comfort around the commands and software | 15 | However, I have always wanted to try Debian as a server OS after testing |
| 16 | availability. | 16 | it out in a VM a few years ago (side-note: I'd love to try Alpine too, |
| 17 | 17 | but I always struggle with compatibility). So, I decided to launch a new | |
| 18 | However, I have always wanted to try Debian as a server OS after testing it out | 18 | VPS and use [[https://www.debian.org][Debian]] 11 as the OS. Spoiler |
| 19 | in a VM a few years ago (side-note: I'd love to try Alpine too, but I always | 19 | alert: it feels identical to Ubuntu for my purposes. |
| 20 | struggle with compatibility). So, I decided to launch a new VPS and use | 20 | |
| 21 | [Debian](https://www.debian.org) 11 as the OS. Spoiler alert: it feels identical | 21 | I did the normal things when first launching the VPS, such as adding a |
| 22 | to Ubuntu for my purposes. | 22 | new user, locking down SSH, etc. If you want to see that level of |
| 23 | 23 | detail, read my other post about | |
| 24 | I did the normal things when first launching the VPS, such as adding a new user, | 24 | [[https://cleberg.net/blog/how-to-set-up-a-vps-web-server/][How to Set |
| 25 | locking down SSH, etc. If you want to see that level of detail, read my other | 25 | Up a VPS Web Server]]. |
| 26 | post about | 26 | |
| 27 | [How to Set Up a VPS Web Server](https://cleberg.net/blog/how-to-set-up-a-vps-web-server/). | 27 | All of this has been similar, apart from small things such as the |
| 28 | 28 | location of users' home folders. No complaints at all from me - Debian | |
| 29 | All of this has been similar, apart from small things such as the location of | 29 | seems great. |
| 30 | users' home folders. No complaints at all from me - Debian seems great. | 30 | |
| 31 | 31 | ** Web Server: Nginx | |
| 32 | ## Web Server: Nginx | 32 | :PROPERTIES: |
| 33 | 33 | :CUSTOM_ID: web-server-nginx | |
| 34 |  | 34 | :END: |
| 35 | 35 | #+caption: Nginx status | |
| 36 | Once I had the baseline server configuration set-up for Debian, I moved on to | 36 | [[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png]] |
| 37 | trying out [Nginx](https://nginx.org) as my web server software. This required | 37 | |
| 38 | me to install the `nginx` and `ufw` packages, as well as setting up the initial | 38 | Once I had the baseline server configuration set-up for Debian, I moved |
| 39 | UFW config: | 39 | on to trying out [[https://nginx.org][Nginx]] as my web server software. |
| 40 | 40 | This required me to install the =nginx= and =ufw= packages, as well as | |
| 41 | ```sh | 41 | setting up the initial UFW config: |
| 42 | |||
| 43 | #+begin_src sh | ||
| 42 | sudo apt install nginx ufw | 44 | sudo apt install nginx ufw |
| 43 | sudo ufw allow 'Nginx Full' | 45 | sudo ufw allow 'Nginx Full' |
| 44 | sudo ufw allow SSH | 46 | sudo ufw allow SSH |
| 45 | sudo ufw enable | 47 | sudo ufw enable |
| 46 | sudo ufw status | 48 | sudo ufw status |
| 47 | sudo systemctl status nginx | 49 | sudo systemctl status nginx |
| 48 | ``` | 50 | #+end_src |
| 49 | 51 | ||
| 50 | Once I had the firewall set, I moved on to creating the directories and files | 52 | Once I had the firewall set, I moved on to creating the directories and |
| 51 | for my website. This is very easy and is basically the same as setting up an | 53 | files for my website. This is very easy and is basically the same as |
| 52 | Apache server, so no struggles here. | 54 | setting up an Apache server, so no struggles here. |
| 53 | 55 | ||
| 54 | ```sh | 56 | #+begin_src sh |
| 55 | sudo mkdir -p /var/www/your_domain/html | 57 | sudo mkdir -p /var/www/your_domain/html |
| 56 | sudo chown -R $USER:$USER /var/www/your_domain/html | 58 | sudo chown -R $USER:$USER /var/www/your_domain/html |
| 57 | sudo chmod -R 755 /var/www/your_domain | 59 | sudo chmod -R 755 /var/www/your_domain |
| 58 | nano /var/www/your_domain/html/index.html | 60 | nano /var/www/your_domain/html/index.html |
| 59 | ``` | 61 | #+end_src |
| 60 | 62 | ||
| 61 | The next part, creating the Nginx configuration files, is quite a bit different | 63 | The next part, creating the Nginx configuration files, is quite a bit |
| 62 | from Apache. First, you need to create the files in the `sites-available` folder | 64 | different from Apache. First, you need to create the files in the |
| 63 | and symlink it the `sites-enabled` folder. | 65 | =sites-available= folder and symlink it the =sites-enabled= folder. |
| 64 | 66 | ||
| 65 | Creating the config file for your domain: | 67 | Creating the config file for your domain: |
| 66 | 68 | ||
| 67 | ```sh | 69 | #+begin_src sh |
| 68 | sudo nano /etc/nginx/sites-available/your_domain | 70 | sudo nano /etc/nginx/sites-available/your_domain |
| 69 | ``` | 71 | #+end_src |
| 70 | 72 | ||
| 71 | Default content for an Nginx config file: | 73 | Default content for an Nginx config file: |
| 72 | 74 | ||
| 73 | ```sh | 75 | #+begin_src sh |
| 74 | server { | 76 | server { |
| 75 | listen 80; | 77 | listen 80; |
| 76 | listen [::]:80; | 78 | listen [::]:80; |
| @@ -84,42 +86,43 @@ server { | |||
| 84 | try_files $uri $uri/ =404; | 86 | try_files $uri $uri/ =404; |
| 85 | } | 87 | } |
| 86 | } | 88 | } |
| 87 | ``` | 89 | #+end_src |
| 88 | 90 | ||
| 89 | Finally, symlink it together: | 91 | Finally, symlink it together: |
| 90 | 92 | ||
| 91 | ```sh | 93 | #+begin_src sh |
| 92 | sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/ | 94 | sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/ |
| 93 | ``` | 95 | #+end_src |
| 94 | 96 | ||
| 95 | This will make your site available to the public (as long as you have | 97 | This will make your site available to the public (as long as you have |
| 96 | `your_domain` DNS records pointed at the server's IP address)! | 98 | =your_domain= DNS records pointed at the server's IP address)! |
| 97 | 99 | ||
| 98 | Next, I used [certbot](https://certbot.eff.org/) to issue an HTTPS certificate | 100 | Next, I used [[https://certbot.eff.org/][certbot]] to issue an HTTPS |
| 99 | for my domains using the following commands: | 101 | certificate for my domains using the following commands: |
| 100 | 102 | ||
| 101 | ```sh | 103 | #+begin_src sh |
| 102 | sudo apt install snapd; sudo snap install core; sudo snap refresh core | 104 | sudo apt install snapd; sudo snap install core; sudo snap refresh core |
| 103 | sudo snap install --classic certbot | 105 | sudo snap install --classic certbot |
| 104 | sudo ln -s /snap/bin/certbot /usr/bin/certbot | 106 | sudo ln -s /snap/bin/certbot /usr/bin/certbot |
| 105 | sudo certbot --nginx | 107 | sudo certbot --nginx |
| 106 | ``` | 108 | #+end_src |
| 107 | 109 | ||
| 108 | Now that certbot ran successfully and updated my Nginx config files to include a | 110 | Now that certbot ran successfully and updated my Nginx config files to |
| 109 | `443` server block of code, I went back in and edited the config file to include | 111 | include a =443= server block of code, I went back in and edited the |
| 110 | security HTTP headers. This part is optional, but is recommended for security | 112 | config file to include security HTTP headers. This part is optional, but |
| 111 | purposes; you can even test a website's HTTP header security at | 113 | is recommended for security purposes; you can even test a website's HTTP |
| 112 | [Security Headers](https://securityheaders.com/). | 114 | header security at [[https://securityheaders.com/][Security Headers]]. |
| 113 | 115 | ||
| 114 | The configuration below shows a set-up where you only want your website to serve | 116 | The configuration below shows a set-up where you only want your website |
| 115 | content from its own domain, except for images and scripts, which may come from | 117 | to serve content from its own domain, except for images and scripts, |
| 116 | `nullitics.com`. All other content would be blocked from loading in a browser. | 118 | which may come from =nullitics.com=. All other content would be blocked |
| 119 | from loading in a browser. | ||
| 117 | 120 | ||
| 118 | ```sh | 121 | #+begin_src sh |
| 119 | sudo nano /etc/nginx/sites-available/your_domain | 122 | sudo nano /etc/nginx/sites-available/your_domain |
| 120 | ``` | 123 | #+end_src |
| 121 | 124 | ||
| 122 | ```sh | 125 | #+begin_src sh |
| 123 | server { | 126 | server { |
| 124 | ... | 127 | ... |
| 125 | add_header Content-Security-Policy "default-src 'none'; img-src 'self' https://nullitics.com; script-src 'self' https://nullitics.com; style-src 'self'; font-src 'self'"; | 128 | add_header Content-Security-Policy "default-src 'none'; img-src 'self' https://nullitics.com; script-src 'self' https://nullitics.com; style-src 'self'; font-src 'self'"; |
| @@ -130,46 +133,50 @@ server { | |||
| 130 | add_header Referrer-Policy "no-referrer"; | 133 | add_header Referrer-Policy "no-referrer"; |
| 131 | ... | 134 | ... |
| 132 | } | 135 | } |
| 133 | ``` | 136 | #+end_src |
| 134 | 137 | ||
| 135 | ```sh | 138 | #+begin_src sh |
| 136 | sudo systemctl restart nginx | 139 | sudo systemctl restart nginx |
| 137 | ``` | 140 | #+end_src |
| 138 | 141 | ||
| 139 | ### Nginx vs. Apache | 142 | *** Nginx vs. Apache |
| 140 | 143 | :PROPERTIES: | |
| 141 | As I stated at the beginning, my historical hesitation with trying Nginx was | 144 | :CUSTOM_ID: nginx-vs.-apache |
| 142 | that the differences in configuration formats scared me away from leaving | 145 | :END: |
| 143 | Apache. | 146 | As I stated at the beginning, my historical hesitation with trying Nginx |
| 144 | However, I prefer Nginx to Apache for a few reasons: | 147 | was that the differences in configuration formats scared me away from |
| 145 | 148 | leaving Apache. However, I prefer Nginx to Apache for a few reasons: | |
| 146 | 1. Nginx uses only one config file (`your_domain`) vs. Apache's two-file | 149 | |
| 147 | approach for HTTP vs. HTTPS (`your_domain.conf` and | 150 | 1. Nginx uses only one config file (=your_domain=) vs. Apache's two-file |
| 148 | `your_domain-le-ssl.conf`). | 151 | approach for HTTP vs. HTTPS (=your_domain.conf= and |
| 149 | 2. Symlinking new configurations files and reloading Nginx are way easier than | 152 | =your_domain-le-ssl.conf=). |
| 150 | Apache's process of having to enable headers with `a2enmod mod_headers`, | 153 | 2. Symlinking new configurations files and reloading Nginx are way |
| 151 | enable PHP with `a2enmod php` (plus any other mods you need), and then | 154 | easier than Apache's process of having to enable headers with |
| 152 | enabling sites with `a2ensite`, and THEN reloading Apache. | 155 | =a2enmod mod_headers=, enable PHP with =a2enmod php= (plus any other |
| 153 | 3. The contents of the Nginx config files seem more organized and logical with | 156 | mods you need), and then enabling sites with =a2ensite=, and THEN |
| 154 | the curly-bracket approach. | 157 | reloading Apache. |
| 155 | This is a minor reason, but everything just felt cleaner while I was | 158 | 3. The contents of the Nginx config files seem more organized and |
| 156 | installing my sites and that had a big quality of life impact on the | 159 | logical with the curly-bracket approach. This is a minor reason, but |
| 157 | installation for me. | 160 | everything just felt cleaner while I was installing my sites and that |
| 158 | 161 | had a big quality of life impact on the installation for me. | |
| 159 | They're both great software packages, but Nginx just seems more organized and | 162 | |
| 160 | easier to use these days. | 163 | They're both great software packages, but Nginx just seems more |
| 161 | I will certainly be exploring the Nginx docs to see what other fun things I | 164 | organized and easier to use these days. I will certainly be exploring |
| 162 | can do with all of this. | 165 | the Nginx docs to see what other fun things I can do with all of this. |
| 163 | 166 | ||
| 164 | ## Gemini Server: Agate | 167 | ** Gemini Server: Agate |
| 165 | 168 | :PROPERTIES: | |
| 166 |  | 169 | :CUSTOM_ID: gemini-server-agate |
| 167 | 170 | :END: | |
| 168 | Finally, I set up the Agate software on this server again to host my Gemini | 171 | #+caption: Agate status |
| 169 | server content, using Rust as I have before. | 172 | [[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png]] |
| 170 | You can read my other post for more information on installing Agate: | 173 | |
| 171 | [Hosting a Gemini Server](https://cleberg.net/blog/hosting-a-gemini-server/). | 174 | Finally, I set up the Agate software on this server again to host my |
| 172 | 175 | Gemini server content, using Rust as I have before. You can read my | |
| 173 | All in all, Debian + Nginx is very slick and I prefer it over my old combination | 176 | other post for more information on installing Agate: |
| 174 | of Ubuntu + Apache (although it's really just Nginx > Apache for me, since | 177 | [[https://cleberg.net/blog/hosting-a-gemini-server/][Hosting a Gemini |
| 175 | Debian seems mostly the same as Ubuntu is so far). | 178 | Server]]. |
| 179 | |||
| 180 | All in all, Debian + Nginx is very slick and I prefer it over my old | ||
| 181 | combination of Ubuntu + Apache (although it's really just Nginx > Apache | ||
| 182 | for me, since Debian seems mostly the same as Ubuntu is so far). | ||
blog/2022-02-17-exiftool.org +57 −53
| @@ -1,63 +1,67 @@ | |||
| 1 | +++ | 1 | #+title: Stripping Image Metadata with exiftool |
| 2 | date = 2022-02-17 | 2 | #+date: 2022-02-17 |
| 3 | title = "Stripping Image Metadata with exiftool" | 3 | |
| 4 | description = "Become a h@ck3r (i.e., just protect your privacy) by using the exiftool command line package to strip metadata from images." | 4 | ** Why Strip Metadata? |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: why-strip-metadata |
| 7 | 7 | :END: | |
| 8 | ## Why Strip Metadata? | 8 | Okay, so you want to strip metadata from your photos. Perhaps you take |
| 9 | 9 | pictures of very rare birds, and the location metadata is a gold mine | |
| 10 | Okay, so you want to strip metadata from your photos. Perhaps you take pictures | 10 | for poachers, or perhaps you're just privacy-oriented like me and prefer |
| 11 | of very rare birds, and the location metadata is a gold mine for poachers, or | 11 | to strip metadata from publicly-available images. |
| 12 | perhaps you're just privacy-oriented like me and prefer to strip metadata from | 12 | |
| 13 | publicly-available images. | 13 | There are various components of image metadata that you may want to |
| 14 | 14 | delete before releasing a photo to the public. Here's an incomplete list | |
| 15 | There are various components of image metadata that you may want to delete | 15 | of things I could easily see just by inspecting a photo on my laptop: |
| 16 | before releasing a photo to the public. Here's an incomplete list of things I | 16 | |
| 17 | could easily see just by inspecting a photo on my laptop: | 17 | - Location (Latitude & Longitude) |
| 18 | 18 | - Dimensions | |
| 19 | - Location (Latitude & Longitude) | 19 | - Device Make & Model |
| 20 | - Dimensions | 20 | - Color Space |
| 21 | - Device Make & Model | 21 | - Color Profile |
| 22 | - Color Space | 22 | - Focal Length |
| 23 | - Color Profile | 23 | - Alpha Channel |
| 24 | - Focal Length | 24 | - Red Eye |
| 25 | - Alpha Channel | 25 | - Metering Mode |
| 26 | - Red Eye | 26 | - F Number |
| 27 | - Metering Mode | 27 | |
| 28 | - F Number | 28 | Regardless of your reasoning, I'm going to explain how I used the |
| 29 | 29 | =exiftool= package in Linux to automatically strip metadata from all | |
| 30 | Regardless of your reasoning, I'm going to explain how I used the `exiftool` | 30 | images in a directory (+ subdirectories). |
| 31 | package in Linux to automatically strip metadata from all images in a directory | 31 | |
| 32 | (+ subdirectories). | 32 | ** Installing =exiftool= |
| 33 | 33 | :PROPERTIES: | |
| 34 | ## Installing `exiftool` | 34 | :CUSTOM_ID: installing-exiftool |
| 35 | 35 | :END: | |
| 36 | First things first: we need to install the tool. I'm running Debian 11 on my | 36 | First things first: we need to install the tool. I'm running Debian 11 |
| 37 | server (Ubuntu will work the same), so the command is as simple as: | 37 | on my server (Ubuntu will work the same), so the command is as simple |
| 38 | 38 | as: | |
| 39 | ```sh | 39 | |
| 40 | #+begin_src sh | ||
| 40 | sudo apt install exiftool | 41 | sudo apt install exiftool |
| 41 | ``` | 42 | #+end_src |
| 42 | 43 | ||
| 43 | There are different tools that can accomplish the same thing across | 44 | There are different tools that can accomplish the same thing across |
| 44 | distributions, but I really only care to test out this one package. | 45 | distributions, but I really only care to test out this one package. |
| 45 | 46 | ||
| 46 | ## Recursively Strip Data | 47 | ** Recursively Strip Data |
| 47 | 48 | :PROPERTIES: | |
| 48 | I actually use this tool extensively to strip any photos uploaded to the website | 49 | :CUSTOM_ID: recursively-strip-data |
| 49 | that serves all the images for my blog (`img.cleberg.net`). | 50 | :END: |
| 51 | I actually use this tool extensively to strip any photos uploaded to the | ||
| 52 | website that serves all the images for my blog (=img.cleberg.net=). | ||
| 50 | 53 | ||
| 51 | The following command is incredibly useful and can be modified to include any | 54 | The following command is incredibly useful and can be modified to |
| 52 | image extensions that `exiftool` supports: | 55 | include any image extensions that =exiftool= supports: |
| 53 | 56 | ||
| 54 | ```sh | 57 | #+begin_src sh |
| 55 | exiftool -r -all= -ext jpg -ext png /path/to/directory/ | 58 | exiftool -r -all= -ext jpg -ext png /path/to/directory/ |
| 56 | ``` | 59 | #+end_src |
| 57 | 60 | ||
| 58 | See below for the results of my most recent usage of `exiftool` after I uploaded | 61 | See below for the results of my most recent usage of =exiftool= after I |
| 59 | the image for this blog post. You can see that the command will let you know how | 62 | uploaded the image for this blog post. You can see that the command will |
| 60 | many directories were scanned, how many images were updated, and how many images | 63 | let you know how many directories were scanned, how many images were |
| 61 | were unchanged. | 64 | updated, and how many images were unchanged. |
| 62 | 65 | ||
| 63 |  | 66 | #+caption: exiftool results |
| 67 | [[https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png]] | ||
blog/2022-02-20-nginx-caching.org +65 −57
| @@ -1,23 +1,22 @@ | |||
| 1 | +++ | 1 | #+title: Caching Static Content with Nginx |
| 2 | date = 2022-02-20 | 2 | #+date: 2022-02-20 |
| 3 | title = "Caching Static Content with Nginx" | 3 | |
| 4 | description = "Learn how to cache static content client-side with Nginx configurations." | 4 | ** Update Your Nginx Config to Cache Static Files |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: update-your-nginx-config-to-cache-static-files |
| 7 | 7 | :END: | |
| 8 | ## Update Your Nginx Config to Cache Static Files | 8 | If you run a website on Nginx that serves static content (i.e., content |
| 9 | 9 | that is not dynamic and changing with interactions from the user), you | |
| 10 | If you run a website on Nginx that serves static content (i.e., content that is | 10 | would likely benefit from caching that content on the client-side. If |
| 11 | not dynamic and changing with interactions from the user), you would likely | 11 | you're used to Apache and looking for the Nginx equivalent, this post |
| 12 | benefit from caching that content on the client-side. If you're used to Apache | 12 | should help. |
| 13 | and looking for the Nginx equivalent, this post should help. | 13 | |
| 14 | 14 | Luckily, setting up the cache is as easy as identifying the file types | |
| 15 | Luckily, setting up the cache is as easy as identifying the file types you want | 15 | you want to cache and determining the expiration length. To include more |
| 16 | to cache and determining the expiration length. To include more file types, | 16 | file types, simply use the bar separator (=|=) and type the new file |
| 17 | simply use the bar separator (`|`) and type the new file extension you want to | 17 | extension you want to include. |
| 18 | include. | 18 | |
| 19 | 19 | #+begin_src config | |
| 20 | ```config | ||
| 21 | server { | 20 | server { |
| 22 | ... | 21 | ... |
| 23 | 22 | ||
| @@ -27,41 +26,50 @@ server { | |||
| 27 | 26 | ||
| 28 | ... | 27 | ... |
| 29 | } | 28 | } |
| 30 | ``` | 29 | #+end_src |
| 31 | 30 | ||
| 32 | I have seen some people who prefer to set `expires` as `365d` or even `max`, but | 31 | I have seen some people who prefer to set =expires= as =365d= or even |
| 33 | that is only for stable, infrequently changing websites. | 32 | =max=, but that is only for stable, infrequently changing websites. As |
| 34 | As my site often changes (i.e., I'm never content with my website), I need | 33 | my site often changes (i.e., I'm never content with my website), I need |
| 35 | to know that my readers are seeing the new content without waiting too long. | 34 | to know that my readers are seeing the new content without waiting too |
| 36 | 35 | long. | |
| 37 | So, I went ahead and set the expiration date at `30d`, which is short enough to | 36 | |
| 38 | refresh for readers but long enough that clients/browsers won't be re-requesting | 37 | So, I went ahead and set the expiration date at =30d=, which is short |
| 39 | the static files too often, hopefully resulting in faster loading times, as | 38 | enough to refresh for readers but long enough that clients/browsers |
| 40 | images should be the only thing slowing down my site. | 39 | won't be re-requesting the static files too often, hopefully resulting |
| 41 | 40 | in faster loading times, as images should be the only thing slowing down | |
| 42 | ## Testing Results | 41 | my site. |
| 43 | 42 | ||
| 43 | ** Testing Results | ||
| 44 | :PROPERTIES: | ||
| 45 | :CUSTOM_ID: testing-results | ||
| 46 | :END: | ||
| 44 | To test my changes to the Nginx configuration, I used the | 47 | To test my changes to the Nginx configuration, I used the |
| 45 | [HTTP Header Live](https://addons.mozilla.org/en-US/firefox/addon/http-header-live/) | 48 | [[https://addons.mozilla.org/en-US/firefox/addon/http-header-live/][HTTP |
| 46 | extension on my Gecko browser and used the sidebar to inspect the headers of a | 49 | Header Live]] extension on my Gecko browser and used the sidebar to |
| 47 | recent image from my blog. | 50 | inspect the headers of a recent image from my blog. |
| 48 | 51 | ||
| 49 | In the image below, you can see that the `Cache-Control` header is now present | 52 | In the image below, you can see that the =Cache-Control= header is now |
| 50 | and set to 2592000, which is 30 days represented in seconds (30 days _ 24 | 53 | present and set to 2592000, which is 30 days represented in seconds (30 |
| 51 | hours/day _ 60 minutes/hour \* 60 seconds/minute = 2,592,000 seconds). | 54 | days _ 24 hours/day _ 60 minutes/hour * 60 seconds/minute = 2,592,000 |
| 52 | 55 | seconds). | |
| 53 | The `Expires` field is now showing 22 March 2022, which is 30 days from the day | 56 | |
| 54 | of this post, 20 February 2022. | 57 | The =Expires= field is now showing 22 March 2022, which is 30 days from |
| 55 | 58 | the day of this post, 20 February 2022. | |
| 56 |  | 59 | |
| 57 | 60 | #+caption: Image Headers | |
| 58 | ## Caveats | 61 | [[https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png]] |
| 59 | 62 | ||
| 60 | Remember that this caching system is **client-side**, which means that content | 63 | ** Caveats |
| 61 | is only cached for as long as a client allows it. For example, my browser purges | 64 | :PROPERTIES: |
| 62 | all caches, data, etc. upon exit, so this caching policy will only work as long | 65 | :CUSTOM_ID: caveats |
| 63 | as my browser remains open and running. | 66 | :END: |
| 64 | 67 | Remember that this caching system is *client-side*, which means that | |
| 65 | If you need to test updates to your site, you'll need to clear the cache to see | 68 | content is only cached for as long as a client allows it. For example, |
| 66 | updates for any file extension you configured. This can often be done with the | 69 | my browser purges all caches, data, etc. upon exit, so this caching |
| 67 | `Shift + F5` or `Ctrl + F5` key combinations in most browsers. | 70 | policy will only work as long as my browser remains open and running. |
| 71 | |||
| 72 | If you need to test updates to your site, you'll need to clear the cache | ||
| 73 | to see updates for any file extension you configured. This can often be | ||
| 74 | done with the =Shift + F5= or =Ctrl + F5= key combinations in most | ||
| 75 | browsers. | ||
blog/2022-02-22-tuesday.org +30 −27
| @@ -1,35 +1,38 @@ | |||
| 1 | +++ | 1 | #+title: Tuesday |
| 2 | date = 2022-02-22 | 2 | #+date: 2022-02-22 |
| 3 | title = "Tuesday" | ||
| 4 | description = "Today is Tuesday, February 22nd, 2022 - also a ubiquitous six-digit palindrome date known as Twosday." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | 3 | ||
| 8 | ## Tuesday, Twosday | 4 | ** Tuesday, Twosday |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: tuesday-twosday | ||
| 7 | :END: | ||
| 8 | I'm taking a break from my usual technology-related posts and writing | ||
| 9 | about something a little more enjoyable today. | ||
| 9 | 10 | ||
| 10 | I'm taking a break from my usual technology-related posts and writing about | 11 | Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is |
| 11 | something a little more enjoyable today. | 12 | Twosday. |
| 12 | |||
| 13 | Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is Twosday. | ||
| 14 | 13 | ||
| 15 | Let's look at everything that fell in place today: | 14 | Let's look at everything that fell in place today: |
| 16 | 15 | ||
| 17 | 1. Written in the `m-dd-yy` or `dd-m-yy` formats, today is 2-22-22 or 22-2-22, | 16 | 1. Written in the =m-dd-yy= or =dd-m-yy= formats, today is 2-22-22 or |
| 18 | which is a neat little palindrome in either format. (The last ubiquitous | 17 | 22-2-22, which is a neat little palindrome in either format. (The |
| 19 | six-digit palindrome was 1-11-11.) | 18 | last ubiquitous six-digit palindrome was 1-11-11.) |
| 20 | 2. Today is Tuesday, which is why everyone is using the nickname Twosday to call | 19 | 2. Today is Tuesday, which is why everyone is using the nickname Twosday |
| 21 | out these similarities. | 20 | to call out these similarities. |
| 22 | 3. Falling on Tuesday means today is the 2nd day of the week (for most | 21 | 3. Falling on Tuesday means today is the 2nd day of the week (for most |
| 23 | cultures. For the US, it's the 3rd day of the week since we start on | 22 | cultures. For the US, it's the 3rd day of the week since we start on |
| 24 | Sunday). | 23 | Sunday). |
| 25 | 4. The only culture I could find with a connection to a `2` is that some Slavic | 24 | 4. The only culture I could find with a connection to a =2= is that some |
| 26 | languages derived their version of Tuesday from the Old Church Slavonic word | 25 | Slavic languages derived their version of Tuesday from the Old Church |
| 27 | `въторъ`, meaning "the second." | 26 | Slavonic word =въторъ=, meaning "the second." |
| 28 | 5. Written in the classic monospaced, digital font (think of digital clocks from | 27 | 5. Written in the classic monospaced, digital font (think of digital |
| 29 | the 80s/90s), there is nice symmetry to the numbers | 28 | clocks from the 80s/90s), there is nice symmetry to the numbers |
| 30 | ([view the image here](https://img.cleberg.net/blog/20220222-tuesday/digital_font.png)!). | 29 | ([[https://img.cleberg.net/blog/20220222-tuesday/digital_font.png][view |
| 31 | 6. This one isn't naturally-occurring, but it seems people around the world are | 30 | the image here]]!). |
| 32 | celebrating the day. For example, a group is putting together | 31 | 6. This one isn't naturally-occurring, but it seems people around the |
| 33 | [a wedding of 222 couples at the California State Capitol in Sacramento](https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597), | 32 | world are celebrating the day. For example, a group is putting |
| 34 | concluding at exactly 2:22 PM. These couples will record their marriage dates | 33 | together |
| 35 | as 2-22-22 2:22 PM. Tickets were on sale for $222.22. | 34 | [[https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597][a |
| 35 | wedding of 222 couples at the California State Capitol in | ||
| 36 | Sacramento]], concluding at exactly 2:22 PM. These couples will | ||
| 37 | record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale | ||
| 38 | for $222.22. | ||
blog/2022-03-02-note-taking.org +132 −100
| @@ -1,125 +1,157 @@ | |||
| 1 | +++ | 1 | #+title: Easy, Reliable Note-Taking |
| 2 | date = 2022-03-02 | 2 | #+date: 2022-03-02 |
| 3 | title = "Easy, Reliable Note-Taking" | ||
| 4 | description = "After years of testing, I have finally settled on maintaining my collection of personal notes and writings." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | 3 | ||
| 8 | ## Choosing Durable File Formats | 4 | ** Choosing Durable File Formats |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: choosing-durable-file-formats | ||
| 7 | :END: | ||
| 9 | 8 | ||
| 10 | > TL;DR: Write in a format that can be easily rendered and read in plain-text | 9 | #+begin_quote |
| 11 | > mode (e.g., `.txt`, `.md`, etc.). | 10 | TL;DR: Write in a format that can be easily rendered and read in |
| 11 | plain-text mode (e.g., =.txt=, =.md=, etc.). | ||
| 12 | |||
| 13 | #+end_quote | ||
| 12 | 14 | ||
| 13 | As I've written more and more over the years, I've found that my love of | 15 | As I've written more and more over the years, I've found that my love of |
| 14 | note-taking is always growing. Everything I learn or need to remember can be | 16 | note-taking is always growing. Everything I learn or need to remember |
| 15 | written down in a note and saved digitally, with no cost to myself. Unlike paper | 17 | can be written down in a note and saved digitally, with no cost to |
| 16 | copies that need physical storage space, digital files simply need space on your | 18 | myself. Unlike paper copies that need physical storage space, digital |
| 17 | local disk or cloud storage, which is fairly abundant these days. | 19 | files simply need space on your local disk or cloud storage, which is |
| 20 | fairly abundant these days. | ||
| 18 | 21 | ||
| 19 | However, I had a historical struggle with migration of notes between different | 22 | However, I had a historical struggle with migration of notes between |
| 20 | apps that require different formats and applied proprietary styling. This meant | 23 | different apps that require different formats and applied proprietary |
| 21 | that I had to go through each note during migration and edit the file to look | 24 | styling. This meant that I had to go through each note during migration |
| 22 | presentable again. | 25 | and edit the file to look presentable again. |
| 23 | 26 | ||
| 24 | For the last year or two, I have written everything exclusively in | 27 | For the last year or two, I have written everything exclusively in |
| 25 | [Markdown](https://en.wikipedia.org/wiki/Markdown) format. Small notes, | 28 | [[https://en.wikipedia.org/wiki/Markdown][Markdown]] format. Small |
| 26 | long-form writings, and even these blog posts are all written in Markdown. | 29 | notes, long-form writings, and even these blog posts are all written in |
| 27 | 30 | Markdown. | |
| 28 | Why Markdown? While I do appreciate the simplicity of plain-text files without | 31 | |
| 29 | any formatting, I often need visual cues such as heading and code blocks to keep | 32 | Why Markdown? While I do appreciate the simplicity of plain-text files |
| 30 | my thoughts straight. Markdown provides a minimal set of styling indicators for | 33 | without any formatting, I often need visual cues such as heading and |
| 31 | me to style my notes without adding any proprietary, embedded data into the | 34 | code blocks to keep my thoughts straight. Markdown provides a minimal |
| 32 | files. If I want a top-level heading, I simply add a hash (`#`) before the line. | 35 | set of styling indicators for me to style my notes without adding any |
| 33 | An added bonus is that even if a system doesn't understand Markdown, it will | 36 | proprietary, embedded data into the files. If I want a top-level |
| 34 | render it as plain-text and I can read it just as easily. | 37 | heading, I simply add a hash (=#=) before the line. An added bonus is |
| 35 | 38 | that even if a system doesn't understand Markdown, it will render it as | |
| 36 | For example, here's how TextEdit on macOS will open and display a Markdown file | 39 | plain-text and I can read it just as easily. |
| 37 | in plain-text, since it does contain any features to preview Markdown as HTML: | 40 | |
| 38 | 41 | For example, here's how TextEdit on macOS will open and display a | |
| 39 |  | 42 | Markdown file in plain-text, since it does contain any features to |
| 40 | 43 | preview Markdown as HTML: | |
| 41 | ## Saving & Syncing Files | 44 | |
| 42 | 45 | #+caption: Plain Text Markdown | |
| 43 | In order to read and edit my notes across platforms, I use my personal cloud | 46 | [[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png]] |
| 44 | storage through Tresorit due to its native integration with macOS and iOS file | 47 | |
| 45 | managers. In addition, Tresorit works well on Debian-based Linux distros, which | 48 | ** Saving & Syncing Files |
| 46 | I used before macOS (and will likely switch back to in a few years). | 49 | :PROPERTIES: |
| 47 | 50 | :CUSTOM_ID: saving-syncing-files | |
| 48 | You can use whatever sync software you want - syncing plain-text or markdown | 51 | :END: |
| 49 | files is incredibly easy and fast, since the files are generally tiny. | 52 | In order to read and edit my notes across platforms, I use my personal |
| 50 | 53 | cloud storage through Tresorit due to its native integration with macOS | |
| 51 | Since the cloud storage syncs files automatically, there is no need for me to | 54 | and iOS file managers. In addition, Tresorit works well on Debian-based |
| 52 | sync anything manually or kick-off a sync job to update my files. This means | 55 | Linux distros, which I used before macOS (and will likely switch back to |
| 53 | that I can edit on mobile, and it takes about 5-10 seconds to see the changes on | 56 | in a few years). |
| 57 | |||
| 58 | You can use whatever sync software you want - syncing plain-text or | ||
| 59 | markdown files is incredibly easy and fast, since the files are | ||
| 60 | generally tiny. | ||
| 61 | |||
| 62 | Since the cloud storage syncs files automatically, there is no need for | ||
| 63 | me to sync anything manually or kick-off a sync job to update my files. | ||
| 64 | This means that I can edit on mobile, and it takes about 5-10 seconds to | ||
| 65 | see the changes on desktop. | ||
| 66 | |||
| 67 | *** Version Control with Git | ||
| 68 | :PROPERTIES: | ||
| 69 | :CUSTOM_ID: version-control-with-git | ||
| 70 | :END: | ||
| 71 | A different approach I've contemplated is storing my notes and | ||
| 72 | attachments is using a hosted Git repository to track changes to the | ||
| 73 | files. However, I don't want to rely on an external service that could | ||
| 74 | potentially see into my data, even if the repository is private. | ||
| 75 | |||
| 76 | I might just do =git init= locally and then commit my changes each time | ||
| 77 | I write or update a note, but that seems to be a lot of work just for | ||
| 78 | tracking changes - which I don't necessarily care to know. | ||
| 79 | |||
| 80 | *** Backups! | ||
| 81 | :PROPERTIES: | ||
| 82 | :CUSTOM_ID: backups | ||
| 83 | :END: | ||
| 84 | One small addition to the storage/sync conversation is the idea of | ||
| 85 | backups. Personally, I manually create periodic backups of my entire | ||
| 86 | cloud storage, compress it into an archive, and store it on my home | ||
| 87 | server. | ||
| 88 | |||
| 89 | To improve my workflow, I am going to be exploring options to | ||
| 90 | automatically compress the mounted cloud directory and send it over to | ||
| 91 | my server on a set schedule. | ||
| 92 | |||
| 93 | ** Writing on Desktop | ||
| 94 | :PROPERTIES: | ||
| 95 | :CUSTOM_ID: writing-on-desktop | ||
| 96 | :END: | ||
| 97 | |||
| 98 | #+begin_quote | ||
| 99 | *Update (06.14.22)*: Since writing this post, I have reverted to simply | ||
| 100 | keeping my =notes= folder open and opening notes individually in | ||
| 101 | TextEdit for a more minimal and relaxing writing experience on the | ||
| 54 | desktop. | 102 | desktop. |
| 55 | 103 | ||
| 56 | ### Version Control with Git | 104 | #+end_quote |
| 57 | |||
| 58 | A different approach I've contemplated is storing my notes and attachments is | ||
| 59 | using a hosted Git repository to track changes to the files. However, I don't | ||
| 60 | want to rely on an external service that could potentially see into my data, | ||
| 61 | even if the repository is private. | ||
| 62 | 105 | ||
| 63 | I might just do `git init` locally and then commit my changes each time I write | 106 | The bulk of my writing occurs in a desktop environment, with a full |
| 64 | or update a note, but that seems to be a lot of work just for tracking changes - | 107 | keyboard layout and wide screen. I don't illustrate with a smart pen, I |
| 65 | which I don't necessarily care to know. | 108 | rarely use embedded images, and I love being able to see all of my |
| 109 | notes/directories in a sidebar. | ||
| 66 | 110 | ||
| 67 | ### Backups! | 111 | With this simple set of requirements, I chose |
| 68 | 112 | [[https://obsidian.md][Obsidian]] as my desktop text editor. Obsidian | |
| 69 | One small addition to the storage/sync conversation is the idea of backups. | 113 | has some in-depth tools like a graph view, command palette, mentions, |
| 70 | Personally, I manually create periodic backups of my entire cloud storage, | 114 | etc., but I've found that using it as a simple Markdown editor is |
| 71 | compress it into an archive, and store it on my home server. | ||
| 72 | |||
| 73 | To improve my workflow, I am going to be exploring options to automatically | ||
| 74 | compress the mounted cloud directory and send it over to my server on a set | ||
| 75 | schedule. | ||
| 76 | |||
| 77 | ## Writing on Desktop | ||
| 78 | |||
| 79 | > **Update (06.14.22)**: Since writing this post, I have reverted to simply | ||
| 80 | > keeping my `notes` folder open and opening notes individually in TextEdit | ||
| 81 | > for a more minimal and relaxing writing experience on the desktop. | ||
| 82 | |||
| 83 | The bulk of my writing occurs in a desktop environment, with a full keyboard | ||
| 84 | layout and wide screen. I don't illustrate with a smart pen, I rarely use | ||
| 85 | embedded images, and I love being able to see all of my notes/directories in a | ||
| 86 | sidebar. | ||
| 87 | |||
| 88 | With this simple set of requirements, I chose [Obsidian](https://obsidian.md) as | ||
| 89 | my desktop text editor. | ||
| 90 | Obsidian has some in-depth tools like a graph view, command palette, | ||
| 91 | mentions, etc., but I've found that using it as a simple Markdown editor is | ||
| 92 | incredibly easy and straightforward. | 115 | incredibly easy and straightforward. |
| 93 | 116 | ||
| 94 | Here's an example of how my Markdown notes look when opened in plain-text mode: | 117 | Here's an example of how my Markdown notes look when opened in |
| 95 | 118 | plain-text mode: | |
| 96 |  | ||
| 97 | |||
| 98 | Here's the "live preview" version, where the Markdown is rendered into its HTML | ||
| 99 | format: | ||
| 100 | 119 | ||
| 101 |  | 120 | #+caption: Obsidian Markdown Source Mode |
| 121 | [[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png]] | ||
| 102 | 122 | ||
| 103 | ### Programming on Desktop | 123 | Here's the "live preview" version, where the Markdown is rendered into |
| 124 | its HTML format: | ||
| 104 | 125 | ||
| 105 | While I was writing this, I realized I should specify that I don't use the same | 126 | #+caption: Obsidian Markdown Live Preview |
| 106 | editor for writing notes and for writing code. For programming purposes, I use | 127 | [[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png]] |
| 107 | [VSCodium](https://vscodium.com) as my development IDE. | ||
| 108 | 128 | ||
| 109 | ## Writing on Mobile | 129 | *** Programming on Desktop |
| 130 | :PROPERTIES: | ||
| 131 | :CUSTOM_ID: programming-on-desktop | ||
| 132 | :END: | ||
| 133 | While I was writing this, I realized I should specify that I don't use | ||
| 134 | the same editor for writing notes and for writing code. For programming | ||
| 135 | purposes, I use [[https://vscodium.com][VSCodium]] as my development | ||
| 136 | IDE. | ||
| 110 | 137 | ||
| 138 | ** Writing on Mobile | ||
| 139 | :PROPERTIES: | ||
| 140 | :CUSTOM_ID: writing-on-mobile | ||
| 141 | :END: | ||
| 111 | Personally, I write very little on mobile, except when needing to take | 142 | Personally, I write very little on mobile, except when needing to take |
| 112 | important notes on-the-go. Any long-form writing, journals, etc. are done at | 143 | important notes on-the-go. Any long-form writing, journals, etc. are |
| 113 | home, where I always have my laptop available. | 144 | done at home, where I always have my laptop available. |
| 114 | 145 | ||
| 115 | I wanted a simple and foolproof editor for iOS, preferably open-source. | 146 | I wanted a simple and foolproof editor for iOS, preferably open-source. |
| 116 | After a long journey of testing the few (& terrible) open-source iOS | 147 | After a long journey of testing the few (& terrible) open-source iOS |
| 117 | note-taking apps, I finally found a phenomenal one: | 148 | note-taking apps, I finally found a phenomenal one: |
| 118 | [Runestone](https://github.com/simonbs/runestone). This app is fantastic | 149 | [[https://github.com/simonbs/runestone][Runestone]]. This app is |
| 119 | for note-taking, has plenty of optional features, and integrates natively | 150 | fantastic for note-taking, has plenty of optional features, and |
| 120 | with the iOS file manager. | 151 | integrates natively with the iOS file manager. |
| 121 | 152 | ||
| 122 | This app opens the iOS file manager and allows you to click any file you want, | 153 | This app opens the iOS file manager and allows you to click any file you |
| 123 | opens it up in an editor, and lets me save and close out of that note. | 154 | want, opens it up in an editor, and lets me save and close out of that |
| 155 | note. | ||
| 124 | 156 | ||
| 125 | Quite simple but effective. | 157 | Quite simple but effective. |
blog/2022-03-03-financial-database.org +173 −154
| @@ -1,153 +1,166 @@ | |||
| 1 | +++ | 1 | #+title: Maintaining a Personal Financial Database |
| 2 | date = 2022-03-03 | 2 | #+date: 2022-03-03 |
| 3 | title = "Maintaining a Personal Financial Database" | 3 | |
| 4 | description = "Detailing my process for creating, maintaining, and utilizing a personal financial database." | 4 | ** Personal Financial Tracking |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: personal-financial-tracking |
| 7 | 7 | :END: | |
| 8 | ## Personal Financial Tracking | 8 | For the last 6-ish years, I've tracked my finances in a spreadsheet. |
| 9 | 9 | This is common practice in the business world, but any good dev will | |
| 10 | For the last 6-ish years, I've tracked my finances in a spreadsheet. This is | 10 | cringe at the thought of storing long-term data in a spreadsheet. A |
| 11 | common practice in the business world, but any good dev will cringe at the | 11 | spreadsheet is not for long-term storage or as a source of data to pull |
| 12 | thought of storing long-term data in a spreadsheet. A spreadsheet is not for | 12 | data/reports. |
| 13 | long-term storage or as a source of data to pull data/reports. | 13 | |
| 14 | 14 | As I wanted to expand the functionality of my financial data (e.g., | |
| 15 | As I wanted to expand the functionality of my financial data (e.g., adding more | 15 | adding more reports), I decided to migrate the data into a database. To |
| 16 | reports), I decided to migrate the data into a database. To run reports, I would | 16 | run reports, I would query the database and use a language like Python |
| 17 | query the database and use a language like Python or Javascript to process the | 17 | or Javascript to process the data, perform calculations, and visualize |
| 18 | data, perform calculations, and visualize the data. | 18 | the data. |
| 19 | 19 | ||
| 20 | ## SQLite | 20 | ** SQLite |
| 21 | 21 | :PROPERTIES: | |
| 22 | When choosing the type of database I wanted to use for this project, I was split | 22 | :CUSTOM_ID: sqlite |
| 23 | between three options: | 23 | :END: |
| 24 | 24 | When choosing the type of database I wanted to use for this project, I | |
| 25 | 1. MySQL: The database I have the most experience with and have used for years. | 25 | was split between three options: |
| 26 | |||
| 27 | 1. MySQL: The database I have the most experience with and have used for | ||
| 28 | years. | ||
| 26 | 2. PostgreSQL: A database I'm new to, but want to learn. | 29 | 2. PostgreSQL: A database I'm new to, but want to learn. |
| 27 | 3. SQLite: A database that I've used for a couple projects and have moderate | 30 | 3. SQLite: A database that I've used for a couple projects and have |
| 28 | experience. | 31 | moderate experience. |
| 29 | 32 | ||
| 30 | I ended up choosing SQLite since it can be maintained within a single `.sqlite` | 33 | I ended up choosing SQLite since it can be maintained within a single |
| 31 | file, which allows me more flexibility for storage and backup. I keep this file | 34 | =.sqlite= file, which allows me more flexibility for storage and backup. |
| 32 | in my cloud storage and pull it up whenever needed. | 35 | I keep this file in my cloud storage and pull it up whenever needed. |
| 33 | 36 | ||
| 34 | ### GUI Editing | 37 | *** GUI Editing |
| 35 | 38 | :PROPERTIES: | |
| 36 | Since I didn't want to try and import 1000–1500 records into my new database | 39 | :CUSTOM_ID: gui-editing |
| 37 | via the command line, I opted to use | 40 | :END: |
| 38 | [DB Browser for SQLite (DB4S)](https://sqlitebrowser.org/) as a GUI tool. This | 41 | Since I didn't want to try and import 1000--1500 records into my new |
| 39 | application is excellent, and I don't see myself going back to the CLI | 42 | database via the command line, I opted to use |
| 40 | when working in this database. | 43 | [[https://sqlitebrowser.org/][DB Browser for SQLite (DB4S)]] as a GUI |
| 44 | tool. This application is excellent, and I don't see myself going back | ||
| 45 | to the CLI when working in this database. | ||
| 41 | 46 | ||
| 42 | DB4S allows you to copy a range of cells from a spreadsheet and paste it | 47 | DB4S allows you to copy a range of cells from a spreadsheet and paste it |
| 43 | straight into the SQL table. I used this process for all 36 accounts, 1290 | 48 | straight into the SQL table. I used this process for all 36 accounts, |
| 44 | account statements, and 126 pay statements. Overall, I'm guessing this took | 49 | 1290 account statements, and 126 pay statements. Overall, I'm guessing |
| 45 | anywhere between 4–8 hours. In comparison, it probably took me 2-3 days to | 50 | this took anywhere between 4--8 hours. In comparison, it probably took |
| 46 | initially create the spreadsheet. | 51 | me 2-3 days to initially create the spreadsheet. |
| 47 | 52 | ||
| 48 |  | 53 | #+caption: DB4S |
| 54 | [[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png]] | ||
| 49 | 55 | ||
| 50 | ### Schema | 56 | *** Schema |
| 51 | 57 | :PROPERTIES: | |
| 52 | The schema for this database is actually extremely simple and involves only | 58 | :CUSTOM_ID: schema |
| 53 | three tables (for now): | 59 | :END: |
| 60 | The schema for this database is actually extremely simple and involves | ||
| 61 | only three tables (for now): | ||
| 54 | 62 | ||
| 55 | 1. Accounts | 63 | 1. Accounts |
| 56 | 2. Statements | 64 | 2. Statements |
| 57 | 3. Payroll | 65 | 3. Payroll |
| 58 | 66 | ||
| 59 | **Accounts** | 67 | *Accounts* |
| 60 | 68 | ||
| 61 | The Accounts table contains summary information about an account, such as a car | 69 | The Accounts table contains summary information about an account, such |
| 62 | loan or a credit card. By viewing this table, you can find high-level data, such | 70 | as a car loan or a credit card. By viewing this table, you can find |
| 63 | as interest rate, credit line, or owner. | 71 | high-level data, such as interest rate, credit line, or owner. |
| 64 | 72 | ||
| 65 | ```sql | 73 | #+begin_src sql |
| 66 | CREATE TABLE "Accounts" ( | 74 | CREATE TABLE "Accounts" ( |
| 67 | "AccountID" INTEGER NOT NULL UNIQUE, | 75 | "AccountID" INTEGER NOT NULL UNIQUE, |
| 68 | "AccountType" TEXT, | 76 | "AccountType" TEXT, |
| 69 | "AccountName" TEXT, | 77 | "AccountName" TEXT, |
| 70 | "InterestRate" NUMERIC, | 78 | "InterestRate" NUMERIC, |
| 71 | "CreditLine" NUMERIC, | 79 | "CreditLine" NUMERIC, |
| 72 | "State" TEXT, | 80 | "State" TEXT, |
| 73 | "Owner" TEXT, | 81 | "Owner" TEXT, |
| 74 | "Co-Owner" TEXT, | 82 | "Co-Owner" TEXT, |
| 75 | PRIMARY KEY("AccountID" AUTOINCREMENT) | 83 | PRIMARY KEY("AccountID" AUTOINCREMENT) |
| 76 | ) | 84 | ) |
| 77 | ``` | 85 | #+end_src |
| 78 | 86 | ||
| 79 | **Statements** | 87 | *Statements* |
| 80 | 88 | ||
| 81 | The Statements table uses the same unique identifier as the Accounts table, | 89 | The Statements table uses the same unique identifier as the Accounts |
| 82 | meaning you can join the tables to find a monthly statement for any of the | 90 | table, meaning you can join the tables to find a monthly statement for |
| 83 | accounts listed in the Accounts table. Each statement has an account ID, | 91 | any of the accounts listed in the Accounts table. Each statement has an |
| 84 | statement date, and total balance. | 92 | account ID, statement date, and total balance. |
| 85 | 93 | ||
| 86 | ```sql | 94 | #+begin_src sql |
| 87 | CREATE TABLE "Statements" ( | 95 | CREATE TABLE "Statements" ( |
| 88 | "StatementID" INTEGER NOT NULL UNIQUE, | 96 | "StatementID" INTEGER NOT NULL UNIQUE, |
| 89 | "AccountID" INTEGER, | 97 | "AccountID" INTEGER, |
| 90 | "StatementDate" INTEGER, | 98 | "StatementDate" INTEGER, |
| 91 | "Balance" NUMERIC, | 99 | "Balance" NUMERIC, |
| 92 | PRIMARY KEY("StatementID" AUTOINCREMENT), | 100 | PRIMARY KEY("StatementID" AUTOINCREMENT), |
| 93 | FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") | 101 | FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") |
| 94 | ) | 102 | ) |
| 95 | ``` | 103 | #+end_src |
| 96 | 104 | ||
| 97 | **Payroll** | 105 | *Payroll* |
| 98 | 106 | ||
| 99 | The Payroll table is a separate entity, unrelated to the Accounts or Statements | 107 | The Payroll table is a separate entity, unrelated to the Accounts or |
| 100 | tables. This table contains all information you would find on a pay statement | 108 | Statements tables. This table contains all information you would find on |
| 101 | from an employer. As you change employers or obtain new perks/benefits, just add | 109 | a pay statement from an employer. As you change employers or obtain new |
| 102 | new columns to adapt to the new data. | 110 | perks/benefits, just add new columns to adapt to the new data. |
| 103 | 111 | ||
| 104 | ```sql | 112 | #+begin_src sql |
| 105 | CREATE TABLE "Payroll" ( | 113 | CREATE TABLE "Payroll" ( |
| 106 | "PaycheckID" INTEGER NOT NULL UNIQUE, | 114 | "PaycheckID" INTEGER NOT NULL UNIQUE, |
| 107 | "PayDate" TEXT, | 115 | "PayDate" TEXT, |
| 108 | "Payee" TEXT, | 116 | "Payee" TEXT, |
| 109 | "Employer" TEXT, | 117 | "Employer" TEXT, |
| 110 | "JobTitle" TEXT, | 118 | "JobTitle" TEXT, |
| 111 | "IncomeRegular" NUMERIC, | 119 | "IncomeRegular" NUMERIC, |
| 112 | "IncomePTO" NUMERIC, | 120 | "IncomePTO" NUMERIC, |
| 113 | "IncomeHoliday" NUMERIC, | 121 | "IncomeHoliday" NUMERIC, |
| 114 | "IncomeBonus" NUMERIC, | 122 | "IncomeBonus" NUMERIC, |
| 115 | "IncomePTOPayout" NUMERIC, | 123 | "IncomePTOPayout" NUMERIC, |
| 116 | "IncomeReimbursements" NUMERIC, | 124 | "IncomeReimbursements" NUMERIC, |
| 117 | "FringeHSA" NUMERIC, | 125 | "FringeHSA" NUMERIC, |
| 118 | "FringeStudentLoan" NUMERIC, | 126 | "FringeStudentLoan" NUMERIC, |
| 119 | "Fringe401k" NUMERIC, | 127 | "Fringe401k" NUMERIC, |
| 120 | "PreTaxMedical" NUMERIC, | 128 | "PreTaxMedical" NUMERIC, |
| 121 | "PreTaxDental" NUMERIC, | 129 | "PreTaxDental" NUMERIC, |
| 122 | "PreTaxVision" NUMERIC, | 130 | "PreTaxVision" NUMERIC, |
| 123 | "PreTaxLifeInsurance" NUMERIC, | 131 | "PreTaxLifeInsurance" NUMERIC, |
| 124 | "PreTax401k" NUMERIC, | 132 | "PreTax401k" NUMERIC, |
| 125 | "PreTaxParking" NUMERIC, | 133 | "PreTaxParking" NUMERIC, |
| 126 | "PreTaxStudentLoan" NUMERIC, | 134 | "PreTaxStudentLoan" NUMERIC, |
| 127 | "PreTaxOther" NUMERIC, | 135 | "PreTaxOther" NUMERIC, |
| 128 | "TaxFederal" NUMERIC, | 136 | "TaxFederal" NUMERIC, |
| 129 | "TaxSocial" NUMERIC, | 137 | "TaxSocial" NUMERIC, |
| 130 | "TaxMedicare" NUMERIC, | 138 | "TaxMedicare" NUMERIC, |
| 131 | "TaxState" NUMERIC, | 139 | "TaxState" NUMERIC, |
| 132 | PRIMARY KEY("PaycheckID" AUTOINCREMENT) | 140 | PRIMARY KEY("PaycheckID" AUTOINCREMENT) |
| 133 | ) | 141 | ) |
| 134 | ``` | 142 | #+end_src |
| 135 | 143 | ||
| 136 | ### Python Reporting | 144 | *** Python Reporting |
| 137 | 145 | :PROPERTIES: | |
| 138 | Once I created the database tables and imported all my data, the only step left | 146 | :CUSTOM_ID: python-reporting |
| 139 | was to create a process to report and visualize on various aspects of the data. | 147 | :END: |
| 140 | 148 | Once I created the database tables and imported all my data, the only | |
| 141 | In order to explore and create the reports I'm interested in, I utilized a | 149 | step left was to create a process to report and visualize on various |
| 142 | two-part process involving Jupyter Notebooks and Python scripts. | 150 | aspects of the data. |
| 143 | 151 | ||
| 144 | #### Step 1: Jupyter Notebooks | 152 | In order to explore and create the reports I'm interested in, I utilized |
| 145 | 153 | a two-part process involving Jupyter Notebooks and Python scripts. | |
| 154 | |||
| 155 | **** Step 1: Jupyter Notebooks | ||
| 156 | :PROPERTIES: | ||
| 157 | :CUSTOM_ID: step-1-jupyter-notebooks | ||
| 158 | :END: | ||
| 146 | When I need to explore data, try different things, and re-run my code | 159 | When I need to explore data, try different things, and re-run my code |
| 147 | cell-by-cell, I use Jupyter Notebooks. For example, I explored the `Accounts` | 160 | cell-by-cell, I use Jupyter Notebooks. For example, I explored the |
| 148 | table until I found the following useful information: | 161 | =Accounts= table until I found the following useful information: |
| 149 | 162 | ||
| 150 | ```python | 163 | #+begin_src python |
| 151 | import sqlite3 | 164 | import sqlite3 |
| 152 | import pandas as pd | 165 | import pandas as pd |
| 153 | import matplotlib | 166 | import matplotlib |
| @@ -167,14 +180,17 @@ matplotlib.rcParams['legend.labelcolor'] = 'black' | |||
| 167 | 180 | ||
| 168 | # Display graph | 181 | # Display graph |
| 169 | df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%') | 182 | df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%') |
| 170 | ``` | 183 | #+end_src |
| 171 | |||
| 172 | #### Step 2: Python Scripts | ||
| 173 | 184 | ||
| 174 | Once I explored enough through the notebooks and had a list of reports I wanted, | 185 | **** Step 2: Python Scripts |
| 175 | I moved on to create a Python project with the following structure: | 186 | :PROPERTIES: |
| 187 | :CUSTOM_ID: step-2-python-scripts | ||
| 188 | :END: | ||
| 189 | Once I explored enough through the notebooks and had a list of reports I | ||
| 190 | wanted, I moved on to create a Python project with the following | ||
| 191 | structure: | ||
| 176 | 192 | ||
| 177 | ``` | 193 | #+begin_example |
| 178 | finance/ | 194 | finance/ |
| 179 | ├── notebooks/ | 195 | ├── notebooks/ |
| 180 | │ │ ├── account_summary.ipynb | 196 | │ │ ├── account_summary.ipynb |
| @@ -191,23 +207,24 @@ finance/ | |||
| 191 | ├── process.py | 207 | ├── process.py |
| 192 | ├── requirements.txt | 208 | ├── requirements.txt |
| 193 | └── README.md | 209 | └── README.md |
| 194 | ``` | 210 | #+end_example |
| 195 | 211 | ||
| 196 | This structure allows me to: | 212 | This structure allows me to: |
| 197 | 213 | ||
| 198 | 1. Compile all required python packages into `requirements.txt` for easy | 214 | 1. Compile all required python packages into =requirements.txt= for easy |
| 199 | installation if I move to a new machine. | 215 | installation if I move to a new machine. |
| 200 | 2. Activate a virtual environment in `venv/` so I don't need to maintain a | 216 | 2. Activate a virtual environment in =venv/= so I don't need to maintain |
| 201 | system-wide Python environment just for this project. | 217 | a system-wide Python environment just for this project. |
| 202 | 3. Keep my `notebooks/` folder to continuously explore the data as I see fit. | 218 | 3. Keep my =notebooks/= folder to continuously explore the data as I see |
| 203 | 4. Maintain a local copy of the database in `src/` for easy access. | 219 | fit. |
| 204 | 5. Export reports, images, HTML files, etc. to `public/`. | 220 | 4. Maintain a local copy of the database in =src/= for easy access. |
| 205 | 221 | 5. Export reports, images, HTML files, etc. to =public/=. | |
| 206 | Now, onto the differences between the code in a Jupyter Notebook and the actual | 222 | |
| 207 | Python files. To create the report in the Notebook snippet above, I created the | 223 | Now, onto the differences between the code in a Jupyter Notebook and the |
| 208 | following function inside `process.py`: | 224 | actual Python files. To create the report in the Notebook snippet above, |
| 209 | 225 | I created the following function inside =process.py=: | |
| 210 | ```python | 226 | |
| 227 | #+begin_src python | ||
| 211 | # Create summary pie chart | 228 | # Create summary pie chart |
| 212 | def summary_data(accounts: pandas.DataFrame) -> None: | 229 | def summary_data(accounts: pandas.DataFrame) -> None: |
| 213 | accounts_01 = accounts[accounts["Owner"] == "Person01"] | 230 | accounts_01 = accounts[accounts["Owner"] == "Person01"] |
| @@ -233,21 +250,23 @@ def summary_data(accounts: pandas.DataFrame) -> None: | |||
| 233 | ) | 250 | ) |
| 234 | ) | 251 | ) |
| 235 | summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200) | 252 | summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200) |
| 236 | ``` | 253 | #+end_src |
| 237 | 254 | ||
| 238 | The result? A high-quality pie chart that is read directly by the | 255 | The result? A high-quality pie chart that is read directly by the |
| 239 | `public/index.html` template I use. | 256 | =public/index.html= template I use. |
| 240 | 257 | ||
| 241 |  | 258 | #+caption: Summary Pie Chart |
| 259 | [[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png]] | ||
| 242 | 260 | ||
| 243 | Other charts generated by this project include: | 261 | Other charts generated by this project include: |
| 244 | 262 | ||
| 245 | - Charts of account balances over time. | 263 | - Charts of account balances over time. |
| 246 | - Line chart of effective tax rate (taxes divided by taxable income). | 264 | - Line chart of effective tax rate (taxes divided by taxable income). |
| 247 | - Salary projections and error limits using past income and inflation rates. | 265 | - Salary projections and error limits using past income and inflation |
| 248 | - Multi-line chart of gross income, taxable income, and net income. | 266 | rates. |
| 267 | - Multi-line chart of gross income, taxable income, and net income. | ||
| 249 | 268 | ||
| 250 | The best thing about this project? I can improve it at any given time, shaping | 269 | The best thing about this project? I can improve it at any given time, |
| 251 | it into whatever helps me the most for that time. I imagine that I will be | 270 | shaping it into whatever helps me the most for that time. I imagine that |
| 252 | introducing an asset tracking table soon to track the depreciating value of | 271 | I will be introducing an asset tracking table soon to track the |
| 253 | cars, houses, etc. Who knows what's next? | 272 | depreciating value of cars, houses, etc. Who knows what's next? |
blog/2022-03-08-plex-migration.org +199 −162
| @@ -1,233 +1,270 @@ | |||
| 1 | +++ | 1 | #+title: Migrating Plex to New Server (+ Nvidia Transcoding) |
| 2 | date = 2022-03-08 | 2 | #+date: 2022-03-08 |
| 3 | title = "Migrating Plex to New Server (+ Nvidia Transcoding)" | 3 | |
| 4 | description = "A quick instructional guide on how to migrate Plex Media Server to a new server, including servers with Nvidia GPUs." | 4 | ** Migration Phases |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: migration-phases |
| 7 | 7 | :END: | |
| 8 | ## Migration Phases | 8 | I recently decided to migrate my server from an old OptiPlex desktop |
| 9 | 9 | machine to a custom-built tower with better hardware in every category. | |
| 10 | I recently decided to migrate my server from an old OptiPlex desktop machine to | 10 | In order to do this, I would need to properly migrate a full Plex |
| 11 | a custom-built tower with better hardware in every category. In order to do | 11 | installation. |
| 12 | this, I would need to properly migrate a full Plex installation. | 12 | |
| 13 | 13 | The second part of this migration is that the new server uses an Nvidia | |
| 14 | The second part of this migration is that the new server uses an Nvidia GPU and | 14 | GPU and does not have any integrated graphics, which requires extra work |
| 15 | does not have any integrated graphics, which requires extra work for | 15 | for installation, but provides much better hardware transcoding options |
| 16 | installation, but provides much better hardware transcoding options for Plex. | 16 | for Plex. |
| 17 | 17 | ||
| 18 | Therefore, I have broken this migration down into three phases: | 18 | Therefore, I have broken this migration down into three phases: |
| 19 | 19 | ||
| 20 | 1. [Configure the New Server](#phase-1-configure-the-new-server) | 20 | 1. [[#phase-1-configure-the-new-server][Configure the New Server]] |
| 21 | 2. [Migrate Plex Data & Devices](#phase-2-migrate-plex-data-devices) | 21 | 2. [[#phase-2-migrate-plex-data-devices][Migrate Plex Data & Devices]] |
| 22 | 3. [Configure GPU Transcoding](#phase-3-configure-gpu-transcoding) | 22 | 3. [[#phase-3-configure-gpu-transcoding][Configure GPU Transcoding]] |
| 23 | |||
| 24 | --- | ||
| 25 | 23 | ||
| 26 | ## Phase 1: Configure the New Server | 24 | -------------- |
| 27 | |||
| 28 | ### Choosing an OS | ||
| 29 | 25 | ||
| 26 | ** Phase 1: Configure the New Server | ||
| 27 | :PROPERTIES: | ||
| 28 | :CUSTOM_ID: phase-1-configure-the-new-server | ||
| 29 | :END: | ||
| 30 | *** Choosing an OS | ||
| 31 | :PROPERTIES: | ||
| 32 | :CUSTOM_ID: choosing-an-os | ||
| 33 | :END: | ||
| 30 | In order to migrate Plex to my new server, I first needed to choose an | 34 | In order to migrate Plex to my new server, I first needed to choose an |
| 31 | appropriate operating system (OS) and install it on the machine. Given that I | 35 | appropriate operating system (OS) and install it on the machine. Given |
| 32 | have encountered numerous issues installing other Linux distributions | 36 | that I have encountered numerous issues installing other Linux |
| 33 | properly with Nvidia graphics, I chose | 37 | distributions properly with Nvidia graphics, I chose |
| 34 | [Ubuntu Server](https://ubuntu.com/download/server). | 38 | [[https://ubuntu.com/download/server][Ubuntu Server]]. |
| 35 | 39 | ||
| 36 | The first step is to create a bootable USB with Ubuntu Server. This is easy with | 40 | The first step is to create a bootable USB with Ubuntu Server. This is |
| 37 | [Etcher](https://www.balena.io/etcher/), an app that runs on many different | 41 | easy with [[https://www.balena.io/etcher/][Etcher]], an app that runs on |
| 38 | platforms. Just download the Ubuntu Server `.iso` image, launch Etcher, and | 42 | many different platforms. Just download the Ubuntu Server =.iso= image, |
| 39 | install the `.iso` on the USB. | 43 | launch Etcher, and install the =.iso= on the USB. |
| 40 | 44 | ||
| 41 | Once the USB is created, insert it into my server, reboot, and click `Esc` (or | 45 | Once the USB is created, insert it into my server, reboot, and click |
| 42 | any of the `F1-12` keys) until the BIOS menu appears. Finally, launch the USB | 46 | =Esc= (or any of the =F1-12= keys) until the BIOS menu appears. Finally, |
| 43 | boot drive. | 47 | launch the USB boot drive. |
| 44 | 48 | ||
| 45 | ### Booting with Nvidia | 49 | *** Booting with Nvidia |
| 46 | 50 | :PROPERTIES: | |
| 51 | :CUSTOM_ID: booting-with-nvidia | ||
| 52 | :END: | ||
| 47 | In order to install Ubuntu Server with an Nvidia Graphics card (and no | 53 | In order to install Ubuntu Server with an Nvidia Graphics card (and no |
| 48 | integrated graphics on this device for some reason), you'll have to configure | 54 | integrated graphics on this device for some reason), you'll have to |
| 49 | the boot menu to allow different graphics drivers to be loaded. | 55 | configure the boot menu to allow different graphics drivers to be |
| 56 | loaded. | ||
| 50 | 57 | ||
| 51 | When booting from the USB, the machine will launch the initial installation | 58 | When booting from the USB, the machine will launch the initial |
| 52 | menu. From this menu, type `e` to view the default command options that come | 59 | installation menu. From this menu, type =e= to view the default command |
| 53 | with the device - it's a good idea to take a photo of this screen, so you can | 60 | options that come with the device - it's a good idea to take a photo of |
| 54 | enter these commands on the next screen (along with adding support for Nvidia). | 61 | this screen, so you can enter these commands on the next screen (along |
| 62 | with adding support for Nvidia). | ||
| 55 | 63 | ||
| 56 | Finally, type `Ctrl + C` to enter the command line. From this command line, | 64 | Finally, type =Ctrl + C= to enter the command line. From this command |
| 57 | enter the commands found on the `e` screen. **Remember to add `nomodeset` to the | 65 | line, enter the commands found on the =e= screen. *Remember to add |
| 58 | `linux ...` line so that your Nvidia device will display the installation | 66 | =nomodeset= to the =linux ...= line so that your Nvidia device will |
| 59 | screens properly!** | 67 | display the installation screens properly!* |
| 60 | 68 | ||
| 61 | Here's an example of the commands I pulled from the `e` screen and entered on | 69 | Here's an example of the commands I pulled from the =e= screen and |
| 62 | the command line. | 70 | entered on the command line. |
| 63 | 71 | ||
| 64 | ```sh | 72 | #+begin_src sh |
| 65 | setparams 'Install Ubuntu Server' | 73 | setparams 'Install Ubuntu Server' |
| 66 | setgfxpayload=keep | 74 | setgfxpayload=keep |
| 67 | linux /casper/vmlinuz quiet nomodeset --- | 75 | linux /casper/vmlinuz quiet nomodeset --- |
| 68 | initrd /casper/initrd | 76 | initrd /casper/initrd |
| 69 | boot | 77 | boot |
| 70 | ``` | 78 | #+end_src |
| 71 | 79 | ||
| 72 |  | 80 | #+caption: Ubuntu Server Installation 01 |
| 81 | [[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png]] | ||
| 73 | 82 | ||
| 74 |  | 83 | #+caption: Ubuntu Server Installation 02 |
| 84 | [[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png]] | ||
| 75 | 85 | ||
| 76 | Once the machine is rebooted, enter the `e` screen again and add `nomodeset` to | 86 | Once the machine is rebooted, enter the =e= screen again and add |
| 77 | the `linux ...` line again and press `Ctrl + X` to save the boot options. | 87 | =nomodeset= to the =linux ...= line again and press =Ctrl + X= to save |
| 88 | the boot options. | ||
| 78 | 89 | ||
| 79 | The machine is now fully installed and can properly display on an external | 90 | The machine is now fully installed and can properly display on an |
| 80 | display using the Nvidia GPU. | 91 | external display using the Nvidia GPU. |
| 81 | 92 | ||
| 82 | Always remember to update and upgrade on a new installation: | 93 | Always remember to update and upgrade on a new installation: |
| 83 | 94 | ||
| 84 | ```sh | 95 | #+begin_src sh |
| 85 | sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y | 96 | sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y |
| 86 | ``` | 97 | #+end_src |
| 87 | 98 | ||
| 88 | --- | 99 | -------------- |
| 89 | |||
| 90 | ## Phase 2: Migrate Plex Data & Devices | ||
| 91 | 100 | ||
| 101 | ** Phase 2: Migrate Plex Data & Devices | ||
| 102 | :PROPERTIES: | ||
| 103 | :CUSTOM_ID: phase-2-migrate-plex-data-devices | ||
| 104 | :END: | ||
| 92 | This phase uses the great Plex article on migrations | 105 | This phase uses the great Plex article on migrations |
| 93 | ([Move an Installation to Another System](https://support.plex.tv/articles/201370363-move-an-install-to-another-system/)) | 106 | ([[https://support.plex.tv/articles/201370363-move-an-install-to-another-system/][Move |
| 94 | and adds a bit more information to help with commands and context. | 107 | an Installation to Another System]]) and adds a bit more information to |
| 95 | 108 | help with commands and context. | |
| 96 | ### Terminology | 109 | |
| 97 | 110 | *** Terminology | |
| 98 | **Source:** The original server that is being replaced. | 111 | :PROPERTIES: |
| 99 | **Destination:** The new server. | 112 | :CUSTOM_ID: terminology |
| 100 | **Client:** Any application that can be used to modify settings for both | 113 | :END: |
| 114 | *Source:* The original server that is being replaced.\\ | ||
| 115 | *Destination:* The new server.\\ | ||
| 116 | *Client:* Any application that can be used to modify settings for both | ||
| 101 | source/destination. | 117 | source/destination. |
| 102 | 118 | ||
| 103 | ### Step 01: [Client] Update Settings | 119 | *** Step 01: [Client] Update Settings |
| 104 | 120 | :PROPERTIES: | |
| 105 | Open up a Plex app and _disable_ the `Account` > `Library` > | 121 | :CUSTOM_ID: step-01-client-update-settings |
| 106 | `Empty trash automatically after every scan` preference for the source server. | 122 | :END: |
| 107 | 123 | Open up a Plex app and /disable/ the =Account= > =Library= > | |
| 108 | ### Step 02: [Destination] Install Plex | 124 | =Empty trash automatically after every scan= preference for the source |
| 109 | 125 | server. | |
| 110 | Open up the | 126 | |
| 111 | [Plex Media Server download page](https://www.plex.tv/media-server-downloads/) | 127 | *** Step 02: [Destination] Install Plex |
| 112 | and copy the link for the appropriate platform. | 128 | :PROPERTIES: |
| 113 | 129 | :CUSTOM_ID: step-02-destination-install-plex | |
| 114 | Execute the following commands on the destination server to install Plex: | 130 | :END: |
| 115 | 131 | Open up the [[https://www.plex.tv/media-server-downloads/][Plex Media | |
| 116 | ```sh | 132 | Server download page]] and copy the link for the appropriate platform. |
| 133 | |||
| 134 | Execute the following commands on the destination server to install | ||
| 135 | Plex: | ||
| 136 | |||
| 137 | #+begin_src sh | ||
| 117 | wget <url> | 138 | wget <url> |
| 118 | sudo dpkg -i <filename> | 139 | sudo dpkg -i <filename> |
| 119 | sudo systemctl stop plexmediaserver.service | 140 | sudo systemctl stop plexmediaserver.service |
| 120 | ``` | 141 | #+end_src |
| 121 | |||
| 122 | ### Step 03: [Source] Stop Plex & Migrate Data | ||
| 123 | 142 | ||
| 124 | First, stop the Plex service so that no data is created or modified during the | 143 | *** Step 03: [Source] Stop Plex & Migrate Data |
| 125 | migration. | 144 | :PROPERTIES: |
| 145 | :CUSTOM_ID: step-03-source-stop-plex-migrate-data | ||
| 146 | :END: | ||
| 147 | First, stop the Plex service so that no data is created or modified | ||
| 148 | during the migration. | ||
| 126 | 149 | ||
| 127 | ```sh | 150 | #+begin_src sh |
| 128 | sudo systemctl stop plexmediaserver.service | 151 | sudo systemctl stop plexmediaserver.service |
| 129 | ``` | 152 | #+end_src |
| 130 | 153 | ||
| 131 | Next, copy the data to the new server. To find where the Plex data directory is | 154 | Next, copy the data to the new server. To find where the Plex data |
| 132 | located, Plex has another excellent article available: | 155 | directory is located, Plex has another excellent article available: |
| 133 | [Where is the Plex Media Server data directory located?](https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/). | 156 | [[https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/][Where |
| 157 | is the Plex Media Server data directory located?]]. | ||
| 134 | 158 | ||
| 135 | There are many ways to copy the data to the new server and will largely depend | 159 | There are many ways to copy the data to the new server and will largely |
| 136 | on the size of the folder being copied. Personally, my data folder was ~23GB and | 160 | depend on the size of the folder being copied. Personally, my data |
| 137 | I opted to simply use the `scp` command to copy the files over SSH. | 161 | folder was ~23GB and I opted to simply use the =scp= command to copy the |
| 162 | files over SSH. | ||
| 138 | 163 | ||
| 139 | This process was throttled by the old server's slow HDD and ports and took | 164 | This process was throttled by the old server's slow HDD and ports and |
| 140 | approximately 90 minutes to complete. In comparison, moving the data from the | 165 | took approximately 90 minutes to complete. In comparison, moving the |
| 141 | new server's `home/user/` directory to the `/var/.../Plex Media Server` | 166 | data from the new server's =home/user/= directory to the |
| 142 | directory took 2-3 minutes. | 167 | =/var/.../Plex Media Server= directory took 2-3 minutes. |
| 143 | 168 | ||
| 144 | ```sh | 169 | #+begin_src sh |
| 145 | scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'" | 170 | scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'" |
| 146 | ``` | 171 | #+end_src |
| 147 | 172 | ||
| 148 | ### Step 04: [Destination] Update File Permissions | 173 | *** Step 04: [Destination] Update File Permissions |
| 174 | :PROPERTIES: | ||
| 175 | :CUSTOM_ID: step-04-destination-update-file-permissions | ||
| 176 | :END: | ||
| 177 | In case you move the data directory to a common area on the new server, | ||
| 178 | it will have to be moved to the proper location before Plex can function | ||
| 179 | properly: | ||
| 149 | 180 | ||
| 150 | In case you move the data directory to a common area on the new server, it will | 181 | #+begin_src sh |
| 151 | have to be moved to the proper location before Plex can function properly: | ||
| 152 | |||
| 153 | ```sh | ||
| 154 | mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/ | 182 | mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/ |
| 155 | ``` | 183 | #+end_src |
| 156 | 184 | ||
| 157 | To ensure permissions were retained properly, the server will need to show that | 185 | To ensure permissions were retained properly, the server will need to |
| 158 | all files and folders in the data directory are owned by `plex:plex` (or | 186 | show that all files and folders in the data directory are owned by |
| 159 | whichever user is running the Plex application). | 187 | =plex:plex= (or whichever user is running the Plex application). |
| 160 | 188 | ||
| 161 | ```sh | 189 | #+begin_src sh |
| 162 | sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" | 190 | sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" |
| 163 | ``` | 191 | #+end_src |
| 164 | 192 | ||
| 165 | Finally, start the service and check the status. | 193 | Finally, start the service and check the status. |
| 166 | 194 | ||
| 167 | ```sh | 195 | #+begin_src sh |
| 168 | sudo systemctl start plexmediaserver.service | 196 | sudo systemctl start plexmediaserver.service |
| 169 | sudo systemctl status plexmediaserver.service | 197 | sudo systemctl status plexmediaserver.service |
| 170 | ``` | 198 | #+end_src |
| 171 | 199 | ||
| 172 | ### Step 05: [Client] Update Libraries & Metadata | 200 | *** Step 05: [Client] Update Libraries & Metadata |
| 173 | 201 | :PROPERTIES: | |
| 174 | The first step - now that the new server is up and running - is to sign out of | 202 | :CUSTOM_ID: step-05-client-update-libraries-metadata |
| 175 | the client and sign back in. Once this is done, update any library locations, if | 203 | :END: |
| 176 | necessary. This was unnecessary in my case since I simply moved my storage | 204 | The first step - now that the new server is up and running - is to sign |
| 177 | drives from the source server to the destination server. | 205 | out of the client and sign back in. Once this is done, update any |
| 206 | library locations, if necessary. This was unnecessary in my case since I | ||
| 207 | simply moved my storage drives from the source server to the destination | ||
| 208 | server. | ||
| 178 | 209 | ||
| 179 | Next, perform the following actions in the client: | 210 | Next, perform the following actions in the client: |
| 180 | 211 | ||
| 181 | 1. On the left sidebar, click `More` > Three-Dot Menu > `Scan Library Files` | 212 | 1. On the left sidebar, click =More= > Three-Dot Menu > |
| 182 | 2. _Enable_ the `Account` > `Library` > | 213 | =Scan Library Files= |
| 183 | `Empty trash automatically after every scan` preference for the source | 214 | 2. /Enable/ the =Account= > =Library= > |
| 184 | server. | 215 | =Empty trash automatically after every scan= preference for the |
| 185 | 3. On the left sidebar, click `More` > Three-Dot Menu > `Manage Server` > | 216 | source server. |
| 186 | `Empty Trash` | 217 | 3. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server= |
| 187 | 4. On the left sidebar, click `More` > Three-Dot Menu > `Manage Server` > | 218 | > =Empty Trash= |
| 188 | `Clean Bundles` | 219 | 4. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server= |
| 189 | 5. On the left sidebar, click `More` > Three-Dot Menu > `Manage Server` > | 220 | > =Clean Bundles= |
| 190 | `Optimize Database` | 221 | 5. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server= |
| 191 | 222 | > =Optimize Database= | |
| 192 | Finally, double-check the Remote Access settings to make sure no changes have | 223 | |
| 193 | caused issues with accessing the server from outside the network. | 224 | Finally, double-check the Remote Access settings to make sure no changes |
| 194 | 225 | have caused issues with accessing the server from outside the network. | |
| 195 | In my case, I use a single port forwarding rule in my router and needed to | 226 | |
| 196 | update the Local LAN IP Address to the new server IP address. | 227 | In my case, I use a single port forwarding rule in my router and needed |
| 197 | 228 | to update the Local LAN IP Address to the new server IP address. | |
| 198 |  | 229 | |
| 199 | 230 | #+caption: Router port forwarding | |
| 200 | --- | 231 | [[https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png]] |
| 201 | 232 | ||
| 202 | ## Phase 3: Configure GPU Transcoding | 233 | -------------- |
| 203 | 234 | ||
| 204 | The final piece to the migration is enabling hardware transcoding so that Plex | 235 | ** Phase 3: Configure GPU Transcoding |
| 205 | can fully utilize the new Nvidia GPU available in the server. The first step is | 236 | :PROPERTIES: |
| 206 | to install Nvidia graphics drivers. This process may take a few minutes, but the | 237 | :CUSTOM_ID: phase-3-configure-gpu-transcoding |
| 207 | commands are pretty simple: | 238 | :END: |
| 208 | 239 | The final piece to the migration is enabling hardware transcoding so | |
| 209 | ```sh | 240 | that Plex can fully utilize the new Nvidia GPU available in the server. |
| 241 | The first step is to install Nvidia graphics drivers. This process may | ||
| 242 | take a few minutes, but the commands are pretty simple: | ||
| 243 | |||
| 244 | #+begin_src sh | ||
| 210 | sudo add-apt-repository ppa:graphics-drivers/ppa | 245 | sudo add-apt-repository ppa:graphics-drivers/ppa |
| 211 | sudo apt update | 246 | sudo apt update |
| 212 | sudo apt-get install ubuntu-drivers-common | 247 | sudo apt-get install ubuntu-drivers-common |
| 213 | sudo ubuntu-drivers autoinstall | 248 | sudo ubuntu-drivers autoinstall |
| 214 | ``` | 249 | #+end_src |
| 215 | 250 | ||
| 216 | Finally, reboot so that the changes are loaded: | 251 | Finally, reboot so that the changes are loaded: |
| 217 | 252 | ||
| 218 | ```sh | 253 | #+begin_src sh |
| 219 | sudo reboot now | 254 | sudo reboot now |
| 220 | ``` | 255 | #+end_src |
| 221 | 256 | ||
| 222 | To ensure that the Nvidia graphics drivers are working properly, run the | 257 | To ensure that the Nvidia graphics drivers are working properly, run the |
| 223 | following command to view the available GPUs, statistics, and processes: | 258 | following command to view the available GPUs, statistics, and processes: |
| 224 | 259 | ||
| 225 | ```sh | 260 | #+begin_src sh |
| 226 | sudo nvidia-smi | 261 | sudo nvidia-smi |
| 227 | ``` | 262 | #+end_src |
| 228 | 263 | ||
| 229 |  | 264 | #+caption: nvidia-smi |
| 265 | [[https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png]] | ||
| 230 | 266 | ||
| 231 | Finally, enable hardware transcoding settings in the Plex application: | 267 | Finally, enable hardware transcoding settings in the Plex application: |
| 232 | 268 | ||
| 233 |  | 269 | #+caption: Plex transcoding settings |
| 270 | [[https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png]] | ||
blog/2022-03-23-cloudflare-dns-api.org +50 −49
| @@ -1,39 +1,40 @@ | |||
| 1 | +++ | 1 | #+title: Dynamic DNS with Cloudflare API |
| 2 | date = 2022-03-23 | 2 | #+date: 2022-03-23 |
| 3 | title = "Dynamic DNS with Cloudflare API" | 3 | |
| 4 | description = "Learn how to update Cloudflare DNS records automatically with a simple bash script." | 4 | ** DDNS: Dynamic DNS |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: ddns-dynamic-dns |
| 7 | 7 | :END: | |
| 8 | ## DDNS: Dynamic DNS | 8 | If you're hosting a service from a location with dynamic DNS (where your |
| 9 | 9 | IP may change at any time), you must have a solution to update the DNS | |
| 10 | If you're hosting a service from a location with dynamic DNS (where your IP may | 10 | so that you can access your service even when the IP of the server |
| 11 | change at any time), you must have a solution to update the DNS so that you can | 11 | changes. |
| 12 | access your service even when the IP of the server changes. | 12 | |
| 13 | 13 | The process below uses the [[https://api.cloudflare.com/][Cloudflare | |
| 14 | The process below uses the [Cloudflare API](https://api.cloudflare.com/) to | 14 | API]] to update DNS =A= and =AAAA= records with the server's current IP. |
| 15 | update DNS `A` and `AAAA` records with the server's current IP. If you use | 15 | If you use another DNS provider, you will have to find a way to update |
| 16 | another DNS provider, you will have to find a way to update your DNS (or find a | 16 | your DNS (or find a way to get a static IP). |
| 17 | way to get a static IP). | 17 | |
| 18 | 18 | First, install =jq= since we will use it in the next script: | |
| 19 | First, install `jq` since we will use it in the next script: | 19 | |
| 20 | 20 | #+begin_src sh | |
| 21 | ```sh | ||
| 22 | sudo apt install jq | 21 | sudo apt install jq |
| 23 | ``` | 22 | #+end_src |
| 24 | 23 | ||
| 25 | Next, create a location for your DDNS update scripts and open the first script: | 24 | Next, create a location for your DDNS update scripts and open the first |
| 25 | script: | ||
| 26 | 26 | ||
| 27 | ```sh | 27 | #+begin_src sh |
| 28 | mkdir ~/ddns | 28 | mkdir ~/ddns |
| 29 | nano ~/ddns/update.sh | 29 | nano ~/ddns/update.sh |
| 30 | ``` | 30 | #+end_src |
| 31 | 31 | ||
| 32 | The following `update.sh` script will take all of your domains and subdomains | 32 | The following =update.sh= script will take all of your domains and |
| 33 | and check Cloudflare to see if the current `A` and `AAAA` records match your | 33 | subdomains and check Cloudflare to see if the current =A= and =AAAA= |
| 34 | server's IP address. If not, it will update the records. | 34 | records match your server's IP address. If not, it will update the |
| 35 | records. | ||
| 35 | 36 | ||
| 36 | ```sh | 37 | #+begin_src sh |
| 37 | # file: update.sh | 38 | # file: update.sh |
| 38 | #!/bin/bash | 39 | #!/bin/bash |
| 39 | 40 | ||
| @@ -55,20 +56,20 @@ do | |||
| 55 | echo -e "\nUpdating $subdomain..." | 56 | echo -e "\nUpdating $subdomain..." |
| 56 | zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh | 57 | zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh |
| 57 | done | 58 | done |
| 58 | ``` | 59 | #+end_src |
| 59 | 60 | ||
| 60 | Next, open up the `ddns.sh` script. Paste the following into the script and | 61 | Next, open up the =ddns.sh= script. Paste the following into the script |
| 61 | update the `api_token` and `email` variables. | 62 | and update the =api_token= and =email= variables. |
| 62 | 63 | ||
| 63 | ```sh | 64 | #+begin_src sh |
| 64 | nano ~/ddns/ddns.sh | 65 | nano ~/ddns/ddns.sh |
| 65 | ``` | 66 | #+end_src |
| 66 | 67 | ||
| 67 | :warning: **Note**: If you want your DNS records to be proxied through | 68 | :warning: *Note*: If you want your DNS records to be proxied through |
| 68 | Cloudflare, find and update the following snippet: `\"proxied\":false}"` to say | 69 | Cloudflare, find and update the following snippet: =\"proxied\":false}"= |
| 69 | `true` instead of `false`. | 70 | to say =true= instead of =false=. |
| 70 | 71 | ||
| 71 | ```sh | 72 | #+begin_src sh |
| 72 | # file: ddns.sh | 73 | # file: ddns.sh |
| 73 | #!/bin/bash | 74 | #!/bin/bash |
| 74 | # based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a | 75 | # based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a |
| @@ -161,30 +162,30 @@ then | |||
| 161 | else | 162 | else |
| 162 | echo "There is a problem with either the email or the password" | 163 | echo "There is a problem with either the email or the password" |
| 163 | fi | 164 | fi |
| 164 | ``` | 165 | #+end_src |
| 165 | 166 | ||
| 166 | Once the script is saved and closed, make the scripts executable: | 167 | Once the script is saved and closed, make the scripts executable: |
| 167 | 168 | ||
| 168 | ```sh | 169 | #+begin_src sh |
| 169 | chmod +x ~/ddns/ddns.sh | 170 | chmod +x ~/ddns/ddns.sh |
| 170 | chmod +x ~/ddns/update.sh | 171 | chmod +x ~/ddns/update.sh |
| 171 | ``` | 172 | #+end_src |
| 172 | 173 | ||
| 173 | You can test the script by running it manually: | 174 | You can test the script by running it manually: |
| 174 | 175 | ||
| 175 | ```sh | 176 | #+begin_src sh |
| 176 | ./update.sh | 177 | ./update.sh |
| 177 | ``` | 178 | #+end_src |
| 178 | 179 | ||
| 179 | To make sure the scripts run automatically, add it to the `cron` file so that it | 180 | To make sure the scripts run automatically, add it to the =cron= file so |
| 180 | will run on a schedule. To do this, open the cron file: | 181 | that it will run on a schedule. To do this, open the cron file: |
| 181 | 182 | ||
| 182 | ```sh | 183 | #+begin_src sh |
| 183 | crontab -e | 184 | crontab -e |
| 184 | ``` | 185 | #+end_src |
| 185 | 186 | ||
| 186 | In the cron file, paste the following at the bottom of the editor: | 187 | In the cron file, paste the following at the bottom of the editor: |
| 187 | 188 | ||
| 188 | ```sh | 189 | #+begin_src sh |
| 189 | */5 * * * * bash /home/<your_username>/ddns/update.sh | 190 | */5 * * * * bash /home/<your_username>/ddns/update.sh |
| 190 | ``` | 191 | #+end_src |
blog/2022-03-23-nextcloud-on-ubuntu.org +95 −75
| @@ -1,80 +1,90 @@ | |||
| 1 | +++ | 1 | #+title: Installing Nextcloud on Ubuntu |
| 2 | date = 2022-03-23 | 2 | #+date: 2022-03-23 |
| 3 | title = "Installing Nextcloud on Ubuntu" | ||
| 4 | description = "Learn how to install the self-hosted cloud storage solution, Nextcloud, on Ubuntu." | ||
| 5 | draft = false | ||
| 6 | +++ | ||
| 7 | 3 | ||
| 8 | ## What is Nextcloud? | 4 | ** What is Nextcloud? |
| 9 | 5 | :PROPERTIES: | |
| 10 | [Nextcloud](https://nextcloud.com/) is a self-hosted solution for storage, | 6 | :CUSTOM_ID: what-is-nextcloud |
| 11 | communications, editing, calendar, contacts, and more. | 7 | :END: |
| 8 | [[https://nextcloud.com/][Nextcloud]] is a self-hosted solution for | ||
| 9 | storage, communications, editing, calendar, contacts, and more. | ||
| 12 | 10 | ||
| 13 | This tutorial assumes that you have an Ubuntu server and a domain name | 11 | This tutorial assumes that you have an Ubuntu server and a domain name |
| 14 | configured to point toward the server. | 12 | configured to point toward the server. |
| 15 | 13 | ||
| 16 | ## Install Dependencies | 14 | ** Install Dependencies |
| 17 | 15 | :PROPERTIES: | |
| 16 | :CUSTOM_ID: install-dependencies | ||
| 17 | :END: | ||
| 18 | To start, you will need to install the packages that Nextcloud requires: | 18 | To start, you will need to install the packages that Nextcloud requires: |
| 19 | 19 | ||
| 20 | ```sh | 20 | #+begin_src sh |
| 21 | sudo apt install apache2 mariadb-server libapache2-mod-php7.4 | 21 | sudo apt install apache2 mariadb-server libapache2-mod-php7.4 |
| 22 | sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl | 22 | sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl |
| 23 | sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip | 23 | sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip |
| 24 | ``` | 24 | #+end_src |
| 25 | 25 | ||
| 26 | ## Set Up MySQL | 26 | ** Set Up MySQL |
| 27 | :PROPERTIES: | ||
| 28 | :CUSTOM_ID: set-up-mysql | ||
| 29 | :END: | ||
| 30 | Next, you will need to log in to MySQL as the =root= user of the | ||
| 31 | machine. | ||
| 27 | 32 | ||
| 28 | Next, you will need to log in to MySQL as the `root` user of the machine. | 33 | #+begin_src sh |
| 29 | |||
| 30 | ```sh | ||
| 31 | sudo mysql -uroot -p | 34 | sudo mysql -uroot -p |
| 32 | ``` | 35 | #+end_src |
| 33 | 36 | ||
| 34 | Once you've logged in, you must create a new user so that Nextcloud can manage | 37 | Once you've logged in, you must create a new user so that Nextcloud can |
| 35 | the database. You will also create a `nextcloud` database and assign privileges: | 38 | manage the database. You will also create a =nextcloud= database and |
| 39 | assign privileges: | ||
| 36 | 40 | ||
| 37 | ```sql | 41 | #+begin_src sql |
| 38 | CREATE USER 'username'@'localhost' IDENTIFIED BY 'password'; | 42 | CREATE USER 'username'@'localhost' IDENTIFIED BY 'password'; |
| 39 | CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; | 43 | CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; |
| 40 | GRANT ALL PRIVILEGES ON nextcloud.* TO 'username'@'localhost'; | 44 | GRANT ALL PRIVILEGES ON nextcloud.* TO 'username'@'localhost'; |
| 41 | FLUSH PRIVILEGES; | 45 | FLUSH PRIVILEGES; |
| 42 | quit; | 46 | quit; |
| 43 | ``` | 47 | #+end_src |
| 44 | |||
| 45 | ## Download & Install Nextcloud | ||
| 46 | 48 | ||
| 49 | ** Download & Install Nextcloud | ||
| 50 | :PROPERTIES: | ||
| 51 | :CUSTOM_ID: download-install-nextcloud | ||
| 52 | :END: | ||
| 47 | To download Nextcloud, go the | 53 | To download Nextcloud, go the |
| 48 | [Nextcloud downloads page](https://nextcloud.com/install/#instructions-server), | 54 | [[https://nextcloud.com/install/#instructions-server][Nextcloud |
| 49 | click on `Archive File` and right-click the big blue button to copy the link. | 55 | downloads page]], click on =Archive File= and right-click the big blue |
| 56 | button to copy the link. | ||
| 50 | 57 | ||
| 51 | Then, go to your server and enter the following commands to download, unzip, and | 58 | Then, go to your server and enter the following commands to download, |
| 52 | move the files to your destination directory. This example uses `example.com` as | 59 | unzip, and move the files to your destination directory. This example |
| 53 | the destination, but you can put it wherever you want to server your files from. | 60 | uses =example.com= as the destination, but you can put it wherever you |
| 61 | want to server your files from. | ||
| 54 | 62 | ||
| 55 | ```sh | 63 | #+begin_src sh |
| 56 | wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip | 64 | wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip |
| 57 | sudo apt install unzip | 65 | sudo apt install unzip |
| 58 | unzip nextcloud-23.0.3.zip | 66 | unzip nextcloud-23.0.3.zip |
| 59 | sudo cp -r nextcloud /var/www/example.com | 67 | sudo cp -r nextcloud /var/www/example.com |
| 60 | ``` | 68 | #+end_src |
| 61 | 69 | ||
| 62 | ## Configure the Apache Web Server | 70 | ** Configure the Apache Web Server |
| 63 | 71 | :PROPERTIES: | |
| 64 | Now that the database is set up and Nextcloud is installed, you need to set up | 72 | :CUSTOM_ID: configure-the-apache-web-server |
| 65 | the Apache configuration files to tell the server how to handle requests for | 73 | :END: |
| 66 | `example.com/nextcloud`. | 74 | Now that the database is set up and Nextcloud is installed, you need to |
| 75 | set up the Apache configuration files to tell the server how to handle | ||
| 76 | requests for =example.com/nextcloud=. | ||
| 67 | 77 | ||
| 68 | First, open the following file in the editor: | 78 | First, open the following file in the editor: |
| 69 | 79 | ||
| 70 | ```sh | 80 | #+begin_src sh |
| 71 | sudo nano /etc/apache2/sites-available/nextcloud.conf | 81 | sudo nano /etc/apache2/sites-available/nextcloud.conf |
| 72 | ``` | 82 | #+end_src |
| 73 | 83 | ||
| 74 | Once the editor is open, paste the following information in. Then, save and | 84 | Once the editor is open, paste the following information in. Then, save |
| 75 | close the file. | 85 | and close the file. |
| 76 | 86 | ||
| 77 | ```config | 87 | #+begin_src config |
| 78 | <VirtualHost *:80> | 88 | <VirtualHost *:80> |
| 79 | DocumentRoot /var/www/example.com | 89 | DocumentRoot /var/www/example.com |
| 80 | ServerName example.com | 90 | ServerName example.com |
| @@ -93,69 +103,79 @@ close the file. | |||
| 93 | </IfModule> | 103 | </IfModule> |
| 94 | </Directory> | 104 | </Directory> |
| 95 | </VirtualHost> | 105 | </VirtualHost> |
| 96 | ``` | 106 | #+end_src |
| 97 | 107 | ||
| 98 | Once the file is saved, enable it with Apache: | 108 | Once the file is saved, enable it with Apache: |
| 99 | 109 | ||
| 100 | ```sh | 110 | #+begin_src sh |
| 101 | sudo a2ensite nextcloud.conf | 111 | sudo a2ensite nextcloud.conf |
| 102 | ``` | 112 | #+end_src |
| 103 | 113 | ||
| 104 | Next, enable the Apache mods required by Nextcloud: | 114 | Next, enable the Apache mods required by Nextcloud: |
| 105 | 115 | ||
| 106 | ```sh | 116 | #+begin_src sh |
| 107 | sudo a2enmod rewrite headers env dir mime | 117 | sudo a2enmod rewrite headers env dir mime |
| 108 | ``` | 118 | #+end_src |
| 109 | 119 | ||
| 110 | Finally, restart Apache. If any errors arise, you must solve those before | 120 | Finally, restart Apache. If any errors arise, you must solve those |
| 111 | continuing. | 121 | before continuing. |
| 112 | 122 | ||
| 113 | ```sh | 123 | #+begin_src sh |
| 114 | sudo systemctl restart apache2 | 124 | sudo systemctl restart apache2 |
| 115 | ``` | 125 | #+end_src |
| 116 | 126 | ||
| 117 | For the app to work, you must have the correct file permissions on your | 127 | For the app to work, you must have the correct file permissions on your |
| 118 | `nextcloud` directory. Set the owner to be `www-data`: | 128 | =nextcloud= directory. Set the owner to be =www-data=: |
| 119 | 129 | ||
| 120 | ```sh | 130 | #+begin_src sh |
| 121 | sudo chown -R www-data:www-data /var/www/example.com/nextcloud/ | 131 | sudo chown -R www-data:www-data /var/www/example.com/nextcloud/ |
| 122 | ``` | 132 | #+end_src |
| 123 | |||
| 124 | ## DNS | ||
| 125 | 133 | ||
| 134 | ** DNS | ||
| 135 | :PROPERTIES: | ||
| 136 | :CUSTOM_ID: dns | ||
| 137 | :END: | ||
| 126 | If you do not have a static IP address, you will need to update your DNS | 138 | If you do not have a static IP address, you will need to update your DNS |
| 127 | settings (at your DNS provider) whenever your dynamic IP address changes. | 139 | settings (at your DNS provider) whenever your dynamic IP address |
| 140 | changes. | ||
| 128 | 141 | ||
| 129 | For an example on how I do that with Cloudflare, see my other post: | 142 | For an example on how I do that with Cloudflare, see my other post: |
| 130 | [Updating Dynamic DNS with Cloudflare API](/blog/updating-dynamic-dns-with-cloudflare-api/) | 143 | [[/blog/updating-dynamic-dns-with-cloudflare-api/][Updating Dynamic DNS |
| 144 | with Cloudflare API]] | ||
| 131 | 145 | ||
| 132 | ## Certbot | 146 | ** Certbot |
| 147 | :PROPERTIES: | ||
| 148 | :CUSTOM_ID: certbot | ||
| 149 | :END: | ||
| 150 | If you want to serve Nextcloud from HTTPS rather than plain HTTP, use | ||
| 151 | the following commands to issue Let's Encrypt SSL certificates: | ||
| 133 | 152 | ||
| 134 | If you want to serve Nextcloud from HTTPS rather than plain HTTP, use the | 153 | #+begin_src sh |
| 135 | following commands to issue Let's Encrypt SSL certificates: | ||
| 136 | |||
| 137 | ```sh | ||
| 138 | sudo apt install snapd | 154 | sudo apt install snapd |
| 139 | sudo snap install core | 155 | sudo snap install core |
| 140 | sudo snap refresh core | 156 | sudo snap refresh core |
| 141 | sudo snap install --classic certbot | 157 | sudo snap install --classic certbot |
| 142 | sudo ln -s /snap/bin/certbot /usr/bin/certbot | 158 | sudo ln -s /snap/bin/certbot /usr/bin/certbot |
| 143 | sudo certbot --apache | 159 | sudo certbot --apache |
| 144 | ``` | 160 | #+end_src |
| 145 | |||
| 146 | ## Results | ||
| 147 | 161 | ||
| 148 | Voilà! | 162 | ** Results |
| 149 | You're all done and should be able to access Nextcloud from your domain or | 163 | :PROPERTIES: |
| 150 | IP address. | 164 | :CUSTOM_ID: results |
| 165 | :END: | ||
| 166 | Voilà! You're all done and should be able to access Nextcloud from your | ||
| 167 | domain or IP address. | ||
| 151 | 168 | ||
| 152 | See the screenshots below for the dashboard and a settings page on my instance | 169 | See the screenshots below for the dashboard and a settings page on my |
| 153 | of Nextcloud, using the `Breeze Dark` theme I installed from the Apps page. | 170 | instance of Nextcloud, using the =Breeze Dark= theme I installed from |
| 171 | the Apps page. | ||
| 154 | 172 | ||
| 155 |  | 173 | #+caption: Nextcloud Dashboard |
| 174 | [[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png]] | ||
| 156 | 175 | ||
| 157 | _Figure 01: Nextcloud Dashboard_ | 176 | /Figure 01: Nextcloud Dashboard/ |
| 158 | 177 | ||
| 159 |  | 178 | #+caption: Nextcloud Settings |
| 179 | [[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png]] | ||
| 160 | 180 | ||
| 161 | _Figure 02: Nextcloud Security Settings_ | 181 | /Figure 02: Nextcloud Security Settings/ |
blog/2022-03-24-server-hardening.org +339 −299
| @@ -1,23 +1,28 @@ | |||
| 1 | +++ | 1 | #+title: Hardening a Public-Facing Home Server |
| 2 | date = 2022-03-24 | 2 | #+date: 2022-03-24 |
| 3 | title = "Hardening a Public-Facing Home Server" | 3 | |
| 4 | description = "My personal reflection on the steps it took to get a home server ready to be publicly-accessible." | 4 | ** Post Updates |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: post-updates |
| 7 | 7 | :END: | |
| 8 | ## Post Updates | 8 | |
| 9 | 9 | #+begin_quote | |
| 10 | > After reviewing this post today (2022-10-04), I noticed quite a few gaps in my | 10 | After reviewing this post today (2022-10-04), I noticed quite a few gaps |
| 11 | > write-up and wanted to add a few things, even though this blog is really | 11 | in my write-up and wanted to add a few things, even though this blog is |
| 12 | > just a retrospective and knowledge dump for myself. | 12 | really just a retrospective and knowledge dump for myself. I left things |
| 13 | > I left things intact and simply crossed them out (~~like this~~) for | 13 | intact and simply crossed them out (+like this+) for posterity. |
| 14 | > posterity. | 14 | |
| 15 | 15 | #+end_quote | |
| 16 | ## Planning Data Flows & Security | 16 | |
| 17 | 17 | ** Planning Data Flows & Security | |
| 18 | ### My Personal Data Flow | 18 | :PROPERTIES: |
| 19 | 19 | :CUSTOM_ID: planning-data-flows-security | |
| 20 | ```txt | 20 | :END: |
| 21 | *** My Personal Data Flow | ||
| 22 | :PROPERTIES: | ||
| 23 | :CUSTOM_ID: my-personal-data-flow | ||
| 24 | :END: | ||
| 25 | #+begin_src txt | ||
| 21 | ┌───────┐ ┌─────────────────┐ | 26 | ┌───────┐ ┌─────────────────┐ |
| 22 | ┌──► VLAN1 ├───► Private Devices │ | 27 | ┌──► VLAN1 ├───► Private Devices │ |
| 23 | │ └───────┘ └─────────────────┘ | 28 | │ └───────┘ └─────────────────┘ |
| @@ -27,320 +32,355 @@ draft = false | |||
| 27 | │ ┌───────┐ ┌───────────────┐ | 32 | │ ┌───────┐ ┌───────────────┐ |
| 28 | └──► VLAN2 ├───► Public Server │ | 33 | └──► VLAN2 ├───► Public Server │ |
| 29 | └───────┘ └───────────────┘ | 34 | └───────┘ └───────────────┘ |
| 30 | ``` | 35 | #+end_src |
| 31 | |||
| 32 | ### Thought Process | ||
| 33 | 36 | ||
| 34 | To serve content from your home server and harden your security posture, you | 37 | *** Thought Process |
| 35 | have to think about the transport of data from `server` to `client`. | 38 | :PROPERTIES: |
| 39 | :CUSTOM_ID: thought-process | ||
| 40 | :END: | ||
| 41 | To serve content from your home server and harden your security posture, | ||
| 42 | you have to think about the transport of data from =server= to =client=. | ||
| 36 | 43 | ||
| 37 | Let's start with the actual server itself. Think about the following: | 44 | Let's start with the actual server itself. Think about the following: |
| 38 | 45 | ||
| 39 | - Do I have a firewall enabled? Do I need to update this to allow new ports or | 46 | - Do I have a firewall enabled? Do I need to update this to allow new |
| 40 | IPs? | 47 | ports or IPs? |
| 41 | - Do I have an IPS/IDS that may prevent outside traffic? | 48 | - Do I have an IPS/IDS that may prevent outside traffic? |
| 42 | - Do I have any other security software installed? | 49 | - Do I have any other security software installed? |
| 43 | - Are the services hosted inside Docker containers, behind a reverse proxy, or | 50 | - Are the services hosted inside Docker containers, behind a reverse |
| 44 | virtualized? If so, are they configured to allow outside traffic? | 51 | proxy, or virtualized? If so, are they configured to allow outside |
| 45 | 52 | traffic? | |
| 46 | Once the data leaves the server, where does it go? In my case, it goes to a | 53 | |
| 47 | managed switch. In this case, I asked the following: | 54 | Once the data leaves the server, where does it go? In my case, it goes |
| 48 | 55 | to a managed switch. In this case, I asked the following: | |
| 49 | - What configurations is the switch using? | 56 | |
| 50 | - Am I using VLANs? | 57 | - What configurations is the switch using? |
| 51 | - Yes, I am using 802.1Q VLANs. | 58 | - Am I using VLANs? |
| 52 | - Are the VLANs configured properly? | 59 | - Yes, I am using 802.1Q VLANs. |
| 53 | - Yes, as shown in the [Switch](#switch) section below, I have a separate | 60 | - Are the VLANs configured properly? |
| 54 | VLAN to allow outside traffic to and from the server alone. No other | 61 | - Yes, as shown in the [[#switch][Switch]] section below, I have a |
| 55 | devices, except for a service port, and in that VLAN. | 62 | separate VLAN to allow outside traffic to and from the server alone. |
| 56 | 63 | No other devices, except for a service port, and in that VLAN. | |
| 57 | At this point, the data has been processed through the switch. Where does it go | 64 | |
| 58 | next? In my case, it's pretty simple: it goes to the router/modem device. | 65 | At this point, the data has been processed through the switch. Where |
| 59 | 66 | does it go next? In my case, it's pretty simple: it goes to the | |
| 60 | - Does my ISP block any ports that I need? | 67 | router/modem device. |
| 61 | - This is an important step that a lot of people run into when | 68 | |
| 62 | self-hosting at home. | 69 | - Does my ISP block any ports that I need? |
| 63 | Use an online port-checker tool for your IP or call your ISP if you | 70 | - This is an important step that a lot of people run into when |
| 64 | think ports are blocked. | 71 | self-hosting at home. Use an online port-checker tool for your IP or |
| 65 | - Is there a router firewall? | 72 | call your ISP if you think ports are blocked. |
| 66 | - Yes, I checked that it's configured to allow the ports I need to run my | 73 | - Is there a router firewall? |
| 67 | services publicly. Common web servers and reverse proxies require ports | 74 | - Yes, I checked that it's configured to allow the ports I need to run |
| 68 | 80 and 443, but other services like media servers or games can require | 75 | my services publicly. Common web servers and reverse proxies require |
| 69 | unique ports, so be sure to check the documentation for your service(s). | 76 | ports 80 and 443, but other services like media servers or games can |
| 70 | - Are there any other settings affecting inbound/outbound traffic? | 77 | require unique ports, so be sure to check the documentation for your |
| 71 | - Schedules or access blocks | 78 | service(s). |
| 72 | - Static Routing | 79 | - Are there any other settings affecting inbound/outbound traffic? |
| 73 | - QoS | 80 | - Schedules or access blocks |
| 74 | - Port Forwarding | 81 | - Static Routing |
| 75 | - DMZ Hosting | 82 | - QoS |
| 76 | - Remote Management (this can sometimes mess with services that also | 83 | - Port Forwarding |
| 77 | require the use of ports 80 and 443) | 84 | - DMZ Hosting |
| 78 | 85 | - Remote Management (this can sometimes mess with services that also | |
| 79 | Once the data leaves my router, it goes to the upstream ISP and can be accessed | 86 | require the use of ports 80 and 443) |
| 80 | publicly. | 87 | |
| 81 | 88 | Once the data leaves my router, it goes to the upstream ISP and can be | |
| 82 | ## Server | 89 | accessed publicly. |
| 83 | 90 | ||
| 84 | ~~The services I run on my server are installed straight into the OS, without | 91 | ** Server |
| 85 | any use of Docker or VMs, so I don't need any extra application configuration to | 92 | :PROPERTIES: |
| 86 | make them accessible to the outside world.~~ | 93 | :CUSTOM_ID: server |
| 87 | 94 | :END: | |
| 88 | As of 2022-10-04, the paragraph above is no longer true as I now run a reverse | 95 | +The services I run on my server are installed straight into the OS, |
| 89 | proxy with Nginx and host many services inside Docker. However, it doesn't | 96 | without any use of Docker or VMs, so I don't need any extra application |
| 90 | change anything regarding this post as I still just need to open ports 80 & 443 | 97 | configuration to make them accessible to the outside world.+ |
| 91 | and create the necessary website configuration files. | 98 | |
| 92 | 99 | As of 2022-10-04, the paragraph above is no longer true as I now run a | |
| 93 | When creating new services - either installed directly on bare metal or within | 100 | reverse proxy with Nginx and host many services inside Docker. However, |
| 94 | something like Docker - I ensure that I read through the documentation | 101 | it doesn't change anything regarding this post as I still just need to |
| 95 | thoroughly to understand a few key things: | 102 | open ports 80 & 443 and create the necessary website configuration |
| 96 | - What network activities should this app perform (if any)? Using which ports | 103 | files. |
| 97 | and protocols? | 104 | |
| 98 | - Does this app require any commands/services to be run as `root`? | 105 | When creating new services - either installed directly on bare metal or |
| 99 | - Does this app log errors, authentication failures/successes, or anything | 106 | within something like Docker - I ensure that I read through the |
| 100 | else that would be useful for an investigation? | 107 | documentation thoroughly to understand a few key things: - What network |
| 101 | 108 | activities should this app perform (if any)? Using which ports and | |
| 102 | For extra security, I use limit all incoming connections to SSH connections | 109 | protocols? - Does this app require any commands/services to be run as |
| 103 | through my server firewall (`ufw`) and disable common SSH settings. After all of | 110 | =root=? - Does this app log errors, authentication failures/successes, |
| 104 | that, I use `fail2ban` as a preventative measure against brute-force login | 111 | or anything else that would be useful for an investigation? |
| 105 | attempts. | 112 | |
| 106 | 113 | For extra security, I use limit all incoming connections to SSH | |
| 107 | As another piece of security, you can randomize your SSH port to ensure that | 114 | connections through my server firewall (=ufw=) and disable common SSH |
| 108 | random scanners or attackers can't easily try to force their way into your | 115 | settings. After all of that, I use =fail2ban= as a preventative measure |
| 109 | network. For example, you can edit the port rules in your server to block all | 116 | against brute-force login attempts. |
| 110 | connection requests to port `22` but forward all remote connections from port | 117 | |
| 111 | `12345` to your server's port `22`. Then you just need to SSH to your network | 118 | As another piece of security, you can randomize your SSH port to ensure |
| 112 | via your randomized port. | 119 | that random scanners or attackers can't easily try to force their way |
| 113 | 120 | into your network. For example, you can edit the port rules in your | |
| 114 | ### `ufw` | 121 | server to block all connection requests to port =22= but forward all |
| 115 | 122 | remote connections from port =12345= to your server's port =22=. Then | |
| 116 | To see how to configure `ufw`, see my other post: | 123 | you just need to SSH to your network via your randomized port. |
| 117 | [Secure Your Network with the Uncomplicated Firewall](/blog/secure-your-network-with-the-uncomplicated-firewall/). | 124 | |
| 118 | 125 | *** =ufw= | |
| 119 | The general notion with an on-device firewall is that you want to deny all | 126 | :PROPERTIES: |
| 120 | incoming connections by default and then selectively open certain ports for | 127 | :CUSTOM_ID: ufw |
| 121 | services or users that you know need access. | 128 | :END: |
| 122 | 129 | To see how to configure =ufw=, see my other post: | |
| 123 | If you know that you will only be logging into this server from a certain set or | 130 | [[/blog/secure-your-network-with-the-uncomplicated-firewall/][Secure |
| 124 | list of IPs, you can always set the firewall to only allow connections to port | 131 | Your Network with the Uncomplicated Firewall]]. |
| 125 | 22 from those IPs. | 132 | |
| 133 | The general notion with an on-device firewall is that you want to deny | ||
| 134 | all incoming connections by default and then selectively open certain | ||
| 135 | ports for services or users that you know need access. | ||
| 136 | |||
| 137 | If you know that you will only be logging into this server from a | ||
| 138 | certain set or list of IPs, you can always set the firewall to only | ||
| 139 | allow connections to port 22 from those IPs. | ||
| 126 | 140 | ||
| 127 | For a quick start to only allow SSH connections to the server, use this: | 141 | For a quick start to only allow SSH connections to the server, use this: |
| 128 | 142 | ||
| 129 | ```sh | 143 | #+begin_src sh |
| 130 | sudo ufw default deny incoming | 144 | sudo ufw default deny incoming |
| 131 | sudo ufw default allow outgoing | 145 | sudo ufw default allow outgoing |
| 132 | sudo ufw allow 22 | 146 | sudo ufw allow 22 |
| 133 | sudo ufw enable | 147 | sudo ufw enable |
| 134 | ``` | 148 | #+end_src |
| 135 | 149 | ||
| 136 |  | 150 | #+caption: ufw |
| 137 | 151 | [[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png]] | |
| 138 | ### `ssh` | 152 | |
| 139 | 153 | *** =ssh= | |
| 140 | #### Using SSH Keys | 154 | :PROPERTIES: |
| 141 | 155 | :CUSTOM_ID: ssh | |
| 142 | First, make sure you have an SSH keypair generated on the device(s) that you'll | 156 | :END: |
| 143 | be using to log in to the server. If you don't have an SSH key, run this | 157 | **** Using SSH Keys |
| 144 | command: | 158 | :PROPERTIES: |
| 145 | 159 | :CUSTOM_ID: using-ssh-keys | |
| 146 | ```sh | 160 | :END: |
| 161 | First, make sure you have an SSH keypair generated on the device(s) that | ||
| 162 | you'll be using to log in to the server. If you don't have an SSH key, | ||
| 163 | run this command: | ||
| 164 | |||
| 165 | #+begin_src sh | ||
| 147 | ssh-keygen | 166 | ssh-keygen |
| 148 | ``` | 167 | #+end_src |
| 149 | 168 | ||
| 150 | Now that we have an SSH key, copy it to the server with the following command, | 169 | Now that we have an SSH key, copy it to the server with the following |
| 151 | which will ask for the user's password before accepting the key: | 170 | command, which will ask for the user's password before accepting the |
| 171 | key: | ||
| 152 | 172 | ||
| 153 | ```sh | 173 | #+begin_src sh |
| 154 | ssh-copy-id my_user@my_server | 174 | ssh-copy-id my_user@my_server |
| 155 | ``` | 175 | #+end_src |
| 156 | 176 | ||
| 157 | If you have multiple keys, you'll need to specify which to use. After it's | 177 | If you have multiple keys, you'll need to specify which to use. After |
| 158 | complete, `ssh` back into the server as that user and make sure it doesn't ask | 178 | it's complete, =ssh= back into the server as that user and make sure it |
| 159 | for a password. | 179 | doesn't ask for a password. |
| 160 | 180 | ||
| 161 | #### Disable Password & Root Authentication | 181 | **** Disable Password & Root Authentication |
| 182 | :PROPERTIES: | ||
| 183 | :CUSTOM_ID: disable-password-root-authentication | ||
| 184 | :END: | ||
| 185 | Now that we can access the server without a password, we will disable | ||
| 186 | password authentication and disable anyone from using =ssh= to login as | ||
| 187 | =root=. | ||
| 162 | 188 | ||
| 163 | Now that we can access the server without a password, we will disable password | 189 | To do this, open the =sshd_config= file: |
| 164 | authentication and disable anyone from using `ssh` to login as `root`. | ||
| 165 | 190 | ||
| 166 | To do this, open the `sshd_config` file: | 191 | #+begin_src sh |
| 167 | |||
| 168 | ```sh | ||
| 169 | sudo nano /etc/ssh/sshd_config | 192 | sudo nano /etc/ssh/sshd_config |
| 170 | ``` | 193 | #+end_src |
| 171 | 194 | ||
| 172 | You'll need to update the parameters to the values below. If one of these rules | 195 | You'll need to update the parameters to the values below. If one of |
| 173 | is commented-out or doesn't exist, create the rule at the bottom of the file. | 196 | these rules is commented-out or doesn't exist, create the rule at the |
| 197 | bottom of the file. | ||
| 174 | 198 | ||
| 175 | ```config | 199 | #+begin_src config |
| 176 | PermitRootLogin no | 200 | PermitRootLogin no |
| 177 | PasswordAuthentication no | 201 | PasswordAuthentication no |
| 178 | PubkeyAuthentication yes | 202 | PubkeyAuthentication yes |
| 179 | ``` | 203 | #+end_src |
| 180 | 204 | ||
| 181 | Finally, restart the `ssh` service: | 205 | Finally, restart the =ssh= service: |
| 182 | 206 | ||
| 183 | ```sh | 207 | #+begin_src sh |
| 184 | sudo systemctl restart sshd.service | 208 | sudo systemctl restart sshd.service |
| 185 | ``` | 209 | #+end_src |
| 186 | 210 | ||
| 187 | To test that everything’s working so far, open ANOTHER terminal and try logging | 211 | To test that everything's working so far, open ANOTHER terminal and try |
| 188 | in as `root` over SSH. It is very important that you keep your current SSH | 212 | logging in as =root= over SSH. It is very important that you keep your |
| 189 | session open and test with an additional session, or you will lock yourself out | 213 | current SSH session open and test with an additional session, or you |
| 190 | at some point and will need to use a recovery method (e.g., hooking monitor up | 214 | will lock yourself out at some point and will need to use a recovery |
| 191 | to home server) to get yourself back in. | 215 | method (e.g., hooking monitor up to home server) to get yourself back |
| 192 | 216 | in. | |
| 193 | #### Enable MFA for `ssh` | 217 | |
| 194 | 218 | **** Enable MFA for =ssh= | |
| 195 | This part is optional, but I highly recommend it. | 219 | :PROPERTIES: |
| 196 | So far, we've ensured that no one can log into our user on the server | 220 | :CUSTOM_ID: enable-mfa-for-ssh |
| 197 | without using our secret key, and we've ensured that no one can log in | 221 | :END: |
| 198 | remotely as `root`. | 222 | This part is optional, but I highly recommend it. So far, we've ensured |
| 199 | Next, you can enable MFA authentication for `ssh` connections. | 223 | that no one can log into our user on the server without using our secret |
| 200 | 224 | key, and we've ensured that no one can log in remotely as =root=. Next, | |
| 201 | This process involves editing a couple files and installing an MFA package, so I | 225 | you can enable MFA authentication for =ssh= connections. |
| 202 | will not include all the details in this post. To see how to configure MFA for | 226 | |
| 203 | `ssh`, see my other post: | 227 | This process involves editing a couple files and installing an MFA |
| 204 | [Enabling MFA for SSH](/blog/enable-totp-mfa-for-ssh/). | 228 | package, so I will not include all the details in this post. To see how |
| 205 | 229 | to configure MFA for =ssh=, see my other post: | |
| 206 |  | 230 | [[/blog/enable-totp-mfa-for-ssh/][Enabling MFA for SSH]]. |
| 207 | 231 | ||
| 208 | ### `fail2ban` | 232 | #+caption: SSH MFA |
| 209 | 233 | [[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png]] | |
| 210 | I haven't written a post on how I use `fail2ban`, but it's quite simple. I use | 234 | |
| 211 | the default `sshd` jail, but you can always create new jails for respective | 235 | *** =fail2ban= |
| 212 | applications or ports. For example, if you use Nginx as your web server, you can | 236 | :PROPERTIES: |
| 213 | use the `nginx-http-auth` jail. | 237 | :CUSTOM_ID: fail2ban |
| 238 | :END: | ||
| 239 | I haven't written a post on how I use =fail2ban=, but it's quite simple. | ||
| 240 | I use the default =sshd= jail, but you can always create new jails for | ||
| 241 | respective applications or ports. For example, if you use Nginx as your | ||
| 242 | web server, you can use the =nginx-http-auth= jail. | ||
| 214 | 243 | ||
| 215 | In order to get it up and running, use the following commands: | 244 | In order to get it up and running, use the following commands: |
| 216 | 245 | ||
| 217 | ```sh | 246 | #+begin_src sh |
| 218 | sudo apt install fail2ban | 247 | sudo apt install fail2ban |
| 219 | sudo fail2ban-client start sshd | 248 | sudo fail2ban-client start sshd |
| 220 | sudo fail2ban-client status sshd | 249 | sudo fail2ban-client status sshd |
| 221 | ``` | 250 | #+end_src |
| 222 | 251 | ||
| 223 | This should be used as a last-resort defense and shouldn't be a replacement for | 252 | This should be used as a last-resort defense and shouldn't be a |
| 224 | the security measures mentioned above. | 253 | replacement for the security measures mentioned above. |
| 225 | 254 | ||
| 226 |  | 255 | #+caption: fail2ban |
| 227 | 256 | [[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png]] | |
| 228 | ## Switch | 257 | |
| 229 | 258 | ** Switch | |
| 230 | Between the router and any local devices is my managed switch, which is used to | 259 | :PROPERTIES: |
| 231 | create VLANs. The example below shows how I would isolate the VLANs if I were | 260 | :CUSTOM_ID: switch |
| 232 | starting to host a single service at home. | 261 | :END: |
| 233 | 262 | Between the router and any local devices is my managed switch, which is | |
| 234 | ### 802.1Q VLAN Configuration | 263 | used to create VLANs. The example below shows how I would isolate the |
| 235 | 264 | VLANs if I were starting to host a single service at home. | |
| 236 | In this configuration, port 8 is the public server that needs to be accessed | 265 | |
| 237 | from the outside. Port 23 is my 'dedicated service port' for this server. In | 266 | *** 802.1Q VLAN Configuration |
| 238 | order to SSH to this server, I need to plug my laptop into port 23 or else I | 267 | :PROPERTIES: |
| 239 | cannot SSH. Otherwise, I'd need to hook up a monitor and keyboard directly to | 268 | :CUSTOM_ID: q-vlan-configuration |
| 240 | the server to manage it. | 269 | :END: |
| 270 | In this configuration, port 8 is the public server that needs to be | ||
| 271 | accessed from the outside. Port 23 is my 'dedicated service port' for | ||
| 272 | this server. In order to SSH to this server, I need to plug my laptop | ||
| 273 | into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a | ||
| 274 | monitor and keyboard directly to the server to manage it. | ||
| 241 | 275 | ||
| 242 | | VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports | | 276 | | VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports | |
| 243 | |:-------:|:---------:|:------------:|:------------:|:--------------:| | 277 | |---------+-----------+--------------+--------------+----------------| |
| 244 | | 1 | Default | 1-24 | | 1-24 | | 278 | | 1 | Default | 1-24 | | 1-24 | |
| 245 | | 2 | Server | 1,8,23 | | 1,8,23 | | 279 | | 2 | Server | 1,8,23 | | 1,8,23 | |
| 246 | 280 | ||
| 247 | ### 802.1Q VLAN PVID Setting | 281 | *** 802.1Q VLAN PVID Setting |
| 248 | 282 | :PROPERTIES: | |
| 249 | Once the VLAN is created, I simply add the `VLAN ID` of `2` as the `PVID` for | 283 | :CUSTOM_ID: q-vlan-pvid-setting |
| 250 | any related ports (in this case, see that ports `8` and `23` have a PVID of | 284 | :END: |
| 251 | `2`). | 285 | Once the VLAN is created, I simply add the =VLAN ID= of =2= as the |
| 286 | =PVID= for any related ports (in this case, see that ports =8= and =23= | ||
| 287 | have a PVID of =2=). | ||
| 252 | 288 | ||
| 253 | | Port | PVID | | 289 | | Port | PVID | |
| 254 | |:----:|:----:| | 290 | |------+------| |
| 255 | | 1 | 1 | | 291 | | 1 | 1 | |
| 256 | | 2 | 1 | | 292 | | 2 | 1 | |
| 257 | | 3 | 1 | | 293 | | 3 | 1 | |
| 258 | | 4 | 1 | | 294 | | 4 | 1 | |
| 259 | | 5 | 1 | | 295 | | 5 | 1 | |
| 260 | | 6 | 1 | | 296 | | 6 | 1 | |
| 261 | | 7 | 1 | | 297 | | 7 | 1 | |
| 262 | | 8 | 2 | | 298 | | 8 | 2 | |
| 263 | | 9 | 1 | | 299 | | 9 | 1 | |
| 264 | | 10 | 1 | | 300 | | 10 | 1 | |
| 265 | | 11 | 1 | | 301 | | 11 | 1 | |
| 266 | | 12 | 1 | | 302 | | 12 | 1 | |
| 267 | | 13 | 1 | | 303 | | 13 | 1 | |
| 268 | | 14 | 1 | | 304 | | 14 | 1 | |
| 269 | | 15 | 1 | | 305 | | 15 | 1 | |
| 270 | | 16 | 1 | | 306 | | 16 | 1 | |
| 271 | | 17 | 1 | | 307 | | 17 | 1 | |
| 272 | | 18 | 1 | | 308 | | 18 | 1 | |
| 273 | | 19 | 1 | | 309 | | 19 | 1 | |
| 274 | | 20 | 1 | | 310 | | 20 | 1 | |
| 275 | | 21 | 1 | | 311 | | 21 | 1 | |
| 276 | | 22 | 1 | | 312 | | 22 | 1 | |
| 277 | | 23 | 2 | | 313 | | 23 | 2 | |
| 278 | | 24 | 1 | | 314 | | 24 | 1 | |
| 279 | 315 | ||
| 280 | ## Router | 316 | ** Router |
| 281 | 317 | :PROPERTIES: | |
| 282 | On my router, the configuration was as easy as opening the firewall settings and | 318 | :CUSTOM_ID: router |
| 283 | unblocking the ports I needed for my services (e.g., HTTP/S, Plex, SSH, MySQL, | 319 | :END: |
| 284 | etc.). | 320 | On my router, the configuration was as easy as opening the firewall |
| 285 | 321 | settings and unblocking the ports I needed for my services (e.g., | |
| 286 | ~~Since I'm relying on an ISP-provided modem/router combo for now (not by | 322 | HTTP/S, Plex, SSH, MySQL, etc.). |
| 287 | choice), I do not use any other advanced settings on my router that would | 323 | |
| 288 | inhibit any valid traffic to these services.~~ | 324 | +Since I'm relying on an ISP-provided modem/router combo for now (not by |
| 289 | 325 | choice), I do not use any other advanced settings on my router that | |
| 290 | The paragraph above regarding the ISP-owned router is no longer accurate as I | 326 | would inhibit any valid traffic to these services.+ |
| 291 | now use the Ubiquiti Unifi Dream Machine Pro as my router. | 327 | |
| 292 | Within this router, I enabled port forwarding/firewall rules, segregate the | 328 | The paragraph above regarding the ISP-owned router is no longer accurate |
| 293 | network based on the device, and enable traffic restrictions (e.g., silently | 329 | as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within |
| 294 | drop traffic from certain countries and threat categories). | 330 | this router, I enabled port forwarding/firewall rules, segregate the |
| 295 | 331 | network based on the device, and enable traffic restrictions (e.g., | |
| 296 | If you have the option with your ISP, I recommend using a personal router with | 332 | silently drop traffic from certain countries and threat categories). |
| 297 | software that you are familiar with so that you can explore all the options | 333 | |
| 298 | available to you. | 334 | If you have the option with your ISP, I recommend using a personal |
| 299 | 335 | router with software that you are familiar with so that you can explore | |
| 300 | ## Physical Security | 336 | all the options available to you. |
| 301 | 337 | ||
| 302 | One large piece of self-hosting that people generally don't discuss online is | 338 | ** Physical Security |
| 303 | physical security. However, physical security is very important for everyone who | 339 | :PROPERTIES: |
| 304 | hosts a server like this. Exactly *how* important it is depends on the server | 340 | :CUSTOM_ID: physical-security |
| 305 | use/purpose. | 341 | :END: |
| 306 | 342 | One large piece of self-hosting that people generally don't discuss | |
| 307 | If you self-host customer applications that hold protected data (HIPAA, GDPR, | 343 | online is physical security. However, physical security is very |
| 308 | COPPA, etc.), then physical security is extremely important and cannot be | 344 | important for everyone who hosts a server like this. Exactly /how/ |
| 309 | ignored. If you simply host a blog and some hobby sites, then it's a relatively | 345 | important it is depends on the server use/purpose. |
| 310 | minor consideration, but one you still need to think about. | 346 | |
| 311 | 347 | If you self-host customer applications that hold protected data (HIPAA, | |
| 312 | ### Location | 348 | GDPR, COPPA, etc.), then physical security is extremely important and |
| 313 | 349 | cannot be ignored. If you simply host a blog and some hobby sites, then | |
| 314 | The first consideration is quite simple: location. | 350 | it's a relatively minor consideration, but one you still need to think |
| 315 | - Is the server within a property you own or housed on someone else's property? | 351 | about. |
| 316 | - Is it nearby (in your house, in your work office, in your neighbor's garage, | 352 | |
| 317 | in a storage unit, etc.)? | 353 | *** Location |
| 318 | - Do you have 24/7 access to the server? | 354 | :PROPERTIES: |
| 319 | - Are there climate considerations, such as humidity, fires, tornadoes, | 355 | :CUSTOM_ID: location |
| 320 | monsoons? | 356 | :END: |
| 321 | - Do you have emergency equipment nearby in case of emergency? | 357 | The first consideration is quite simple: location. - Is the server |
| 322 | 358 | within a property you own or housed on someone else's property? - Is it | |
| 323 | ### Hardware Ownership | 359 | nearby (in your house, in your work office, in your neighbor's garage, |
| 324 | 360 | in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are | |
| 325 | Secondly, consider the hardware itself: | 361 | there climate considerations, such as humidity, fires, tornadoes, |
| 326 | - Do you own the server in its entirety? | 362 | monsoons? - Do you have emergency equipment nearby in case of emergency? |
| 327 | - Are any other users able to access the server, even if your data/space is | 363 | |
| 328 | segregated? | 364 | *** Hardware Ownership |
| 329 | - If you're utilizing a third party, do they have any documentation to show | 365 | :PROPERTIES: |
| 330 | responsibility? This could be a SOC 1/2/3 report, ISO compliance report, | 366 | :CUSTOM_ID: hardware-ownership |
| 331 | internal security/safety documentation. | 367 | :END: |
| 332 | 368 | Secondly, consider the hardware itself: - Do you own the server in its | |
| 333 | ### Physical Controls | 369 | entirety? - Are any other users able to access the server, even if your |
| 334 | 370 | data/space is segregated? - If you're utilizing a third party, do they | |
| 335 | Regardless of who owns the hardware, ensure that there are adequate safeguards | 371 | have any documentation to show responsibility? This could be a SOC 1/2/3 |
| 336 | in place, if necessary. These usually don't apply to small home servers and are | 372 | report, ISO compliance report, internal security/safety documentation. |
| 337 | usually covered already if you're utilizing a third party. | 373 | |
| 338 | 374 | *** Physical Controls | |
| 339 | These can include: | 375 | :PROPERTIES: |
| 340 | - Server bezel locks | 376 | :CUSTOM_ID: physical-controls |
| 341 | - Server room locks - physical, digital, or biometric authentication | 377 | :END: |
| 342 | - Security cameras | 378 | Regardless of who owns the hardware, ensure that there are adequate |
| 343 | - Raised floors/lowered ceilings with proper guards/gates in-place within the | 379 | safeguards in place, if necessary. These usually don't apply to small |
| 344 | floors or ceilings | 380 | home servers and are usually covered already if you're utilizing a third |
| 345 | - Security personnel | 381 | party. |
| 346 | - Log sheets and/or guest badges | 382 | |
| 383 | These can include: - Server bezel locks - Server room locks - physical, | ||
| 384 | digital, or biometric authentication - Security cameras - Raised | ||
| 385 | floors/lowered ceilings with proper guards/gates in-place within the | ||
| 386 | floors or ceilings - Security personnel - Log sheets and/or guest badges | ||
blog/2022-03-26-ssh-mfa.org +115 −103
| @@ -1,82 +1,88 @@ | |||
| 1 | +++ | 1 | #+title: Enable TOTP MFA for SSH |
| 2 | date = 2022-03-26 | 2 | #+date: 2022-03-26 |
| 3 | title = "Enable TOTP MFA for SSH" | 3 | |
| 4 | description = "Harden your SSH target with user-enabled MFA options, such as TOTP." | 4 | ** Why Do I Need MFA for SSH? |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: why-do-i-need-mfa-for-ssh |
| 7 | 7 | :END: | |
| 8 | ## Why Do I Need MFA for SSH? | 8 | If you are a sysadmin of a server anywhere (that includes at home!), you |
| 9 | 9 | may want an added layer of protection against intruders. This is not a | |
| 10 | If you are a sysadmin of a server anywhere (that includes at home!), you may | 10 | replacement for other security measures, such as: |
| 11 | want an added layer of protection against intruders. This is not a replacement | 11 | |
| 12 | for other security measures, such as: | 12 | - Disable root SSH |
| 13 | 13 | - Disable SSH password authentication | |
| 14 | - Disable root SSH | 14 | - Allow only certain users to login via SSH |
| 15 | - Disable SSH password authentication | 15 | - Allow SSH only from certain IPs |
| 16 | - Allow only certain users to login via SSH | 16 | |
| 17 | - Allow SSH only from certain IPs | 17 | However, MFA can be added as an additional security measure to ensure |
| 18 | 18 | that your server is protected. This is especially important if you need | |
| 19 | However, MFA can be added as an additional security measure to ensure that your | 19 | to allow password authentication for SSH. |
| 20 | server is protected. This is especially important if you need to allow password | ||
| 21 | authentication for SSH. | ||
| 22 | 20 | ||
| 23 | For more guidance on server security measures, see my other post: | 21 | For more guidance on server security measures, see my other post: |
| 24 | [Hardening a Public-Facing Home Server](/blog/hardening-a-public-facing-home-server/). | 22 | [[/blog/hardening-a-public-facing-home-server/][Hardening a |
| 25 | 23 | Public-Facing Home Server]]. | |
| 26 | ## Install MFA PAM Module | 24 | |
| 27 | 25 | ** Install MFA PAM Module | |
| 28 | PAM, which stands for Pluggable Authentication Module, is an authentication | 26 | :PROPERTIES: |
| 29 | infrastructure used on Linux systems to authenticate a user. In order to use | 27 | :CUSTOM_ID: install-mfa-pam-module |
| 30 | this technology, let's install the `libpam-google-authenticator` package: | 28 | :END: |
| 31 | 29 | PAM, which stands for Pluggable Authentication Module, is an | |
| 32 | ```sh | 30 | authentication infrastructure used on Linux systems to authenticate a |
| 31 | user. In order to use this technology, let's install the | ||
| 32 | =libpam-google-authenticator= package: | ||
| 33 | |||
| 34 | #+begin_src sh | ||
| 33 | sudo apt-get update | 35 | sudo apt-get update |
| 34 | ``` | 36 | #+end_src |
| 35 | 37 | ||
| 36 | ```sh | 38 | #+begin_src sh |
| 37 | sudo apt-get install libpam-google-authenticator | 39 | sudo apt-get install libpam-google-authenticator |
| 38 | ``` | 40 | #+end_src |
| 39 | 41 | ||
| 40 | ## Initialize the PAM Module | 42 | ** Initialize the PAM Module |
| 41 | 43 | :PROPERTIES: | |
| 42 | ### Interactive Method | 44 | :CUSTOM_ID: initialize-the-pam-module |
| 43 | 45 | :END: | |
| 44 | Once the package is installed, initialize it and following the interactive | 46 | *** Interactive Method |
| 45 | prompts to generate your OTP or TOTP: | 47 | :PROPERTIES: |
| 46 | 48 | :CUSTOM_ID: interactive-method | |
| 47 | ```sh | 49 | :END: |
| 50 | Once the package is installed, initialize it and following the | ||
| 51 | interactive prompts to generate your OTP or TOTP: | ||
| 52 | |||
| 53 | #+begin_src sh | ||
| 48 | google-authenticator | 54 | google-authenticator |
| 49 | ``` | 55 | #+end_src |
| 50 | 56 | ||
| 51 | If you are not sure how to answer, read the prompts carefully and think about | 57 | If you are not sure how to answer, read the prompts carefully and think |
| 52 | having to how each situation would affect your normal login attempts. If you are | 58 | about having to how each situation would affect your normal login |
| 53 | still not sure, use my default responses below. | 59 | attempts. If you are still not sure, use my default responses below. |
| 54 | 60 | ||
| 55 | ```txt | 61 | #+begin_src txt |
| 56 | OUTPUT | 62 | OUTPUT |
| 57 | 63 | ||
| 58 | Do you want authentication tokens to be time-based (y/n) y | 64 | Do you want authentication tokens to be time-based (y/n) y |
| 59 | ``` | 65 | #+end_src |
| 60 | 66 | ||
| 61 | At this point, use an authenticator app somewhere one of your devices to scan | 67 | At this point, use an authenticator app somewhere one of your devices to |
| 62 | the QR code. Any future login attempts after our upcoming configuration changes | 68 | scan the QR code. Any future login attempts after our upcoming |
| 63 | will require that TOTP. | 69 | configuration changes will require that TOTP. |
| 64 | 70 | ||
| 65 | ```txt | 71 | #+begin_src txt |
| 66 | OUTPUT | 72 | OUTPUT |
| 67 | 73 | ||
| 68 | Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y | 74 | Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y |
| 69 | ``` | 75 | #+end_src |
| 70 | 76 | ||
| 71 | ```txt | 77 | #+begin_src txt |
| 72 | OUTPUT | 78 | OUTPUT |
| 73 | 79 | ||
| 74 | Do you want to disallow multiple uses of the same authentication | 80 | Do you want to disallow multiple uses of the same authentication |
| 75 | token? This restricts you to one login about every 30s, but it increases | 81 | token? This restricts you to one login about every 30s, but it increases |
| 76 | your chances to notice or even prevent man-in-the-middle attacks (y/n) y | 82 | your chances to notice or even prevent man-in-the-middle attacks (y/n) y |
| 77 | ``` | 83 | #+end_src |
| 78 | 84 | ||
| 79 | ```txt | 85 | #+begin_src txt |
| 80 | OUTPUT | 86 | OUTPUT |
| 81 | 87 | ||
| 82 | By default, a new token is generated every 30 seconds by the mobile app. | 88 | By default, a new token is generated every 30 seconds by the mobile app. |
| @@ -89,30 +95,32 @@ code, the next code) to 17 permitted codes (the 8 previous codes, the current | |||
| 89 | code, and the 8 next codes). This will permit for a time skew of up to 4 minutes | 95 | code, and the 8 next codes). This will permit for a time skew of up to 4 minutes |
| 90 | between client and server. | 96 | between client and server. |
| 91 | Do you want to do so? (y/n) n | 97 | Do you want to do so? (y/n) n |
| 92 | ``` | 98 | #+end_src |
| 93 | 99 | ||
| 94 | ```txt | 100 | #+begin_src txt |
| 95 | OUTPUT | 101 | OUTPUT |
| 96 | 102 | ||
| 97 | If the computer that you are logging into isn't hardened against brute-force | 103 | If the computer that you are logging into isn't hardened against brute-force |
| 98 | login attempts, you can enable rate-limiting for the authentication module. | 104 | login attempts, you can enable rate-limiting for the authentication module. |
| 99 | By default, this limits attackers to no more than 3 login attempts every 30s. | 105 | By default, this limits attackers to no more than 3 login attempts every 30s. |
| 100 | Do you want to enable rate-limiting? (y/n) y | 106 | Do you want to enable rate-limiting? (y/n) y |
| 101 | ``` | 107 | #+end_src |
| 102 | 108 | ||
| 103 | ### Non-Interactive Method | 109 | *** Non-Interactive Method |
| 110 | :PROPERTIES: | ||
| 111 | :CUSTOM_ID: non-interactive-method | ||
| 112 | :END: | ||
| 113 | If you need to do this quickly, know your responses to the prompts, or | ||
| 114 | are setting this up for numerous users, the non-interactive method can | ||
| 115 | be much faster: | ||
| 104 | 116 | ||
| 105 | If you need to do this quickly, know your responses to the prompts, or are | 117 | #+begin_src sh |
| 106 | setting this up for numerous users, the non-interactive method can be much | ||
| 107 | faster: | ||
| 108 | |||
| 109 | ```sh | ||
| 110 | google-authenticator -t -d -f -r 3 -R 30 -w 3 | 118 | google-authenticator -t -d -f -r 3 -R 30 -w 3 |
| 111 | ``` | 119 | #+end_src |
| 112 | 120 | ||
| 113 | The options referenced above are as follows: | 121 | The options referenced above are as follows: |
| 114 | 122 | ||
| 115 | ```txt | 123 | #+begin_src txt |
| 116 | google-authenticator [<options>] | 124 | google-authenticator [<options>] |
| 117 | -h, --help Print this message | 125 | -h, --help Print this message |
| 118 | -c, --counter-based Set up counter-based (HOTP) verification | 126 | -c, --counter-based Set up counter-based (HOTP) verification |
| @@ -132,65 +140,69 @@ google-authenticator [<options>] | |||
| 132 | -w, --window-size=W Set window of concurrently valid codes | 140 | -w, --window-size=W Set window of concurrently valid codes |
| 133 | -W, --minimal-window Disable window of concurrently valid codes | 141 | -W, --minimal-window Disable window of concurrently valid codes |
| 134 | -e, --emergency-codes=N Number of emergency codes to generate | 142 | -e, --emergency-codes=N Number of emergency codes to generate |
| 135 | ``` | 143 | #+end_src |
| 136 | 144 | ||
| 137 | This fully configures the authenticator, saves it to a file, and then outputs | 145 | This fully configures the authenticator, saves it to a file, and then |
| 138 | the secret key, QR code, and recovery codes. (If you add the flag `-q`, then | 146 | outputs the secret key, QR code, and recovery codes. (If you add the |
| 139 | there won’t be any output). If you use this command in an automated fashion, | 147 | flag =-q=, then there won't be any output). If you use this command in |
| 140 | make sure your script captures the secret key and/or recovery codes and makes | 148 | an automated fashion, make sure your script captures the secret key |
| 141 | them available to the user. | 149 | and/or recovery codes and makes them available to the user. |
| 142 | 150 | ||
| 143 | ## PAM Configuration Settings | 151 | ** PAM Configuration Settings |
| 144 | 152 | :PROPERTIES: | |
| 145 | Once you've enabled the T/OTP and have it saved to an MFA app on your phone or | 153 | :CUSTOM_ID: pam-configuration-settings |
| 146 | other device, open the PAM `sshd` file: | 154 | :END: |
| 147 | 155 | Once you've enabled the T/OTP and have it saved to an MFA app on your | |
| 148 | ```sh | 156 | phone or other device, open the PAM =sshd= file: |
| 157 | |||
| 158 | #+begin_src sh | ||
| 149 | sudo nano /etc/pam.d/sshd | 159 | sudo nano /etc/pam.d/sshd |
| 150 | ``` | 160 | #+end_src |
| 151 | 161 | ||
| 152 | You need to do two things in this file. First, add the following lines to the | 162 | You need to do two things in this file. First, add the following lines |
| 153 | bottom of the file: | 163 | to the bottom of the file: |
| 154 | 164 | ||
| 155 | ```config | 165 | #+begin_src config |
| 156 | auth required pam_google_authenticator.so nullok | 166 | auth required pam_google_authenticator.so nullok |
| 157 | auth required pam_permit.so | 167 | auth required pam_permit.so |
| 158 | ``` | 168 | #+end_src |
| 159 | 169 | ||
| 160 | Second, comment-out the following line near the top of the file. | 170 | Second, comment-out the following line near the top of the file. |
| 161 | 171 | ||
| 162 | If you leave this line uncommented, every SSH login attempt will ask for the | 172 | If you leave this line uncommented, every SSH login attempt will ask for |
| 163 | following three authentication factors: | 173 | the following three authentication factors: |
| 164 | 174 | ||
| 165 | 1. Publickey | 175 | 1. Publickey |
| 166 | 2. Password | 176 | 2. Password |
| 167 | 3. T/OTP code | 177 | 3. T/OTP code |
| 168 | 178 | ||
| 169 | ```config | 179 | #+begin_src config |
| 170 | #@include common-auth | 180 | #@include common-auth |
| 171 | ``` | 181 | #+end_src |
| 172 | |||
| 173 | ## SSH Configuration Settings | ||
| 174 | 182 | ||
| 175 | Finally, edit the `sshd_config` file again: | 183 | ** SSH Configuration Settings |
| 184 | :PROPERTIES: | ||
| 185 | :CUSTOM_ID: ssh-configuration-settings | ||
| 186 | :END: | ||
| 187 | Finally, edit the =sshd_config= file again: | ||
| 176 | 188 | ||
| 177 | ```sh | 189 | #+begin_src sh |
| 178 | sudo nano /etc/ssh/sshd_config | 190 | sudo nano /etc/ssh/sshd_config |
| 179 | ``` | 191 | #+end_src |
| 180 | 192 | ||
| 181 | You'll need to change `ChallengeResponseAuthentication` to yes and add the | 193 | You'll need to change =ChallengeResponseAuthentication= to yes and add |
| 182 | `AuthenticationMethods` line to the bottom of the file. | 194 | the =AuthenticationMethods= line to the bottom of the file. |
| 183 | 195 | ||
| 184 | ```config | 196 | #+begin_src config |
| 185 | ChallengeResponseAuthentication yes | 197 | ChallengeResponseAuthentication yes |
| 186 | AuthenticationMethods publickey,password publickey,keyboard-interactive | 198 | AuthenticationMethods publickey,password publickey,keyboard-interactive |
| 187 | ``` | 199 | #+end_src |
| 188 | 200 | ||
| 189 | Finally, restart the `ssh` service: | 201 | Finally, restart the =ssh= service: |
| 190 | 202 | ||
| 191 | ```sh | 203 | #+begin_src sh |
| 192 | sudo systemctl restart sshd.service | 204 | sudo systemctl restart sshd.service |
| 193 | ``` | 205 | #+end_src |
| 194 | 206 | ||
| 195 | The next time you log in, you should be greeted with a verification code | 207 | The next time you log in, you should be greeted with a verification code |
| 196 | request! | 208 | request! |
blog/2022-04-02-nginx-reverse-proxy.org +163 −142
| @@ -1,42 +1,43 @@ | |||
| 1 | +++ | 1 | #+title: Set-Up a Reverse Proxy with Nginx |
| 2 | date = 2022-04-02 | 2 | #+date: 2022-04-02 |
| 3 | title = "Set-Up a Reverse Proxy with Nginx" | 3 | |
| 4 | description = "Learn how to set-up a reverse proxy server with Nginx on Ubuntu." | 4 | ** What is a Reverse Proxy? |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: what-is-a-reverse-proxy |
| 7 | 7 | :END: | |
| 8 | ## What is a Reverse Proxy? | 8 | A reverse proxy is a server that is placed between local servers or |
| 9 | 9 | services and clients/users (e.g., the internet). The reverse proxy | |
| 10 | A reverse proxy is a server that is placed between local servers or services and | 10 | intercepts all requests from clients at the network edge and uses its |
| 11 | clients/users (e.g., the internet). The reverse proxy intercepts all requests | 11 | configuration files to determine where each request should be sent. |
| 12 | from clients at the network edge and uses its configuration files to determine | 12 | |
| 13 | where each request should be sent. | 13 | *** A Brief Example |
| 14 | 14 | :PROPERTIES: | |
| 15 | ### A Brief Example | 15 | :CUSTOM_ID: a-brief-example |
| 16 | 16 | :END: | |
| 17 | For example, let's say that I run three servers in my home: | 17 | For example, let's say that I run three servers in my home: |
| 18 | 18 | ||
| 19 | - Server_01 (`example.com`) | 19 | - Server_01 (=example.com=) |
| 20 | - Server_02 (`service01.example.com`) | 20 | - Server_02 (=service01.example.com=) |
| 21 | - Server_03 (`service02.example.com`) | 21 | - Server_03 (=service02.example.com=) |
| 22 | 22 | ||
| 23 | I also run a reverse proxy in my home that intercepts all public traffic: | 23 | I also run a reverse proxy in my home that intercepts all public |
| 24 | traffic: | ||
| 24 | 25 | ||
| 25 | - Reverse Proxy | 26 | - Reverse Proxy |
| 26 | 27 | ||
| 27 | Assume that I have a domain name (`example.com`) that allows clients to request | 28 | Assume that I have a domain name (=example.com=) that allows clients to |
| 28 | websites or services from my home servers. | 29 | request websites or services from my home servers. |
| 29 | 30 | ||
| 30 | In this case, the reverse proxy will intercept all traffic from `example.com` | 31 | In this case, the reverse proxy will intercept all traffic from |
| 31 | that enters my network and determine if the client is requesting valid data, | 32 | =example.com= that enters my network and determine if the client is |
| 32 | based on my configuration. | 33 | requesting valid data, based on my configuration. |
| 33 | 34 | ||
| 34 | If the user is requesting `example.com` and my configuration files say that | 35 | If the user is requesting =example.com= and my configuration files say |
| 35 | Server_01 holds that data, Nginx will send the user to Server_01. If I were to | 36 | that Server_01 holds that data, Nginx will send the user to Server_01. |
| 36 | change the configuration so that `example.com` is routed to Server_02, that same | 37 | If I were to change the configuration so that =example.com= is routed to |
| 37 | user would be sent to Server_02 instead. | 38 | Server_02, that same user would be sent to Server_02 instead. |
| 38 | 39 | ||
| 39 | ```txt | 40 | #+begin_src txt |
| 40 | ┌──────┐ ┌───────────┐ | 41 | ┌──────┐ ┌───────────┐ |
| 41 | │ User │─┐ ┌──► Server_01 │ | 42 | │ User │─┐ ┌──► Server_01 │ |
| 42 | └──────┘ │ │ └───────────┘ | 43 | └──────┘ │ │ └───────────┘ |
| @@ -46,133 +47,151 @@ user would be sent to Server_02 instead. | |||
| 46 | ┌──────┐ │ │ ┌───────────┐ | 47 | ┌──────┐ │ │ ┌───────────┐ |
| 47 | │ User │─┘ └──► Server_03 │ | 48 | │ User │─┘ └──► Server_03 │ |
| 48 | └──────┘ └───────────┘ | 49 | └──────┘ └───────────┘ |
| 49 | ``` | 50 | #+end_src |
| 50 | 51 | ||
| 51 | ## Reverse Proxy Options | 52 | ** Reverse Proxy Options |
| 52 | 53 | :PROPERTIES: | |
| 53 | There are a lot of options when it comes to reverse proxy servers, so I'm just | 54 | :CUSTOM_ID: reverse-proxy-options |
| 54 | going to list a few of the options I've heard recommended over the last few | 55 | :END: |
| 55 | years: | 56 | There are a lot of options when it comes to reverse proxy servers, so |
| 56 | 57 | I'm just going to list a few of the options I've heard recommended over | |
| 57 | - [Nginx](https://nginx.com) | 58 | the last few years: |
| 58 | - [Caddy](https://caddyserver.com) | 59 | |
| 59 | - [Traefik](https://traefik.io/) | 60 | - [[https://nginx.com][Nginx]] |
| 60 | - [HAProxy](https://www.haproxy.org/) | 61 | - [[https://caddyserver.com][Caddy]] |
| 61 | - [Squid](https://ubuntu.com/server/docs/proxy-servers-squid) | 62 | - [[https://traefik.io/][Traefik]] |
| 62 | 63 | - [[https://www.haproxy.org/][HAProxy]] | |
| 63 | In this post, we will be using Nginx as our reverse proxy, running on Ubuntu | 64 | - [[https://ubuntu.com/server/docs/proxy-servers-squid][Squid]] |
| 64 | Server 20.04.4 LTS. | 65 | |
| 65 | 66 | In this post, we will be using Nginx as our reverse proxy, running on | |
| 66 | ## Nginx Reverse Proxy Example | 67 | Ubuntu Server 20.04.4 LTS. |
| 67 | 68 | ||
| 68 | ### Local Applications | 69 | ** Nginx Reverse Proxy Example |
| 69 | 70 | :PROPERTIES: | |
| 70 | You may be like me and have a lot of applications running on your local network | 71 | :CUSTOM_ID: nginx-reverse-proxy-example |
| 71 | that you'd like to expose publicly with a domain. | 72 | :END: |
| 72 | 73 | *** Local Applications | |
| 73 | In my case, I have services running in multiple Docker containers within a | 74 | :PROPERTIES: |
| 74 | single server and want a way to visit those services from anywhere with a URL. | 75 | :CUSTOM_ID: local-applications |
| 75 | For example, on my local network, [Dashy](https://dashy.to) runs through port | 76 | :END: |
| 76 | 4000 (`localhost:4000`) and | 77 | You may be like me and have a lot of applications running on your local |
| 77 | [Uptime Kuma](https://github.com/louislam/uptime-kuma) runs through port 3001 | 78 | network that you'd like to expose publicly with a domain. |
| 78 | (`localhost:3001`). | 79 | |
| 80 | In my case, I have services running in multiple Docker containers within | ||
| 81 | a single server and want a way to visit those services from anywhere | ||
| 82 | with a URL. For example, on my local network, | ||
| 83 | [[https://dashy.to][Dashy]] runs through port 4000 (=localhost:4000=) | ||
| 84 | and [[https://github.com/louislam/uptime-kuma][Uptime Kuma]] runs | ||
| 85 | through port 3001 (=localhost:3001=). | ||
| 79 | 86 | ||
| 80 | In order to expose these services to the public, I will need to do the | 87 | In order to expose these services to the public, I will need to do the |
| 81 | following: | 88 | following: |
| 82 | 89 | ||
| 83 | 1. Set up DNS records for a domain or subdomain (one per service) to point | 90 | 1. Set up DNS records for a domain or subdomain (one per service) to |
| 84 | toward the IP address of the server. | 91 | point toward the IP address of the server. |
| 85 | 2. Open up the server network's HTTP and HTTPS ports (80 & 443) so that the | 92 | 2. Open up the server network's HTTP and HTTPS ports (80 & 443) so that |
| 86 | reverse proxy can accept traffic and determine where to send it. | 93 | the reverse proxy can accept traffic and determine where to send it. |
| 87 | 3. Install the reverse proxy software. | 94 | 3. Install the reverse proxy software. |
| 88 | 4. Configure the reverse proxy to recognize which service should get traffic | 95 | 4. Configure the reverse proxy to recognize which service should get |
| 89 | from any of the domains or subdomains. | 96 | traffic from any of the domains or subdomains. |
| 90 | 97 | ||
| 91 | ### Step 1: DNS Configuration | 98 | *** Step 1: DNS Configuration |
| 99 | :PROPERTIES: | ||
| 100 | :CUSTOM_ID: step-1-dns-configuration | ||
| 101 | :END: | ||
| 102 | To start, update your DNS configuration so that you have an =A= record | ||
| 103 | for each domain or subdomain. | ||
| 92 | 104 | ||
| 93 | To start, update your DNS configuration so that you have an `A` record for each | 105 | The =A= records should point toward the public IP address of the server. |
| 94 | domain or subdomain. | 106 | If you don't know the public IP address, log in to the server and run |
| 107 | the following command: | ||
| 95 | 108 | ||
| 96 | The `A` records should point toward the public IP address of the server. If you | 109 | #+begin_src sh |
| 97 | don't know the public IP address, log in to the server and run the following | ||
| 98 | command: | ||
| 99 | |||
| 100 | ```sh | ||
| 101 | curl ifconfig.co | 110 | curl ifconfig.co |
| 102 | ``` | 111 | #+end_src |
| 103 | 112 | ||
| 104 | In the DNS example below, `xxx.xxx.xxx.xxx` is the public IP address of the | 113 | In the DNS example below, =xxx.xxx.xxx.xxx= is the public IP address of |
| 105 | server. | 114 | the server. |
| 106 | 115 | ||
| 107 | ```config | 116 | #+begin_src config |
| 108 | example.com A xxx.xxx.xxx.xxx | 117 | example.com A xxx.xxx.xxx.xxx |
| 109 | uptime.example.com A xxx.xxx.xxx.xxx | 118 | uptime.example.com A xxx.xxx.xxx.xxx |
| 110 | dashy.example.com A xxx.xxx.xxx.xxx | 119 | dashy.example.com A xxx.xxx.xxx.xxx |
| 111 | www CNAME example.com | 120 | www CNAME example.com |
| 112 | ``` | 121 | #+end_src |
| 113 | 122 | ||
| 114 | Finally, ensure the DNS has propagated correctly with | 123 | Finally, ensure the DNS has propagated correctly with |
| 115 | [DNS Checker](https://dnschecker.org) by entering your domains or subdomains in | 124 | [[https://dnschecker.org][DNS Checker]] by entering your domains or |
| 116 | the search box and ensuring the results are showing the correct IP address. | 125 | subdomains in the search box and ensuring the results are showing the |
| 117 | 126 | correct IP address. | |
| 118 | ### Step 2: Open Network Ports | 127 | |
| 119 | 128 | *** Step 2: Open Network Ports | |
| 120 | This step will be different depending on which router you have in your home. If | 129 | :PROPERTIES: |
| 121 | you're not sure, try to visit [192.168.1.1](http://192.168.1.1) in your browser. | 130 | :CUSTOM_ID: step-2-open-network-ports |
| 122 | Login credentials are usually written on a sticker somewhere on your | 131 | :END: |
| 123 | modem/router. | 132 | This step will be different depending on which router you have in your |
| 124 | 133 | home. If you're not sure, try to visit | |
| 125 | Once you're able to log in to your router, find the Port Forwarding settings. | 134 | [[http://192.168.1.1][192.168.1.1]] in your browser. Login credentials |
| 126 | You will need to forward ports `80` and `443` to whichever machine is | 135 | are usually written on a sticker somewhere on your modem/router. |
| 127 | running the reverse proxy. | 136 | |
| 128 | 137 | Once you're able to log in to your router, find the Port Forwarding | |
| 129 | In my case, the table below shows the port-forwarding rules I've created. | 138 | settings. You will need to forward ports =80= and =443= to whichever |
| 130 | In this table, `xxx.xxx.xxx.xxx` is the local device IP of the reverse proxy | 139 | machine is running the reverse proxy. |
| 131 | server, it will probably be an IP between `192.168.1.1` and `192.168.1.255`. | 140 | |
| 132 | 141 | In my case, the table below shows the port-forwarding rules I've | |
| 133 | | NAME | FROM | PORT | DEST PORT/IP | ENABLED | | 142 | created. In this table, =xxx.xxx.xxx.xxx= is the local device IP of the |
| 134 | |:-----:|:----:|:----:|:---------------:|:-------:| | 143 | reverse proxy server, it will probably be an IP between =192.168.1.1= |
| 135 | | HTTP | \* | 80 | xxx.xxx.xxx.xxx | TRUE | | 144 | and =192.168.1.255=. |
| 136 | | HTTPS | \* | 443 | xxx.xxx.xxx.xxx | TRUE | | 145 | |
| 137 | 146 | | NAME | FROM | PORT | DEST PORT/IP | ENABLED | | |
| 138 | Once configured, these rules will direct all web traffic to your reverse proxy. | 147 | |-------+------+------+-----------------+---------| |
| 139 | 148 | | HTTP | * | 80 | xxx.xxx.xxx.xxx | TRUE | | |
| 140 | ### Step 3: Nginx Installation | 149 | | HTTPS | * | 443 | xxx.xxx.xxx.xxx | TRUE | |
| 141 | 150 | ||
| 151 | Once configured, these rules will direct all web traffic to your reverse | ||
| 152 | proxy. | ||
| 153 | |||
| 154 | *** Step 3: Nginx Installation | ||
| 155 | :PROPERTIES: | ||
| 156 | :CUSTOM_ID: step-3-nginx-installation | ||
| 157 | :END: | ||
| 142 | To install Nginx, simply run the following command: | 158 | To install Nginx, simply run the following command: |
| 143 | 159 | ||
| 144 | ```sh | 160 | #+begin_src sh |
| 145 | sudo apt install nginx | 161 | sudo apt install nginx |
| 146 | ``` | 162 | #+end_src |
| 147 | 163 | ||
| 148 | If you have a firewall enabled, open up ports `80` and `443` on your server so | 164 | If you have a firewall enabled, open up ports =80= and =443= on your |
| 149 | that Nginx can accept web traffic from the router. | 165 | server so that Nginx can accept web traffic from the router. |
| 150 | 166 | ||
| 151 | For example, if you want to use `ufw` for web traffic and SSH, run the following | 167 | For example, if you want to use =ufw= for web traffic and SSH, run the |
| 152 | commands: | 168 | following commands: |
| 153 | 169 | ||
| 154 | ```sh | 170 | #+begin_src sh |
| 155 | sudo ufw allow 'Nginx Full' | 171 | sudo ufw allow 'Nginx Full' |
| 156 | sudo ufw allow SSH | 172 | sudo ufw allow SSH |
| 157 | sudo ufw enable | 173 | sudo ufw enable |
| 158 | ``` | 174 | #+end_src |
| 159 | 175 | ||
| 160 | ### Step 4: Nginx Configuration | 176 | *** Step 4: Nginx Configuration |
| 161 | 177 | :PROPERTIES: | |
| 162 | Now that we have domains pointing toward the server, the only step left is to | 178 | :CUSTOM_ID: step-4-nginx-configuration |
| 163 | configure the reverse proxy to direct traffic from domains to local services. | 179 | :END: |
| 180 | Now that we have domains pointing toward the server, the only step left | ||
| 181 | is to configure the reverse proxy to direct traffic from domains to | ||
| 182 | local services. | ||
| 164 | 183 | ||
| 165 | To start, you'll need to create a configuration file for each domain in | 184 | To start, you'll need to create a configuration file for each domain in |
| 166 | `/etc/nginx/sites-available/`. They will look identical except for the | 185 | =/etc/nginx/sites-available/=. They will look identical except for the |
| 167 | `server_name` variable and the `proxy_pass` port. | 186 | =server_name= variable and the =proxy_pass= port. |
| 168 | 187 | ||
| 169 | Dashy: | 188 | Dashy: |
| 170 | 189 | ||
| 171 | ```sh | 190 | #+begin_src sh |
| 172 | nano /etc/nginx/sites-available/dashy.example.com | 191 | nano /etc/nginx/sites-available/dashy.example.com |
| 173 | ``` | 192 | #+end_src |
| 174 | 193 | ||
| 175 | ```config | 194 | #+begin_src config |
| 176 | server { | 195 | server { |
| 177 | listen 80; | 196 | listen 80; |
| 178 | server_name dashy.example.com; | 197 | server_name dashy.example.com; |
| @@ -181,15 +200,15 @@ server { | |||
| 181 | proxy_pass http://localhost:4000; | 200 | proxy_pass http://localhost:4000; |
| 182 | } | 201 | } |
| 183 | } | 202 | } |
| 184 | ``` | 203 | #+end_src |
| 185 | 204 | ||
| 186 | Uptime: | 205 | Uptime: |
| 187 | 206 | ||
| 188 | ```sh | 207 | #+begin_src sh |
| 189 | nano /etc/nginx/sites-available/uptime.example.com | 208 | nano /etc/nginx/sites-available/uptime.example.com |
| 190 | ``` | 209 | #+end_src |
| 191 | 210 | ||
| 192 | ```config | 211 | #+begin_src config |
| 193 | server { | 212 | server { |
| 194 | listen 80; | 213 | listen 80; |
| 195 | server_name uptime.example.com; | 214 | server_name uptime.example.com; |
| @@ -198,29 +217,31 @@ server { | |||
| 198 | proxy_pass http://localhost:3001; | 217 | proxy_pass http://localhost:3001; |
| 199 | } | 218 | } |
| 200 | } | 219 | } |
| 201 | ``` | 220 | #+end_src |
| 202 | 221 | ||
| 203 | Once the configuration files are created, you will need to enable them with the | 222 | Once the configuration files are created, you will need to enable them |
| 204 | `symlink` command: | 223 | with the =symlink= command: |
| 205 | 224 | ||
| 206 | ```sh | 225 | #+begin_src sh |
| 207 | sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/ | 226 | sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/ |
| 208 | ``` | 227 | #+end_src |
| 209 | 228 | ||
| 210 | Voilà! Your local services should now be available through their URLs. | 229 | Voilà! Your local services should now be available through their URLs. |
| 211 | 230 | ||
| 212 | ## HTTPS with Certbot | 231 | ** HTTPS with Certbot |
| 213 | 232 | :PROPERTIES: | |
| 214 | If you've followed along, you'll notice that your services are only available | 233 | :CUSTOM_ID: https-with-certbot |
| 215 | via HTTP (not HTTPS). | 234 | :END: |
| 235 | If you've followed along, you'll notice that your services are only | ||
| 236 | available via HTTP (not HTTPS). | ||
| 216 | 237 | ||
| 217 | If you want to enable HTTPS for your new domains, you will need to generate | 238 | If you want to enable HTTPS for your new domains, you will need to |
| 218 | SSL/TLS certificates for them. The easiest way to generate certificates on Nginx | 239 | generate SSL/TLS certificates for them. The easiest way to generate |
| 219 | is [Certbot](https://certbot.eff.org): | 240 | certificates on Nginx is [[https://certbot.eff.org][Certbot]]: |
| 220 | 241 | ||
| 221 | ```sh | 242 | #+begin_src sh |
| 222 | sudo apt install snapd; sudo snap install core; sudo snap refresh core | 243 | sudo apt install snapd; sudo snap install core; sudo snap refresh core |
| 223 | sudo snap install --classic certbot | 244 | sudo snap install --classic certbot |
| 224 | sudo ln -s /snap/bin/certbot /usr/bin/certbot | 245 | sudo ln -s /snap/bin/certbot /usr/bin/certbot |
| 225 | sudo certbot --nginx | 246 | sudo certbot --nginx |
| 226 | ``` | 247 | #+end_src |
blog/2022-04-09-pinetime.org +160 −130
| @@ -1,148 +1,178 @@ | |||
| 1 | +++ | 1 | #+title: PineTime: An Open-Source SmartWatch |
| 2 | date = 2022-04-09 | 2 | #+date: 2022-04-09 |
| 3 | title = "PineTime: An Open-Source SmartWatch" | 3 | |
| 4 | description = "Exploring the PineTime, an open-source smartwatch created by PINE64." | 4 | ** PineTime Product Information |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: pinetime-product-information |
| 7 | 7 | :END: | |
| 8 | ## PineTime Product Information | 8 | *** Overview |
| 9 | 9 | :PROPERTIES: | |
| 10 | ### Overview | 10 | :CUSTOM_ID: overview |
| 11 | 11 | :END: | |
| 12 | The [PineTime](https://www.pine64.org/pinetime/) is an open-source smartwatch, | 12 | The [[https://www.pine64.org/pinetime/][PineTime]] is an open-source |
| 13 | created by [PINE64](https://www.pine64.org). Originally announced in September | 13 | smartwatch, created by [[https://www.pine64.org][PINE64]]. Originally |
| 14 | 2019, this ARM-based watch is a fantastic option for users who want the benefits | 14 | announced in September 2019, this ARM-based watch is a fantastic option |
| 15 | of a modern smartwatch with the backing of open-source components and software. | 15 | for users who want the benefits of a modern smartwatch with the backing |
| 16 | 16 | of open-source components and software. | |
| 17 | ### Product Specifications | 17 | |
| 18 | 18 | *** Product Specifications | |
| 19 | :PROPERTIES: | ||
| 20 | :CUSTOM_ID: product-specifications | ||
| 21 | :END: | ||
| 19 | I won't dive into too many details that you can find on | 22 | I won't dive into too many details that you can find on |
| 20 | [the product page](https://www.pine64.org/pinetime/), but I wanted to point out | 23 | [[https://www.pine64.org/pinetime/][the product page]], but I wanted to |
| 21 | the prices for each watch and the primary functions: | 24 | point out the prices for each watch and the primary functions: |
| 22 | 25 | ||
| 23 | #### Price: | 26 | **** Price: |
| 24 | 27 | :PROPERTIES: | |
| 25 | - $26.99 (Sealed) | 28 | :CUSTOM_ID: price |
| 26 | - $24.99 (Dev Kit) | 29 | :END: |
| 27 | - $51.98 (One Sealed + One Dev Kit) | 30 | - $26.99 (Sealed) |
| 28 | 31 | - $24.99 (Dev Kit) | |
| 29 | #### Primary Functionality: | 32 | - $51.98 (One Sealed + One Dev Kit) |
| 30 | 33 | ||
| 31 | - Clock (+ Smartphone Sync) | 34 | **** Primary Functionality: |
| 32 | - Pedometer | 35 | :PROPERTIES: |
| 33 | - Heart Rate Monitor | 36 | :CUSTOM_ID: primary-functionality |
| 34 | - Sleep Monitor | 37 | :END: |
| 35 | - Calories burned | 38 | - Clock (+ Smartphone Sync) |
| 36 | - Messaging | 39 | - Pedometer |
| 37 | - Smartphone Notifications | 40 | - Heart Rate Monitor |
| 38 | - Media Controls | 41 | - Sleep Monitor |
| 39 | 42 | - Calories burned | |
| 40 | ## Unboxing | 43 | - Messaging |
| 41 | 44 | - Smartphone Notifications | |
| 42 | Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and was | 45 | - Media Controls |
| 43 | delivered on 2022-03-23. | 46 | |
| 44 | With the current delays on shipping times around the world (and the | 47 | ** Unboxing |
| 45 | semiconductor shortage), a month for delivery from China seems reasonable to me. | 48 | :PROPERTIES: |
| 46 | 49 | :CUSTOM_ID: unboxing | |
| 47 | The packaging is simple, and the watch comes with instructions, technical | 50 | :END: |
| 48 | information, the watch, and a charger (it does not include a USB wall adapter). | 51 | Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and |
| 49 | 52 | was delivered on 2022-03-23. With the current delays on shipping times | |
| 50 | The watch itself was able to turn on immediately when I pulled it out of the | 53 | around the world (and the semiconductor shortage), a month for delivery |
| 51 | box, but the battery was depleted and required charging right away. | 54 | from China seems reasonable to me. |
| 52 | 55 | ||
| 53 |  | 56 | The packaging is simple, and the watch comes with instructions, |
| 54 | 57 | technical information, the watch, and a charger (it does not include a | |
| 55 | ## Software | 58 | USB wall adapter). |
| 56 | 59 | ||
| 57 | ### Watch OS: InfiniTime | 60 | The watch itself was able to turn on immediately when I pulled it out of |
| 58 | 61 | the box, but the battery was depleted and required charging right away. | |
| 59 | While turning on the watch for the first time, some of the main design choices | 62 | |
| 60 | you can see in the watch OS, | 63 | #+caption: PineTime Box Contents |
| 61 | [InfiniTime](https://wiki.pine64.org/wiki/InfiniTime), are: | 64 | [[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png]] |
| 62 | 65 | ||
| 63 | - A square bezel, not too thin against the sides of the watch. | 66 | ** Software |
| 64 | - A simple, rubber band. | 67 | :PROPERTIES: |
| 65 | - Basic font and screen pixel design. | 68 | :CUSTOM_ID: software |
| 66 | - Swipe gestures to access other screens. | 69 | :END: |
| 67 | 70 | *** Watch OS: InfiniTime | |
| 68 |  | 71 | :PROPERTIES: |
| 69 | 72 | :CUSTOM_ID: watch-os-infinitime | |
| 70 | The OS itself is fantastic in terms of functionality for me. It does exactly | 73 | :END: |
| 71 | what a smartwatch should do - track time, steps, heart rates, and connect to | 74 | While turning on the watch for the first time, some of the main design |
| 72 | another smart device, without being overly burdensome to the user. | 75 | choices you can see in the watch OS, |
| 73 | 76 | [[https://wiki.pine64.org/wiki/InfiniTime][InfiniTime]], are: | |
| 74 | My only gripe so far is that it's _really_ difficult to swipe to different | 77 | |
| 75 | screens, such as pulling down the notification tray. I'm not sure if this is an | 78 | - A square bezel, not too thin against the sides of the watch. |
| 76 | OS or hardware issue, but it makes it quite hard to quickly move around the | 79 | - A simple, rubber band. |
| 77 | screens. | 80 | - Basic font and screen pixel design. |
| 78 | 81 | - Swipe gestures to access other screens. | |
| 79 | However, my absolute favorite design choice is that the button the side turns | 82 | |
| 80 | the screen on and off and tilting/waving my wrist doesn't accidentally turn on | 83 | #+caption: PineTime Screens |
| 81 | the screen. With other watches, I absolutely hated not being able to turn off | 84 | [[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png]] |
| 82 | the raise-to-wake or wave features (i.e., blinding myself while wearing a watch | 85 | |
| 83 | at night because I moved my arm). | 86 | The OS itself is fantastic in terms of functionality for me. It does |
| 84 | 87 | exactly what a smartwatch should do - track time, steps, heart rates, | |
| 85 | ### iOS App: InfiniLink | 88 | and connect to another smart device, without being overly burdensome to |
| 86 | 89 | the user. | |
| 90 | |||
| 91 | My only gripe so far is that it's /really/ difficult to swipe to | ||
| 92 | different screens, such as pulling down the notification tray. I'm not | ||
| 93 | sure if this is an OS or hardware issue, but it makes it quite hard to | ||
| 94 | quickly move around the screens. | ||
| 95 | |||
| 96 | However, my absolute favorite design choice is that the button the side | ||
| 97 | turns the screen on and off and tilting/waving my wrist doesn't | ||
| 98 | accidentally turn on the screen. With other watches, I absolutely hated | ||
| 99 | not being able to turn off the raise-to-wake or wave features (i.e., | ||
| 100 | blinding myself while wearing a watch at night because I moved my arm). | ||
| 101 | |||
| 102 | *** iOS App: InfiniLink | ||
| 103 | :PROPERTIES: | ||
| 104 | :CUSTOM_ID: ios-app-infinilink | ||
| 105 | :END: | ||
| 87 | Since I am using iOS as my primary mobile device OS, I am using the | 106 | Since I am using iOS as my primary mobile device OS, I am using the |
| 88 | [InfiniLink](https://github.com/xan-m/InfiniLink) app to connect my watch. | 107 | [[https://github.com/xan-m/InfiniLink][InfiniLink]] app to connect my |
| 108 | watch. | ||
| 89 | 109 | ||
| 90 | This app provides the following for PineTime owners: | 110 | This app provides the following for PineTime owners: |
| 91 | 111 | ||
| 92 | - Firmware updates | 112 | - Firmware updates |
| 93 | - Steps | 113 | - Steps |
| 94 | - Charts | 114 | - Charts |
| 95 | - Notifications | 115 | - Notifications |
| 96 | 116 | ||
| 97 | I mashed up a few screenshots to show off the home page, menu, firmware update, | 117 | I mashed up a few screenshots to show off the home page, menu, firmware |
| 98 | and notification test screens: | 118 | update, and notification test screens: |
| 99 | 119 | ||
| 100 |  | 120 | #+caption: InfiniLink Home |
| 121 | [[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png]] | ||
| 101 | 122 | ||
| 102 | Another big feature of InfiniLink is the ability to track pedometer steps in a | 123 | Another big feature of InfiniLink is the ability to track pedometer |
| 103 | collection of beautiful graphs, with the option to change your step goal and | 124 | steps in a collection of beautiful graphs, with the option to change |
| 104 | add in manual steps. | 125 | your step goal and add in manual steps. |
| 105 | 126 | ||
| 106 |  | 127 | #+caption: InfiniLink Steps |
| 128 | [[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png]] | ||
| 107 | 129 | ||
| 108 | Finally, there are charts to display the battery percentage and heart rates over | 130 | Finally, there are charts to display the battery percentage and heart |
| 109 | time. This area also comes with an option to clear data. | 131 | rates over time. This area also comes with an option to clear data. |
| 110 | 132 | ||
| 111 |  | 133 | #+caption: InfiniLink Charts |
| 134 | [[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png]] | ||
| 112 | 135 | ||
| 113 | ## Final Thoughts | 136 | ** Final Thoughts |
| 114 | 137 | :PROPERTIES: | |
| 115 | ### Pros | 138 | :CUSTOM_ID: final-thoughts |
| 116 | 139 | :END: | |
| 117 | After wearing my watch for a few weeks, I have mostly positive thoughts about | 140 | *** Pros |
| 118 | the watch so far. | 141 | :PROPERTIES: |
| 119 | In the past, I have owned smartwatches by FitBit, Fossil, Apple, etc. - | 142 | :CUSTOM_ID: pros |
| 120 | **but I prefer the PineTime over all of those watches**. | 143 | :END: |
| 144 | After wearing my watch for a few weeks, I have mostly positive thoughts | ||
| 145 | about the watch so far. In the past, I have owned smartwatches by | ||
| 146 | FitBit, Fossil, Apple, etc. - *but I prefer the PineTime over all of | ||
| 147 | those watches*. | ||
| 121 | 148 | ||
| 122 | The PineTime strips out all the unnecessary features and performs the | 149 | The PineTime strips out all the unnecessary features and performs the |
| 123 | functions that it provides effectively and efficiently. | 150 | functions that it provides effectively and efficiently. |
| 124 | 151 | ||
| 125 | The battery life is amazing on this device. By default, the watch seems to last | 152 | The battery life is amazing on this device. By default, the watch seems |
| 126 | anywhere from a few days to a week before dying. | 153 | to last anywhere from a few days to a week before dying. |
| 127 | 154 | ||
| 128 | And of course, it's open source and backed by some of the most dedicated | 155 | And of course, it's open source and backed by some of the most dedicated |
| 129 | enthusiasts and developers I've seen. Watching the Matrix channel, forums, and | 156 | enthusiasts and developers I've seen. Watching the Matrix channel, |
| 130 | website have been exciting to see. | 157 | forums, and website have been exciting to see. |
| 131 | 158 | ||
| 132 | ### Cons | 159 | *** Cons |
| 133 | 160 | :PROPERTIES: | |
| 134 | If I had to complain about anything, it would simply be the small bugs in some | 161 | :CUSTOM_ID: cons |
| 135 | features that can be contributed to the companion apps more than the watch | 162 | :END: |
| 136 | itself. | 163 | If I had to complain about anything, it would simply be the small bugs |
| 137 | 164 | in some features that can be contributed to the companion apps more than | |
| 138 | A regular user would want native notification support out-of-the-box, which is | 165 | the watch itself. |
| 139 | the biggest item not working for me at the moment. | 166 | |
| 140 | 167 | A regular user would want native notification support out-of-the-box, | |
| 141 | My only other complaint is that the battery indicator on the watch doesn't seem | 168 | which is the biggest item not working for me at the moment. |
| 142 | accurate when it's nearing depletion - it seems that there's a bit of battery | 169 | |
| 143 | life left and then my watch is dead very suddenly after. This could just be me | 170 | My only other complaint is that the battery indicator on the watch |
| 144 | misinterpreting the battery level icons, but it has fooled me a few times into | 171 | doesn't seem accurate when it's nearing depletion - it seems that |
| 145 | thinking I had more battery left than I actually did. | 172 | there's a bit of battery life left and then my watch is dead very |
| 146 | 173 | suddenly after. This could just be me misinterpreting the battery level | |
| 147 | Other than those small items, I really do love this watch and am glad I replaced | 174 | icons, but it has fooled me a few times into thinking I had more battery |
| 148 | my Apple Watch with the PineTime. | 175 | left than I actually did. |
| 176 | |||
| 177 | Other than those small items, I really do love this watch and am glad I | ||
| 178 | replaced my Apple Watch with the PineTime. | ||
blog/2022-06-01-ditching-cloudflare.org +103 −87
| @@ -1,89 +1,105 @@ | |||
| 1 | +++ | 1 | #+title: Ditching Cloudflare for Njalla |
| 2 | date = 2022-06-01 | 2 | #+date: 2022-06-01 |
| 3 | title = "Ditching Cloudflare for Njalla" | 3 | |
| 4 | description = "After spending a few years jumping around to different DNS hosts, I'm finally ditching Cloudflare for good." | 4 | ** Registrar |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: registrar |
| 7 | 7 | :END: | |
| 8 | ## Registrar | 8 | After spending a year or so using Cloudflare for DNS only - no proxying |
| 9 | 9 | or applications - I spent the last few months using Cloudflare Tunnels | |
| 10 | After spending a year or so using Cloudflare for DNS only - no proxying or | 10 | and Cloudflare Access to protect my self-hosted websites and |
| 11 | applications - I spent the last few months using Cloudflare Tunnels and | 11 | applications via their proxy traffic model. |
| 12 | Cloudflare Access to protect my self-hosted websites and applications via their | 12 | |
| 13 | proxy traffic model. | 13 | However, I have never liked using Cloudflare due to their increasingly |
| 14 | 14 | large share of control over web traffic, as well as their business model | |
| 15 | However, I have never liked using Cloudflare due to their increasingly large | 15 | of being a MITM for all of your traffic. |
| 16 | share of control over web traffic, as well as their business model of being a | 16 | |
| 17 | MITM for all of your traffic. | 17 | So, as of today, I have switched over to [[https://njal.la][Njalla]] as |
| 18 | 18 | my registrar and DNS manager. I was able to easily transfer my domains | |
| 19 | So, as of today, I have switched over to [Njalla](https://njal.la) as my | 19 | over rapidly, with only one domain taking more than 15-30 minutes to |
| 20 | registrar and DNS manager. I was able to easily transfer my domains over | 20 | propagate. |
| 21 | rapidly, with only one domain taking more than 15-30 minutes to propagate. | 21 | |
| 22 | 22 | +I do still have two domains sitting at Cloudflare for the moment while | |
| 23 | ~~I do still have two domains sitting at Cloudflare for the moment while I | 23 | I decide if they're worth the higher rates (one domain is 30€ and the |
| 24 | decide if they're worth the higher rates (one domain is 30€ and the other is | 24 | other is 45€).+ |
| 25 | 45€).~~ | 25 | |
| 26 | 26 | #+begin_quote | |
| 27 | > **Update (2022.06.03)**: I ended up transferring my final two domains over to | 27 | *Update (2022.06.03)*: I ended up transferring my final two domains over |
| 28 | > Njalla, clearing my Cloudflare account of personal data, and deleting the | 28 | to Njalla, clearing my Cloudflare account of personal data, and deleting |
| 29 | > Cloudflare account entirely. _I actually feel relieved to have moved on to a | 29 | the Cloudflare account entirely. /I actually feel relieved to have moved |
| 30 | > provider I trust._ | 30 | on to a provider I trust./ |
| 31 | 31 | ||
| 32 | ## DNS | 32 | #+end_quote |
| 33 | 33 | ||
| 34 | As noted above, I'm using Njalla exclusively for DNS configurations on my | 34 | ** DNS |
| 35 | domains. | 35 | :PROPERTIES: |
| 36 | 36 | :CUSTOM_ID: dns | |
| 37 | However, the transfer process was not ideal. As soon as the domains transferred | 37 | :END: |
| 38 | over, I switched the nameservers from Cloudflare to Njalla and lost most of the | 38 | As noted above, I'm using Njalla exclusively for DNS configurations on |
| 39 | associated DNS records. So, the majority of the time spent during the migration | 39 | my domains. |
| 40 | was simply re-typing all the DNS records back in one-by-one. | 40 | |
| 41 | 41 | However, the transfer process was not ideal. As soon as the domains | |
| 42 | This would be much simpler if I were able to edit the plain-text format of the | 42 | transferred over, I switched the nameservers from Cloudflare to Njalla |
| 43 | DNS configuration. I was able to do that at a past registrar (perhaps it was | 43 | and lost most of the associated DNS records. So, the majority of the |
| 44 | [Gandi.net](https://gandi.net/)?) and it made life a lot easier. | 44 | time spent during the migration was simply re-typing all the DNS records |
| 45 | 45 | back in one-by-one. | |
| 46 | ### Dynamic DNS Updates | 46 | |
| 47 | 47 | This would be much simpler if I were able to edit the plain-text format | |
| 48 | I have built an easy Python script to run (or set-up in `cron` to run | 48 | of the DNS configuration. I was able to do that at a past registrar |
| 49 | automatically) that will check my server's IPv4 and IPv6, compare it to Njalla, | 49 | (perhaps it was [[https://gandi.net/][Gandi.net]]?) and it made life a |
| 50 | and update the DNS records if they don't match. You can see the full script and | 50 | lot easier. |
| 51 | process in my other post: | 51 | |
| 52 | [Updating Dynamic DNS with Njalla API](/blog/njalla-dns-api/). | 52 | *** Dynamic DNS Updates |
| 53 | 53 | :PROPERTIES: | |
| 54 | I haven't used this other method, but I do know that you can create `Dynamic` | 54 | :CUSTOM_ID: dynamic-dns-updates |
| 55 | DNS records with Njalla that | 55 | :END: |
| 56 | [work for updating dynamic subdomains](https://njal.la/docs/ddns/). | 56 | I have built an easy Python script to run (or set-up in =cron= to run |
| 57 | 57 | automatically) that will check my server's IPv4 and IPv6, compare it to | |
| 58 | ### Njalla's DNS Tool | 58 | Njalla, and update the DNS records if they don't match. You can see the |
| 59 | 59 | full script and process in my other post: | |
| 60 | [[/blog/njalla-dns-api/][Updating Dynamic DNS with Njalla API]]. | ||
| 61 | |||
| 62 | I haven't used this other method, but I do know that you can create | ||
| 63 | =Dynamic= DNS records with Njalla that | ||
| 64 | [[https://njal.la/docs/ddns/][work for updating dynamic subdomains]]. | ||
| 65 | |||
| 66 | *** Njalla's DNS Tool | ||
| 67 | :PROPERTIES: | ||
| 68 | :CUSTOM_ID: njallas-dns-tool | ||
| 69 | :END: | ||
| 60 | One neat upside to Njalla is that they have a | 70 | One neat upside to Njalla is that they have a |
| 61 | [DNS lookup tool](https://check.njal.la/dns/) that provides a lot of great | 71 | [[https://check.njal.la/dns/][DNS lookup tool]] that provides a lot of |
| 62 | information for those of you (AKA: me) who hate using the `dig` command. | 72 | great information for those of you (AKA: me) who hate using the =dig= |
| 63 | 73 | command. | |
| 64 | This was very useful for monitoring a couple of my transferred domains to see | 74 | |
| 65 | when the changes in nameservers, records, and DNSSEC went into effect. | 75 | This was very useful for monitoring a couple of my transferred domains |
| 66 | 76 | to see when the changes in nameservers, records, and DNSSEC went into | |
| 67 | ## Tunnel | 77 | effect. |
| 68 | 78 | ||
| 79 | ** Tunnel | ||
| 80 | :PROPERTIES: | ||
| 81 | :CUSTOM_ID: tunnel | ||
| 82 | :END: | ||
| 69 | Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on | 83 | Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on |
| 70 | Cloudflare's servers) and allowed me to mask the private IP address of the | 84 | Cloudflare's servers) and allowed me to mask the private IP address of |
| 71 | server hosting my various websites and apps. | 85 | the server hosting my various websites and apps. |
| 72 | 86 | ||
| 73 | However, as I was moving away from Cloudflare, I was not able to find a suitable | 87 | However, as I was moving away from Cloudflare, I was not able to find a |
| 74 | replacement that was both inexpensive and simple. So, I simply went back to | 88 | suitable replacement that was both inexpensive and simple. So, I simply |
| 75 | hosting [my own reverse proxy with Nginx](/blog/set-up-nginx-reverse-proxy/). | 89 | went back to hosting [[/blog/set-up-nginx-reverse-proxy/][my own reverse |
| 76 | With the recent additions of Unifi hardware in my server/network rack, I am much | 90 | proxy with Nginx]]. With the recent additions of Unifi hardware in my |
| 77 | more protected against spam and malicious attacks at the network edge than I was | 91 | server/network rack, I am much more protected against spam and malicious |
| 78 | before I switched to Cloudflare. | 92 | attacks at the network edge than I was before I switched to Cloudflare. |
| 79 | 93 | ||
| 80 | ## Access | 94 | ** Access |
| 81 | 95 | :PROPERTIES: | |
| 82 | Cloudflare Access, another app I used in combination with Cloudflare Tunnel, | 96 | :CUSTOM_ID: access |
| 83 | provided an authentication screen that required you to enter valid credentials | 97 | :END: |
| 84 | before Cloudflare would forward you to the actual website or app (if the | 98 | Cloudflare Access, another app I used in combination with Cloudflare |
| 85 | website/app has their own authentication, you'd then have to authenticate a | 99 | Tunnel, provided an authentication screen that required you to enter |
| 86 | second time). | 100 | valid credentials before Cloudflare would forward you to the actual |
| 87 | 101 | website or app (if the website/app has their own authentication, you'd | |
| 88 | I did not replace this service with anything since I only host a handful of | 102 | then have to authenticate a second time). |
| 89 | non-sensitive apps that don't require duplicate authentication. | 103 | |
| 104 | I did not replace this service with anything since I only host a handful | ||
| 105 | of non-sensitive apps that don't require duplicate authentication. | ||
blog/2022-06-04-njalla-dns-api.org +112 −99
| @@ -1,104 +1,113 @@ | |||
| 1 | +++ | 1 | #+title: Dynamic DNS with Njalla API |
| 2 | date = 2022-06-04 | 2 | #+date: 2022-06-04 |
| 3 | title = "Dynamic DNS with Njalla API" | 3 | |
| 4 | description = "Learn how to update Njalla DNS records automatically with a simple Python script." | 4 | ** Njalla's API |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: njallas-api |
| 7 | 7 | :END: | |
| 8 | ## Njalla's API | 8 | As noted in my recent post about [[/blog/ditching-cloudflare/][switching |
| 9 | 9 | to Njalla from Cloudflare]], I was searching for a way to replace my | |
| 10 | As noted in my recent post about | 10 | very easy-to-use bash script to [[/blog/cloudflare-dns-api/][update |
| 11 | [switching to Njalla from Cloudflare](/blog/ditching-cloudflare/), I was | 11 | Cloudflare's DNS via their API]]. |
| 12 | searching for a way to replace my very easy-to-use bash script to | 12 | |
| 13 | [update Cloudflare's DNS via their API](/blog/cloudflare-dns-api/). | 13 | To reiterate what I said in those posts, this is a common necessity for |
| 14 | 14 | those of us who have non-static IP addresses that can change at any | |
| 15 | To reiterate what I said in those posts, this is a common necessity for those of | 15 | moment due to ISP policy. |
| 16 | us who have non-static IP addresses that can change at any moment due to ISP | 16 | |
| 17 | policy. | 17 | In order to keep a home server running smoothly, the server admin needs |
| 18 | 18 | to have a process to constantly monitor their public IP address and | |
| 19 | In order to keep a home server running smoothly, the server admin needs to have | 19 | update their domain's DNS records if it changes. |
| 20 | a process to constantly monitor their public IP address and update their | 20 | |
| 21 | domain's DNS records if it changes. | 21 | This post explains how to use Python to update Njalla's DNS records |
| 22 | 22 | whenever a machine's public IP address changes. | |
| 23 | This post explains how to use Python to update Njalla's DNS records whenever a | 23 | |
| 24 | machine's public IP address changes. | 24 | *** Creating a Token |
| 25 | 25 | :PROPERTIES: | |
| 26 | ### Creating a Token | 26 | :CUSTOM_ID: creating-a-token |
| 27 | 27 | :END: | |
| 28 | To use Njalla's API, you will first need to create a token that will be used to | 28 | To use Njalla's API, you will first need to create a token that will be |
| 29 | authenticate you every time you call the API. Luckily, this is very easy to do | 29 | used to authenticate you every time you call the API. Luckily, this is |
| 30 | if you have an account with Njalla. | 30 | very easy to do if you have an account with Njalla. |
| 31 | 31 | ||
| 32 | Simply go the [API Settings](https://njal.la/settings/api/) page and click the | 32 | Simply go the [[https://njal.la/settings/api/][API Settings]] page and |
| 33 | `Add Token` button. Next, enter a name for the token and click `Add`. | 33 | click the =Add Token= button. Next, enter a name for the token and click |
| 34 | 34 | =Add=. | |
| 35 | Finally, click the `Manage` button next to your newly created token and copy the | 35 | |
| 36 | `API Token` field. | 36 | Finally, click the =Manage= button next to your newly created token and |
| 37 | 37 | copy the =API Token= field. | |
| 38 | ### Finding the Correct API Request | 38 | |
| 39 | 39 | *** Finding the Correct API Request | |
| 40 | Once you have a token, you're ready to call the Njalla API for any number of | 40 | :PROPERTIES: |
| 41 | requests. For a full listing of available requests, see the | 41 | :CUSTOM_ID: finding-the-correct-api-request |
| 42 | [Njalla API Documentation](https://njal.la/api/). | 42 | :END: |
| 43 | 43 | Once you have a token, you're ready to call the Njalla API for any | |
| 44 | For this demo, we are using the `list-records` and `edit-record` requests. | 44 | number of requests. For a full listing of available requests, see the |
| 45 | 45 | [[https://njal.la/api/][Njalla API Documentation]]. | |
| 46 | The `list-records` request requires the following payload to be sent when | 46 | |
| 47 | calling the API: | 47 | For this demo, we are using the =list-records= and =edit-record= |
| 48 | 48 | requests. | |
| 49 | ```txt | 49 | |
| 50 | The =list-records= request requires the following payload to be sent | ||
| 51 | when calling the API: | ||
| 52 | |||
| 53 | #+begin_src txt | ||
| 50 | params: { | 54 | params: { |
| 51 | domain: string | 55 | domain: string |
| 52 | } | 56 | } |
| 53 | ``` | 57 | #+end_src |
| 54 | 58 | ||
| 55 | The `edit-record` request requires the following payload to be sent when calling | 59 | The =edit-record= request requires the following payload to be sent when |
| 56 | the API: | 60 | calling the API: |
| 57 | 61 | ||
| 58 | ```txt | 62 | #+begin_src txt |
| 59 | params: { | 63 | params: { |
| 60 | domain: string | 64 | domain: string |
| 61 | id: int | 65 | id: int |
| 62 | content: string | 66 | content: string |
| 63 | } | 67 | } |
| 64 | ``` | 68 | #+end_src |
| 65 | 69 | ||
| 66 | ## Server Set-Up | 70 | ** Server Set-Up |
| 67 | 71 | :PROPERTIES: | |
| 68 | To create this script, we will be using Python. By default, I use Python 3 on my | 72 | :CUSTOM_ID: server-set-up |
| 69 | servers, so please note that I did not test this in Python 2, and I do not know | 73 | :END: |
| 70 | if Python 2 will work for this. | 74 | To create this script, we will be using Python. By default, I use Python |
| 71 | 75 | 3 on my servers, so please note that I did not test this in Python 2, | |
| 72 | ### Creating the Script | 76 | and I do not know if Python 2 will work for this. |
| 73 | 77 | ||
| 74 | First, find a suitable place to create your script. Personally, I just create a | 78 | *** Creating the Script |
| 75 | directory called `ddns` in my home directory: | 79 | :PROPERTIES: |
| 76 | 80 | :CUSTOM_ID: creating-the-script | |
| 77 | ```sh | 81 | :END: |
| 82 | First, find a suitable place to create your script. Personally, I just | ||
| 83 | create a directory called =ddns= in my home directory: | ||
| 84 | |||
| 85 | #+begin_src sh | ||
| 78 | mkdir ~/ddns | 86 | mkdir ~/ddns |
| 79 | ``` | 87 | #+end_src |
| 80 | 88 | ||
| 81 | Next, create a Python script file: | 89 | Next, create a Python script file: |
| 82 | 90 | ||
| 83 | ```sh | 91 | #+begin_src sh |
| 84 | nano ~/ddns/ddns.py | 92 | nano ~/ddns/ddns.py |
| 85 | ``` | 93 | #+end_src |
| 86 | 94 | ||
| 87 | The following code snippet is quite long, so I won't go into depth on each part. | 95 | The following code snippet is quite long, so I won't go into depth on |
| 88 | However, I suggest you read through the entire script before running it; it is | 96 | each part. However, I suggest you read through the entire script before |
| 89 | quite simple and contains comments to help explain each code block. | 97 | running it; it is quite simple and contains comments to help explain |
| 98 | each code block. | ||
| 90 | 99 | ||
| 91 | :warning: **Note**: You will need to update the following variables for this to | 100 | :warning: *Note*: You will need to update the following variables for |
| 92 | work: | 101 | this to work: |
| 93 | 102 | ||
| 94 | - `token`: This is the Njalla API token you created earlier. | 103 | - =token=: This is the Njalla API token you created earlier. |
| 95 | - `user_domain`: This is the top-level domain you want to modify. | 104 | - =user_domain=: This is the top-level domain you want to modify. |
| 96 | - `include_subdomains`: Set this to `True` if you also want to modify | 105 | - =include_subdomains=: Set this to =True= if you also want to modify |
| 97 | subdomains found under the TLD. | 106 | subdomains found under the TLD. |
| 98 | - `subdomains`: If `include_subdomains` = `True`, you can include your list of | 107 | - =subdomains=: If =include_subdomains= = =True=, you can include your |
| 99 | subdomains to be modified here. | 108 | list of subdomains to be modified here. |
| 100 | 109 | ||
| 101 | ```python | 110 | #+begin_src python |
| 102 | #!/usr/bin/python | 111 | #!/usr/bin/python |
| 103 | # -*- coding: utf-8 -*- | 112 | # -*- coding: utf-8 -*- |
| 104 | # Import Python modules | 113 | # Import Python modules |
| @@ -177,29 +186,33 @@ for record in data['records']: | |||
| 177 | 'does not match Njalla\'s value of', | 186 | 'does not match Njalla\'s value of', |
| 178 | record['content'], '. Updating...') | 187 | record['content'], '. Updating...') |
| 179 | update_record(user_domain, record['id'], ipv6) | 188 | update_record(user_domain, record['id'], ipv6) |
| 180 | ``` | 189 | #+end_src |
| 181 | 190 | ||
| 182 | ### Running the Script | 191 | *** Running the Script |
| 192 | :PROPERTIES: | ||
| 193 | :CUSTOM_ID: running-the-script | ||
| 194 | :END: | ||
| 195 | Once you've created the script and are ready to test it, run the | ||
| 196 | following command: | ||
| 183 | 197 | ||
| 184 | Once you've created the script and are ready to test it, run the following | 198 | #+begin_src sh |
| 185 | command: | ||
| 186 | |||
| 187 | ```sh | ||
| 188 | python3 ~/ddns/ddns.py | 199 | python3 ~/ddns/ddns.py |
| 189 | ``` | 200 | #+end_src |
| 190 | |||
| 191 | ### Setting the Script to Run Automatically | ||
| 192 | 201 | ||
| 193 | To make sure the scripts run automatically, add it to the `cron` file so that it | 202 | *** Setting the Script to Run Automatically |
| 194 | will run on a schedule. To do this, open the `cron` file: | 203 | :PROPERTIES: |
| 204 | :CUSTOM_ID: setting-the-script-to-run-automatically | ||
| 205 | :END: | ||
| 206 | To make sure the scripts run automatically, add it to the =cron= file so | ||
| 207 | that it will run on a schedule. To do this, open the =cron= file: | ||
| 195 | 208 | ||
| 196 | ```sh | 209 | #+begin_src sh |
| 197 | crontab -e | 210 | crontab -e |
| 198 | ``` | 211 | #+end_src |
| 199 | 212 | ||
| 200 | In the cron file, paste the following at the bottom of the editor in order to | 213 | In the cron file, paste the following at the bottom of the editor in |
| 201 | check the IP every five minutes: | 214 | order to check the IP every five minutes: |
| 202 | 215 | ||
| 203 | ```sh | 216 | #+begin_src sh |
| 204 | */5 * * * * python3 /home/<your_username>/ddns/ddns.py | 217 | */5 * * * * python3 /home/<your_username>/ddns/ddns.py |
| 205 | ``` | 218 | #+end_src |
blog/2022-06-07-freshrss.org +175 −152
| @@ -1,67 +1,75 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting FreshRSS |
| 2 | date = 2022-06-07 | 2 | #+date: 2022-06-07 |
| 3 | title = "Self-Hosting FreshRSS" | 3 | |
| 4 | description = "A detailed description of my process to self-host FreshRSS on Ubuntu." | 4 | ** Why RSS? |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: why-rss |
| 7 | 7 | :END: | |
| 8 | ## Why RSS? | 8 | After noticing that I have collected 50+ blogs as bookmarks, I decided |
| 9 | 9 | to migrate back to using RSS feeds to stay up-to-date with my favorite | |
| 10 | After noticing that I have collected 50+ blogs as bookmarks, I decided to | 10 | websites. Using RSS allows me to read all of these posts in a single app |
| 11 | migrate back to using RSS feeds to stay up-to-date with my favorite websites. | 11 | (on both mobile & desktop) and allows me to be notified when new posts |
| 12 | Using RSS allows me to read all of these posts in a single app (on both mobile & | 12 | are available. |
| 13 | desktop) and allows me to be notified when new posts are available. | 13 | |
| 14 | 14 | However, I ran into one issue: syncing subscriptions and read/unread | |
| 15 | However, I ran into one issue: syncing subscriptions and read/unread posts | 15 | posts across devices. Since I want to be able to easily read on both |
| 16 | across devices. Since I want to be able to easily read on both mobile and | 16 | mobile and desktop, I decided to look for a self-hosted RSS solution. |
| 17 | desktop, I decided to look for a self-hosted RSS solution. | 17 | |
| 18 | 18 | Thus, I found [[https://www.freshrss.org/][FreshRSS]] and was able to | |
| 19 | Thus, I found [FreshRSS](https://www.freshrss.org/) and was able to successfully | 19 | successfully install it on my server in about 30 minutes. |
| 20 | install it on my server in about 30 minutes. | 20 | |
| 21 | 21 | ** Documentation | |
| 22 | ## Documentation | 22 | :PROPERTIES: |
| 23 | 23 | :CUSTOM_ID: documentation | |
| 24 | :END: | ||
| 24 | While it's certainly not robust, the | 25 | While it's certainly not robust, the |
| 25 | [FreshRSS documentation](https://freshrss.github.io/FreshRSS/) is helpful for | 26 | [[https://freshrss.github.io/FreshRSS/][FreshRSS documentation]] is |
| 26 | figuring out basic information about the service. | 27 | helpful for figuring out basic information about the service. |
| 27 | 28 | ||
| 28 | However, I wanted to install this service as a Docker container and stumbled | 29 | However, I wanted to install this service as a Docker container and |
| 29 | across the | 30 | stumbled across the |
| 30 | [Docker README](https://github.com/FreshRSS/FreshRSS/tree/edge/Docker) within | 31 | [[https://github.com/FreshRSS/FreshRSS/tree/edge/Docker][Docker README]] |
| 31 | the GitHub repository. | 32 | within the GitHub repository. |
| 32 | 33 | ||
| 33 | This README was the documentation I actually needed. However, as you'll see | 34 | This README was the documentation I actually needed. However, as you'll |
| 34 | below, I still had to manually edit one file (`config.php`) to access the API | 35 | see below, I still had to manually edit one file (=config.php=) to |
| 35 | externally via my RSS apps. | 36 | access the API externally via my RSS apps. |
| 36 | 37 | ||
| 37 | ## Installation | 38 | ** Installation |
| 38 | 39 | :PROPERTIES: | |
| 39 | ### DNS | 40 | :CUSTOM_ID: installation |
| 40 | 41 | :END: | |
| 41 | The first step, as required by any external web service, was assigning a domain | 42 | *** DNS |
| 42 | name to use. I chose to use a subdomain, like `rss.example.com`. | 43 | :PROPERTIES: |
| 43 | 44 | :CUSTOM_ID: dns | |
| 44 | To assign this, I created an `A` record in my DNS settings with the IPv4 address | 45 | :END: |
| 45 | of the server and an `AAAA` record with the IPv6 address of the server. Note: | 46 | The first step, as required by any external web service, was assigning a |
| 46 | assigning an IPv6 (`AAAA`) record is optional, but I like to enable IPV6 for my | 47 | domain name to use. I chose to use a subdomain, like =rss.example.com=. |
| 47 | services. | 48 | |
| 48 | 49 | To assign this, I created an =A= record in my DNS settings with the IPv4 | |
| 49 | ```config | 50 | address of the server and an =AAAA= record with the IPv6 address of the |
| 51 | server. Note: assigning an IPv6 (=AAAA=) record is optional, but I like | ||
| 52 | to enable IPV6 for my services. | ||
| 53 | |||
| 54 | #+begin_src config | ||
| 50 | rss.example.com A xxx.xxx.xxx.xxx | 55 | rss.example.com A xxx.xxx.xxx.xxx |
| 51 | rss.example.com AAAA xxxx:xxxx: ... :xxxx | 56 | rss.example.com AAAA xxxx:xxxx: ... :xxxx |
| 52 | ``` | 57 | #+end_src |
| 53 | 58 | ||
| 54 | ### Docker | 59 | *** Docker |
| 55 | 60 | :PROPERTIES: | |
| 56 | I initially tried to set up a `docker-compose.yml` file with a `.env` file | 61 | :CUSTOM_ID: docker |
| 57 | because I prefer to have a file I can look back at later to see how I initially | 62 | :END: |
| 58 | started the container, but it simply wouldn't work for me. I'm not sure why, but | 63 | I initially tried to set up a =docker-compose.yml= file with a =.env= |
| 59 | I assume I wasn't telling `docker-compose` where the `.env` file was. | 64 | file because I prefer to have a file I can look back at later to see how |
| 60 | 65 | I initially started the container, but it simply wouldn't work for me. | |
| 61 | Regardless, I chose to simply run the service with `docker run`. See the | 66 | I'm not sure why, but I assume I wasn't telling =docker-compose= where |
| 62 | following command for my `docker run` configuration: | 67 | the =.env= file was. |
| 63 | 68 | ||
| 64 | ```sh | 69 | Regardless, I chose to simply run the service with =docker run=. See the |
| 70 | following command for my =docker run= configuration: | ||
| 71 | |||
| 72 | #+begin_src sh | ||
| 65 | sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ | 73 | sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ |
| 66 | -p 8080:80 \ | 74 | -p 8080:80 \ |
| 67 | -e TZ=America/Chicago \ | 75 | -e TZ=America/Chicago \ |
| @@ -70,42 +78,46 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ | |||
| 70 | -v freshrss_extensions:/var/www/FreshRSS/extensions \ | 78 | -v freshrss_extensions:/var/www/FreshRSS/extensions \ |
| 71 | --name freshrss \ | 79 | --name freshrss \ |
| 72 | freshrss/freshrss | 80 | freshrss/freshrss |
| 73 | ``` | 81 | #+end_src |
| 74 | 82 | ||
| 75 | This started the container successfully and allowed me to visit the FreshRSS | 83 | This started the container successfully and allowed me to visit the |
| 76 | instance at `localhost:8080`. | 84 | FreshRSS instance at =localhost:8080=. |
| 77 | 85 | ||
| 78 | ### Fresh RSS Set-Up | 86 | *** Fresh RSS Set-Up |
| 79 | 87 | :PROPERTIES: | |
| 80 | I **HIGHLY** suggest that you set up your user account prior to | 88 | :CUSTOM_ID: fresh-rss-set-up |
| 81 | exposing this service to the public. It's unlikely that someone is trying to | 89 | :END: |
| 82 | access the exact domain or IP/port you're assigning here, but as soon as you | 90 | I *HIGHLY* suggest that you set up your user account prior to exposing |
| 83 | expose this service, the first person to open the URL will be able to create the | 91 | this service to the public. It's unlikely that someone is trying to |
| 84 | admin user. | 92 | access the exact domain or IP/port you're assigning here, but as soon as |
| 85 | 93 | you expose this service, the first person to open the URL will be able | |
| 86 | In order to set up your FreshRSS service, open the `localhost:8080` URL in your | 94 | to create the admin user. |
| 87 | browser (you may need to use a local IP instead of `localhost` if you're | 95 | |
| 88 | accessing the page from a different machine on the network - e.g., | 96 | In order to set up your FreshRSS service, open the =localhost:8080= URL |
| 89 | `192.168.1.20:8080`). | 97 | in your browser (you may need to use a local IP instead of =localhost= |
| 98 | if you're accessing the page from a different machine on the network - | ||
| 99 | e.g., =192.168.1.20:8080=). | ||
| 90 | 100 | ||
| 91 | Once the page loads, set up your default user with a strong username and | 101 | Once the page loads, set up your default user with a strong username and |
| 92 | password. You may also choose to configure other settings prior to exposing this | 102 | password. You may also choose to configure other settings prior to |
| 93 | service. | 103 | exposing this service. |
| 94 | |||
| 95 | ### Nginx Reverse-Proxy | ||
| 96 | 104 | ||
| 105 | *** Nginx Reverse-Proxy | ||
| 106 | :PROPERTIES: | ||
| 107 | :CUSTOM_ID: nginx-reverse-proxy | ||
| 108 | :END: | ||
| 97 | In order to access this service outside my home, I needed to set up a | 109 | In order to access this service outside my home, I needed to set up a |
| 98 | reverse-proxy to connect `localhost:8080` to `rss.example.com`. | 110 | reverse-proxy to connect =localhost:8080= to =rss.example.com=. |
| 99 | 111 | ||
| 100 | First, I created a new Nginx configuration file: | 112 | First, I created a new Nginx configuration file: |
| 101 | 113 | ||
| 102 | ```sh | 114 | #+begin_src sh |
| 103 | sudo nano /etc/nginx/sites-available/rss.example.com | 115 | sudo nano /etc/nginx/sites-available/rss.example.com |
| 104 | ``` | 116 | #+end_src |
| 105 | 117 | ||
| 106 | Within the config file, I pasted the following code: | 118 | Within the config file, I pasted the following code: |
| 107 | 119 | ||
| 108 | ```config | 120 | #+begin_src config |
| 109 | upstream freshrss { | 121 | upstream freshrss { |
| 110 | server 127.0.0.1:8080; | 122 | server 127.0.0.1:8080; |
| 111 | keepalive 64; | 123 | keepalive 64; |
| @@ -134,80 +146,91 @@ server { | |||
| 134 | proxy_pass_header Authorization; | 146 | proxy_pass_header Authorization; |
| 135 | } | 147 | } |
| 136 | } | 148 | } |
| 137 | ``` | 149 | #+end_src |
| 138 | 150 | ||
| 139 | Finally, restart Nginx and you will be able to access your service via HTTP: | 151 | Finally, restart Nginx and you will be able to access your service via |
| 152 | HTTP: | ||
| 140 | 153 | ||
| 141 | ```sh | 154 | #+begin_src sh |
| 142 | sudo systemctl restart nginx.service | 155 | sudo systemctl restart nginx.service |
| 143 | ``` | 156 | #+end_src |
| 144 | 157 | ||
| 145 | ### HTTPS | 158 | *** HTTPS |
| 146 | 159 | :PROPERTIES: | |
| 147 | However, I don't want to access my RSS feeds via HTTP. I want it available only | 160 | :CUSTOM_ID: https |
| 148 | via HTTPS. In order to do this, I ran the [certbot](https://certbot.eff.org/) | 161 | :END: |
| 149 | program to generate SSL certificates for me: | 162 | However, I don't want to access my RSS feeds via HTTP. I want it |
| 150 | 163 | available only via HTTPS. In order to do this, I ran the | |
| 151 | ```sh | 164 | [[https://certbot.eff.org/][certbot]] program to generate SSL |
| 165 | certificates for me: | ||
| 166 | |||
| 167 | #+begin_src sh | ||
| 152 | sudo certbot --nginx | 168 | sudo certbot --nginx |
| 153 | ``` | 169 | #+end_src |
| 154 | 170 | ||
| 155 | This process will automatically generate an SSL certificate for you and modify | 171 | This process will automatically generate an SSL certificate for you and |
| 156 | the Nginx configuration file to include a redirect from HTTP to HTTPS. | 172 | modify the Nginx configuration file to include a redirect from HTTP to |
| 157 | 173 | HTTPS. | |
| 158 | ## Post-Installation Fixes | 174 | |
| 159 | 175 | ** Post-Installation Fixes | |
| 160 | At this point, we have a functional FreshRSS website, available from anywhere | 176 | :PROPERTIES: |
| 161 | and secured with HTTPS. However, attempting to connect this service to an RSS | 177 | :CUSTOM_ID: post-installation-fixes |
| 162 | app resulted in many errors regarding unavailable URLs and incorrect | 178 | :END: |
| 163 | credentials. | 179 | At this point, we have a functional FreshRSS website, available from |
| 164 | 180 | anywhere and secured with HTTPS. However, attempting to connect this | |
| 165 | ### API Set-Up | 181 | service to an RSS app resulted in many errors regarding unavailable URLs |
| 166 | 182 | and incorrect credentials. | |
| 167 | First, you need to open your user profile in FreshRSS (`Settings` > `Profile`) | 183 | |
| 168 | and set an API password in the field at the bottom. This is the password you | 184 | *** API Set-Up |
| 169 | will need to provide to your RSS apps. | 185 | :PROPERTIES: |
| 170 | 186 | :CUSTOM_ID: api-set-up | |
| 171 | Once that is set and saved, click the link below the API password field to open | 187 | :END: |
| 172 | the API check tool. It should look something like `https://localhost:8080/api/` | 188 | First, you need to open your user profile in FreshRSS (=Settings= > |
| 173 | or `https://rss.example.com/api/`. | 189 | =Profile=) and set an API password in the field at the bottom. This is |
| 174 | 190 | the password you will need to provide to your RSS apps. | |
| 175 | Within this page, you _should_ see your correct external URL and "PASS" at the | 191 | |
| 176 | bottom of each API type. | 192 | Once that is set and saved, click the link below the API password field |
| 177 | This would mean everything is set up correctly, and you can now move on and | 193 | to open the API check tool. It should look something like |
| 178 | login to any RSS apps that support self-hosted options. | 194 | =https://localhost:8080/api/= or =https://rss.example.com/api/=. |
| 195 | |||
| 196 | Within this page, you /should/ see your correct external URL and "PASS" | ||
| 197 | at the bottom of each API type. This would mean everything is set up | ||
| 198 | correctly, and you can now move on and login to any RSS apps that | ||
| 199 | support self-hosted options. | ||
| 179 | 200 | ||
| 180 | In my case, the URL showed an internal URL and I had a warning that the | 201 | In my case, the URL showed an internal URL and I had a warning that the |
| 181 | `base_url` variable may be misconfigured. If this is the case, see the next | 202 | =base_url= variable may be misconfigured. If this is the case, see the |
| 182 | section for a fix. | 203 | next section for a fix. |
| 183 | |||
| 184 | ### Base URL Fix | ||
| 185 | 204 | ||
| 186 | In order to fix the `base_url` for the API, I opened up my docker container with | 205 | *** Base URL Fix |
| 187 | the following command: | 206 | :PROPERTIES: |
| 207 | :CUSTOM_ID: base-url-fix | ||
| 208 | :END: | ||
| 209 | In order to fix the =base_url= for the API, I opened up my docker | ||
| 210 | container with the following command: | ||
| 188 | 211 | ||
| 189 | ```sh | 212 | #+begin_src sh |
| 190 | sudo docker exec -it freshrss bash | 213 | sudo docker exec -it freshrss bash |
| 191 | ``` | 214 | #+end_src |
| 192 | 215 | ||
| 193 | Within this container, update the packages and install an editor: | 216 | Within this container, update the packages and install an editor: |
| 194 | 217 | ||
| 195 | ```sh | 218 | #+begin_src sh |
| 196 | apt-get update | 219 | apt-get update |
| 197 | apt-get install nano | 220 | apt-get install nano |
| 198 | ``` | 221 | #+end_src |
| 199 | 222 | ||
| 200 | Finally, open up `config.php` in the `data` directory: | 223 | Finally, open up =config.php= in the =data= directory: |
| 201 | 224 | ||
| 202 | ```sh | 225 | #+begin_src sh |
| 203 | nano data/config.php | 226 | nano data/config.php |
| 204 | ``` | 227 | #+end_src |
| 205 | 228 | ||
| 206 | Within `config.php`, you will need to update the `base_url` variable and update | 229 | Within =config.php=, you will need to update the =base_url= variable and |
| 207 | it to match your external URL. In my case, I simply commented-out the incorrect | 230 | update it to match your external URL. In my case, I simply commented-out |
| 208 | URL with `//` and added the correct one on a new line: | 231 | the incorrect URL with =//= and added the correct one on a new line: |
| 209 | 232 | ||
| 210 | ```php | 233 | #+begin_src php |
| 211 | <?php | 234 | <?php |
| 212 | return array ( | 235 | return array ( |
| 213 | ... | 236 | ... |
| @@ -216,25 +239,25 @@ URL with `//` and added the correct one on a new line: | |||
| 216 | ... | 239 | ... |
| 217 | ) | 240 | ) |
| 218 | > | 241 | > |
| 219 | ``` | 242 | #+end_src |
| 220 | 243 | ||
| 221 | You can now exit the file with `Ctrl + x`, press `y` to save the file, and then | 244 | You can now exit the file with =Ctrl + x=, press =y= to save the file, |
| 222 | click `Enter` to keep the same file name. | 245 | and then click =Enter= to keep the same file name. |
| 223 | 246 | ||
| 224 | Finally, just exit out of the docker container: | 247 | Finally, just exit out of the docker container: |
| 225 | 248 | ||
| 226 | ```sh | 249 | #+begin_src sh |
| 227 | exit | 250 | exit |
| 228 | ``` | 251 | #+end_src |
| 229 | 252 | ||
| 230 | Next, just restart the container: | 253 | Next, just restart the container: |
| 231 | 254 | ||
| 232 | ```sh | 255 | #+begin_src sh |
| 233 | sudo docker restart freshrss | 256 | sudo docker restart freshrss |
| 234 | ``` | 257 | #+end_src |
| 235 | 258 | ||
| 236 | Voilà! Your API check should now "PASS" and you should be able to use one of the | 259 | Voilà! Your API check should now "PASS" and you should be able to use |
| 237 | API URLs in your RSS apps. | 260 | one of the API URLs in your RSS apps. |
| 238 | 261 | ||
| 239 | In my case, I use [NetNewsWire](https://netnewswire.com) on my desktop and | 262 | In my case, I use [[https://netnewswire.com][NetNewsWire]] on my desktop |
| 240 | phone. | 263 | and phone. |
blog/2022-06-16-terminal-lifestyle.org +188 −160
| @@ -1,43 +1,48 @@ | |||
| 1 | +++ | 1 | #+title: A Terminal Lifestyle |
| 2 | date = 2022-06-16 | 2 | #+date: 2022-06-16 |
| 3 | title = "A Terminal Lifestyle" | 3 | |
| 4 | description = "A detailed description on how I'm living my digital life in the terminal." | 4 | ** Text-Based Simplicity |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: text-based-simplicity | |
| 7 | ## Text-Based Simplicity | 7 | :END: |
| 8 | 8 | I've detailed my views on web-based minimalism and related topics in | |
| 9 | I've detailed my views on web-based minimalism and related topics in other | 9 | other posts throughout the years; e.g., JavaScript/CSS bloat slowing |
| 10 | posts throughout the years; e.g., JavaScript/CSS bloat slowing down websites | 10 | down websites that are essentially a text document. However, I have |
| 11 | that are essentially a text document. However, I have never really expanded | 11 | never really expanded beyond talking about the web and describing how I |
| 12 | beyond talking about the web and describing how I focus on minimizing | 12 | focus on minimizing distractions in other digital environments. |
| 13 | distractions in other digital environments. | 13 | |
| 14 | 14 | This post is going to set the baseline for how I /try/ to live my | |
| 15 | This post is going to set the baseline for how I *try* to live my digital life. | 15 | digital life. It does not necessarily get into my physical life, which |
| 16 | It does not necessarily get into my physical life, which is often harder to | 16 | is often harder to control and contain all the noise in our modern |
| 17 | control and contain all the noise in our modern world. | 17 | world. |
| 18 | 18 | ||
| 19 | While there are new things to do every day in our digital world, I find that | 19 | While there are new things to do every day in our digital world, I find |
| 20 | keeping a core set of values and interests can ground you and keep you mindful | 20 | that keeping a core set of values and interests can ground you and keep |
| 21 | of *why* you are participating in the digital world. For example, if - at your | 21 | you mindful of /why/ you are participating in the digital world. For |
| 22 | core - you have no interest in what strangers think about random topics, it | 22 | example, if - at your core - you have no interest in what strangers |
| 23 | would be unwise to start participating in social media. However, I am someone | 23 | think about random topics, it would be unwise to start participating in |
| 24 | who has been dragged in by effective advertising to participate in communities | 24 | social media. However, I am someone who has been dragged in by effective |
| 25 | that I realize I do not care for. | 25 | advertising to participate in communities that I realize I do not care |
| 26 | 26 | for. | |
| 27 | I won't dive much further into explaining the philosophy of all this, but I will | 27 | |
| 28 | link a few helpful articles that may pique your interest if you're in search of | 28 | I won't dive much further into explaining the philosophy of all this, |
| 29 | more meaningful experiences: | 29 | but I will link a few helpful articles that may pique your interest if |
| 30 | 30 | you're in search of more meaningful experiences: | |
| 31 | - [Mindfulness](https://en.wikipedia.org/wiki/Mindfulness) | 31 | |
| 32 | - [Minimalism](https://en.wikipedia.org/wiki/Minimalism) | 32 | - [[https://en.wikipedia.org/wiki/Mindfulness][Mindfulness]] |
| 33 | - [Stoicism](https://en.wikipedia.org/wiki/Stoicism) | 33 | - [[https://en.wikipedia.org/wiki/Minimalism][Minimalism]] |
| 34 | 34 | - [[https://en.wikipedia.org/wiki/Stoicism][Stoicism]] | |
| 35 | ## Living Life in the Terminal | 35 | |
| 36 | 36 | ** Living Life in the Terminal | |
| 37 | My personal approach to reducing digital distractions and increasing my focus on | 37 | :PROPERTIES: |
| 38 | the task at hand is to use a terminal for as much as I possibly can. | 38 | :CUSTOM_ID: living-life-in-the-terminal |
| 39 | 39 | :END: | |
| 40 | Most days, this means that I have a few tabs open constantly in my terminal: | 40 | My personal approach to reducing digital distractions and increasing my |
| 41 | focus on the task at hand is to use a terminal for as much as I possibly | ||
| 42 | can. | ||
| 43 | |||
| 44 | Most days, this means that I have a few tabs open constantly in my | ||
| 45 | terminal: | ||
| 41 | 46 | ||
| 42 | 1. A web browser | 47 | 1. A web browser |
| 43 | 2. A chat client | 48 | 2. A chat client |
| @@ -46,157 +51,180 @@ Most days, this means that I have a few tabs open constantly in my terminal: | |||
| 46 | 5. A local shell for navigating my computer's files | 51 | 5. A local shell for navigating my computer's files |
| 47 | 6. A remote shell for managing servers and other machines | 52 | 6. A remote shell for managing servers and other machines |
| 48 | 53 | ||
| 49 | Beyond this, I rarely open other tabs or GUI applications, unless absolutely | 54 | Beyond this, I rarely open other tabs or GUI applications, unless |
| 50 | necessary. If you look, you may be surprised what can be accomplished in the | 55 | absolutely necessary. If you look, you may be surprised what can be |
| 51 | terminal. | 56 | accomplished in the terminal. |
| 52 | 57 | ||
| 53 | For example, I have moved my music and entertainment downloads to the | 58 | For example, I have moved my music and entertainment downloads to the |
| 54 | terminal, along with my device VPN connections. I am exploring options for | 59 | terminal, along with my device VPN connections. I am exploring options |
| 55 | moving my RSS subscriptions to something like [Newsboat](https://newsboat.org/), | 60 | for moving my RSS subscriptions to something like |
| 56 | so that I can read my daily articles without all the fuss. | 61 | [[https://newsboat.org/][Newsboat]], so that I can read my daily |
| 62 | articles without all the fuss. | ||
| 57 | 63 | ||
| 58 | Now that we have some examples out of the way, let's dive into the | 64 | Now that we have some examples out of the way, let's dive into the |
| 59 | specifics. | 65 | specifics. |
| 60 | 66 | ||
| 61 | ### Browsing the Web | 67 | *** Browsing the Web |
| 62 | 68 | :PROPERTIES: | |
| 63 | I'm going to start off with a hard topic for those who prefer to live in the | 69 | :CUSTOM_ID: browsing-the-web |
| 64 | terminal: web browsing. This task is made hard mostly by websites and web apps | 70 | :END: |
| 65 | that require JavaScript to run. The other difficult part is that if you're using | 71 | I'm going to start off with a hard topic for those who prefer to live in |
| 66 | a text-based browser, that means images won't load (hopefully that's obvious). | 72 | the terminal: web browsing. This task is made hard mostly by websites |
| 67 | 73 | and web apps that require JavaScript to run. The other difficult part is | |
| 68 | I am using [Lynx](https://lynx.invisible-island.net), a text-based browser that | 74 | that if you're using a text-based browser, that means images won't load |
| 69 | runs quickly and easily in the terminal. Lynx allows me to browser most websites | 75 | (hopefully that's obvious). |
| 70 | by simply typing `g` and then typing in the URL I want. | 76 | |
| 71 | 77 | I am using [[https://lynx.invisible-island.net][Lynx]], a text-based | |
| 72 |  | 78 | browser that runs quickly and easily in the terminal. Lynx allows me to |
| 73 | 79 | browser most websites by simply typing =g= and then typing in the URL I | |
| 74 | If you need a search engine while in Lynx, I recommend [DuckDuckGo | 80 | want. |
| 75 | (Lite)](https://lite.duckduckgo.com/lite/), which allows you to search the web | 81 | |
| 76 | using their text-only interface. | 82 | #+caption: Lynx |
| 77 | 83 | [[https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png]] | |
| 78 |  | 85 | If you need a search engine while in Lynx, I recommend |
| 80 | 86 | [[https://lite.duckduckgo.com/lite/][DuckDuckGo (Lite)]], which allows | |
| 81 | Eventually, you will run into websites that don't work (or are just too ugly | 87 | you to search the web using their text-only interface. |
| 82 | and messy) in a text-only mode, and you'll be forced to switch over to a GUI | 88 | |
| 83 | browser to look at that site. Personally, I don't mind this as it doesn't happen | 89 | #+caption: DuckDuckGo !Lite |
| 84 | as often as I thought it would. | 90 | [[https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png]] |
| 85 | 91 | ||
| 86 | The only time I need to do this is when I want to browse an image/video-focused | 92 | Eventually, you will run into websites that don't work (or are just too |
| 87 | webpage or if I need to log in to a site, and it doesn't support a text-only | 93 | ugly and messy) in a text-only mode, and you'll be forced to switch over |
| 88 | login page. | 94 | to a GUI browser to look at that site. Personally, I don't mind this as |
| 89 | For example, I am able to easily log in to [Sourcehut](https://sr.ht) in lynx. | 95 | it doesn't happen as often as I thought it would. |
| 90 | 96 | ||
| 91 | ### Chatting with Friends | 97 | The only time I need to do this is when I want to browse an |
| 92 | 98 | image/video-focused webpage or if I need to log in to a site, and it | |
| 93 | After web browsing activities, my main form of terminal communication is Matrix. | 99 | doesn't support a text-only login page. For example, I am able to easily |
| 94 | I use the [gomuks](https://docs.mau.fi/gomuks/) client currently. | 100 | log in to [[https://sr.ht][Sourcehut]] in lynx. |
| 95 | 101 | ||
| 96 | This was incredibly easy to install on macOS (but I will need to see if it'll | 102 | *** Chatting with Friends |
| 97 | be just as easy on Linux when my new laptop arrives): | 103 | :PROPERTIES: |
| 98 | 104 | :CUSTOM_ID: chatting-with-friends | |
| 99 | ```sh | 105 | :END: |
| 106 | After web browsing activities, my main form of terminal communication is | ||
| 107 | Matrix. I use the [[https://docs.mau.fi/gomuks/][gomuks]] client | ||
| 108 | currently. | ||
| 109 | |||
| 110 | This was incredibly easy to install on macOS (but I will need to see if | ||
| 111 | it'll be just as easy on Linux when my new laptop arrives): | ||
| 112 | |||
| 113 | #+begin_src sh | ||
| 100 | brew install gomuks | 114 | brew install gomuks |
| 101 | ``` | 115 | #+end_src |
| 102 | 116 | ||
| 103 | Once you launch gomuks, it will sync and require your username and password to | 117 | Once you launch gomuks, it will sync and require your username and |
| 104 | login. After doing so, the only problem I ran into was verifying my gomuks | 118 | password to login. After doing so, the only problem I ran into was |
| 105 | client so that I could participate in rooms with E2EE. | 119 | verifying my gomuks client so that I could participate in rooms with |
| 120 | E2EE. | ||
| 106 | 121 | ||
| 107 | Finally, I was able to verify the session by opening the Element desktop app (I | 122 | Finally, I was able to verify the session by opening the Element desktop |
| 108 | assume you can do this in the browser and mobile app too, but I'm not sure) and | 123 | app (I assume you can do this in the browser and mobile app too, but I'm |
| 109 | manually verifying myself with this process: | 124 | not sure) and manually verifying myself with this process: |
| 110 | 125 | ||
| 111 | 1. Open the Element desktop app | 126 | 1. Open the Element desktop app |
| 112 | 2. Open a room I was a member of | 127 | 2. Open a room I was a member of |
| 113 | 3. Open the `Room Info` pane | 128 | 3. Open the =Room Info= pane |
| 114 | 4. Open the `People` menu and search for myself | 129 | 4. Open the =People= menu and search for myself |
| 115 | 5. Click on my profile name | 130 | 5. Click on my profile name |
| 116 | 6. Click on the session link under the `Security` section and follow the | 131 | 6. Click on the session link under the =Security= section and follow the |
| 117 | prompts to manually verify the session | 132 | prompts to manually verify the session |
| 118 | 133 | ||
| 119 | Overall, I like gomuks and am able to enjoy all the features I was using in | 134 | Overall, I like gomuks and am able to enjoy all the features I was using |
| 120 | Element. The only hiccup I have occurred is manually downloading images to view | 135 | in Element. The only hiccup I have occurred is manually downloading |
| 121 | them, which can be annoying. | 136 | images to view them, which can be annoying. |
| 122 | 137 | ||
| 123 |  | 138 | #+caption: gomuks |
| 124 | 139 | [[https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png]] | |
| 125 | 140 | ||
| 126 | 141 | ||
| 127 | Moving email to the terminal has been the hardest of the tasks for me. Unlike | 142 | :PROPERTIES: |
| 128 | web browsing, where I can simply decide to not look at a website that does not | 143 | :CUSTOM_ID: email |
| 129 | work in the terminal, I cannot simply ignore emails sent to me. | 144 | :END: |
| 130 | 145 | Moving email to the terminal has been the hardest of the tasks for me. | |
| 131 | Personally, I am experimenting with [neomutt](https://neomutt.org/) as a | 146 | Unlike web browsing, where I can simply decide to not look at a website |
| 132 | potential email client. | 147 | that does not work in the terminal, I cannot simply ignore emails sent |
| 133 | 148 | to me. | |
| 134 | However, this requires a **TON** of configuration and tweaking to get right. | 149 | |
| 135 | Even when I was able to set up neomutt, configure my email account, and | 150 | Personally, I am experimenting with [[https://neomutt.org/][neomutt]] as |
| 136 | customize a few personal preferences, a lot of emails still do not display | 151 | a potential email client. |
| 137 | correctly (mostly due to HTML and images). | 152 | |
| 138 | 153 | However, this requires a *TON* of configuration and tweaking to get | |
| 139 | I won't get into the details of configuring `neomutt`; I mostly followed this | 154 | right. Even when I was able to set up neomutt, configure my email |
| 140 | blog post: [Email in the Terminal: Configuring | 155 | account, and customize a few personal preferences, a lot of emails still |
| 141 | Neomutt](https://gideonwolfe.com/posts/workflow/neomutt/intro/). | 156 | do not display correctly (mostly due to HTML and images). |
| 142 | 157 | ||
| 143 | Finally, I have yet to figure out how to connect my GPG keys to `neomutt`, but | 158 | I won't get into the details of configuring =neomutt=; I mostly followed |
| 144 | that's a problem for another day. | 159 | this blog post: |
| 145 | 160 | [[https://gideonwolfe.com/posts/workflow/neomutt/intro/][Email in the | |
| 146 | ### RSS Feed Reader | 161 | Terminal: Configuring Neomutt]]. |
| 147 | 162 | ||
| 148 | I have just started using [Newsboat](https://newsboat.org/) to read articles in | 163 | Finally, I have yet to figure out how to connect my GPG keys to |
| 149 | my terminal and have found quick success with it. | 164 | =neomutt=, but that's a problem for another day. |
| 165 | |||
| 166 | *** RSS Feed Reader | ||
| 167 | :PROPERTIES: | ||
| 168 | :CUSTOM_ID: rss-feed-reader | ||
| 169 | :END: | ||
| 170 | I have just started using [[https://newsboat.org/][Newsboat]] to read | ||
| 171 | articles in my terminal and have found quick success with it. | ||
| 150 | 172 | ||
| 151 | I'll show you a quick screenshot first: | 173 | I'll show you a quick screenshot first: |
| 152 | 174 | ||
| 153 |  | 175 | #+caption: Newsboat |
| 176 | [[https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png]] | ||
| 154 | 177 | ||
| 155 | The configuration was super easy for this app; I simply installed the app, | 178 | The configuration was super easy for this app; I simply installed the |
| 156 | created a file for URLs, and imported my OPML subscriptions that I had exported | 179 | app, created a file for URLs, and imported my OPML subscriptions that I |
| 157 | out of my old feed reader: | 180 | had exported out of my old feed reader: |
| 158 | 181 | ||
| 159 | ```sh | 182 | #+begin_src sh |
| 160 | brew install newsboat | 183 | brew install newsboat |
| 161 | ``` | 184 | #+end_src |
| 162 | 185 | ||
| 163 | ```sh | 186 | #+begin_src sh |
| 164 | touch ~/.newsboat/urls | 187 | touch ~/.newsboat/urls |
| 165 | ``` | 188 | #+end_src |
| 166 | 189 | ||
| 167 | ```sh | 190 | #+begin_src sh |
| 168 | newsboat -i=my_subscriptions.opml | 191 | newsboat -i=my_subscriptions.opml |
| 169 | ``` | 192 | #+end_src |
| 170 | |||
| 171 | ### Writing & Programming | ||
| 172 | 193 | ||
| 173 | Unfortunately, the weak link in my terminal-based environment right now is my | 194 | *** Writing & Programming |
| 174 | grasp of the possibilities of editing files within a shell. | 195 | :PROPERTIES: |
| 196 | :CUSTOM_ID: writing-programming | ||
| 197 | :END: | ||
| 198 | Unfortunately, the weak link in my terminal-based environment right now | ||
| 199 | is my grasp of the possibilities of editing files within a shell. | ||
| 175 | 200 | ||
| 176 | I am used to the easy extensions found in VSCodium and Kate, so I am slowly | 201 | I am used to the easy extensions found in VSCodium and Kate, so I am |
| 177 | learning how to mold the default editing tools to my needs. Currently, this | 202 | slowly learning how to mold the default editing tools to my needs. |
| 178 | means I am using `nano` with the following configuration: | 203 | Currently, this means I am using =nano= with the following |
| 204 | configuration: | ||
| 179 | 205 | ||
| 180 | ```config | 206 | #+begin_src config |
| 181 | set breaklonglines | 207 | set breaklonglines |
| 182 | set autoindent | 208 | set autoindent |
| 183 | set linenumbers | 209 | set linenumbers |
| 184 | set tabstospaces | 210 | set tabstospaces |
| 185 | set tabsize 2 | 211 | set tabsize 2 |
| 186 | set fill 80 | 212 | set fill 80 |
| 187 | ``` | 213 | #+end_src |
| 188 | 214 | ||
| 189 | This configuration allows nano to automatically hard-wrap lines at 80 | 215 | This configuration allows nano to automatically hard-wrap lines at 80 |
| 190 | characters, autoindent the wrapped lines (if the previous line was indented), | 216 | characters, autoindent the wrapped lines (if the previous line was |
| 191 | use 2 spaces per tab, and display line numbers within each file I open. | 217 | indented), use 2 spaces per tab, and display line numbers within each |
| 218 | file I open. | ||
| 192 | 219 | ||
| 193 | I am currently looking to see if `vim` or `emacs` would be more useful for my | 220 | I am currently looking to see if =vim= or =emacs= would be more useful |
| 194 | current needs, but I'm not in any rush, so I don't expect to find an answer | 221 | for my current needs, but I'm not in any rush, so I don't expect to find |
| 195 | anytime soon. | 222 | an answer anytime soon. |
| 196 | 223 | ||
| 197 | With my current life demands, I am not programming at the moment and have not | 224 | With my current life demands, I am not programming at the moment and |
| 198 | explored the best terminal set-up for programming. However, I have seen many | 225 | have not explored the best terminal set-up for programming. However, I |
| 199 | peers find success configuring `vim` and `emacs`, so that's where I will start | 226 | have seen many peers find success configuring =vim= and =emacs=, so |
| 200 | when I pick my projects back up. | 227 | that's where I will start when I pick my projects back up. |
| 201 | 228 | ||
| 202 |  | 229 | #+caption: nano |
| 230 | [[https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png]] | ||
blog/2022-06-22-daily-poetry.org +140 −114
| @@ -1,71 +1,80 @@ | |||
| 1 | +++ | 1 | #+title: Daily Plaintext Poetry via Email |
| 2 | date = 2022-06-22 | 2 | #+date: 2022-06-22 |
| 3 | title = "Daily Plaintext Poetry via Email" | 3 | |
| 4 | description = "A quick tutorial showing how to use Python and the PoetryDB API to email daily poetry." | 4 | ** Source Code |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: source-code |
| 7 | 7 | :END: | |
| 8 | ## Source Code | 8 | I don't want to bury the lede here, so if you'd like to see the full |
| 9 | 9 | source code I use to email myself plaintext poems daily, visit the | |
| 10 | I don't want to bury the lede here, so if you'd like to see the full source | 10 | repository: [[https://git.sr.ht/~cmc/daily-poem/][daily-poem]]. |
| 11 | code I use to email myself plaintext poems daily, visit the repository: | 11 | |
| 12 | [daily-poem](https://git.sr.ht/~cmc/daily-poem/). | 12 | ** My Daily Dose of Poetry |
| 13 | 13 | :PROPERTIES: | |
| 14 | ## My Daily Dose of Poetry | 14 | :CUSTOM_ID: my-daily-dose-of-poetry |
| 15 | 15 | :END: | |
| 16 | Most of my programming projects are small, random projects that are made | 16 | Most of my programming projects are small, random projects that are made |
| 17 | strictly to fix some small problem I have or enhance my quality of life. | 17 | strictly to fix some small problem I have or enhance my quality of life. |
| 18 | 18 | ||
| 19 | In this case, I was looking for a simply and easy way to get a daily dose of | 19 | In this case, I was looking for a simply and easy way to get a daily |
| 20 | literature or poetry to read in the mornings. | 20 | dose of literature or poetry to read in the mornings. |
| 21 | |||
| 22 | However, I don't want to sign up for a random mailing list on just any website. | ||
| 23 | I also don't want to have to work to find the reading content each morning, as I | ||
| 24 | know I would simply give up and stop reading daily. | ||
| 25 | 21 | ||
| 26 | Thus, I found a way to deliver poetry to myself in plain-text format, on a daily | 22 | However, I don't want to sign up for a random mailing list on just any |
| 27 | basis, and scheduled to deliver automatically. | 23 | website. I also don't want to have to work to find the reading content |
| 24 | each morning, as I know I would simply give up and stop reading daily. | ||
| 28 | 25 | ||
| 29 | ## Prerequisites | 26 | Thus, I found a way to deliver poetry to myself in plain-text format, on |
| 27 | a daily basis, and scheduled to deliver automatically. | ||
| 30 | 28 | ||
| 31 | This solution uses Python and email, so the following process requires the | 29 | ** Prerequisites |
| 32 | following to be installed: | 30 | :PROPERTIES: |
| 31 | :CUSTOM_ID: prerequisites | ||
| 32 | :END: | ||
| 33 | This solution uses Python and email, so the following process requires | ||
| 34 | the following to be installed: | ||
| 33 | 35 | ||
| 34 | 1. An SMTP server, which can be as easy as installing `mailutils` if you're on | 36 | 1. An SMTP server, which can be as easy as installing =mailutils= if |
| 35 | a Debian-based distro. | 37 | you're on a Debian-based distro. |
| 36 | 2. Python (& pip!) | 38 | 2. Python (& pip!) |
| 37 | 3. The following Python packages: `email`, `smtplib`, `json`, and `requests` | 39 | 3. The following Python packages: =email=, =smtplib=, =json=, and |
| 38 | 40 | =requests= | |
| 39 | ## Breaking Down the Logic | 41 | |
| 40 | 42 | ** Breaking Down the Logic | |
| 41 | I want to break down the logic for this program, as it's quite simple and | 43 | :PROPERTIES: |
| 42 | informational. | 44 | :CUSTOM_ID: breaking-down-the-logic |
| 43 | 45 | :END: | |
| 44 | ### Required Packages | 46 | I want to break down the logic for this program, as it's quite simple |
| 45 | 47 | and informational. | |
| 46 | This program starts with a simple import of the required packages, so I wanted | 48 | |
| 47 | to explain why each package is used: | 49 | *** Required Packages |
| 48 | 50 | :PROPERTIES: | |
| 49 | ```py | 51 | :CUSTOM_ID: required-packages |
| 52 | :END: | ||
| 53 | This program starts with a simple import of the required packages, so I | ||
| 54 | wanted to explain why each package is used: | ||
| 55 | |||
| 56 | #+begin_src py | ||
| 50 | from email.mime.text import MIMEText # Required for translating MIMEText | 57 | from email.mime.text import MIMEText # Required for translating MIMEText |
| 51 | import smtplib # Required to process the SMTP mail delivery | 58 | import smtplib # Required to process the SMTP mail delivery |
| 52 | import json # Required to parse the poetry API results | 59 | import json # Required to parse the poetry API results |
| 53 | import requests # Required to send out a request to the API | 60 | import requests # Required to send out a request to the API |
| 54 | ``` | 61 | #+end_src |
| 55 | |||
| 56 | ### Sending the API Request | ||
| 57 | 62 | ||
| 58 | Next, we need to actually send the API request. In my case, I'm calling a random | 63 | *** Sending the API Request |
| 59 | poem from the entire API. If you want, you can call specific poems or authors | 64 | :PROPERTIES: |
| 60 | from this API. | 65 | :CUSTOM_ID: sending-the-api-request |
| 66 | :END: | ||
| 67 | Next, we need to actually send the API request. In my case, I'm calling | ||
| 68 | a random poem from the entire API. If you want, you can call specific | ||
| 69 | poems or authors from this API. | ||
| 61 | 70 | ||
| 62 | ```py | 71 | #+begin_src py |
| 63 | json_data = requests.get('https://poetrydb.org/random').json() | 72 | json_data = requests.get('https://poetrydb.org/random').json() |
| 64 | ``` | 73 | #+end_src |
| 65 | 74 | ||
| 66 | This gives us the following result in JSON: | 75 | This gives us the following result in JSON: |
| 67 | 76 | ||
| 68 | ```json | 77 | #+begin_src json |
| 69 | [ | 78 | [ |
| 70 | { | 79 | { |
| 71 | "title": "Sonnet XXII: With Fools and Children", | 80 | "title": "Sonnet XXII: With Fools and Children", |
| @@ -91,80 +100,95 @@ This gives us the following result in JSON: | |||
| 91 | "linecount": "15" | 100 | "linecount": "15" |
| 92 | } | 101 | } |
| 93 | ] | 102 | ] |
| 94 | ``` | 103 | #+end_src |
| 95 | 104 | ||
| 96 | ### Parsing the API Results | 105 | *** Parsing the API Results |
| 97 | 106 | :PROPERTIES: | |
| 98 | In order to parse this into a readable format, we need to use the `json` package | 107 | :CUSTOM_ID: parsing-the-api-results |
| 99 | and extract the fields we want. In the example below, I am grabbing every field | 108 | :END: |
| 100 | presented by the API. | 109 | In order to parse this into a readable format, we need to use the =json= |
| 101 | 110 | package and extract the fields we want. In the example below, I am | |
| 102 | For the actual poem content, we need to loop over each line in the `lines` | 111 | grabbing every field presented by the API. |
| 103 | variable since each line is a separate string by default. | 112 | |
| 104 | 113 | For the actual poem content, we need to loop over each line in the | |
| 105 | > You *could* also extract the title or author and make another call out to the | 114 | =lines= variable since each line is a separate string by default. |
| 106 | > API to avoid having to build the plaintext poem with a loop, but it just | 115 | |
| 107 | > doesn't make sense to me to send multiple requests when we can create a simple | 116 | #+begin_quote |
| 108 | > loop on our local machine to work with the data we already have. | 117 | You /could/ also extract the title or author and make another call out |
| 109 | > | 118 | to the API to avoid having to build the plaintext poem with a loop, but |
| 110 | > For | 119 | it just doesn't make sense to me to send multiple requests when we can |
| 111 | > [example](https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text), | 120 | create a simple loop on our local machine to work with the data we |
| 112 | > look at the raw data response of this link to see the poem's lines returned | 121 | already have. |
| 113 | > in plaintext. | 122 | |
| 114 | 123 | For | |
| 115 | ```py | 124 | [[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]], |
| 125 | look at the raw data response of this link to see the poem's lines | ||
| 126 | returned in plaintext. | ||
| 127 | |||
| 128 | #+end_quote | ||
| 129 | |||
| 130 | #+begin_src py | ||
| 116 | title = json_data[0]['title'] | 131 | title = json_data[0]['title'] |
| 117 | author = json_data[0]['author'] | 132 | author = json_data[0]['author'] |
| 118 | line_count = json_data[0]['linecount'] | 133 | line_count = json_data[0]['linecount'] |
| 119 | lines = '' | 134 | lines = '' |
| 120 | for line in json_data[0]['lines']: | 135 | for line in json_data[0]['lines']: |
| 121 | lines = lines + line + "\n" | 136 | lines = lines + line + "\n" |
| 122 | ``` | 137 | #+end_src |
| 123 | |||
| 124 | ### Composing the Email | ||
| 125 | 138 | ||
| 126 | Now that I have all the data I need, I just need to compose it into a message | 139 | *** Composing the Email |
| 127 | and prepare the message metadata. | 140 | :PROPERTIES: |
| 141 | :CUSTOM_ID: composing-the-email | ||
| 142 | :END: | ||
| 143 | Now that I have all the data I need, I just need to compose it into a | ||
| 144 | message and prepare the message metadata. | ||
| 128 | 145 | ||
| 129 | For my daily email, I want to see the title of the poem first, followed by the | 146 | For my daily email, I want to see the title of the poem first, followed |
| 130 | author, then a blank line, and finally the full poem. This code snippet combines | 147 | by the author, then a blank line, and finally the full poem. This code |
| 131 | that data and packages it into a MIMEText container, ready to be emailed. | 148 | snippet combines that data and packages it into a MIMEText container, |
| 149 | ready to be emailed. | ||
| 132 | 150 | ||
| 133 | ```py | 151 | #+begin_src py |
| 134 | msg_body = title + "\n" + author + "\n\n" + lines | 152 | msg_body = title + "\n" + author + "\n\n" + lines |
| 135 | msg = MIMEText(msg_body) | 153 | msg = MIMEText(msg_body) |
| 136 | ``` | 154 | #+end_src |
| 137 | 155 | ||
| 138 | Before we send the email, we need to prepare the metadata (subject, from, to, | 156 | Before we send the email, we need to prepare the metadata (subject, |
| 139 | etc.): | 157 | from, to, etc.): |
| 140 | 158 | ||
| 141 | ```py | 159 | #+begin_src py |
| 142 | sender_email = 'example@server.local' | 160 | sender_email = 'example@server.local' |
| 143 | recipient_emails = ['user@example.com'] | 161 | recipient_emails = ['user@example.com'] |
| 144 | msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)' | 162 | msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)' |
| 145 | msg['From'] = sender_email | 163 | msg['From'] = sender_email |
| 146 | msg['To'] = recipient_email | 164 | msg['To'] = recipient_email |
| 147 | ``` | 165 | #+end_src |
| 148 | 166 | ||
| 149 | ### Sending the Email | 167 | *** Sending the Email |
| 150 | 168 | :PROPERTIES: | |
| 151 | Now that I have everything ready to be emailed, the last step is to simply | 169 | :CUSTOM_ID: sending-the-email |
| 152 | connect to an SMTP server and send the email out to the recipients. In my case, | 170 | :END: |
| 153 | I installed `mailutils` on Ubuntu and let my SMTP server be `localhost`. | 171 | Now that I have everything ready to be emailed, the last step is to |
| 154 | 172 | simply connect to an SMTP server and send the email out to the | |
| 155 | ```py | 173 | recipients. In my case, I installed =mailutils= on Ubuntu and let my |
| 174 | SMTP server be =localhost=. | ||
| 175 | |||
| 176 | #+begin_src py | ||
| 156 | smtp_server = 'localhost' | 177 | smtp_server = 'localhost' |
| 157 | s = smtplib.SMTP(smtp_server) | 178 | s = smtplib.SMTP(smtp_server) |
| 158 | s.sendmail(sender_email, recipient_emails, msg.as_string()) | 179 | s.sendmail(sender_email, recipient_emails, msg.as_string()) |
| 159 | s.quit() | 180 | s.quit() |
| 160 | ``` | 181 | #+end_src |
| 161 | 182 | ||
| 162 | ## The Result! | 183 | ** The Result! |
| 184 | :PROPERTIES: | ||
| 185 | :CUSTOM_ID: the-result | ||
| 186 | :END: | ||
| 187 | Instead of including a screenshot, I've copied the contents of the email | ||
| 188 | that was delivered to my inbox below since I set this process up in | ||
| 189 | plaintext format. | ||
| 163 | 190 | ||
| 164 | Instead of including a screenshot, I've copied the contents of the email that | 191 | #+begin_src txt |
| 165 | was delivered to my inbox below since I set this process up in plaintext format. | ||
| 166 | |||
| 167 | ```txt | ||
| 168 | Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC) | 192 | Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC) |
| 169 | From: REDACTED | 193 | From: REDACTED |
| 170 | To: REDACTED | 194 | To: REDACTED |
| @@ -190,24 +214,26 @@ You that behold us, laugh us not to scorn; | |||
| 190 | Give Nature thanks you are not such as we. | 214 | Give Nature thanks you are not such as we. |
| 191 | Yet fools and children sometimes tell in play | 215 | Yet fools and children sometimes tell in play |
| 192 | Some, wise in show, more fools indeed than they. | 216 | Some, wise in show, more fools indeed than they. |
| 193 | ``` | 217 | #+end_src |
| 194 | |||
| 195 | ## Scheduling the Daily Email | ||
| 196 | 218 | ||
| 197 | Last, but not least, is scheduling this Python script with `crontab`. To | 219 | ** Scheduling the Daily Email |
| 198 | schedule a script to run daily, you can add it to the `crontab` file. To do | 220 | :PROPERTIES: |
| 199 | this, open `crontab` in editing mode: | 221 | :CUSTOM_ID: scheduling-the-daily-email |
| 222 | :END: | ||
| 223 | Last, but not least, is scheduling this Python script with =crontab=. To | ||
| 224 | schedule a script to run daily, you can add it to the =crontab= file. To | ||
| 225 | do this, open =crontab= in editing mode: | ||
| 200 | 226 | ||
| 201 | ```sh | 227 | #+begin_src sh |
| 202 | crontab -e | 228 | crontab -e |
| 203 | ``` | 229 | #+end_src |
| 204 | 230 | ||
| 205 | In the file, simply paste the following snippet at the bottom of the file and | 231 | In the file, simply paste the following snippet at the bottom of the |
| 206 | ensure that the file path is correctly pointing to wherever you saved your | 232 | file and ensure that the file path is correctly pointing to wherever you |
| 207 | Python script: | 233 | saved your Python script: |
| 208 | 234 | ||
| 209 | ```config | 235 | #+begin_src config |
| 210 | 0 8 * * * python3 /home/<your_user>/dailypoem/main.py | 236 | 0 8 * * * python3 /home/<your_user>/dailypoem/main.py |
| 211 | ``` | 237 | #+end_src |
| 212 | 238 | ||
| 213 | We have now set up the script and scheduled it to run daily at 08:00! | 239 | We have now set up the script and scheduled it to run daily at 08:00! |
blog/2022-06-24-fedora-i3.org +118 −101
| @@ -1,78 +1,86 @@ | |||
| 1 | +++ | 1 | #+title: Rebooting My Love Affair with Linux |
| 2 | date = 2022-06-24 | 2 | #+date: 2022-06-24 |
| 3 | title = "Rebooting My Love Affair with Linux" | 3 | |
| 4 | description = "A quick recap on my migration from macOS to Fedora i3." | 4 | ** Leaving macOS |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: leaving-macos |
| 7 | 7 | :END: | |
| 8 | ## Leaving macOS | 8 | As I noted [[/blog/foss-macos-apps][in a recent post]], I have been |
| 9 | 9 | planning on migrating from macOS back to a Linux-based OS. I am happy to | |
| 10 | As I noted [in a recent post](/blog/foss-macos-apps), I have been planning on | 10 | say that I have finally completed my migration and am now stuck in the |
| 11 | migrating from macOS back to a Linux-based OS. I am happy to say that I have | 11 | wonderful world of Linux again. |
| 12 | finally completed my migration and am now stuck in the wonderful world of Linux | 12 | |
| 13 | again. | 13 | My decision to leave macOS really came down to just a few important |
| 14 | 14 | things: | |
| 15 | My decision to leave macOS really came down to just a few important things: | 15 | |
| 16 | 16 | - Apple Security (Gatekeeper) restricting me from running any software I | |
| 17 | - Apple Security (Gatekeeper) restricting me from running any software I want. | 17 | want. Even if you disable Gatekeeper and allow software to bypass the |
| 18 | Even if you disable Gatekeeper and allow software to bypass the rest of the | 18 | rest of the device installation security, you still have to repeat |
| 19 | device installation security, you still have to repeat that process every time | 19 | that process every time the allowed software is updated. |
| 20 | the allowed software is updated. | 20 | - macOS sends out nearly constant connections, pings, telemetry, etc. to |
| 21 | - macOS sends out nearly constant connections, pings, telemetry, etc. to a | 21 | a myriad of mysterious Apple services. I'm not even going to dive into |
| 22 | myriad of mysterious Apple services. I'm not even going to dive into how many | 22 | how many macOS apps have constant telemetry on, as well. |
| 23 | macOS apps have constant telemetry on, as well. | 23 | - Lastly, I just /really/ missed the customization and freedom that |
| 24 | - Lastly, I just *really* missed the customization and freedom that comes with | 24 | comes with Linux. Being able to switch to entirely new kernel, OS, or |
| 25 | Linux. Being able to switch to entirely new kernel, OS, or desktop within | 25 | desktop within minutes is a freedom I took for granted when I switched |
| 26 | minutes is a freedom I took for granted when I switched to macOS. | 26 | to macOS. |
| 27 | 27 | ||
| 28 | Now that I've covered macOS, I'm going to move on to more exciting topics: my | 28 | Now that I've covered macOS, I'm going to move on to more exciting |
| 29 | personal choice of OS, DE, and various customizations I'm using. | 29 | topics: my personal choice of OS, DE, and various customizations I'm |
| 30 | 30 | using. | |
| 31 | ## Fedora | 31 | |
| 32 | 32 | ** Fedora | |
| 33 | After trying a ton of distros (I think I booted and tested around 20-25 | 33 | :PROPERTIES: |
| 34 | distros), I finally landed on [Fedora Linux](https://getfedora.org/). I have | 34 | :CUSTOM_ID: fedora |
| 35 | quite a bit of experience with Fedora and enjoy the `dnf` package manager. | 35 | :END: |
| 36 | Fedora allows me to keep up-to-date with recent software (I'm looking at you, | 36 | After trying a ton of distros (I think I booted and tested around 20-25 |
| 37 | Debian), but still provides a level of stability you don't find in every distro. | 37 | distros), I finally landed on [[https://getfedora.org/][Fedora Linux]]. |
| 38 | 38 | I have quite a bit of experience with Fedora and enjoy the =dnf= package | |
| 39 | In a very close second place was Arch Linux, as well as its spin-off: Garuda | 39 | manager. Fedora allows me to keep up-to-date with recent software (I'm |
| 40 | Linux (Garuda w/ sway is *beautiful*). Arch is great for compatibility and the | 40 | looking at you, Debian), but still provides a level of stability you |
| 41 | massive community it has, but I have just never had the time to properly sit | 41 | don't find in every distro. |
| 42 | down and learn the methodology behind their packaging systems. | 42 | |
| 43 | 43 | In a very close second place was Arch Linux, as well as its spin-off: | |
| 44 | Basically, everything else I tested was unacceptable in at least one way or | 44 | Garuda Linux (Garuda w/ sway is /beautiful/). Arch is great for |
| 45 | another. | 45 | compatibility and the massive community it has, but I have just never |
| 46 | Void (`glibc`) was great, but doesn't support all the software I need. | 46 | had the time to properly sit down and learn the methodology behind their |
| 47 | Slackware worked well as a tui, but I wasn't skilled enough to get a tiling | 47 | packaging systems. |
| 48 | window manager (WM) working on it. | 48 | |
| 49 | 49 | Basically, everything else I tested was unacceptable in at least one way | |
| 50 | ### i3 | 50 | or another. Void (=glibc=) was great, but doesn't support all the |
| 51 | 51 | software I need. Slackware worked well as a tui, but I wasn't skilled | |
| 52 | One of the reasons I settled on Fedora is that it comes with an official i3 | 52 | enough to get a tiling window manager (WM) working on it. |
| 53 | spin. Being able to use a tiling WM, such as i3 or sway, is one of the biggest | 53 | |
| 54 | things I wanted to do as soon as I adopted Linux again. | 54 | *** i3 |
| 55 | 55 | :PROPERTIES: | |
| 56 | I will probably set up a dotfile repository soon, so that I don't lose any | 56 | :CUSTOM_ID: i3 |
| 57 | of my configurations, but nothing big has been configured thus far. | 57 | :END: |
| 58 | 58 | One of the reasons I settled on Fedora is that it comes with an official | |
| 59 | The two main things I have updated in i3wm are natural scrolling and binding my | 59 | i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of |
| 60 | brightness keys to the `brightnessctl` program. | 60 | the biggest things I wanted to do as soon as I adopted Linux again. |
| 61 | 61 | ||
| 62 | #### Natural Scrolling | 62 | I will probably set up a dotfile repository soon, so that I don't lose |
| 63 | 63 | any of my configurations, but nothing big has been configured thus far. | |
| 64 | |||
| 65 | The two main things I have updated in i3wm are natural scrolling and | ||
| 66 | binding my brightness keys to the =brightnessctl= program. | ||
| 67 | |||
| 68 | **** Natural Scrolling | ||
| 69 | :PROPERTIES: | ||
| 70 | :CUSTOM_ID: natural-scrolling | ||
| 71 | :END: | ||
| 64 | You can enable natural scrolling by opening the following file: | 72 | You can enable natural scrolling by opening the following file: |
| 65 | 73 | ||
| 66 | ```sh | 74 | #+begin_src sh |
| 67 | sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf | 75 | sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf |
| 68 | ``` | 76 | #+end_src |
| 69 | 77 | ||
| 70 | Within the `40-libinput.conf` file, find the following input sections and enable | 78 | Within the =40-libinput.conf= file, find the following input sections |
| 71 | the natural scrolling option. | 79 | and enable the natural scrolling option. |
| 72 | 80 | ||
| 73 | This is the `pointer` section: | 81 | This is the =pointer= section: |
| 74 | 82 | ||
| 75 | ```conf | 83 | #+begin_src conf |
| 76 | Section "InputClass" | 84 | Section "InputClass" |
| 77 | Identifier "libinput pointer catchall" | 85 | Identifier "libinput pointer catchall" |
| 78 | MatchIsPointer "on" | 86 | MatchIsPointer "on" |
| @@ -80,11 +88,11 @@ Section "InputClass" | |||
| 80 | Driver "libinput" | 88 | Driver "libinput" |
| 81 | Option "NaturalScrolling" "True" | 89 | Option "NaturalScrolling" "True" |
| 82 | EndSection | 90 | EndSection |
| 83 | ``` | 91 | #+end_src |
| 84 | 92 | ||
| 85 | This is the `touchpad` section: | 93 | This is the =touchpad= section: |
| 86 | 94 | ||
| 87 | ```conf | 95 | #+begin_src conf |
| 88 | Section "InputClass" | 96 | Section "InputClass" |
| 89 | Identifier "libinput touchpad catchall" | 97 | Identifier "libinput touchpad catchall" |
| 90 | MatchIsTouchpad "on" | 98 | MatchIsTouchpad "on" |
| @@ -92,44 +100,53 @@ Section "InputClass" | |||
| 92 | Driver "libinput" | 100 | Driver "libinput" |
| 93 | Option "NaturalScrolling" "True" | 101 | Option "NaturalScrolling" "True" |
| 94 | EndSection | 102 | EndSection |
| 95 | ``` | 103 | #+end_src |
| 96 | 104 | ||
| 97 | #### Enabling Brightness Keys | 105 | **** Enabling Brightness Keys |
| 98 | 106 | :PROPERTIES: | |
| 99 | Likewise, enabling brightness key functionality is as simple as binding the keys | 107 | :CUSTOM_ID: enabling-brightness-keys |
| 100 | to the `brightnessctl` program. | 108 | :END: |
| 109 | Likewise, enabling brightness key functionality is as simple as binding | ||
| 110 | the keys to the =brightnessctl= program. | ||
| 101 | 111 | ||
| 102 | To do this, open up your i3 config file. Mine is located here: | 112 | To do this, open up your i3 config file. Mine is located here: |
| 103 | 113 | ||
| 104 | ```sh | 114 | #+begin_src sh |
| 105 | nano /home/<my-user>/.config/i3/config | 115 | nano /home/<my-user>/.config/i3/config |
| 106 | ``` | 116 | #+end_src |
| 107 | 117 | ||
| 108 | ```conf | 118 | #+begin_src conf |
| 109 | # Use brightnessctl to adjust brightness. | 119 | # Use brightnessctl to adjust brightness. |
| 110 | bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%- | 120 | bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%- |
| 111 | bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+ | 121 | bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+ |
| 112 | ``` | 122 | #+end_src |
| 113 | 123 | ||
| 114 | #### `polybar` | 124 | **** =polybar= |
| 115 | 125 | :PROPERTIES: | |
| 116 | Instead of using the default `i3status` bar, I have opted to use `polybar` | 126 | :CUSTOM_ID: polybar |
| 117 | instead (as you can also see in the screenshot above). | 127 | :END: |
| 118 | 128 | Instead of using the default =i3status= bar, I have opted to use | |
| 119 | My config for this menu bar is basically just the default settings with modified | 129 | =polybar= instead (as you can also see in the screenshot above). |
| 120 | colors and an added battery block to quickly show me the machine's battery info. | 130 | |
| 121 | 131 | My config for this menu bar is basically just the default settings with | |
| 122 | #### `alacritty` | 132 | modified colors and an added battery block to quickly show me the |
| 123 | 133 | machine's battery info. | |
| 124 | Not much to say on this part yet, as I haven't configured it much, but I | 134 | |
| 125 | installed `alacritty` as my default terminal, and I am using `zsh` and the | 135 | **** =alacritty= |
| 126 | shell. | 136 | :PROPERTIES: |
| 127 | 137 | :CUSTOM_ID: alacritty | |
| 128 | ## Software Choices | 138 | :END: |
| 129 | 139 | Not much to say on this part yet, as I haven't configured it much, but I | |
| 130 | Again, I'm not going to say much that I haven't said yet in other blog | 140 | installed =alacritty= as my default terminal, and I am using =zsh= and |
| 131 | posts, so I'll just do a quick rundown of the apps I installed immediately after | 141 | the shell. |
| 132 | I set up the environment. | 142 | |
| 143 | ** Software Choices | ||
| 144 | :PROPERTIES: | ||
| 145 | :CUSTOM_ID: software-choices | ||
| 146 | :END: | ||
| 147 | Again, I'm not going to say much that I haven't said yet in other blog | ||
| 148 | posts, so I'll just do a quick rundown of the apps I installed | ||
| 149 | immediately after I set up the environment. | ||
| 133 | 150 | ||
| 134 | Flatpak Apps: | 151 | Flatpak Apps: |
| 135 | 152 | ||
blog/2022-07-01-git-server.org +357 −305
| @@ -1,244 +1,271 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting a Personal Git Server |
| 2 | date = 2022-07-01 | 2 | #+date: 2022-07-01 |
| 3 | title = "Self-Hosting a Personal Git Server" | ||
| 4 | description = "My retrospective on successfully setting up a personal git server at home." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## My Approach to Self-Hosting Git | 4 | ** My Approach to Self-Hosting Git |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: my-approach-to-self-hosting-git | ||
| 7 | :END: | ||
| 8 | I have often tried to self-host my Git repositories, but have always | ||
| 9 | fallen short when I tried to find a suitable web interface to show on | ||
| 10 | the front-end. | ||
| 8 | 11 | ||
| 9 | I have often tried to self-host my Git repositories, but have always fallen | 12 | After a few years, I have finally found a combination of methods that |
| 10 | short when I tried to find a suitable web interface to show on the front-end. | 13 | allow me to easily self-host my projects, view them on the web, and |
| 14 | access them from anywhere. | ||
| 11 | 15 | ||
| 12 | After a few years, I have finally found a combination of methods that allow me | 16 | Before I dive into the details, I want to state a high-level summary of |
| 13 | to easily self-host my projects, view them on the web, and access them from | 17 | my self-hosted Git approach: |
| 14 | anywhere. | ||
| 15 | 18 | ||
| 16 | Before I dive into the details, I want to state a high-level summary of my | 19 | - This method uses the =ssh://= (read & write) and =git://= (read-only) |
| 17 | self-hosted Git approach: | ||
| 18 | |||
| 19 | - This method uses the `ssh://` (read & write) and `git://` (read-only) | ||
| 20 | protocols for push and pull access. | 20 | protocols for push and pull access. |
| 21 | - For the `git://` protocol, I create a `git-daemon-export-ok` file in any | 21 | - For the =git://= protocol, I create a =git-daemon-export-ok= file in |
| 22 | repository that I want to be cloneable by anyone. | 22 | any repository that I want to be cloneable by anyone. |
| 23 | - The web interface I am using (`cgit`) allows simple HTTP cloning by | 23 | - The web interface I am using (=cgit=) allows simple HTTP cloning by |
| 24 | default. | 24 | default. I do not disable this setting as I want beginners to be |
| 25 | I do not disable this setting as I want beginners to be able to | 25 | able to clone one of my repositories even if they don't know the |
| 26 | clone one of my repositories even if they don't know the proper method. | 26 | proper method. |
| 27 | - I am not enabling Smart HTTPS for any repositories. Updates to repositories | 27 | - I am not enabling Smart HTTPS for any repositories. Updates to |
| 28 | must be pushed via SSH. | 28 | repositories must be pushed via SSH. |
| 29 | - Beyond the actual repository management, I am using `cgit` for the front-end | 29 | - Beyond the actual repository management, I am using =cgit= for the |
| 30 | web interface. | 30 | front-end web interface. |
| 31 | - If you use the `scan-path=<path>` configuration in the `cgitrc` | 31 | - If you use the =scan-path=<path>= configuration in the =cgitrc= |
| 32 | configuration file to automatically find repositories, you can't exclude a | 32 | configuration file to automatically find repositories, you can't |
| 33 | repository from `cgit` if it's stored within the path that `cgit` reads. To | 33 | exclude a repository from =cgit= if it's stored within the path that |
| 34 | host private repositories, you'd need to set up another directory that `cgit` | 34 | =cgit= reads. To host private repositories, you'd need to set up |
| 35 | can't read. | 35 | another directory that =cgit= can't read. |
| 36 | 36 | ||
| 37 | ## Assumptions | 37 | ** Assumptions |
| 38 | 38 | :PROPERTIES: | |
| 39 | :CUSTOM_ID: assumptions | ||
| 40 | :END: | ||
| 39 | For the purposes of this walkthrough, I am assuming you have a URL | 41 | For the purposes of this walkthrough, I am assuming you have a URL |
| 40 | (`git.example.com`) or IP address (`207.84.26.991`) addressed to the server that | 42 | (=git.example.com=) or IP address (=207.84.26.991=) addressed to the |
| 41 | you will be using to host your git repositories. | 43 | server that you will be using to host your git repositories. |
| 42 | 44 | ||
| 43 | ## Adding a Git User | 45 | ** Adding a Git User |
| 44 | 46 | :PROPERTIES: | |
| 45 | In order to use the SSH method associated with git, we will need to add a | 47 | :CUSTOM_ID: adding-a-git-user |
| 46 | user named `git`. If you have used the SSH method for other git hosting sites, | 48 | :END: |
| 47 | you are probably used to the following syntax: | 49 | In order to use the SSH method associated with git, we will need to add |
| 48 | 50 | a user named =git=. If you have used the SSH method for other git | |
| 49 | ```sh | 51 | hosting sites, you are probably used to the following syntax: |
| 52 | |||
| 53 | #+begin_src sh | ||
| 50 | git clone [user@]server:project.git | 54 | git clone [user@]server:project.git |
| 51 | ``` | 55 | #+end_src |
| 52 | 56 | ||
| 53 | The syntax above is an `scp`-like syntax for using SSH on the `git` user on the | 57 | The syntax above is an =scp=-like syntax for using SSH on the =git= user |
| 54 | server to access your repository. | 58 | on the server to access your repository. |
| 55 | 59 | ||
| 56 | Let's delete any remnants of an old `git` user, if any, and create the new user | 60 | Let's delete any remnants of an old =git= user, if any, and create the |
| 57 | account: | 61 | new user account: |
| 58 | 62 | ||
| 59 | ```sh | 63 | #+begin_src sh |
| 60 | sudo deluser --remove-home git | 64 | sudo deluser --remove-home git |
| 61 | sudo adduser git | 65 | sudo adduser git |
| 62 | ``` | 66 | #+end_src |
| 63 | 67 | ||
| 64 | ### Import Your SSH Keys to the Git User | 68 | *** Import Your SSH Keys to the Git User |
| 65 | 69 | :PROPERTIES: | |
| 66 | Once the `git` user is created, you will need to copy your public SSH key on | 70 | :CUSTOM_ID: import-your-ssh-keys-to-the-git-user |
| 67 | your local development machine to the `git` user on the server. | 71 | :END: |
| 72 | Once the =git= user is created, you will need to copy your public SSH | ||
| 73 | key on your local development machine to the =git= user on the server. | ||
| 68 | 74 | ||
| 69 | If you don't have an SSH key yet, create one with this command: | 75 | If you don't have an SSH key yet, create one with this command: |
| 70 | 76 | ||
| 71 | ```sh | 77 | #+begin_src sh |
| 72 | ssh-keygen | 78 | ssh-keygen |
| 73 | ``` | 79 | #+end_src |
| 74 | 80 | ||
| 75 | Once you create the key pair, the public should be saved to `~/.ssh/id_rsa.pub`. | 81 | Once you create the key pair, the public should be saved to |
| 82 | =~/.ssh/id_rsa.pub=. | ||
| 76 | 83 | ||
| 77 | If your server still has password-based authentication available, you can copy | 84 | If your server still has password-based authentication available, you |
| 78 | it over to your user's home directory like this: | 85 | can copy it over to your user's home directory like this: |
| 79 | 86 | ||
| 80 | ```sh | 87 | #+begin_src sh |
| 81 | ssh-copy-id git@server | 88 | ssh-copy-id git@server |
| 82 | ``` | 89 | #+end_src |
| 83 | 90 | ||
| 84 | Otherwise, copy it over to any user that you can access. | 91 | Otherwise, copy it over to any user that you can access. |
| 85 | 92 | ||
| 86 | ```sh | 93 | #+begin_src sh |
| 87 | scp ~/.ssh/id_rsa.pub your_user@your_server: | 94 | scp ~/.ssh/id_rsa.pub your_user@your_server: |
| 88 | ``` | 95 | #+end_src |
| 89 | 96 | ||
| 90 | Once on the server, you will need to copy the contents into the `git` user's | 97 | Once on the server, you will need to copy the contents into the =git= |
| 91 | `authorized_keys` file: | 98 | user's =authorized_keys= file: |
| 92 | 99 | ||
| 93 | ```sh | 100 | #+begin_src sh |
| 94 | cat id_rsa.pub > /home/git/.ssh/authorized_keys | 101 | cat id_rsa.pub > /home/git/.ssh/authorized_keys |
| 95 | ``` | 102 | #+end_src |
| 96 | |||
| 97 | ### (Optional) Disable Password-Based SSH | ||
| 98 | 103 | ||
| 99 | If you want to lock down your server and ensure that no one can authenticate in | 104 | *** (Optional) Disable Password-Based SSH |
| 100 | via SSH with a password, you will need to edit your SSH configuration. | 105 | :PROPERTIES: |
| 106 | :CUSTOM_ID: optional-disable-password-based-ssh | ||
| 107 | :END: | ||
| 108 | If you want to lock down your server and ensure that no one can | ||
| 109 | authenticate in via SSH with a password, you will need to edit your SSH | ||
| 110 | configuration. | ||
| 101 | 111 | ||
| 102 | ```sh | 112 | #+begin_src sh |
| 103 | sudo nano /etc/ssh/sshd_config | 113 | sudo nano /etc/ssh/sshd_config |
| 104 | ``` | 114 | #+end_src |
| 105 | 115 | ||
| 106 | Within this file, find the following settings and set them to the values I am | 116 | Within this file, find the following settings and set them to the values |
| 107 | showing below: | 117 | I am showing below: |
| 108 | 118 | ||
| 109 | ```conf | 119 | #+begin_src conf |
| 110 | PermitRootLogin no | 120 | PermitRootLogin no |
| 111 | PasswordAuthentication no | 121 | PasswordAuthentication no |
| 112 | AuthenticationMethods publickey | 122 | AuthenticationMethods publickey |
| 113 | ``` | 123 | #+end_src |
| 114 | 124 | ||
| 115 | You may have other Authentication Methods required in your personal set-up, so | 125 | You may have other Authentication Methods required in your personal |
| 116 | the key here is just to ensure that `AuthenticationMethods` does not allow | 126 | set-up, so the key here is just to ensure that =AuthenticationMethods= |
| 117 | passwords. | 127 | does not allow passwords. |
| 118 | 128 | ||
| 119 | ## Setting up the Base Directory | 129 | ** Setting up the Base Directory |
| 130 | :PROPERTIES: | ||
| 131 | :CUSTOM_ID: setting-up-the-base-directory | ||
| 132 | :END: | ||
| 133 | Now that we have set up a =git= user to handle all transport methods, we | ||
| 134 | need to set up the directory that we will be using as our base of all | ||
| 135 | repositories. | ||
| 120 | 136 | ||
| 121 | Now that we have set up a `git` user to handle all transport methods, we need | 137 | In my case, I am using =/git= as my source folder. To create this folder |
| 122 | to set up the directory that we will be using as our base of all repositories. | 138 | and assign it to the user we created, execute the following commands: |
| 123 | 139 | ||
| 124 | In my case, I am using `/git` as my source folder. To create this folder and | 140 | #+begin_src sh |
| 125 | assign it to the user we created, execute the following commands: | ||
| 126 | |||
| 127 | ```sh | ||
| 128 | sudo mkdir /git | 141 | sudo mkdir /git |
| 129 | sudo chown -R git:git /git | 142 | sudo chown -R git:git /git |
| 130 | ``` | 143 | #+end_src |
| 131 | |||
| 132 | ## Creating a Test Repository | ||
| 133 | 144 | ||
| 134 | On your server, switch over to the `git` user in order to start managing git | 145 | ** Creating a Test Repository |
| 135 | files. | 146 | :PROPERTIES: |
| 147 | :CUSTOM_ID: creating-a-test-repository | ||
| 148 | :END: | ||
| 149 | On your server, switch over to the =git= user in order to start managing | ||
| 150 | git files. | ||
| 136 | 151 | ||
| 137 | ```sh | 152 | #+begin_src sh |
| 138 | su git | 153 | su git |
| 139 | ``` | 154 | #+end_src |
| 140 | 155 | ||
| 141 | Once logged-in as the `git` user, go to your base directory and create a test | 156 | Once logged-in as the =git= user, go to your base directory and create a |
| 142 | repository. | 157 | test repository. |
| 143 | 158 | ||
| 144 | ```sh | 159 | #+begin_src sh |
| 145 | cd /git | 160 | cd /git |
| 146 | mkdir test.git && cd test.git | 161 | mkdir test.git && cd test.git |
| 147 | git init --bare | 162 | git init --bare |
| 148 | ``` | 163 | #+end_src |
| 149 | 164 | ||
| 150 | If you want to make this repo viewable/cloneable to the public via the `git://` | 165 | If you want to make this repo viewable/cloneable to the public via the |
| 151 | protocol, you need to create a `git-daemon-export-ok` file inside the | 166 | =git://= protocol, you need to create a =git-daemon-export-ok= file |
| 152 | repository. | 167 | inside the repository. |
| 153 | 168 | ||
| 154 | ```sh | 169 | #+begin_src sh |
| 155 | touch git-daemon-export-ok | 170 | touch git-daemon-export-ok |
| 156 | ``` | 171 | #+end_src |
| 157 | 172 | ||
| 158 | ## Change the Login Shell for `git` | 173 | ** Change the Login Shell for =git= |
| 174 | :PROPERTIES: | ||
| 175 | :CUSTOM_ID: change-the-login-shell-for-git | ||
| 176 | :END: | ||
| 177 | To make sure that the =git= user is only used for git operations and | ||
| 178 | nothing else, you need to change the user's login shell. To do this, | ||
| 179 | simply use the =chsh= command: | ||
| 159 | 180 | ||
| 160 | To make sure that the `git` user is only used for git operations and nothing | 181 | #+begin_src sh |
| 161 | else, you need to change the user's login shell. To do this, simply use the | ||
| 162 | `chsh` command: | ||
| 163 | |||
| 164 | ```sh | ||
| 165 | sudo chsh git | 182 | sudo chsh git |
| 166 | ``` | 183 | #+end_src |
| 167 | 184 | ||
| 168 | The interactive prompt will ask which shell you want the `git` user to use. You | 185 | The interactive prompt will ask which shell you want the =git= user to |
| 169 | must use the following value: | 186 | use. You must use the following value: |
| 170 | 187 | ||
| 171 | ```sh | 188 | #+begin_src sh |
| 172 | /usr/bin/git-shell | 189 | /usr/bin/git-shell |
| 173 | ``` | 190 | #+end_src |
| 174 | |||
| 175 | Once done, no one will be able to SSH to the `git` user or execute commands | ||
| 176 | other than the standard git commands. | ||
| 177 | 191 | ||
| 178 | ## Opening the Firewall | 192 | Once done, no one will be able to SSH to the =git= user or execute |
| 193 | commands other than the standard git commands. | ||
| 179 | 194 | ||
| 180 | Don't forget to open up ports on the device firewall and network firewall if | 195 | ** Opening the Firewall |
| 181 | you want to access these repositories publicly. If you're using default ports, | 196 | :PROPERTIES: |
| 182 | forward ports `22` (ssh) and `9418` (git) from your router to your server's IP | 197 | :CUSTOM_ID: opening-the-firewall |
| 183 | address. | 198 | :END: |
| 199 | Don't forget to open up ports on the device firewall and network | ||
| 200 | firewall if you want to access these repositories publicly. If you're | ||
| 201 | using default ports, forward ports =22= (ssh) and =9418= (git) from your | ||
| 202 | router to your server's IP address. | ||
| 184 | 203 | ||
| 185 | If your server also has a firewall, ensure that the firewall allows the same | 204 | If your server also has a firewall, ensure that the firewall allows the |
| 186 | ports that are forwarded from the router. For example, if you use `ufw`: | 205 | same ports that are forwarded from the router. For example, if you use |
| 206 | =ufw=: | ||
| 187 | 207 | ||
| 188 | ```sh | 208 | #+begin_src sh |
| 189 | sudo ufw allow 22 | 209 | sudo ufw allow 22 |
| 190 | sudo ufw allow 9418 | 210 | sudo ufw allow 9418 |
| 191 | ``` | 211 | #+end_src |
| 192 | |||
| 193 | ### Non-Standard SSH Ports | ||
| 194 | 212 | ||
| 195 | If you use a non-standard port for SSH, such as `9876`, you will need to create | 213 | *** Non-Standard SSH Ports |
| 196 | an SSH configuration file on your local development machine in order to connect | 214 | :PROPERTIES: |
| 197 | to your server's git repositories. | 215 | :CUSTOM_ID: non-standard-ssh-ports |
| 216 | :END: | ||
| 217 | If you use a non-standard port for SSH, such as =9876=, you will need to | ||
| 218 | create an SSH configuration file on your local development machine in | ||
| 219 | order to connect to your server's git repositories. | ||
| 198 | 220 | ||
| 199 | To do this, you'll need to define your custom port on your client machine in | 221 | To do this, you'll need to define your custom port on your client |
| 200 | your `~/.ssh/config` file: | 222 | machine in your =~/.ssh/config= file: |
| 201 | 223 | ||
| 202 | ```sh | 224 | #+begin_src sh |
| 203 | nano ~/.ssh/config | 225 | nano ~/.ssh/config |
| 204 | ``` | 226 | #+end_src |
| 205 | 227 | ||
| 206 | ```conf | 228 | #+begin_src conf |
| 207 | Host git.example.com | 229 | Host git.example.com |
| 208 | # HostName can be a URL or an IP address | 230 | # HostName can be a URL or an IP address |
| 209 | HostName git.example.com | 231 | HostName git.example.com |
| 210 | Port 9876 | 232 | Port 9876 |
| 211 | User git | 233 | User git |
| 212 | ``` | 234 | #+end_src |
| 213 | |||
| 214 | ### Testing SSH | ||
| 215 | 235 | ||
| 236 | *** Testing SSH | ||
| 237 | :PROPERTIES: | ||
| 238 | :CUSTOM_ID: testing-ssh | ||
| 239 | :END: | ||
| 216 | There are two main syntaxes you can use to manage git over SSH: | 240 | There are two main syntaxes you can use to manage git over SSH: |
| 217 | 241 | ||
| 218 | - `git clone [user@]server:project.git` | 242 | - =git clone [user@]server:project.git= |
| 219 | - `git clone ssh://[user@]server/project.git` | 243 | - =git clone ssh://[user@]server/project.git= |
| 220 | 244 | ||
| 221 | I prefer the first, which is an `scp`-like syntax. To test it, try to clone the | 245 | I prefer the first, which is an =scp=-like syntax. To test it, try to |
| 222 | test repository you set up on the server: | 246 | clone the test repository you set up on the server: |
| 223 | 247 | ||
| 224 | ```sh | 248 | #+begin_src sh |
| 225 | git clone git@git.example.com:/git/test.git | 249 | git clone git@git.example.com:/git/test.git |
| 226 | ``` | 250 | #+end_src |
| 227 | |||
| 228 | ## Enabling Read-Only Access | ||
| 229 | 251 | ||
| 230 | If you want people to be able to clone any repository where you've placed a | 252 | ** Enabling Read-Only Access |
| 231 | `git-daemon-export-ok` file, you will need to start the git daemon. | 253 | :PROPERTIES: |
| 254 | :CUSTOM_ID: enabling-read-only-access | ||
| 255 | :END: | ||
| 256 | If you want people to be able to clone any repository where you've | ||
| 257 | placed a =git-daemon-export-ok= file, you will need to start the git | ||
| 258 | daemon. | ||
| 232 | 259 | ||
| 233 | To do this on a system with `systemd`, create a service file: | 260 | To do this on a system with =systemd=, create a service file: |
| 234 | 261 | ||
| 235 | ```sh | 262 | #+begin_src sh |
| 236 | sudo nano /etc/systemd/system/git-daemon.service | 263 | sudo nano /etc/systemd/system/git-daemon.service |
| 237 | ``` | 264 | #+end_src |
| 238 | 265 | ||
| 239 | Inside the `git-daemon.service` file, paste the following: | 266 | Inside the =git-daemon.service= file, paste the following: |
| 240 | 267 | ||
| 241 | ```conf | 268 | #+begin_src conf |
| 242 | [Unit] | 269 | [Unit] |
| 243 | Description=Start Git Daemon | 270 | Description=Start Git Daemon |
| 244 | 271 | ||
| @@ -257,72 +284,79 @@ Group=git | |||
| 257 | 284 | ||
| 258 | [Install] | 285 | [Install] |
| 259 | WantedBy=multi-user.target | 286 | WantedBy=multi-user.target |
| 260 | ``` | 287 | #+end_src |
| 261 | 288 | ||
| 262 | Once created, enable and start the service: | 289 | Once created, enable and start the service: |
| 263 | 290 | ||
| 264 | ```sh | 291 | #+begin_src sh |
| 265 | sudo systemctl enable git-daemon.service | 292 | sudo systemctl enable git-daemon.service |
| 266 | sudo systemctl start git-daemon.service | 293 | sudo systemctl start git-daemon.service |
| 267 | ``` | 294 | #+end_src |
| 268 | 295 | ||
| 269 | To clone read-only via the `git://` protocol, you can use the following syntax: | 296 | To clone read-only via the =git://= protocol, you can use the following |
| 297 | syntax: | ||
| 270 | 298 | ||
| 271 | ```sh | 299 | #+begin_src sh |
| 272 | git clone git://git.example.com/test.git | 300 | git clone git://git.example.com/test.git |
| 273 | ``` | 301 | #+end_src |
| 274 | |||
| 275 | ## Migrating Repositories | ||
| 276 | 302 | ||
| 303 | ** Migrating Repositories | ||
| 304 | :PROPERTIES: | ||
| 305 | :CUSTOM_ID: migrating-repositories | ||
| 306 | :END: | ||
| 277 | At this point, we have a working git server that works with both SSH and | 307 | At this point, we have a working git server that works with both SSH and |
| 278 | read-only access. | 308 | read-only access. |
| 279 | 309 | ||
| 280 | For each of the repositories I had hosted a different provider, I executed the | 310 | For each of the repositories I had hosted a different provider, I |
| 281 | following commands in order to place a copy on my server as my new source of | 311 | executed the following commands in order to place a copy on my server as |
| 282 | truth: | 312 | my new source of truth: |
| 283 | 313 | ||
| 284 | Server: | 314 | Server: |
| 285 | 315 | ||
| 286 | ```sh | 316 | #+begin_src sh |
| 287 | su git | 317 | su git |
| 288 | mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git | 318 | mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git |
| 289 | git init --bare | 319 | git init --bare |
| 290 | 320 | ||
| 291 | # If you want to make this repo viewable/cloneable to the public | 321 | # If you want to make this repo viewable/cloneable to the public |
| 292 | touch git-daemon-export-ok | 322 | touch git-daemon-export-ok |
| 293 | ``` | 323 | #+end_src |
| 294 | 324 | ||
| 295 | Client: | 325 | Client: |
| 296 | 326 | ||
| 297 | ```sh | 327 | #+begin_src sh |
| 298 | git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME> | 328 | git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME> |
| 299 | git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git | 329 | git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git |
| 300 | git push | 330 | git push |
| 301 | ``` | 331 | #+end_src |
| 302 | 332 | ||
| 303 | ## Optional Web View: `cgit` | 333 | ** Optional Web View: =cgit= |
| 304 | 334 | :PROPERTIES: | |
| 305 | If you want a web viewer for your repositories, you can use various tools, such | 335 | :CUSTOM_ID: optional-web-view-cgit |
| 306 | as `gitweb`, `cgit`, or `klaus`. I chose `cgit` due to its simple interface and | 336 | :END: |
| 307 | fairly easy set-up (compared to others). Not to mention that the | 337 | If you want a web viewer for your repositories, you can use various |
| 308 | [Linux kernel uses `cgit`](https://git.kernel.org/). | 338 | tools, such as =gitweb=, =cgit=, or =klaus=. I chose =cgit= due to its |
| 309 | 339 | simple interface and fairly easy set-up (compared to others). Not to | |
| 310 | ### Docker Compose | 340 | mention that the [[https://git.kernel.org/][Linux kernel uses =cgit=]]. |
| 311 | 341 | ||
| 312 | Instead of using my previous method of using a `docker run` command, I've | 342 | *** Docker Compose |
| 313 | updated this section to use `docker-compose` instead for an easier installation | 343 | :PROPERTIES: |
| 314 | and simpler management and configuration. | 344 | :CUSTOM_ID: docker-compose |
| 315 | 345 | :END: | |
| 316 | In order to use Docker Compose, you will set up a `docker-compose.yml` file to | 346 | Instead of using my previous method of using a =docker run= command, |
| 317 | automatically connect resources like the repositories, `cgitrc`, and various | 347 | I've updated this section to use =docker-compose= instead for an easier |
| 318 | files or folders to the `cgit` container you're creating: | 348 | installation and simpler management and configuration. |
| 319 | 349 | ||
| 320 | ```sh | 350 | In order to use Docker Compose, you will set up a =docker-compose.yml= |
| 351 | file to automatically connect resources like the repositories, =cgitrc=, | ||
| 352 | and various files or folders to the =cgit= container you're creating: | ||
| 353 | |||
| 354 | #+begin_src sh | ||
| 321 | mkdir ~/cgit && cd ~/cgit | 355 | mkdir ~/cgit && cd ~/cgit |
| 322 | nano docker-compose.yml | 356 | nano docker-compose.yml |
| 323 | ``` | 357 | #+end_src |
| 324 | 358 | ||
| 325 | ```conf | 359 | #+begin_src conf |
| 326 | # docker-compose.yml | 360 | # docker-compose.yml |
| 327 | version: '3' | 361 | version: '3' |
| 328 | 362 | ||
| @@ -338,39 +372,41 @@ services: | |||
| 338 | ports: | 372 | ports: |
| 339 | - "8763:80" | 373 | - "8763:80" |
| 340 | restart: always | 374 | restart: always |
| 341 | ``` | 375 | #+end_src |
| 342 | 376 | ||
| 343 | Then, just start the container: | 377 | Then, just start the container: |
| 344 | 378 | ||
| 345 | ```sh | 379 | #+begin_src sh |
| 346 | sudo docker-compose up -d | 380 | sudo docker-compose up -d |
| 347 | ``` | 381 | #+end_src |
| 348 | 382 | ||
| 349 | Once it's finished installing, you can access the site at `<SERVER_IP>:8763` or | 383 | Once it's finished installing, you can access the site at |
| 350 | use a reverse-proxy service to forward `cgit` to a URL, such as | 384 | =<SERVER_IP>:8763= or use a reverse-proxy service to forward =cgit= to a |
| 351 | `git.example.com`. See the next section for more details on reverse proxying a | 385 | URL, such as =git.example.com=. See the next section for more details on |
| 352 | URL to a local port. | 386 | reverse proxying a URL to a local port. |
| 353 | 387 | ||
| 354 | ### Nginx Reverse Proxy | 388 | *** Nginx Reverse Proxy |
| 355 | 389 | :PROPERTIES: | |
| 356 | I am using Nginx as my reverse proxy so that the `cgit` Docker container can use | 390 | :CUSTOM_ID: nginx-reverse-proxy |
| 357 | `git.example.com` as its URL. To do so, I simply created the following | 391 | :END: |
| 358 | configuration file: | 392 | I am using Nginx as my reverse proxy so that the =cgit= Docker container |
| 359 | 393 | can use =git.example.com= as its URL. To do so, I simply created the | |
| 360 | ```sh | 394 | following configuration file: |
| 395 | |||
| 396 | #+begin_src sh | ||
| 361 | sudo nano /etc/nginx/sites-available/git.example.com | 397 | sudo nano /etc/nginx/sites-available/git.example.com |
| 362 | ``` | 398 | #+end_src |
| 363 | 399 | ||
| 364 | ```conf | 400 | #+begin_src conf |
| 365 | server { | 401 | server { |
| 366 | listen 80; | 402 | listen 80; |
| 367 | server_name git.example.com; | 403 | server_name git.example.com; |
| 368 | 404 | ||
| 369 | if ($host = git.example.com) { | 405 | if ($host = git.example.com) { |
| 370 | return 301 https://$host$request_uri; | 406 | return 301 https://$host$request_uri; |
| 371 | } | 407 | } |
| 372 | 408 | ||
| 373 | return 404; | 409 | return 404; |
| 374 | } | 410 | } |
| 375 | 411 | ||
| 376 | server { | 412 | server { |
| @@ -379,7 +415,7 @@ server { | |||
| 379 | 415 | ||
| 380 | location / { | 416 | location / { |
| 381 | # The final `/` is important. | 417 | # The final `/` is important. |
| 382 | proxy_pass http://localhost:8763/; | 418 | proxy_pass http://localhost:8763/; |
| 383 | add_header X-Frame-Options SAMEORIGIN; | 419 | add_header X-Frame-Options SAMEORIGIN; |
| 384 | add_header X-XSS-Protection "1; mode=block"; | 420 | add_header X-XSS-Protection "1; mode=block"; |
| 385 | proxy_redirect off; | 421 | proxy_redirect off; |
| @@ -395,64 +431,71 @@ server { | |||
| 395 | include /etc/letsencrypt/options-ssl-nginx.conf; | 431 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 396 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 432 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 397 | } | 433 | } |
| 398 | 434 | #+end_src | |
| 399 | ``` | ||
| 400 | 435 | ||
| 401 | Once created, symlink it and restart the web server. | 436 | Once created, symlink it and restart the web server. |
| 402 | 437 | ||
| 403 | ```sh | 438 | #+begin_src sh |
| 404 | sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/ | 439 | sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/ |
| 405 | sudo systemctl restart nginx.service | 440 | sudo systemctl restart nginx.service |
| 406 | ``` | 441 | #+end_src |
| 407 | |||
| 408 | As we can see below, my site at `git.example.com` is available and running: | ||
| 409 | 442 | ||
| 410 | ### Settings Up Git Details | 443 | As we can see below, my site at =git.example.com= is available and |
| 444 | running: | ||
| 411 | 445 | ||
| 412 | Once you have `cgit` running, you can add some small details, such as repository | 446 | *** Settings Up Git Details |
| 413 | owners and descriptions by editing the following files within each repository. | 447 | :PROPERTIES: |
| 448 | :CUSTOM_ID: settings-up-git-details | ||
| 449 | :END: | ||
| 450 | Once you have =cgit= running, you can add some small details, such as | ||
| 451 | repository owners and descriptions by editing the following files within | ||
| 452 | each repository. | ||
| 414 | 453 | ||
| 415 | Alternatively, you can use the `cgitrc` file to edit these details if you only | 454 | Alternatively, you can use the =cgitrc= file to edit these details if |
| 416 | care to edit them for the purpose of seeing them on your website. | 455 | you only care to edit them for the purpose of seeing them on your |
| 456 | website. | ||
| 417 | 457 | ||
| 418 | The `description` file within the repository on your server will display the | 458 | The =description= file within the repository on your server will display |
| 419 | description online. | 459 | the description online. |
| 420 | 460 | ||
| 421 | ```sh | 461 | #+begin_src sh |
| 422 | cd /git/example.git | 462 | cd /git/example.git |
| 423 | nano description | 463 | nano description |
| 424 | ``` | 464 | #+end_src |
| 425 | 465 | ||
| 426 | You can add a `[gitweb]` block to the `config` file in order to display the | 466 | You can add a =[gitweb]= block to the =config= file in order to display |
| 427 | owner of the repository. | 467 | the owner of the repository. |
| 428 | 468 | ||
| 429 | ```sh | 469 | #+begin_src sh |
| 430 | cd /git/example.git | 470 | cd /git/example.git |
| 431 | nano config | 471 | nano config |
| 432 | ``` | 472 | #+end_src |
| 433 | 473 | ||
| 434 | ```conf | 474 | #+begin_src conf |
| 435 | [gitweb] | 475 | [gitweb] |
| 436 | owner = "YourName" | 476 | owner = "YourName" |
| 437 | ``` | 477 | #+end_src |
| 438 | 478 | ||
| 439 | Note that you can ignore the configuration within each repository and simply set | 479 | Note that you can ignore the configuration within each repository and |
| 440 | up this information in the `cgitrc` file, if you want to do it that way. | 480 | simply set up this information in the =cgitrc= file, if you want to do |
| 481 | it that way. | ||
| 441 | 482 | ||
| 442 | ### Editing `cgit` | 483 | *** Editing =cgit= |
| 484 | :PROPERTIES: | ||
| 485 | :CUSTOM_ID: editing-cgit | ||
| 486 | :END: | ||
| 487 | In order to edit certain items within =cgit=, you need to edit the | ||
| 488 | =cgitrc= file. | ||
| 443 | 489 | ||
| 444 | In order to edit certain items within `cgit`, you need to edit the `cgitrc` | 490 | #+begin_src sh |
| 445 | file. | ||
| 446 | |||
| 447 | ```sh | ||
| 448 | nano ~/cgit/cgitrc | 491 | nano ~/cgit/cgitrc |
| 449 | ``` | 492 | #+end_src |
| 450 | 493 | ||
| 451 | Below is an example configuration for `cgitrc`. | 494 | Below is an example configuration for =cgitrc=. You can find all the |
| 452 | You can find all the configuration options within the [configuration manual] | 495 | configuration options within the [configuration manual] |
| 453 | (https://git.zx2c4.com/cgit/plain/cgitrc.5.txt). | 496 | (https://git.zx2c4.com/cgit/plain/cgitrc.5.txt). |
| 454 | 497 | ||
| 455 | ```conf | 498 | #+begin_src conf |
| 456 | css=/cgit.css | 499 | css=/cgit.css |
| 457 | logo=/logo.png | 500 | logo=/logo.png |
| 458 | favicon=/favicon.png | 501 | favicon=/favicon.png |
| @@ -524,103 +567,112 @@ repo.path=/git/test.git | |||
| 524 | repo.readme=:README.md | 567 | repo.readme=:README.md |
| 525 | repo.owner=John Doe | 568 | repo.owner=John Doe |
| 526 | repo.desc=An example repository! | 569 | repo.desc=An example repository! |
| 527 | ``` | 570 | #+end_src |
| 528 | |||
| 529 | ### Final Fixes: Syntax Highlighting & README Rendering | ||
| 530 | 571 | ||
| 531 | After completing my initial install and playing around with it for a few days, I | 572 | *** Final Fixes: Syntax Highlighting & README Rendering |
| 532 | noticed two issues: | 573 | :PROPERTIES: |
| 574 | :CUSTOM_ID: final-fixes-syntax-highlighting-readme-rendering | ||
| 575 | :END: | ||
| 576 | After completing my initial install and playing around with it for a few | ||
| 577 | days, I noticed two issues: | ||
| 533 | 578 | ||
| 534 | 1. Syntax highlighting did not work when viewing the source code within a file. | 579 | 1. Syntax highlighting did not work when viewing the source code within |
| 535 | 2. The `about` tab within a repository was not rendered to HTML. | 580 | a file. |
| 581 | 2. The =about= tab within a repository was not rendered to HTML. | ||
| 536 | 582 | ||
| 537 | The following process fixes these issues. | 583 | The following process fixes these issues. To start, let's go to the |
| 538 | To start, let's go to the `cgit` directory where we were editing our | 584 | =cgit= directory where we were editing our configuration file earlier. |
| 539 | configuration file earlier. | ||
| 540 | 585 | ||
| 541 | ```sh | 586 | #+begin_src sh |
| 542 | cd ~/cgit | 587 | cd ~/cgit |
| 543 | ``` | 588 | #+end_src |
| 544 | 589 | ||
| 545 | In here, create two folders that will hold our syntax files: | 590 | In here, create two folders that will hold our syntax files: |
| 546 | 591 | ||
| 547 | ```sh | 592 | #+begin_src sh |
| 548 | mkdir filters && mkdir filters/html-converters && cd filters | 593 | mkdir filters && mkdir filters/html-converters && cd filters |
| 549 | ``` | 594 | #+end_src |
| 550 | 595 | ||
| 551 | Next, download the default filters: | 596 | Next, download the default filters: |
| 552 | 597 | ||
| 553 | ```sh | 598 | #+begin_src sh |
| 554 | curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh | 599 | curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh |
| 555 | chmod 755 about-formatting.sh | 600 | chmod 755 about-formatting.sh |
| 556 | curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py | 601 | curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py |
| 557 | chmod 755 syntax-highlighting.py | 602 | chmod 755 syntax-highlighting.py |
| 558 | ``` | 603 | #+end_src |
| 559 | 604 | ||
| 560 | Finally, download the HTML conversion files you need. | 605 | Finally, download the HTML conversion files you need. The example below |
| 561 | The example below downloads the Markdown converter: | 606 | downloads the Markdown converter: |
| 562 | 607 | ||
| 563 | ```sh | 608 | #+begin_src sh |
| 564 | cd html-converters | 609 | cd html-converters |
| 565 | curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html | 610 | curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html |
| 566 | chmod 755 md2html | 611 | chmod 755 md2html |
| 567 | ``` | 612 | #+end_src |
| 568 | 613 | ||
| 569 | If you need other filters or html-converters found within [the cgit project | 614 | If you need other filters or html-converters found within |
| 570 | files](https://git.zx2c4.com/cgit/tree/filters), repeat the `curl` and `chmod` | 615 | [[https://git.zx2c4.com/cgit/tree/filters][the cgit project files]], |
| 571 | process above for whichever files you need. | 616 | repeat the =curl= and =chmod= process above for whichever files you |
| 617 | need. | ||
| 572 | 618 | ||
| 573 | However, formatting will not work quite yet since the Docker cgit container | 619 | However, formatting will not work quite yet since the Docker cgit |
| 574 | we're using doesn't have the formatting package installed. You can install this | 620 | container we're using doesn't have the formatting package installed. You |
| 575 | easily by install Python 3+ and the `pygments` package: | 621 | can install this easily by install Python 3+ and the =pygments= package: |
| 576 | 622 | ||
| 577 | ```sh | 623 | #+begin_src sh |
| 578 | # Enter the container's command line | 624 | # Enter the container's command line |
| 579 | sudo docker exec -it cgit bash | 625 | sudo docker exec -it cgit bash |
| 580 | ``` | 626 | #+end_src |
| 581 | 627 | ||
| 582 | ```sh | 628 | #+begin_src sh |
| 583 | # Install the necessary packages and then exit | 629 | # Install the necessary packages and then exit |
| 584 | yum update -y && \ | 630 | yum update -y && \ |
| 585 | yum upgrade -y && \ | 631 | yum upgrade -y && \ |
| 586 | yum install python3 python3-pip -y && \ | 632 | yum install python3 python3-pip -y && \ |
| 587 | pip3 install markdown pygments && \ | 633 | pip3 install markdown pygments && \ |
| 588 | exit | 634 | exit |
| 589 | ``` | 635 | #+end_src |
| 590 | 636 | ||
| 591 | **You will need to enter the cgit docker container and re-run these `yum` | 637 | *You will need to enter the cgit docker container and re-run these =yum= |
| 592 | commands every time you kill and restart the container!** | 638 | commands every time you kill and restart the container!* |
| 593 | 639 | ||
| 594 | If not done already, we need to add the following variables to our | 640 | If not done already, we need to add the following variables to our |
| 595 | `cgitrc` file in order for `cgit` to know where our filtering files are: | 641 | =cgitrc= file in order for =cgit= to know where our filtering files are: |
| 596 | 642 | ||
| 597 | ```conf | 643 | #+begin_src conf |
| 598 | # Highlight source code with python pygments-based highlighter | 644 | # Highlight source code with python pygments-based highlighter |
| 599 | source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py | 645 | source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py |
| 600 | 646 | ||
| 601 | # Format markdown, restructuredtext, manpages, text files, and html files | 647 | # Format markdown, restructuredtext, manpages, text files, and html files |
| 602 | # through the right converters | 648 | # through the right converters |
| 603 | about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh | 649 | about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh |
| 604 | ``` | 650 | #+end_src |
| 605 | 651 | ||
| 606 | Now you should see that syntax highlighting and README rendering to the `about` | 652 | Now you should see that syntax highlighting and README rendering to the |
| 607 | tab is fixed. | 653 | =about= tab is fixed. |
| 608 | 654 | ||
| 609 | ### Theming | 655 | *** Theming |
| 610 | 656 | :PROPERTIES: | |
| 611 | I won't go into much detail in this section, but you can fully theme your | 657 | :CUSTOM_ID: theming |
| 612 | installation of `cgit` since you have access to the `cgit.css` file in your web | 658 | :END: |
| 613 | root. This is another file you can add as a volume to the `docker-compose.yml` | 659 | I won't go into much detail in this section, but you can fully theme |
| 614 | file if you want to edit this without entering the container's command line. | 660 | your installation of =cgit= since you have access to the =cgit.css= file |
| 615 | 661 | in your web root. This is another file you can add as a volume to the | |
| 616 | ## :warning: Remember to Back Up Your Data! | 662 | =docker-compose.yml= file if you want to edit this without entering the |
| 617 | 663 | container's command line. | |
| 618 | The last thing to note is that running services on your own equipment means that | 664 | |
| 619 | you're assuming a level of risk that exists regarding data loss, catastrophes, | 665 | ** :warning: Remember to Back Up Your Data! |
| 620 | etc. In order to reduce the impact of any such occurrence, I suggest backing up | 666 | :PROPERTIES: |
| 621 | your data regularly. | 667 | :CUSTOM_ID: warning-remember-to-back-up-your-data |
| 622 | 668 | :END: | |
| 623 | Backups can be automated via `cron`, by hooking your base directory up to a | 669 | The last thing to note is that running services on your own equipment |
| 624 | cloud provider, or even setting up hooks to push all repository info to git | 670 | means that you're assuming a level of risk that exists regarding data |
| 625 | mirrors on other git hosts. Whatever the method, make sure that your data | 671 | loss, catastrophes, etc. In order to reduce the impact of any such |
| 626 | doesn't vanish in the event that your drives or servers fail. | 672 | occurrence, I suggest backing up your data regularly. |
| 673 | |||
| 674 | Backups can be automated via =cron=, by hooking your base directory up | ||
| 675 | to a cloud provider, or even setting up hooks to push all repository | ||
| 676 | info to git mirrors on other git hosts. Whatever the method, make sure | ||
| 677 | that your data doesn't vanish in the event that your drives or servers | ||
| 678 | fail. | ||
blog/2022-07-14-gnupg.org +220 −197
| @@ -1,39 +1,41 @@ | |||
| 1 | +++ | 1 | #+title: GNU Privacy Guard (GPG) |
| 2 | date = 2022-07-14 | 2 | #+date: 2022-07-14 |
| 3 | title = "GNU Privacy Guard (GPG)" | 3 | |
| 4 | description = "An overview of the GPG encryption program and its uses." | 4 | ** The History of GPG |
| 5 | draft = false | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: the-history-of-gpg |
| 7 | 7 | :END: | |
| 8 | ## The History of GPG | 8 | [[https://gnupg.org/][GNU Privacy Guard]], also known as GnuPG and GPG, |
| 9 | 9 | is a free ("free" as in both speech and beer) software that fully | |
| 10 | [GNU Privacy Guard](https://gnupg.org/), also known as GnuPG and GPG, is a free | 10 | implements the OpenPGP Message Format documented in |
| 11 | ("free" as in both speech and beer) software that fully implements the OpenPGP | 11 | [[https://www.rfc-editor.org/rfc/rfc4880][RFC 4880]]. |
| 12 | Message Format documented in [RFC 4880](https://www.rfc-editor.org/rfc/rfc4880). | 12 | |
| 13 | 13 | I won't go in-depth on the full history of the software in this post, | |
| 14 | I won't go in-depth on the full history of the software in this post, but it is | 14 | but it is important to understand that GPG is not the same as PGP |
| 15 | important to understand that GPG is not the same as PGP (Pretty Good Privacy), | 15 | (Pretty Good Privacy), which is a different implementation of RFC 4880. |
| 16 | which is a different implementation of RFC 4880. However, GPG was designed to | 16 | However, GPG was designed to interoperate with PGP. |
| 17 | interoperate with PGP. | 17 | |
| 18 | 18 | GPG was originally developed in the late 1990s by | |
| 19 | GPG was originally developed in the late 1990s by [Werner | 19 | [[https://en.wikipedia.org/wiki/Werner_Koch][Werner Koch]] and has |
| 20 | Koch](https://en.wikipedia.org/wiki/Werner_Koch) and has historically been | 20 | historically been funded generously by the German government. |
| 21 | funded generously by the German government. | 21 | |
| 22 | 22 | Now that we have all the high-level info out of the way, let's dive into | |
| 23 | Now that we have all the high-level info out of the way, let's dive into the | 23 | the different aspects of GPG and its uses. |
| 24 | different aspects of GPG and its uses. | 24 | |
| 25 | 25 | ** Encryption Algorithms | |
| 26 | ## Encryption Algorithms | 26 | :PROPERTIES: |
| 27 | 27 | :CUSTOM_ID: encryption-algorithms | |
| 28 | GPG supports a wide range of different encryption algorithms, including | 28 | :END: |
| 29 | public-key, cipher, hash, and compression algorithms. The support for these | 29 | GPG supports a wide range of different encryption algorithms, including |
| 30 | algorithms has grown since the adoption of the Libgcrypt library in the 2.x | 30 | public-key, cipher, hash, and compression algorithms. The support for |
| 31 | versions of GPG. | 31 | these algorithms has grown since the adoption of the Libgcrypt library |
| 32 | 32 | in the 2.x versions of GPG. | |
| 33 | As you will be able to see below in an example of a full key generation with the | 33 | |
| 34 | GPG command line tool, GPG recommends the following algorithms to new users: | 34 | As you will be able to see below in an example of a full key generation |
| 35 | 35 | with the GPG command line tool, GPG recommends the following algorithms | |
| 36 | ```sh | 36 | to new users: |
| 37 | |||
| 38 | #+begin_src sh | ||
| 37 | Please select what kind of key you want: | 39 | Please select what kind of key you want: |
| 38 | (1) RSA and RSA | 40 | (1) RSA and RSA |
| 39 | (2) DSA and Elgamal | 41 | (2) DSA and Elgamal |
| @@ -41,80 +43,84 @@ Please select what kind of key you want: | |||
| 41 | (4) RSA (sign only) | 43 | (4) RSA (sign only) |
| 42 | (9) ECC (sign and encrypt) *default* | 44 | (9) ECC (sign and encrypt) *default* |
| 43 | (10) ECC (sign only) | 45 | (10) ECC (sign only) |
| 44 | ``` | 46 | #+end_src |
| 45 | 47 | ||
| 46 | I am not doing an in-depth explanation here in order to keep the focus on GPG | 48 | I am not doing an in-depth explanation here in order to keep the focus |
| 47 | and not encryption algorithms. If you want a deep dive into cryptography | 49 | on GPG and not encryption algorithms. If you want a deep dive into |
| 48 | or encryption algorithms, please read my other posts: | 50 | cryptography or encryption algorithms, please read my other posts: |
| 49 | 51 | ||
| 50 | - [AES Encryption](/blog/aes-encryption/) (2018) | 52 | - [[/blog/aes-encryption/][AES Encryption]] (2018) |
| 51 | - [Cryptography Basics](/blog/cryptography-basics/) (2020) | 53 | - [[/blog/cryptography-basics/][Cryptography Basics]] (2020) |
| 52 | 54 | ||
| 53 | ### Vulnerabilities | 55 | *** Vulnerabilities |
| 54 | 56 | :PROPERTIES: | |
| 55 | As of 2022-07-14, there are a few different vulnerabilities associated with | 57 | :CUSTOM_ID: vulnerabilities |
| 56 | GPG or the libraries it uses: | 58 | :END: |
| 57 | 59 | As of 2022-07-14, there are a few different vulnerabilities associated | |
| 58 | - GPG versions 1.0.2–1.2.3 contains a bug where "as soon as one | 60 | with GPG or the libraries it uses: |
| 59 | (GPG-generated) ElGamal signature of an arbitrary message is released, one | 61 | |
| 60 | can recover the signer's private key in less than a second on a PC." | 62 | - GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one |
| 61 | ([Source](https://www.di.ens.fr/~pnguyen/pub_Ng04.htm)) | 63 | (GPG-generated) ElGamal signature of an arbitrary message is released, |
| 62 | - GPG versions prior to 1.4.2.1 contain a false positive signature | 64 | one can recover the signer's private key in less than a second on a |
| 65 | PC." ([[https://www.di.ens.fr/~pnguyen/pub_Ng04.htm][Source]]) | ||
| 66 | - GPG versions prior to 1.4.2.1 contain a false positive signature | ||
| 63 | verification bug. | 67 | verification bug. |
| 64 | ([Source](https://lists.gnupg. | 68 | ([[https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html][Source]]) |
| 65 | org/pipermail/gnupg-announce/2006q1/000211.html)) | 69 | - GPG versions prior to 1.4.2.2 cannot detect injection of unsigned |
| 66 | - GPG versions prior to 1.4.2.2 cannot detect injection of unsigned data. | 70 | data. ( |
| 67 | ( | 71 | [[https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html][Source]]) |
| 68 | [Source](https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html)) | 72 | - Libgcrypt, a library used by GPG, contained a bug which enabled full |
| 69 | - Libgcrypt, a library used by GPG, contained a bug which enabled full key | 73 | key recovery for RSA-1024 and some RSA-2048 keys. This was resolved in |
| 70 | recovery for RSA-1024 and some RSA-2048 keys. | 74 | a GPG update in 2017. ([[https://lwn.net/Articles/727179/][Source]]) |
| 71 | This was resolved in a GPG update in 2017. | 75 | - The [[https://en.wikipedia.org/wiki/ROCA_vulnerability][ROCA |
| 72 | ([Source](https://lwn.net/Articles/727179/)) | 76 | Vulnerability]] affects RSA keys generated by YubiKey 4 tokens. |
| 73 | - The [ROCA Vulnerability](https://en.wikipedia.org/wiki/ROCA_vulnerability) | 77 | ([[https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf][Source]]) |
| 74 | affects RSA keys generated by YubiKey 4 tokens. | 78 | - The [[https://en.wikipedia.org/wiki/SigSpoof][SigSpoof Attack]] allows |
| 75 | ([Source](https://crocs.fi. | 79 | an attacker to spoof digital signatures. |
| 76 | muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf)) | 80 | ([[https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/][Source]]) |
| 77 | - The [SigSpoof Attack](https://en.wikipedia.org/wiki/SigSpoof) allows an | 81 | - Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer |
| 78 | attacker to spoof digital signatures. | 82 | overflow, fixed in Libgcrypt 1.9.1 |
| 79 | ([Source](https://arstechnica. | 83 | ([[https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/][Source]]) |
| 80 | com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/)) | 84 | |
| 81 | - Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer overflow, | 85 | ** Platforms |
| 82 | fixed in Libgcrypt 1.9.1 ([Source](https://web.archive. | 86 | :PROPERTIES: |
| 83 | org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/)) | 87 | :CUSTOM_ID: platforms |
| 84 | 88 | :END: | |
| 85 | ## Platforms | 89 | Originally developed as a command-line program for *nix systems, GPG now |
| 86 | 90 | has a wealth of front-end applications and libraries available for | |
| 87 | Originally developed as a command-line program for *nix systems, GPG now has a | 91 | end-users. However, the most recommended programs remain the same: |
| 88 | wealth of front-end applications and libraries available for end-users. However, | 92 | |
| 89 | the most recommended programs remain the same: | 93 | - [[https://gnupg.org][GnuPG]] for Linux (depending on distro) |
| 90 | 94 | - [[https://gpg4win.org][Gpg4win]] for Windows | |
| 91 | - [GnuPG](https://gnupg.org) for Linux (depending on distro) | 95 | - [[https://gpgtools.org][GPGTools]] for macOS |
| 92 | - [Gpg4win](https://gpg4win.org) for Windows | 96 | |
| 93 | - [GPGTools](https://gpgtools.org) for macOS | 97 | ** Creating a Key Pair |
| 94 | 98 | :PROPERTIES: | |
| 95 | ## Creating a Key Pair | 99 | :CUSTOM_ID: creating-a-key-pair |
| 96 | 100 | :END: | |
| 97 | In order to create a GPG key pair, a user would first need to install GPG on | 101 | In order to create a GPG key pair, a user would first need to install |
| 98 | their system. If we're assuming that the user is on Fedora Linux, they would | 102 | GPG on their system. If we're assuming that the user is on Fedora Linux, |
| 99 | execute the following: | 103 | they would execute the following: |
| 100 | 104 | ||
| 101 | ```sh | 105 | #+begin_src sh |
| 102 | sudo dnf install gpg | 106 | sudo dnf install gpg |
| 103 | ``` | 107 | #+end_src |
| 104 | 108 | ||
| 105 | Once installed, a user can create a new key pair with the following command(s): | 109 | Once installed, a user can create a new key pair with the following |
| 110 | command(s): | ||
| 106 | 111 | ||
| 107 | ```sh | 112 | #+begin_src sh |
| 108 | gpg --full-generate-key | 113 | gpg --full-generate-key |
| 109 | ``` | 114 | #+end_src |
| 110 | 115 | ||
| 111 | GPG will walk the user through an interactive setup that asks for an algorithm | 116 | GPG will walk the user through an interactive setup that asks for an |
| 112 | preference, expiration date, name, and email to associate with this key. | 117 | algorithm preference, expiration date, name, and email to associate with |
| 118 | this key. | ||
| 113 | 119 | ||
| 114 | See the following example key set-up for a default key generation using the | 120 | See the following example key set-up for a default key generation using |
| 115 | GnuPG command-line interface: | 121 | the GnuPG command-line interface: |
| 116 | 122 | ||
| 117 | ```sh | 123 | #+begin_src sh |
| 118 | gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc. | 124 | gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc. |
| 119 | This is free software: you are free to change and redistribute it. | 125 | This is free software: you are free to change and redistribute it. |
| 120 | There is NO WARRANTY, to the extent permitted by law. | 126 | There is NO WARRANTY, to the extent permitted by law. |
| @@ -166,13 +172,15 @@ pub ed25519 2022-07-14 [SC] | |||
| 166 | E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C | 172 | E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C |
| 167 | uid John Doe (test key) <johndoe@example.com> | 173 | uid John Doe (test key) <johndoe@example.com> |
| 168 | sub cv25519 2022-07-14 [E] | 174 | sub cv25519 2022-07-14 [E] |
| 169 | ``` | 175 | #+end_src |
| 170 | 176 | ||
| 171 | Please note that GUI apps may differ slightly from the GPG command-line | 177 | Please note that GUI apps may differ slightly from the GPG command-line |
| 172 | interface. | 178 | interface. |
| 173 | 179 | ||
| 174 | ## Common Usage | 180 | ** Common Usage |
| 175 | 181 | :PROPERTIES: | |
| 182 | :CUSTOM_ID: common-usage | ||
| 183 | :END: | ||
| 176 | As noted in RFC 4880, the general functions of OpenPGP are as follows: | 184 | As noted in RFC 4880, the general functions of OpenPGP are as follows: |
| 177 | 185 | ||
| 178 | - digital signatures | 186 | - digital signatures |
| @@ -181,98 +189,110 @@ As noted in RFC 4880, the general functions of OpenPGP are as follows: | |||
| 181 | - Radix-64 conversion | 189 | - Radix-64 conversion |
| 182 | - key management and certificate services | 190 | - key management and certificate services |
| 183 | 191 | ||
| 184 | From this, you can probably gather that the main use of GPG is for encrypting | 192 | From this, you can probably gather that the main use of GPG is for |
| 185 | data and/or signing the data with a key. The purpose of encrypting data with GPG | 193 | encrypting data and/or signing the data with a key. The purpose of |
| 186 | is to ensure that no one except the intended recipient(s) can access the data. | 194 | encrypting data with GPG is to ensure that no one except the intended |
| 195 | recipient(s) can access the data. | ||
| 187 | 196 | ||
| 188 | Let's explore some specific GPG use-cases. | 197 | Let's explore some specific GPG use-cases. |
| 189 | 198 | ||
| 190 | 199 | ||
| 191 | 200 | :PROPERTIES: | |
| 192 | One of the more popular uses of GPG is to sign and/or encrypt emails. With the | 201 | :CUSTOM_ID: email |
| 193 | use of a GPG keypair, you can encrypt a message, its subject, and even the | 202 | :END: |
| 194 | attachments within. | 203 | One of the more popular uses of GPG is to sign and/or encrypt emails. |
| 195 | 204 | With the use of a GPG keypair, you can encrypt a message, its subject, | |
| 196 | The first process, regarding the signing of a message without any encryption, is | 205 | and even the attachments within. |
| 197 | generally used to provide assurance that an email is truly coming from the | 206 | |
| 198 | sender that the message claims. When I send an email, and it's signed with my | 207 | The first process, regarding the signing of a message without any |
| 199 | public key, the recipient(s) of the message can verify that the message was | 208 | encryption, is generally used to provide assurance that an email is |
| 200 | signed with my personal key. | 209 | truly coming from the sender that the message claims. When I send an |
| 201 | 210 | email, and it's signed with my public key, the recipient(s) of the | |
| 202 | The second process, regarding the actual encryption of the message and its | 211 | message can verify that the message was signed with my personal key. |
| 203 | contents, works by using a combination of the sender's keys and the recipient's | 212 | |
| 204 | keys. This process may vary slightly by implementation, but it most commonly | 213 | The second process, regarding the actual encryption of the message and |
| 205 | uses asymmetric cryptography, also known as public-key cryptography. In this | 214 | its contents, works by using a combination of the sender's keys and the |
| 206 | version of encryption, the sender's private key to sign the message and a | 215 | recipient's keys. This process may vary slightly by implementation, but |
| 207 | combination of the sender's keys and the recipient's public key to encrypt the | 216 | it most commonly uses asymmetric cryptography, also known as public-key |
| 208 | message. | 217 | cryptography. In this version of encryption, the sender's private key to |
| 209 | 218 | sign the message and a combination of the sender's keys and the | |
| 210 | If two people each have their own private keys and exchange their public keys, | 219 | recipient's public key to encrypt the message. |
| 211 | they can send encrypted messages back and forth with GPG. This is also possible | 220 | |
| 212 | with symmetric cryptography, but the process differs since there are no key | 221 | If two people each have their own private keys and exchange their public |
| 213 | pairs. | 222 | keys, they can send encrypted messages back and forth with GPG. This is |
| 214 | 223 | also possible with symmetric cryptography, but the process differs since | |
| 215 | Implementation of email encryption varies greatly between email clients, so you | 224 | there are no key pairs. |
| 216 | will need to reference your email client's documentation to ensure you are | 225 | |
| 217 | setting it up correctly for that specific client. | 226 | Implementation of email encryption varies greatly between email clients, |
| 218 | 227 | so you will need to reference your email client's documentation to | |
| 219 | ### File Encryption | 228 | ensure you are setting it up correctly for that specific client. |
| 220 | 229 | ||
| 221 | As noted in the section above regarding emails, GPG enables users to be able to | 230 | *** File Encryption |
| 222 | send a message to each other if they are both set-up with GPG keys. | 231 | :PROPERTIES: |
| 223 | In this example, I am going to show how a user could send a file called | 232 | :CUSTOM_ID: file-encryption |
| 224 | `example_file.txt` to another user via the recipient's email. | 233 | :END: |
| 225 | 234 | As noted in the section above regarding emails, GPG enables users to be | |
| 226 | The sender would find the file they want to send and execute the following | 235 | able to send a message to each other if they are both set-up with GPG |
| 227 | command: | 236 | keys. In this example, I am going to show how a user could send a file |
| 228 | 237 | called =example_file.txt= to another user via the recipient's email. | |
| 229 | ```sh | 238 | |
| 239 | The sender would find the file they want to send and execute the | ||
| 240 | following command: | ||
| 241 | |||
| 242 | #+begin_src sh | ||
| 230 | gpg --encrypt --output example_file.txt.gpg --recipient \ | 243 | gpg --encrypt --output example_file.txt.gpg --recipient \ |
| 231 | recipient@example.com example_file.txt | 244 | recipient@example.com example_file.txt |
| 232 | ``` | 245 | #+end_src |
| 233 | 246 | ||
| 234 | Once received, the recipient can decrypt the file with the following command: | 247 | Once received, the recipient can decrypt the file with the following |
| 248 | command: | ||
| 235 | 249 | ||
| 236 | ```sh | 250 | #+begin_src sh |
| 237 | gpg --decrypt --output example_file.txt example_file.txt.gpg | 251 | gpg --decrypt --output example_file.txt example_file.txt.gpg |
| 238 | ``` | 252 | #+end_src |
| 239 | 253 | ||
| 240 | ### Ownership Signatures | 254 | *** Ownership Signatures |
| 241 | 255 | :PROPERTIES: | |
| 242 | One important aspect of GPG, especially for developers, is the ability to sign | 256 | :CUSTOM_ID: ownership-signatures |
| 243 | data without encrypting it. For example, developers often sign code changes when | 257 | :END: |
| 244 | they commit the changes back to a central repository, in order to display | 258 | One important aspect of GPG, especially for developers, is the ability |
| 245 | ownership of who made the changes. This allows other users to look at a code | 259 | to sign data without encrypting it. For example, developers often sign |
| 246 | change and determine that the change was valid. | 260 | code changes when they commit the changes back to a central repository, |
| 247 | 261 | in order to display ownership of who made the changes. This allows other | |
| 248 | In order to do this using [Git](https://git-scm.com), the developer simply needs | 262 | users to look at a code change and determine that the change was valid. |
| 249 | to alter the `git commit` command to include the `-S` flag. Here's an example: | 263 | |
| 250 | 264 | In order to do this using [[https://git-scm.com][Git]], the developer | |
| 251 | ```sh | 265 | simply needs to alter the =git commit= command to include the =-S= flag. |
| 266 | Here's an example: | ||
| 267 | |||
| 268 | #+begin_src sh | ||
| 252 | git commit -S -m "my commit message" | 269 | git commit -S -m "my commit message" |
| 253 | ``` | 270 | #+end_src |
| 254 | 271 | ||
| 255 | As an expansion of the example above, Git users can configure their environment | 272 | As an expansion of the example above, Git users can configure their |
| 256 | with a default key to use by adding their GPG signature: | 273 | environment with a default key to use by adding their GPG signature: |
| 257 | 274 | ||
| 258 | ```sh | 275 | #+begin_src sh |
| 259 | git config --global user.signingkey XXXXXXXXXXXXXXXX | 276 | git config --global user.signingkey XXXXXXXXXXXXXXXX |
| 260 | ``` | 277 | #+end_src |
| 261 | 278 | ||
| 262 | If you're not sure what your signature is, you can find it titled `sig` in the | 279 | If you're not sure what your signature is, you can find it titled =sig= |
| 263 | output of this command: | 280 | in the output of this command: |
| 264 | 281 | ||
| 265 | ```sh | 282 | #+begin_src sh |
| 266 | gpg --list-signatures | 283 | gpg --list-signatures |
| 267 | ``` | 284 | #+end_src |
| 268 | 285 | ||
| 269 | ### File Integrity | 286 | *** File Integrity |
| 270 | 287 | :PROPERTIES: | |
| 271 | When a person generates a signature for data, they are allowing users the | 288 | :CUSTOM_ID: file-integrity |
| 272 | ability to verify the signature on that data in the future to ensure the data | 289 | :END: |
| 273 | has not been corrupted. This is most common with software applications hosted on | 290 | When a person generates a signature for data, they are allowing users |
| 274 | the internet - developers provide signatures so that users can verify a website | 291 | the ability to verify the signature on that data in the future to ensure |
| 275 | was not hijacked and download links replaced with dangerous software. | 292 | the data has not been corrupted. This is most common with software |
| 293 | applications hosted on the internet - developers provide signatures so | ||
| 294 | that users can verify a website was not hijacked and download links | ||
| 295 | replaced with dangerous software. | ||
| 276 | 296 | ||
| 277 | In order to verify signed data, a user needs to have: | 297 | In order to verify signed data, a user needs to have: |
| 278 | 298 | ||
| @@ -280,26 +300,29 @@ In order to verify signed data, a user needs to have: | |||
| 280 | 2. A signature file | 300 | 2. A signature file |
| 281 | 3. The public GPG key of the signer | 301 | 3. The public GPG key of the signer |
| 282 | 302 | ||
| 283 | Once the signer's public key is imported on the user's system, and they have | 303 | Once the signer's public key is imported on the user's system, and they |
| 284 | the data and signature, they can verify the data with the following commands: | 304 | have the data and signature, they can verify the data with the following |
| 305 | commands: | ||
| 285 | 306 | ||
| 286 | ```sh | 307 | #+begin_src sh |
| 287 | # If the signature is attached to the data | 308 | # If the signature is attached to the data |
| 288 | gpg --verify [signature-file] | 309 | gpg --verify [signature-file] |
| 289 | 310 | ||
| 290 | # If the signature is detached as a separate file from the data | 311 | # If the signature is detached as a separate file from the data |
| 291 | gpg --verify [signature-file] [original-file] | 312 | gpg --verify [signature-file] [original-file] |
| 292 | ``` | 313 | #+end_src |
| 293 | 314 | ||
| 294 | ## Finding Public Keys | 315 | ** Finding Public Keys |
| 295 | 316 | :PROPERTIES: | |
| 296 | In order to use GPG with others, a user needs to know the other user(s) keys. | 317 | :CUSTOM_ID: finding-public-keys |
| 297 | This is easy to do if the user knows the other user(s) in person, but may be | 318 | :END: |
| 298 | hard if the relationship is strictly digital. Luckily, there are a few options. | 319 | In order to use GPG with others, a user needs to know the other user(s) |
| 299 | The first option is to look at a user's web page or social pages if they have | 320 | keys. This is easy to do if the user knows the other user(s) in person, |
| 300 | them. | 321 | but may be hard if the relationship is strictly digital. Luckily, there |
| 322 | are a few options. The first option is to look at a user's web page or | ||
| 323 | social pages if they have them. | ||
| 301 | 324 | ||
| 302 | Otherwise, the best option is to use a keyserver, such as: | 325 | Otherwise, the best option is to use a keyserver, such as: |
| 303 | 326 | ||
| 304 | - [pgp.mit.edu](https://pgp.mit.edu) | 327 | - [[https://pgp.mit.edu][pgp.mit.edu]] |
| 305 | - [keys.openpgp.org](https://keys.openpgp.org) | 328 | - [[https://keys.openpgp.org][keys.openpgp.org]] |
blog/2022-07-25-curseradio.org +94 −79
| @@ -1,99 +1,114 @@ | |||
| 1 | +++ | 1 | #+title: CurseRadio: Listening to the Radio on the Command Line |
| 2 | date = 2022-07-25 | 2 | #+date: 2022-07-25 |
| 3 | title = "CurseRadio: Listening to the Radio on the Command Line" | 3 | |
| 4 | description = "A quick and easy installation for CurseRadio on Fedora Linux." | 4 | ** Overview |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: overview | |
| 7 | ## Overview | 7 | :END: |
| 8 | 8 | While exploring some interesting Linux applications, I stumbled across | |
| 9 | While exploring some interesting Linux applications, I stumbled across | 9 | [[https://github.com/chronitis/curseradio][curseradio]], a command-line |
| 10 | [curseradio](https://github.com/chronitis/curseradio), a command-line radio | 10 | radio player based on Python. |
| 11 | player based on Python. | 11 | |
| 12 | 12 | This application is fantastic and incredibly easy to install, so I | |
| 13 | This application is fantastic and incredibly easy to install, so I wanted to | 13 | wanted to dedicate a post today to this app. Let's look at the features |
| 14 | dedicate a post today to this app. Let's look at the features within the app and | 14 | within the app and then walk through the installation process I took to |
| 15 | then walk through the installation process I took to get `curseradio` working. | 15 | get =curseradio= working. |
| 16 | 16 | ||
| 17 | ## Features | 17 | ** Features |
| 18 | 18 | :PROPERTIES: | |
| 19 |  | 19 | :CUSTOM_ID: features |
| 20 | 20 | :END: | |
| 21 | The radio player itself is quite minimal. As you can see in the screenshot | 21 | #+caption: curseradio |
| 22 | above, it contains a simple plaintext list of all available categories, which | 22 | [[https://img.cleberg.net/blog/20220725-curseradio/curseradio.png]] |
| 23 | can be broken down further and further. In addition, radio shows are available | 23 | |
| 24 | for listening, alongside regular radio stations. | 24 | The radio player itself is quite minimal. As you can see in the |
| 25 | 25 | screenshot above, it contains a simple plaintext list of all available | |
| 26 | For example, the `Sports` > `Pro Basketball` > `Shows` category contains a | 26 | categories, which can be broken down further and further. In addition, |
| 27 | number of specific shows related to Professional Basketball. | 27 | radio shows are available for listening, alongside regular radio |
| 28 | 28 | stations. | |
| 29 | Aside from being able to play any of the listed stations/shows, you can make | 29 | |
| 30 | a channel your favorite by pressing `f`. | 30 | For example, the =Sports= > =Pro Basketball= > =Shows= category contains |
| 31 | It will now show up at the top of the radio player in the `Favourites` category. | 31 | a number of specific shows related to Professional Basketball. |
| 32 | 32 | ||
| 33 | ### Commands/Shortcuts | 33 | Aside from being able to play any of the listed stations/shows, you can |
| 34 | 34 | make a channel your favorite by pressing =f=. It will now show up at the | |
| 35 | | Key(s) | Command | | 35 | top of the radio player in the =Favourites= category. |
| 36 | |:----------:|:-------------------------------:| | 36 | |
| 37 | | ↑, ↓ | navigate | | 37 | *** Commands/Shortcuts |
| 38 | | PgUp, PgDn | navigate quickly | | 38 | :PROPERTIES: |
| 39 | | Home, End | to top/bottom | | 39 | :CUSTOM_ID: commandsshortcuts |
| 40 | | Enter | open/close folders, play stream | | 40 | :END: |
| 41 | | k | stop playing stream | | 41 | | Key(s) | Command | |
| 42 | | q | quit | | 42 | |------------+---------------------------------| |
| 43 | | f | toggle favourite | | 43 | | ↑, ↓ | navigate | |
| 44 | 44 | | PgUp, PgDn | navigate quickly | | |
| 45 | ## Installation | 45 | | Home, End | to top/bottom | |
| 46 | 46 | | Enter | open/close folders, play stream | | |
| 47 | ### Dependencies | 47 | | k | stop playing stream | |
| 48 | 48 | | q | quit | | |
| 49 | Before installing `curseradio`, a handful of system and Python packages are | 49 | | f | toggle favourite | |
| 50 | required. To get started, install `python3`, `pip3`, and `mpv` on your system. | 50 | |
| 51 | In this example, I'm using Fedora Linux, which uses the `dnf` package manager. | 51 | ** Installation |
| 52 | You may need to adjust this if you're using a different system. | 52 | :PROPERTIES: |
| 53 | 53 | :CUSTOM_ID: installation | |
| 54 | ```sh | 54 | :END: |
| 55 | *** Dependencies | ||
| 56 | :PROPERTIES: | ||
| 57 | :CUSTOM_ID: dependencies | ||
| 58 | :END: | ||
| 59 | Before installing =curseradio=, a handful of system and Python packages | ||
| 60 | are required. To get started, install =python3=, =pip3=, and =mpv= on | ||
| 61 | your system. In this example, I'm using Fedora Linux, which uses the | ||
| 62 | =dnf= package manager. You may need to adjust this if you're using a | ||
| 63 | different system. | ||
| 64 | |||
| 65 | #+begin_src sh | ||
| 55 | sudo dnf install python3 pip3 mpv | 66 | sudo dnf install python3 pip3 mpv |
| 56 | ``` | 67 | #+end_src |
| 57 | 68 | ||
| 58 | Next, use `pip3` to install `requests`, `xdg`, and `lxml`: | 69 | Next, use =pip3= to install =requests=, =xdg=, and =lxml=: |
| 59 | 70 | ||
| 60 | ```sh | 71 | #+begin_src sh |
| 61 | pip3 install requests xdg lxml | 72 | pip3 install requests xdg lxml |
| 62 | ``` | 73 | #+end_src |
| 63 | 74 | ||
| 64 | ### Repository Source Installation | 75 | *** Repository Source Installation |
| 76 | :PROPERTIES: | ||
| 77 | :CUSTOM_ID: repository-source-installation | ||
| 78 | :END: | ||
| 79 | Once all the dependencies are installed, we can clone the source code | ||
| 80 | and enter that directory: | ||
| 65 | 81 | ||
| 66 | Once all the dependencies are installed, we can clone the source code and enter | 82 | #+begin_src sh |
| 67 | that directory: | ||
| 68 | |||
| 69 | ```sh | ||
| 70 | git clone https://github.com/chronitis/curseradio && cd curseradio | 83 | git clone https://github.com/chronitis/curseradio && cd curseradio |
| 71 | ``` | 84 | #+end_src |
| 72 | 85 | ||
| 73 | Once you're within the `curseradio` directory, you can install the application | 86 | Once you're within the =curseradio= directory, you can install the |
| 74 | with the provided `setup.py` script. | 87 | application with the provided =setup.py= script. |
| 75 | 88 | ||
| 76 | ```sh | 89 | #+begin_src sh |
| 77 | sudo python3 setup.py install | 90 | sudo python3 setup.py install |
| 78 | ``` | 91 | #+end_src |
| 79 | 92 | ||
| 80 | In my case, I ran into a few errors and needed to create the folders that | 93 | In my case, I ran into a few errors and needed to create the folders |
| 81 | curseradio wanted to use for its installation. If you don't get any errors, you | 94 | that curseradio wanted to use for its installation. If you don't get any |
| 82 | can skip this and run the app. | 95 | errors, you can skip this and run the app. |
| 83 | 96 | ||
| 84 | ```sh | 97 | #+begin_src sh |
| 85 | sudo mkdir /usr/local/lib/python3.10/ | 98 | sudo mkdir /usr/local/lib/python3.10/ |
| 86 | sudo mkdir /usr/local/lib/python3.10/site-packages/ | 99 | sudo mkdir /usr/local/lib/python3.10/site-packages/ |
| 87 | ``` | 100 | #+end_src |
| 88 | 101 | ||
| 89 | ```sh | 102 | #+begin_src sh |
| 90 | sudo chown -R $USER:$USER /usr/local/lib/python3.10/ | 103 | sudo chown -R $USER:$USER /usr/local/lib/python3.10/ |
| 91 | ``` | 104 | #+end_src |
| 92 | |||
| 93 | ## Run the Application | ||
| 94 | 105 | ||
| 106 | ** Run the Application | ||
| 107 | :PROPERTIES: | ||
| 108 | :CUSTOM_ID: run-the-application | ||
| 109 | :END: | ||
| 95 | Once fully installed without errors, you can run the application! | 110 | Once fully installed without errors, you can run the application! |
| 96 | 111 | ||
| 97 | ```sh | 112 | #+begin_src sh |
| 98 | python3 /usr/local/bin/curseradio | 113 | python3 /usr/local/bin/curseradio |
| 99 | ``` | 114 | #+end_src |
blog/2022-07-30-flac-to-opus.org +89 −81
| @@ -1,67 +1,72 @@ | |||
| 1 | +++ | 1 | #+title: Recursive Command-Line FLAC to Opus Conversion |
| 2 | date = 2022-07-30 | 2 | #+date: 2022-07-30 |
| 3 | title = "Recursive Command-Line FLAC to Opus Conversion" | 3 | |
| 4 | description = "A retrospective on how I recursively converting my FLAC file music library to OPUS with opusenc." | 4 | ** Converting FLAC to OPUS |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: converting-flac-to-opus | |
| 7 | ## Converting FLAC to OPUS | 7 | :END: |
| 8 | 8 | I am currently rebuilding my music library from scratch so that I can | |
| 9 | I am currently rebuilding my music library from scratch so that I can | 9 | effectively archive all the music I own in the |
| 10 | effectively archive all the music I own in the [FLAC file | 10 | [[https://en.wikipedia.org/wiki/FLAC][FLAC file format]], a lossless |
| 11 | format](https://en.wikipedia.org/wiki/FLAC), a lossless audio codec. | 11 | audio codec. |
| 12 | 12 | ||
| 13 | However, streaming FLAC files outside the home can be difficult due to the | 13 | However, streaming FLAC files outside the home can be difficult due to |
| 14 | size of the files, especially if you're using a weak connection. | 14 | the size of the files, especially if you're using a weak connection. |
| 15 | 15 | ||
| 16 | So, in order to archive the music in a lossless format and still be able to | 16 | So, in order to archive the music in a lossless format and still be able |
| 17 | stream it easily, I opted to create a copy of my FLAC files in the [Opus audio | 17 | to stream it easily, I opted to create a copy of my FLAC files in the |
| 18 | codec](https://en.wikipedia.org/wiki/Opus_(audio_format)). This allows me to | 18 | [[https://en.wikipedia.org/wiki/Opus_(audio_format)][Opus audio codec]]. |
| 19 | archive a quality, lossless version of the music and then point my streaming | 19 | This allows me to archive a quality, lossless version of the music and |
| 20 | service to the smaller, stream-ready version. | 20 | then point my streaming service to the smaller, stream-ready version. |
| 21 | 21 | ||
| 22 | ### Dependencies | 22 | *** Dependencies |
| 23 | 23 | :PROPERTIES: | |
| 24 | The process I follow utilizes the `opus-tools` package in Ubuntu. Before | 24 | :CUSTOM_ID: dependencies |
| 25 | :END: | ||
| 26 | The process I follow utilizes the =opus-tools= package in Ubuntu. Before | ||
| 25 | proceeding, install the package: | 27 | proceeding, install the package: |
| 26 | 28 | ||
| 27 | ```sh | 29 | #+begin_src sh |
| 28 | sudo apt install opus-tools | 30 | sudo apt install opus-tools |
| 29 | ``` | 31 | #+end_src |
| 30 | 32 | ||
| 31 | If you want to use a different conversion method, such as `ffmpeg` or `avconv`, | 33 | If you want to use a different conversion method, such as =ffmpeg= or |
| 32 | simply install that package instead. | 34 | =avconv=, simply install that package instead. |
| 33 | 35 | ||
| 34 | ### Conversion Process | 36 | *** Conversion Process |
| 37 | :PROPERTIES: | ||
| 38 | :CUSTOM_ID: conversion-process | ||
| 39 | :END: | ||
| 40 | The script I'm using is stored in my home directory, but feel free to | ||
| 41 | create it wherever you want. It does not need to be in the same | ||
| 42 | directory as your music files. | ||
| 35 | 43 | ||
| 36 | The script I'm using is stored in my home directory, but feel free to create it | 44 | #+begin_src sh |
| 37 | wherever you want. It does not need to be in the same directory as your music | ||
| 38 | files. | ||
| 39 | |||
| 40 | ```sh | ||
| 41 | cd ~ && nano transform.sh | 45 | cd ~ && nano transform.sh |
| 42 | ``` | 46 | #+end_src |
| 43 | 47 | ||
| 44 | Once you have your new bash script opened in an editor, go ahead and paste the | 48 | Once you have your new bash script opened in an editor, go ahead and |
| 45 | following logic into the script. | 49 | paste the following logic into the script. |
| 46 | 50 | ||
| 47 | You **MUST** edit the following variables in order for it to work: | 51 | You *MUST* edit the following variables in order for it to work: |
| 48 | 52 | ||
| 49 | - `source`: The source directory where your FLAC files are stored. | 53 | - =source=: The source directory where your FLAC files are stored. |
| 50 | - `dest`: The destination directory where you want the resulting Opus files to | 54 | - =dest=: The destination directory where you want the resulting Opus |
| 51 | be stored. | 55 | files to be stored. |
| 52 | 56 | ||
| 53 | You **MAY** want to edit the following variables to suit your needs: | 57 | You *MAY* want to edit the following variables to suit your needs: |
| 54 | 58 | ||
| 55 | - `filename`: If you are converting to a file format other than Opus, you'll | 59 | - =filename=: If you are converting to a file format other than Opus, |
| 56 | need to edit this so that your resulting files have the correct filename | 60 | you'll need to edit this so that your resulting files have the correct |
| 57 | extension. | 61 | filename extension. |
| 58 | - `reldir`: This variable can be edited to strip out more leading directories in | 62 | - =reldir=: This variable can be edited to strip out more leading |
| 59 | the file path. As you'll see later, I ignore this for now and simply clean it up | 63 | directories in the file path. As you'll see later, I ignore this for |
| 60 | afterward. | 64 | now and simply clean it up afterward. |
| 61 | - `opusenc`: This is the actual conversion process. You may want to edit the | 65 | - =opusenc=: This is the actual conversion process. You may want to edit |
| 62 | bitrate to suit your needs. I set mine at 128 but some prefer 160 or higher. | 66 | the bitrate to suit your needs. I set mine at 128 but some prefer 160 |
| 67 | or higher. | ||
| 63 | 68 | ||
| 64 | ```sh | 69 | #+begin_src sh |
| 65 | #!/bin/bash | 70 | #!/bin/bash |
| 66 | ## - The IFS takes care of spaces in file and dirnames | 71 | ## - The IFS takes care of spaces in file and dirnames |
| 67 | ## - your folders may vary | 72 | ## - your folders may vary |
| @@ -123,52 +128,55 @@ opusenc --vbr --bitrate 128 --date "$DATE" \ | |||
| 123 | ## just for testing | 128 | ## just for testing |
| 124 | # sleep 1 | 129 | # sleep 1 |
| 125 | done | 130 | done |
| 126 | ``` | 131 | #+end_src |
| 127 | 132 | ||
| 128 | Once you're done, simply save the file and exit your editor. Don't forget to | 133 | Once you're done, simply save the file and exit your editor. Don't |
| 129 | enable execution of the script: | 134 | forget to enable execution of the script: |
| 130 | 135 | ||
| 131 | ```sh | 136 | #+begin_src sh |
| 132 | chmod +x transform.sh | 137 | chmod +x transform.sh |
| 133 | ``` | 138 | #+end_src |
| 134 | 139 | ||
| 135 | Finally, you may now run the script: | 140 | Finally, you may now run the script: |
| 136 | 141 | ||
| 137 | ```sh | 142 | #+begin_src sh |
| 138 | ./transform.sh | 143 | ./transform.sh |
| 139 | ``` | 144 | #+end_src |
| 140 | 145 | ||
| 141 | If you used `opusenc`, you'll see the conversions happen within the terminal as | 146 | If you used =opusenc=, you'll see the conversions happen within the |
| 142 | it progresses. | 147 | terminal as it progresses. You will also see variables printed if you |
| 143 | You will also see variables printed if you uncommented any of the bash | 148 | uncommented any of the bash script's comments. |
| 144 | script's comments. | 149 | |
| 145 | 150 | *** Cleanup | |
| 146 | ### Cleanup | 151 | :PROPERTIES: |
| 147 | 152 | :CUSTOM_ID: cleanup | |
| 148 | As I noted above, I didn't customize my `reldir` variable in the script, which | 153 | :END: |
| 149 | caused my output directory to be `/mnt/music/library/archives` instead of | 154 | As I noted above, I didn't customize my =reldir= variable in the script, |
| 150 | `/mnt/music/library`. So, I moved the output up one level and deleted the | 155 | which caused my output directory to be =/mnt/music/library/archives= |
| 151 | accidental directory. | 156 | instead of =/mnt/music/library=. So, I moved the output up one level and |
| 152 | 157 | deleted the accidental directory. | |
| 153 | ```sh | 158 | |
| 159 | #+begin_src sh | ||
| 154 | cd /mnt/music/library | 160 | cd /mnt/music/library |
| 155 | mv archives/* . | 161 | mv archives/* . |
| 156 | rm -rf archives | 162 | rm -rf archives |
| 157 | ``` | 163 | #+end_src |
| 158 | |||
| 159 | ### Check the Resulting Size | ||
| 160 | 164 | ||
| 161 | If you want to see what kind of file size savings you've gained, you can always | 165 | *** Check the Resulting Size |
| 162 | use the `du` command to check: | 166 | :PROPERTIES: |
| 167 | :CUSTOM_ID: check-the-resulting-size | ||
| 168 | :END: | ||
| 169 | If you want to see what kind of file size savings you've gained, you can | ||
| 170 | always use the =du= command to check: | ||
| 163 | 171 | ||
| 164 | ```sh | 172 | #+begin_src sh |
| 165 | cd /mnt/music | 173 | cd /mnt/music |
| 166 | du -h --max-depth=1 . | 174 | du -h --max-depth=1 . |
| 167 | ``` | 175 | #+end_src |
| 168 | 176 | ||
| 169 | In my case, my small library went from 78GB to 6.3GB! | 177 | In my case, my small library went from 78GB to 6.3GB! |
| 170 | 178 | ||
| 171 | ```txt | 179 | #+begin_src txt |
| 172 | 78G ./archives | 180 | 78G ./archives |
| 173 | 6.3G ./library | 181 | 6.3G ./library |
| 174 | ``` | 182 | #+end_src |
blog/2022-07-31-bash-it.org +161 −140
| @@ -1,116 +1,126 @@ | |||
| 1 | +++ | 1 | #+title: Upgrade Bash with Bash-It & Ble.sh |
| 2 | date = 2022-07-31 | 2 | #+date: 2022-07-31 |
| 3 | title = "Upgrade Bash with Bash-It & Ble.sh" | 3 | |
| 4 | description = "In this post, I walk through the Bash-It and Ble.sh projects, their features, and how they help users power-up their bash shell." | 4 | ** Bash |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: bash | |
| 7 | ## Bash | 7 | :END: |
| 8 | 8 | For those who are not familiar, | |
| 9 | For those who are not familiar, | 9 | [[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][Bash]] is a Unix |
| 10 | [Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) is a Unix shell that is | 10 | shell that is used as the default login shell for most Linux |
| 11 | used as the default login shell for most Linux distributions. This shell and | 11 | distributions. This shell and command processor should be familiar if |
| 12 | command processor should be familiar if you've used Linux (or older version of | 12 | you've used Linux (or older version of macOS) before. |
| 13 | macOS) before. | 13 | |
| 14 | 14 | However, bash is not the only option. There are numerous other shells | |
| 15 | However, bash is not the only option. | 15 | that exist. Here are some popular examples: |
| 16 | There are numerous other shells that exist. | 16 | |
| 17 | Here are some popular examples: | 17 | - [[https://en.wikipedia.org/wiki/Z_shell][zsh]] |
| 18 | 18 | - [[https://en.wikipedia.org/wiki/Fish_(Unix_shell)][fish]] | |
| 19 | - [zsh](https://en.wikipedia.org/wiki/Z_shell) | 19 | - [[https://github.com/ibara/oksh][oksh]] |
| 20 | - [fish](https://en.wikipedia.org/wiki/Fish_(Unix_shell)) | 20 | - [[https://wiki.gentoo.org/wiki/Mksh][mksh]] |
| 21 | - [oksh](https://github.com/ibara/oksh) | 21 | - [[https://en.wikipedia.org/wiki/Debian_Almquist_shell][dash]] |
| 22 | - [mksh](https://wiki.gentoo.org/wiki/Mksh) | 22 | |
| 23 | - [dash](https://en.wikipedia.org/wiki/Debian_Almquist_shell) | 23 | While each shell has its differences, bash is POSIX compliant and the |
| 24 | 24 | default for many Linux users. Because of this, I am going to explore a | |
| 25 | While each shell has its differences, bash is POSIX compliant and the default | 25 | program called =bash-it= below that helps bash users increase the |
| 26 | for many Linux users. Because of this, I am going to explore a program called | 26 | utility of their shell without installing a completely new shell. |
| 27 | `bash-it` below that helps bash users increase the utility of their shell | 27 | |
| 28 | without installing a completely new shell. | 28 | *** Installation |
| 29 | 29 | :PROPERTIES: | |
| 30 | ### Installation | 30 | :CUSTOM_ID: installation |
| 31 | 31 | :END: | |
| 32 | First, if bash is not already installed on your system, you can [download bash | 32 | First, if bash is not already installed on your system, you can |
| 33 | from GNU](https://www.gnu.org/software/bash/) or use your package manager to | 33 | [[https://www.gnu.org/software/bash/][download bash from GNU]] or use |
| 34 | install it. | 34 | your package manager to install it. |
| 35 | 35 | ||
| 36 | For example, this is how you can install bash on Fedora Linux: | 36 | For example, this is how you can install bash on Fedora Linux: |
| 37 | 37 | ||
| 38 | ```sh | 38 | #+begin_src sh |
| 39 | sudo dnf install bash | 39 | sudo dnf install bash |
| 40 | ``` | 40 | #+end_src |
| 41 | 41 | ||
| 42 | If you are not using bash as your default shell, use the `chsh` command to | 42 | If you are not using bash as your default shell, use the =chsh= command |
| 43 | change your shell: | 43 | to change your shell: |
| 44 | 44 | ||
| 45 | ```sh | 45 | #+begin_src sh |
| 46 | chsh | 46 | chsh |
| 47 | ``` | 47 | #+end_src |
| 48 | 48 | ||
| 49 | You should see a prompt like the one below. If the brackets (`[]`) contain | 49 | You should see a prompt like the one below. If the brackets (=[]=) |
| 50 | `bash` already, you're done, and you can simply continue by hitting the Enter | 50 | contain =bash= already, you're done, and you can simply continue by |
| 51 | key. | 51 | hitting the Enter key. |
| 52 | 52 | ||
| 53 | If the brackets contain another shell path (e.g. `/usr/bin/zsh`), enter the path | 53 | If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter |
| 54 | to the bash program on your system (it's most likely located at | 54 | the path to the bash program on your system (it's most likely located at |
| 55 | `/usr/bin/bash`). | 55 | =/usr/bin/bash=). |
| 56 | 56 | ||
| 57 | ```sh | 57 | #+begin_src sh |
| 58 | Changing shell for <user>. | 58 | Changing shell for <user>. |
| 59 | New shell [/usr/bin/bash]: | 59 | New shell [/usr/bin/bash]: |
| 60 | ``` | 60 | #+end_src |
| 61 | 61 | ||
| 62 | You must log out or restart the machine in order for the login shell to be | 62 | You must log out or restart the machine in order for the login shell to |
| 63 | refreshed. You can do it now or wait until you're finished customizing the | 63 | be refreshed. You can do it now or wait until you're finished |
| 64 | shell. | 64 | customizing the shell. |
| 65 | 65 | ||
| 66 | ```sh | 66 | #+begin_src sh |
| 67 | sudo reboot now | 67 | sudo reboot now |
| 68 | ``` | 68 | #+end_src |
| 69 | 69 | ||
| 70 | ## Bash-it | 70 | ** Bash-it |
| 71 | :PROPERTIES: | ||
| 72 | :CUSTOM_ID: bash-it | ||
| 73 | :END: | ||
| 74 | As noted on the [[https://github.com/Bash-it/bash-it][Bash-it]] | ||
| 75 | repository: | ||
| 71 | 76 | ||
| 72 | As noted on the [Bash-it](https://github.com/Bash-it/bash-it) repository: | 77 | #+begin_quote |
| 78 | Bash-it is a collection of community Bash commands and scripts for Bash | ||
| 79 | 3.2+. (And a shameless ripoff of oh-my-zsh 😃) | ||
| 73 | 80 | ||
| 74 | > Bash-it is a collection of community Bash commands and scripts for Bash 3.2+. | 81 | #+end_quote |
| 75 | > (And a shameless ripoff of oh-my-zsh 😃) | ||
| 76 | 82 | ||
| 77 | Bash-it makes it easy to install plugins, set up aliases for common commands, | 83 | Bash-it makes it easy to install plugins, set up aliases for common |
| 78 | and easily change the visual theme of your shell. | 84 | commands, and easily change the visual theme of your shell. |
| 79 | 85 | ||
| 80 | ### Installation | 86 | *** Installation |
| 87 | :PROPERTIES: | ||
| 88 | :CUSTOM_ID: installation-1 | ||
| 89 | :END: | ||
| 90 | To install the framework, simply copy the repository files and use the | ||
| 91 | =install.sh= script provided. If you want, you can (and should!) inspect | ||
| 92 | the contents of the installation script before you run it. | ||
| 81 | 93 | ||
| 82 | To install the framework, simply copy the repository files and use the | 94 | #+begin_src sh |
| 83 | `install.sh` script provided. If you want, you can (and should!) inspect the | ||
| 84 | contents of the installation script before you run it. | ||
| 85 | |||
| 86 | ```sh | ||
| 87 | git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it | 95 | git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it |
| 88 | ~/.bash_it/install.sh | 96 | ~/.bash_it/install.sh |
| 89 | ``` | 97 | #+end_src |
| 90 | 98 | ||
| 91 | If you didn't restart your session after making bash the default, and are | 99 | If you didn't restart your session after making bash the default, and |
| 92 | currently working within another shell, be sure to enter a bash session before | 100 | are currently working within another shell, be sure to enter a bash |
| 93 | using `bash-it`: | 101 | session before using =bash-it=: |
| 94 | 102 | ||
| 95 | ```sh | 103 | #+begin_src sh |
| 96 | bash | 104 | bash |
| 97 | ``` | 105 | #+end_src |
| 98 | |||
| 99 | ### Aliases | ||
| 100 | 106 | ||
| 101 | Bash-it contains a number of aliases for common commands to help improve | 107 | *** Aliases |
| 102 | efficiency in the terminal. To list all available options, use the following | 108 | :PROPERTIES: |
| 103 | command: | 109 | :CUSTOM_ID: aliases |
| 110 | :END: | ||
| 111 | Bash-it contains a number of aliases for common commands to help improve | ||
| 112 | efficiency in the terminal. To list all available options, use the | ||
| 113 | following command: | ||
| 104 | 114 | ||
| 105 | ```sh | 115 | #+begin_src sh |
| 106 | bash-it show aliases | 116 | bash-it show aliases |
| 107 | ``` | 117 | #+end_src |
| 108 | 118 | ||
| 109 | This will provide you a list that looks like the following text block. | 119 | This will provide you a list that looks like the following text block. |
| 110 | Within this screen, you will be able to see all available options and which | 120 | Within this screen, you will be able to see all available options and |
| 111 | ones are currently enabled. | 121 | which ones are currently enabled. |
| 112 | 122 | ||
| 113 | ```txt | 123 | #+begin_src txt |
| 114 | Alias Enabled? Description | 124 | Alias Enabled? Description |
| 115 | ag [ ] the silver searcher (ag) aliases | 125 | ag [ ] the silver searcher (ag) aliases |
| 116 | ansible [ ] ansible abbreviations | 126 | ansible [ ] ansible abbreviations |
| @@ -123,32 +133,35 @@ clipboard [ ] xclip shortcuts | |||
| 123 | composer [ ] common composer abbreviations | 133 | composer [ ] common composer abbreviations |
| 124 | curl [x] Curl aliases for convenience. | 134 | curl [x] Curl aliases for convenience. |
| 125 | ... | 135 | ... |
| 126 | ``` | 136 | #+end_src |
| 127 | 137 | ||
| 128 | To enable an alias, do: | 138 | To enable an alias, do: |
| 129 | 139 | ||
| 130 | ```sh | 140 | #+begin_src sh |
| 131 | bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all | 141 | bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all |
| 132 | ``` | 142 | #+end_src |
| 133 | 143 | ||
| 134 | To disable an alias, do: | 144 | To disable an alias, do: |
| 135 | 145 | ||
| 136 | ```sh | 146 | #+begin_src sh |
| 137 | bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all | 147 | bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all |
| 138 | ``` | 148 | #+end_src |
| 139 | 149 | ||
| 140 | ### Plugins | 150 | *** Plugins |
| 151 | :PROPERTIES: | ||
| 152 | :CUSTOM_ID: plugins | ||
| 153 | :END: | ||
| 154 | Similar to aliases, plugins are available with bash-it. You can find a | ||
| 155 | complete list of plugins in the same way as aliases. Simply execute the | ||
| 156 | following: | ||
| 141 | 157 | ||
| 142 | Similar to aliases, plugins are available with bash-it. You can find a complete | 158 | #+begin_src sh |
| 143 | list of plugins in the same way as aliases. Simply execute the following: | ||
| 144 | |||
| 145 | ```sh | ||
| 146 | bash-it show plugins | 159 | bash-it show plugins |
| 147 | ``` | 160 | #+end_src |
| 148 | 161 | ||
| 149 | You will see the following output showing enabled and disabled plugins: | 162 | You will see the following output showing enabled and disabled plugins: |
| 150 | 163 | ||
| 151 | ```txt | 164 | #+begin_src txt |
| 152 | Plugin Enabled? Description | 165 | Plugin Enabled? Description |
| 153 | alias-completion [ ] | 166 | alias-completion [ ] |
| 154 | autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details | 167 | autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details |
| @@ -159,79 +172,87 @@ battery [x] display info about your battery charge level | |||
| 159 | blesh [ ] load ble.sh, the Bash line editor! | 172 | blesh [ ] load ble.sh, the Bash line editor! |
| 160 | boot2docker [ ] Helpers to get Docker setup correctly for boot2docker | 173 | boot2docker [ ] Helpers to get Docker setup correctly for boot2docker |
| 161 | browser [ ] render commandline output in your browser | 174 | browser [ ] render commandline output in your browser |
| 162 | ``` | 175 | #+end_src |
| 163 | 176 | ||
| 164 | To enable a plugin, do: | 177 | To enable a plugin, do: |
| 165 | 178 | ||
| 166 | ```sh | 179 | #+begin_src sh |
| 167 | bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all | 180 | bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all |
| 168 | ``` | 181 | #+end_src |
| 169 | 182 | ||
| 170 | To disable a plugin, do: | 183 | To disable a plugin, do: |
| 171 | 184 | ||
| 172 | ```sh | 185 | #+begin_src sh |
| 173 | bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all | 186 | bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all |
| 174 | ``` | 187 | #+end_src |
| 175 | 188 | ||
| 176 | ### Themes | 189 | *** Themes |
| 177 | 190 | :PROPERTIES: | |
| 178 | There are quite a few pre-defined | 191 | :CUSTOM_ID: themes |
| 179 | [themes](https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes) | 192 | :END: |
| 193 | There are quite a few pre-defined | ||
| 194 | [[https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes][themes]] | ||
| 180 | available with bash-it. | 195 | available with bash-it. |
| 181 | 196 | ||
| 182 | To list all themes: | 197 | To list all themes: |
| 183 | 198 | ||
| 184 | ```sh | 199 | #+begin_src sh |
| 185 | ls ~/.bash_it/themes/ | 200 | ls ~/.bash_it/themes/ |
| 186 | ``` | 201 | #+end_src |
| 187 | 202 | ||
| 188 | To use a new theme, you'll need to edit `.bashrc` and alter the `BASH_IT_THEME` | 203 | To use a new theme, you'll need to edit =.bashrc= and alter the |
| 189 | variable to your desired theme. For example, I am using the `zork` theme. | 204 | =BASH_IT_THEME= variable to your desired theme. For example, I am using |
| 205 | the =zork= theme. | ||
| 190 | 206 | ||
| 191 | ```sh | 207 | #+begin_src sh |
| 192 | nano ~/.bashrc | 208 | nano ~/.bashrc |
| 193 | ``` | 209 | #+end_src |
| 194 | 210 | ||
| 195 | ```sh | 211 | #+begin_src sh |
| 196 | export BASH_IT_THEME='zork' | 212 | export BASH_IT_THEME='zork' |
| 197 | ``` | 213 | #+end_src |
| 198 | 214 | ||
| 199 | Once you save your changes, you just need to exit your terminal and create a new | 215 | Once you save your changes, you just need to exit your terminal and |
| 200 | one in order to see your changes to the `.bashrc` file. You can also `source` | 216 | create a new one in order to see your changes to the =.bashrc= file. You |
| 201 | the file to see changes, but I recommend starting a completely new shell | 217 | can also =source= the file to see changes, but I recommend starting a |
| 202 | instead. | 218 | completely new shell instead. |
| 203 | 219 | ||
| 204 | ## ble.sh | 220 | ** ble.sh |
| 205 | 221 | :PROPERTIES: | |
| 206 | One big feature I was missing in Bash that both `zsh` and `fish` have is an | 222 | :CUSTOM_ID: ble.sh |
| 207 | autosuggestion feature. To explain: as you type, an autosuggestion feature in | 223 | :END: |
| 208 | the shell will offer suggestions in a lighter font color beyond the characters | 224 | One big feature I was missing in Bash that both =zsh= and =fish= have is |
| 209 | already typed. Once you see the command you want, you can click the right arrow | 225 | an autosuggestion feature. To explain: as you type, an autosuggestion |
| 210 | and have the shell auto-complete that line for you. | 226 | feature in the shell will offer suggestions in a lighter font color |
| 211 | 227 | beyond the characters already typed. Once you see the command you want, | |
| 212 | Luckily, the [Bash Line Editor](https://github.com/akinomyoga/ble.sh) (ble.sh) | 228 | you can click the right arrow and have the shell auto-complete that line |
| 213 | exists! This program provides a wonderful autosuggestions feature perfectly, | 229 | for you. |
| 214 | among other features that I haven't tested yet. | 230 | |
| 231 | Luckily, the [[https://github.com/akinomyoga/ble.sh][Bash Line Editor]] | ||
| 232 | (ble.sh) exists! This program provides a wonderful autosuggestions | ||
| 233 | feature perfectly, among other features that I haven't tested yet. | ||
| 215 | 234 | ||
| 216 | In order to install ble.sh, execute the following: | 235 | In order to install ble.sh, execute the following: |
| 217 | 236 | ||
| 218 | ```sh | 237 | #+begin_src sh |
| 219 | git clone --recursive https://github.com/akinomyoga/ble.sh.git | 238 | git clone --recursive https://github.com/akinomyoga/ble.sh.git |
| 220 | make -C ble.sh install PREFIX=~/.local | 239 | make -C ble.sh install PREFIX=~/.local |
| 221 | echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc | 240 | echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc |
| 222 | ``` | 241 | #+end_src |
| 223 | |||
| 224 | Again, exit the terminal and open a new one in order to see the newly-configured | ||
| 225 | shell. | ||
| 226 | 242 | ||
| 227 | ## Restart the Session | 243 | Again, exit the terminal and open a new one in order to see the |
| 244 | newly-configured shell. | ||
| 228 | 245 | ||
| 229 | Finally, as mentioned above, you'll need to restart the session to ensure that | 246 | ** Restart the Session |
| 230 | your user is using bash by default. | 247 | :PROPERTIES: |
| 248 | :CUSTOM_ID: restart-the-session | ||
| 249 | :END: | ||
| 250 | Finally, as mentioned above, you'll need to restart the session to | ||
| 251 | ensure that your user is using bash by default. | ||
| 231 | 252 | ||
| 232 | You will also need to exit and re-open a shell (e.g., terminal or terminal tab) | 253 | You will also need to exit and re-open a shell (e.g., terminal or |
| 233 | any time you make changes to the `.bashrc` file. | 254 | terminal tab) any time you make changes to the =.bashrc= file. |
| 234 | 255 | ||
| 235 | ```sh | 256 | #+begin_src sh |
| 236 | sudo reboot now | 257 | sudo reboot now |
| 237 | ``` | 258 | #+end_src |
blog/2022-08-31-privacy.com-changes.org +103 −85
| @@ -1,92 +1,110 @@ | |||
| 1 | +++ | 1 | #+title: Concerning Changes on Privacy.com |
| 2 | date = 2022-08-31 | 2 | #+date: 2022-08-31 |
| 3 | title = "Concerning Changes on Privacy.com" | 3 | |
| 4 | description = "A quick description of the recent changes of Privacy.com toward a credit card model." | 4 | ** Privacy.com Changes Their Terms |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: privacy.com-changes-their-terms | |
| 7 | ## Privacy.com Changes Their Terms | 7 | :END: |
| 8 | 8 | Recently, Privacy.com reached out to their customers regarding a change | |
| 9 | Recently, Privacy.com reached out to their customers regarding a change in their | 9 | in their terms of use. Further, all customers are required to agree to |
| 10 | terms of use. Further, all customers are required to agree to the changes in | 10 | the changes in order to continue using their accounts. |
| 11 | order to continue using their accounts. | 11 | |
| 12 | 12 | [[https://privacy.com/commercial-cardholder-agreement][You can view the | |
| 13 | [You can view the new cardholder agreement | 13 | new cardholder agreement here]]. |
| 14 | here](https://privacy.com/commercial-cardholder-agreement). | 14 | |
| 15 | 15 | When you log in, you'll be greeted with a pop-up window asking you to | |
| 16 | When you log in, you'll be greeted with a pop-up window asking you to review | 16 | review and agree to the new terms of use. You will also not be able to |
| 17 | and agree to the new terms of use. | 17 | open any new cards until the terms are agreed to. |
| 18 | You will also not be able to open any new cards until the terms are agreed to. | 18 | |
| 19 | 19 | *** Changing from a "Prepaid Debit" Model to a "Charge Card" Model | |
| 20 | ### Changing from a "Prepaid Debit" Model to a "Charge Card" Model | 20 | :PROPERTIES: |
| 21 | 21 | :CUSTOM_ID: changing-from-a-prepaid-debit-model-to-a-charge-card-model | |
| 22 | The actual content of the changes is interesting. While the historical model of | 22 | :END: |
| 23 | using Privacy.com was akin to prepaid debit cards, the new model is very similar | 23 | The actual content of the changes is interesting. While the historical |
| 24 | to a credit card (they use the term "charge card"). | 24 | model of using Privacy.com was akin to prepaid debit cards, the new |
| 25 | 25 | model is very similar to a credit card (they use the term "charge | |
| 26 | I have used Privacy.com for 1-2 years, and the process was always that you | 26 | card"). |
| 27 | would create a single-use or merchant-locked card. | 27 | |
| 28 | This card could be used for any dollar limit you set and would immediately | 28 | I have used Privacy.com for 1-2 years, and the process was always that |
| 29 | draw the funds from whatever funding source you connected, e.g. PayPal | 29 | you would create a single-use or merchant-locked card. This card could |
| 30 | account or a bank account. | 30 | be used for any dollar limit you set and would immediately draw the |
| 31 | 31 | funds from whatever funding source you connected, e.g. PayPal account or | |
| 32 | The benefit this service provides with a premium account is masking the merchant | 32 | a bank account. |
| 33 | names from your funding source. If you have a concern that your bank account | 33 | |
| 34 | uses merchant data from your account, you could direct all charges through | 34 | The benefit this service provides with a premium account is masking the |
| 35 | Privacy.com and set the merchant as one of their pre-set options, such as | 35 | merchant names from your funding source. If you have a concern that your |
| 36 | "Smiley's Corner Store" or "NSA Gift Shop." | 36 | bank account uses merchant data from your account, you could direct all |
| 37 | 37 | charges through Privacy.com and set the merchant as one of their pre-set | |
| 38 | The new model still works with a bank account as a funding source, but the model | 38 | options, such as "Smiley's Corner Store" or "NSA Gift Shop." |
| 39 | is changed so that you get a "line of credit" set according to a 14-day billing | 39 | |
| 40 | cycle. It seems that Privacy.com will now allow charges to be incurred without | 40 | The new model still works with a bank account as a funding source, but |
| 41 | being immediately paid. | 41 | the model is changed so that you get a "line of credit" set according to |
| 42 | 42 | a 14-day billing cycle. It seems that Privacy.com will now allow charges | |
| 43 | ### Daily Payments and Available Credit | 43 | to be incurred without being immediately paid. |
| 44 | 44 | ||
| 45 | Instead of paying as charges are incurred, you must make a "Daily Payment" and | 45 | *** Daily Payments and Available Credit |
| 46 | your "Available Credit" will be locked until you make that payment. There are | 46 | :PROPERTIES: |
| 47 | also "End of Billing Cycle Payments" that are assigned a due date. | 47 | :CUSTOM_ID: daily-payments-and-available-credit |
| 48 | 48 | :END: | |
| 49 | Further, Privacy.com will decline charges that would cause you to exceed your | 49 | Instead of paying as charges are incurred, you must make a "Daily |
| 50 | Available Credit or Credit Limit. | 50 | Payment" and your "Available Credit" will be locked until you make that |
| 51 | payment. There are also "End of Billing Cycle Payments" that are | ||
| 52 | assigned a due date. | ||
| 53 | |||
| 54 | Further, Privacy.com will decline charges that would cause you to exceed | ||
| 55 | your Available Credit or Credit Limit. | ||
| 51 | 56 | ||
| 52 | One particular interesting section states the following: | 57 | One particular interesting section states the following: |
| 53 | 58 | ||
| 54 | > YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER OBLIGATIONS | 59 | #+begin_quote |
| 55 | > YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT PAY US BACK FOR ANY | 60 | YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER |
| 56 | > AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE WILL EXERCISE OUR INTEREST | 61 | OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT |
| 57 | > AND DEBIT THE SECURED ACCOUNT, AND YOU WILL LOSE THE MONEY IN THE SECURED | 62 | PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE |
| 58 | > ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT FOR MORE INFORMATION. | 63 | WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL |
| 59 | 64 | LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT | |
| 60 | ### Personal Information | 65 | FOR MORE INFORMATION. |
| 61 | 66 | ||
| 62 | Now that Privacy.com is more of a financial institution, they are obligated to | 67 | #+end_quote |
| 63 | comply with the [know your | 68 | |
| 64 | customer](https://en.wikipedia.org/wiki/Know_your_customer) guidelines/laws. | 69 | *** Personal Information |
| 65 | 70 | :PROPERTIES: | |
| 66 | I did not proceed with the change to my Privacy.com account, but I have heard | 71 | :CUSTOM_ID: personal-information |
| 67 | from some peers that the changes require more personal information to be | 72 | :END: |
| 68 | submitted, such as SSN. I am not aware of all new personal information required | 73 | Now that Privacy.com is more of a financial institution, they are |
| 69 | or if the funding source is now required to only be a bank account. | 74 | obligated to comply with the |
| 70 | 75 | [[https://en.wikipedia.org/wiki/Know_your_customer][know your customer]] | |
| 71 | ### Fees | 76 | guidelines/laws. |
| 72 | 77 | ||
| 73 | Luckily, the fees section did not change much. The subscription fees for a | 78 | I did not proceed with the change to my Privacy.com account, but I have |
| 74 | premium account are still the only fees. | 79 | heard from some peers that the changes require more personal information |
| 75 | 80 | to be submitted, such as SSN. I am not aware of all new personal | |
| 76 | ## My Thoughts | 81 | information required or if the funding source is now required to only be |
| 77 | 82 | a bank account. | |
| 78 | Personally, I wiped my personal information from my account and then permanently | 83 | |
| 79 | deleted it when I heard about these changes. I have no interest in yet another | 84 | *** Fees |
| 80 | method of credit lending offered by private companies. While I accepted that | 85 | :PROPERTIES: |
| 81 | they would have access to my bank account information for the purposes of paying | 86 | :CUSTOM_ID: fees |
| 82 | off my prepaid debit payments, I have no interest in incurring charges that will | 87 | :END: |
| 83 | need to be paid back at a later date. I also have no interest in submitting | 88 | Luckily, the fees section did not change much. The subscription fees for |
| 89 | a premium account are still the only fees. | ||
| 90 | |||
| 91 | ** My Thoughts | ||
| 92 | :PROPERTIES: | ||
| 93 | :CUSTOM_ID: my-thoughts | ||
| 94 | :END: | ||
| 95 | Personally, I wiped my personal information from my account and then | ||
| 96 | permanently deleted it when I heard about these changes. I have no | ||
| 97 | interest in yet another method of credit lending offered by private | ||
| 98 | companies. While I accepted that they would have access to my bank | ||
| 99 | account information for the purposes of paying off my prepaid debit | ||
| 100 | payments, I have no interest in incurring charges that will need to be | ||
| 101 | paid back at a later date. I also have no interest in submitting | ||
| 84 | personal information to Privacy.com. | 102 | personal information to Privacy.com. |
| 85 | 103 | ||
| 86 | This type of change toward a "buy it now, pay us later" model is concerning, | 104 | This type of change toward a "buy it now, pay us later" model is |
| 87 | and I will be watching Privacy.com to see if they further their interests in | 105 | concerning, and I will be watching Privacy.com to see if they further |
| 88 | the credit model as time goes on. | 106 | their interests in the credit model as time goes on. |
| 89 | 107 | ||
| 90 | Could we see them start charging interest, fees, etc.? I'm not sure, but | 108 | Could we see them start charging interest, fees, etc.? I'm not sure, but |
| 91 | this change does not inspire confidence in their mission as a | 109 | this change does not inspire confidence in their mission as a |
| 92 | privacy-focused company. | 110 | privacy-focused company. |
blog/2022-09-17-serenity-os.org +96 −81
| @@ -1,113 +1,128 @@ | |||
| 1 | +++ | 1 | #+title: Serenity OS: Testing Out a Unique System |
| 2 | date = 2022-09-17 | 2 | #+date: 2022-09-17 |
| 3 | title = "Serenity OS: Testing Out a Unique System" | ||
| 4 | description = "Walking through my exploration of Serenity OS, a unique 90s-esque system." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Overview | 4 | ** Overview |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: overview | ||
| 7 | :END: | ||
| 8 | [[https://serenityos.org][SerenityOS]] is a unique operating system (OS) | ||
| 9 | that I have seen pop up in my news feed a few times over the last few | ||
| 10 | years, but I have never had time to test it out until now. | ||
| 8 | 11 | ||
| 9 | [SerenityOS](https://serenityos.org) is a unique operating system (OS) that I | 12 | Testing out this system brought back fond memories of yellowed, |
| 10 | have seen pop up in my news feed a few times over the last few years, but I have | 13 | modem-screeching, 100-pound computers that brought so many fond memories |
| 11 | never had time to test it out until now. | 14 | to my youth. |
| 12 | |||
| 13 | Testing out this system brought back fond memories of yellowed, | ||
| 14 | modem-screeching, 100-pound computers that brought so many fond memories to my | ||
| 15 | youth. | ||
| 16 | 15 | ||
| 17 | Per their website: | 16 | Per their website: |
| 18 | 17 | ||
| 19 | > A graphical Unix-like operating system for desktop computers! | 18 | #+begin_quote |
| 20 | > | 19 | A graphical Unix-like operating system for desktop computers! |
| 21 | > SerenityOS is a love letter to '90s user interfaces with a custom Unix-like | ||
| 22 | > core. | ||
| 23 | > It flatters with sincerity by stealing beautiful ideas from various | ||
| 24 | > other systems. | ||
| 25 | > | ||
| 26 | > Roughly speaking, the goal is a marriage between the aesthetic of late-1990s | ||
| 27 | > productivity software and the power-user accessibility of late-2000s *nix. | ||
| 28 | > | ||
| 29 | > This is a system by us, for us, based on the things we like. | ||
| 30 | |||
| 31 | ## Building | ||
| 32 | |||
| 33 | Your first question may be "Where's the iso?" and the answer is... there are | ||
| 34 | none. SerenityOS does not provide pre-built images for testing. You must build | ||
| 35 | the images yourself. This seems intentionally to limit participation to only | ||
| 36 | those who are truly interested enough to learn how to build the OS. | ||
| 37 | 20 | ||
| 38 | ### Clone | 21 | SerenityOS is a love letter to '90s user interfaces with a custom |
| 39 | 22 | Unix-like core. It flatters with sincerity by stealing beautiful ideas | |
| 40 | In order to get started, you'll need to clone the source repository: | 23 | from various other systems. |
| 41 | 24 | ||
| 42 | ```sh | 25 | Roughly speaking, the goal is a marriage between the aesthetic of |
| 43 | git clone https://github.com/SerenityOS/serenity && cd serenity | 26 | late-1990s productivity software and the power-user accessibility of |
| 44 | ``` | 27 | late-2000s *nix. |
| 45 | 28 | ||
| 46 | ### Build | 29 | This is a system by us, for us, based on the things we like. |
| 47 | 30 | ||
| 48 | Note that I followed the [Build | 31 | #+end_quote |
| 49 | Instructions](https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md) | ||
| 50 | in the SerenityOS repository as of commit | ||
| 51 | `660d2b53b1206e868d5470eee80b5e62d7e30da7`. | ||
| 52 | Things may have changed since my installation, and you should double-check | ||
| 53 | the instructions first. | ||
| 54 | 32 | ||
| 55 | Regardless, I want to repeat my steps here to illustrate any errors or differing | 33 | ** Building |
| 56 | commands I needed to run in order to build and run SerenityOS. | 34 | :PROPERTIES: |
| 35 | :CUSTOM_ID: building | ||
| 36 | :END: | ||
| 37 | Your first question may be "Where's the iso?" and the answer is... there | ||
| 38 | are none. SerenityOS does not provide pre-built images for testing. You | ||
| 39 | must build the images yourself. This seems intentionally to limit | ||
| 40 | participation to only those who are truly interested enough to learn how | ||
| 41 | to build the OS. | ||
| 57 | 42 | ||
| 58 | Since I am running Fedora, I needed to install these packages in order to build | 43 | *** Clone |
| 59 | the OS images: | 44 | :PROPERTIES: |
| 45 | :CUSTOM_ID: clone | ||
| 46 | :END: | ||
| 47 | In order to get started, you'll need to clone the source repository: | ||
| 60 | 48 | ||
| 61 | ```sh | 49 | #+begin_src sh |
| 50 | git clone https://github.com/SerenityOS/serenity && cd serenity | ||
| 51 | #+end_src | ||
| 52 | |||
| 53 | *** Build | ||
| 54 | :PROPERTIES: | ||
| 55 | :CUSTOM_ID: build | ||
| 56 | :END: | ||
| 57 | Note that I followed the | ||
| 58 | [[https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md][Build | ||
| 59 | Instructions]] in the SerenityOS repository as of commit | ||
| 60 | =660d2b53b1206e868d5470eee80b5e62d7e30da7=. Things may have changed | ||
| 61 | since my installation, and you should double-check the instructions | ||
| 62 | first. | ||
| 63 | |||
| 64 | Regardless, I want to repeat my steps here to illustrate any errors or | ||
| 65 | differing commands I needed to run in order to build and run SerenityOS. | ||
| 66 | |||
| 67 | Since I am running Fedora, I needed to install these packages in order | ||
| 68 | to build the OS images: | ||
| 69 | |||
| 70 | #+begin_src sh | ||
| 62 | sudo dnf install texinfo binutils-devel curl cmake mpfr-devel libmpc-devel gmp-devel e2fsprogs ninja-build patch ccache rsync @"C Development Tools and Libraries" @Virtualization | 71 | sudo dnf install texinfo binutils-devel curl cmake mpfr-devel libmpc-devel gmp-devel e2fsprogs ninja-build patch ccache rsync @"C Development Tools and Libraries" @Virtualization |
| 63 | ``` | 72 | #+end_src |
| 64 | 73 | ||
| 65 | Next, make sure you're inside the `serenity` directory created earlier | 74 | Next, make sure you're inside the =serenity= directory created earlier |
| 66 | during the git cloning process and process to build the toolchain: | 75 | during the git cloning process and process to build the toolchain: |
| 67 | 76 | ||
| 68 | ```sh | 77 | #+begin_src sh |
| 69 | Meta/serenity.sh rebuild-toolchain | 78 | Meta/serenity.sh rebuild-toolchain |
| 70 | ``` | 79 | #+end_src |
| 71 | 80 | ||
| 72 | Once the toolchain is built, you can Build and run the OS! | 81 | Once the toolchain is built, you can Build and run the OS! |
| 73 | 82 | ||
| 74 | ```sh | 83 | #+begin_src sh |
| 75 | Meta/serenity.sh run | 84 | Meta/serenity.sh run |
| 76 | ``` | 85 | #+end_src |
| 77 | |||
| 78 | After this process is completed, the image should run automatically and launch. | ||
| 79 | |||
| 80 | ## Issues | ||
| 81 | 86 | ||
| 82 | I played around in SerenityOS for an hour or two in order to see what I could do | 87 | After this process is completed, the image should run automatically and |
| 83 | and had a lot of fun with it. The only issue I ran into was a lack of working | 88 | launch. |
| 84 | internet. I didn't try very hard, but I could tell that the main network link | ||
| 85 | wasn't connecting to my Fedora host properly. | ||
| 86 | 89 | ||
| 87 | ## Screenshots | 90 | ** Issues |
| 91 | :PROPERTIES: | ||
| 92 | :CUSTOM_ID: issues | ||
| 93 | :END: | ||
| 94 | I played around in SerenityOS for an hour or two in order to see what I | ||
| 95 | could do and had a lot of fun with it. The only issue I ran into was a | ||
| 96 | lack of working internet. I didn't try very hard, but I could tell that | ||
| 97 | the main network link wasn't connecting to my Fedora host properly. | ||
| 88 | 98 | ||
| 89 | The initial launch of the image displays the SerenityOS desktop, with a simple | 99 | ** Screenshots |
| 90 | terminal already launched: | 100 | :PROPERTIES: |
| 101 | :CUSTOM_ID: screenshots | ||
| 102 | :END: | ||
| 103 | The initial launch of the image displays the SerenityOS desktop, with a | ||
| 104 | simple terminal already launched: | ||
| 91 | 105 | ||
| 92 |  | 106 | [[https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png]] |
| 93 | 107 | ||
| 94 | Here you can see the Fire application (literally just shows fire burning), a | 108 | Here you can see the Fire application (literally just shows fire |
| 95 | browser with the local Serenity Browser page loaded, and a text editor. | 109 | burning), a browser with the local Serenity Browser page loaded, and a |
| 110 | text editor. | ||
| 96 | 111 | ||
| 97 |  | 112 | [[https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png]] |
| 98 | 113 | ||
| 99 | I also poked around the system utilities and found most tools you'd expect to | 114 | I also poked around the system utilities and found most tools you'd |
| 100 | find within a standard desktop. | 115 | expect to find within a standard desktop. |
| 101 | 116 | ||
| 102 |  | 117 | [[https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png]] |
| 103 | 118 | ||
| 104 | Lastly, I noted that the default desktop contains numerous pre-defined themes to | 119 | Lastly, I noted that the default desktop contains numerous pre-defined |
| 105 | choose from. This is a small piece, but it's actually wonderful to see desktop | 120 | themes to choose from. This is a small piece, but it's actually |
| 106 | developers consider theming directly out of the box rather than using an | 121 | wonderful to see desktop developers consider theming directly out of the |
| 107 | addon-based mentality. | 122 | box rather than using an addon-based mentality. |
| 108 | 123 | ||
| 109 |  | 124 | [[https://img.cleberg.net/blog/20220917-serenityos/themes.png]] |
| 110 | 125 | ||
| 111 | I didn't take a screenshot of the other pre-installed games, but I did spend | 126 | I didn't take a screenshot of the other pre-installed games, but I did |
| 112 | nearly 30 minutes playing Solitaire before remembering that I was supposed to be | 127 | spend nearly 30 minutes playing Solitaire before remembering that I was |
| 113 | writing a post about the OS. | 128 | supposed to be writing a post about the OS. |
blog/2022-09-21-graphene-os.org +142 −116
| @@ -1,160 +1,186 @@ | |||
| 1 | +++ | 1 | #+title: Installing Graphene OS on the Pixel 6 Pro |
| 2 | date = 2022-09-21 | 2 | #+date: 2022-09-21 |
| 3 | title = "Installing Graphene OS on the Pixel 6 Pro" | 3 | |
| 4 | description = "A walkthrough tutorial on how to install GrapheneOS on the Pixel 6 Pro with Fedora Linux." | 4 | ** Introduction |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: introduction | |
| 7 | ## Introduction | 7 | :END: |
| 8 | 8 | After using iOS for a couple of years, I finally took the plunge and | |
| 9 | After using iOS for a couple of years, I finally took the plunge and | ||
| 10 | purchased a Pixel 6 Pro in order to test and use [GrapheneOS] | 9 | purchased a Pixel 6 Pro in order to test and use [GrapheneOS] |
| 11 | (https://grapheneos.org). | 10 | (https://grapheneos.org). |
| 12 | 11 | ||
| 13 | The installation process was rather quick once you have the tools and files you | 12 | The installation process was rather quick once you have the tools and |
| 14 | need. Overall, it can be done in just a few minutes. | 13 | files you need. Overall, it can be done in just a few minutes. |
| 15 | 14 | ||
| 16 | ## Gathering Tools & Files | 15 | ** Gathering Tools & Files |
| 17 | 16 | :PROPERTIES: | |
| 18 | ### Android Tools | 17 | :CUSTOM_ID: gathering-tools-files |
| 19 | 18 | :END: | |
| 20 | First, in order to interact with the device, we will need the [Android | 19 | *** Android Tools |
| 21 | platform | 20 | :PROPERTIES: |
| 22 | tools](https://developer.android.com/studio/releases/platform-tools.html). Find | 21 | :CUSTOM_ID: android-tools |
| 23 | the Linux download and save the ZIP folder to your preferred location. | 22 | :END: |
| 24 | 23 | First, in order to interact with the device, we will need the | |
| 25 | Once we've downloaded the files, we will need to unzip them, enter the | 24 | [[https://developer.android.com/studio/releases/platform-tools.html][Android |
| 26 | directory, and move the necessary executables to a central location, such as | 25 | platform tools]]. Find the Linux download and save the ZIP folder to |
| 27 | `/usr/bin/`. For this installation, we only need the `fastboot` and `adb` | 26 | your preferred location. |
| 28 | executables. | 27 | |
| 29 | 28 | Once we've downloaded the files, we will need to unzip them, enter the | |
| 30 | ```sh | 29 | directory, and move the necessary executables to a central location, |
| 30 | such as =/usr/bin/=. For this installation, we only need the =fastboot= | ||
| 31 | and =adb= executables. | ||
| 32 | |||
| 33 | #+begin_src sh | ||
| 31 | cd ~/Downloads | 34 | cd ~/Downloads |
| 32 | ``` | 35 | #+end_src |
| 33 | 36 | ||
| 34 | ```sh | 37 | #+begin_src sh |
| 35 | unzip platform-tools_r33.0.3-linux.zip | 38 | unzip platform-tools_r33.0.3-linux.zip |
| 36 | cd platform-tools | 39 | cd platform-tools |
| 37 | sudo mv fastboot /usr/bin/ | 40 | sudo mv fastboot /usr/bin/ |
| 38 | sudo mv adb /usr/bin | 41 | sudo mv adb /usr/bin |
| 39 | ``` | 42 | #+end_src |
| 40 | |||
| 41 | ### GrapheneOS Files | ||
| 42 | 43 | ||
| 43 | Next, we need the [GrapheneOS files](https://grapheneos.org/releases) for | 44 | *** GrapheneOS Files |
| 44 | our device and model. For example, the Pixel 6 Pro is codenamed `raven` on the | 45 | :PROPERTIES: |
| 45 | release page. | 46 | :CUSTOM_ID: grapheneos-files |
| 47 | :END: | ||
| 48 | Next, we need the [[https://grapheneos.org/releases][GrapheneOS files]] | ||
| 49 | for our device and model. For example, the Pixel 6 Pro is codenamed | ||
| 50 | =raven= on the release page. | ||
| 46 | 51 | ||
| 47 | Once we have the links, let's download them to our working directory: | 52 | Once we have the links, let's download them to our working directory: |
| 48 | 53 | ||
| 49 | ```sh | 54 | #+begin_src sh |
| 50 | curl -O https://releases.grapheneos.org/factory.pub | 55 | curl -O https://releases.grapheneos.org/factory.pub |
| 51 | curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip | 56 | curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip |
| 52 | curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig | 57 | curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig |
| 53 | ``` | 58 | #+end_src |
| 54 | |||
| 55 | #### Validate Integrity | ||
| 56 | 59 | ||
| 57 | In order to validate the integrity of the downloaded files, we will need the | 60 | **** Validate Integrity |
| 58 | `signify` package and Graphene's `factory.pub` file. | 61 | :PROPERTIES: |
| 62 | :CUSTOM_ID: validate-integrity | ||
| 63 | :END: | ||
| 64 | In order to validate the integrity of the downloaded files, we will need | ||
| 65 | the =signify= package and Graphene's =factory.pub= file. | ||
| 59 | 66 | ||
| 60 | ```sh | 67 | #+begin_src sh |
| 61 | sudo dnf install signify | 68 | sudo dnf install signify |
| 62 | ``` | 69 | #+end_src |
| 63 | 70 | ||
| 64 | ```sh | 71 | #+begin_src sh |
| 65 | curl -O https://releases.grapheneos.org/factory.pub | 72 | curl -O https://releases.grapheneos.org/factory.pub |
| 66 | ``` | 73 | #+end_src |
| 67 | 74 | ||
| 68 | Then we can validate the files and ensure that no data was corrupted or | 75 | Then we can validate the files and ensure that no data was corrupted or |
| 69 | modified before it was saved to our device. | 76 | modified before it was saved to our device. |
| 70 | 77 | ||
| 71 | ```sh | 78 | #+begin_src sh |
| 72 | signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified | 79 | signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified |
| 73 | ``` | 80 | #+end_src |
| 74 | 81 | ||
| 75 | #### Unzip Files | 82 | **** Unzip Files |
| 83 | :PROPERTIES: | ||
| 84 | :CUSTOM_ID: unzip-files | ||
| 85 | :END: | ||
| 86 | Once the files are verified, we can unzip the Graphene image and enter | ||
| 87 | the directory: | ||
| 76 | 88 | ||
| 77 | Once the files are verified, we can unzip the Graphene image and enter the | 89 | #+begin_src sh |
| 78 | directory: | ||
| 79 | |||
| 80 | ```sh | ||
| 81 | unzip raven-factory-2022091400.zip && cd raven-factory-2022091400 | 90 | unzip raven-factory-2022091400.zip && cd raven-factory-2022091400 |
| 82 | ``` | 91 | #+end_src |
| 83 | 92 | ||
| 84 | ## Installation Process | 93 | ** Installation Process |
| 85 | 94 | :PROPERTIES: | |
| 86 | ### Enable Developer Debugging & OEM Unlock | 95 | :CUSTOM_ID: installation-process |
| 87 | 96 | :END: | |
| 88 | Before we can actually flash anything to the phone, we will need to enable | 97 | *** Enable Developer Debugging & OEM Unlock |
| 89 | OEM Unlocking, as well as either USB Debugging or Wireless Debugging, depending on | 98 | :PROPERTIES: |
| 90 | which method we will be using. | 99 | :CUSTOM_ID: enable-developer-debugging-oem-unlock |
| 91 | 100 | :END: | |
| 92 | To start, enable developer mode by going to `Settings` > `About` and tapping | 101 | Before we can actually flash anything to the phone, we will need to |
| 93 | `Build Number` seven (7) times. You may need to enter your PIN to enable this | 102 | enable OEM Unlocking, as well as either USB Debugging or Wireless |
| 94 | mode. | 103 | Debugging, depending on which method we will be using. |
| 95 | 104 | ||
| 96 | Once developer mode is enabled, go to `Settings` > `System` > `Devloper | 105 | To start, enable developer mode by going to =Settings= > =About= and |
| 97 | Options` and enable OEM Unlocking, as well as USB or Wireless Debugging. In my | 106 | tapping =Build Number= seven (7) times. You may need to enter your PIN |
| 98 | case, I chose USB Debugging and performed all actions via USB cable. | 107 | to enable this mode. |
| 99 | 108 | ||
| 100 | Once these options are enabled, plug the phone into the computer and execute the | 109 | Once developer mode is enabled, go to =Settings= > =System= > |
| 101 | following command: | 110 | =Devloper Options= and enable OEM Unlocking, as well as USB or Wireless |
| 102 | 111 | Debugging. In my case, I chose USB Debugging and performed all actions | |
| 103 | ```sh | 112 | via USB cable. |
| 113 | |||
| 114 | Once these options are enabled, plug the phone into the computer and | ||
| 115 | execute the following command: | ||
| 116 | |||
| 117 | #+begin_src sh | ||
| 104 | adb devices | 118 | adb devices |
| 105 | ``` | 119 | #+end_src |
| 106 | |||
| 107 | If an unauthorized error occurs, make sure the USB mode on the phone is | ||
| 108 | changed from charging to something like "File Transfer" or "PTP." | ||
| 109 | You can find the USB mode in the notification tray. | ||
| 110 | 120 | ||
| 111 | ### Reboot Device | 121 | If an unauthorized error occurs, make sure the USB mode on the phone is |
| 122 | changed from charging to something like "File Transfer" or "PTP." You | ||
| 123 | can find the USB mode in the notification tray. | ||
| 112 | 124 | ||
| 113 | Once we have found the device via `adb`, we can either boot into the | 125 | *** Reboot Device |
| 114 | bootloader interface by holding the volume down button while the phone reboots | 126 | :PROPERTIES: |
| 115 | or by executing the following command: | 127 | :CUSTOM_ID: reboot-device |
| 128 | :END: | ||
| 129 | Once we have found the device via =adb=, we can either boot into the | ||
| 130 | bootloader interface by holding the volume down button while the phone | ||
| 131 | reboots or by executing the following command: | ||
| 116 | 132 | ||
| 117 | ```sh | 133 | #+begin_src sh |
| 118 | adb reboot bootloader | 134 | adb reboot bootloader |
| 119 | ``` | 135 | #+end_src |
| 120 | 136 | ||
| 121 | ### Unlock the Bootloader | 137 | *** Unlock the Bootloader |
| 138 | :PROPERTIES: | ||
| 139 | :CUSTOM_ID: unlock-the-bootloader | ||
| 140 | :END: | ||
| 141 | The phone will reboot and load the bootloader screen upon startup. At | ||
| 142 | this point, we are ready to start the actual flashing of GrapheneOS onto | ||
| 143 | the device. | ||
| 122 | 144 | ||
| 123 | The phone will reboot and load the bootloader screen upon startup. At this | 145 | *NOTE*: In my situation, I needed to use =sudo= with every =fastboot= |
| 124 | point, we are ready to start the actual flashing of GrapheneOS onto the device. | 146 | command, but not with =adb= commands. I am not sure if this is standard |
| 147 | or a Fedora quirk, but I'm documenting my commands verbatim in this | ||
| 148 | post. | ||
| 125 | 149 | ||
| 126 | **NOTE**: In my situation, I needed to use `sudo` with every `fastboot` | 150 | First, we start by unlocking the bootloader so that we can load other |
| 127 | command, but not with `adb` commands. I am not sure if this is standard or a | 151 | ROMs: |
| 128 | Fedora quirk, but I'm documenting my commands verbatim in this post. | ||
| 129 | 152 | ||
| 130 | First, we start by unlocking the bootloader so that we can load other ROMs: | 153 | #+begin_src sh |
| 131 | |||
| 132 | ```sh | ||
| 133 | sudo fastboot flashing unlock | 154 | sudo fastboot flashing unlock |
| 134 | ``` | 155 | #+end_src |
| 135 | |||
| 136 | ### Flashing Factory Images | ||
| 137 | 156 | ||
| 138 | Once the phone is unlocked, we can flash it with the `flash-all.sh` script found | 157 | *** Flashing Factory Images |
| 139 | inside the `raven-factory-2022091400` folder we entered earlier: | 158 | :PROPERTIES: |
| 159 | :CUSTOM_ID: flashing-factory-images | ||
| 160 | :END: | ||
| 161 | Once the phone is unlocked, we can flash it with the =flash-all.sh= | ||
| 162 | script found inside the =raven-factory-2022091400= folder we entered | ||
| 163 | earlier: | ||
| 140 | 164 | ||
| 141 | ```sh | 165 | #+begin_src sh |
| 142 | sudo ./flash-all.sh | 166 | sudo ./flash-all.sh |
| 143 | ``` | 167 | #+end_src |
| 144 | 168 | ||
| 145 | This process should take a few minutes and will print informational messages as | 169 | This process should take a few minutes and will print informational |
| 146 | things progress. Avoid doing anything on the phone while this process is | 170 | messages as things progress. Avoid doing anything on the phone while |
| 147 | operating. | 171 | this process is operating. |
| 148 | 172 | ||
| 149 | ### Lock the Bootloader | 173 | *** Lock the Bootloader |
| 150 | 174 | :PROPERTIES: | |
| 151 | If everything was successful, the phone should reboot a few times and finally | 175 | :CUSTOM_ID: lock-the-bootloader |
| 152 | land back on the bootloader screen. At this point, we can re-lock the | 176 | :END: |
| 153 | bootloader to enable full verified boot and protect the device from unwanted | 177 | If everything was successful, the phone should reboot a few times and |
| 154 | flashing or erasure of data. | 178 | finally land back on the bootloader screen. At this point, we can |
| 155 | 179 | re-lock the bootloader to enable full verified boot and protect the | |
| 156 | ```sh | 180 | device from unwanted flashing or erasure of data. |
| 181 | |||
| 182 | #+begin_src sh | ||
| 157 | sudo fastboot flashing lock | 183 | sudo fastboot flashing lock |
| 158 | ``` | 184 | #+end_src |
| 159 | 185 | ||
| 160 | Once done, the device will be wiped and ready for a fresh set-up! | 186 | Once done, the device will be wiped and ready for a fresh set-up! |
blog/2022-10-04-mtp-linux.org +61 −55
| @@ -1,79 +1,85 @@ | |||
| 1 | +++ | 1 | #+title: How to Mount an MTP Mobile Device on Linux |
| 2 | date = 2022-10-04 | 2 | #+date: 2022-10-04 |
| 3 | title = "How to Mount an MTP Mobile Device on Linux" | 3 | |
| 4 | description = "Learn how to easily mount Android MTP smartphones on Linux using the command line." | 4 | I recently ran into trouble attempting to mount my GrapheneOS phone to my laptop |
| 5 | +++ | 5 | running Fedora Linux via the [[https://en.wikipedia.org/wiki/Media_transfer_protocol][Media Transfer Protocol]] (MTP) and discovered a |
| 6 | 6 | simple and effective solution. | |
| 7 | I recently ran into trouble attempting to mount my GrapheneOS phone to my laptop | 7 | |
| 8 | running Fedora Linux via the [Media Transfer | 8 | ** Use a USB 3.0 Port |
| 9 | Protocol](https://en.wikipedia.org/wiki/Media_transfer_protocol) (MTP) and | 9 | :PROPERTIES: |
| 10 | discovered a simple and effective solution. | 10 | :CUSTOM_ID: use-a-usb-3.0-port |
| 11 | 11 | :END: | |
| 12 | ## Use a USB 3.0 Port | 12 | First, ensure that the device was plugged in to the laptop through a USB |
| 13 | 13 | 3.0 port, if possible. From a brief glance online, it seems that USB 2.0 | |
| 14 | First, ensure that the device was plugged in to the laptop through a | 14 | ports may cause issues with dropped connections over MTP. This is purely |
| 15 | USB 3.0 port, if possible. From a brief glance online, it seems that USB 2.0 | 15 | anecdotal since I don't have any evidence to link showing that USB 2.0 |
| 16 | ports may cause issues with dropped connections over MTP. This is purely | 16 | causes issues, but I can confirm that switching to a USB 3.0 port seemed |
| 17 | anecdotal since I don't have any evidence to link showing that USB 2.0 causes | 17 | to cut out most of my issues. |
| 18 | issues, but I can confirm that switching to a USB 3.0 port seemed to cut out | 18 | |
| 19 | most | 19 | ** Switch USB Preferences to MTP |
| 20 | of my issues. | 20 | :PROPERTIES: |
| 21 | 21 | :CUSTOM_ID: switch-usb-preferences-to-mtp | |
| 22 | ## Switch USB Preferences to MTP | 22 | :END: |
| 23 | 23 | Secondly, you need to ensure that the phone's USB preferences/mode is | |
| 24 | Secondly, you need to ensure that the phone's USB preferences/mode is | 24 | changed to MTP or File Transfer once the phone is plugged in. Other |
| 25 | changed to MTP or File Transfer once the phone is plugged in. Other modes will | 25 | modes will not allow you to access the phone's file system. |
| 26 | not allow you to access the phone's file system. | 26 | |
| 27 | 27 | ** Install =jmtpfs= | |
| 28 | ## Install `jmtpfs` | 28 | :PROPERTIES: |
| 29 | 29 | :CUSTOM_ID: install-jmtpfs | |
| 30 | Next, I used the `jmtpfs` package to mount my phone to my laptop. There are | 30 | :END: |
| 31 | other packages that exist, but this one worked perfectly for me. On Fedora | 31 | Next, I used the =jmtpfs= package to mount my phone to my laptop. There |
| 32 | Linux, you can install it like this: | 32 | are other packages that exist, but this one worked perfectly for me. On |
| 33 | 33 | Fedora Linux, you can install it like this: | |
| 34 | ```sh | 34 | |
| 35 | #+begin_src sh | ||
| 35 | sudo dnf install jmtpfs -y | 36 | sudo dnf install jmtpfs -y |
| 36 | ``` | 37 | #+end_src |
| 37 | 38 | ||
| 38 | ## Create a Mount Point | 39 | ** Create a Mount Point |
| 40 | :PROPERTIES: | ||
| 41 | :CUSTOM_ID: create-a-mount-point | ||
| 42 | :END: | ||
| 43 | Once you have the package installed, you just need to create a folder | ||
| 44 | for the device to use as a mount point. In my case, I used =/mnt/pixel=: | ||
| 39 | 45 | ||
| 40 | Once you have the package installed, you just need to create a folder for the | 46 | #+begin_src sh |
| 41 | device to use as a mount point. In my case, I used `/mnt/pixel`: | ||
| 42 | |||
| 43 | ```sh | ||
| 44 | sudo mkdir /mnt/pixel | 47 | sudo mkdir /mnt/pixel |
| 45 | sudo chown -R $USER:$USER /mnt/pixel | 48 | sudo chown -R $USER:$USER /mnt/pixel |
| 46 | ``` | 49 | #+end_src |
| 47 | |||
| 48 | ## Mount & Access the Phone's File System | ||
| 49 | 50 | ||
| 50 | Finally, plug-in and mount the device, and you should be able to see all | 51 | ** Mount & Access the Phone's File System |
| 52 | :PROPERTIES: | ||
| 53 | :CUSTOM_ID: mount-access-the-phones-file-system | ||
| 54 | :END: | ||
| 55 | Finally, plug-in and mount the device, and you should be able to see all | ||
| 51 | storage (internal and external) inside your new folder! | 56 | storage (internal and external) inside your new folder! |
| 52 | 57 | ||
| 53 | ```sh | 58 | #+begin_src sh |
| 54 | jmtpfs /mnt/pixel | 59 | jmtpfs /mnt/pixel |
| 55 | ``` | 60 | #+end_src |
| 56 | 61 | ||
| 57 | The output should look something like this: | 62 | The output should look something like this: |
| 58 | 63 | ||
| 59 | ```sh | 64 | #+begin_src sh |
| 60 | Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP). | 65 | Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP). |
| 61 | Android device detected, assigning default bug flags | 66 | Android device detected, assigning default bug flags |
| 62 | ``` | 67 | #+end_src |
| 63 | 68 | ||
| 64 | Now you are mounted and can do anything you'd like with the device's files: | 69 | Now you are mounted and can do anything you'd like with the device's |
| 70 | files: | ||
| 65 | 71 | ||
| 66 | ```sh | 72 | #+begin_src sh |
| 67 | cd /mnt/pixel | 73 | cd /mnt/pixel |
| 68 | ls -lha | 74 | ls -lha |
| 69 | ``` | 75 | #+end_src |
| 70 | 76 | ||
| 71 | From here, you will be able to see any internal or external storage available on | 77 | From here, you will be able to see any internal or external storage |
| 72 | the device: | 78 | available on the device: |
| 73 | 79 | ||
| 74 | ```sh | 80 | #+begin_src sh |
| 75 | total 0 | 81 | total 0 |
| 76 | drwxr-xr-x. 3 user user 0 Jan 1 1970 . | 82 | drwxr-xr-x. 3 user user 0 Jan 1 1970 . |
| 77 | drwxr-xr-x. 1 root root 10 Oct 4 13:29 .. | 83 | drwxr-xr-x. 1 root root 10 Oct 4 13:29 .. |
| 78 | drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage' | 84 | drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage' |
| 79 | ``` | 85 | #+end_src |
blog/2022-10-20-syncthing.org +176 −144
| @@ -1,166 +1,198 @@ | |||
| 1 | +++ | 1 | #+title: Syncthing: A Minimal Self-Hosted Cloud Storage Solution |
| 2 | date = 2022-10-20 | 2 | #+date: 2022-10-20 |
| 3 | title = "Syncthing: A Minimal Self-Hosted Cloud Storage Solution" | 3 | |
| 4 | description = "In this post, I walk through the Syncthing software and how to set it up on multiple devices." | 4 | ** An Overview of Syncthing |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: an-overview-of-syncthing | |
| 7 | ## An Overview of Syncthing | 7 | :END: |
| 8 | 8 | If you've been looking around the self-hosted cloud storage space for a | |
| 9 | If you've been looking around the self-hosted cloud storage space for a while, | 9 | while, you've undoubtedly run into someone suggesting |
| 10 | you've undoubtedly run into someone suggesting | 10 | [[https://syncthing.net][Syncthing]] as an option. However, it is an |
| 11 | [Syncthing](https://syncthing.net) as an option. However, it is an unusual | 11 | unusual alternative for those users out there who are used to having a |
| 12 | alternative for those users out there who are used to having a centralized cloud | 12 | centralized cloud server that serves as the "controller" of the data and |
| 13 | server that serves as the "controller" of the data and interacts with clients on | 13 | interacts with clients on devices to fetch files. |
| 14 | devices to fetch files. | 14 | |
| 15 | 15 | This post is a walkthrough of the Syncthing software, how I set up my | |
| 16 | This post is a walkthrough of the Syncthing software, how I set up my personal | 16 | personal storage, and some pros and cons of using the software. |
| 17 | storage, and some pros and cons of using the software. | 17 | |
| 18 | 18 | ** Installing Syncthing | |
| 19 | ## Installing Syncthing | 19 | :PROPERTIES: |
| 20 | 20 | :CUSTOM_ID: installing-syncthing | |
| 21 | To install Syncthing, visit the [Downloads](https://syncthing.net/downloads/) | 21 | :END: |
| 22 | page or install via your device's package manager. | 22 | To install Syncthing, visit the |
| 23 | 23 | [[https://syncthing.net/downloads/][Downloads]] page or install via your | |
| 24 | ### Server & Desktop | 24 | device's package manager. |
| 25 | 25 | ||
| 26 | You can install Syncthing on servers and desktops via the Downloads page linked | 26 | *** Server & Desktop |
| 27 | above or via the command-line. | 27 | :PROPERTIES: |
| 28 | :CUSTOM_ID: server-desktop | ||
| 29 | :END: | ||
| 30 | You can install Syncthing on servers and desktops via the Downloads page | ||
| 31 | linked above or via the command-line. | ||
| 28 | 32 | ||
| 29 | For Debian-based distros: | 33 | For Debian-based distros: |
| 30 | 34 | ||
| 31 | ```sh | 35 | #+begin_src sh |
| 32 | sudo apt install syncthing | 36 | sudo apt install syncthing |
| 33 | ``` | 37 | #+end_src |
| 34 | 38 | ||
| 35 | For Fedora-based distros: | 39 | For Fedora-based distros: |
| 36 | 40 | ||
| 37 | ```sh | 41 | #+begin_src sh |
| 38 | sudo dnf install syncthing | 42 | sudo dnf install syncthing |
| 39 | ``` | 43 | #+end_src |
| 40 | 44 | ||
| 41 | ### Mobile | 45 | *** Mobile |
| 42 | 46 | :PROPERTIES: | |
| 43 | Syncthing for Android is available on | 47 | :CUSTOM_ID: mobile |
| 44 | [F-Droid](https://f-droid.org/packages/com.nutomic.syncthingandroid/) and | 48 | :END: |
| 45 | [Google Play](https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid). | 49 | Syncthing for Android is available on |
| 46 | Syncthing does not have an official iOS client, but there is a third-party | 50 | [[https://f-droid.org/packages/com.nutomic.syncthingandroid/][F-Droid]] |
| 47 | client called [Möbius | 51 | and |
| 48 | Sync](https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216). | 52 | [[https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid][Google |
| 49 | 53 | Play]]. Syncthing does not have an official iOS client, but there is a | |
| 50 | ## How Does Syncthing Work? | 54 | third-party client called |
| 51 | 55 | [[https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216][Möbius | |
| 52 | To start, I wanted to include the main marketing blurb from their website: | 56 | Sync]]. |
| 53 | 57 | ||
| 54 | > Syncthing is a continuous file synchronization program. It synchronizes files | 58 | ** How Does Syncthing Work? |
| 55 | > between two or more computers in real time, safely protected from prying eyes. | 59 | :PROPERTIES: |
| 56 | > Your data is your data alone, and you deserve to choose where it is stored, | 60 | :CUSTOM_ID: how-does-syncthing-work |
| 57 | > whether it is shared with some third party, and how it's transmitted over the | 61 | :END: |
| 58 | > internet. | 62 | To start, I wanted to include the main marketing blurb from their |
| 59 | 63 | website: | |
| 60 | Let's break this apart and add in some other details to help explain what | 64 | |
| 61 | exactly Syncthing does in order to sync files between devices. | 65 | #+begin_quote |
| 62 | 66 | Syncthing is a continuous file synchronization program. It synchronizes | |
| 63 | ### Local Syncthing Server(s) | 67 | files between two or more computers in real time, safely protected from |
| 64 | 68 | prying eyes. Your data is your data alone, and you deserve to choose | |
| 65 | Syncthing syncs files between multiple devices by creating a local server on | 69 | where it is stored, whether it is shared with some third party, and how |
| 66 | each device. These local servers handle a few different things, such as | 70 | it's transmitted over the internet. |
| 67 | watching files and directories for changes, hosting an administrative GUI | 71 | |
| 68 | website, and authenticating with connected devices. | 72 | #+end_quote |
| 69 | 73 | ||
| 70 | You can also start, stop, and restart the Syncthing server via the command-line | 74 | Let's break this apart and add in some other details to help explain |
| 71 | or web dashboard. If you're running Syncthing on a device with `systemd`, you | 75 | what exactly Syncthing does in order to sync files between devices. |
| 72 | can use the following commands: | 76 | |
| 73 | 77 | *** Local Syncthing Server(s) | |
| 74 | ```sh | 78 | :PROPERTIES: |
| 79 | :CUSTOM_ID: local-syncthing-servers | ||
| 80 | :END: | ||
| 81 | Syncthing syncs files between multiple devices by creating a local | ||
| 82 | server on each device. These local servers handle a few different | ||
| 83 | things, such as watching files and directories for changes, hosting an | ||
| 84 | administrative GUI website, and authenticating with connected devices. | ||
| 85 | |||
| 86 | You can also start, stop, and restart the Syncthing server via the | ||
| 87 | command-line or web dashboard. If you're running Syncthing on a device | ||
| 88 | with =systemd=, you can use the following commands: | ||
| 89 | |||
| 90 | #+begin_src sh | ||
| 75 | sudo systemctl start syncthing@username.service | 91 | sudo systemctl start syncthing@username.service |
| 76 | sudo systemctl restart syncthing@username.service | 92 | sudo systemctl restart syncthing@username.service |
| 77 | sudo systemctl stop syncthing@username.service | 93 | sudo systemctl stop syncthing@username.service |
| 78 | ``` | 94 | #+end_src |
| 79 | 95 | ||
| 80 | ### Syncthing Dashboard | 96 | *** Syncthing Dashboard |
| 81 | 97 | :PROPERTIES: | |
| 82 | This biggest part of Syncthing is the admin GUI website that runs on each | 98 | :CUSTOM_ID: syncthing-dashboard |
| 83 | device (note that mobile devices will use the Syncthing app rather than the | 99 | :END: |
| 84 | web GUI). The admin GUI is available through the web browser on the | 100 | This biggest part of Syncthing is the admin GUI website that runs on |
| 85 | local device that is running Syncthing - simply go to `http://localhost:8384` | 101 | each device (note that mobile devices will use the Syncthing app rather |
| 86 | or `http://127.0.0.1:8384`. This web page is the place where you will change | 102 | than the web GUI). The admin GUI is available through the web browser on |
| 87 | settings, add/modify synced files, and add/modify connected devices. | 103 | the local device that is running Syncthing - simply go to |
| 104 | =http://localhost:8384= or =http://127.0.0.1:8384=. This web page is the | ||
| 105 | place where you will change settings, add/modify synced files, and | ||
| 106 | add/modify connected devices. | ||
| 88 | 107 | ||
| 89 | Here's an example web GUI dashboard: | 108 | Here's an example web GUI dashboard: |
| 90 | 109 | ||
| 91 |  | 110 | #+caption: Syncthing Dashboard |
| 92 | 111 | [[https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png]] | |
| 93 | ### Remote Devices | 112 | |
| 94 | 113 | *** Remote Devices | |
| 95 | A cloud storage solution wouldn't be very useful if you aren't able to share | 114 | :PROPERTIES: |
| 96 | data among various devices. Syncthing does this by sharing Device IDs to connect | 115 | :CUSTOM_ID: remote-devices |
| 97 | servers, and then by manually sharing Folders with devices that have been | 116 | :END: |
| 98 | connected. | 117 | A cloud storage solution wouldn't be very useful if you aren't able to |
| 99 | 118 | share data among various devices. Syncthing does this by sharing Device | |
| 100 | For instance, if you have a laptop running Syncthing and then install the | 119 | IDs to connect servers, and then by manually sharing Folders with |
| 101 | Syncthing mobile app on a phone, you could scan the laptop's QR code for Device | 120 | devices that have been connected. |
| 102 | ID and then accept the authentication on the laptop's dashboard. Next, you can | 121 | |
| 103 | use either device to select a folder for sharing and dictating which device | 122 | For instance, if you have a laptop running Syncthing and then install |
| 104 | should send, receive, or both. | 123 | the Syncthing mobile app on a phone, you could scan the laptop's QR code |
| 105 | 124 | for Device ID and then accept the authentication on the laptop's | |
| 106 | When you connect devices, you can set one device as an "Introducer," which can | 125 | dashboard. Next, you can use either device to select a folder for |
| 107 | add devices from the introducer to the device list, for mutually shared folders. | 126 | sharing and dictating which device should send, receive, or both. |
| 108 | You can also configure Auto Accept, compression, rate limits, and more settings | 127 | |
| 109 | per device. | 128 | When you connect devices, you can set one device as an "Introducer," |
| 110 | 129 | which can add devices from the introducer to the device list, for | |
| 111 | ## My Personal Cloud Storage Set-up | 130 | mutually shared folders. You can also configure Auto Accept, |
| 112 | 131 | compression, rate limits, and more settings per device. | |
| 113 | Personally, I use a model similar to a traditional cloud storage service. I have | 132 | |
| 114 | a "centralized" server running 24/7 that acts as an Introducer for my Syncthing | 133 | ** My Personal Cloud Storage Set-up |
| 115 | network. I think of this as my main storage and all other devices as tertiary | 134 | :PROPERTIES: |
| 116 | client devices. I will likely add additional servers as backups as time goes on | 135 | :CUSTOM_ID: my-personal-cloud-storage-set-up |
| 117 | so that I don't have to rely on my laptop or phone as the only backups. | 136 | :END: |
| 118 | 137 | Personally, I use a model similar to a traditional cloud storage | |
| 119 | Currently, I have one desktop and one mobile device connected to the network, | 138 | service. I have a "centralized" server running 24/7 that acts as an |
| 120 | both running intermittently as they are not powered-on 24/7. | 139 | Introducer for my Syncthing network. I think of this as my main storage |
| 121 | 140 | and all other devices as tertiary client devices. I will likely add | |
| 122 | The initial set-up of the software was easy enough, but data transfer rates were | 141 | additional servers as backups as time goes on so that I don't have to |
| 123 | incredibly slow for me due to the Wi-Fi. Instead, I plugged my laptop into the | 142 | rely on my laptop or phone as the only backups. |
| 124 | ethernet network that my server is on and manually copied my folders over to the | 143 | |
| 125 | server with `scp`. Once complete, Syncthing validated that all files were there | 144 | Currently, I have one desktop and one mobile device connected to the |
| 126 | and not missing, and it did not need to transfer any data through the WAN. | 145 | network, both running intermittently as they are not powered-on 24/7. |
| 127 | 146 | ||
| 128 | As slow as the transfer was going, this probably saved me a few days of waiting | 147 | The initial set-up of the software was easy enough, but data transfer |
| 129 | for my ~100GB sync. | 148 | rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged |
| 130 | 149 | my laptop into the ethernet network that my server is on and manually | |
| 131 | ## Pros & Cons | 150 | copied my folders over to the server with =scp=. Once complete, |
| 132 | 151 | Syncthing validated that all files were there and not missing, and it | |
| 133 | I've put together a short list of pros and cons for Syncthing. I thought about | 152 | did not need to transfer any data through the WAN. |
| 134 | my experiences with Nextcloud, WebDAV, proprietary services (Google Drive, | 153 | |
| 135 | iCloud, etc.), and privacy-focused cloud solutions (pCloud, Tresorit, etc.). | 154 | As slow as the transfer was going, this probably saved me a few days of |
| 136 | 155 | waiting for my ~100GB sync. | |
| 137 | **Pros:** | 156 | |
| 157 | ** Pros & Cons | ||
| 158 | :PROPERTIES: | ||
| 159 | :CUSTOM_ID: pros-cons | ||
| 160 | :END: | ||
| 161 | I've put together a short list of pros and cons for Syncthing. I thought | ||
| 162 | about my experiences with Nextcloud, WebDAV, proprietary services | ||
| 163 | (Google Drive, iCloud, etc.), and privacy-focused cloud solutions | ||
| 164 | (pCloud, Tresorit, etc.). | ||
| 165 | |||
| 166 | *Pros:* | ||
| 138 | 167 | ||
| 139 | - I've faced no data loss at all through my two-month trial run. | 168 | - I've faced no data loss at all through my two-month trial run. |
| 140 | - No third-parties store your data on their servers. | 169 | - No third-parties store your data on their servers. |
| 141 | - You have full control over your data and can take your data and leave at any | 170 | - You have full control over your data and can take your data and leave |
| 142 | time. | 171 | at any time. |
| 143 | - It's possible to encrypt client-side easily with software like Cryptomator. | 172 | - It's possible to encrypt client-side easily with software like |
| 144 | - No proprietary clients or mounted volumes, just plain files and folders. | 173 | Cryptomator. |
| 174 | - No proprietary clients or mounted volumes, just plain files and | ||
| 175 | folders. | ||
| 145 | 176 | ||
| 146 | **Cons:** | 177 | *Cons:* |
| 147 | 178 | ||
| 148 | - The learning curve is steeper than traditional cloud services and is | 179 | - The learning curve is steeper than traditional cloud services and is |
| 149 | focused on a technical audience. | 180 | focused on a technical audience. |
| 150 | - If a device needs to modify files in a Folder, the devices will need to | 181 | - If a device needs to modify files in a Folder, the devices will need |
| 151 | sync ALL files from the folder, which may be large. To avoid size | 182 | to sync ALL files from the folder, which may be large. To avoid size |
| 152 | restraints, split large folders into smaller folders for syncing. | 183 | restraints, split large folders into smaller folders for syncing. |
| 153 | - Syncing can be slow due to the clients/servers initially connecting or | 184 | - Syncing can be slow due to the clients/servers initially connecting or |
| 154 | re-connecting after sleeping. | 185 | re-connecting after sleeping. |
| 155 | - Multiple personal devices are required and require the user to own or rent | 186 | - Multiple personal devices are required and require the user to own or |
| 156 | them as no third-party servers are involved in the storage of data. | 187 | rent them as no third-party servers are involved in the storage of |
| 157 | 188 | data. | |
| 158 | Overall, I've had a great experience with Syncthing so far. I've had no data | 189 | |
| 159 | loss, syncing has been quick and easy when changes are made to files, device | 190 | Overall, I've had a great experience with Syncthing so far. I've had no |
| 160 | connections are reliable, and I love the freedom of controlling the clients and | 191 | data loss, syncing has been quick and easy when changes are made to |
| 161 | servers as I choose. | 192 | files, device connections are reliable, and I love the freedom of |
| 162 | 193 | controlling the clients and servers as I choose. | |
| 163 | Not to mention that I appreciate that I - or someone else - could pull the | 194 | |
| 164 | Syncthing [source code](https://github.com/syncthing) and continue | 195 | Not to mention that I appreciate that I - or someone else - could pull |
| 165 | development/support if the Syncthing Foundation decides to stop developing the | 196 | the Syncthing [[https://github.com/syncthing][source code]] and continue |
| 166 | software or sells the business. | 197 | development/support if the Syncthing Foundation decides to stop |
| 198 | developing the software or sells the business. | ||
blog/2022-10-22-alpine-linux.org +223 −186
| @@ -1,264 +1,301 @@ | |||
| 1 | +++ | 1 | #+title: Alpine Linux: My New Server OS |
| 2 | date = 2022-10-22 | 2 | #+date: 2022-10-22 |
| 3 | title = "Alpine Linux: My New Server OS" | 3 | |
| 4 | description = "In this post, I explain Alpine Linux's benefits as a server OS and explain my current setup." | 4 | ** Alpine Linux |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: alpine-linux | |
| 7 | ## Alpine Linux | 7 | :END: |
| 8 | 8 | [[https://alpinelinux.org][Alpine Linux]] is a very small distro, built | |
| 9 | [Alpine Linux](https://alpinelinux.org) is a very small distro, built on musl | 9 | on musl libc and busybox. It uses ash as the default shell, OpenRC as |
| 10 | libc and busybox. It uses ash as the default shell, OpenRC as the init | 10 | the init system, and apk as the package manager. According to their |
| 11 | system, and apk as the package manager. According to their website, an Alpine | 11 | website, an Alpine container "requires no more than 8 MB and a minimal |
| 12 | container "requires no more than 8 MB and a minimal installation to disk | 12 | installation to disk requires around 130 MB of storage." An actual bare |
| 13 | requires around 130 MB of storage." An actual bare metal machine is recommended | 13 | metal machine is recommended to have 100 MB of RAM and 0-700 MB of |
| 14 | to have 100 MB of RAM and 0-700 MB of storage space. | 14 | storage space. |
| 15 | 15 | ||
| 16 | Historically, I've used Ubuntu's minimal installation image as my server OS for | 16 | Historically, I've used Ubuntu's minimal installation image as my server |
| 17 | the last five years. Ubuntu worked well and helped as my original server | 17 | OS for the last five years. Ubuntu worked well and helped as my original |
| 18 | contained an nVidia GPU and no onboard graphics, so quite a few distros won't | 18 | server contained an nVidia GPU and no onboard graphics, so quite a few |
| 19 | boot or install without a lot of tinkering. | 19 | distros won't boot or install without a lot of tinkering. |
| 20 | 20 | ||
| 21 | Alpine has given me a huge increase in performance across my Docker apps and | 21 | Alpine has given me a huge increase in performance across my Docker apps |
| 22 | Nginx websites. CPU load for the new server I'm using to test Alpine hovers | 22 | and Nginx websites. CPU load for the new server I'm using to test Alpine |
| 23 | around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @ 3.70GHz. | 23 | hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @ |
| 24 | 24 | 3.70GHz. | |
| 25 | The only services I haven't moved over to Alpine are Plex Media Server and | 25 | |
| 26 | Syncthing, which may increase CPU load quite a bit depending on how many | 26 | The only services I haven't moved over to Alpine are Plex Media Server |
| 27 | streams are running. | 27 | and Syncthing, which may increase CPU load quite a bit depending on how |
| 28 | 28 | many streams are running. | |
| 29 | ### Installation | 29 | |
| 30 | 30 | *** Installation | |
| 31 | In terms of installation, Alpine has an incredibly useful | 31 | :PROPERTIES: |
| 32 | [wiki](https://wiki.alpinelinux.org/wiki/Installation) that will guide a user | 32 | :CUSTOM_ID: installation |
| 33 | throughout the installation and post-installation processes, as well as various | 33 | :END: |
| 34 | other articles and guides. | 34 | In terms of installation, Alpine has an incredibly useful |
| 35 | 35 | [[https://wiki.alpinelinux.org/wiki/Installation][wiki]] that will guide | |
| 36 | To install Alpine, find an appropriate [image to | 36 | a user throughout the installation and post-installation processes, as |
| 37 | download](https://alpinelinux.org/downloads/) and flash it to a USB using | 37 | well as various other articles and guides. |
| 38 | software such as Rufus or Etcher. I opted to use the Standard image for my | 38 | |
| 39 | x86_64 architecture. | 39 | To install Alpine, find an appropriate |
| 40 | 40 | [[https://alpinelinux.org/downloads/][image to download]] and flash it | |
| 41 | Once the USB is ready, plug it into the machine and reboot. | 41 | to a USB using software such as Rufus or Etcher. I opted to use the |
| 42 | Note that you may have to use a key such as `Esc` or `F1-12` to access the | 42 | Standard image for my x86_64 architecture. |
| 43 | boot menu. | 43 | |
| 44 | The Alpine Linux terminal will load quickly and for a login. | 44 | Once the USB is ready, plug it into the machine and reboot. Note that |
| 45 | 45 | you may have to use a key such as =Esc= or =F1-12= to access the boot | |
| 46 | To log in to the installation image, use the `root` account; there is no | 46 | menu. The Alpine Linux terminal will load quickly and for a login. |
| 47 | |||
| 48 | To log in to the installation image, use the =root= account; there is no | ||
| 47 | password. Once logged-in, execute the setup command: | 49 | password. Once logged-in, execute the setup command: |
| 48 | 50 | ||
| 49 | ```sh | 51 | #+begin_src sh |
| 50 | setup-alpine | 52 | setup-alpine |
| 51 | ``` | 53 | #+end_src |
| 52 | 54 | ||
| 53 | The setup script will ask a series of questions to configure the system. Be sure | 55 | The setup script will ask a series of questions to configure the system. |
| 54 | to answer carefully or else you may have to re-configure the system after boot. | 56 | Be sure to answer carefully or else you may have to re-configure the |
| 57 | system after boot. | ||
| 55 | 58 | ||
| 56 | - Keyboard Layout (Local keyboard language and usage mode, e.g., us and variant | 59 | - Keyboard Layout (Local keyboard language and usage mode, e.g., us and |
| 57 | of us-nodeadkeys.) | 60 | variant of us-nodeadkeys.) |
| 58 | - Hostname (The name for the computer.) | 61 | - Hostname (The name for the computer.) |
| 59 | - Network (For example, automatic IP address discovery with the "DHCP" | 62 | - Network (For example, automatic IP address discovery with the "DHCP" |
| 60 | protocol.) | 63 | protocol.) |
| 61 | - DNS Servers (Domain Name Servers to query. For privacy reasons, it is NOT | 64 | - DNS Servers (Domain Name Servers to query. For privacy reasons, it is |
| 62 | recommended to route every local request to servers like Google's 8.8.8.8 .) | 65 | NOT recommended to route every local request to servers like Google's |
| 66 | 8.8.8.8 .) | ||
| 63 | - Timezone | 67 | - Timezone |
| 64 | - Proxy (Proxy server to use for accessing the web. Use "none" for direct | 68 | - Proxy (Proxy server to use for accessing the web. Use "none" for |
| 65 | connections to the internet.) | 69 | direct connections to the internet.) |
| 66 | - Mirror (From where to download packages. Choose the organization you trust | 70 | - Mirror (From where to download packages. Choose the organization you |
| 67 | giving your usage patterns to.) | 71 | trust giving your usage patterns to.) |
| 68 | - SSH (Secure SHell remote access server. "Openssh" is part of the default | 72 | - SSH (Secure SHell remote access server. "Openssh" is part of the |
| 69 | install image. Use "none" to disable remote login, e.g. on laptops.) | 73 | default install image. Use "none" to disable remote login, e.g. on |
| 70 | - NTP (Network Time Protocol client used for keeping the system clock in sync | 74 | laptops.) |
| 71 | with a time-server. Package "chrony" is part of the default install image.) | 75 | - NTP (Network Time Protocol client used for keeping the system clock in |
| 72 | - Disk Mode (Select between diskless (disk="none"), "data" or "sys", as | 76 | sync with a time-server. Package "chrony" is part of the default |
| 73 | described above.) | 77 | install image.) |
| 74 | 78 | - Disk Mode (Select between diskless (disk="none"), "data" or "sys", as | |
| 75 | Once the setup script is finished, be sure to reboot the machine and remove the | 79 | described above.) |
| 76 | USB device. | 80 | |
| 77 | 81 | Once the setup script is finished, be sure to reboot the machine and | |
| 78 | ```sh | 82 | remove the USB device. |
| 83 | |||
| 84 | #+begin_src sh | ||
| 79 | reboot | 85 | reboot |
| 80 | ``` | 86 | #+end_src |
| 81 | 87 | ||
| 82 | ### Post-Installation | 88 | *** Post-Installation |
| 83 | 89 | :PROPERTIES: | |
| 84 | There are many things you can do once your Alpine Linux system is up and | 90 | :CUSTOM_ID: post-installation |
| 85 | running, and it largely depends on what you'll use the machine for. | 91 | :END: |
| 86 | I'm going to walk through my personal post-installation setup for my web server. | 92 | There are many things you can do once your Alpine Linux system is up and |
| 87 | 93 | running, and it largely depends on what you'll use the machine for. I'm | |
| 88 | #### Upgrade the System | 94 | going to walk through my personal post-installation setup for my web |
| 89 | 95 | server. | |
| 90 | First, login as `root` in order to update and upgrade the system: | 96 | |
| 91 | 97 | **** Upgrade the System | |
| 92 | ```sh | 98 | :PROPERTIES: |
| 99 | :CUSTOM_ID: upgrade-the-system | ||
| 100 | :END: | ||
| 101 | First, login as =root= in order to update and upgrade the system: | ||
| 102 | |||
| 103 | #+begin_src sh | ||
| 93 | apk -U upgrade | 104 | apk -U upgrade |
| 94 | ``` | 105 | #+end_src |
| 95 | |||
| 96 | #### Adding a User | ||
| 97 | 106 | ||
| 98 | I needed to add a user so that I don't need to log in as root. | 107 | **** Adding a User |
| 99 | Note that if you're used to using the `sudo` command, you will now need to | 108 | :PROPERTIES: |
| 100 | use the `doas` command on Alpine Linux. | 109 | :CUSTOM_ID: adding-a-user |
| 110 | :END: | ||
| 111 | I needed to add a user so that I don't need to log in as root. Note that | ||
| 112 | if you're used to using the =sudo= command, you will now need to use the | ||
| 113 | =doas= command on Alpine Linux. | ||
| 101 | 114 | ||
| 102 | ```sh | 115 | #+begin_src sh |
| 103 | apk add doas | 116 | apk add doas |
| 104 | adduser <username> | 117 | adduser <username> |
| 105 | adduser <username> wheel | 118 | adduser <username> wheel |
| 106 | ``` | 119 | #+end_src |
| 107 | 120 | ||
| 108 | You can now log out and log back in using the newly-created user: | 121 | You can now log out and log back in using the newly-created user: |
| 109 | 122 | ||
| 110 | ```sh | 123 | #+begin_src sh |
| 111 | exit | 124 | exit |
| 112 | ``` | 125 | #+end_src |
| 113 | 126 | ||
| 114 | #### Enable Community Packages | 127 | **** Enable Community Packages |
| 128 | :PROPERTIES: | ||
| 129 | :CUSTOM_ID: enable-community-packages | ||
| 130 | :END: | ||
| 131 | In order to install more common packages that aren't found in the =main= | ||
| 132 | repository, you will need to enable the =community= repository: | ||
| 115 | 133 | ||
| 116 | In order to install more common packages that aren't found in the `main` | 134 | #+begin_src sh |
| 117 | repository, you will need to enable the `community` repository: | ||
| 118 | |||
| 119 | ```sh | ||
| 120 | doas nano /etc/apk/repositories | 135 | doas nano /etc/apk/repositories |
| 121 | ``` | 136 | #+end_src |
| 122 | 137 | ||
| 123 | Uncomment the community line for whichever version of Alpine you're running: | 138 | Uncomment the community line for whichever version of Alpine you're |
| 139 | running: | ||
| 124 | 140 | ||
| 125 | ```sh | 141 | #+begin_src sh |
| 126 | /media/usb/apks | 142 | /media/usb/apks |
| 127 | http://dl-cdn.alpinelinux.org/alpine/v3.16/main | 143 | http://dl-cdn.alpinelinux.org/alpine/v3.16/main |
| 128 | http://dl-cdn.alpinelinux.org/alpine/v3.16/community | 144 | http://dl-cdn.alpinelinux.org/alpine/v3.16/community |
| 129 | #http://dl-cdn.alpinelinux.org/alpine/edge/main | 145 | #http://dl-cdn.alpinelinux.org/alpine/edge/main |
| 130 | #http://dl-cdn.alpinelinux.org/alpine/edge/community | 146 | #http://dl-cdn.alpinelinux.org/alpine/edge/community |
| 131 | #http://dl-cdn.alpinelinux.org/alpine/edge/testing | 147 | #http://dl-cdn.alpinelinux.org/alpine/edge/testing |
| 132 | ``` | 148 | #+end_src |
| 133 | |||
| 134 | #### Install Required Packages | ||
| 135 | 149 | ||
| 136 | Now that the community packages are available, you can install any packages you | 150 | **** Install Required Packages |
| 137 | need. In my case, I installed the web server packages I need for my services: | 151 | :PROPERTIES: |
| 152 | :CUSTOM_ID: install-required-packages | ||
| 153 | :END: | ||
| 154 | Now that the community packages are available, you can install any | ||
| 155 | packages you need. In my case, I installed the web server packages I | ||
| 156 | need for my services: | ||
| 138 | 157 | ||
| 139 | ```sh | 158 | #+begin_src sh |
| 140 | doas apk add nano nginx docker docker-compose ufw | 159 | doas apk add nano nginx docker docker-compose ufw |
| 141 | ``` | 160 | #+end_src |
| 142 | |||
| 143 | #### SSH | ||
| 144 | 161 | ||
| 145 | If you didn't install OpenSSH as part of the installation, you can do so now: | 162 | **** SSH |
| 163 | :PROPERTIES: | ||
| 164 | :CUSTOM_ID: ssh | ||
| 165 | :END: | ||
| 166 | If you didn't install OpenSSH as part of the installation, you can do so | ||
| 167 | now: | ||
| 146 | 168 | ||
| 147 | ```sh | 169 | #+begin_src sh |
| 148 | doas apk add openssh | 170 | doas apk add openssh |
| 149 | ``` | 171 | #+end_src |
| 150 | 172 | ||
| 151 | Next, either create a new key or copy your SSH key to the server from your | 173 | Next, either create a new key or copy your SSH key to the server from |
| 152 | current machines: | 174 | your current machines: |
| 153 | 175 | ||
| 154 | ```sh | 176 | #+begin_src sh |
| 155 | # Create a new key | 177 | # Create a new key |
| 156 | ssh-keygen | 178 | ssh-keygen |
| 157 | ``` | 179 | #+end_src |
| 158 | 180 | ||
| 159 | If you need to copy an existing SSH key from a current machine: | 181 | If you need to copy an existing SSH key from a current machine: |
| 160 | 182 | ||
| 161 | ```sh | 183 | #+begin_src sh |
| 162 | # Copy key from existing machines | 184 | # Copy key from existing machines |
| 163 | ssh-copy-id <username>@<ip_address> | 185 | ssh-copy-id <username>@<ip_address> |
| 164 | ``` | 186 | #+end_src |
| 165 | 187 | ||
| 166 | #### Firewall | 188 | **** Firewall |
| 189 | :PROPERTIES: | ||
| 190 | :CUSTOM_ID: firewall | ||
| 191 | :END: | ||
| 192 | Lastly, I installed =ufw= above as my firewall. To set up, default to | ||
| 193 | deny incoming and allow outgoing connections. Then selectively allow | ||
| 194 | other ports or apps as needed. | ||
| 167 | 195 | ||
| 168 | Lastly, I installed `ufw` above as my firewall. | 196 | #+begin_src sh |
| 169 | To set up, default to deny incoming and allow outgoing connections. | ||
| 170 | Then selectively allow other ports or apps as needed. | ||
| 171 | |||
| 172 | ```sh | ||
| 173 | doas ufw default deny incoming | 197 | doas ufw default deny incoming |
| 174 | doas ufw default allow outgoing | 198 | doas ufw default allow outgoing |
| 175 | doas ufw allow SSH | 199 | doas ufw allow SSH |
| 176 | doas ufw allow "WWW Full" | 200 | doas ufw allow "WWW Full" |
| 177 | doas ufw allow 9418 # Git server port | 201 | doas ufw allow 9418 # Git server port |
| 178 | ``` | 202 | #+end_src |
| 179 | |||
| 180 | #### Change Hostname | ||
| 181 | 203 | ||
| 182 | If you don't like the hostname set during installation, you just need to edit | 204 | **** Change Hostname |
| 183 | two files. First, edit the simple hostname file: | 205 | :PROPERTIES: |
| 206 | :CUSTOM_ID: change-hostname | ||
| 207 | :END: | ||
| 208 | If you don't like the hostname set during installation, you just need to | ||
| 209 | edit two files. First, edit the simple hostname file: | ||
| 184 | 210 | ||
| 185 | ```sh | 211 | #+begin_src sh |
| 186 | doas nano /etc/hostname | 212 | doas nano /etc/hostname |
| 187 | ``` | 213 | #+end_src |
| 188 | 214 | ||
| 189 | ```sh | 215 | #+begin_src sh |
| 190 | <hostname> | 216 | <hostname> |
| 191 | ``` | 217 | #+end_src |
| 192 | 218 | ||
| 193 | Next, edit the `hosts` file: | 219 | Next, edit the =hosts= file: |
| 194 | 220 | ||
| 195 | ```sh | 221 | #+begin_src sh |
| 196 | doas nano /etc/hosts | 222 | doas nano /etc/hosts |
| 197 | ``` | 223 | #+end_src |
| 198 | 224 | ||
| 199 | ```sh | 225 | #+begin_src sh |
| 200 | 127.0.0.1 <hostname>.local <hostname> localhost.local localhost | 226 | 127.0.0.1 <hostname>.local <hostname> localhost.local localhost |
| 201 | ::1 <hostname> <hostname>.local | 227 | ::1 <hostname> <hostname>.local |
| 202 | ``` | 228 | #+end_src |
| 203 | 229 | ||
| 204 | ## Nginx Web Server | 230 | ** Nginx Web Server |
| 205 | 231 | :PROPERTIES: | |
| 206 | To set up my web server, I simply created the `www` user and created the | 232 | :CUSTOM_ID: nginx-web-server |
| 233 | :END: | ||
| 234 | To set up my web server, I simply created the =www= user and created the | ||
| 207 | necessary files. | 235 | necessary files. |
| 208 | 236 | ||
| 209 | ```sh | 237 | #+begin_src sh |
| 210 | doas adduser -D -g 'www' www | 238 | doas adduser -D -g 'www' www |
| 211 | mkdir /www | 239 | mkdir /www |
| 212 | doas mkdir /www | 240 | doas mkdir /www |
| 213 | doas chown -R www:www /var/lib/nginx/ | 241 | doas chown -R www:www /var/lib/nginx/ |
| 214 | doas chown -R www:www /www | 242 | doas chown -R www:www /www |
| 215 | ``` | 243 | #+end_src |
| 216 | 244 | ||
| 217 | If you're running a simple webroot, you can alter the main `nginx.conf` file. | 245 | If you're running a simple webroot, you can alter the main =nginx.conf= |
| 218 | Otherwise, you can drop configuration files in the following directory. You | 246 | file. Otherwise, you can drop configuration files in the following |
| 219 | don't need to enable or symlink the configuration file like you do in other | 247 | directory. You don't need to enable or symlink the configuration file |
| 220 | systems. | 248 | like you do in other systems. |
| 221 | 249 | ||
| 222 | ```sh | 250 | #+begin_src sh |
| 223 | doas nano /etc/nginx/http.d/example_website.conf | 251 | doas nano /etc/nginx/http.d/example_website.conf |
| 224 | ``` | 252 | #+end_src |
| 225 | 253 | ||
| 226 | Once the configuration is set and pointed at the `/www` directory to serve | 254 | Once the configuration is set and pointed at the =/www= directory to |
| 227 | files, enable the Nginx service: | 255 | serve files, enable the Nginx service: |
| 228 | 256 | ||
| 229 | ```sh | 257 | #+begin_src sh |
| 230 | # Note that 'default' must be included or Nginx will not start on boot | 258 | # Note that 'default' must be included or Nginx will not start on boot |
| 231 | doas rc-update add nginx default | 259 | doas rc-update add nginx default |
| 232 | ``` | 260 | #+end_src |
| 233 | 261 | ||
| 234 | ## Docker Containers | 262 | ** Docker Containers |
| 235 | 263 | :PROPERTIES: | |
| 236 | Docker works exactly the same as other systems. Either execute a `docker run` | 264 | :CUSTOM_ID: docker-containers |
| 237 | command or create a `docker-compose.yml` file and do `docker-compose up -d`. | 265 | :END: |
| 238 | 266 | Docker works exactly the same as other systems. Either execute a | |
| 239 | ## Git Server | 267 | =docker run= command or create a =docker-compose.yml= file and do |
| 240 | 268 | =docker-compose up -d=. | |
| 241 | I went in-depth on how to self-host a git server in another post: | 269 | |
| 242 | [Self-Hosting a Personal Git Server](/blog/git-server/). | 270 | ** Git Server |
| 243 | 271 | :PROPERTIES: | |
| 244 | However, there are a few differences with Alpine. First note that in order to | 272 | :CUSTOM_ID: git-server |
| 245 | change the `git` user's shell, you must do a few things a little different: | 273 | :END: |
| 246 | 274 | I went in-depth on how to self-host a git server in another post: | |
| 247 | ```sh | 275 | [[/blog/git-server/][Self-Hosting a Personal Git Server]]. |
| 276 | |||
| 277 | However, there are a few differences with Alpine. First note that in | ||
| 278 | order to change the =git= user's shell, you must do a few things a | ||
| 279 | little different: | ||
| 280 | |||
| 281 | #+begin_src sh | ||
| 248 | doas apk add libuser | 282 | doas apk add libuser |
| 249 | doas touch /etc/login.defs | 283 | doas touch /etc/login.defs |
| 250 | doas mkdir /etc/default | 284 | doas mkdir /etc/default |
| 251 | doas touch /etc/default/useradd | 285 | doas touch /etc/default/useradd |
| 252 | doas lchsh git | 286 | doas lchsh git |
| 253 | ``` | 287 | #+end_src |
| 254 | 288 | ||
| 255 | ## Thoughts on Alpine | 289 | ** Thoughts on Alpine |
| 256 | 290 | :PROPERTIES: | |
| 257 | So far, I love Alpine Linux. I have no complaints about anything at this point, | 291 | :CUSTOM_ID: thoughts-on-alpine |
| 258 | but I'm not completely finished with the migration yet. Once I'm able to upgrade | 292 | :END: |
| 259 | my hardware to a rack-mounted server, I will migrate Plex and Syncthing over to | 293 | So far, I love Alpine Linux. I have no complaints about anything at this |
| 260 | Alpine as well - possibly putting Plex into a container or VM. | 294 | point, but I'm not completely finished with the migration yet. Once I'm |
| 261 | 295 | able to upgrade my hardware to a rack-mounted server, I will migrate | |
| 262 | The performance is stellar, the `apk` package manager is seamless, and system | 296 | Plex and Syncthing over to Alpine as well - possibly putting Plex into a |
| 263 | administration tasks are effortless. My only regret is that I didn't | 297 | container or VM. |
| 264 | install Alpine sooner. | 298 | |
| 299 | The performance is stellar, the =apk= package manager is seamless, and | ||
| 300 | system administration tasks are effortless. My only regret is that I | ||
| 301 | didn't install Alpine sooner. | ||
blog/2022-10-30-linux-display-manager.org +65 −58
| @@ -1,78 +1,85 @@ | |||
| 1 | +++ | 1 | #+title: How to Disable or Change the Display Manager on Void Linux |
| 2 | date = 2022-10-30 | 2 | #+date: 2022-10-30 |
| 3 | title = "How to Disable or Change the Display Manager on Void Linux" | 3 | |
| 4 | description = "Learn how to change the default dm on Void Linux or disable it completely." | 4 | ** Display Manager Services |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: display-manager-services | |
| 7 | ## Display Manager Services | 7 | :END: |
| 8 | 8 | In order to change the | |
| 9 | In order to change the [display | 9 | [[https://en.wikipedia.org/wiki/Display_manager][display manager]] on |
| 10 | manager](https://en.wikipedia.org/wiki/Display_manager) on Void Linux - or any | 10 | Void Linux - or any other Linux distro - you need to identify the |
| 11 | other Linux distro - you need to identify the currently enabled display manager. | 11 | currently enabled display manager. |
| 12 | 12 | ||
| 13 | ### Disabling the Current Display Manager | 13 | *** Disabling the Current Display Manager |
| 14 | 14 | :PROPERTIES: | |
| 15 | Void Linux only has one ISO available for download with a pre-built display | 15 | :CUSTOM_ID: disabling-the-current-display-manager |
| 16 | manager at the time of this post: the XFCE ISO. If you've installed this | 16 | :END: |
| 17 | version, the pre-assigned display manager is `lxdm`. If you installed another | 17 | Void Linux only has one ISO available for download with a pre-built |
| 18 | display manager, replace `lxdm` in the following command with the display | 18 | display manager at the time of this post: the XFCE ISO. If you've |
| 19 | manager you have installed. | 19 | installed this version, the pre-assigned display manager is =lxdm=. If |
| 20 | 20 | you installed another display manager, replace =lxdm= in the following | |
| 21 | To disable `lxdm`, simply remove the service symlink: | 21 | command with the display manager you have installed. |
| 22 | 22 | ||
| 23 | ```sh | 23 | To disable =lxdm=, simply remove the service symlink: |
| 24 | |||
| 25 | #+begin_src sh | ||
| 24 | sudo rm /var/service/lxdm | 26 | sudo rm /var/service/lxdm |
| 25 | ``` | 27 | #+end_src |
| 26 | |||
| 27 | ### Enabling a New Display Manager | ||
| 28 | |||
| 29 | If you want to enable a new display manager, you can do so after `lxdm` is | ||
| 30 | disabled. Make sure to replace `<new_display_manager>` with your new DM, such as | ||
| 31 | `gdm`, `xdm`, etc. | ||
| 32 | 28 | ||
| 29 | *** Enabling a New Display Manager | ||
| 30 | :PROPERTIES: | ||
| 31 | :CUSTOM_ID: enabling-a-new-display-manager | ||
| 32 | :END: | ||
| 33 | If you want to enable a new display manager, you can do so after =lxdm= | ||
| 34 | is disabled. Make sure to replace =<new_display_manager>= with your new | ||
| 35 | DM, such as =gdm=, =xdm=, etc. | ||
| 33 | 36 | ||
| 34 | ```sh | 37 | #+begin_src sh |
| 35 | sudo ln -s /etc/sv/<new_display_manager> /var/service | 38 | sudo ln -s /etc/sv/<new_display_manager> /var/service |
| 36 | ``` | 39 | #+end_src |
| 37 | 40 | ||
| 38 | ## Set Up `.xinitrc` | 41 | ** Set Up =.xinitrc= |
| 42 | :PROPERTIES: | ||
| 43 | :CUSTOM_ID: set-up-.xinitrc | ||
| 44 | :END: | ||
| 45 | Depending on your setup, you may need to create a few X files, such as | ||
| 46 | =~/.xinitrc=. For my personal set-up, I created this file to launch the | ||
| 47 | i3wm as my desktop. | ||
| 39 | 48 | ||
| 40 | Depending on your setup, you may need to create a few X files, such as | 49 | #+begin_src sh |
| 41 | `~/.xinitrc`. For my personal set-up, I created this file to launch the i3wm as | ||
| 42 | my desktop. | ||
| 43 | |||
| 44 | ```sh | ||
| 45 | nano ~/.xinitrc | 50 | nano ~/.xinitrc |
| 46 | ``` | 51 | #+end_src |
| 47 | 52 | ||
| 48 | ```sh | 53 | #+begin_src sh |
| 49 | #!/bin/sh | 54 | #!/bin/sh |
| 50 | 55 | ||
| 51 | exec i3 | 56 | exec i3 |
| 52 | ``` | 57 | #+end_src |
| 53 | |||
| 54 | If you run a desktop other than i3, simply replace `i3` with the shell command | ||
| 55 | that launches that desktop. | ||
| 56 | 58 | ||
| 57 | ## Set Up Your Shell Profile | 59 | If you run a desktop other than i3, simply replace =i3= with the shell |
| 60 | command that launches that desktop. | ||
| 58 | 61 | ||
| 59 | Finally, in order to automatically launch an X session upon login, you will need | 62 | ** Set Up Your Shell Profile |
| 60 | to edit the `.bash_profile` (bash) or `.zprofile` (zsh) files for your | 63 | :PROPERTIES: |
| 61 | shell: | 64 | :CUSTOM_ID: set-up-your-shell-profile |
| 65 | :END: | ||
| 66 | Finally, in order to automatically launch an X session upon login, you | ||
| 67 | will need to edit the =.bash_profile= (bash) or =.zprofile= (zsh) files | ||
| 68 | for your shell: | ||
| 62 | 69 | ||
| 63 | ```sh | 70 | #+begin_src sh |
| 64 | nano ~/.zprofile | 71 | nano ~/.zprofile |
| 65 | ``` | 72 | #+end_src |
| 66 | 73 | ||
| 67 | Add the following snippet to the end of the shell profile file. This will | 74 | Add the following snippet to the end of the shell profile file. This |
| 68 | execute the `startx` command upon login. | 75 | will execute the =startx= command upon login. |
| 69 | 76 | ||
| 70 | ```sh | 77 | #+begin_src sh |
| 71 | if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then | 78 | if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then |
| 72 | exec startx | 79 | exec startx |
| 73 | fi | 80 | fi |
| 74 | ``` | 81 | #+end_src |
| 75 | 82 | ||
| 76 | Alternatively, you can ignore this step and simply choose to manually execute | 83 | Alternatively, you can ignore this step and simply choose to manually |
| 77 | `startx` upon login. This can be useful if you have issues with your desktop or | 84 | execute =startx= upon login. This can be useful if you have issues with |
| 78 | like to manually launch different desktops by choice. | 85 | your desktop or like to manually launch different desktops by choice. |
blog/2022-11-07-matrix-synapse.org +124 −107
| @@ -1,103 +1,114 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting Matrix Synapse on Alpine Linux |
| 2 | date = 2022-11-07 | 2 | #+date: 2022-11-07 |
| 3 | title = "Self-Hosting Matrix Synapse on Alpine Linux" | 3 | |
| 4 | description = "A short guide on how I was able to get Synapse working on Alpine Linux." | 4 | ** Synpase |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: synpase | |
| 7 | ## Synpase | 7 | :END: |
| 8 | 8 | If you're reading this, you likely know that | |
| 9 | If you're reading this, you likely know that | 9 | [[https://github.com/matrix-org/synapse/][Synapse]] is a popular |
| 10 | [Synapse](https://github.com/matrix-org/synapse/) is a popular | 10 | [[https://matrix.org/][Matrix]] home server software that allows users |
| 11 | [Matrix](https://matrix.org/) home server software that allows users to run | 11 | to run their own Matrix home server. |
| 12 | their own Matrix home server. | 12 | |
| 13 | 13 | This post is a short guide describing how I was able to get Synapse | |
| 14 | This post is a short guide describing how I was able to get Synapse working in a | 14 | working in a minimally-usable state on Alpine Linux. |
| 15 | minimally-usable state on Alpine Linux. | 15 | |
| 16 | 16 | ** Installation Process | |
| 17 | ## Installation Process | 17 | :PROPERTIES: |
| 18 | 18 | :CUSTOM_ID: installation-process | |
| 19 | ### Dependencies | 19 | :END: |
| 20 | 20 | *** Dependencies | |
| 21 | First, since there is no Alpine-specific package for Synapse, we need to ensure | 21 | :PROPERTIES: |
| 22 | that Alpine has the required dependencies for the Python-based installation | 22 | :CUSTOM_ID: dependencies |
| 23 | method. | 23 | :END: |
| 24 | 24 | First, since there is no Alpine-specific package for Synapse, we need to | |
| 25 | ```sh | 25 | ensure that Alpine has the required dependencies for the Python-based |
| 26 | installation method. | ||
| 27 | |||
| 28 | #+begin_src sh | ||
| 26 | doas apk -U update | 29 | doas apk -U update |
| 27 | doas apk add python3 py3-virtualenv | 30 | doas apk add python3 py3-virtualenv |
| 28 | ``` | 31 | #+end_src |
| 29 | 32 | ||
| 30 | Next, we need to set up a Python virtual environment for Synapse: | 33 | Next, we need to set up a Python virtual environment for Synapse: |
| 31 | 34 | ||
| 32 | ```sh | 35 | #+begin_src sh |
| 33 | mkdir -p ~/synapse && cd ~/synapse | 36 | mkdir -p ~/synapse && cd ~/synapse |
| 34 | virtualenv -p python3 ~/synapse/env | 37 | virtualenv -p python3 ~/synapse/env |
| 35 | source ~/synapse/env/bin/activate | 38 | source ~/synapse/env/bin/activate |
| 36 | pip install --upgrade pip | 39 | pip install --upgrade pip |
| 37 | pip install --upgrade setuptools | 40 | pip install --upgrade setuptools |
| 38 | pip install matrix-synapse | 41 | pip install matrix-synapse |
| 39 | ``` | 42 | #+end_src |
| 40 | 43 | ||
| 41 | ### Running Synapse | 44 | *** Running Synapse |
| 42 | 45 | :PROPERTIES: | |
| 43 | Once installed, running Synapse is easy. Simply execute the following command, | 46 | :CUSTOM_ID: running-synapse |
| 44 | replacing `example.com` with the domain name that will be used with this | 47 | :END: |
| 45 | home server. This will generate the configuration files needed to run the | 48 | Once installed, running Synapse is easy. Simply execute the following |
| 46 | server. | 49 | command, replacing =example.com= with the domain name that will be used |
| 47 | 50 | with this home server. This will generate the configuration files needed | |
| 48 | ```sh | 51 | to run the server. |
| 52 | |||
| 53 | #+begin_src sh | ||
| 49 | python -m synapse.app.homeserver \ | 54 | python -m synapse.app.homeserver \ |
| 50 | --server-name example.com \ | 55 | --server-name example.com \ |
| 51 | --config-path homeserver.yaml \ | 56 | --config-path homeserver.yaml \ |
| 52 | --generate-config \ | 57 | --generate-config \ |
| 53 | --report-stats=no | 58 | --report-stats=no |
| 54 | ``` | 59 | #+end_src |
| 55 | 60 | ||
| 56 | Once the configuration is generated, we can start up the Synapse server: | 61 | Once the configuration is generated, we can start up the Synapse server: |
| 57 | 62 | ||
| 58 | ```sh | 63 | #+begin_src sh |
| 59 | synctl start | 64 | synctl start |
| 60 | ``` | 65 | #+end_src |
| 61 | 66 | ||
| 62 | ### Configuring Synapse | 67 | *** Configuring Synapse |
| 68 | :PROPERTIES: | ||
| 69 | :CUSTOM_ID: configuring-synapse | ||
| 70 | :END: | ||
| 71 | To make any change to Synapse, we need to edit the =YAML= configuration | ||
| 72 | file: | ||
| 63 | 73 | ||
| 64 | To make any change to Synapse, we need to edit the `YAML` configuration file: | 74 | #+begin_src sh |
| 65 | |||
| 66 | ```sh | ||
| 67 | nano ~/synapse/homeserver.yaml | 75 | nano ~/synapse/homeserver.yaml |
| 68 | ``` | 76 | #+end_src |
| 69 | 77 | ||
| 70 | For now, we just need to ensure the `server_name` is accurate. However, there | 78 | For now, we just need to ensure the =server_name= is accurate. However, |
| 71 | are a lot of other configuration options found in the [Configuring | 79 | there are a lot of other configuration options found in the |
| 72 | Synapse](https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html) | 80 | [[https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html][Configuring |
| 73 | documentation that can be enabled/disabled at any point. | 81 | Synapse]] documentation that can be enabled/disabled at any point. |
| 74 | 82 | ||
| 75 | ```yaml | 83 | #+begin_src yaml |
| 76 | server_name: "example.com" | 84 | server_name: "example.com" |
| 77 | ``` | 85 | #+end_src |
| 78 | 86 | ||
| 79 | Make sure to restart Synapse when you make changes to the configuration: | 87 | Make sure to restart Synapse when you make changes to the configuration: |
| 80 | 88 | ||
| 81 | ```sh | 89 | #+begin_src sh |
| 82 | synctl restart | 90 | synctl restart |
| 83 | ``` | 91 | #+end_src |
| 84 | |||
| 85 | ### Nginx Reverse-Proxy | ||
| 86 | 92 | ||
| 87 | To ensure that Synapse is reachable from the public, we need to connect our | 93 | *** Nginx Reverse-Proxy |
| 88 | domain to the Synapse server. | 94 | :PROPERTIES: |
| 89 | In my case, I use a Nginx reverse-proxy for this purpose. | 95 | :CUSTOM_ID: nginx-reverse-proxy |
| 96 | :END: | ||
| 97 | To ensure that Synapse is reachable from the public, we need to connect | ||
| 98 | our domain to the Synapse server. In my case, I use a Nginx | ||
| 99 | reverse-proxy for this purpose. | ||
| 90 | 100 | ||
| 91 | To use Nginx, we need to create a reverse-proxy configuration file: | 101 | To use Nginx, we need to create a reverse-proxy configuration file: |
| 92 | 102 | ||
| 93 | ```sh | 103 | #+begin_src sh |
| 94 | doas nano /etc/nginx/http.d/example.com.conf | 104 | doas nano /etc/nginx/http.d/example.com.conf |
| 95 | ``` | 105 | #+end_src |
| 96 | 106 | ||
| 97 | If you already have TLS certificates for this domain (`example.com`), you can | 107 | If you already have TLS certificates for this domain (=example.com=), |
| 98 | simply use the SSL configuration and point toward your TLS certificates. | 108 | you can simply use the SSL configuration and point toward your TLS |
| 109 | certificates. | ||
| 99 | 110 | ||
| 100 | ```conf | 111 | #+begin_src conf |
| 101 | server { | 112 | server { |
| 102 | listen 443 ssl http2; | 113 | listen 443 ssl http2; |
| 103 | listen [::]:443 ssl http2; | 114 | listen [::]:443 ssl http2; |
| @@ -128,22 +139,23 @@ server { | |||
| 128 | } | 139 | } |
| 129 | 140 | ||
| 130 | server { | 141 | server { |
| 131 | if ($host = example.com) { | 142 | if ($host = example.com) { |
| 132 | return 301 https://$host$request_uri; | 143 | return 301 https://$host$request_uri; |
| 133 | } | 144 | } |
| 134 | 145 | ||
| 135 | server_name example.com; | 146 | server_name example.com; |
| 136 | listen 80; | 147 | listen 80; |
| 137 | return 404; | 148 | return 404; |
| 138 | } | 149 | } |
| 139 | ``` | 150 | #+end_src |
| 140 | 151 | ||
| 141 | If you need to generate TLS certificates (I recommend | 152 | If you need to generate TLS certificates (I recommend |
| 142 | [Certbot](https://certbot.eff.org/)), you'll need a more minimal Nginx conf file | 153 | [[https://certbot.eff.org/][Certbot]]), you'll need a more minimal Nginx |
| 143 | before you can use the TLS-enabled example above. Instead, use this | 154 | conf file before you can use the TLS-enabled example above. Instead, use |
| 144 | configuration file during the Certbot certificate generation process: | 155 | this configuration file during the Certbot certificate generation |
| 156 | process: | ||
| 145 | 157 | ||
| 146 | ```conf | 158 | #+begin_src conf |
| 147 | server { | 159 | server { |
| 148 | server_name example.com; | 160 | server_name example.com; |
| 149 | location / { | 161 | location / { |
| @@ -151,62 +163,67 @@ server { | |||
| 151 | } | 163 | } |
| 152 | listen 80; | 164 | listen 80; |
| 153 | } | 165 | } |
| 154 | ``` | 166 | #+end_src |
| 155 | 167 | ||
| 156 | Once you're done editing the Nginx conf file, restart Nginx: | 168 | Once you're done editing the Nginx conf file, restart Nginx: |
| 157 | 169 | ||
| 158 | ```sh | 170 | #+begin_src sh |
| 159 | doas rc-service nginx restart | 171 | doas rc-service nginx restart |
| 160 | ``` | 172 | #+end_src |
| 161 | 173 | ||
| 162 | If you still need to generate TLS certificates, run `certbot` now and obtain the | 174 | If you still need to generate TLS certificates, run =certbot= now and |
| 163 | certificates. Certbot will ask if you want to use a webroot or spin up a | 175 | obtain the certificates. Certbot will ask if you want to use a webroot |
| 164 | temporary web server. I **highly** recommend using the temporary web server due | 176 | or spin up a temporary web server. I *highly* recommend using the |
| 165 | to the many issues with using a webroot. | 177 | temporary web server due to the many issues with using a webroot. |
| 166 | 178 | ||
| 167 | You will need to stop Nginx in order to user the temporary web server option | 179 | You will need to stop Nginx in order to user the temporary web server |
| 168 | with Certbot: | 180 | option with Certbot: |
| 169 | 181 | ||
| 170 | ```sh | 182 | #+begin_src sh |
| 171 | # Stop Nginx so certbot can spin up a temp webserver for cert generation | 183 | # Stop Nginx so certbot can spin up a temp webserver for cert generation |
| 172 | doas rc-service nginx stop | 184 | doas rc-service nginx stop |
| 173 | doas certbot certonly -v | 185 | doas certbot certonly -v |
| 174 | doas rc-service nginx start | 186 | doas rc-service nginx start |
| 175 | ``` | 187 | #+end_src |
| 176 | 188 | ||
| 177 | ### Open Firewall & Router Ports | 189 | *** Open Firewall & Router Ports |
| 178 | 190 | :PROPERTIES: | |
| 179 | If you use a firewall on the server, open the `8448` port for discovery and | 191 | :CUSTOM_ID: open-firewall-router-ports |
| 180 | federation, as well as the normal web server ports if you're using a reverse | 192 | :END: |
| 181 | proxy. If you want additional services, such as voice calls, you will need | 193 | If you use a firewall on the server, open the =8448= port for discovery |
| 182 | to read the Synapse documentation to see which ports need to be opened for those | 194 | and federation, as well as the normal web server ports if you're using a |
| 183 | features. | 195 | reverse proxy. If you want additional services, such as voice calls, you |
| 196 | will need to read the Synapse documentation to see which ports need to | ||
| 197 | be opened for those features. | ||
| 184 | 198 | ||
| 185 | Here's an example of the Universal Firewall (UFW) software: | 199 | Here's an example of the Universal Firewall (UFW) software: |
| 186 | 200 | ||
| 187 | ```sh | 201 | #+begin_src sh |
| 188 | # Matrix port | 202 | # Matrix port |
| 189 | doas ufw allow 8448 | 203 | doas ufw allow 8448 |
| 190 | # Standard web server ports | 204 | # Standard web server ports |
| 191 | doas ufw allow "Nginx Full" | 205 | doas ufw allow "Nginx Full" |
| 192 | ``` | 206 | #+end_src |
| 193 | |||
| 194 | Remember to forward any Synapse ports, such as `8448`, `80`, and `443`, in your | ||
| 195 | Router from the internet to your server's IP address. | ||
| 196 | 207 | ||
| 197 | ### Adding Matrix Users | 208 | Remember to forward any Synapse ports, such as =8448=, =80=, and =443=, |
| 209 | in your Router from the internet to your server's IP address. | ||
| 198 | 210 | ||
| 199 | Finally, if you didn't enable public registration in the `homeserver.yaml` file, | 211 | *** Adding Matrix Users |
| 200 | you can manually create users via the command-line: | 212 | :PROPERTIES: |
| 213 | :CUSTOM_ID: adding-matrix-users | ||
| 214 | :END: | ||
| 215 | Finally, if you didn't enable public registration in the | ||
| 216 | =homeserver.yaml= file, you can manually create users via the | ||
| 217 | command-line: | ||
| 201 | 218 | ||
| 202 | ```sh | 219 | #+begin_src sh |
| 203 | cd ~/synapse | 220 | cd ~/synapse |
| 204 | register_new_matrix_user -c homeserver.yaml | 221 | register_new_matrix_user -c homeserver.yaml |
| 205 | ``` | 222 | #+end_src |
| 206 | 223 | ||
| 207 | Remember that the format for federated Matrix usernames is | 224 | Remember that the format for federated Matrix usernames is |
| 208 | `@username:example.com` when logging in to client applications. | 225 | =@username:example.com= when logging in to client applications. |
| 209 | 226 | ||
| 210 | Once Synapse is running, and you have a username, you are ready to log in to a | 227 | Once Synapse is running, and you have a username, you are ready to log |
| 211 | Matrix client and start sending messages, joining rooms, and utilizing your very | 228 | in to a Matrix client and start sending messages, joining rooms, and |
| 212 | own Matrix server. | 229 | utilizing your very own Matrix server. |
blog/2022-11-11-nginx-tmp-errors.org +41 −38
| @@ -1,58 +1,61 @@ | |||
| 1 | +++ | 1 | #+title: Fixing Permission Errors in /var/lib/nginx" description |
| 2 | date = 2022-11-11 | 2 | #+date: 2022-11-11 |
| 3 | title = "Fixing Permission Errors in /var/lib/nginx" | ||
| 4 | description = "A quick fix for Nginx permission errors on the tmp directory." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | *This is a brief post so that I personally remember the solution as it has | 4 | /This is a brief post so that I personally remember the solution as it |
| 8 | occurred multiple times for me.* | 5 | has occurred multiple times for me./ |
| 9 | 6 | ||
| 10 | ## The Problem | 7 | ** The Problem |
| 11 | 8 | :PROPERTIES: | |
| 12 | After migrating to a new server OS, I started receiving quite a few permission | 9 | :CUSTOM_ID: the-problem |
| 13 | errors like the one below. These popped up for various different websites I'm | 10 | :END: |
| 14 | serving via Nginx on this server, but did not prevent the website from loading. | 11 | After migrating to a new server OS, I started receiving quite a few |
| 12 | permission errors like the one below. These popped up for various | ||
| 13 | different websites I'm serving via Nginx on this server, but did not | ||
| 14 | prevent the website from loading. | ||
| 15 | 15 | ||
| 16 | I found the errors in the standard log file: | 16 | I found the errors in the standard log file: |
| 17 | 17 | ||
| 18 | ```sh | 18 | #+begin_src sh |
| 19 | cat /var/log/nginx/error.log | 19 | cat /var/log/nginx/error.log |
| 20 | ``` | 20 | #+end_src |
| 21 | 21 | ||
| 22 | ```sh | 22 | #+begin_src sh |
| 23 | 2022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/" | 23 | 2022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/" |
| 24 | ``` | 24 | #+end_src |
| 25 | 25 | ||
| 26 | You can see that the error is `13: Permission denied` and it occurs in the | 26 | You can see that the error is =13: Permission denied= and it occurs in |
| 27 | `/var/lib/nginx/tmp/` directory. In my case, I had thousands of errors where | 27 | the =/var/lib/nginx/tmp/= directory. In my case, I had thousands of |
| 28 | Nginx was denied permission to read/write files in this directory. | 28 | errors where Nginx was denied permission to read/write files in this |
| 29 | directory. | ||
| 29 | 30 | ||
| 30 | So how do I fix it? | 31 | So how do I fix it? |
| 31 | 32 | ||
| 32 | ## The Solution | 33 | ** The Solution |
| 33 | 34 | :PROPERTIES: | |
| 34 | In order to resolve the issue, I had to ensure the `/var/lib/nginx` | 35 | :CUSTOM_ID: the-solution |
| 35 | directory is owned by Nginx. Mine was owned by the `www` user and Nginx was not | 36 | :END: |
| 36 | able to read or write files within that directory. This prevented Nginx from | 37 | In order to resolve the issue, I had to ensure the =/var/lib/nginx= |
| 37 | caching temporary files. | 38 | directory is owned by Nginx. Mine was owned by the =www= user and Nginx |
| 39 | was not able to read or write files within that directory. This | ||
| 40 | prevented Nginx from caching temporary files. | ||
| 38 | 41 | ||
| 39 | ```sh | 42 | #+begin_src sh |
| 40 | # Alpine Linux | 43 | # Alpine Linux |
| 41 | doas chown -R nginx:nginx /var/lib/nginx | 44 | doas chown -R nginx:nginx /var/lib/nginx |
| 42 | 45 | ||
| 43 | # Other Distros | 46 | # Other Distros |
| 44 | sudo chown -R nginx:nginx /var/lib/nginx | 47 | sudo chown -R nginx:nginx /var/lib/nginx |
| 45 | ``` | 48 | #+end_src |
| 46 | 49 | ||
| 47 | You *may* also be able to change the `proxy_temp_path` in your Nginx config, but | 50 | You /may/ also be able to change the =proxy_temp_path= in your Nginx |
| 48 | I did not try this. Here's a suggestion I found online that may work if the | 51 | config, but I did not try this. Here's a suggestion I found online that |
| 49 | above solution does not: | 52 | may work if the above solution does not: |
| 50 | 53 | ||
| 51 | ```sh | 54 | #+begin_src sh |
| 52 | nano /etc/nginx/http.d/example.com.conf | 55 | nano /etc/nginx/http.d/example.com.conf |
| 53 | ``` | 56 | #+end_src |
| 54 | 57 | ||
| 55 | ```conf | 58 | #+begin_src conf |
| 56 | server { | 59 | server { |
| 57 | ... | 60 | ... |
| 58 | 61 | ||
| @@ -62,15 +65,15 @@ server { | |||
| 62 | 65 | ||
| 63 | ... | 66 | ... |
| 64 | } | 67 | } |
| 65 | ``` | 68 | #+end_src |
| 66 | 69 | ||
| 67 | Finally, restart Nginx and your server should be able to cache temporary files | 70 | Finally, restart Nginx and your server should be able to cache temporary |
| 68 | again. | 71 | files again. |
| 69 | 72 | ||
| 70 | ```sh | 73 | #+begin_src sh |
| 71 | # Alpine Linux (OpenRC) | 74 | # Alpine Linux (OpenRC) |
| 72 | doas rc-service nginx restart | 75 | doas rc-service nginx restart |
| 73 | 76 | ||
| 74 | # Other Distros (systemd) | 77 | # Other Distros (systemd) |
| 75 | sudo systemctl restart nginx | 78 | sudo systemctl restart nginx |
| 76 | ``` | 79 | #+end_src |
blog/2022-11-27-server-build.org +127 −114
| @@ -1,113 +1,124 @@ | |||
| 1 | +++ | 1 | #+title: Building a Custom Rack-Mount Server |
| 2 | date = 2022-11-27 | 2 | #+date: 2022-11-27 |
| 3 | title = "Building a Custom Rack-Mount Server" | 3 | |
| 4 | description = "I finally built my own custom rack-mounted server after years of indecision." | 4 | ** The Dilemma |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: the-dilemma | |
| 7 | ## The Dilemma | 7 | :END: |
| 8 | 8 | For years, I have been using desktops and a Raspberry Pi as the backbone | |
| 9 | For years, I have been using desktops and a Raspberry Pi as the backbone of my | 9 | of my homelab. I have always wanted to move toward a single dedicated |
| 10 | homelab. I have always wanted to move toward a single dedicated server that | 10 | server that could handle all of my tasks, but was often put off by the |
| 11 | could handle all of my tasks, but was often put off by the complexity of the | 11 | complexity of the choices (and financial roadblocks at some times). |
| 12 | choices (and financial roadblocks at some times). | 12 | |
| 13 | 13 | However, after purchasing a small server rack this past year, I have | |
| 14 | However, after purchasing a small server rack this past year, I have been | 14 | been researching to see what kind of rack-mounted server I could buy. I |
| 15 | researching to see what kind of rack-mounted server I could buy. I initially | 15 | initially bought a Dell R720XD loaded up with everything I could ever |
| 16 | bought a Dell R720XD loaded up with everything I could ever want in a server - | 16 | want in a server - but I did not care for it. It was far too loud, and |
| 17 | but I did not care for it. It was far too loud, and the age of the CPU/RAM was | 17 | the age of the CPU/RAM was something I wanted to improve upon. |
| 18 | something I wanted to improve upon. | 18 | |
| 19 | 19 | After returning the R720XD, I decided that I wanted to build my own | |
| 20 | After returning the R720XD, I decided that I wanted to build my own server with | 20 | server with modern, consumer-grade PC components. This time, I am very |
| 21 | modern, consumer-grade PC components. This time, I am very happy with the | 21 | happy with the results of my server. |
| 22 | results of my server. | 22 | |
| 23 | 23 | ** Components | |
| 24 | ## Components | 24 | :PROPERTIES: |
| 25 | 25 | :CUSTOM_ID: components | |
| 26 | :END: | ||
| 26 | I'll start by listing all the components I used for this server build: | 27 | I'll start by listing all the components I used for this server build: |
| 27 | 28 | ||
| 28 | - **Case**: [Rosewill RSV-R4100U 4U Server Chassis Rackmount | 29 | - *Case*: |
| 29 | Case](https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825) | 30 | [[https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825][Rosewill |
| 30 | - **Motherboard**: [NZXT B550](https://nzxt.com/product/n7-b550) | 31 | RSV-R4100U 4U Server Chassis Rackmount Case]] |
| 31 | - **CPU**: AMD Ryzen 7 5700G with Radeon Graphics | 32 | - *Motherboard*: [[https://nzxt.com/product/n7-b550][NZXT B550]] |
| 32 | - **GPU**: N/A - I specifically chose one of the few AMD CPUs that support | 33 | - *CPU*: AMD Ryzen 7 5700G with Radeon Graphics |
| 33 | onboard graphics. | 34 | - *GPU*: N/A - I specifically chose one of the few AMD CPUs that support |
| 34 | - **RAM**: 64GB RAM (2x32GB) _Max of 128GB RAM on this motherboard_ | 35 | onboard graphics. |
| 35 | - **Boot Drive**: Western Digital 500GB M.2 NVME SSD | 36 | - *RAM*: 64GB RAM (2x32GB) /Max of 128GB RAM on this motherboard/ |
| 36 | - **HDD Bay**: | 37 | - *Boot Drive*: Western Digital 500GB M.2 NVME SSD |
| 37 | - 10TB WD White _(shucked, moved from previous server)_ | 38 | - *HDD Bay*: |
| 38 | - 8TB WD White _(shucked, moved from previous server)_ | 39 | - 10TB WD White /(shucked, moved from previous server)/ |
| 39 | - 2 x 8TB WD Red Plus _(Black Friday lined up perfectly with this build, so I | 40 | - 8TB WD White /(shucked, moved from previous server)/ |
| 40 | grabbed two of these)_ | 41 | - 2 x 8TB WD Red Plus /(Black Friday lined up perfectly with this |
| 41 | - **PSU**: Corsair RM850 PSU | 42 | build, so I grabbed two of these)/ |
| 42 | - **Extras**: | 43 | - *PSU*: Corsair RM850 PSU |
| 44 | - *Extras*: | ||
| 43 | - Corsair TM3Q Thermal Paste | 45 | - Corsair TM3Q Thermal Paste |
| 44 | - Noctua 120mm fan _(replacement for front case fan)_ | 46 | - Noctua 120mm fan /(replacement for front case fan)/ |
| 45 | - 2 x Noctua 80mm fans _(replacement for rear case fans)_ | 47 | - 2 x Noctua 80mm fans /(replacement for rear case fans)/ |
| 46 | - CableMatters 6Gbps SATA Cables | 48 | - CableMatters 6Gbps SATA Cables |
| 47 | 49 | ||
| 48 | ## Building the Server | 50 | ** Building the Server |
| 49 | 51 | :PROPERTIES: | |
| 50 | This took quite a while for me to build (in my opinion of time), totaling | 52 | :CUSTOM_ID: building-the-server |
| 51 | around 3 hours from start to finish. The case has some peculiar construction, so | 53 | :END: |
| 52 | you have to completely remove the ODD & HDD cages to install the motherboard and | 54 | This took quite a while for me to build (in my opinion of time), |
| 53 | other components first. | 55 | totaling around 3 hours from start to finish. The case has some peculiar |
| 54 | 56 | construction, so you have to completely remove the ODD & HDD cages to | |
| 55 | Now, I've never built a computer of any kind before, so I was quite | 57 | install the motherboard and other components first. |
| 56 | nervous. Personally, the only challenging part was getting the CPU cooler to | 58 | |
| 57 | screw into the motherboard without sliding the thermal paste around too much | 59 | Now, I've never built a computer of any kind before, so I was quite |
| 58 | underneath. I'm still not entirely sure if I did a great job of it, but | 60 | nervous. Personally, the only challenging part was getting the CPU |
| 59 | nothing's broken yet. | 61 | cooler to screw into the motherboard without sliding the thermal paste |
| 60 | 62 | around too much underneath. I'm still not entirely sure if I did a great | |
| 61 | The main components were all fine and simple. | 63 | job of it, but nothing's broken yet. |
| 62 | However, installing the hard drives is slightly tedious as I need to power | 64 | |
| 63 | off the server and completely unscrew the HDD cage to install or remove any | 65 | The main components were all fine and simple. However, installing the |
| 64 | drives. | 66 | hard drives is slightly tedious as I need to power off the server and |
| 65 | Additionally, the drives are screwed directly into the metal cage with small | 67 | completely unscrew the HDD cage to install or remove any drives. |
| 66 | screws, which are quite a bit different from the HDD trays I'm used to in | 68 | Additionally, the drives are screwed directly into the metal cage with |
| 67 | other machines. | 69 | small screws, which are quite a bit different from the HDD trays I'm |
| 68 | 70 | used to in other machines. | |
| 69 | Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay dealing | 71 | |
| 70 | with the tedium of removing the cage to install new drives. | 72 | Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay |
| 71 | 73 | dealing with the tedium of removing the cage to install new drives. | |
| 72 | ## Software | 74 | |
| 73 | 75 | ** Software | |
| 74 | I'm not going to dive into the software as I have done so in other recent posts. | 76 | :PROPERTIES: |
| 75 | However, I wanted to note that I am using Alpine Linux on this server and | 77 | :CUSTOM_ID: software |
| 76 | hosting most services inside Docker. No virtual machines (VMs) and very few | 78 | :END: |
| 77 | bare-metal services. | 79 | I'm not going to dive into the software as I have done so in other |
| 78 | 80 | recent posts. However, I wanted to note that I am using Alpine Linux on | |
| 79 | ## The Results | 81 | this server and hosting most services inside Docker. No virtual machines |
| 80 | 82 | (VMs) and very few bare-metal services. | |
| 81 | How did my build turn out? Well, after migrating my other servers and their | 83 | |
| 82 | services over, I found that my server is blazing fast. The heaviest of my | 84 | ** The Results |
| 83 | applications, Plex, is handled with ease. Even 4k streaming seems to be | 85 | :PROPERTIES: |
| 84 | effortless. | 86 | :CUSTOM_ID: the-results |
| 85 | 87 | :END: | |
| 86 | I am very happy with the results and will likely continue to improve on this | 88 | How did my build turn out? Well, after migrating my other servers and |
| 87 | server as the years go by rather than buying another used server online. | 89 | their services over, I found that my server is blazing fast. The |
| 88 | 90 | heaviest of my applications, Plex, is handled with ease. Even 4k | |
| 89 | ### Mistakes I Made | 91 | streaming seems to be effortless. |
| 90 | 92 | ||
| 91 | This post wouldn't be complete unless I wrote about the mistakes I made while | 93 | I am very happy with the results and will likely continue to improve on |
| 92 | building. The only real mistake I made beyond a "whoops I dropped a screw" | 94 | this server as the years go by rather than buying another used server |
| 93 | related to airflow and fan direction. | 95 | online. |
| 94 | 96 | ||
| 95 | While installing the two new hard drives that showed up on 2022-11-30 and | 97 | *** Mistakes I Made |
| 96 | getting ready to install the case in my rack, I noticed that the hard drive | 98 | :PROPERTIES: |
| 97 | temperatures were quite high. | 99 | :CUSTOM_ID: mistakes-i-made |
| 98 | 100 | :END: | |
| 99 | I used the `smartctl` command for each of my drives (`/dev/sda` through | 101 | This post wouldn't be complete unless I wrote about the mistakes I made |
| 100 | `/dev/sdd`): | 102 | while building. The only real mistake I made beyond a "whoops I dropped |
| 101 | 103 | a screw" related to airflow and fan direction. | |
| 102 | ```sh | 104 | |
| 105 | While installing the two new hard drives that showed up on 2022-11-30 | ||
| 106 | and getting ready to install the case in my rack, I noticed that the | ||
| 107 | hard drive temperatures were quite high. | ||
| 108 | |||
| 109 | I used the =smartctl= command for each of my drives (=/dev/sda= through | ||
| 110 | =/dev/sdd=): | ||
| 111 | |||
| 112 | #+begin_src sh | ||
| 103 | doas smartctl -a /dev/sda | grep Temperature_Celsius | 113 | doas smartctl -a /dev/sda | grep Temperature_Celsius |
| 104 | ``` | 114 | #+end_src |
| 105 | 115 | ||
| 106 | The results were unusual - all four drives were idling at ~44-46 degrees | 116 | The results were unusual - all four drives were idling at ~44-46 degrees |
| 107 | Celsius. The only drive that was cooler was my 10TB drive, which was at 38 | 117 | Celsius. The only drive that was cooler was my 10TB drive, which was at |
| 108 | degrees Celsius. I noted that this 10TB drive was also closest to the case fan. | 118 | 38 degrees Celsius. I noted that this 10TB drive was also closest to the |
| 119 | case fan. | ||
| 109 | 120 | ||
| 110 | ```sh | 121 | #+begin_src sh |
| 111 | ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE | 122 | ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE |
| 112 | # /dev/sda | 123 | # /dev/sda |
| 113 | 194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46) | 124 | 194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46) |
| @@ -117,18 +128,19 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_ | |||
| 117 | 194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61) | 128 | 194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61) |
| 118 | # /dev/sdd | 129 | # /dev/sdd |
| 119 | 194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56) | 130 | 194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56) |
| 120 | ``` | 131 | #+end_src |
| 121 | 132 | ||
| 122 | After looking to see if I could fit more fans into the case, I noticed that the | 133 | After looking to see if I could fit more fans into the case, I noticed |
| 123 | 120mm fan used for intake from the front of the case was actually pushing air | 134 | that the 120mm fan used for intake from the front of the case was |
| 124 | out of the case by mistake. This fan sits right in front of the hard drive bay. | 135 | actually pushing air out of the case by mistake. This fan sits right in |
| 136 | front of the hard drive bay. | ||
| 125 | 137 | ||
| 126 | Once I flipped the fan around to act as an intake fan, the temperatures dropped | 138 | Once I flipped the fan around to act as an intake fan, the temperatures |
| 127 | immediately! They are now idling at ~31-33 degrees Celsius. A single fan | 139 | dropped immediately! They are now idling at ~31-33 degrees Celsius. A |
| 128 | spinning the wrong way caused my drives to idle 10-15 degrees higher than they | 140 | single fan spinning the wrong way caused my drives to idle 10-15 degrees |
| 129 | should have. | 141 | higher than they should have. |
| 130 | 142 | ||
| 131 | ```sh | 143 | #+begin_src sh |
| 132 | ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE | 144 | ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE |
| 133 | # /dev/sda | 145 | # /dev/sda |
| 134 | 194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56) | 146 | 194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56) |
| @@ -138,7 +150,8 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_ | |||
| 138 | 194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48) | 150 | 194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48) |
| 139 | # /dev/sdd | 151 | # /dev/sdd |
| 140 | 194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46) | 152 | 194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46) |
| 141 | ``` | 153 | #+end_src |
| 142 | 154 | ||
| 143 | This was a silly error to make, but I'm glad I found it today before I screwed | 155 | This was a silly error to make, but I'm glad I found it today before I |
| 144 | the case into the rack and made things a lot more tedious to fix. | 156 | screwed the case into the rack and made things a lot more tedious to |
| 157 | fix. | ||
blog/2022-11-29-nginx-referrer-ban-list.org +71 −65
| @@ -1,26 +1,24 @@ | |||
| 1 | +++ | 1 | #+title: Creating a Referrer Ban List on Nginx |
| 2 | date = 2022-11-29 | 2 | #+date: 2022-11-29 |
| 3 | title = "Creating a Referrer Ban List on Nginx" | ||
| 4 | description = "A quick explanation detailing my own way of banning referral domains on Nginx." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Creating the Ban List | 4 | ** Creating the Ban List |
| 8 | 5 | :PROPERTIES: | |
| 9 | In order to ban list referral domains or websites with Nginx, you need to | 6 | :CUSTOM_ID: creating-the-ban-list |
| 10 | create a ban list file. | 7 | :END: |
| 11 | The file below will accept regexes for different domains or websites you | 8 | In order to ban list referral domains or websites with Nginx, you need |
| 12 | wish to block. | 9 | to create a ban list file. The file below will accept regexes for |
| 10 | different domains or websites you wish to block. | ||
| 13 | 11 | ||
| 14 | First, create the file in your nginx directory: | 12 | First, create the file in your nginx directory: |
| 15 | 13 | ||
| 16 | ```sh | 14 | #+begin_src sh |
| 17 | doas nano /etc/nginx/banlist.conf | 15 | doas nano /etc/nginx/banlist.conf |
| 18 | ``` | 16 | #+end_src |
| 19 | 17 | ||
| 20 | Next, paste the following contents in and fill out the regexes with whichever | 18 | Next, paste the following contents in and fill out the regexes with |
| 21 | domains you're blocking. | 19 | whichever domains you're blocking. |
| 22 | 20 | ||
| 23 | ```conf | 21 | #+begin_src conf |
| 24 | # /etc/nginx/banlist.conf | 22 | # /etc/nginx/banlist.conf |
| 25 | 23 | ||
| 26 | map $http_referer $bad_referer { | 24 | map $http_referer $bad_referer { |
| @@ -31,21 +29,23 @@ map $http_referer $bad_referer { | |||
| 31 | # Put regexes for undesired referrers here | 29 | # Put regexes for undesired referrers here |
| 32 | "~news.ycombinator.com" 1; | 30 | "~news.ycombinator.com" 1; |
| 33 | } | 31 | } |
| 34 | ``` | 32 | #+end_src |
| 35 | |||
| 36 | ## Configuring Nginx | ||
| 37 | 33 | ||
| 38 | In order for the ban list to work, Nginx needs to know it exists and how to | 34 | ** Configuring Nginx |
| 39 | handle it. For this, edit the `nginx.conf` file. | 35 | :PROPERTIES: |
| 36 | :CUSTOM_ID: configuring-nginx | ||
| 37 | :END: | ||
| 38 | In order for the ban list to work, Nginx needs to know it exists and how | ||
| 39 | to handle it. For this, edit the =nginx.conf= file. | ||
| 40 | 40 | ||
| 41 | ```sh | 41 | #+begin_src sh |
| 42 | doas nano /etc/nginx/nginx.conf | 42 | doas nano /etc/nginx/nginx.conf |
| 43 | ``` | 43 | #+end_src |
| 44 | 44 | ||
| 45 | Within this file, find the `http` block and add your ban list file location to | 45 | Within this file, find the =http= block and add your ban list file |
| 46 | the end of the block. | 46 | location to the end of the block. |
| 47 | 47 | ||
| 48 | ```conf | 48 | #+begin_src conf |
| 49 | # /etc/nginx/nginx.conf | 49 | # /etc/nginx/nginx.conf |
| 50 | 50 | ||
| 51 | http { | 51 | http { |
| @@ -54,29 +54,31 @@ http { | |||
| 54 | # Include ban list | 54 | # Include ban list |
| 55 | include /etc/nginx/banlist.conf; | 55 | include /etc/nginx/banlist.conf; |
| 56 | } | 56 | } |
| 57 | ``` | 57 | #+end_src |
| 58 | 58 | ||
| 59 | ## Enabling the Ban List | 59 | ** Enabling the Ban List |
| 60 | 60 | :PROPERTIES: | |
| 61 | Finally, we need to take action when a bad referral site is found. To do so, | 61 | :CUSTOM_ID: enabling-the-ban-list |
| 62 | edit the configuration file for your website. For example, I have all website | 62 | :END: |
| 63 | configuration files in the `http.d` directory. You may have them in the | 63 | Finally, we need to take action when a bad referral site is found. To do |
| 64 | `sites-available` directory on some distributions. | 64 | so, edit the configuration file for your website. For example, I have |
| 65 | 65 | all website configuration files in the =http.d= directory. You may have | |
| 66 | ```sh | 66 | them in the =sites-available= directory on some distributions. |
| 67 | |||
| 68 | #+begin_src sh | ||
| 67 | doas nano /etc/nginx/http.d/example.com.conf | 69 | doas nano /etc/nginx/http.d/example.com.conf |
| 68 | ``` | 70 | #+end_src |
| 69 | 71 | ||
| 70 | Within each website's configuration file, edit the `server` blocks that are | 72 | Within each website's configuration file, edit the =server= blocks that |
| 71 | listening to ports 80 and 443 and create a check for the `$bad_referrer` | 73 | are listening to ports 80 and 443 and create a check for the |
| 72 | variable we created in the ban list file. | 74 | =$bad_referrer= variable we created in the ban list file. |
| 73 | 75 | ||
| 74 | If a matching site is found, you can return any [HTTP Status | 76 | If a matching site is found, you can return any |
| 75 | Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) you want. Code | 77 | [[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes][HTTP Status |
| 76 | 403 (Forbidden) is logical in this case since you are preventing a client | 78 | Code]] you want. Code 403 (Forbidden) is logical in this case since you |
| 77 | connection due to a banned domain. | 79 | are preventing a client connection due to a banned domain. |
| 78 | 80 | ||
| 79 | ```conf | 81 | #+begin_src conf |
| 80 | server { | 82 | server { |
| 81 | ... | 83 | ... |
| 82 | 84 | ||
| @@ -87,42 +89,46 @@ server { | |||
| 87 | 89 | ||
| 88 | ... | 90 | ... |
| 89 | } | 91 | } |
| 90 | ``` | 92 | #+end_src |
| 91 | |||
| 92 | ## Restart Nginx | ||
| 93 | 93 | ||
| 94 | ** Restart Nginx | ||
| 95 | :PROPERTIES: | ||
| 96 | :CUSTOM_ID: restart-nginx | ||
| 97 | :END: | ||
| 94 | Lastly, restart Nginx to enable all changes made. | 98 | Lastly, restart Nginx to enable all changes made. |
| 95 | 99 | ||
| 96 | ```sh | 100 | #+begin_src sh |
| 97 | doas rc-service nginx restart | 101 | doas rc-service nginx restart |
| 98 | ``` | 102 | #+end_src |
| 99 | |||
| 100 | ## Testing Results | ||
| 101 | 103 | ||
| 102 | In order to test the results, let's curl the contents of our site. To start, | 104 | ** Testing Results |
| 103 | I'll curl the site normally: | 105 | :PROPERTIES: |
| 106 | :CUSTOM_ID: testing-results | ||
| 107 | :END: | ||
| 108 | In order to test the results, let's curl the contents of our site. To | ||
| 109 | start, I'll curl the site normally: | ||
| 104 | 110 | ||
| 105 | ```sh | 111 | #+begin_src sh |
| 106 | curl https://cleberg.net | 112 | curl https://cleberg.net |
| 107 | ``` | 113 | #+end_src |
| 108 | 114 | ||
| 109 | The HTML contents of the page come back successfully: | 115 | The HTML contents of the page come back successfully: |
| 110 | 116 | ||
| 111 | ```html | 117 | #+begin_src html |
| 112 | <!doctype html>...</html> | 118 | <!doctype html>...</html> |
| 113 | ``` | 119 | #+end_src |
| 114 | 120 | ||
| 115 | Next, let's include a banned referrer: | 121 | Next, let's include a banned referrer: |
| 116 | 122 | ||
| 117 | ```sh | 123 | #+begin_src sh |
| 118 | curl --referer https://news.ycombinator.com https://cleberg.net | 124 | curl --referer https://news.ycombinator.com https://cleberg.net |
| 119 | ``` | 125 | #+end_src |
| 120 | 126 | ||
| 121 | This time, I'm met with a 403 Forbidden response page. That means we are | 127 | This time, I'm met with a 403 Forbidden response page. That means we are |
| 122 | successful and any clients being referred from a banned domain will be met | 128 | successful and any clients being referred from a banned domain will be |
| 123 | with this same response code. | 129 | met with this same response code. |
| 124 | 130 | ||
| 125 | ```html | 131 | #+begin_src html |
| 126 | <html> | 132 | <html> |
| 127 | <head><title>403 Forbidden</title></head> | 133 | <head><title>403 Forbidden</title></head> |
| 128 | <body> | 134 | <body> |
| @@ -130,4 +136,4 @@ with this same response code. | |||
| 130 | <hr><center>nginx</center> | 136 | <hr><center>nginx</center> |
| 131 | </body> | 137 | </body> |
| 132 | </html> | 138 | </html> |
| 133 | ``` | 139 | #+end_src |
blog/2022-12-01-nginx-compression.org +59 −50
| @@ -1,40 +1,44 @@ | |||
| 1 | +++ | 1 | #+title: Enable GZIP Compression on Nginx |
| 2 | date = 2022-12-01 | 2 | #+date: 2022-12-01 |
| 3 | title = "Enable GZIP Compression on Nginx" | ||
| 4 | desciption = "Enable gzip compression on Nginx to server small text-based resources faster." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Text Compression | 4 | ** Text Compression |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: text-compression | ||
| 7 | :ID: 24C2F6E7-129E-42F3-91F0-A6C6C519FA01 | ||
| 8 | :END: | ||
| 9 | Text compression allows a web server to serve text-based resources | ||
| 10 | faster than uncompressed data. This can speed up things like First | ||
| 11 | Contentful Paint, Tie to Interactive, and Speed Index. | ||
| 8 | 12 | ||
| 9 | Text compression allows a web server to serve text-based resources faster than | 13 | ** Enable Nginx Compression with gzip |
| 10 | uncompressed data. This can speed up things like First Contentful Paint, Tie to | 14 | :PROPERTIES: |
| 11 | Interactive, and Speed Index. | 15 | :CUSTOM_ID: enable-nginx-compression-with-gzip |
| 16 | :ID: C018C991-E253-4779-A702-AEB69614499F | ||
| 17 | :END: | ||
| 18 | In order to enable text compression on Nginx, we need to enable it | ||
| 19 | within the configuration file: | ||
| 12 | 20 | ||
| 13 | ## Enable Nginx Compression with gzip | 21 | #+begin_src sh |
| 14 | |||
| 15 | In order to enable text compression on Nginx, we need to enable it within the | ||
| 16 | configuration file: | ||
| 17 | |||
| 18 | ```sh | ||
| 19 | nano /etc/nginx/nginx.conf | 22 | nano /etc/nginx/nginx.conf |
| 20 | ``` | 23 | #+end_src |
| 21 | 24 | ||
| 22 | Within the `http` block, find the section that shows something like the block | 25 | Within the =http= block, find the section that shows something like the |
| 23 | below. This is the default gzip configuration I found in my `nginx.conf` file on | 26 | block below. This is the default gzip configuration I found in my |
| 24 | Alpine Linux 3.17. Yours may look slightly different, just make sure that | 27 | =nginx.conf= file on Alpine Linux 3.17. Yours may look slightly |
| 25 | you're not creating any duplicate gzip options. | 28 | different, just make sure that you're not creating any duplicate gzip |
| 29 | options. | ||
| 26 | 30 | ||
| 27 | ```conf | 31 | #+begin_src conf |
| 28 | # Enable gzipping of responses. | 32 | # Enable gzipping of responses. |
| 29 | #gzip on; | 33 | #gzip on; |
| 30 | 34 | ||
| 31 | # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. | 35 | # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. |
| 32 | gzip_vary on; | 36 | gzip_vary on; |
| 33 | ``` | 37 | #+end_src |
| 34 | 38 | ||
| 35 | Remove the default gzip lines and replace them with the following: | 39 | Remove the default gzip lines and replace them with the following: |
| 36 | 40 | ||
| 37 | ```conf | 41 | #+begin_src conf |
| 38 | # Enable gzipping of responses. | 42 | # Enable gzipping of responses. |
| 39 | gzip on; | 43 | gzip on; |
| 40 | gzip_vary on; | 44 | gzip_vary on; |
| @@ -42,33 +46,38 @@ gzip_min_length 10240; | |||
| 42 | gzip_proxied expired no-cache no-store private auth; | 46 | gzip_proxied expired no-cache no-store private auth; |
| 43 | gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml; | 47 | gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml; |
| 44 | gzip_disable "MSIE [1-6]\."; | 48 | gzip_disable "MSIE [1-6]\."; |
| 45 | ``` | 49 | #+end_src |
| 46 | |||
| 47 | ## Explanations of ngx_http_gzip_module Options | ||
| 48 | |||
| 49 | Each of the lines above enables a different aspect of the gzip response for | ||
| 50 | Nginx. Here are the full explanations: | ||
| 51 | 50 | ||
| 51 | ** Explanations of ngx_http_gzip_module Options | ||
| 52 | :PROPERTIES: | ||
| 53 | :CUSTOM_ID: explanations-of-ngx_http_gzip_module-options | ||
| 54 | :ID: C180752B-E1B9-49A5-B180-84488068A76A | ||
| 55 | :END: | ||
| 56 | Each of the lines above enables a different aspect of the gzip response | ||
| 57 | for Nginx. Here are the full explanations: | ||
| 52 | 58 | ||
| 53 | - `gzip` – Enables or disables gzipping of responses. | 59 | - =gzip= -- Enables or disables gzipping of responses. |
| 54 | - `gzip_vary` – Enables or disables inserting the “Vary: Accept-Encoding” | 60 | - =gzip_vary= -- Enables or disables inserting the "Vary: |
| 55 | response header field if the directives gzip, gzip_static, or gunzip are active. | 61 | Accept-Encoding" response header field if the directives gzip, |
| 56 | - `gzip_min_length` – Sets the minimum length of a response that will be | 62 | gzip_static, or gunzip are active. |
| 57 | gzipped. The length is determined only from the “Content-Length” response header field. | 63 | - =gzip_min_length= -- Sets the minimum length of a response that will |
| 58 | - `gzip_proxied` – Enables or disables gzipping of responses for proxied | 64 | be gzipped. The length is determined only from the "Content-Length" |
| 59 | requests depending on the request and response. The fact that the request is | 65 | response header field. |
| 60 | proxied is determined by the presence of the “Via” request header field. | 66 | - =gzip_proxied= -- Enables or disables gzipping of responses for |
| 61 | - `gzip_types` – Enables gzipping of responses for the specified MIME types in | 67 | proxied requests depending on the request and response. The fact that |
| 62 | addition to “text/html”. The special value “*” matches any MIME type (0.8.29). | 68 | the request is proxied is determined by the presence of the "Via" |
| 63 | Responses with the “text/html” type are always compressed. | 69 | request header field. |
| 64 | - `gzip_disable` – Disables gzipping of responses for requests | 70 | - =gzip_types= -- Enables gzipping of responses for the specified MIME |
| 65 | with “User-Agent” header fields matching any of the specified regular | 71 | types in addition to "text/html". The special value “*” matches any |
| 66 | expressions. | 72 | MIME type (0.8.29). Responses with the "text/html" type are always |
| 67 | - The special mask “msie6” (0.7.12) corresponds to the regular expression “MSIE | 73 | compressed. |
| 68 | [4-6]\.”, but works faster. Starting from version 0.8.11, “MSIE 6.0; ... SV1” is | 74 | - =gzip_disable= -- Disables gzipping of responses for requests with |
| 69 | excluded from this mask. | 75 | "User-Agent" header fields matching any of the specified regular |
| 76 | expressions. | ||
| 77 | - The special mask "msie6" (0.7.12) corresponds to the regular | ||
| 78 | expression "MSIE [4-6].", but works faster. Starting from version | ||
| 79 | 0.8.11, "MSIE 6.0; ... SV1" is excluded from this mask. | ||
| 70 | 80 | ||
| 71 | More information on these directives and their options can be found on the | 81 | More information on these directives and their options can be found on |
| 72 | [Module | 82 | the [[https://nginx.org/en/docs/http/ngx_http_gzip_module.html][Module |
| 73 | ngx_http_gzip_module](https://nginx.org/en/docs/http/ngx_http_gzip_module.html) | 83 | ngx_http_gzip_module]] page in Nginx's documentation. |
| 74 | page in Nginx's documentation. | ||
blog/2022-12-07-nginx-wildcard-redirect.org +60 −56
| @@ -1,25 +1,26 @@ | |||
| 1 | +++ | 1 | #+title: Redirect Nginx Subdomains & Trailing Content with Regex |
| 2 | date = 2022-12-07 | 2 | #+date: 2022-12-07 |
| 3 | title = "Redirect Nginx Subdomains & Trailing Content with Regex" | 3 | |
| 4 | description= "You can easily redirect Nginx traffic from subdomains and TLDs to a new domain while preserving trailing content." | 4 | ** Problem |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: problem | |
| 7 | ## Problem | 7 | :END: |
| 8 | 8 | I recently migrated domains and replaced the old webpage with a simple | |
| 9 | I recently migrated domains and replaced the | 9 | info page with instructions to users on how to edit their bookmarks and |
| 10 | old webpage with a simple info page with instructions to users on how | 10 | URLs to get to the page they were seeking. |
| 11 | to edit their bookmarks and URLs to get to the page they were seeking. | 11 | |
| 12 | 12 | This was not ideal as it left the work up to the user and may have | |
| 13 | This was not ideal as it left the work up to the user and may have caused | 13 | caused friction for users who accessed my RSS feed. |
| 14 | friction for users who accessed my RSS feed. | 14 | |
| 15 | 15 | ** Solution | |
| 16 | ## Solution | 16 | :PROPERTIES: |
| 17 | 17 | :CUSTOM_ID: solution | |
| 18 | Instead, I finally found a solution that allows me to redirect both subdomains | 18 | :END: |
| 19 | AND trailing content. For example, both of these URLs now redirect properly | 19 | Instead, I finally found a solution that allows me to redirect both |
| 20 | using the logic I'll explain below: | 20 | subdomains AND trailing content. For example, both of these URLs now |
| 21 | 21 | redirect properly using the logic I'll explain below: | |
| 22 | ```txt | 22 | |
| 23 | #+begin_src txt | ||
| 23 | # Example 1 - Simple base domain redirect with trailing content | 24 | # Example 1 - Simple base domain redirect with trailing content |
| 24 | https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ | 25 | https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ |
| 25 | 26 | ||
| @@ -27,23 +28,25 @@ https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ | |||
| 27 | https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ | 28 | https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ |
| 28 | -> | 29 | -> |
| 29 | https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ | 30 | https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ |
| 30 | ``` | 31 | #+end_src |
| 31 | 32 | ||
| 32 | Go ahead, try the URLs if you want to test them. | 33 | Go ahead, try the URLs if you want to test them. |
| 33 | 34 | ||
| 34 | ### Nginx Config | 35 | *** Nginx Config |
| 36 | :PROPERTIES: | ||
| 37 | :CUSTOM_ID: nginx-config | ||
| 38 | :END: | ||
| 39 | To make this possible. I needed to configure a proper redirect scheme in | ||
| 40 | my Nginx configuration. | ||
| 35 | 41 | ||
| 36 | To make this possible. I needed to configure a proper redirect scheme in my | 42 | #+begin_src sh |
| 37 | Nginx configuration. | ||
| 38 | |||
| 39 | ```sh | ||
| 40 | doas nano /etc/nginx/http.d/domain1.conf | 43 | doas nano /etc/nginx/http.d/domain1.conf |
| 41 | ``` | 44 | #+end_src |
| 42 | 45 | ||
| 43 | Within this file, I had one block configured to redirect HTTP requests to HTTPS | 46 | Within this file, I had one block configured to redirect HTTP requests |
| 44 | for the base domain and all subdomains. | 47 | to HTTPS for the base domain and all subdomains. |
| 45 | 48 | ||
| 46 | ```conf | 49 | #+begin_src conf |
| 47 | server { | 50 | server { |
| 48 | listen [::]:80; | 51 | listen [::]:80; |
| 49 | listen 80; | 52 | listen 80; |
| @@ -59,13 +62,14 @@ server { | |||
| 59 | 62 | ||
| 60 | return 404; | 63 | return 404; |
| 61 | } | 64 | } |
| 62 | ``` | 65 | #+end_src |
| 63 | 66 | ||
| 64 | For the base domain, I have another `server` block dedicated to redirecting all | 67 | For the base domain, I have another =server= block dedicated to |
| 65 | base domain requests. You can see that the `rewrite` line is instructing Nginx | 68 | redirecting all base domain requests. You can see that the =rewrite= |
| 66 | to gather all trailing content and append it to the new `domain2.com` URL. | 69 | line is instructing Nginx to gather all trailing content and append it |
| 70 | to the new =domain2.com= URL. | ||
| 67 | 71 | ||
| 68 | ```conf | 72 | #+begin_src conf |
| 69 | server { | 73 | server { |
| 70 | listen [::]:443 ssl http2; | 74 | listen [::]:443 ssl http2; |
| 71 | listen 443 ssl http2; | 75 | listen 443 ssl http2; |
| @@ -77,20 +81,21 @@ server { | |||
| 77 | ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; | 81 | ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; |
| 78 | ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; | 82 | ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; |
| 79 | } | 83 | } |
| 80 | ``` | 84 | #+end_src |
| 81 | 85 | ||
| 82 | Finally, the tricky part is figuring out how to tell Nginx to redirect while | 86 | Finally, the tricky part is figuring out how to tell Nginx to redirect |
| 83 | keeping both a subdomain and trailing content intact. I found that the easiest | 87 | while keeping both a subdomain and trailing content intact. I found that |
| 84 | way to do this is to give it a `server` block of its own. | 88 | the easiest way to do this is to give it a =server= block of its own. |
| 85 | 89 | ||
| 86 | Within this block, we need to do some regex on the `server_name` line before we | 90 | Within this block, we need to do some regex on the =server_name= line |
| 87 | can rewrite anything. This creates a variable called `subdomain`. | 91 | before we can rewrite anything. This creates a variable called |
| 92 | =subdomain=. | ||
| 88 | 93 | ||
| 89 | Once the server gets to the `rewrite` line, it pulls the `subdomain` variable | 94 | Once the server gets to the =rewrite= line, it pulls the =subdomain= |
| 90 | from above and uses it on the new `domain2.com` domain before appending the | 95 | variable from above and uses it on the new =domain2.com= domain before |
| 91 | trailing content (`$request_uri`). | 96 | appending the trailing content (=$request_uri=). |
| 92 | 97 | ||
| 93 | ```conf | 98 | #+begin_src conf |
| 94 | server { | 99 | server { |
| 95 | listen [::]:443 ssl http2; | 100 | listen [::]:443 ssl http2; |
| 96 | listen 443 ssl http2; | 101 | listen 443 ssl http2; |
| @@ -102,18 +107,17 @@ server { | |||
| 102 | ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; | 107 | ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; |
| 103 | ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; | 108 | ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; |
| 104 | } | 109 | } |
| 105 | ``` | 110 | #+end_src |
| 106 | 111 | ||
| 107 | That's all there is to it. With this, I simply restarted Nginx and watched the | 112 | That's all there is to it. With this, I simply restarted Nginx and |
| 108 | redirections work in-action. | 113 | watched the redirections work in-action. |
| 109 | 114 | ||
| 110 | ```sh | 115 | #+begin_src sh |
| 111 | doas rc-service nginx restart | 116 | doas rc-service nginx restart |
| 112 | ``` | 117 | #+end_src |
| 113 | |||
| 114 | 118 | ||
| 115 | Looking back on it, I wish I had done this sooner. Who knows how many people | 119 | Looking back on it, I wish I had done this sooner. Who knows how many |
| 116 | went looking for my sites or bookmarks and gave up when they saw the redirect | 120 | people went looking for my sites or bookmarks and gave up when they saw |
| 117 | instructions page. | 121 | the redirect instructions page. |
| 118 | 122 | ||
| 119 | Oh well, it's done now. Live and learn. | 123 | Oh well, it's done now. Live and learn. |
blog/2022-12-17-st.org +71 −60
| @@ -1,89 +1,100 @@ | |||
| 1 | +++ | 1 | #+title: Simple Terminal |
| 2 | date = 2022-12-17 | 2 | #+date: 2022-12-17 |
| 3 | title = "Simple Terminal" | 3 | |
| 4 | description = "An easy guide on how to build st on Fedora Workstation." | 4 | ** st |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: st | |
| 7 | ## st | 7 | :END: |
| 8 | 8 | [[https://st.suckless.org][st]] standards for Simple Terminal, a simple | |
| 9 | [st](https://st.suckless.org) standards for Simple Terminal, a simple terminal | 9 | terminal implementation for X made by the |
| 10 | implementation for X made by the [suckless](https://suckless.org) team. | 10 | [[https://suckless.org][suckless]] team. |
| 11 | 11 | ||
| 12 | This post walks through the dependencies needed and process to build and install | 12 | This post walks through the dependencies needed and process to build and |
| 13 | `st` on Fedora Workstation. | 13 | install =st= on Fedora Workstation. |
| 14 | 14 | ||
| 15 | ### Obtain Files | 15 | *** Obtain Files |
| 16 | 16 | :PROPERTIES: | |
| 17 | To start, obtain the source files for `st` via `git clone`. | 17 | :CUSTOM_ID: obtain-files |
| 18 | 18 | :END: | |
| 19 | ```sh | 19 | To start, obtain the source files for =st= via =git clone=. |
| 20 | |||
| 21 | #+begin_src sh | ||
| 20 | mkdir ~/suckless && cd ~/suckless | 22 | mkdir ~/suckless && cd ~/suckless |
| 21 | git clone https://git.suckless.org/st && cd st | 23 | git clone https://git.suckless.org/st && cd st |
| 22 | ``` | 24 | #+end_src |
| 23 | |||
| 24 | ### Dependencies | ||
| 25 | 25 | ||
| 26 | Once you have the files and are in the `st` directory, ensure the following | 26 | *** Dependencies |
| 27 | packages are installed. | 27 | :PROPERTIES: |
| 28 | :CUSTOM_ID: dependencies | ||
| 29 | :END: | ||
| 30 | Once you have the files and are in the =st= directory, ensure the | ||
| 31 | following packages are installed. | ||
| 28 | 32 | ||
| 29 | ```sh | 33 | #+begin_src sh |
| 30 | sudo dnf update && sudo dnf upgrade | 34 | sudo dnf update && sudo dnf upgrade |
| 31 | sudo dnf install gcc patch libX11-devel libXft-devel | 35 | sudo dnf install gcc patch libX11-devel libXft-devel |
| 32 | ``` | 36 | #+end_src |
| 33 | |||
| 34 | ### Building | ||
| 35 | 37 | ||
| 38 | *** Building | ||
| 39 | :PROPERTIES: | ||
| 40 | :CUSTOM_ID: building | ||
| 41 | :END: | ||
| 36 | Before building, ensure that you read the README file. | 42 | Before building, ensure that you read the README file. |
| 37 | 43 | ||
| 38 | ```sh | 44 | #+begin_src sh |
| 39 | cat README | 45 | cat README |
| 40 | ``` | 46 | #+end_src |
| 41 | 47 | ||
| 42 | Once you've read the instructions, open the `config.mk` file and ensure it | 48 | Once you've read the instructions, open the =config.mk= file and ensure |
| 43 | matches your setup. If you're not sure, leave the default options within the | 49 | it matches your setup. If you're not sure, leave the default options |
| 44 | file. | 50 | within the file. |
| 45 | 51 | ||
| 46 | Finally, you can build `st` with the following command. Ensure you run as root | 52 | Finally, you can build =st= with the following command. Ensure you run |
| 47 | (e.g., `sudo`) or else you may not end up with a usable application file. | 53 | as root (e.g., =sudo=) or else you may not end up with a usable |
| 54 | application file. | ||
| 48 | 55 | ||
| 49 | ```sh | 56 | #+begin_src sh |
| 50 | sudo make clean install | 57 | sudo make clean install |
| 51 | ``` | 58 | #+end_src |
| 52 | |||
| 53 | ### Customization (Patches) | ||
| 54 | 59 | ||
| 55 | Note that customizing `st` requires you to modify the source files or to | 60 | *** Customization (Patches) |
| 56 | download one of the [available patches](https://st.suckless.org/patches/) for | 61 | :PROPERTIES: |
| 57 | suckless.org. | 62 | :CUSTOM_ID: customization-patches |
| 63 | :END: | ||
| 64 | Note that customizing =st= requires you to modify the source files or to | ||
| 65 | download one of the [[https://st.suckless.org/patches/][available | ||
| 66 | patches]] for suckless.org. | ||
| 58 | 67 | ||
| 59 | If you've already installed `st` and want to customize or install a patch, start | 68 | If you've already installed =st= and want to customize or install a |
| 60 | by uninstalling the current program. | 69 | patch, start by uninstalling the current program. |
| 61 | 70 | ||
| 62 | ```sh | 71 | #+begin_src sh |
| 63 | cd ~/suckless/st | 72 | cd ~/suckless/st |
| 64 | sudo make uninstall | 73 | sudo make uninstall |
| 65 | ``` | 74 | #+end_src |
| 66 | 75 | ||
| 67 | Next, grab the `<path>.diff` file from the page of the patch you chose. For | 76 | Next, grab the =<path>.diff= file from the page of the patch you chose. |
| 68 | example, I will be using the | 77 | For example, I will be using the |
| 69 | [defaultfontsize](https://st.suckless.org/patches/defaultfontsize/) patch in the below example. | 78 | [[https://st.suckless.org/patches/defaultfontsize/][defaultfontsize]] |
| 79 | patch in the below example. | ||
| 70 | 80 | ||
| 71 | ```sh | 81 | #+begin_src sh |
| 72 | wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff | 82 | wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff |
| 73 | ``` | 83 | #+end_src |
| 74 | 84 | ||
| 75 | Once the file is downloaded inside the `st` folder, apply the patch and | 85 | Once the file is downloaded inside the =st= folder, apply the patch and |
| 76 | re-install the program. You may need to install the `patch` command if you don't | 86 | re-install the program. You may need to install the =patch= command if |
| 77 | have it installed already (you should have installed it above). | 87 | you don't have it installed already (you should have installed it |
| 88 | above). | ||
| 78 | 89 | ||
| 79 | ```sh | 90 | #+begin_src sh |
| 80 | patch -i st-defaultfontsize-20210225-4ef0cbd.diff | 91 | patch -i st-defaultfontsize-20210225-4ef0cbd.diff |
| 81 | sudo make clean install | 92 | sudo make clean install |
| 82 | ``` | 93 | #+end_src |
| 83 | 94 | ||
| 84 | Once installed, you can use the default font size patch to launch `st` with any | 95 | Once installed, you can use the default font size patch to launch =st= |
| 85 | font size you wish: | 96 | with any font size you wish: |
| 86 | 97 | ||
| 87 | ```sh | 98 | #+begin_src sh |
| 88 | st -z 16 | 99 | st -z 16 |
| 89 | ``` | 100 | #+end_src |
blog/2022-12-23-alpine-desktop.org +141 −125
| @@ -1,89 +1,95 @@ | |||
| 1 | +++ | 1 | #+title: Alpine Linux as a Desktop OS |
| 2 | date = 2022-12-23 | 2 | #+date: 2022-12-23 |
| 3 | title = "Alpine Linux as a Desktop OS" | 3 | |
| 4 | description = "Documenting my steps to a useable Alpine Linux desktop experience." | 4 | ** Isn't Alpine Linux for Servers? |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: isnt-alpine-linux-for-servers | |
| 7 | ## Isn't Alpine Linux for Servers? | 7 | :END: |
| 8 | 8 | This is a question I see a lot when people are presented with an example | |
| 9 | This is a question I see a lot when people are presented with an example of | 9 | of Alpine Linux running as a desktop OS. |
| 10 | Alpine Linux running as a desktop OS. | 10 | |
| 11 | 11 | While Alpine is small, fast, and minimal, that doesn't stop it from | |
| 12 | While Alpine is small, fast, and minimal, that doesn't stop it from functioning | 12 | functioning at a productive level for desktop users. |
| 13 | at a productive level for desktop users. | 13 | |
| 14 | 14 | This post is documentation of how I installed and modified Alpine Linux | |
| 15 | This post is documentation of how I installed and modified Alpine Linux to | 15 | to become my daily desktop OS. |
| 16 | become my daily desktop OS. | 16 | |
| 17 | 17 | ** Installation | |
| 18 | ## Installation | 18 | :PROPERTIES: |
| 19 | 19 | :CUSTOM_ID: installation | |
| 20 | Note that I cover the installation of Alpine Linux in my other post, so I | 20 | :END: |
| 21 | won't repeat it here: [Alpine Linux: My New Server OS](/blog/alpine-linux/). | 21 | Note that I cover the installation of Alpine Linux in my other post, so |
| 22 | 22 | I won't repeat it here: [[/blog/alpine-linux/][Alpine Linux: My New | |
| 23 | Basically, get a bootable USB or whatever you prefer with Alpine on it, boot the | 23 | Server OS]]. |
| 24 | ISO, and run the setup script. | 24 | |
| 25 | 25 | Basically, get a bootable USB or whatever you prefer with Alpine on it, | |
| 26 | ```sh | 26 | boot the ISO, and run the setup script. |
| 27 | |||
| 28 | #+begin_src sh | ||
| 27 | setup-alpine | 29 | setup-alpine |
| 28 | ``` | 30 | #+end_src |
| 29 | 31 | ||
| 30 | Once you have gone through all the options and installer finishes without | 32 | Once you have gone through all the options and installer finishes |
| 31 | errors, reboot. | 33 | without errors, reboot. |
| 32 | 34 | ||
| 33 | ```sh | 35 | #+begin_src sh |
| 34 | reboot | 36 | reboot |
| 35 | ``` | 37 | #+end_src |
| 36 | 38 | ||
| 37 | ## Initial Setup | 39 | ** Initial Setup |
| 38 | 40 | :PROPERTIES: | |
| 39 | Once Alpine is installed and the machine has rebooted, login is as root | 41 | :CUSTOM_ID: initial-setup |
| 40 | initially or `su` to root once you log in as your user. | 42 | :END: |
| 41 | From here, you should start by updating and upgrading the system in case the | 43 | Once Alpine is installed and the machine has rebooted, login is as root |
| 42 | ISO was not fully up-to-date. | 44 | initially or =su= to root once you log in as your user. From here, you |
| 43 | 45 | should start by updating and upgrading the system in case the ISO was | |
| 44 | ```sh | 46 | not fully up-to-date. |
| 47 | |||
| 48 | #+begin_src sh | ||
| 45 | # Update and upgrade system | 49 | # Update and upgrade system |
| 46 | apk -U update && apk -U upgrade | 50 | apk -U update && apk -U upgrade |
| 47 | 51 | ||
| 48 | # Add an editor so we can enable the community repository | 52 | # Add an editor so we can enable the community repository |
| 49 | apk add nano | 53 | apk add nano |
| 50 | ``` | 54 | #+end_src |
| 51 | 55 | ||
| 52 | You need to uncomment the `community` repository for your version of Alpine | 56 | You need to uncomment the =community= repository for your version of |
| 53 | Linux. | 57 | Alpine Linux. |
| 54 | 58 | ||
| 55 | For v3.17, the `repositories` file should look like this: | 59 | For v3.17, the =repositories= file should look like this: |
| 56 | 60 | ||
| 57 | ```sh | 61 | #+begin_src sh |
| 58 | nano /etc/apk/repositories | 62 | nano /etc/apk/repositories |
| 59 | ``` | 63 | #+end_src |
| 60 | 64 | ||
| 61 | ```conf | 65 | #+begin_src conf |
| 62 | #/media/sda/apks | 66 | #/media/sda/apks |
| 63 | http://mirrors.gigenet.com/alpinelinux/v3.17/main | 67 | http://mirrors.gigenet.com/alpinelinux/v3.17/main |
| 64 | http://mirrors.gigenet.com/alpinelinux/v3.17/community | 68 | http://mirrors.gigenet.com/alpinelinux/v3.17/community |
| 65 | #http://mirrors.gigenet.com/alpinelinux/edge/main | 69 | #http://mirrors.gigenet.com/alpinelinux/edge/main |
| 66 | #http://mirrors.gigenet.com/alpinelinux/edge/community | 70 | #http://mirrors.gigenet.com/alpinelinux/edge/community |
| 67 | #http://mirrors.gigenet.com/alpinelinux/edge/testing | 71 | #http://mirrors.gigenet.com/alpinelinux/edge/testing |
| 68 | ``` | 72 | #+end_src |
| 69 | 73 | ||
| 70 | ```sh | 74 | #+begin_src sh |
| 71 | # Add the rest of your packages | 75 | # Add the rest of your packages |
| 72 | apk add linux-firmware iwd doas git curl wget | 76 | apk add linux-firmware iwd doas git curl wget |
| 73 | 77 | ||
| 74 | # Add yourself to the wheel group so you can use the doas command | 78 | # Add yourself to the wheel group so you can use the doas command |
| 75 | adduser $USER wheel | 79 | adduser $USER wheel |
| 76 | ``` | 80 | #+end_src |
| 77 | |||
| 78 | ## Window Manager (Desktop) | ||
| 79 | 81 | ||
| 80 | The [Sway installation guide](https://wiki.alpinelinux.org/wiki/Sway) has | 82 | ** Window Manager (Desktop) |
| 81 | everything you need to get Sway working on Alpine. | 83 | :PROPERTIES: |
| 84 | :CUSTOM_ID: window-manager-desktop | ||
| 85 | :END: | ||
| 86 | The [[https://wiki.alpinelinux.org/wiki/Sway][Sway installation guide]] | ||
| 87 | has everything you need to get Sway working on Alpine. | ||
| 82 | 88 | ||
| 83 | However, I'll include a brief list of the commands I ran and their purpose for | 89 | However, I'll include a brief list of the commands I ran and their |
| 84 | posterity here. | 90 | purpose for posterity here. |
| 85 | 91 | ||
| 86 | ```sh | 92 | #+begin_src sh |
| 87 | # Add eudev and set it up | 93 | # Add eudev and set it up |
| 88 | apk add eudev | 94 | apk add eudev |
| 89 | setup-devd udev | 95 | setup-devd udev |
| @@ -120,21 +126,21 @@ apk add \ # Install optional dependencies: | |||
| 120 | swaylock swaylockd \ # lockscreen tool | 126 | swaylock swaylockd \ # lockscreen tool |
| 121 | swaybg \ # wallpaper daemon | 127 | swaybg \ # wallpaper daemon |
| 122 | swayidle # idle management (DPMS) daemon | 128 | swayidle # idle management (DPMS) daemon |
| 123 | ``` | 129 | #+end_src |
| 124 | 130 | ||
| 125 | Once you have the packages installed and set-up, you need to export the | 131 | Once you have the packages installed and set-up, you need to export the |
| 126 | `XDG_RUNTIME_DIR` upon login. To do this, edit your `.profile` file. | 132 | =XDG_RUNTIME_DIR= upon login. To do this, edit your =.profile= file. |
| 127 | 133 | ||
| 128 | If you use another shell, such as `zsh`, you need to edit that shell's profile | 134 | If you use another shell, such as =zsh=, you need to edit that shell's |
| 129 | (e.g., `~/.zprofile`)! | 135 | profile (e.g., =~/.zprofile=)! |
| 130 | 136 | ||
| 131 | ```sh | 137 | #+begin_src sh |
| 132 | nano ~/.profile | 138 | nano ~/.profile |
| 133 | ``` | 139 | #+end_src |
| 134 | 140 | ||
| 135 | Within the file, paste this: | 141 | Within the file, paste this: |
| 136 | 142 | ||
| 137 | ```sh | 143 | #+begin_src sh |
| 138 | if test -z "${XDG_RUNTIME_DIR}"; then | 144 | if test -z "${XDG_RUNTIME_DIR}"; then |
| 139 | export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir | 145 | export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir |
| 140 | if ! test -d "${XDG_RUNTIME_DIR}"; then | 146 | if ! test -d "${XDG_RUNTIME_DIR}"; then |
| @@ -142,19 +148,22 @@ if test -z "${XDG_RUNTIME_DIR}"; then | |||
| 142 | chmod 0700 "${XDG_RUNTIME_DIR}" | 148 | chmod 0700 "${XDG_RUNTIME_DIR}" |
| 143 | fi | 149 | fi |
| 144 | fi | 150 | fi |
| 145 | ``` | 151 | #+end_src |
| 146 | 152 | ||
| 147 | Once that's complete, you can launch Sway manually. | 153 | Once that's complete, you can launch Sway manually. |
| 148 | 154 | ||
| 149 | ```sh | 155 | #+begin_src sh |
| 150 | dbus-run-session -- sway | 156 | dbus-run-session -- sway |
| 151 | ``` | 157 | #+end_src |
| 152 | |||
| 153 | ## Personal Touches | ||
| 154 | 158 | ||
| 155 | I also added the following packages, per my personal preferences and situation. | 159 | ** Personal Touches |
| 160 | :PROPERTIES: | ||
| 161 | :CUSTOM_ID: personal-touches | ||
| 162 | :END: | ||
| 163 | I also added the following packages, per my personal preferences and | ||
| 164 | situation. | ||
| 156 | 165 | ||
| 157 | ```sh | 166 | #+begin_src sh |
| 158 | doas apk add brightnessctl \ # Brightness controller | 167 | doas apk add brightnessctl \ # Brightness controller |
| 159 | zsh \ # Shell | 168 | zsh \ # Shell |
| 160 | firefox \ # Browser | 169 | firefox \ # Browser |
| @@ -164,91 +173,98 @@ doas apk add brightnessctl \ # Brightness controller | |||
| 164 | neomutt \ # CLI email client | 173 | neomutt \ # CLI email client |
| 165 | thunderbird \ # GUI email client | 174 | thunderbird \ # GUI email client |
| 166 | gnupg # GPG key manager | 175 | gnupg # GPG key manager |
| 167 | ``` | 176 | #+end_src |
| 168 | 177 | ||
| 169 | From here, I use my Syncthing storage to pull all the configuration files I | 178 | From here, I use my Syncthing storage to pull all the configuration |
| 170 | stored from prior desktops, such as [my | 179 | files I stored from prior desktops, such as |
| 171 | dotfiles](https://git.sr.ht/~cmc/dotfiles). | 180 | [[https://git.sr.ht/~cmc/dotfiles][my dotfiles]]. |
| 172 | 181 | ||
| 173 | ## Resolving Issues | 182 | ** Resolving Issues |
| 174 | 183 | :PROPERTIES: | |
| 175 | ### WiFi Issues | 184 | :CUSTOM_ID: resolving-issues |
| 176 | 185 | :END: | |
| 177 | I initially tried to set up my Wi-Fi the standard way with `iwd`, but it didn't | 186 | *** WiFi Issues |
| 178 | work. | 187 | :PROPERTIES: |
| 179 | 188 | :CUSTOM_ID: wifi-issues | |
| 180 | Here is what I initially tried (I did all of this as `root`): | 189 | :END: |
| 181 | 190 | I initially tried to set up my Wi-Fi the standard way with =iwd=, but it | |
| 182 | ```sh | 191 | didn't work. |
| 192 | |||
| 193 | Here is what I initially tried (I did all of this as =root=): | ||
| 194 | |||
| 195 | #+begin_src sh | ||
| 183 | apk add iwd | 196 | apk add iwd |
| 184 | rc-service iwd start | 197 | rc-service iwd start |
| 185 | iwctl station wlan0 connect <SSID> # This will prompt for the password | 198 | iwctl station wlan0 connect <SSID> # This will prompt for the password |
| 186 | rc-update add iwd boot && rc-update add dbus boot | 199 | rc-update add iwd boot && rc-update add dbus boot |
| 187 | ``` | 200 | #+end_src |
| 188 | 201 | ||
| 189 | Then, I added the Wi-Fi entry to the bottom of the networking interface file: | 202 | Then, I added the Wi-Fi entry to the bottom of the networking interface |
| 203 | file: | ||
| 190 | 204 | ||
| 191 | ```sh | 205 | #+begin_src sh |
| 192 | nano /etc/network/interfaces | 206 | nano /etc/network/interfaces |
| 193 | ``` | 207 | #+end_src |
| 194 | 208 | ||
| 195 | ```conf | 209 | #+begin_src conf |
| 196 | auto wlan0 | 210 | auto wlan0 |
| 197 | iface wlan0 inet dhcp | 211 | iface wlan0 inet dhcp |
| 198 | ``` | 212 | #+end_src |
| 199 | 213 | ||
| 200 | Finally, restart the networking service: | 214 | Finally, restart the networking service: |
| 201 | 215 | ||
| 202 | ```sh | 216 | #+begin_src sh |
| 203 | rc-service networking restart | 217 | rc-service networking restart |
| 204 | ``` | 218 | #+end_src |
| 205 | 219 | ||
| 206 | My Wi-Fi interface would receive an IP address from the router, but it could | 220 | My Wi-Fi interface would receive an IP address from the router, but it |
| 207 | not ping anything in the network. | 221 | could not ping anything in the network. To solve the Wi-Fi issues, I |
| 208 | To solve the Wi-Fi issues, I originally upgraded to Alpine's `edge` | 222 | originally upgraded to Alpine's =edge= repositories, which was |
| 209 | repositories, which was unnecessary. | 223 | unnecessary. |
| 210 | 224 | ||
| 211 | Really, the solution was to enable the `NameResolvingService=resolvconf` in | 225 | Really, the solution was to enable the =NameResolvingService=resolvconf= |
| 212 | `/etc/iwd/main.conf`. | 226 | in =/etc/iwd/main.conf=. |
| 213 | 227 | ||
| 214 | ```sh | 228 | #+begin_src sh |
| 215 | doas nano /etc/iwd/main.conf | 229 | doas nano /etc/iwd/main.conf |
| 216 | ``` | 230 | #+end_src |
| 217 | 231 | ||
| 218 | ```conf | 232 | #+begin_src conf |
| 219 | [Network] | 233 | [Network] |
| 220 | 234 | ||
| 221 | NameResolvingService=resolvconf | 235 | NameResolvingService=resolvconf |
| 222 | ``` | 236 | #+end_src |
| 223 | 237 | ||
| 224 | Once I finished this process, my Wi-Fi is working flawlessly. | 238 | Once I finished this process, my Wi-Fi is working flawlessly. |
| 225 | 239 | ||
| 226 | ### Sound Issues | 240 | *** Sound Issues |
| 227 | 241 | :PROPERTIES: | |
| 228 | Same as with the Wi-Fi, I had no sound and could not control the mute/unmute or | 242 | :CUSTOM_ID: sound-issues |
| 229 | volume buttons on my laptop. | 243 | :END: |
| 244 | Same as with the Wi-Fi, I had no sound and could not control the | ||
| 245 | mute/unmute or volume buttons on my laptop. | ||
| 230 | 246 | ||
| 231 | To resolve this, I installed | 247 | To resolve this, I installed |
| 232 | [pipewire](https://wiki.alpinelinux.org/wiki/PipeWire). | 248 | [[https://wiki.alpinelinux.org/wiki/PipeWire][pipewire]]. |
| 233 | 249 | ||
| 234 | ```sh | 250 | #+begin_src sh |
| 235 | # Add your user to the following groups | 251 | # Add your user to the following groups |
| 236 | addgroup $USER audio | 252 | addgroup $USER audio |
| 237 | addgroup $USER video | 253 | addgroup $USER video |
| 238 | 254 | ||
| 239 | # Install pipewire and other useful packages | 255 | # Install pipewire and other useful packages |
| 240 | apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa | 256 | apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa |
| 241 | ``` | 257 | #+end_src |
| 242 | 258 | ||
| 243 | Finally, I needed to add `/usr/libexec/pipewire-launcher` to my | 259 | Finally, I needed to add =/usr/libexec/pipewire-launcher= to my |
| 244 | `.config/sway/config` file so that Pipewire would run every time I launched | 260 | =.config/sway/config= file so that Pipewire would run every time I |
| 245 | sway. | 261 | launched sway. |
| 246 | 262 | ||
| 247 | ```sh | 263 | #+begin_src sh |
| 248 | nano ~/.config/sway/config | 264 | nano ~/.config/sway/config |
| 249 | ``` | 265 | #+end_src |
| 250 | 266 | ||
| 251 | ```conf | 267 | #+begin_src conf |
| 252 | # Run pipewire audio server | 268 | # Run pipewire audio server |
| 253 | exec /usr/libexec/pipewire-launcher | 269 | exec /usr/libexec/pipewire-launcher |
| 254 | 270 | ||
| @@ -257,10 +273,10 @@ bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT | |||
| 257 | bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% | 273 | bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% |
| 258 | bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle | 274 | bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle |
| 259 | bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle | 275 | bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle |
| 260 | ``` | 276 | #+end_src |
| 261 | 277 | ||
| 262 | Note that I do not use bluetooth or screen sharing, so I won't cover those | 278 | Note that I do not use bluetooth or screen sharing, so I won't cover |
| 263 | options in this post. | 279 | those options in this post. |
| 264 | 280 | ||
| 265 | Other than these issues, I have a working Alpine desktop. No other complaints | 281 | Other than these issues, I have a working Alpine desktop. No other |
| 266 | thus far! | 282 | complaints thus far! |
blog/2023-01-03-recent-website-changes.org +90 −79
| @@ -1,82 +1,93 @@ | |||
| 1 | +++ | 1 | #+title: Recent Website Changes |
| 2 | date = 2023-01-03 | 2 | #+date: 2023-01-03 |
| 3 | title = "Recent Website Changes" | 3 | |
| 4 | description = "A quick review on this website's recent changes." | 4 | ** The State of This Website |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: the-state-of-this-website | |
| 7 | ## The State of This Website | 7 | :END: |
| 8 | 8 | Over the years, this website has changed URLs, styles, content focus, | |
| 9 | Over the years, this website has changed URLs, styles, content focus, and much | 9 | and much more. However, it seems that I am never done, as I am yet again |
| 10 | more. However, it seems that I am never done, as I am yet again changing this | 10 | changing this page to fit my current needs and wants. |
| 11 | page to fit my current needs and wants. | 11 | |
| 12 | 12 | While this site was already minimal (~12kb), it contained a lot of | |
| 13 | While this site was already minimal (~12kb), it contained a lot of disorganized | 13 | disorganized content and some poorly chosen color schemes. |
| 14 | content and some poorly chosen color schemes. | 14 | |
| 15 | 15 | The recent updates attempt to fix these items while focusing on what I | |
| 16 | The recent updates attempt to fix these items while focusing on what I truly | 16 | truly care about here: the content within each page. |
| 17 | care about here: the content within each page. | 17 | |
| 18 | 18 | ** Recent Changes | |
| 19 | ## Recent Changes | 19 | :PROPERTIES: |
| 20 | 20 | :CUSTOM_ID: recent-changes | |
| 21 | I've made quite a few changes to the style of this website today, both in the | 21 | :END: |
| 22 | CSS and in the HTML. | 22 | I've made quite a few changes to the style of this website today, both |
| 23 | in the CSS and in the HTML. | ||
| 23 | 24 | ||
| 24 | Here are some of the key takeaways from today's changes: | 25 | Here are some of the key takeaways from today's changes: |
| 25 | 26 | ||
| 26 | 1. Reduce the width of the website from `60em` to `40em`. | 27 | 1. Reduce the width of the website from =60em= to =40em=. |
| 27 | 2. Remove breadcrumb navigation and replaced with a simple "Return Home" link | 28 | 2. Remove breadcrumb navigation and replaced with a simple "Return Home" |
| 28 | when visiting anything other than the homepage. | 29 | link when visiting anything other than the homepage. |
| 29 | 3. Remove syntax highlighting from code blocks. CSS now loads from a single | 30 | 3. Remove syntax highlighting from code blocks. CSS now loads from a |
| 30 | file. | 31 | single file. |
| 31 | 4. Move blog posts on the homepage to the top, pushing tertiary content down. | 32 | 4. Move blog posts on the homepage to the top, pushing tertiary content |
| 32 | 5. Update font-family from `monospace` to `sans-serif` for readability. | 33 | down. |
| 33 | 34 | 5. Update font-family from =monospace= to =sans-serif= for readability. | |
| 34 | ## Future Focus | 35 | |
| 35 | 36 | ** Future Focus | |
| 36 | ### Accessibility | 37 | :PROPERTIES: |
| 37 | 38 | :CUSTOM_ID: future-focus | |
| 38 | My main focus amidst all the changes this past year was accessibility. | 39 | :END: |
| 39 | I tried to improve the accessibility of all pages on this site as well as I | 40 | *** Accessibility |
| 40 | know how. | 41 | :PROPERTIES: |
| 41 | For example, I added aria labels to identify different navigation menus in | 42 | :CUSTOM_ID: accessibility |
| 42 | November. | 43 | :END: |
| 43 | 44 | My main focus amidst all the changes this past year was accessibility. I | |
| 44 | I want to continue this in 2023 and learn more about the accessibility features | 45 | tried to improve the accessibility of all pages on this site as well as |
| 45 | I can utilize to help those who may need them. | 46 | I know how. For example, I added aria labels to identify different |
| 46 | 47 | navigation menus in November. | |
| 47 | ### Content First | 48 | |
| 48 | 49 | I want to continue this in 2023 and learn more about the accessibility | |
| 49 | Beyond accessibility, I am making a concerted effort to bring the content of | 50 | features I can utilize to help those who may need them. |
| 50 | this site to the forefront and push the tertiary information (e.g., About Me, | 51 | |
| 51 | Services, etc.) down below the content. | 52 | *** Content First |
| 52 | 53 | :PROPERTIES: | |
| 53 | Further, I want to review and edit previous blog posts for grammar, accuracy, | 54 | :CUSTOM_ID: content-first |
| 54 | dead links, and more. Where necessary, I may even add a blurb at the top of old | 55 | :END: |
| 55 | posts that are no longer accurate or helpful. | 56 | Beyond accessibility, I am making a concerted effort to bring the |
| 56 | 57 | content of this site to the forefront and push the tertiary information | |
| 57 | ### Style | 58 | (e.g., About Me, Services, etc.) down below the content. |
| 58 | 59 | ||
| 59 | As always, I will be searching for ways to reduce distractions and bring a | 60 | Further, I want to review and edit previous blog posts for grammar, |
| 60 | minimalistic approach to this site. While there are certainly more drastic | 61 | accuracy, dead links, and more. Where necessary, I may even add a blurb |
| 61 | measures I could take, such as making this a [Shinobi | 62 | at the top of old posts that are no longer accurate or helpful. |
| 62 | Website](https://shinobi.bt.ht) or a [No CSS Website](https://nocss.club), I | 63 | |
| 63 | prefer to keep some modern features here. | 64 | *** Style |
| 64 | 65 | :PROPERTIES: | |
| 65 | Two ideas have been floating around in my head, but I am not sure how I feel | 66 | :CUSTOM_ID: style |
| 66 | about these approaches yet: | 67 | :END: |
| 67 | 68 | As always, I will be searching for ways to reduce distractions and bring | |
| 68 | 1. Going all-in with Emacs org-mode and creating a script to auto-publish all my | 69 | a minimalistic approach to this site. While there are certainly more |
| 69 | blog posts from org-mode to plain HTML files and then letting my CSS style it | 70 | drastic measures I could take, such as making this a |
| 70 | after the fact. | 71 | [[https://shinobi.bt.ht][Shinobi Website]] or a [[https://nocss.club][No |
| 71 | 2. Focus on publishing for Gemini or Gopher and then utilize a conversion | 72 | CSS Website]], I prefer to keep some modern features here. |
| 72 | program to translate the pages to HTML. | 73 | |
| 73 | 74 | Two ideas have been floating around in my head, but I am not sure how I | |
| 74 | ## Looking Back | 75 | feel about these approaches yet: |
| 75 | 76 | ||
| 76 | As I sit here and finalize the changes, as well as this post, I am happy with | 77 | 1. Going all-in with Emacs org-mode and creating a script to |
| 77 | the current state of this website. It's certainly a long way from my first | 78 | auto-publish all my blog posts from org-mode to plain HTML files and |
| 78 | attempts (parallax background images, anyone?) and it's good to see the | 79 | then letting my CSS style it after the fact. |
| 79 | progress. | 80 | 2. Focus on publishing for Gemini or Gopher and then utilize a |
| 80 | 81 | conversion program to translate the pages to HTML. | |
| 81 | I can only hope and work hard to ensure that I make more meaningful progress by | 82 | |
| 82 | this time next year. | 83 | ** Looking Back |
| 84 | :PROPERTIES: | ||
| 85 | :CUSTOM_ID: looking-back | ||
| 86 | :END: | ||
| 87 | As I sit here and finalize the changes, as well as this post, I am happy | ||
| 88 | with the current state of this website. It's certainly a long way from | ||
| 89 | my first attempts (parallax background images, anyone?) and it's good to | ||
| 90 | see the progress. | ||
| 91 | |||
| 92 | I can only hope and work hard to ensure that I make more meaningful | ||
| 93 | progress by this time next year. | ||
blog/2023-01-05-mass-unlike-tumblr-posts.org +78 −65
| @@ -1,31 +1,37 @@ | |||
| 1 | +++ | 1 | #+title: How to Easily Mass Unlike Tumblr Posts With Javascript |
| 2 | date = 2023-01-05 | 2 | #+date: 2023-01-05 |
| 3 | title = "How to Easily Mass Unlike Tumblr Posts With Javascript" | 3 | |
| 4 | description = "A quick Javascript snippet that allows you to quickly unlike your Tumblr likes." | 4 | ** The Dilemma |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: the-dilemma | |
| 7 | ## The Dilemma | 7 | :END: |
| 8 | 8 | The dilemma I had was pretty simple: I wanted to unlike all the posts I | |
| 9 | The dilemma I had was pretty simple: I wanted to unlike all the posts I have | 9 | have liked on Tumblr so that I could follow a new focus on blogs and |
| 10 | liked on Tumblr so that I could follow a new focus on blogs and start fresh. | 10 | start fresh. Otherwise, Tumblr will keep recommending content based on |
| 11 | Otherwise, Tumblr will keep recommending content based on your previous likes. | 11 | your previous likes. |
| 12 | 12 | ||
| 13 | ## The Solution | 13 | ** The Solution |
| 14 | 14 | :PROPERTIES: | |
| 15 | I searched the web for a while and noted that most solutions referenced Tumblr | 15 | :CUSTOM_ID: the-solution |
| 16 | setting and dashboard pages that no longer exist. Additionally, I did not want | 16 | :END: |
| 17 | to install a third party extension to do this, as some suggested. | 17 | I searched the web for a while and noted that most solutions referenced |
| 18 | 18 | Tumblr setting and dashboard pages that no longer exist. Additionally, I | |
| 19 | Luckily, I used Javascript for a while a few years ago and figured it would be | 19 | did not want to install a third party extension to do this, as some |
| 20 | easy enough to script a solution, as long as Tumblr had a system for the unlike | 20 | suggested. |
| 21 | buttons. | 21 | |
| 22 | 22 | Luckily, I used Javascript for a while a few years ago and figured it | |
| 23 | ### Identifying Unlike Buttons | 23 | would be easy enough to script a solution, as long as Tumblr had a |
| 24 | 24 | system for the unlike buttons. | |
| 25 | Tumblr's unlike buttons are structured as you can see in the following code | 25 | |
| 26 | block. All unlike buttons have an `aria-label` with a value of `Unlike`. | 26 | *** Identifying Unlike Buttons |
| 27 | 27 | :PROPERTIES: | |
| 28 | ```html | 28 | :CUSTOM_ID: identifying-unlike-buttons |
| 29 | :END: | ||
| 30 | Tumblr's unlike buttons are structured as you can see in the following | ||
| 31 | code block. All unlike buttons have an =aria-label= with a value of | ||
| 32 | =Unlike=. | ||
| 33 | |||
| 34 | #+begin_src html | ||
| 29 | <button class="TRX6J" aria-label="Unlike"> | 35 | <button class="TRX6J" aria-label="Unlike"> |
| 30 | <span class="EvhBA B1Z5w ztpfZ" tabindex="-1"> | 36 | <span class="EvhBA B1Z5w ztpfZ" tabindex="-1"> |
| 31 | <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23" | 37 | <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23" |
| @@ -34,33 +40,36 @@ block. All unlike buttons have an `aria-label` with a value of `Unlike`. | |||
| 34 | </svg> | 40 | </svg> |
| 35 | </span> | 41 | </span> |
| 36 | </button> | 42 | </button> |
| 37 | ``` | 43 | #+end_src |
| 38 | |||
| 39 | ### Running a Script to Unlike All Likes | ||
| 40 | 44 | ||
| 41 | To run this script, you will need to load the [Likes | | 45 | *** Running a Script to Unlike All Likes |
| 42 | Tumblr](https://www.tumblr.com/likes) page while logged in to your account. | 46 | :PROPERTIES: |
| 47 | :CUSTOM_ID: running-a-script-to-unlike-all-likes | ||
| 48 | :END: | ||
| 49 | To run this script, you will need to load the | ||
| 50 | [[https://www.tumblr.com/likes][Likes | Tumblr]] page while logged in to | ||
| 51 | your account. | ||
| 43 | 52 | ||
| 44 | Further, be sure to scroll down to the bottom and force Tumblr to load more | 53 | Further, be sure to scroll down to the bottom and force Tumblr to load |
| 45 | posts so that this script unlikes more posts at a time. | 54 | more posts so that this script unlikes more posts at a time. |
| 46 | 55 | ||
| 47 | Once you are logged in and the page is loaded, open the Developer Tools and be | 56 | Once you are logged in and the page is loaded, open the Developer Tools |
| 48 | sure you're on the "Console" tab. It should look something like this (this is | 57 | and be sure you're on the "Console" tab. It should look something like |
| 49 | in Firefox, Chromium should be similar): | 58 | this (this is in Firefox, Chromium should be similar): |
| 50 | 59 | ||
| 51 |  | 61 | [[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png]] |
| 53 | 62 | ||
| 54 | All you need to do is paste the following snippet into the dev console. This | 63 | All you need to do is paste the following snippet into the dev console. |
| 55 | code will collect all unlike buttons (`elements`) and then click each button to | 64 | This code will collect all unlike buttons (=elements=) and then click |
| 56 | unlike it. | 65 | each button to unlike it. |
| 57 | 66 | ||
| 58 | Optionally, you can comment-out the line `elements[i].click();` and uncomment | 67 | Optionally, you can comment-out the line =elements[i].click();= and |
| 59 | the `console.log()` lines to simply print out information without performing any | 68 | uncomment the =console.log()= lines to simply print out information |
| 60 | actions. This can be useful to debug issues or confirm that the code below isn't | 69 | without performing any actions. This can be useful to debug issues or |
| 61 | doing anything you don't want it to. | 70 | confirm that the code below isn't doing anything you don't want it to. |
| 62 | 71 | ||
| 63 | ```javascript | 72 | #+begin_src javascript |
| 64 | const elements = document.querySelectorAll('[aria-label="Unlike"]'); | 73 | const elements = document.querySelectorAll('[aria-label="Unlike"]'); |
| 65 | // console.log(elements); // 👉 [button] | 74 | // console.log(elements); // 👉 [button] |
| 66 | 75 | ||
| @@ -68,20 +77,24 @@ for (let i=0; i < elements.length; i++) { | |||
| 68 | // console.log(elements[i]); | 77 | // console.log(elements[i]); |
| 69 | elements[i].click(); | 78 | elements[i].click(); |
| 70 | } | 79 | } |
| 71 | ``` | 80 | #+end_src |
| 72 | 81 | ||
| 73 | ## Results | 82 | ** Results |
| 74 | 83 | :PROPERTIES: | |
| 75 | The results were quick for my situation, as it unliked ~200 posts within 2-3 | 84 | :CUSTOM_ID: results |
| 76 | seconds. I am not sure how this will perform on larger sets of likes (or if | 85 | :END: |
| 77 | Tumblr has a limit to unliking posts). | 86 | The results were quick for my situation, as it unliked ~200 posts within |
| 78 | 87 | 2-3 seconds. I am not sure how this will perform on larger sets of likes | |
| 79 | You can see the below screenshot showing that I pasted the snippet into the | 88 | (or if Tumblr has a limit to unliking posts). |
| 80 | console, pressed Enter, and then the posts are automatically unliked. | 89 | |
| 81 | 90 | You can see the below screenshot showing that I pasted the snippet into | |
| 82 |  | 92 | unliked. |
| 84 | 93 | ||
| 85 | Thinking about this further, I would bet that this would be fairly simple to | 94 | #+caption: Script !Results |
| 86 | package into a browser add-on so that users could install the add-on, go to | 95 | [[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png]] |
| 87 | their Likes page, and click a button to run the script. Food for thought. | 96 | |
| 97 | Thinking about this further, I would bet that this would be fairly | ||
| 98 | simple to package into a browser add-on so that users could install the | ||
| 99 | add-on, go to their Likes page, and click a button to run the script. | ||
| 100 | Food for thought. | ||
blog/2023-01-08-fedora-remove-login-manager.org +36 −33
| @@ -1,44 +1,47 @@ | |||
| 1 | +++ | 1 | #+title: Remove the Login Manager from Fedora i3 |
| 2 | date = 2023-01-08 | 2 | #+date: 2023-01-08 |
| 3 | title = "Remove the Login Manager from Fedora i3" | 3 | |
| 4 | description = "Quickly remove Fedora i3's login manager to launch your desktop manually." | 4 | ** Fedora i3's Login Manager |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: fedora-i3s-login-manager | |
| 7 | ## Fedora i3's Login Manager | 7 | :END: |
| 8 | 8 | Since I use the i3 spin of Fedora Workstation, I don't like to have a | |
| 9 | Since I use the i3 spin of Fedora Workstation, I don't like to have a login | 9 | login manager installed by default. As of the current version of Fedora |
| 10 | manager installed by default. As of the current version of Fedora i3, the | 10 | i3, the default login manager is LightDM. |
| 11 | default login manager is LightDM. | 11 | |
| 12 | 12 | If this is no longer the case, you can search for currently-installed | |
| 13 | If this is no longer the case, you can search for currently-installed packages | 13 | packages with the following command and see if you can identify a |
| 14 | with the following command and see if you can identify a different login | 14 | different login manager. |
| 15 | manager. | 15 | |
| 16 | 16 | #+begin_src sh | |
| 17 | ```sh | ||
| 18 | sudo dnf list installed | 17 | sudo dnf list installed |
| 19 | ``` | 18 | #+end_src |
| 20 | |||
| 21 | ## Removing the Login Manager | ||
| 22 | 19 | ||
| 20 | ** Removing the Login Manager | ||
| 21 | :PROPERTIES: | ||
| 22 | :CUSTOM_ID: removing-the-login-manager | ||
| 23 | :END: | ||
| 23 | In order to remove the login manager, simply uninstall the package. | 24 | In order to remove the login manager, simply uninstall the package. |
| 24 | 25 | ||
| 25 | ```sh | 26 | #+begin_src sh |
| 26 | sudo dnf remove lightdm | 27 | sudo dnf remove lightdm |
| 27 | ``` | 28 | #+end_src |
| 28 | |||
| 29 | ## Launching i3 Manually | ||
| 30 | 29 | ||
| 31 | In order to launch i3 manually, you need to set up your X session properly. | 30 | ** Launching i3 Manually |
| 32 | To start, create or edit the `~/.xinitrc` file to include the following at | 31 | :PROPERTIES: |
| 33 | the bottom. | 32 | :CUSTOM_ID: launching-i3-manually |
| 33 | :END: | ||
| 34 | In order to launch i3 manually, you need to set up your X session | ||
| 35 | properly. To start, create or edit the =~/.xinitrc= file to include the | ||
| 36 | following at the bottom. | ||
| 34 | 37 | ||
| 35 | ```config | 38 | #+begin_src config |
| 36 | exec i3 | 39 | exec i3 |
| 37 | ``` | 40 | #+end_src |
| 38 | 41 | ||
| 39 | Now, whenever you log in to the TTY, you can launch your desktop with the | 42 | Now, whenever you log in to the TTY, you can launch your desktop with |
| 40 | following command. | 43 | the following command. |
| 41 | 44 | ||
| 42 | ```sh | 45 | #+begin_src sh |
| 43 | startx | 46 | startx |
| 44 | ``` | 47 | #+end_src |
blog/2023-01-21-running-flatpak-apps-easily.org +34 −29
| @@ -1,45 +1,50 @@ | |||
| 1 | +++ | 1 | #+title: Running Flatpak Apps with Symlinks |
| 2 | date = 2023-01-21 | 2 | #+date: 2023-01-21 |
| 3 | title = "Running Flatpak Apps with Symlinks" | ||
| 4 | description = "Run flatpak apps faster and easier with a simple symlink solution." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Running Flatpak Apps Should Be Faster | 4 | ** Running Flatpak Apps Should Be Faster |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: running-flatpak-apps-should-be-faster | ||
| 7 | :END: | ||
| 8 | If you're like me and use Flatpak for those pesky apps that cannot run | ||
| 9 | on your system for one reason or another, you likely get annoyed with | ||
| 10 | opening a terminal and manually running the Flatpak app with the lengthy | ||
| 11 | =flatpak run ...= command. | ||
| 8 | 12 | ||
| 9 | If you're like me and use Flatpak for those pesky apps that cannot run on your | 13 | In the past, I manually created aliases in my =.zshrc= file for certain |
| 10 | system for one reason or another, you likely get annoyed with opening a terminal | 14 | apps. For example, an alias would look like the example below. |
| 11 | and manually running the Flatpak app with the lengthy `flatpak run ...` command. | ||
| 12 | |||
| 13 | In the past, I manually created aliases in my `.zshrc` file for certain apps. | ||
| 14 | For example, an alias would look like the example below. | ||
| 15 | 15 | ||
| 16 | This would allow me to run the command fast within the terminal, but it | 16 | This would allow me to run the command fast within the terminal, but it |
| 17 | wouldn't allow me to run it in an application launcher. | 17 | wouldn't allow me to run it in an application launcher. |
| 18 | 18 | ||
| 19 | ```sh | 19 | #+begin_src sh |
| 20 | # ~/.zshrc | 20 | # ~/.zshrc |
| 21 | alias librewolf = "flatpak run io.gitlab.librewolf-community" | 21 | alias librewolf = "flatpak run io.gitlab.librewolf-community" |
| 22 | ``` | 22 | #+end_src |
| 23 | |||
| 24 | However, I now use a much faster and better method that integrates with the | ||
| 25 | tiling WMs I use and their application launchers - `dmenu` and `bemenu`. | ||
| 26 | 23 | ||
| 27 | ## Creating Symlinks for Flatpak Apps | 24 | However, I now use a much faster and better method that integrates with |
| 25 | the tiling WMs I use and their application launchers - =dmenu= and | ||
| 26 | =bemenu=. | ||
| 28 | 27 | ||
| 29 | Let's use the example of Librewolf below. I can install the application like so: | 28 | ** Creating Symlinks for Flatpak Apps |
| 29 | :PROPERTIES: | ||
| 30 | :CUSTOM_ID: creating-symlinks-for-flatpak-apps | ||
| 31 | :END: | ||
| 32 | Let's use the example of Librewolf below. I can install the application | ||
| 33 | like so: | ||
| 30 | 34 | ||
| 31 | ```sh | 35 | #+begin_src sh |
| 32 | flatpak install flathub io.gitlab.librewolf-community | 36 | flatpak install flathub io.gitlab.librewolf-community |
| 33 | ``` | 37 | #+end_src |
| 34 | 38 | ||
| 35 | Once installed, I can create a symlink to link the flatpak app to my new symlink | 39 | Once installed, I can create a symlink to link the flatpak app to my new |
| 36 | in a location commonly included in your PATH. In this case, I chose `/usr/bin`. | 40 | symlink in a location commonly included in your PATH. In this case, I |
| 37 | You may need to choose a different location if `/usr/bin` isn't in your PATH. | 41 | chose =/usr/bin=. You may need to choose a different location if |
| 42 | =/usr/bin= isn't in your PATH. | ||
| 38 | 43 | ||
| 39 | ```sh | 44 | #+begin_src sh |
| 40 | ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf | 45 | ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf |
| 41 | ``` | 46 | #+end_src |
| 42 | 47 | ||
| 43 | Once complete, you should be able to launch the app using the command name you | 48 | Once complete, you should be able to launch the app using the command |
| 44 | chose above in the symlink (`librewolf`) from a terminal or from your | 49 | name you chose above in the symlink (=librewolf=) from a terminal or |
| 45 | application launcher! | 50 | from your application launcher! |
blog/2023-01-23-random-mullvad-wireguard.org +60 −55
| @@ -1,47 +1,49 @@ | |||
| 1 | +++ | 1 | #+title: Connecting to a Random Mullvad Wireguard Host |
| 2 | date = 2023-01-23 | 2 | #+date: 2023-01-23 |
| 3 | title = "Connecting to a Random Mullvad Wireguard Host" | ||
| 4 | description = "Read to see how I used a shell script to randomly connect to a Mullvad Wireguard host each time I run the script." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Mullvad Wireguard | 4 | ** Mullvad Wireguard |
| 8 | 5 | :PROPERTIES: | |
| 9 | If you're using an OS that does not support one of Mullvad's apps, you're | 6 | :CUSTOM_ID: mullvad-wireguard |
| 10 | likely using the Wireguard configuration files instead. | 7 | :END: |
| 8 | If you're using an OS that does not support one of Mullvad's apps, | ||
| 9 | you're likely using the Wireguard configuration files instead. | ||
| 11 | 10 | ||
| 12 | If not, the first step is to visit Mullvad's | 11 | If not, the first step is to visit Mullvad's |
| 13 | [Wireguard configuration | 12 | [[https://mullvad.net/en/account/#/wireguard-config][Wireguard |
| 14 | files](https://mullvad.net/en/account/#/wireguard-config) page and download a | 13 | configuration files]] page and download a ZIP of the configuration files |
| 15 | ZIP of the configuration files you want to use. | 14 | you want to use. |
| 16 | 15 | ||
| 17 | Personally, I downloaded all configuration files across the world and chose my | 16 | Personally, I downloaded all configuration files across the world and |
| 18 | connections using the script below. | 17 | chose my connections using the script below. |
| 19 | 18 | ||
| 20 | Once the files are downloaded, unzip them and move them to your preferred | 19 | Once the files are downloaded, unzip them and move them to your |
| 21 | location: | 20 | preferred location: |
| 22 | 21 | ||
| 23 | ```sh | 22 | #+begin_src sh |
| 24 | cd Downloads | 23 | cd Downloads |
| 25 | unzip mullvad_wireguard_linux_all_all.zip | 24 | unzip mullvad_wireguard_linux_all_all.zip |
| 26 | mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/ | 25 | mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/ |
| 27 | ``` | 26 | #+end_src |
| 28 | |||
| 29 | ## Creating a Script to Connect to a Random Host | ||
| 30 | 27 | ||
| 31 | Once you have a folder of Wireguard configuration files from Mullvad, you can | 28 | ** Creating a Script to Connect to a Random Host |
| 32 | create a script to randomly connect to any one of the locations. | 29 | :PROPERTIES: |
| 30 | :CUSTOM_ID: creating-a-script-to-connect-to-a-random-host | ||
| 31 | :END: | ||
| 32 | Once you have a folder of Wireguard configuration files from Mullvad, | ||
| 33 | you can create a script to randomly connect to any one of the locations. | ||
| 33 | 34 | ||
| 34 | Start by creating a shell script - mine is called `vpn.sh`. | 35 | Start by creating a shell script - mine is called =vpn.sh=. |
| 35 | 36 | ||
| 36 | ```sh | 37 | #+begin_src sh |
| 37 | nano ~/vpn.sh | 38 | nano ~/vpn.sh |
| 38 | ``` | 39 | #+end_src |
| 39 | 40 | ||
| 40 | Within this script, you can paste the following info. Note that I specify `us-*` | 41 | Within this script, you can paste the following info. Note that I |
| 41 | in my script, which means that it will only consider US-based VPN locations. You | 42 | specify =us-*= in my script, which means that it will only consider |
| 42 | can alter this or simply change it `*` to consider all locations. | 43 | US-based VPN locations. You can alter this or simply change it =*= to |
| 44 | consider all locations. | ||
| 43 | 45 | ||
| 44 | ```sh | 46 | #+begin_src sh |
| 45 | #!/bin/sh | 47 | #!/bin/sh |
| 46 | 48 | ||
| 47 | ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do | 49 | ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do |
| @@ -52,19 +54,19 @@ ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do | |||
| 52 | printf "\n\nPrinting new IP info:\n" | 54 | printf "\n\nPrinting new IP info:\n" |
| 53 | curl https://am.i.mullvad.net/connected | 55 | curl https://am.i.mullvad.net/connected |
| 54 | done | 56 | done |
| 55 | ``` | 57 | #+end_src |
| 56 | 58 | ||
| 57 | Once you've modified the script to your liking, add executable permissions | 59 | Once you've modified the script to your liking, add executable |
| 58 | and run the script: | 60 | permissions and run the script: |
| 59 | 61 | ||
| 60 | ```sh | 62 | #+begin_src sh |
| 61 | chmod +x ~/vpn.sh | 63 | chmod +x ~/vpn.sh |
| 62 | ~/vpn.sh | 64 | ~/vpn.sh |
| 63 | ``` | 65 | #+end_src |
| 64 | 66 | ||
| 65 | The output should look like the following: | 67 | The output should look like the following: |
| 66 | 68 | ||
| 67 | ```txt | 69 | #+begin_src txt |
| 68 | doas (user@host) password: | 70 | doas (user@host) password: |
| 69 | 71 | ||
| 70 | # ... The script will process all of the iptables and wg commands here | 72 | # ... The script will process all of the iptables and wg commands here |
| @@ -73,42 +75,45 @@ Created Mullvad wireguard connection with file: /home/user/mullvad/us-nyc-wg-210 | |||
| 73 | 75 | ||
| 74 | Printing new IP info: | 76 | Printing new IP info: |
| 75 | You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99 | 77 | You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99 |
| 76 | ``` | 78 | #+end_src |
| 77 | 79 | ||
| 78 | That's all there is to it. You can see your new location and IP via the | 80 | That's all there is to it. You can see your new location and IP via the |
| 79 | `printf` and `curl` commands included in the script. | 81 | =printf= and =curl= commands included in the script. |
| 80 | |||
| 81 | You can also go to the [Connection Check | | ||
| 82 | Mullvad](https://mullvad.net/en/check/) page to see if you are fully connected | ||
| 83 | to Mullvad and if any leaks exist. | ||
| 84 | 82 | ||
| 85 |  | 83 | You can also go to the [[https://mullvad.net/en/check/][Connection Check |
| 84 | | Mullvad]] page to see if you are fully connected to Mullvad and if any | ||
| 85 | leaks exist. | ||
| 86 | 86 | ||
| 87 | ## Disconnecting from the Wireguard Connection | 87 | #+caption: Mullvad Connection Check |
| 88 | [[https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png]] | ||
| 88 | 89 | ||
| 89 | If you forget which connection you're using, you can execute the following | 90 | ** Disconnecting from the Wireguard Connection |
| 90 | command to see where Wireguard is currently connected: | 91 | :PROPERTIES: |
| 92 | :CUSTOM_ID: disconnecting-from-the-wireguard-connection | ||
| 93 | :END: | ||
| 94 | If you forget which connection you're using, you can execute the | ||
| 95 | following command to see where Wireguard is currently connected: | ||
| 91 | 96 | ||
| 92 | ```sh | 97 | #+begin_src sh |
| 93 | wg show | 98 | wg show |
| 94 | ``` | 99 | #+end_src |
| 95 | 100 | ||
| 96 | This command will show you the Wireguard interfaces and should output a | 101 | This command will show you the Wireguard interfaces and should output a |
| 97 | connection like so: `interface: us-lax-wg-104`. | 102 | connection like so: =interface: us-lax-wg-104=. |
| 98 | 103 | ||
| 99 | Once you have this, just disconnect using that files' full path: | 104 | Once you have this, just disconnect using that files' full path: |
| 100 | 105 | ||
| 101 | ```sh | 106 | #+begin_src sh |
| 102 | wg-quick down /home/user/mullvad/us-lax-wg-104.conf | 107 | wg-quick down /home/user/mullvad/us-lax-wg-104.conf |
| 103 | ``` | 108 | #+end_src |
| 104 | 109 | ||
| 105 | I have a TODO item on figuring out how to easily export an environment variable | 110 | I have a TODO item on figuring out how to easily export an environment |
| 106 | that contains the configuration file's full name, so that I can just execute the | 111 | variable that contains the configuration file's full name, so that I can |
| 107 | following: | 112 | just execute the following: |
| 108 | 113 | ||
| 109 | ```sh | 114 | #+begin_src sh |
| 110 | # Ideal situation if I can export the $file variable to the environment | 115 | # Ideal situation if I can export the $file variable to the environment |
| 111 | wg-quick down $file | 116 | wg-quick down $file |
| 112 | ``` | 117 | #+end_src |
| 113 | 118 | ||
| 114 | If you have an idea on how to do this, email me! | 119 | If you have an idea on how to do this, email me! |
blog/2023-01-28-self-hosting-wger-workout-manager.org +100 −78
| @@ -1,27 +1,33 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting Wger Workout Manager |
| 2 | date = 2023-01-28 | 2 | #+date: 2023-01-28 |
| 3 | title = "Self-Hosting Wger Workout Manager" | 3 | |
| 4 | description = "A walkthrough on how to host Wger with Docker on Linux." | 4 | ** Wger: The Self-Hosted Workout Manager |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: wger-the-self-hosted-workout-manager | |
| 7 | ## Wger: The Self-Hosted Workout Manager | 7 | :END: |
| 8 | 8 | [[https://wger.de][Wger Workout Manager]] is a fitness tracking tool for | |
| 9 | [Wger Workout Manager](https://wger.de) is a fitness tracking tool for those who | 9 | those who enjoy self-hosting their data. You can also register an |
| 10 | enjoy self-hosting their data. You can also register an account on their main | 10 | account on their main website if you'd prefer to try without |
| 11 | website if you'd prefer to try without self-hosting. | 11 | self-hosting. |
| 12 | 12 | ||
| 13 | ### Features | 13 | *** Features |
| 14 | 14 | :PROPERTIES: | |
| 15 | I didn't see a full listing of features anywhere, so I compiled this list of my | 15 | :CUSTOM_ID: features |
| 16 | own after installing wger: | 16 | :END: |
| 17 | 17 | I didn't see a full listing of features anywhere, so I compiled this | |
| 18 | #### Dashboard | 18 | list of my own after installing wger: |
| 19 | 19 | ||
| 20 | - Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, & last 5 | 20 | **** Dashboard |
| 21 | Weight Logs | 21 | :PROPERTIES: |
| 22 | 22 | :CUSTOM_ID: dashboard | |
| 23 | #### Training | 23 | :END: |
| 24 | 24 | - Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, & | |
| 25 | last 5 Weight Logs | ||
| 26 | |||
| 27 | **** Training | ||
| 28 | :PROPERTIES: | ||
| 29 | :CUSTOM_ID: training | ||
| 30 | :END: | ||
| 25 | - Workout Log | 31 | - Workout Log |
| 26 | - Workout Schedule | 32 | - Workout Schedule |
| 27 | - Calendar (shows weight logs and Bad/Neutral/Good days) | 33 | - Calendar (shows weight logs and Bad/Neutral/Good days) |
| @@ -30,72 +36,83 @@ Weight Logs | |||
| 30 | - Public templates | 36 | - Public templates |
| 31 | - Exercises | 37 | - Exercises |
| 32 | 38 | ||
| 33 | #### Nutrition | 39 | **** Nutrition |
| 34 | 40 | :PROPERTIES: | |
| 41 | :CUSTOM_ID: nutrition | ||
| 42 | :END: | ||
| 35 | - Nutrition plans | 43 | - Nutrition plans |
| 36 | - BMI calculator | 44 | - BMI calculator |
| 37 | - Daily calories calculator | 45 | - Daily calories calculator |
| 38 | - Ingredient overview | 46 | - Ingredient overview |
| 39 | 47 | ||
| 40 | #### Body Weight | 48 | **** Body Weight |
| 41 | 49 | :PROPERTIES: | |
| 50 | :CUSTOM_ID: body-weight | ||
| 51 | :END: | ||
| 42 | - Weight overview | 52 | - Weight overview |
| 43 | 53 | ||
| 44 | ### Documentation | 54 | *** Documentation |
| 45 | 55 | :PROPERTIES: | |
| 56 | :CUSTOM_ID: documentation | ||
| 57 | :END: | ||
| 46 | In order to self-host wger, I opted to use the Docker version of the | 58 | In order to self-host wger, I opted to use the Docker version of the |
| 47 | application. You can read the README within the | 59 | application. You can read the README within the |
| 48 | [wger-project/docker](https://github.com/wger-project/docker) project on GitHub | 60 | [[https://github.com/wger-project/docker][wger-project/docker]] project |
| 49 | for information and installation instructions. | 61 | on GitHub for information and installation instructions. |
| 50 | 62 | ||
| 51 | ### Installation | 63 | *** Installation |
| 64 | :PROPERTIES: | ||
| 65 | :CUSTOM_ID: installation | ||
| 66 | :END: | ||
| 67 | To start the installation, I created a folder for wger and started | ||
| 68 | creating the three necessary files: | ||
| 52 | 69 | ||
| 53 | To start the installation, I created a folder for wger and started creating the | 70 | #+begin_src sh |
| 54 | three necessary files: | ||
| 55 | |||
| 56 | ```sh | ||
| 57 | mkdir ~/wger && mkdir ~/wger/config | 71 | mkdir ~/wger && mkdir ~/wger/config |
| 58 | touch ~/wger/docker-compose.yml && \ | 72 | touch ~/wger/docker-compose.yml && \ |
| 59 | touch ~/wger/config/prod.env && \ | 73 | touch ~/wger/config/prod.env && \ |
| 60 | touch ~/wger/config/nginx.conf | 74 | touch ~/wger/config/nginx.conf |
| 61 | ``` | 75 | #+end_src |
| 62 | 76 | ||
| 63 | Once you have the folders and files created, you will need to copy the contents | 77 | Once you have the folders and files created, you will need to copy the |
| 64 | of the `docker-compose.yml`, `prod.env`, and `nginx.conf` from the GitHub link | 78 | contents of the =docker-compose.yml=, =prod.env=, and =nginx.conf= from |
| 65 | above. | 79 | the GitHub link above. |
| 66 | 80 | ||
| 67 | A few notes to explain the changes I made to the default files: | 81 | A few notes to explain the changes I made to the default files: |
| 68 | 82 | ||
| 69 | - I updated the `ALLOW_REGISTRAION` variable in `prod.env` to `False` after I | 83 | - I updated the =ALLOW_REGISTRAION= variable in =prod.env= to =False= |
| 70 | created an account via my LAN connection, **before** I connected this app to a | 84 | after I created an account via my LAN connection, *before* I connected |
| 71 | publicly-available domain. | 85 | this app to a publicly-available domain. |
| 72 | - I uncommented and updated `CSRF_TRUSTED_ORIGINS` to be equal to the public | 86 | - I uncommented and updated =CSRF_TRUSTED_ORIGINS= to be equal to the |
| 73 | version of this app: `https://wger.example.com`. | 87 | public version of this app: =https://wger.example.com=. |
| 74 | - I updated the port within `docker-compose.yml`, within the `nginx` block. The | 88 | - I updated the port within =docker-compose.yml=, within the =nginx= |
| 75 | port I updated this to will be reflected in my nginx configuration file on the | 89 | block. The port I updated this to will be reflected in my nginx |
| 76 | server (NOT the wger nginx.conf file). | 90 | configuration file on the server (NOT the wger nginx.conf file). |
| 77 | 91 | ||
| 78 | ### Deploy | 92 | *** Deploy |
| 79 | 93 | :PROPERTIES: | |
| 94 | :CUSTOM_ID: deploy | ||
| 95 | :END: | ||
| 80 | Once all files are created and modified to your needs, simply start the | 96 | Once all files are created and modified to your needs, simply start the |
| 81 | container. | 97 | container. |
| 82 | 98 | ||
| 83 | ```sh | 99 | #+begin_src sh |
| 84 | docker-compose up -d | 100 | docker-compose up -d |
| 85 | ``` | 101 | #+end_src |
| 86 | 102 | ||
| 87 | You can now visit the website on your LAN by going to `localhost:YOUR_PORT` or | 103 | You can now visit the website on your LAN by going to |
| 88 | by the server's IP, if you're not on the same machine that is running the | 104 | =localhost:YOUR_PORT= or by the server's IP, if you're not on the same |
| 89 | container. | 105 | machine that is running the container. |
| 90 | 106 | ||
| 91 | If you wish to connect this app to a public domain name, you'll need to point an | 107 | If you wish to connect this app to a public domain name, you'll need to |
| 92 | `A` DNS record from the domain to your server's public IP. You'll then need to | 108 | point an =A= DNS record from the domain to your server's public IP. |
| 93 | create a configuration file for whichever web server or reverse proxy you're | 109 | You'll then need to create a configuration file for whichever web server |
| 94 | using. | 110 | or reverse proxy you're using. |
| 95 | 111 | ||
| 96 | Wger's README suggests the following reverse proxy configuration for Nginx: | 112 | Wger's README suggests the following reverse proxy configuration for |
| 113 | Nginx: | ||
| 97 | 114 | ||
| 98 | ```conf | 115 | #+begin_src conf |
| 99 | upstream wger { | 116 | upstream wger { |
| 100 | # This port should match the port in the `nginx` block of docker-compose.yml | 117 | # This port should match the port in the `nginx` block of docker-compose.yml |
| 101 | # If the container is running on this same machine, replace this with | 118 | # If the container is running on this same machine, replace this with |
| @@ -122,27 +139,32 @@ server { | |||
| 122 | ssl_certificate /path/to/https/certificate.crt; | 139 | ssl_certificate /path/to/https/certificate.crt; |
| 123 | ssl_certificate_key /path/to/https/certificate.key; | 140 | ssl_certificate_key /path/to/https/certificate.key; |
| 124 | } | 141 | } |
| 125 | ``` | 142 | #+end_src |
| 126 | |||
| 127 | ## Thoughts on Wger | ||
| 128 | 143 | ||
| 144 | ** Thoughts on Wger | ||
| 145 | :PROPERTIES: | ||
| 146 | :CUSTOM_ID: thoughts-on-wger | ||
| 147 | :END: | ||
| 129 | I'm still playing around with the app itself, but it seems to be a solid | 148 | I'm still playing around with the app itself, but it seems to be a solid |
| 130 | all-around workout manager, weight log, and food log. | 149 | all-around workout manager, weight log, and food log. |
| 131 | 150 | ||
| 132 | I like that the weight log graph is fluid and updates quickly. You can also | 151 | I like that the weight log graph is fluid and updates quickly. You can |
| 133 | import or export data in CSV format if you'd like to move your data elsewhere. | 152 | also import or export data in CSV format if you'd like to move your data |
| 134 | 153 | elsewhere. | |
| 135 | The workout manager is slightly odd, as it requires you to enter sets and reps | ||
| 136 | for each exercise when you enter it into the plan. Then, when you add a log | ||
| 137 | entry for performing a workout, you then add what you actually performed, in | ||
| 138 | terms of reps and weight. | ||
| 139 | 154 | ||
| 140 | I haven't tried the food log yet and I likely will not, at least for a | 155 | The workout manager is slightly odd, as it requires you to enter sets |
| 141 | while. | 156 | and reps for each exercise when you enter it into the plan. Then, when |
| 142 | I have no need for a food log or calorie tracker at the moment. | 157 | you add a log entry for performing a workout, you then add what you |
| 158 | actually performed, in terms of reps and weight. | ||
| 143 | 159 | ||
| 144 | ### Screenshot Example | 160 | I haven't tried the food log yet and I likely will not, at least for a |
| 161 | while. I have no need for a food log or calorie tracker at the moment. | ||
| 145 | 162 | ||
| 163 | *** Screenshot Example | ||
| 164 | :PROPERTIES: | ||
| 165 | :CUSTOM_ID: screenshot-example | ||
| 166 | :END: | ||
| 146 | You can see an example of a dashboard with placeholder data here: | 167 | You can see an example of a dashboard with placeholder data here: |
| 147 | 168 | ||
| 148 |  | 169 | #+caption: wger dashboard |
| 170 | [[https://img.cleberg.net/blog/20230128-wger/wger.png]] | ||
blog/2023-02-02-exploring-hare.org +145 −118
| @@ -1,162 +1,189 @@ | |||
| 1 | +++ | 1 | #+title: Exploring the Hare Programming Language |
| 2 | date = 2023-02-02 | 2 | #+date: 2023-02-02 |
| 3 | title = "Exploring the Hare Programming Language" | 3 | |
| 4 | description = "Walking back through my first project using the Hare programming language." | 4 | ** A Quick Note |
| 5 | aliases = ["/blog/testing-out-hare"] | 5 | :PROPERTIES: |
| 6 | +++ | 6 | :CUSTOM_ID: a-quick-note |
| 7 | 7 | :END: | |
| 8 | ## A Quick Note | 8 | By no means am I a professional developer, so this post will be rather |
| 9 | 9 | short. I won't be going into depth on the specification or anything that | |
| 10 | By no means am I a professional developer, so this post will be rather short. | 10 | technical. |
| 11 | I won't be going into depth on the specification or anything that technical. | 11 | |
| 12 | 12 | Instead, I will simply be talking about how I (a relatively basic | |
| 13 | Instead, I will simply be talking about how I (a relatively basic hobbyist | 13 | hobbyist programmer) have been playing with Hare and what intrigues me |
| 14 | programmer) have been playing with Hare and what intrigues me about the | 14 | about the language. |
| 15 | language. | 15 | |
| 16 | 16 | ** Hare | |
| 17 | ## Hare | 17 | :PROPERTIES: |
| 18 | 18 | :CUSTOM_ID: hare | |
| 19 | The [Hare](https://harelang.org) programming language is a straightforward | 19 | :END: |
| 20 | language that should look familiar if you've ever programmed with C, Rust, | 20 | The [[https://harelang.org][Hare]] programming language is a |
| 21 | or other languages that aim to build software at the system-level. | 21 | straightforward language that should look familiar if you've ever |
| 22 | programmed with C, Rust, or other languages that aim to build software | ||
| 23 | at the system-level. | ||
| 22 | 24 | ||
| 23 | The Hare homepage states the following: | 25 | The Hare homepage states the following: |
| 24 | 26 | ||
| 25 | > Hare is a systems programming language designed to be simple, stable, and | 27 | #+begin_quote |
| 26 | > robust. Hare uses a static type system, manual memory management, and | 28 | Hare is a systems programming language designed to be simple, stable, |
| 27 | > minimal runtime. It is well-suited to writing operating systems, system | 29 | and robust. Hare uses a static type system, manual memory management, |
| 28 | > tools, compilers, networking software, and other low-level, high performance | 30 | and minimal runtime. It is well-suited to writing operating systems, |
| 29 | > tasks. | 31 | system tools, compilers, networking software, and other low-level, high |
| 32 | performance tasks. | ||
| 30 | 33 | ||
| 31 | I have found this all to be true while playing with it for the first time today. | 34 | #+end_quote |
| 32 | In the next few sections, I'm going to walk through my installation and first | ||
| 33 | program. | ||
| 34 | 35 | ||
| 35 | ### Installation | 36 | I have found this all to be true while playing with it for the first |
| 37 | time today. In the next few sections, I'm going to walk through my | ||
| 38 | installation and first program. | ||
| 36 | 39 | ||
| 37 | I'm currently running Alpine Linux on my Thinkpad, so the installation was quite | 40 | *** Installation |
| 38 | easy as there is a package for Hare in the `apk` repositories. | 41 | :PROPERTIES: |
| 42 | :CUSTOM_ID: installation | ||
| 43 | :END: | ||
| 44 | I'm currently running Alpine Linux on my Thinkpad, so the installation | ||
| 45 | was quite easy as there is a package for Hare in the =apk= repositories. | ||
| 39 | 46 | ||
| 40 | ```sh | 47 | #+begin_src sh |
| 41 | doas apk add hare hare-doc | 48 | doas apk add hare hare-doc |
| 42 | ``` | 49 | #+end_src |
| 43 | 50 | ||
| 44 | However, I was able to install Hare from scratch on Fedora Linux a short while | 51 | However, I was able to install Hare from scratch on Fedora Linux a short |
| 45 | ago, which was also very easy to do. If you need further instructions and Hare | 52 | while ago, which was also very easy to do. If you need further |
| 46 | doesn't have a package on your system, take a look at the [Hare | 53 | instructions and Hare doesn't have a package on your system, take a look |
| 47 | Installation](https://harelang.org/installation/) page. | 54 | at the [[https://harelang.org/installation/][Hare Installation]] page. |
| 48 | |||
| 49 | ### Creating a Test Project | ||
| 50 | 55 | ||
| 56 | *** Creating a Test Project | ||
| 57 | :PROPERTIES: | ||
| 58 | :CUSTOM_ID: creating-a-test-project | ||
| 59 | :END: | ||
| 51 | In order to play with the language, I created | 60 | In order to play with the language, I created |
| 52 | [hare-test](https://git.sr.ht/~cmc/hare-projects) and will be | 61 | [[https://git.sr.ht/~cmc/hare-projects][hare-test]] and will be putting |
| 53 | putting any of my Hare-related adventures in here. | 62 | any of my Hare-related adventures in here. |
| 63 | |||
| 64 | #+begin_quote | ||
| 65 | *Update:* I also created a simple Hare program for creating a file from | ||
| 66 | user input: | ||
| 67 | [[https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha][files.ha]] | ||
| 54 | 68 | ||
| 55 | > **Update:** I also created a simple Hare program for creating a file from user | 69 | #+end_quote |
| 56 | > input: | ||
| 57 | > [files.ha](https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha) | ||
| 58 | 70 | ||
| 59 | Luckily, Hare doesn't require any complex set-up tools or build environment. | 71 | Luckily, Hare doesn't require any complex set-up tools or build |
| 60 | Once you have Hare installed, you simply need to create a file ending with `.ha` | 72 | environment. Once you have Hare installed, you simply need to create a |
| 61 | and you can run a Hare program. | 73 | file ending with =.ha= and you can run a Hare program. |
| 62 | 74 | ||
| 63 | I created a file called `rgb.ha` in order to test out the random number | 75 | I created a file called =rgb.ha= in order to test out the random number |
| 64 | generation and passing parameters between functions. | 76 | generation and passing parameters between functions. |
| 65 | 77 | ||
| 66 | ```sh | 78 | #+begin_src sh |
| 67 | nano rgb.ha | 79 | nano rgb.ha |
| 68 | ``` | 80 | #+end_src |
| 69 | 81 | ||
| 70 | Within this file, I was able to easily import a few of the [standard library | 82 | Within this file, I was able to easily import a few of the |
| 71 | modules](https://harelang.org/tutorials/stdlib/): `fmt`, `math::random`, and | 83 | [[https://harelang.org/tutorials/stdlib/][standard library modules]]: |
| 72 | `datetime`. | 84 | =fmt=, =math::random=, and =datetime=. |
| 73 | 85 | ||
| 74 | With these modules, I created two functions: | 86 | With these modules, I created two functions: |
| 75 | 87 | ||
| 76 | 1. `main`: This function calls the `generate_rgb` function and then prints out | 88 | 1. =main=: This function calls the =generate_rgb= function and then |
| 77 | the returned values. | 89 | prints out the returned values. |
| 78 | 2. `generate_rgb`: This function uses the current Unix epoch time to generate a | 90 | 2. =generate_rgb=: This function uses the current Unix epoch time to |
| 79 | pseudo-random value and uses this value to create three more random values | 91 | generate a pseudo-random value and uses this value to create three |
| 80 | between 0 and 255. These three numbers represent a color in RGB format. | 92 | more random values between 0 and 255. These three numbers represent a |
| 93 | color in RGB format. | ||
| 81 | 94 | ||
| 82 | > **Note**: Some syntax coloring may look odd, as Zola currently doesn't have a | 95 | #+begin_quote |
| 83 | > syntax highlighting theme for Hare. Instead, I'm using the C theme, which may | 96 | *Note*: Some syntax coloring may look odd, as Zola currently doesn't |
| 84 | > not be exactly accurate when coloring the code below. | 97 | have a syntax highlighting theme for Hare. Instead, I'm using the C |
| 98 | theme, which may not be exactly accurate when coloring the code below. | ||
| 85 | 99 | ||
| 86 | ```c | 100 | #+end_quote |
| 101 | |||
| 102 | #+begin_src C | ||
| 87 | use datetime; | 103 | use datetime; |
| 88 | use fmt; | 104 | use fmt; |
| 89 | use math::random; | 105 | use math::random; |
| 90 | 106 | ||
| 91 | export fn main() void = { | 107 | export fn main() void = { |
| 92 | const rgb = generate_rgb(); | 108 | const rgb = generate_rgb(); |
| 93 | fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!; | 109 | fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!; |
| 94 | }; | 110 | }; |
| 95 | 111 | ||
| 96 | fn generate_rgb() []u64 = { | 112 | fn generate_rgb() []u64 = { |
| 97 | // Use the current Unix epoch time as the seed value | 113 | // Use the current Unix epoch time as the seed value |
| 98 | let datetime = datetime::epochunix(&datetime::now()); | 114 | let datetime = datetime::epochunix(&datetime::now()); |
| 99 | 115 | ||
| 100 | // Generate initial pseudo-random value | 116 | // Generate initial pseudo-random value |
| 101 | // You must cast the datetime from int to u64 | 117 | // You must cast the datetime from int to u64 |
| 102 | let x = random::init(datetime: u64); | 118 | let x = random::init(datetime: u64); |
| 103 | 119 | ||
| 104 | // Generate RGB values between (0, 255) using pseudo-random init value | 120 | // Generate RGB values between (0, 255) using pseudo-random init value |
| 105 | let r = random::u64n(&x, 255); | 121 | let r = random::u64n(&x, 255); |
| 106 | let g = random::u64n(&x, 255); | 122 | let g = random::u64n(&x, 255); |
| 107 | let b = random::u64n(&x, 255); | 123 | let b = random::u64n(&x, 255); |
| 108 | 124 | ||
| 109 | // Structure data as array and return | 125 | // Structure data as array and return |
| 110 | let rgb_array: [3]u64 = [r, g, b]; | 126 | let rgb_array: [3]u64 = [r, g, b]; |
| 111 | return rgb_array; | 127 | return rgb_array; |
| 112 | }; | 128 | }; |
| 113 | ``` | 129 | #+end_src |
| 114 | |||
| 115 | ### Running a Program | ||
| 116 | 130 | ||
| 117 | Once you have a Hare file written and ready to run, you simply need to run it: | 131 | *** Running a Program |
| 132 | :PROPERTIES: | ||
| 133 | :CUSTOM_ID: running-a-program | ||
| 134 | :END: | ||
| 135 | Once you have a Hare file written and ready to run, you simply need to | ||
| 136 | run it: | ||
| 118 | 137 | ||
| 119 | ```sh | 138 | #+begin_src sh |
| 120 | hare run file.ha | 139 | hare run file.ha |
| 121 | ``` | 140 | #+end_src |
| 122 | 141 | ||
| 123 | You can also compile the program into an executable: | 142 | You can also compile the program into an executable: |
| 124 | 143 | ||
| 125 | ```sh | 144 | #+begin_src sh |
| 126 | hare build -o example file.ha | 145 | hare build -o example file.ha |
| 127 | ./example | 146 | ./example |
| 128 | ``` | 147 | #+end_src |
| 129 | 148 | ||
| 130 | ### Initial Thoughts | 149 | *** Initial Thoughts |
| 131 | 150 | :PROPERTIES: | |
| 132 | #### Documentation Improvements Would Help | 151 | :CUSTOM_ID: initial-thoughts |
| 133 | 152 | :END: | |
| 134 | While I was able to piece everything together eventually, the biggest downfall | 153 | **** Documentation Improvements Would Help |
| 135 | right now in Hare's documentation. For such a new project, the documentation is | 154 | :PROPERTIES: |
| 136 | in a great spot. However, bare specifications don't help as much as a brief | 155 | :CUSTOM_ID: documentation-improvements-would-help |
| 137 | examples section would. | 156 | :END: |
| 138 | 157 | While I was able to piece everything together eventually, the biggest | |
| 139 | For example, it took me a while to figure out what the `u64n` function was | 158 | downfall right now in Hare's documentation. For such a new project, the |
| 140 | looking for. I could tell that it took two parameters and the second was my max | 159 | documentation is in a great spot. However, bare specifications don't |
| 141 | value (255), but couldn't figure out what the first value should be. Eventually, | 160 | help as much as a brief examples section would. |
| 142 | I inspected the `random.ha` file in the [Hare source | 161 | |
| 143 | code](https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha) | 162 | For example, it took me a while to figure out what the =u64n= function |
| 144 | and found the test suite that helped me discover that it needed an `init()` | 163 | was looking for. I could tell that it took two parameters and the second |
| 145 | value in the form of `&var`. | 164 | was my max value (255), but couldn't figure out what the first value |
| 146 | 165 | should be. Eventually, I inspected the =random.ha= file in the | |
| 147 | #### More Basic Modules | 166 | [[https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha][Hare |
| 148 | 167 | source code]] and found the test suite that helped me discover that it | |
| 168 | needed an =init()= value in the form of =&var=. | ||
| 169 | |||
| 170 | **** More Basic Modules | ||
| 171 | :PROPERTIES: | ||
| 172 | :CUSTOM_ID: more-basic-modules | ||
| 173 | :END: | ||
| 149 | This is another point that comes from Hare being new and awaiting more | 174 | This is another point that comes from Hare being new and awaiting more |
| 150 | contributions, but there are some basic functions that I would personally enjoy | 175 | contributions, but there are some basic functions that I would |
| 151 | seeing in Hare, such as one to convert decimal (base 10) values to hexadecimal | 176 | personally enjoy seeing in Hare, such as one to convert decimal |
| 152 | (base 16). | 177 | (base 10) values to hexadecimal (base 16). |
| 153 | 178 | ||
| 154 | If I'm feeling comfortable with my math, I may work on the list of functions I | 179 | If I'm feeling comfortable with my math, I may work on the list of |
| 155 | want and see if any can make it into the Hare source code. | 180 | functions I want and see if any can make it into the Hare source code. |
| 156 | 181 | ||
| 157 | #### Overall Thoughts | 182 | **** Overall Thoughts |
| 158 | 183 | :PROPERTIES: | |
| 159 | Overall, I actually really enjoy Hare. It's not as tedious to get a project up | 184 | :CUSTOM_ID: overall-thoughts |
| 160 | and running as Rust, but it's also simpler and more user-friendly than learning | 185 | :END: |
| 161 | C. I am going to continue playing with it and see if I can make anything of | 186 | Overall, I actually really enjoy Hare. It's not as tedious to get a |
| 162 | particular value. | 187 | project up and running as Rust, but it's also simpler and more |
| 188 | user-friendly than learning C. I am going to continue playing with it | ||
| 189 | and see if I can make anything of particular value. | ||
blog/2023-05-22-burnout.org +47 −42
| @@ -1,43 +1,48 @@ | |||
| 1 | +++ | 1 | #+title: Burnout |
| 2 | date = 2023-05-22 | 2 | #+date: 2023-05-22 |
| 3 | title = "Burnout" | 3 | |
| 4 | description = "Dealing with burnout." | 4 | ** RE: Burnout |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: re-burnout | |
| 7 | ## RE: Burnout | 7 | :END: |
| 8 | 8 | I recently read | |
| 9 | I recently read [Drew DeVault's post on burnout](https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html) | 9 | [[https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html][Drew |
| 10 | around the same time I was pulling out of a burnout rut myself earlier this month. | 10 | DeVault's post on burnout]] around the same time I was pulling out of a |
| 11 | Finally, seeing the light at the end of my burnout tunnel made me want to | 11 | burnout rut myself earlier this month. Finally, seeing the light at the |
| 12 | write my first post back on this topic. | 12 | end of my burnout tunnel made me want to write my first post back on |
| 13 | 13 | this topic. | |
| 14 | ## Busy Seasons on Busy Seasons | 14 | |
| 15 | 15 | ** Busy Seasons on Busy Seasons | |
| 16 | :PROPERTIES: | ||
| 17 | :CUSTOM_ID: busy-seasons-on-busy-seasons | ||
| 18 | :END: | ||
| 16 | My career deals with busy seasons, generally driven by client demand. | 19 | My career deals with busy seasons, generally driven by client demand. |
| 17 | This last year, I dealt with a harsh busy season from Aug to Oct 2022 to | 20 | This last year, I dealt with a harsh busy season from Aug to Oct 2022 to |
| 18 | issue a few SOC reports for the period ending 2022-09-30. | 21 | issue a few SOC reports for the period ending 2022-09-30. Immediately |
| 19 | Immediately following that, I had to pivot and found another busy season | 22 | following that, I had to pivot and found another busy season from Oct to |
| 20 | from Oct to Jan for financial statement audits ending on 2022-12-31. | 23 | Jan for financial statement audits ending on 2022-12-31. Then again, |
| 21 | Then again, supporting other clients from Jan to Mar 2023, followed by my | 24 | supporting other clients from Jan to Mar 2023, followed by my current |
| 22 | current client workload aiming for SOC reports due on 2023-06-30. | 25 | client workload aiming for SOC reports due on 2023-06-30. |
| 23 | 26 | ||
| 24 | The result? A busy season that has lasted from August 2022 through today. I | 27 | The result? A busy season that has lasted from August 2022 through |
| 25 | will likely be rushing throughout the next month or two before I have a brief | 28 | today. I will likely be rushing throughout the next month or two before |
| 26 | break and need to focus on the 2023-09-30 SOC reports again. While auditing and | 29 | I have a brief break and need to focus on the 2023-09-30 SOC reports |
| 27 | consulting always involve a busy season, this is the first time I've | 30 | again. While auditing and consulting always involve a busy season, this |
| 28 | had one last 9+ months without a break. | 31 | is the first time I've had one last 9+ months without a break. |
| 29 | 32 | ||
| 30 | While it's been tough, I have a handful of breaks pre-planned throughout this | 33 | While it's been tough, I have a handful of breaks pre-planned throughout |
| 31 | next cycle and should be able to moderate the level of commitment required for | 34 | this next cycle and should be able to moderate the level of commitment |
| 32 | each client. | 35 | required for each client. |
| 33 | 36 | ||
| 34 | ## Refocusing | 37 | ** Refocusing |
| 35 | 38 | :PROPERTIES: | |
| 36 | Outside of work, I finally have time to work on hobbies such as this website, | 39 | :CUSTOM_ID: refocusing |
| 37 | programming, athletics, games, etc. | 40 | :END: |
| 38 | 41 | Outside of work, I finally have time to work on hobbies such as this | |
| 39 | You may have noticed my absence if you're in the same channels, forums, and | 42 | website, programming, athletics, games, etc. |
| 40 | rooms that I am, but I should finally be active again. | 43 | |
| 41 | 44 | You may have noticed my absence if you're in the same channels, forums, | |
| 42 | I'm hoping to break an item out of my backlog soon and start working on building | 45 | and rooms that I am, but I should finally be active again. |
| 43 | a new project or hack around with a stale one. | 46 | |
| 47 | I'm hoping to break an item out of my backlog soon and start working on | ||
| 48 | building a new project or hack around with a stale one. | ||
blog/2023-06-08-goaccess-geoip.org +54 −45
| @@ -1,46 +1,51 @@ | |||
| 1 | +++ | 1 | #+title: Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data |
| 2 | date = 2023-06-08T22:05:00Z | 2 | #+date: 2023-06-08 |
| 3 | title = "Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data" | 3 | |
| 4 | description = "" | 4 | ** Overview |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: overview | |
| 7 | ## Overview | 7 | :END: |
| 8 | 8 | [[https://goaccess.io/][GoAccess]] is an open source real-time web log | |
| 9 | [GoAccess](https://goaccess.io/) is an open source real-time web log analyzer | 9 | analyzer and interactive viewer that runs in a terminal in *nix systems |
| 10 | and interactive viewer that runs in a terminal in *nix systems or through your | 10 | or through your browser. |
| 11 | browser. | 11 | |
| 12 | 12 | ** Installation | |
| 13 | ## Installation | 13 | :PROPERTIES: |
| 14 | 14 | :CUSTOM_ID: installation | |
| 15 | To start, you'll need to install GoAccess for your OS. Here's an example for | 15 | :END: |
| 16 | Debian-based distros: | 16 | To start, you'll need to install GoAccess for your OS. Here's an example |
| 17 | 17 | for Debian-based distros: | |
| 18 | ```bash | 18 | |
| 19 | #+begin_src sh | ||
| 19 | sudo apt install goaccess | 20 | sudo apt install goaccess |
| 20 | ``` | 21 | #+end_src |
| 21 | 22 | ||
| 22 | Next, find any number of the MaxMind GeoIP database files on GitHub or another | 23 | Next, find any number of the MaxMind GeoIP database files on GitHub or |
| 23 | file hosting website. We're going to use P3TERX's version in this example: | 24 | another file hosting website. We're going to use P3TERX's version in |
| 25 | this example: | ||
| 24 | 26 | ||
| 25 | ```bash | 27 | #+begin_src sh |
| 26 | wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb | 28 | wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb |
| 27 | ``` | 29 | #+end_src |
| 28 | 30 | ||
| 29 | Be sure to save this file in an easy to remember location! | 31 | Be sure to save this file in an easy to remember location! |
| 30 | 32 | ||
| 31 | ## Usage | 33 | ** Usage |
| 32 | 34 | :PROPERTIES: | |
| 33 | In order to utilize the full capabilities of GoAccess and MMDB, start with the | 35 | :CUSTOM_ID: usage |
| 34 | command template below and customize as necessary. This will export an HTML | 36 | :END: |
| 35 | view of the GoAccess dashboard, showing all relevant information related to | 37 | In order to utilize the full capabilities of GoAccess and MMDB, start |
| 36 | that site's access log. You can also omit the `-o output.html` parameter if you | 38 | with the command template below and customize as necessary. This will |
| 37 | prefer to view the data within the CLI instead of creating an HTML file. | 39 | export an HTML view of the GoAccess dashboard, showing all relevant |
| 38 | 40 | information related to that site's access log. You can also omit the | |
| 39 | With the addition of the GeoIP Database parameter, section `16 - Geo Location` | 41 | =-o output.html= parameter if you prefer to view the data within the CLI |
| 40 | will be added with the various countries that are associated with the collected | 42 | instead of creating an HTML file. |
| 41 | IP addresses. | 43 | |
| 42 | 44 | With the addition of the GeoIP Database parameter, section | |
| 43 | ```bash | 45 | =16 - Geo Location= will be added with the various countries that are |
| 46 | associated with the collected IP addresses. | ||
| 47 | |||
| 48 | #+begin_src sh | ||
| 44 | zcat /var/log/nginx/example.access.log.*.gz | goaccess \ | 49 | zcat /var/log/nginx/example.access.log.*.gz | goaccess \ |
| 45 | --geoip-database=/home/user/GeoLite2-City.mmdb \ | 50 | --geoip-database=/home/user/GeoLite2-City.mmdb \ |
| 46 | --date-format=%d/%b/%Y \ | 51 | --date-format=%d/%b/%Y \ |
| @@ -48,18 +53,22 @@ zcat /var/log/nginx/example.access.log.*.gz | goaccess \ | |||
| 48 | --log-format=COMBINED \ | 53 | --log-format=COMBINED \ |
| 49 | -o output.html \ | 54 | -o output.html \ |
| 50 | /var/log/nginx/example.access.log - | 55 | /var/log/nginx/example.access.log - |
| 51 | ``` | 56 | #+end_src |
| 52 | |||
| 53 | ### Example Output | ||
| 54 | 57 | ||
| 58 | *** Example Output | ||
| 59 | :PROPERTIES: | ||
| 60 | :CUSTOM_ID: example-output | ||
| 61 | :END: | ||
| 55 | See below for an example of the HTML output: | 62 | See below for an example of the HTML output: |
| 56 | 63 | ||
| 57 |  | 64 | #+caption: GoAccess HTML |
| 65 | [[https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png]] | ||
| 58 | 66 | ||
| 59 | You can also see the GeoIP card created by the integration of the MaxMind | 67 | You can also see the GeoIP card created by the integration of the |
| 60 | database information. | 68 | MaxMind database information. |
| 61 | 69 | ||
| 62 |  | 70 | #+caption: GoAccess GeoIP |
| 71 | [[https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png]] | ||
| 63 | 72 | ||
| 64 | That's all there is to it! | 73 | That's all there is to it! Informational data is provided in an |
| 65 | Informational data is provided in an organized fashion with minimal effort. | 74 | organized fashion with minimal effort. |
blog/2023-06-08-self-hosting-baikal-server.org +96 −87
| @@ -1,34 +1,34 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting Baïkal Server |
| 2 | date = 2023-06-08T21:48:00Z | 2 | #+date: 2023-06-08 |
| 3 | title = "Self-Hosting Baïkal Server (CalDAV & CardDAV)" | 3 | |
| 4 | description = "A quick guide on install Baïkal with docker-compose." | 4 | ** What is Baïkal? |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: what-is-baïkal | |
| 7 | ## What is Baïkal? | 7 | :END: |
| 8 | 8 | [[https://sabre.io/baikal/][Baïkal]] is a lightweight CalDAV + CardDAV | |
| 9 | [Baïkal](https://sabre.io/baikal/) is a lightweight CalDAV + CardDAV server | 9 | server that you can self-host on your own machine. While I have tried (& |
| 10 | that you can self-host on your own machine. | 10 | failed) to get this CalDAV + CardDAV server running before, it was quite |
| 11 | While I have tried (& failed) to get this CalDAV + CardDAV server running | 11 | easy this time. Not really sure what I did differently this time, but |
| 12 | before, it was quite easy this time. | 12 | I'm documenting my process here to ensure I don't forget. |
| 13 | Not really sure what I did differently this time, but I'm documenting my | 13 | |
| 14 | process here to ensure I don't forget. | 14 | ** Installation |
| 15 | 15 | :PROPERTIES: | |
| 16 | ## Installation | 16 | :CUSTOM_ID: installation |
| 17 | 17 | :END: | |
| 18 | First, create a folder on your server and open a `docker-compose.yml` file for | 18 | First, create a folder on your server and open a =docker-compose.yml= |
| 19 | editing: | 19 | file for editing: |
| 20 | 20 | ||
| 21 | ```bash | 21 | #+begin_src sh |
| 22 | mkdir baikal && cd baikal | 22 | mkdir baikal && cd baikal |
| 23 | nano docker-compose.yml | 23 | nano docker-compose.yml |
| 24 | ``` | 24 | #+end_src |
| 25 | 25 | ||
| 26 | Within this file, you'll need to paste the information below. You can customize | 26 | Within this file, you'll need to paste the information below. You can |
| 27 | the `ports` section to use any port on your server to pass through to port 80 in | 27 | customize the =ports= section to use any port on your server to pass |
| 28 | the container. You can also edit the `volumes` section to use docker volumes | 28 | through to port 80 in the container. You can also edit the =volumes= |
| 29 | instead of local folders. | 29 | section to use docker volumes instead of local folders. |
| 30 | 30 | ||
| 31 | ```conf | 31 | #+begin_src conf |
| 32 | version: "2" | 32 | version: "2" |
| 33 | services: | 33 | services: |
| 34 | baikal: | 34 | baikal: |
| @@ -39,61 +39,68 @@ services: | |||
| 39 | volumes: | 39 | volumes: |
| 40 | - ./config:/var/www/baikal/config | 40 | - ./config:/var/www/baikal/config |
| 41 | - ./data:/var/www/baikal/Specific | 41 | - ./data:/var/www/baikal/Specific |
| 42 | ``` | 42 | #+end_src |
| 43 | 43 | ||
| 44 | Once finished with editing, save and close the file. Then, launch the docker | 44 | Once finished with editing, save and close the file. Then, launch the |
| 45 | container: | 45 | docker container: |
| 46 | 46 | ||
| 47 | ```bash | 47 | #+begin_src sh |
| 48 | sudo docker-compose up -d | 48 | sudo docker-compose up -d |
| 49 | ``` | 49 | #+end_src |
| 50 | 50 | ||
| 51 | ## Intial Setup | 51 | ** Intial Setup |
| 52 | 52 | :PROPERTIES: | |
| 53 | As long as no issues came up when starting the container, you should be able | 53 | :CUSTOM_ID: intial-setup |
| 54 | to visit the server's set-up page at `http://<server_ip>:<port>`. | 54 | :END: |
| 55 | The application will ask you to create an administrator account and choose | 55 | As long as no issues came up when starting the container, you should be |
| 56 | the database type for your storage. | 56 | able to visit the server's set-up page at =http://<server_ip>:<port>=. |
| 57 | Personally, I opted to use SQLite. | 57 | The application will ask you to create an administrator account and |
| 58 | 58 | choose the database type for your storage. Personally, I opted to use | |
| 59 | Make sure the administrator credentials are adequate to protect against | 59 | SQLite. |
| 60 | |||
| 61 | Make sure the administrator credentials are adequate to protect against | ||
| 60 | common attacks. | 62 | common attacks. |
| 61 | 63 | ||
| 62 | ## Creating Users | 64 | ** Creating Users |
| 63 | 65 | :PROPERTIES: | |
| 64 | Once you've set up the application, you will be greeted by the Dashboard page, | 66 | :CUSTOM_ID: creating-users |
| 65 | which will show the version of the app, status of the admin/CalDAV/CardDAV | 67 | :END: |
| 66 | services, and the number of users, calendars, events, address books, and | 68 | Once you've set up the application, you will be greeted by the Dashboard |
| 67 | contacts. | 69 | page, which will show the version of the app, status of the |
| 70 | admin/CalDAV/CardDAV services, and the number of users, calendars, | ||
| 71 | events, address books, and contacts. | ||
| 68 | 72 | ||
| 69 |  | 73 | #+caption: Baïkal Dashboard |
| 74 | [[https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png]] | ||
| 70 | 75 | ||
| 71 | To create a new user, navigate to the `Users and resources` page. This process | 76 | To create a new user, navigate to the =Users and resources= page. This |
| 72 | is as simple as entering a username, password, and email. | 77 | process is as simple as entering a username, password, and email. |
| 73 | 78 | ||
| 74 | Once a user has been created, you can create any number of calendars and address | 79 | Once a user has been created, you can create any number of calendars and |
| 75 | books for user, as well as inspect their information. | 80 | address books for user, as well as inspect their information. |
| 76 | 81 | ||
| 77 | ## Setting Up a Public URL | 82 | ** Setting Up a Public URL |
| 83 | :PROPERTIES: | ||
| 84 | :CUSTOM_ID: setting-up-a-public-url | ||
| 85 | :END: | ||
| 86 | Once your application is working locally, you can open access remotely | ||
| 87 | via a URL by using a reverse-proxy like Nginx. | ||
| 78 | 88 | ||
| 79 | Once your application is working locally, you can open access remotely via a URL | 89 | As a prerequisite, you must own a domain name and set up DNS records to |
| 80 | by using a reverse-proxy like Nginx. | 90 | point a domain name to the server hosting Baïkal. |
| 81 | 91 | ||
| 82 | As a prerequisite, you must own a domain name and set up DNS records | 92 | Start by navigating to your web server's configuration directory and |
| 83 | to point a domain name to the server hosting Baïkal. | 93 | create a new file for this application. |
| 84 | 94 | ||
| 85 | Start by navigating to your web server's configuration directory and create a | 95 | #+begin_src sh |
| 86 | new file for this application. | ||
| 87 | |||
| 88 | ```bash | ||
| 89 | cd /etc/nginx/sites-available/ | 96 | cd /etc/nginx/sites-available/ |
| 90 | nano dav | 97 | nano dav |
| 91 | ``` | 98 | #+end_src |
| 92 | 99 | ||
| 93 | Within this file, paste in the configuration from below and change | 100 | Within this file, paste in the configuration from below and change |
| 94 | `dav.example.com` to match the URL you'll be using. | 101 | =dav.example.com= to match the URL you'll be using. |
| 95 | 102 | ||
| 96 | ```conf | 103 | #+begin_src conf |
| 97 | server { | 104 | server { |
| 98 | if ($host ~ ^[^.]+\.example\.com$) { | 105 | if ($host ~ ^[^.]+\.example\.com$) { |
| 99 | return 301 https://$host$request_uri; | 106 | return 301 https://$host$request_uri; |
| @@ -123,35 +130,37 @@ server { | |||
| 123 | include /etc/letsencrypt/options-ssl-nginx.conf; | 130 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 124 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 131 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 125 | } | 132 | } |
| 126 | ``` | 133 | #+end_src |
| 127 | 134 | ||
| 128 | For Nginx on Ubuntu, you'll need to symlink the configuration file to the | 135 | For Nginx on Ubuntu, you'll need to symlink the configuration file to |
| 129 | `sites-enabled` directory and then restart Nginx. | 136 | the =sites-enabled= directory and then restart Nginx. |
| 130 | 137 | ||
| 131 | ```bash | 138 | #+begin_src sh |
| 132 | sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav | 139 | sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav |
| 133 | sudo systemctl restart nginx.service | 140 | sudo systemctl restart nginx.service |
| 134 | ``` | 141 | #+end_src |
| 135 | |||
| 136 | At this point, the Baïkal server should be available over the internet at the | ||
| 137 | URL configured above! | ||
| 138 | 142 | ||
| 139 | ## Configuring Clients | 143 | At this point, the Baïkal server should be available over the internet |
| 144 | at the URL configured above! | ||
| 140 | 145 | ||
| 141 | Lastly, configuring clients and applications is essential to making sure the DAV | 146 | ** Configuring Clients |
| 142 | server is being fully utilized. | 147 | :PROPERTIES: |
| 148 | :CUSTOM_ID: configuring-clients | ||
| 149 | :END: | ||
| 150 | Lastly, configuring clients and applications is essential to making sure | ||
| 151 | the DAV server is being fully utilized. | ||
| 143 | 152 | ||
| 144 | You can also use the `Users and resources` page to inspect a user's personal | 153 | You can also use the =Users and resources= page to inspect a user's |
| 145 | link to their calendars and address books by clicking the info (i) button. It | 154 | personal link to their calendars and address books by clicking the info |
| 146 | will show a URI like | 155 | (i) button. It will show a URI like |
| 147 | `/dav.php/calendars/your-user/default/`. | 156 | =/dav.php/calendars/your-user/default/=. |
| 148 | 157 | ||
| 149 | However, I found that the following URL works for most applications: | 158 | However, I found that the following URL works for most applications: |
| 150 | `/dav.php/principals/your-user/`. | 159 | =/dav.php/principals/your-user/=. |
| 151 | 160 | ||
| 152 | I used the `principals` URL above for Thunderbird (calendar, tasks, and | 161 | I used the =principals= URL above for Thunderbird (calendar, tasks, and |
| 153 | contacts), as well as iOS (calendar, tasks, and contacts) and everything works | 162 | contacts), as well as iOS (calendar, tasks, and contacts) and everything |
| 154 | flawlessly so far. | 163 | works flawlessly so far. |
| 155 | 164 | ||
| 156 | Syncing is quick between the server and clients, and I haven't seen any | 165 | Syncing is quick between the server and clients, and I haven't seen any |
| 157 | disruptions in the service or data integrity. | 166 | disruptions in the service or data integrity. |
blog/2023-06-18-unifi-ip-blocklist.org +72 −60
| @@ -1,37 +1,40 @@ | |||
| 1 | +++ | 1 | #+title: Block IP Addresses and Subnets with the Unifi Network Firewall |
| 2 | date = 2023-06-18 | 2 | #+date: 2023-06-18 |
| 3 | title = "Block IP Addresses and Subnets with Unifi Network Firewall" | 3 | |
| 4 | description = "A short tutorial on how to create IP Groups in Unifi's Network application and block them via the firewall." | 4 | ** Identifying Abusive IPs |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: identifying-abusive-ips | |
| 7 | ## Identifying Abusive IPs | 7 | :END: |
| 8 | 8 | If you're like me and use Unifi network equipment at the edge of the | |
| 9 | If you're like me and use Unifi network equipment at the edge of the network you | 9 | network you manage, you may know that Unifi is only somewhat decent at |
| 10 | manage, you may know that Unifi is only somewhat decent at identifying and | 10 | identifying and blocking IPs that represent abusive or threat actors. |
| 11 | blocking IPs that represent abusive or threat actors. | 11 | |
| 12 | 12 | While Unifi has a | |
| 13 | While Unifi has a [threat | 13 | [[https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management][threat |
| 14 | management](https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management) | 14 | management]] tool inside their Network application, it can be lacking in |
| 15 | tool inside their Network application, it can be lacking in functionality and | 15 | functionality and identification. For example, I have my UDM Pro set to |
| 16 | identification. For example, I have my UDM Pro set to identify and block almost | 16 | identify and block almost all categories of threats available within the |
| 17 | all categories of threats available within the Unifi settings. However, I | 17 | Unifi settings. However, I regularly identify abusive actors on my web |
| 18 | regularly identify abusive actors on my web server via the server logs. | 18 | server via the server logs. |
| 19 | 19 | ||
| 20 | In addition, I have identified IP addresses and subnets directly within Unifi's | 20 | In addition, I have identified IP addresses and subnets directly within |
| 21 | logs that the UDM did not block for whatever reason. | 21 | Unifi's logs that the UDM did not block for whatever reason. |
| 22 | 22 | ||
| 23 | This guide is meant to be another step in the process to manually block abusive | 23 | This guide is meant to be another step in the process to manually block |
| 24 | IP addresses or subnets that you have identified but are not being automatically | 24 | abusive IP addresses or subnets that you have identified but are not |
| 25 | blocked yet. | 25 | being automatically blocked yet. |
| 26 | 26 | ||
| 27 | ## Create an IP Group Profile | 27 | ** Create an IP Group Profile |
| 28 | 28 | :PROPERTIES: | |
| 29 | To start, login to the Unifi machine's web GUI and navigate to the Network app > | 29 | :CUSTOM_ID: create-an-ip-group-profile |
| 30 | Settings > Profiles. | 30 | :END: |
| 31 | 31 | To start, login to the Unifi machine's web GUI and navigate to the | |
| 32 | Within this page, choose the `IP Groups` tab and click `Create New`. | 32 | Network app > Settings > Profiles. |
| 33 | 33 | ||
| 34 |  | 34 | Within this page, choose the =IP Groups= tab and click =Create New=. |
| 35 | |||
| 36 | #+caption: Network Profiles | ||
| 37 | [[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png]] | ||
| 35 | 38 | ||
| 36 | Each IP Group profile can be used as one of three options: | 39 | Each IP Group profile can be used as one of three options: |
| 37 | 40 | ||
| @@ -39,39 +42,48 @@ Each IP Group profile can be used as one of three options: | |||
| 39 | 2. IPv4 Address/Subnet | 42 | 2. IPv4 Address/Subnet |
| 40 | 3. IPv6 Address/Subnet | 43 | 3. IPv6 Address/Subnet |
| 41 | 44 | ||
| 42 | In this example, I'm creating an IPv4 Address/Subnet group and adding a few | 45 | In this example, I'm creating an IPv4 Address/Subnet group and adding a |
| 43 | different IP addresses and a subnet. | 46 | few different IP addresses and a subnet. Once you've added all IP |
| 44 | Once you've added all IP addresses and subnets, click the `Apply` button | 47 | addresses and subnets, click the =Apply= button that should appear at |
| 45 | that should appear at the bottom. | 48 | the bottom. |
| 46 | 49 | ||
| 47 |  | 50 | #+caption: Network Profile IPs |
| 51 | [[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png]] | ||
| 48 | 52 | ||
| 49 | At this point, the IPv4 Address/Subnet has been created but not yet used. | 53 | At this point, the IPv4 Address/Subnet has been created but not yet |
| 54 | used. | ||
| 50 | 55 | ||
| 51 | ## Drop IP Group Profile via the Unifi Firewall | 56 | ** Drop IP Group Profile via the Unifi Firewall |
| 57 | :PROPERTIES: | ||
| 58 | :CUSTOM_ID: drop-ip-group-profile-via-the-unifi-firewall | ||
| 59 | :END: | ||
| 60 | To instruct the Unifi machine to block the profile we just created, we | ||
| 61 | need to navigate to the Network app > Settings > Firewall & Security. | ||
| 52 | 62 | ||
| 53 | To instruct the Unifi machine to block the profile we just created, we need | 63 | Within this screen, find the Firewall Rules table and click |
| 54 | to navigate to the Network app > Settings > Firewall & Security. | 64 | =Create Entry=. This entry should contain the following settings: |
| 55 | 65 | ||
| 56 | Within this screen, find the Firewall Rules table and click `Create Entry`. This | 66 | - Type: =Internet In= |
| 57 | entry should contain the following settings: | 67 | - Description: =<Your Custom Rule>= |
| 68 | - Rule Applied: =Before Predefined Rules= | ||
| 69 | - Action: =Drop= | ||
| 70 | - Source Type: =Port/IP Group= | ||
| 71 | - IPv4 Address Group: =<Name of the Group Profile You Created Above>= | ||
| 58 | 72 | ||
| 59 | - Type: `Internet In` | 73 | Customize the remaining configurations to your liking, and then save and |
| 60 | - Description: `<Your Custom Rule>` | 74 | enable the firewall rule. |
| 61 | - Rule Applied: `Before Predefined Rules` | ||
| 62 | - Action: `Drop` | ||
| 63 | - Source Type: `Port/IP Group` | ||
| 64 | - IPv4 Address Group: `<Name of the Group Profile You Created Above>` | ||
| 65 | 75 | ||
| 66 | Customize the remaining configurations to your liking, and then save and enable | 76 | #+caption: Firewall Rule |
| 67 | the firewall rule. | 77 | [[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png]] |
| 68 | 78 | ||
| 69 |  | 79 | Once enabled, the Unifi machine will be able to drop all incoming |
| 80 | connections from the defined IP addresses and subnets within the created | ||
| 81 | profile. | ||
| 70 | 82 | ||
| 71 | Once enabled, the Unifi machine will be able to drop all incoming connections | 83 | #+begin_quote |
| 72 | from the defined IP addresses and subnets within the created profile. | 84 | As a personal aside to this topic, I'm looking for a convenient way to |
| 85 | update the firewall rules or profiles remotely (within the LAN) from the | ||
| 86 | web server to accelerate this process. If you have an idea on how to | ||
| 87 | automatically update Unifi IP groups or firewall rules, let me know! | ||
| 73 | 88 | ||
| 74 | > As a personal aside to this topic, I'm looking for a convenient way to update | 89 | #+end_quote |
| 75 | > the firewall rules or profiles remotely (within the LAN) from the web server | ||
| 76 | > to accelerate this process. If you have an idea on how to automatically update | ||
| 77 | > Unifi IP groups or firewall rules, let me know! | ||
blog/2023-06-20-audit-review-cheatsheet.org +72 −66
| @@ -1,75 +1,81 @@ | |||
| 1 | +++ | 1 | #+title: Audit Review Checklist |
| 2 | date = 2023-06-20 | 2 | #+date: 2023-06-20 |
| 3 | title = "Cheatsheet: Review Audit Test Results" | ||
| 4 | description = "A handy cheatsheet for reviewing audit testing on FSA and SOC report engagements." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Overview | ||
| 8 | 4 | ||
| 9 | This post is a *very* brief overview on the basic process to review audit | 5 | ** Overview |
| 10 | test results, focusing on work done as part of a financial statement audit | 6 | :PROPERTIES: |
| 11 | (FSA) or service organization controls (SOC) report. | 7 | :CUSTOM_ID: overview |
| 8 | :END: | ||
| 9 | This post is a /very/ brief overview on the basic process to review | ||
| 10 | audit test results, focusing on work done as part of a financial | ||
| 11 | statement audit (FSA) or service organization controls (SOC) report. | ||
| 12 | 12 | ||
| 13 | While there are numerous different things to review and look for - all varying | 13 | While there are numerous different things to review and look for - all |
| 14 | wildly depending on the report, client, and tester - this list serves as a solid | 14 | varying wildly depending on the report, client, and tester - this list |
| 15 | base foundation for a reviewer. | 15 | serves as a solid base foundation for a reviewer. |
| 16 | 16 | ||
| 17 | I have used this throughout my career as a starting point to my reviews, and it | 17 | I have used this throughout my career as a starting point to my reviews, |
| 18 | has worked wonders for creating a consistent and objective template to my | 18 | and it has worked wonders for creating a consistent and objective |
| 19 | reviews. The goal is to keep this base high-level enough to be used on a wide | 19 | template to my reviews. The goal is to keep this base high-level enough |
| 20 | variety of engagements, while still ensuring that all key areas are covered. | 20 | to be used on a wide variety of engagements, while still ensuring that |
| 21 | 21 | all key areas are covered. | |
| 22 | ## Cheatsheet | ||
| 23 | 22 | ||
| 23 | ** Cheatsheet | ||
| 24 | :PROPERTIES: | ||
| 25 | :CUSTOM_ID: cheatsheet | ||
| 26 | :END: | ||
| 24 | 1. [ ] Check all documents for spelling and grammar. | 27 | 1. [ ] Check all documents for spelling and grammar. |
| 25 | 2. [ ] Ensure all acronyms are fully explained upon first use. | 28 | 2. [ ] Ensure all acronyms are fully explained upon first use. |
| 26 | 3. [ ] For all people referenced, use their full names and job titles upon | 29 | 3. [ ] For all people referenced, use their full names and job titles |
| 27 | first use. | 30 | upon first use. |
| 28 | 4. [ ] All supporting documents must cross-reference to the lead sheet and | 31 | 4. [ ] All supporting documents must cross-reference to the lead sheet |
| 29 | vice-versa. | 32 | and vice-versa. |
| 30 | 5. [ ] Verify that the control has been adequately tested: | 33 | 5. [ ] Verify that the control has been adequately tested: |
| 31 | - [ ] **Test of Design**: Did the tester obtain information regarding how | 34 | - [ ] *Test of Design*: Did the tester obtain information regarding |
| 32 | the control should perform normally and abnormally (e.g., emergency | 35 | how the control should perform normally and abnormally (e.g., |
| 33 | scenarios)? | 36 | emergency scenarios)? |
| 34 | - [ ] **Test of Operating Effectiveness**: Did the tester inquire, observe, | 37 | - [ ] *Test of Operating Effectiveness*: Did the tester inquire, |
| 35 | inspect, or re-perform sufficient evidence to support their | 38 | observe, inspect, or re-perform sufficient evidence to support |
| 36 | conclusion over the control? Inquiry alone is not adequate! | 39 | their conclusion over the control? Inquiry alone is not adequate! |
| 37 | 6. [ ] For any information used in the control, whether by the control operator | 40 | 6. [ ] For any information used in the control, whether by the control |
| 38 | or by the tester, did the tester appropriately document the source (system or | 41 | operator or by the tester, did the tester appropriately document the |
| 39 | person), extraction method, parameters, and completeness and accuracy (C&A)? | 42 | source (system or person), extraction method, parameters, and |
| 40 | - [ ] For any reports, queries, etc. used in the extraction, did the tester | 43 | completeness and accuracy (C&A)? |
| 41 | include a copy and notate C&A considerations? | 44 | - [ ] For any reports, queries, etc. used in the extraction, did the |
| 42 | 7. [ ] Did the tester document the specific criteria that the control is being | 45 | tester include a copy and notate C&A considerations? |
| 43 | tested against? | 46 | 7. [ ] Did the tester document the specific criteria that the control is |
| 44 | 8. [ ] Did the tester notate in the supporting documents where each criterion | 47 | being tested against? |
| 45 | was satisfied? | 48 | 8. [ ] Did the tester notate in the supporting documents where each |
| 46 | 9. [ ] If testing specific policies or procedures, are the documents adequate? | 49 | criterion was satisfied? |
| 47 | - [ ] e.g., a test to validate that a review of policy XYZ occurs | 50 | 9. [ ] If testing specific policies or procedures, are the documents |
| 48 | periodically should also evaluate the sufficiency of the policy itself, if | 51 | adequate? |
| 49 | meant to cover the risk that such a policy does not exist and is not | 52 | - [ ] e.g., a test to validate that a review of policy XYZ occurs |
| 50 | reviewed. | 53 | periodically should also evaluate the sufficiency of the policy |
| 54 | itself, if meant to cover the risk that such a policy does not | ||
| 55 | exist and is not reviewed. | ||
| 51 | 10. [ ] Does the test cover the appropriate period under review? | 56 | 10. [ ] Does the test cover the appropriate period under review? |
| 52 | - [ ] If the test is meant to cover only a portion of the audit period, do | 57 | - [ ] If the test is meant to cover only a portion of the audit |
| 53 | other controls exist to mitigate the risks that exist for the remainder of | 58 | period, do other controls exist to mitigate the risks that exist |
| 54 | the period? | 59 | for the remainder of the period? |
| 55 | 11. [ ] For any computer-aided audit tools (CAATs) or other automation | 60 | 11. [ ] For any computer-aided audit tools (CAATs) or other automation |
| 56 | techniques used in the test, is the use of such tools explained and | 61 | techniques used in the test, is the use of such tools explained and |
| 57 | appropriately documented? | 62 | appropriately documented? |
| 58 | 12. [ ] If prior-period documentation exists, are there any missing pieces of | 63 | 12. [ ] If prior-period documentation exists, are there any missing |
| 59 | evidence that would further enhance the quality of the test? | 64 | pieces of evidence that would further enhance the quality of the |
| 60 | 13. [ ] Was any information discovered during the walkthrough or inquiry phase | 65 | test? |
| 61 | that was not incorporated into the test? | 66 | 13. [ ] Was any information discovered during the walkthrough or inquiry |
| 62 | 14. [ ] Are there new rules or expectations from your company's internal | 67 | phase that was not incorporated into the test? |
| 63 | guidance or your regulatory bodies that would affect the audit approach for this | 68 | 14. [ ] Are there new rules or expectations from your company's internal |
| 64 | control? | 69 | guidance or your regulatory bodies that would affect the audit |
| 65 | 15. [ ] Was an exception, finding, or deficiency identified as a result of this | 70 | approach for this control? |
| 66 | test? | 71 | 15. [ ] Was an exception, finding, or deficiency identified as a result |
| 67 | - [ ] Was the control deficient in design, operation, or both? | 72 | of this test? |
| 68 | - [ ] What was the root cause of the finding? | 73 | - [ ] Was the control deficient in design, operation, or both? |
| 69 | - [ ] Does the finding indicate other findings or potential fraud? | 74 | - [ ] What was the root cause of the finding? |
| 70 | - [ ] What's the severity and scope of the finding? | 75 | - [ ] Does the finding indicate other findings or potential fraud? |
| 71 | - [ ] Do other controls exist as a form of compensation against the | 76 | - [ ] What's the severity and scope of the finding? |
| 72 | finding's severity, and do they mitigate the risk within the control | 77 | - [ ] Do other controls exist as a form of compensation against the |
| 73 | objective? | 78 | finding's severity, and do they mitigate the risk within the |
| 74 | - [ ] Does the finding exist at the end of the period, or was it resolved | 79 | control objective? |
| 75 | within the audit period?\ No newline at end of file | 80 | - [ ] Does the finding exist at the end of the period, or was it |
| 81 | resolved within the audit period? | ||
blog/2023-06-23-byobu.org +33 −28
| @@ -1,43 +1,48 @@ | |||
| 1 | +++ | 1 | #+title: Byobu |
| 2 | date = 2023-06-23T19:15:28 | 2 | #+date: 2023-06-23 |
| 3 | title = "Byobu" | ||
| 4 | description = "A quick look at byobu, a text based window manager." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Byobu | 4 | ** Byobu |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: byobu | ||
| 7 | :END: | ||
| 8 | [[https://www.byobu.org][byobu]] is a command-line tool that allows you | ||
| 9 | to use numerous screens within a single terminal emulator instance. More | ||
| 10 | specifically, it's a text based window manager, using either =screen= or | ||
| 11 | =tmux=. | ||
| 8 | 12 | ||
| 9 | [byobu](https://www.byobu.org) is a command-line tool that allows you to use | 13 | This post is mostly just a self-reference as I explore byobu, so I may |
| 10 | numerous screens within a single terminal emulator instance. More specifically, | 14 | come back later and update this post with more content. |
| 11 | it's a text based window manager, using either `screen` or `tmux`. | ||
| 12 | 15 | ||
| 13 | This post is mostly just a self-reference as I explore byobu, so I may come back | 16 | *** Screenshot |
| 14 | later and update this post with more content. | 17 | :PROPERTIES: |
| 18 | :CUSTOM_ID: screenshot | ||
| 19 | :END: | ||
| 20 | Take a look below at my current multi-window set-up in byobu while I | ||
| 21 | write this blog post: | ||
| 15 | 22 | ||
| 16 | ### Screenshot | 23 | #+caption: byobu |
| 24 | [[https://img.cleberg.net/blog/20230623-byobu/byobu.png]] | ||
| 17 | 25 | ||
| 18 | Take a look below at my current multi-window set-up in byobu while I write this | 26 | ** Keybindings |
| 19 | blog post: | 27 | :PROPERTIES: |
| 28 | :CUSTOM_ID: keybindings | ||
| 29 | :END: | ||
| 30 | You can open the help menu with either of the following commands; they | ||
| 31 | will both open the same manpage: | ||
| 20 | 32 | ||
| 21 |  | 33 | #+begin_src sh |
| 22 | |||
| 23 | ## Keybindings | ||
| 24 | |||
| 25 | You can open the help menu with either of the following commands; they will both | ||
| 26 | open the same manpage: | ||
| 27 | |||
| 28 | ```sh | ||
| 29 | byobu --help | 34 | byobu --help |
| 30 | # or | 35 | # or |
| 31 | man byobu | 36 | man byobu |
| 32 | ``` | 37 | #+end_src |
| 33 | 38 | ||
| 34 | While the manpage contains a ton of information about the functionality of | 39 | While the manpage contains a ton of information about the functionality |
| 35 | byobu (such as status notifications, sessions, and windows), the first location | 40 | of byobu (such as status notifications, sessions, and windows), the |
| 36 | to explore should be the keybindings section. | 41 | first location to explore should be the keybindings section. |
| 37 | 42 | ||
| 38 | The keybindings are configured as follows: | 43 | The keybindings are configured as follows: |
| 39 | 44 | ||
| 40 | ```txt | 45 | #+begin_src txt |
| 41 | byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or | 46 | byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or |
| 42 | within .screenrc if byobu-export was used). The common key bindings are: | 47 | within .screenrc if byobu-export was used). The common key bindings are: |
| 43 | 48 | ||
| @@ -65,4 +70,4 @@ Ctrl-a R - Reload profile | |||
| 65 | Ctrl-a ! - Toggle key bindings on and off | 70 | Ctrl-a ! - Toggle key bindings on and off |
| 66 | Ctrl-a k - Kill the current window | 71 | Ctrl-a k - Kill the current window |
| 67 | Ctrl-a ~ - Save the current window's scrollback buffer | 72 | Ctrl-a ~ - Save the current window's scrollback buffer |
| 68 | ``` | 73 | #+end_src |
blog/2023-06-23-self-hosting-convos-irc.org +112 −103
| @@ -1,40 +1,43 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting Convos: IRC Web Client |
| 2 | date = 2023-06-23T20:50:00 | 2 | #+date: 2023-06-23 |
| 3 | title = "Self-Hosting Convos IRC Web Client" | ||
| 4 | description = "" | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Convos | 4 | ** Convos |
| 8 | 5 | :PROPERTIES: | |
| 9 | [Convos](https://convos.chat/) is an always-online web client for IRC. It | 6 | :CUSTOM_ID: convos |
| 10 | has a few features that made it attractive to me as a self-hosted option: | 7 | :END: |
| 8 | [[https://convos.chat/][Convos]] is an always-online web client for IRC. | ||
| 9 | It has a few features that made it attractive to me as a self-hosted | ||
| 10 | option: | ||
| 11 | 11 | ||
| 12 | - Extremely simple Docker Compose installation method. | 12 | - Extremely simple Docker Compose installation method. |
| 13 | - Runs in the background and monitors chats even while you're not logged in. | 13 | - Runs in the background and monitors chats even while you're not logged |
| 14 | in. | ||
| 14 | - Neatly organized sidebar for conversation and client settings. | 15 | - Neatly organized sidebar for conversation and client settings. |
| 15 | - Ability to connect to different hosts and create profiles for hosts. | 16 | - Ability to connect to different hosts and create profiles for hosts. |
| 16 | - By default, registration is closed to the public. You can enable public | 17 | - By default, registration is closed to the public. You can enable |
| 17 | registration on the Settings page or generate invitation links on the | 18 | public registration on the Settings page or generate invitation links |
| 18 | Users page. | 19 | on the Users page. |
| 19 | - Customization of the client theme, organization name and URL, admin email, | 20 | - Customization of the client theme, organization name and URL, admin |
| 20 | and video service. | 21 | email, and video service. |
| 21 | 22 | ||
| 22 | ## Docker Installation | 23 | ** Docker Installation |
| 23 | 24 | :PROPERTIES: | |
| 24 | To install Convos, simply create a directory and a `docker-compose.yml` file: | 25 | :CUSTOM_ID: docker-installation |
| 25 | 26 | :END: | |
| 26 | ```sh | 27 | To install Convos, simply create a directory and a =docker-compose.yml= |
| 28 | file: | ||
| 29 | |||
| 30 | #+begin_src sh | ||
| 27 | mkdir ~/convos && cd ~/convos | 31 | mkdir ~/convos && cd ~/convos |
| 28 | nano docker-compose.yml | 32 | nano docker-compose.yml |
| 29 | ``` | 33 | #+end_src |
| 30 | 34 | ||
| 31 | With the `docker-compose.yml` file open, paste the configuration below into | 35 | With the =docker-compose.yml= file open, paste the configuration below |
| 32 | the file. | 36 | into the file. You can customize the host port to be something unique, |
| 33 | You can customize the host port to be something unique, such as `21897:3000`. | 37 | such as =21897:3000=. You can also change the =data= folder to be a |
| 34 | You can also change the `data` folder to be a docker volume instead, if you | 38 | docker volume instead, if you prefer. |
| 35 | prefer. | ||
| 36 | 39 | ||
| 37 | ```config | 40 | #+begin_src config |
| 38 | version: '3' | 41 | version: '3' |
| 39 | 42 | ||
| 40 | services: | 43 | services: |
| @@ -47,118 +50,124 @@ services: | |||
| 47 | environment: | 50 | environment: |
| 48 | - CONVOS_REVERSE_PROXY=1 | 51 | - CONVOS_REVERSE_PROXY=1 |
| 49 | restart: always | 52 | restart: always |
| 50 | ``` | 53 | #+end_src |
| 51 | 54 | ||
| 52 | Save the `docker-compose.yml` file and bring the container up: | 55 | Save the =docker-compose.yml= file and bring the container up: |
| 53 | 56 | ||
| 54 | ```sh | 57 | #+begin_src sh |
| 55 | sudo docker-compose up -d | 58 | sudo docker-compose up -d |
| 56 | ``` | 59 | #+end_src |
| 57 | 60 | ||
| 58 | At this point, Convos is available at `<server_ip>:3000` but not available | 61 | At this point, Convos is available at =<server_ip>:3000= but not |
| 59 | to the public. | 62 | available to the public. |
| 60 | 63 | ||
| 61 | ## Nginx Reverse Proxy | 64 | ** Nginx Reverse Proxy |
| 65 | :PROPERTIES: | ||
| 66 | :CUSTOM_ID: nginx-reverse-proxy | ||
| 67 | :END: | ||
| 68 | If you're using Nginx, you can create a configuration file for the | ||
| 69 | convos application. Start by opening the file: | ||
| 62 | 70 | ||
| 63 | If you're using Nginx, you can create a configuration file for the convos | 71 | #+begin_src sh |
| 64 | application. | ||
| 65 | Start by opening the file: | ||
| 66 | |||
| 67 | ```sh | ||
| 68 | nano /etc/nginx/sites-available/convos | 72 | nano /etc/nginx/sites-available/convos |
| 69 | ``` | 73 | #+end_src |
| 70 | 74 | ||
| 71 | Within the nginx configuration file, paste the following content and be sure | 75 | Within the nginx configuration file, paste the following content and be |
| 72 | to update `convos.example.com` to match your domain and `127.0.0.1:3000` to | 76 | sure to update =convos.example.com= to match your domain and |
| 73 | match the port you opened in the `docker-compose.yml` file. | 77 | =127.0.0.1:3000= to match the port you opened in the |
| 78 | =docker-compose.yml= file. | ||
| 74 | 79 | ||
| 75 | ```config | 80 | #+begin_src config |
| 76 | # Host and port where convos is running | 81 | # Host and port where convos is running |
| 77 | upstream convos_upstream { server 127.0.0.1:3000; } | 82 | upstream convos_upstream { server 127.0.0.1:3000; } |
| 78 | 83 | ||
| 79 | server { | 84 | server { |
| 80 | if ($host ~ ^[^.]+\.example\.com$) { | 85 | if ($host ~ ^[^.]+\.example\.com$) { |
| 81 | return 301 https://$host$request_uri; | 86 | return 301 https://$host$request_uri; |
| 82 | } | 87 | } |
| 83 | 88 | ||
| 84 | listen [::]:80; | 89 | listen [::]:80; |
| 85 | listen 80; | 90 | listen 80; |
| 86 | server_name convos.example.com; | 91 | server_name convos.example.com; |
| 87 | return 404; | 92 | return 404; |
| 88 | } | 93 | } |
| 89 | 94 | ||
| 90 | server { | 95 | server { |
| 91 | listen [::]:443 ssl http2; | 96 | listen [::]:443 ssl http2; |
| 92 | listen 443 ssl http2; | 97 | listen 443 ssl http2; |
| 93 | server_name convos.example.com; | 98 | server_name convos.example.com; |
| 94 | access_log /var/log/nginx/convos.access.log; | 99 | access_log /var/log/nginx/convos.access.log; |
| 95 | error_log /var/log/nginx/convos.error.log; | 100 | error_log /var/log/nginx/convos.error.log; |
| 96 | 101 | ||
| 97 | location / { | 102 | location / { |
| 98 | proxy_pass http://convos_upstream; | 103 | proxy_pass http://convos_upstream; |
| 99 | proxy_http_version 1.1; | 104 | proxy_http_version 1.1; |
| 100 | proxy_set_header Upgrade $http_upgrade; | 105 | proxy_set_header Upgrade $http_upgrade; |
| 101 | proxy_set_header Connection "upgrade"; | 106 | proxy_set_header Connection "upgrade"; |
| 102 | client_max_body_size 0; | 107 | client_max_body_size 0; |
| 103 | proxy_set_header Host $host; | 108 | proxy_set_header Host $host; |
| 104 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 109 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 105 | proxy_set_header X-Request-Base "$scheme://$host/"; | 110 | proxy_set_header X-Request-Base "$scheme://$host/"; |
| 106 | } | 111 | } |
| 107 | 112 | ||
| 108 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; | 113 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; |
| 109 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; | 114 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; |
| 110 | include /etc/letsencrypt/options-ssl-nginx.conf; | 115 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 111 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 116 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 112 | } | 117 | } |
| 113 | ``` | 118 | #+end_src |
| 114 | 119 | ||
| 115 | Once the file is saved, link it to the `sites-enabled` directory and restart | 120 | Once the file is saved, link it to the =sites-enabled= directory and |
| 116 | Nginx. | 121 | restart Nginx. |
| 117 | 122 | ||
| 118 | ```sh | 123 | #+begin_src sh |
| 119 | sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos | 124 | sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos |
| 120 | sudo systemctl restart nginx.service | 125 | sudo systemctl restart nginx.service |
| 121 | ``` | 126 | #+end_src |
| 122 | |||
| 123 | ## Screenshots | ||
| 124 | 127 | ||
| 125 | See the screenshots below for an example of the Convos homepage and an | 128 | ** Screenshots |
| 129 | :PROPERTIES: | ||
| 130 | :CUSTOM_ID: screenshots | ||
| 131 | :END: | ||
| 132 | See the screenshots below for an example of the Convos homepage and an | ||
| 126 | excerpt of the chat screen. | 133 | excerpt of the chat screen. |
| 127 | 134 | ||
| 128 | There are numerous themes to choose from; the theme shown in the images | 135 | There are numerous themes to choose from; the theme shown in the images |
| 129 | below is Dracula. | 136 | below is Dracula. |
| 130 | 137 | ||
| 131 |  | 139 | [[https://img.cleberg.net/blog/20230623-convos/convos_home.png%20%22Convos%20Home%22]] |
| 133 | |||
| 134 |  | ||
| 136 | 140 | ||
| 137 | ## Registering a Nickname | 141 | #+caption: Convos Chat |
| 142 | [[https://img.cleberg.net/blog/20230623-convos/convos_chat.png%20%22Convos%20Chat%22]] | ||
| 138 | 143 | ||
| 139 | Hop into the server channel so that you can message NickServ. In the case of | 144 | ** Registering a Nickname |
| 140 | Convos, the default sever is libera.chat. | 145 | :PROPERTIES: |
| 141 | Simply click the `libera` conversation at the top of the sidebar to open it. | 146 | :CUSTOM_ID: registering-a-nickname |
| 142 | Once the chat is open, you can claim a nickname by typing: | 147 | :END: |
| 148 | Hop into the server channel so that you can message NickServ. In the | ||
| 149 | case of Convos, the default sever is libera.chat. Simply click the | ||
| 150 | =libera= conversation at the top of the sidebar to open it. Once the | ||
| 151 | chat is open, you can claim a nickname by typing: | ||
| 143 | 152 | ||
| 144 | ```txt | 153 | #+begin_src txt |
| 145 | /nick <nick> | 154 | /nick <nick> |
| 146 | ``` | 155 | #+end_src |
| 147 | 156 | ||
| 148 | If the nickname is available, and you'd like to register the nickname to | 157 | If the nickname is available, and you'd like to register the nickname to |
| 149 | yourself, you'll need to type another command: | 158 | yourself, you'll need to type another command: |
| 150 | 159 | ||
| 151 | ```txt | 160 | #+begin_src txt |
| 152 | /msg NickServ REGISTER | 161 | /msg NickServ REGISTER |
| 153 | <password> <email> | 162 | <password> <email> |
| 154 | ``` | 163 | #+end_src |
| 155 | 164 | ||
| 156 | On libera.chat, the server will send a confirmation email with a command | 165 | On libera.chat, the server will send a confirmation email with a command |
| 157 | that you must message in IRC to verify registration of the nickname: | 166 | that you must message in IRC to verify registration of the nickname: |
| 158 | 167 | ||
| 159 | ```txt | 168 | #+begin_src txt |
| 160 | /msg NickServ VERIFY REGISTER <nick> <verification_code> | 169 | /msg NickServ VERIFY REGISTER <nick> <verification_code> |
| 161 | ``` | 170 | #+end_src |
| 162 | 171 | ||
| 163 | Once entered, the server should confirm registration of the nickname to the | 172 | Once entered, the server should confirm registration of the nickname to |
| 164 | supplied email with the password specified. | 173 | the supplied email with the password specified. |
blog/2023-06-28-backblaze-b2.org +151 −125
| @@ -1,16 +1,16 @@ | |||
| 1 | +++ | 1 | #+title: Getting Started with Backblaze B2 Cloud Storage |
| 2 | date = 2023-06-28T16:00:00 | 2 | #+date: 2023-06-28 |
| 3 | title = "Getting Started with Backblaze B2 Cloud Storage" | ||
| 4 | description = "A simple tutorial to create a Backblaze B2 account for free and back up data from Linux." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Overview | 4 | ** Overview |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: overview | ||
| 7 | :END: | ||
| 8 | Backblaze [[https://www.backblaze.com/b2/cloud-storage.html][B2 Cloud | ||
| 9 | Storage]] is an inexpensive and reliable on-demand cloud storage and | ||
| 10 | backup solution. | ||
| 8 | 11 | ||
| 9 | Backblaze [B2 Cloud Storage](https://www.backblaze.com/b2/cloud-storage.html) is | 12 | The service starts at $5/TB/month ($0.005/GB/month) with a download rate |
| 10 | an inexpensive and reliable on-demand cloud storage and backup solution. | 13 | of $0.01/GB/month. |
| 11 | |||
| 12 | The service starts at $5/TB/month ($0.005/GB/month) with a download rate of | ||
| 13 | $0.01/GB/month. | ||
| 14 | 14 | ||
| 15 | However, there are free tiers: | 15 | However, there are free tiers: |
| 16 | 16 | ||
| @@ -20,162 +20,188 @@ However, there are free tiers: | |||
| 20 | - The first 2500 Class B transactions each day are free. | 20 | - The first 2500 Class B transactions each day are free. |
| 21 | - The first 2500 Class C transactions each day are free. | 21 | - The first 2500 Class C transactions each day are free. |
| 22 | 22 | ||
| 23 | You can see which API calls fall into categories A, B, or C here: | 23 | You can see which API calls fall into categories A, B, or C here: |
| 24 | [Pricing Organized by API | 24 | [[https://www.backblaze.com/b2/b2-transactions-price.html][Pricing |
| 25 | Calls](https://www.backblaze.com/b2/b2-transactions-price.html). | 25 | Organized by API Calls]]. |
| 26 | 26 | ||
| 27 | For someone like me, who wants an offsite backup of their server's `/home/` | 27 | For someone like me, who wants an offsite backup of their server's |
| 28 | directory and various other server configs that fall under 10 GB total, | 28 | =/home/= directory and various other server configs that fall under 10 |
| 29 | Backblaze is a great solution from a financial perspective. | 29 | GB total, Backblaze is a great solution from a financial perspective. |
| 30 | 30 | ||
| 31 | ## Create An Account | 31 | ** Create An Account |
| 32 | 32 | :PROPERTIES: | |
| 33 | To start with Backblaze, you'll need to [create a free | 33 | :CUSTOM_ID: create-an-account |
| 34 | account](https://www.backblaze.com/b2/sign-up.html) - no payment method is | 34 | :END: |
| 35 | required to sign up. | 35 | To start with Backblaze, you'll need to |
| 36 | 36 | [[https://www.backblaze.com/b2/sign-up.html][create a free account]] - | |
| 37 | Once you have an account, you can test out the service with their web GUI, their | 37 | no payment method is required to sign up. |
| 38 | mobile app, or their CLI tool. I'm going to use the CLI tool below to test a | 38 | |
| 39 | file upload and then sync an entire directory to my Backblaze bucket. | 39 | Once you have an account, you can test out the service with their web |
| 40 | 40 | GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool | |
| 41 | ## Create a Bucket | 41 | below to test a file upload and then sync an entire directory to my |
| 42 | 42 | Backblaze bucket. | |
| 43 | Before you can start uploading, you need to create a bucket. If you're familiar | 43 | |
| 44 | with other object storage services, this will feel familiar. If not, it's pretty | 44 | ** Create a Bucket |
| 45 | simple to create one. | 45 | :PROPERTIES: |
| 46 | :CUSTOM_ID: create-a-bucket | ||
| 47 | :END: | ||
| 48 | Before you can start uploading, you need to create a bucket. If you're | ||
| 49 | familiar with other object storage services, this will feel familiar. If | ||
| 50 | not, it's pretty simple to create one. | ||
| 46 | 51 | ||
| 47 | As their webpage says: | 52 | As their webpage says: |
| 48 | 53 | ||
| 49 | > A bucket is a container that holds files that are uploaded into B2 Cloud | 54 | #+begin_quote |
| 50 | > Storage. The bucket name must be globally unique and must have a minimum of 6 | 55 | A bucket is a container that holds files that are uploaded into B2 Cloud |
| 51 | > characters. A limit of 100 buckets may be created per account. An unlimited | 56 | Storage. The bucket name must be globally unique and must have a minimum |
| 52 | > number of files may be uploaded into a bucket. | 57 | of 6 characters. A limit of 100 buckets may be created per account. An |
| 53 | 58 | unlimited number of files may be uploaded into a bucket. | |
| 54 | Once you click the `Create a Bucket` button on their webpage or mobile app, you | ||
| 55 | need to provide the following: | ||
| 56 | |||
| 57 | - Bucket Unique Name | ||
| 58 | - Files in Bucket are: `Private` or `Public` | ||
| 59 | - Default Encryption: `Disable` or `Enable` | ||
| 60 | - Object Lock: `Disable` or `Enable` | ||
| 61 | |||
| 62 | For my bucket, I created a private bucket with encryption enabled and object | ||
| 63 | lock disabled. | ||
| 64 | |||
| 65 | Once your bucket is created, you can test the upload/download feature on their | ||
| 66 | web GUI or mobile app! At this point, you have a fully functional bucket and | ||
| 67 | account. | ||
| 68 | |||
| 69 | ## Linux CLI Tool | ||
| 70 | 59 | ||
| 71 | ### Installation | 60 | #+end_quote |
| 72 | 61 | ||
| 73 | To install the `b2` CLI tool, you'll need to download it from the [CLI | 62 | Once you click the =Create a Bucket= button on their webpage or mobile |
| 74 | Tools](https://www.backblaze.com/docs/cloud-storage-command-line-tools) page. | 63 | app, you need to provide the following: |
| 75 | I recommend copying the URL from the link that says `Linux` and using wget to | ||
| 76 | download it, as shown below. | ||
| 77 | 64 | ||
| 78 | Once downloaded, make the file executable and move it to a location on your | 65 | - Bucket Unique Name |
| 79 | `$PATH`, so that you can execute that command from anywhere on the machine. | 66 | - Files in Bucket are: =Private= or =Public= |
| 80 | 67 | - Default Encryption: =Disable= or =Enable= | |
| 81 | ```sh | 68 | - Object Lock: =Disable= or =Enable= |
| 69 | |||
| 70 | For my bucket, I created a private bucket with encryption enabled and | ||
| 71 | object lock disabled. | ||
| 72 | |||
| 73 | Once your bucket is created, you can test the upload/download feature on | ||
| 74 | their web GUI or mobile app! At this point, you have a fully functional | ||
| 75 | bucket and account. | ||
| 76 | |||
| 77 | ** Linux CLI Tool | ||
| 78 | :PROPERTIES: | ||
| 79 | :CUSTOM_ID: linux-cli-tool | ||
| 80 | :END: | ||
| 81 | *** Installation | ||
| 82 | :PROPERTIES: | ||
| 83 | :CUSTOM_ID: installation | ||
| 84 | :END: | ||
| 85 | To install the =b2= CLI tool, you'll need to download it from the | ||
| 86 | [[https://www.backblaze.com/docs/cloud-storage-command-line-tools][CLI | ||
| 87 | Tools]] page. I recommend copying the URL from the link that says | ||
| 88 | =Linux= and using wget to download it, as shown below. | ||
| 89 | |||
| 90 | Once downloaded, make the file executable and move it to a location on | ||
| 91 | your =$PATH=, so that you can execute that command from anywhere on the | ||
| 92 | machine. | ||
| 93 | |||
| 94 | #+begin_src sh | ||
| 82 | wget <b2_cli_url> | 95 | wget <b2_cli_url> |
| 83 | chmod +x b2_linux | 96 | chmod +x b2_linux |
| 84 | mv b2_linux /usr/bin/b2 | 97 | mv b2_linux /usr/bin/b2 |
| 85 | ``` | 98 | #+end_src |
| 86 | |||
| 87 | ### Log In | ||
| 88 | 99 | ||
| 89 | The first step after installation is to log in. To do this, execute the | 100 | *** Log In |
| 90 | following command and provide your `<applicationKeyId>` and `<applicationKey>`. | 101 | :PROPERTIES: |
| 102 | :CUSTOM_ID: log-in | ||
| 103 | :END: | ||
| 104 | The first step after installation is to log in. To do this, execute the | ||
| 105 | following command and provide your =<applicationKeyId>= and | ||
| 106 | =<applicationKey>=. | ||
| 91 | 107 | ||
| 92 | If you don't want to provide these values in the command itself, you can simply | 108 | If you don't want to provide these values in the command itself, you can |
| 93 | execute the base command and it will request them in an interactive prompt. | 109 | simply execute the base command and it will request them in an |
| 110 | interactive prompt. | ||
| 94 | 111 | ||
| 95 | ```sh | 112 | #+begin_src sh |
| 96 | # if you want to provide the keys directly: | 113 | # if you want to provide the keys directly: |
| 97 | b2 authorize-account [<applicationKeyId>] [<applicationKey>] | 114 | b2 authorize-account [<applicationKeyId>] [<applicationKey>] |
| 98 | 115 | ||
| 99 | # or, if you don't want your keys in your shell history: | 116 | # or, if you don't want your keys in your shell history: |
| 100 | b2 authorize-account | 117 | b2 authorize-account |
| 101 | ``` | 118 | #+end_src |
| 102 | 119 | ||
| 103 | ### Upload a Test File | 120 | *** Upload a Test File |
| 104 | 121 | :PROPERTIES: | |
| 105 | In order to test the functionality of the CLI tool, I'll start by uploading | 122 | :CUSTOM_ID: upload-a-test-file |
| 106 | a single test file to the bucket I created above. | 123 | :END: |
| 107 | We can do this with the `upload_file` function. | 124 | In order to test the functionality of the CLI tool, I'll start by |
| 125 | uploading a single test file to the bucket I created above. We can do | ||
| 126 | this with the =upload_file= function. | ||
| 108 | 127 | ||
| 109 | The command is issued as follows: | 128 | The command is issued as follows: |
| 110 | 129 | ||
| 111 | ```sh | 130 | #+begin_src sh |
| 112 | b2 upload_file <bucket_name> <local_file> <remote_file> | 131 | b2 upload_file <bucket_name> <local_file> <remote_file> |
| 113 | ``` | 132 | #+end_src |
| 114 | 133 | ||
| 115 | In my situation, I executed the following command with my username. | 134 | In my situation, I executed the following command with my username. |
| 116 | 135 | ||
| 117 | ```sh | 136 | #+begin_src sh |
| 118 | b2 upload_file my_unique_bucket /home/<user>/test.md test.md | 137 | b2 upload_file my_unique_bucket /home/<user>/test.md test.md |
| 119 | ``` | 138 | #+end_src |
| 120 | 139 | ||
| 121 | To confirm that the file was uploaded successfully, list the files in your | 140 | To confirm that the file was uploaded successfully, list the files in |
| 122 | bucket: | 141 | your bucket: |
| 123 | 142 | ||
| 124 | ```sh | 143 | #+begin_src sh |
| 125 | b2 ls <bucket_name> | 144 | b2 ls <bucket_name> |
| 126 | ``` | 145 | #+end_src |
| 127 | 146 | ||
| 128 | ```txt | 147 | #+begin_src txt |
| 129 | test.md | 148 | test.md |
| 130 | ``` | 149 | #+end_src |
| 131 | |||
| 132 | ### Sync a Directory | ||
| 133 | 150 | ||
| 134 | If you have numerous files, you can use the `sync` function to perform | 151 | *** Sync a Directory |
| 135 | functionality similar to `rsync`, where you can check what's in your bucket | 152 | :PROPERTIES: |
| 136 | and sync anything that is new or modified. | 153 | :CUSTOM_ID: sync-a-directory |
| 154 | :END: | ||
| 155 | If you have numerous files, you can use the =sync= function to perform | ||
| 156 | functionality similar to =rsync=, where you can check what's in your | ||
| 157 | bucket and sync anything that is new or modified. | ||
| 137 | 158 | ||
| 138 | The command is issued as follows: | 159 | The command is issued as follows: |
| 139 | 160 | ||
| 140 | ```sh | 161 | #+begin_src sh |
| 141 | b2 sync <source file location> <B2 bucket destination> | 162 | b2 sync <source file location> <B2 bucket destination> |
| 142 | ``` | 163 | #+end_src |
| 143 | 164 | ||
| 144 | In my case, I can sync my user's entire home directory to my bucket without | 165 | In my case, I can sync my user's entire home directory to my bucket |
| 145 | specifying any of the files directly: | 166 | without specifying any of the files directly: |
| 146 | 167 | ||
| 147 | ```sh | 168 | #+begin_src sh |
| 148 | b2 sync /home/<user>/ "b2://<bucketName>/home/<user>" | 169 | b2 sync /home/<user>/ "b2://<bucketName>/home/<user>" |
| 149 | ``` | 170 | #+end_src |
| 150 | 171 | ||
| 151 | ## Caveats | 172 | ** Caveats |
| 152 | 173 | :PROPERTIES: | |
| 153 | ### Timing of Updates to the Web GUI | 174 | :CUSTOM_ID: caveats |
| 154 | 175 | :END: | |
| 155 | When performing actions over a bucket, there is a slight delay in the web | 176 | *** Timing of Updates to the Web GUI |
| 156 | GUI when inspecting a bucket or its file. | 177 | :PROPERTIES: |
| 157 | Note that simple actions such as uploading or deleting files may have a | 178 | :CUSTOM_ID: timing-of-updates-to-the-web-gui |
| 158 | delay of a few minutes up to 24 hours. | 179 | :END: |
| 159 | In my experience (<10 GB and ~20,000 files), any actions took only a few | 180 | When performing actions over a bucket, there is a slight delay in the |
| 160 | minutes to update across clients. | 181 | web GUI when inspecting a bucket or its file. Note that simple actions |
| 161 | 182 | such as uploading or deleting files may have a delay of a few minutes up | |
| 162 | ### Symlinks | 183 | to 24 hours. In my experience (<10 GB and ~20,000 files), any actions |
| 163 | 184 | took only a few minutes to update across clients. | |
| 164 | Note that symlinks are resolved by b2, so if you have a link from | 185 | |
| 165 | `/home/<user>/nas-storage` that symlinks out to a `/mnt/nas-storage` folder | 186 | *** Symlinks |
| 166 | that has 10TB of data, `b2` will resolve that link and start uploading all 10TB | 187 | :PROPERTIES: |
| 167 | of data linked within the folder. | 188 | :CUSTOM_ID: symlinks |
| 168 | 189 | :END: | |
| 169 | If you're not sure if you have any symlinks, a symlink will look like this | 190 | Note that symlinks are resolved by b2, so if you have a link from |
| 170 | (note the `->` symbol): | 191 | =/home/<user>/nas-storage= that symlinks out to a =/mnt/nas-storage= |
| 171 | 192 | folder that has 10TB of data, =b2= will resolve that link and start | |
| 172 | ```sh | 193 | uploading all 10TB of data linked within the folder. |
| 194 | |||
| 195 | If you're not sure if you have any symlinks, a symlink will look like | ||
| 196 | this (note the =->= symbol): | ||
| 197 | |||
| 198 | #+begin_src sh | ||
| 173 | > ls -lha | 199 | > ls -lha |
| 174 | lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/ | 200 | lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/ |
| 175 | ``` | 201 | #+end_src |
| 176 | 202 | ||
| 177 | You can recursively find symlink in a path with the following command: | 203 | You can recursively find symlink in a path with the following command: |
| 178 | 204 | ||
| 179 | ```sh | 205 | #+begin_src sh |
| 180 | ls -lR /path/to/search | grep '^l' | 206 | ls -lR /path/to/search | grep '^l' |
| 181 | ``` | 207 | #+end_src |
blog/2023-06-30-self-hosting-voyager-lemmy-client.org +88 −83
| @@ -1,54 +1,57 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting Voyager: A Lemmy Web Client |
| 2 | date = 2023-06-30 | 2 | #+date: 2023-06-30 |
| 3 | title = "Self Hosting Voyager - A Lemmy Web Client" | 3 | |
| 4 | description = "A guide on how to self-host your own instance of Voyager, a mobile-first Lemmy web client." | 4 | ** Installation Guide |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: installation-guide | |
| 7 | ## Installation Guide | 7 | :END: |
| 8 | 8 | [[https://github.com/aeharding/voyager][Voyager]] is a mobile-first | |
| 9 | [Voyager](https://github.com/aeharding/voyager) is a mobile-first Lemmy web | 9 | Lemmy web client, based on iOS design standards. It follows very closely |
| 10 | client, based on iOS design standards. It follows very closely to Apollo's | 10 | to Apollo's design. |
| 11 | design. | 11 | |
| 12 | 12 | This post is a guide showing how I was able to build and launch my own | |
| 13 | This post is a guide showing how I was able to build and launch my own instance | 13 | instance of Voyager via Docker Compose. |
| 14 | of Voyager via Docker Compose. | 14 | |
| 15 | 15 | *** Clone the Repository | |
| 16 | ### Clone the Repository | 16 | :PROPERTIES: |
| 17 | 17 | :CUSTOM_ID: clone-the-repository | |
| 18 | :END: | ||
| 18 | Start by cloning the repository and entering it: | 19 | Start by cloning the repository and entering it: |
| 19 | 20 | ||
| 20 | ```sh | 21 | #+begin_src sh |
| 21 | git clone https://github.com/aeharding/voyager | 22 | git clone https://github.com/aeharding/voyager |
| 22 | cd voyager | 23 | cd voyager |
| 23 | ``` | 24 | #+end_src |
| 24 | |||
| 25 | ### Build the Image | ||
| 26 | 25 | ||
| 27 | With this repository, you can build the image yourself without any further | 26 | *** Build the Image |
| 28 | configuration. | 27 | :PROPERTIES: |
| 29 | When complete, it'll give you the image ID for you to run. | 28 | :CUSTOM_ID: build-the-image |
| 29 | :END: | ||
| 30 | With this repository, you can build the image yourself without any | ||
| 31 | further configuration. When complete, it'll give you the image ID for | ||
| 32 | you to run. | ||
| 30 | 33 | ||
| 31 | ```sh | 34 | #+begin_src sh |
| 32 | sudo docker build . | 35 | sudo docker build . |
| 33 | # Successfully built 5f00723cb5be | 36 | # Successfully built 5f00723cb5be |
| 34 | ``` | 37 | #+end_src |
| 35 | 38 | ||
| 36 | With the image ID above, you can run the container and pass the requested port | 39 | With the image ID above, you can run the container and pass the |
| 37 | `5314` through or use a custom port, if you wish. | 40 | requested port =5314= through or use a custom port, if you wish. |
| 38 | 41 | ||
| 39 | You can also set the `CUSTOM_LEMMY_SERVERS` environment variable if you want to | 42 | You can also set the =CUSTOM_LEMMY_SERVERS= environment variable if you |
| 40 | add to the default suggested login servers. | 43 | want to add to the default suggested login servers. This must be set |
| 41 | This must be set with a comma separated list of suggested servers. | 44 | with a comma separated list of suggested servers. The first instance in |
| 42 | The first instance in the list will be the default view for logged-out users. | 45 | the list will be the default view for logged-out users. |
| 43 | 46 | ||
| 44 | I will be using a `docker-compose.yml` file to run this container, instead of a | 47 | I will be using a =docker-compose.yml= file to run this container, |
| 45 | `docker run` command. | 48 | instead of a =docker run= command. |
| 46 | 49 | ||
| 47 | ```sh | 50 | #+begin_src sh |
| 48 | nano docker-compose.yml | 51 | nano docker-compose.yml |
| 49 | ``` | 52 | #+end_src |
| 50 | 53 | ||
| 51 | ```conf | 54 | #+begin_src conf |
| 52 | version: "2" | 55 | version: "2" |
| 53 | services: | 56 | services: |
| 54 | voyager: | 57 | voyager: |
| @@ -58,67 +61,69 @@ services: | |||
| 58 | - "<custom_port>:5314" | 61 | - "<custom_port>:5314" |
| 59 | environment: | 62 | environment: |
| 60 | - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org | 63 | - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org |
| 61 | ``` | 64 | #+end_src |
| 62 | 65 | ||
| 63 | ```sh | 66 | #+begin_src sh |
| 64 | sudo docker-compose up -d | 67 | sudo docker-compose up -d |
| 65 | ``` | 68 | #+end_src |
| 66 | |||
| 67 | The web app will now be available at the following address: | ||
| 68 | `<machine_ip>:<custom_port>`. | ||
| 69 | If you are running it on your local device, try `localhost:<custom_port>`. | ||
| 70 | 69 | ||
| 71 | ### Reverse Proxy | 70 | The web app will now be available at the following address: |
| 71 | =<machine_ip>:<custom_port>=. If you are running it on your local | ||
| 72 | device, try =localhost:<custom_port>=. | ||
| 72 | 73 | ||
| 73 | If you want to visit this app via an external URL or domain name, you'll need to | 74 | *** Reverse Proxy |
| 74 | set up a reverse proxy. | 75 | :PROPERTIES: |
| 75 | The example below uses Nginx as a reverse proxy. | 76 | :CUSTOM_ID: reverse-proxy |
| 77 | :END: | ||
| 78 | If you want to visit this app via an external URL or domain name, you'll | ||
| 79 | need to set up a reverse proxy. The example below uses Nginx as a | ||
| 80 | reverse proxy. | ||
| 76 | 81 | ||
| 77 | Simply create the configuration file, paste the contents below, save the file, | 82 | Simply create the configuration file, paste the contents below, save the |
| 78 | symlink the file, and restart Nginx. | 83 | file, symlink the file, and restart Nginx. |
| 79 | 84 | ||
| 80 | ```sh | 85 | #+begin_src sh |
| 81 | sudo nano /etc/nginx/sites-available/voyager | 86 | sudo nano /etc/nginx/sites-available/voyager |
| 82 | ``` | 87 | #+end_src |
| 83 | 88 | ||
| 84 | ```conf | 89 | #+begin_src conf |
| 85 | server { | 90 | server { |
| 86 | if ($host ~ ^[^.]+\.example\.com$) { | 91 | if ($host ~ ^[^.]+\.example\.com$) { |
| 87 | return 301 https://$host$request_uri; | 92 | return 301 https://$host$request_uri; |
| 88 | } | 93 | } |
| 89 | 94 | ||
| 90 | listen [::]:80; | 95 | listen [::]:80; |
| 91 | listen 80; | 96 | listen 80; |
| 92 | server_name voyager.example.com; | 97 | server_name voyager.example.com; |
| 93 | return 404; | 98 | return 404; |
| 94 | } | 99 | } |
| 95 | 100 | ||
| 96 | server { | 101 | server { |
| 97 | listen [::]:443 ssl http2; | 102 | listen [::]:443 ssl http2; |
| 98 | listen 443 ssl http2; | 103 | listen 443 ssl http2; |
| 99 | server_name voyager.example.com; | 104 | server_name voyager.example.com; |
| 100 | access_log /var/log/nginx/voyager.access.log; | 105 | access_log /var/log/nginx/voyager.access.log; |
| 101 | error_log /var/log/nginx/voyager.error.log; | 106 | error_log /var/log/nginx/voyager.error.log; |
| 102 | 107 | ||
| 103 | location / { | 108 | location / { |
| 104 | proxy_http_version 1.1; | 109 | proxy_http_version 1.1; |
| 105 | proxy_pass http://localhost:5314; | 110 | proxy_pass http://localhost:5314; |
| 106 | proxy_set_header Host $host; | 111 | proxy_set_header Host $host; |
| 107 | } | 112 | } |
| 108 | 113 | ||
| 109 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; | 114 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; |
| 110 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; | 115 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; |
| 111 | include /etc/letsencrypt/options-ssl-nginx.conf; | 116 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 112 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 117 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 113 | } | 118 | } |
| 114 | ``` | 119 | #+end_src |
| 115 | 120 | ||
| 116 | ```sh | 121 | #+begin_src sh |
| 117 | sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager | 122 | sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager |
| 118 | sudo systemctl restart nginx.service | 123 | sudo systemctl restart nginx.service |
| 119 | ``` | 124 | #+end_src |
| 120 | 125 | ||
| 121 | The site will now be available at the `server_name` you specified above! | 126 | The site will now be available at the =server_name= you specified above! |
| 122 | 127 | ||
| 123 | You can visit my instance at [voyager.cleberg.net](https://voyager.cleberg.net) | 128 | You can visit my instance at |
| 124 | for an example. | 129 | [[https://voyager.cleberg.net][voyager.cleberg.net]] for an example. |
blog/2023-07-12-mullvad-wireguard-lan.org +80 −71
| @@ -1,31 +1,32 @@ | |||
| 1 | +++ | 1 | #+title: Enabling LAN Access in Mullvad Wireguard Conf Files |
| 2 | date = 2023-07-12T19:31:00 | 2 | #+date: 2023-07-12 |
| 3 | title = "Enabling LAN Access in Mullvad Wireguard Conf Files" | 3 | |
| 4 | description = "" | 4 | ** Download Configuration Files from Mullvad |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: download-configuration-files-from-mullvad | |
| 7 | ## Download Configuration Files from Mullvad | 7 | :END: |
| 8 | 8 | To begin, you'll need | |
| 9 | To begin, you'll need [Wireguard configuration files from | 9 | [[https://mullvad.net/account/wireguard-config][Wireguard configuration |
| 10 | Mullvad](https://mullvad.net/account/wireguard-config). | 10 | files from Mullvad]]. You can choose any of the options as you download |
| 11 | You can choose any of the options as you download them. | 11 | them. For example, I enabled the kill switch, selected all countries, |
| 12 | For example, I enabled the kill switch, selected all countries, and selected a | 12 | and selected a few content filters. |
| 13 | few content filters. | 13 | |
| 14 | 14 | Once downloaded, unzip the files and move them to the Wireguard folder | |
| 15 | Once downloaded, unzip the files and move them to the Wireguard folder on your | 15 | on your system. |
| 16 | system. | 16 | |
| 17 | 17 | #+begin_src sh | |
| 18 | ```sh | ||
| 19 | cd ~/Downloads | 18 | cd ~/Downloads |
| 20 | unzip mullvad_wireguard_linux_all_all.zip | 19 | unzip mullvad_wireguard_linux_all_all.zip |
| 21 | doas mv *.conf /etc/wireguard/ | 20 | doas mv *.conf /etc/wireguard/ |
| 22 | ``` | 21 | #+end_src |
| 23 | |||
| 24 | ### Configuration File Layout | ||
| 25 | 22 | ||
| 23 | *** Configuration File Layout | ||
| 24 | :PROPERTIES: | ||
| 25 | :CUSTOM_ID: configuration-file-layout | ||
| 26 | :END: | ||
| 26 | The default configuration files will look something like this: | 27 | The default configuration files will look something like this: |
| 27 | 28 | ||
| 28 | ```conf | 29 | #+begin_src conf |
| 29 | [Interface] | 30 | [Interface] |
| 30 | # Device: <redacted> | 31 | # Device: <redacted> |
| 31 | PrivateKey = <redacted> | 32 | PrivateKey = <redacted> |
| @@ -38,43 +39,50 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad | |||
| 38 | PublicKey = <redacted> | 39 | PublicKey = <redacted> |
| 39 | AllowedIPs = <redacted> | 40 | AllowedIPs = <redacted> |
| 40 | Endpoint = <redacted> | 41 | Endpoint = <redacted> |
| 41 | ``` | 42 | #+end_src |
| 42 | 43 | ||
| 43 | > Note: If you didn't select the kill switch option, you won't see the `PostUp` | 44 | #+begin_quote |
| 44 | > and `PreDown` lines. | 45 | Note: If you didn't select the kill switch option, you won't see the |
| 45 | > In this case, you'll need to modify the script below to simply append those | 46 | =PostUp= and =PreDown= lines. In this case, you'll need to modify the |
| 46 | > lines to the `[Interface]` block. | 47 | script below to simply append those lines to the =[Interface]= block. |
| 47 | 48 | ||
| 48 | ## Editing the Configuration Files | 49 | #+end_quote |
| 49 | 50 | ||
| 50 | Once you have the files, you'll need to edit them and replace the `PostUp` and | 51 | ** Editing the Configuration Files |
| 51 | `PreDown` lines to enable LAN access. | 52 | :PROPERTIES: |
| 53 | :CUSTOM_ID: editing-the-configuration-files | ||
| 54 | :END: | ||
| 55 | Once you have the files, you'll need to edit them and replace the | ||
| 56 | =PostUp= and =PreDown= lines to enable LAN access. | ||
| 52 | 57 | ||
| 53 | I recommend that you do this process as root, since you'll need to be able to | 58 | I recommend that you do this process as root, since you'll need to be |
| 54 | access files in `/etc/wireguard`, which are generally owned by root. | 59 | able to access files in =/etc/wireguard=, which are generally owned by |
| 55 | You can also try using `sudo` or `doas`, but I didn't test that scenario so you | 60 | root. You can also try using =sudo= or =doas=, but I didn't test that |
| 56 | may need to adjust, as necessary. | 61 | scenario so you may need to adjust, as necessary. |
| 57 | 62 | ||
| 58 | ```sh | 63 | #+begin_src sh |
| 59 | su | 64 | su |
| 60 | ``` | 65 | #+end_src |
| 61 | 66 | ||
| 62 | Create the Python file that we'll be using to update the Wireguard configuration | 67 | Create the Python file that we'll be using to update the Wireguard |
| 63 | files. | 68 | configuration files. |
| 64 | 69 | ||
| 65 | ```sh | 70 | #+begin_src sh |
| 66 | nano replace.py | 71 | nano replace.py |
| 67 | ``` | 72 | #+end_src |
| 68 | 73 | ||
| 69 | Within the Python file, copy and paste the logic below. | 74 | Within the Python file, copy and paste the logic below. This script will |
| 70 | This script will open a directory, loop through every configuration file within | 75 | open a directory, loop through every configuration file within the |
| 71 | the directory, and replace the `PostUp` and `PreDown` lines with the new | 76 | directory, and replace the =PostUp= and =PreDown= lines with the new |
| 72 | LAN-enabled iptables commands. | 77 | LAN-enabled iptables commands. |
| 73 | 78 | ||
| 74 | > Note: If your LAN is on a subnet other than `192.168.1.0/24`, you'll need to | 79 | #+begin_quote |
| 75 | > update the Python script below appropriately. | 80 | Note: If your LAN is on a subnet other than =192.168.1.0/24=, you'll |
| 81 | need to update the Python script below appropriately. | ||
| 82 | |||
| 83 | #+end_quote | ||
| 76 | 84 | ||
| 77 | ```python | 85 | #+begin_src python |
| 78 | import os | 86 | import os |
| 79 | import fileinput | 87 | import fileinput |
| 80 | 88 | ||
| @@ -83,35 +91,35 @@ print("--- starting ---") | |||
| 83 | dir = "/etc/wireguard/" | 91 | dir = "/etc/wireguard/" |
| 84 | 92 | ||
| 85 | for file in os.listdir(dir): | 93 | for file in os.listdir(dir): |
| 86 | print(os.path.join(dir, file)) | 94 | print(os.path.join(dir, file)) |
| 87 | for line in fileinput.input(os.path.join(dir, file), inplace=True): | 95 | for line in fileinput.input(os.path.join(dir, file), inplace=True): |
| 88 | if "PostUp" in line: | 96 | if "PostUp" in line: |
| 89 | print("PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL ! -d 192.168.1.0/24 -j REJECT && ip6tables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT") | 97 | print("PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL ! -d 192.168.1.0/24 -j REJECT && ip6tables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT") |
| 90 | elif "PreDown" in line: | 98 | elif "PreDown" in line: |
| 91 | print("PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL ! -d 192.168.1.0/24 -j REJECT && ip6tables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT") | 99 | print("PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL ! -d 192.168.1.0/24 -j REJECT && ip6tables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT") |
| 92 | else: | 100 | else: |
| 93 | print(line, end="") | 101 | print(line, end="") |
| 94 | 102 | ||
| 95 | print("--- done ---") | 103 | print("--- done ---") |
| 96 | ``` | 104 | #+end_src |
| 97 | 105 | ||
| 98 | Once you're done, save and close the file. You can now run the Python script | 106 | Once you're done, save and close the file. You can now run the Python |
| 99 | and watch as each file is updated. | 107 | script and watch as each file is updated. |
| 100 | 108 | ||
| 101 | ```sh | 109 | #+begin_src sh |
| 102 | python3 replace.py | 110 | python3 replace.py |
| 103 | ``` | 111 | #+end_src |
| 104 | 112 | ||
| 105 | To confirm it worked, you can `cat` one of the configuration files to inspect | 113 | To confirm it worked, you can =cat= one of the configuration files to |
| 106 | the new logic and connect to one to test it out. | 114 | inspect the new logic and connect to one to test it out. |
| 107 | 115 | ||
| 108 | ```sh | 116 | #+begin_src sh |
| 109 | cat /etc/wireguard/us-chi-wg-001.conf | 117 | cat /etc/wireguard/us-chi-wg-001.conf |
| 110 | ``` | 118 | #+end_src |
| 111 | 119 | ||
| 112 | The configuration files should now look like this: | 120 | The configuration files should now look like this: |
| 113 | 121 | ||
| 114 | ```conf | 122 | #+begin_src conf |
| 115 | [Interface] | 123 | [Interface] |
| 116 | # Device: <redacted> | 124 | # Device: <redacted> |
| 117 | PrivateKey = <redacted> | 125 | PrivateKey = <redacted> |
| @@ -124,19 +132,20 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad | |||
| 124 | PublicKey = <redacted> | 132 | PublicKey = <redacted> |
| 125 | AllowedIPs = <redacted> | 133 | AllowedIPs = <redacted> |
| 126 | Endpoint = <redacted> | 134 | Endpoint = <redacted> |
| 127 | ``` | 135 | #+end_src |
| 128 | 136 | ||
| 129 | If you connect to a Wireguard interface, such as `us-chi-wg-001`, you can test | 137 | If you connect to a Wireguard interface, such as =us-chi-wg-001=, you |
| 130 | your SSH functionality and see that it works even while on the VPN. | 138 | can test your SSH functionality and see that it works even while on the |
| 139 | VPN. | ||
| 131 | 140 | ||
| 132 | ```sh | 141 | #+begin_src sh |
| 133 | wg-quick up us-chi-wg-001 | 142 | wg-quick up us-chi-wg-001 |
| 134 | ssh user@lan-host | 143 | ssh user@lan-host |
| 135 | ``` | 144 | #+end_src |
| 136 | 145 | ||
| 137 | To confirm your VPN connection, you can curl Mullvad's connection API: | 146 | To confirm your VPN connection, you can curl Mullvad's connection API: |
| 138 | 147 | ||
| 139 | ```sh | 148 | #+begin_src sh |
| 140 | curl https://am.i.mullvad.net/connected | 149 | curl https://am.i.mullvad.net/connected |
| 141 | # You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted> | 150 | # You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted> |
| 142 | ``` | 151 | #+end_src |
blog/2023-07-19-plex-transcoder-errors.org +47 −41
| @@ -1,62 +1,68 @@ | |||
| 1 | +++ | 1 | #+title: How to Fix Plex Error: 'Conversion failed. The transcoder failed to start up.' |
| 2 | date = 2023-07-19T17:00:00 | 2 | #+date: 2023-07-19 |
| 3 | title = "How to Fix Plex Error: 'Conversion failed. The transcoder failed to start up.'" | ||
| 4 | description = "Read to learn how to fix a common Plex error where the transcoder crashes due to SRT subtitle files." | ||
| 5 | +++ | ||
| 6 | |||
| 7 | ## Plex Transcoder Error | ||
| 8 | 3 | ||
| 4 | ** Plex Transcoder Error | ||
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: plex-transcoder-error | ||
| 7 | :END: | ||
| 9 | Occasionally, you may see an error in your Plex client that references a | 8 | Occasionally, you may see an error in your Plex client that references a |
| 10 | failure with the transcoder conversion process. | 9 | failure with the transcoder conversion process. The specific error |
| 11 | The specific error wording is: | 10 | wording is: |
| 12 | 11 | ||
| 13 | ```txt | 12 | #+begin_src txt |
| 14 | Conversion failed. The transcoder failed to start up. | 13 | Conversion failed. The transcoder failed to start up. |
| 15 | ``` | 14 | #+end_src |
| 16 | |||
| 17 | ## Debugging the Cause | ||
| 18 | 15 | ||
| 19 | In order to get a better look at what is causing the error, I'm going to observe | 16 | ** Debugging the Cause |
| 20 | the Plex console while the error occurs. | 17 | :PROPERTIES: |
| 21 | To do this, open the Plex web client, go to `Settings` > `Manage` > `Console`. | 18 | :CUSTOM_ID: debugging-the-cause |
| 22 | Now, try to play the title again and watch to see which errors occur. | 19 | :END: |
| 20 | In order to get a better look at what is causing the error, I'm going to | ||
| 21 | observe the Plex console while the error occurs. To do this, open the | ||
| 22 | Plex web client, go to =Settings= > =Manage= > =Console=. Now, try to | ||
| 23 | play the title again and watch to see which errors occur. | ||
| 23 | 24 | ||
| 24 | In my case, you can see the errors below are related to a subtitle file (`.srt`) | 25 | In my case, you can see the errors below are related to a subtitle file |
| 25 | causing the transcoder to crash. | 26 | (=.srt=) causing the transcoder to crash. |
| 26 | 27 | ||
| 27 | ```txt | 28 | #+begin_src txt |
| 28 | Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt | 29 | Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt |
| 29 | Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file | 30 | Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file |
| 30 | Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID. | 31 | Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID. |
| 31 | Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available | 32 | Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available |
| 32 | Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt" | 33 | Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt" |
| 33 | ``` | 34 | #+end_src |
| 34 | 35 | ||
| 35 | ## Solving the Error | 36 | ** Solving the Error |
| 37 | :PROPERTIES: | ||
| 38 | :CUSTOM_ID: solving-the-error | ||
| 39 | :END: | ||
| 40 | In my case, I simply removed the subtitle file because it was not | ||
| 41 | critical to keep. You may also avoid this by turning off subtitles if | ||
| 42 | you don't want to delete the file. | ||
| 36 | 43 | ||
| 37 | In my case, I simply removed the subtitle file because it was not critical to | 44 | #+begin_src sh |
| 38 | keep. | ||
| 39 | You may also avoid this by turning off subtitles if you don't want to delete the | ||
| 40 | file. | ||
| 41 | |||
| 42 | ```sh | ||
| 43 | rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt" | 45 | rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt" |
| 44 | ``` | 46 | #+end_src |
| 45 | 47 | ||
| 46 | Once the subtitle is removed from the directory or subtitles are turned off, | 48 | Once the subtitle is removed from the directory or subtitles are turned |
| 47 | try to play the title again. | 49 | off, try to play the title again. At this point, it should play without |
| 48 | At this point, it should play without error. | 50 | error. If not, reopen or refresh your Plex client and the Plex server: |
| 49 | If not, reopen or refresh your Plex client and the Plex server: | ||
| 50 | 51 | ||
| 51 | ```sh | 52 | #+begin_src sh |
| 52 | sudo systemctl restart plexmediaserver.service | 53 | sudo systemctl restart plexmediaserver.service |
| 53 | ``` | 54 | #+end_src |
| 54 | |||
| 55 | ## Related Discussion | ||
| 56 | 55 | ||
| 57 | Looking at [a related Plex forum post](https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2), | 56 | ** Related Discussion |
| 58 | it seems that `.srt` subtitles are the core issue here. | 57 | :PROPERTIES: |
| 59 | However, Plex does not seem to have a resolution that fixes these errors. | 58 | :CUSTOM_ID: related-discussion |
| 59 | :END: | ||
| 60 | Looking at | ||
| 61 | [[https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2][a | ||
| 62 | related Plex forum post]], it seems that =.srt= subtitles are the core | ||
| 63 | issue here. However, Plex does not seem to have a resolution that fixes | ||
| 64 | these errors. | ||
| 60 | 65 | ||
| 61 | Unfortunately, I would suggest converting subtitle formats, burning the | 66 | Unfortunately, I would suggest converting subtitle formats, burning the |
| 62 | subtitles into the title, or simply removing subtitles when they cause crashes. | 67 | subtitles into the title, or simply removing subtitles when they cause |
| 68 | crashes. | ||
blog/2023-08-18-agile-auditing.org +142 −122
| @@ -1,152 +1,172 @@ | |||
| 1 | +++ | 1 | #+title: Agile Auditing: An Introduction |
| 2 | date = 2023-08-18T17:11:38+00:00 | 2 | #+date: 2023-08-18 |
| 3 | title = "Agile Auditing: An Introduction" | 3 | |
| 4 | description = "A quick introduction to Agile, Scrum, and Kanban for audit engagement teams." | 4 | ** What is Agile Auditing? |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: what-is-agile-auditing | |
| 7 | ## What is Agile Auditing? | 7 | :END: |
| 8 | 8 | [[https://en.wikipedia.org/wiki/Agile_software_development][Agile]], the | |
| 9 | [Agile](https://en.wikipedia.org/wiki/Agile_software_development), the | 9 | collaborative philosophy behind many software development methods, has |
| 10 | collaborative philosophy behind many software development methods, has been | 10 | been picking up steam as a beneficial tool to use in the external and |
| 11 | picking up steam as a beneficial tool to use in the external and internal | 11 | internal auditing world. |
| 12 | auditing world. | 12 | |
| 13 | 13 | This blog post will walk through commonly used terms within Agile, | |
| 14 | This blog post will walk through commonly used terms within Agile, Scrum, | 14 | Scrum, and Kanban in order to translate these terms and roles into |
| 15 | and Kanban in order to translate these terms and roles into audit-specific | 15 | audit-specific terms. |
| 16 | terms. | ||
| 17 | 16 | ||
| 18 | Whether your team is in charge of a financial statement audit, an | 17 | Whether your team is in charge of a financial statement audit, an |
| 19 | attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms used | 18 | attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms |
| 20 | throughout this post should still apply. | 19 | used throughout this post should still apply. |
| 21 | |||
| 22 | ## Agile | ||
| 23 | 20 | ||
| 21 | ** Agile | ||
| 22 | :PROPERTIES: | ||
| 23 | :CUSTOM_ID: agile | ||
| 24 | :END: | ||
| 24 | To start, I'll take a look at Agile. | 25 | To start, I'll take a look at Agile. |
| 25 | 26 | ||
| 26 | > The Agile methodology is a project management approach that involves | 27 | #+begin_quote |
| 27 | > breaking the project into phases and emphasizes continuous collaboration | 28 | The Agile methodology is a project management approach that involves |
| 28 | > and improvement. Teams follow a cycle of planning, executing, and evaluating. | 29 | breaking the project into phases and emphasizes continuous collaboration |
| 30 | and improvement. Teams follow a cycle of planning, executing, and | ||
| 31 | evaluating. | ||
| 29 | 32 | ||
| 30 | While this approach may seem familiar to what audit teams have historically | 33 | #+end_quote |
| 31 | done, an audit team must make distinct changes in their mentality and how | ||
| 32 | they approach and manage a project. | ||
| 33 | 34 | ||
| 34 | ### Agile Values | 35 | While this approach may seem familiar to what audit teams have |
| 36 | historically done, an audit team must make distinct changes in their | ||
| 37 | mentality and how they approach and manage a project. | ||
| 35 | 38 | ||
| 36 | The Agile Manifesto, written in 2001 at a summit in Utah, contain a set of four | 39 | *** Agile Values |
| 37 | main values that comprise the Agile approach: | 40 | :PROPERTIES: |
| 41 | :CUSTOM_ID: agile-values | ||
| 42 | :END: | ||
| 43 | The Agile Manifesto, written in 2001 at a summit in Utah, contain a set | ||
| 44 | of four main values that comprise the Agile approach: | ||
| 38 | 45 | ||
| 39 | 1. Individuals and interactions over processes and tools. | 46 | 1. Individuals and interactions over processes and tools. |
| 40 | 2. Working software over comprehensive documentation. | 47 | 2. Working software over comprehensive documentation. |
| 41 | 3. Customer collaboration over contract negotiation. | 48 | 3. Customer collaboration over contract negotiation. |
| 42 | 4. Responding to change over following a plan. | 49 | 4. Responding to change over following a plan. |
| 43 | 50 | ||
| 44 | Beyond the four values, [twelve | 51 | Beyond the four values, |
| 45 | principles](https://agilemanifesto.org/principles.html) were also written as | 52 | [[https://agilemanifesto.org/principles.html][twelve principles]] were |
| 46 | part of the summit. | 53 | also written as part of the summit. |
| 47 | 54 | ||
| 48 | In order to relate these values to an audit or attestation engagement, we | 55 | In order to relate these values to an audit or attestation engagement, |
| 49 | need to shift the focus from software development to the main goal of an | 56 | we need to shift the focus from software development to the main goal of |
| 50 | engagement: completing sufficient audit testing to address to relevant risks | 57 | an engagement: completing sufficient audit testing to address to |
| 51 | over the processes and controls at hand. | 58 | relevant risks over the processes and controls at hand. |
| 52 | 59 | ||
| 53 | Audit Examples: | 60 | Audit Examples: |
| 54 | 61 | ||
| 55 | - Engagement teams must value the team members, client contacts, and their | 62 | - Engagement teams must value the team members, client contacts, and |
| 56 | interactions over the historical processes and tools that have been used. | 63 | their interactions over the historical processes and tools that have |
| 57 | - Engagement teams must value a final report that contains sufficient | 64 | been used. |
| 65 | - Engagement teams must value a final report that contains sufficient | ||
| 58 | audit documentation over excessive documentation or scope creep. | 66 | audit documentation over excessive documentation or scope creep. |
| 59 | - Engagement teams must collaborate with the audit clients as much as | 67 | - Engagement teams must collaborate with the audit clients as much as |
| 60 | feasible to ensure that both sides are constantly updated with current | 68 | feasible to ensure that both sides are constantly updated with current |
| 61 | knowledge of the engagement's status and any potential findings, rather | 69 | knowledge of the engagement's status and any potential findings, |
| 62 | than waiting for pre-set meetings or the end of the engagement to communicate. | 70 | rather than waiting for pre-set meetings or the end of the engagement |
| 63 | - Engagement teams must be able to respond to change in an engagement's | 71 | to communicate. |
| 64 | schedule, scope, or environment to ensure that the project is completed in | 72 | - Engagement teams must be able to respond to change in an engagement's |
| 65 | a timely manner and that all relevant areas are tested. | 73 | schedule, scope, or environment to ensure that the project is |
| 66 | - In terms of an audit department's portfolio, they must be able to | 74 | completed in a timely manner and that all relevant areas are tested. |
| 67 | respond to changes in their company's or client's environment and be | 75 | - In terms of an audit department's portfolio, they must be able to |
| 68 | able to dynamically change their audit plan accordingly. | 76 | respond to changes in their company's or client's environment and be |
| 69 | 77 | able to dynamically change their audit plan accordingly. | |
| 70 | ## Scrum | 78 | |
| 71 | 79 | ** Scrum | |
| 72 | The above section discusses the high-level details of the Agile philosophy | 80 | :PROPERTIES: |
| 73 | and how an audit team can potentially mold that mindset into the audit world, | 81 | :CUSTOM_ID: scrum |
| 74 | but how does a team implement these ideas? | 82 | :END: |
| 83 | The above section discusses the high-level details of the Agile | ||
| 84 | philosophy and how an audit team can potentially mold that mindset into | ||
| 85 | the audit world, but how does a team implement these ideas? | ||
| 75 | 86 | ||
| 76 | There are many methods that use an Agile mindset, but I prefer | 87 | There are many methods that use an Agile mindset, but I prefer |
| 77 | [Scrum](https://en.wikipedia.org/wiki/Scrum_(software_development)). Scrum | 88 | [[https://en.wikipedia.org/wiki/Scrum_(software_development)][Scrum]]. |
| 78 | is a framework based on Agile that enables a team to work through a project | 89 | Scrum is a framework based on Agile that enables a team to work through |
| 79 | through a series of roles, ceremonies, artifacts, and values. | 90 | a project through a series of roles, ceremonies, artifacts, and values. |
| 80 | 91 | ||
| 81 | Let's dive into each of these individually. | 92 | Let's dive into each of these individually. |
| 82 | 93 | ||
| 83 | ### Scrum Team | 94 | *** Scrum Team |
| 84 | 95 | :PROPERTIES: | |
| 85 | A scrum project is only as good as the team running the project. Standard | 96 | :CUSTOM_ID: scrum-team |
| 86 | scrum teams are separated into three distinct areas: | 97 | :END: |
| 87 | 98 | A scrum project is only as good as the team running the project. | |
| 88 | 1. **Product Owner (Client Contact)**: The client contact is the audit | 99 | Standard scrum teams are separated into three distinct areas: |
| 89 | equivalent of the product owner in Scrum. They are responsible for | 100 | |
| 90 | partnering with the engagement or audit team to ensure progress is being | 101 | 1. *Product Owner (Client Contact)*: The client contact is the audit |
| 91 | made, priorities are established, and clear guidance is given when | 102 | equivalent of the product owner in Scrum. They are responsible for |
| 92 | questions or findings arise within each sprint. | 103 | partnering with the engagement or audit team to ensure progress is |
| 93 | 2. **Scrum Master (Engagement Lead)**: The engagement or audit team lead is | 104 | being made, priorities are established, and clear guidance is given |
| 94 | responsible for coaching the team and the client contact on the scrum | 105 | when questions or findings arise within each sprint. |
| 95 | process, tracking team progress against plan, scheduling necessary | 106 | 2. *Scrum Master (Engagement Lead)*: The engagement or audit team lead |
| 96 | resources, and helping remove obstacles. | 107 | is responsible for coaching the team and the client contact on the |
| 97 | 3. **Scrum Developers (Engagement Members)**: The engagement or audit team | 108 | scrum process, tracking team progress against plan, scheduling |
| 98 | is the set of team members responsible for getting the work done. These | 109 | necessary resources, and helping remove obstacles. |
| 99 | team members will work on each task, report progress, resolve obstacles, | 110 | 3. *Scrum Developers (Engagement Members)*: The engagement or audit team |
| 100 | and collaborate with other team members and the client contact to ensure | 111 | is the set of team members responsible for getting the work done. |
| 101 | goals are being met. | 112 | These team members will work on each task, report progress, resolve |
| 102 | 113 | obstacles, and collaborate with other team members and the client | |
| 103 | ### Scrum Ceremonies | 114 | contact to ensure goals are being met. |
| 104 | 115 | ||
| 116 | *** Scrum Ceremonies | ||
| 117 | :PROPERTIES: | ||
| 118 | :CUSTOM_ID: scrum-ceremonies | ||
| 119 | :END: | ||
| 105 | Scrum ceremonies are events that are performed on a regular basis. | 120 | Scrum ceremonies are events that are performed on a regular basis. |
| 106 | 121 | ||
| 107 | 1. **Sprint Planning**: The team works together to plan the upcoming sprint | 122 | 1. *Sprint Planning*: The team works together to plan the upcoming |
| 108 | goal | 123 | sprint goal and which user stories (tasks) will be added to the |
| 109 | and which user stories (tasks) will be added to the sprint to achieve | 124 | sprint to achieve that goal. |
| 110 | that goal. | 125 | 2. *Sprint*: The time period, typically at least one week and no more |
| 111 | 2. **Sprint**: The time period, typically at least one week and no more than one | 126 | than one month in length, where the team works on the stories and |
| 112 | month in length, where the team works on the stories and anything in the | 127 | anything in the backlog. |
| 113 | backlog. | 128 | 3. *Daily Scrum*: A very short meeting held each day, typically 15 |
| 114 | 3. **Daily Scrum**: A very short meeting held each day, typically 15 minutes, to | 129 | minutes, to quickly emphasize alignment on the sprint goal and plan |
| 115 | quickly emphasize alignment on the sprint goal and plan the next 24 hours. | 130 | the next 24 hours. Each team member may share what they did the day |
| 116 | Each team member may share what they did the day before, what they'll do | 131 | before, what they'll do today, and any obstacles to their work. |
| 117 | today, and any obstacles to their work. | 132 | 4. *Sprint Review*: At the end of each sprint, the team will gather and |
| 118 | 4. **Sprint Review**: At the end of each sprint, the team will gather and | 133 | discuss the progress, obstacles, and backlog from the previous |
| 119 | discuss the progress, obstacles, and backlog from the previous sprint. | 134 | sprint. |
| 120 | 5. **Sprint Retrospective**: More specific than the sprint review, the | 135 | 5. *Sprint Retrospective*: More specific than the sprint review, the |
| 121 | retrospective is meant to discuss what worked and what did not work | 136 | retrospective is meant to discuss what worked and what did not work |
| 122 | during the sprint. This may be processes, tools, people, or even things | 137 | during the sprint. This may be processes, tools, people, or even |
| 123 | related to the Scrum ceremonies. | 138 | things related to the Scrum ceremonies. |
| 124 | 139 | ||
| 125 | One additional ceremony that may be applicable is organizing the backlog. | 140 | One additional ceremony that may be applicable is organizing the |
| 126 | This is typically the responsibility of the engagement leader and is meant | 141 | backlog. This is typically the responsibility of the engagement leader |
| 127 | to prioritize and clarify what needs to be done to complete items in the | 142 | and is meant to prioritize and clarify what needs to be done to complete |
| 128 | backlog. | 143 | items in the backlog. |
| 129 | 144 | ||
| 130 | ### Artifacts | 145 | *** Artifacts |
| 131 | 146 | :PROPERTIES: | |
| 132 | While artifacts are generally not customizable in the audit world (i.e., | 147 | :CUSTOM_ID: artifacts |
| 133 | each control test must include some kind of working paper with evidence | 148 | :END: |
| 134 | supporting the test results), I wanted to include some quick notes on | 149 | While artifacts are generally not customizable in the audit world (i.e., |
| 150 | each control test must include some kind of working paper with evidence | ||
| 151 | supporting the test results), I wanted to include some quick notes on | ||
| 135 | associating scrum artifact terms with an audit. | 152 | associating scrum artifact terms with an audit. |
| 136 | 153 | ||
| 137 | 1. **Product Backlog**: This is the overall backlog of unfinished audit | 154 | 1. *Product Backlog*: This is the overall backlog of unfinished audit |
| 138 | tasks from all prior sprints. | 155 | tasks from all prior sprints. |
| 139 | 2. **Sprint Backlog**: This is the backlog of unfinished audit tasks from | 156 | 2. *Sprint Backlog*: This is the backlog of unfinished audit tasks from |
| 140 | one individual sprint. | 157 | one individual sprint. |
| 141 | 3. **Increment**: This is the output of each sprint - generally this is best | 158 | 3. *Increment*: This is the output of each sprint - generally this is |
| 142 | thought of as any documentation prepared during the sprint, such as risk | 159 | best thought of as any documentation prepared during the sprint, such |
| 143 | assessments, control working papers, deficiency analysis, etc. | 160 | as risk assessments, control working papers, deficiency analysis, |
| 144 | 161 | etc. | |
| 145 | ## Kanban | 162 | |
| 146 | 163 | ** Kanban | |
| 147 | Last but not least, Kanban is a methodology that relies on boards to | 164 | :PROPERTIES: |
| 148 | categorize work into distinct, descriptive categories that allow an agile or | 165 | :CUSTOM_ID: kanban |
| 149 | scrum team to effectively plan the work of a sprint or project. | 166 | :END: |
| 150 | 167 | Last but not least, Kanban is a methodology that relies on boards to | |
| 151 | See Atlassian's [Kanban](https://www.atlassian.com/agile/kanban) page for | 168 | categorize work into distinct, descriptive categories that allow an |
| 152 | more information. | 169 | agile or scrum team to effectively plan the work of a sprint or project. |
| 170 | |||
| 171 | See Atlassian's [[https://www.atlassian.com/agile/kanban][Kanban]] page | ||
| 172 | for more information. | ||
blog/2023-09-15-gitweb.org +42 −34
| @@ -1,39 +1,44 @@ | |||
| 1 | +++ | 1 | #+title: GitWeb via Nginx |
| 2 | date = 2023-09-16T03:10:37+00:00 | 2 | #+date: 2023-09-16 |
| 3 | title = "GitWeb via Nginx" | ||
| 4 | description = "A short guide on how to serve GitWeb via Nginx." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Overview | 4 | ** Overview |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: overview | ||
| 7 | :END: | ||
| 8 | [[https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb][GitWeb]] is a | ||
| 9 | simple web-based visualizer for git repositories. By default, GitWeb | ||
| 10 | will only run with the =lighttpd= or =webrick= web servers. | ||
| 8 | 11 | ||
| 9 | [GitWeb](https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb) is a simple | 12 | However, this guide will show you how to keep GitWeb running in the |
| 10 | web-based visualizer for git repositories. | 13 | background and display information for all repositories in a chosen |
| 11 | By default, GitWeb will only run with the `lighttpd` or `webrick` web servers. | 14 | directory. |
| 12 | |||
| 13 | However, this guide will show you how to keep GitWeb running in the background | ||
| 14 | and display information for all repositories in a chosen directory. | ||
| 15 | 15 | ||
| 16 | See below for the final result: | 16 | See below for the final result: |
| 17 | 17 | ||
| 18 |  | 18 | #+caption: Gitweb |
| 19 | 19 | [[https://img.cleberg.net/blog/20230915-gitweb/gitweb.png]] | |
| 20 | ## Install Dependencies | ||
| 21 | 20 | ||
| 21 | ** Install Dependencies | ||
| 22 | :PROPERTIES: | ||
| 23 | :CUSTOM_ID: install-dependencies | ||
| 24 | :END: | ||
| 22 | To start, you'll need install the following packages: | 25 | To start, you'll need install the following packages: |
| 23 | 26 | ||
| 24 | ```sh | 27 | #+begin_src sh |
| 25 | sudo apt install git gitweb fcgiwrap nginx | 28 | sudo apt install git gitweb fcgiwrap nginx |
| 26 | ``` | 29 | #+end_src |
| 27 | |||
| 28 | ## Configure Nginx | ||
| 29 | 30 | ||
| 31 | ** Configure Nginx | ||
| 32 | :PROPERTIES: | ||
| 33 | :CUSTOM_ID: configure-nginx | ||
| 34 | :END: | ||
| 30 | Once installed, create an Nginx configuration file. | 35 | Once installed, create an Nginx configuration file. |
| 31 | 36 | ||
| 32 | ```sh | 37 | #+begin_src sh |
| 33 | sudo nano /etc/nginx/sites-available/git.example.com | 38 | sudo nano /etc/nginx/sites-available/git.example.com |
| 34 | ``` | 39 | #+end_src |
| 35 | 40 | ||
| 36 | ```conf | 41 | #+begin_src conf |
| 37 | server { | 42 | server { |
| 38 | listen 80; | 43 | listen 80; |
| 39 | server_name example.com; | 44 | server_name example.com; |
| @@ -52,23 +57,26 @@ server { | |||
| 52 | index index.cgi; | 57 | index index.cgi; |
| 53 | } | 58 | } |
| 54 | } | 59 | } |
| 55 | ``` | 60 | #+end_src |
| 56 | 61 | ||
| 57 | To make the configuration active, you need to symlink it and then restart Nginx. | 62 | To make the configuration active, you need to symlink it and then |
| 63 | restart Nginx. | ||
| 58 | 64 | ||
| 59 | ```sh | 65 | #+begin_src sh |
| 60 | sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com | 66 | sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com |
| 61 | sudo systemctl restart nginx.service | 67 | sudo systemctl restart nginx.service |
| 62 | ``` | 68 | #+end_src |
| 63 | |||
| 64 | The GitWeb application should now be available via the URL you set in the Nginx | ||
| 65 | configuration above. | ||
| 66 | 69 | ||
| 67 | ## Customize GitWeb | 70 | The GitWeb application should now be available via the URL you set in |
| 71 | the Nginx configuration above. | ||
| 68 | 72 | ||
| 69 | If you need to, you can customize many things about Gitweb by editing the | 73 | ** Customize GitWeb |
| 70 | [gitweb.conf](https://git-scm.com/docs/gitweb.conf) file. | 74 | :PROPERTIES: |
| 75 | :CUSTOM_ID: customize-gitweb | ||
| 76 | :END: | ||
| 77 | If you need to, you can customize many things about Gitweb by editing | ||
| 78 | the [[https://git-scm.com/docs/gitweb.conf][gitweb.conf]] file. | ||
| 71 | 79 | ||
| 72 | ```sh | 80 | #+begin_src sh |
| 73 | sudo nano /etc/gitweb.conf | 81 | sudo nano /etc/gitweb.conf |
| 74 | ``` | 82 | #+end_src |
blog/2023-09-19-audit-sql-scripts.org +76 −64
| @@ -1,23 +1,25 @@ | |||
| 1 | +++ | 1 | #+title: Useful SQL Scripts for Auditing Logical Access |
| 2 | date = 2023-09-20T01:12:51+00:00 | 2 | #+date: 2023-09-20 |
| 3 | title = "Useful SQL Scripts for Auditing Logical Access" | ||
| 4 | description = "A handful of SQL scripts in different dialects to gather user permissions on a database." | ||
| 5 | +++ | ||
| 6 | 3 | ||
| 7 | ## Overview | 4 | ** Overview |
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: overview | ||
| 7 | :END: | ||
| 8 | When you have to scope a database into your engagement, you may be | ||
| 9 | curious how to best extract the information from the database. While | ||
| 10 | there are numerous different methods to extract this type of | ||
| 11 | information, I'm going to show an example of how to gather all users and | ||
| 12 | privileges from three main database types: Oracle, Microsoft SQL, and | ||
| 13 | MySQL. | ||
| 8 | 14 | ||
| 9 | When you have to scope a database into your engagement, you may be curious | 15 | ** Oracle |
| 10 | how to best extract the information from the database. While there are | 16 | :PROPERTIES: |
| 11 | numerous different methods to extract this type of information, I'm going to | 17 | :CUSTOM_ID: oracle |
| 12 | show an example of how to gather all users and privileges from three main | 18 | :END: |
| 13 | database types: Oracle, Microsoft SQL, and MySQL. | 19 | You can use the following SQL script to see all users and their |
| 20 | privileges in an Oracle database: | ||
| 14 | 21 | ||
| 15 | ## Oracle | 22 | #+begin_src sql |
| 16 | |||
| 17 | You can use the following SQL script to see all users and their privileges | ||
| 18 | in an Oracle database: | ||
| 19 | |||
| 20 | ```sql | ||
| 21 | SELECT | 23 | SELECT |
| 22 | grantee AS "User", | 24 | grantee AS "User", |
| 23 | privilege AS "Privilege" | 25 | privilege AS "Privilege" |
| @@ -33,38 +35,43 @@ FROM | |||
| 33 | dba_tab_privs | 35 | dba_tab_privs |
| 34 | WHERE | 36 | WHERE |
| 35 | grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs); | 37 | grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs); |
| 36 | ``` | 38 | #+end_src |
| 37 | |||
| 38 | This script queries the `dba_sys_privs` and `dba_tab_privs` views to | ||
| 39 | retrieve system and table-level privileges respectively. It then combines | ||
| 40 | the results using `UNION ALL` to show all users and their associated | ||
| 41 | privileges. Please note that this method does not extract information from | ||
| 42 | the `dba_role_privs` table - use the method below for that data. | ||
| 43 | 39 | ||
| 44 | Please note that you might need appropriate privileges (e.g., DBA privileges) | 40 | This script queries the =dba_sys_privs= and =dba_tab_privs= views to |
| 45 | to access these views, and you should exercise caution when querying system | 41 | retrieve system and table-level privileges respectively. It then |
| 46 | tables in a production Oracle database. | 42 | combines the results using =UNION ALL= to show all users and their |
| 43 | associated privileges. Please note that this method does not extract | ||
| 44 | information from the =dba_role_privs= table - use the method below for | ||
| 45 | that data. | ||
| 47 | 46 | ||
| 48 | ### Alternative Oracle Query | 47 | Please note that you might need appropriate privileges (e.g., DBA |
| 48 | privileges) to access these views, and you should exercise caution when | ||
| 49 | querying system tables in a production Oracle database. | ||
| 49 | 50 | ||
| 50 | You can also extract each table's information separately and perform | 51 | *** Alternative Oracle Query |
| 51 | processing outside the database to explore and determine the information | 52 | :PROPERTIES: |
| 53 | :CUSTOM_ID: alternative-oracle-query | ||
| 54 | :END: | ||
| 55 | You can also extract each table's information separately and perform | ||
| 56 | processing outside the database to explore and determine the information | ||
| 52 | necessary for the audit: | 57 | necessary for the audit: |
| 53 | 58 | ||
| 54 | ```sql | 59 | #+begin_src sql |
| 55 | SELECT * FROM sys.dba_role_privs; | 60 | SELECT * FROM sys.dba_role_privs; |
| 56 | SELECT * FROM sys.dba_sys_privs; | 61 | SELECT * FROM sys.dba_sys_privs; |
| 57 | SELECT * FROM sys.dba_tab_privs; | 62 | SELECT * FROM sys.dba_tab_privs; |
| 58 | SELECT * FROM sys.dba_users; | 63 | SELECT * FROM sys.dba_users; |
| 59 | ``` | 64 | #+end_src |
| 60 | 65 | ||
| 61 | ## Microsoft SQL | 66 | ** Microsoft SQL |
| 67 | :PROPERTIES: | ||
| 68 | :CUSTOM_ID: microsoft-sql | ||
| 69 | :END: | ||
| 70 | You can use the following SQL script to see all users and their | ||
| 71 | privileges in a Microsoft SQL Server database | ||
| 72 | ([[https://stackoverflow.com/a/30040784][source]]): | ||
| 62 | 73 | ||
| 63 | You can use the following SQL script to see all users and their privileges | 74 | #+begin_src sql |
| 64 | in a Microsoft SQL Server database | ||
| 65 | ([source](https://stackoverflow.com/a/30040784)): | ||
| 66 | |||
| 67 | ```sql | ||
| 68 | /* | 75 | /* |
| 69 | Security Audit Report | 76 | Security Audit Report |
| 70 | 1) List all access provisioned to a sql user or windows user/group directly | 77 | 1) List all access provisioned to a sql user or windows user/group directly |
| @@ -209,42 +216,47 @@ ORDER BY | |||
| 209 | perm.[permission_name], | 216 | perm.[permission_name], |
| 210 | perm.[state_desc], | 217 | perm.[state_desc], |
| 211 | obj.type_desc--perm.[class_desc] | 218 | obj.type_desc--perm.[class_desc] |
| 212 | ``` | 219 | #+end_src |
| 213 | |||
| 214 | ## MySQL | ||
| 215 | 220 | ||
| 216 | You can use the following SQL script to see all users and their privileges | 221 | ** MySQL |
| 217 | in a MySQL database: | 222 | :PROPERTIES: |
| 223 | :CUSTOM_ID: mysql | ||
| 224 | :END: | ||
| 225 | You can use the following SQL script to see all users and their | ||
| 226 | privileges in a MySQL database: | ||
| 218 | 227 | ||
| 219 | ```shell | 228 | #+begin_src shell |
| 220 | mysql -u root -p | 229 | mysql -u root -p |
| 221 | ``` | 230 | #+end_src |
| 222 | 231 | ||
| 223 | Find all users and hosts with access to the database: | 232 | Find all users and hosts with access to the database: |
| 224 | 233 | ||
| 225 | ```sql | 234 | #+begin_src sql |
| 226 | SELECT * FROM information_schema.user_privileges; | 235 | SELECT * FROM information_schema.user_privileges; |
| 227 | ``` | 236 | #+end_src |
| 228 | |||
| 229 | This script retrieves user information and their associated database-level | ||
| 230 | privileges from the `information_schema.user_privileges` table in MySQL. It | ||
| 231 | lists various privileges such as SELECT, INSERT, UPDATE, DELETE, CREATE, and | ||
| 232 | more for each user and database combination. | ||
| 233 | 237 | ||
| 234 | Please note that you may need appropriate privileges (e.g., `SELECT` | 238 | This script retrieves user information and their associated |
| 235 | privileges on `information_schema.user_privileges`) to access this | 239 | database-level privileges from the =information_schema.user_privileges= |
| 236 | information in a MySQL database. Additionally, some privileges like GRANT | 240 | table in MySQL. It lists various privileges such as SELECT, INSERT, |
| 237 | OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all users and | 241 | UPDATE, DELETE, CREATE, and more for each user and database combination. |
| 238 | databases. | ||
| 239 | 242 | ||
| 240 | ### Alternative MySQL Query | 243 | Please note that you may need appropriate privileges (e.g., =SELECT= |
| 244 | privileges on =information_schema.user_privileges=) to access this | ||
| 245 | information in a MySQL database. Additionally, some privileges like | ||
| 246 | GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all | ||
| 247 | users and databases. | ||
| 241 | 248 | ||
| 242 | You can also grab individual sets of data from MySQL if you prefer to join them | 249 | *** Alternative MySQL Query |
| 243 | after extraction. I have marked the queries below with `SELECT ...` and excluded | 250 | :PROPERTIES: |
| 244 | most `WHERE` clauses for brevity. You should determine the relevant privileges | 251 | :CUSTOM_ID: alternative-mysql-query |
| 245 | in-scope and query for those privileges to reduce the length of time to query. | 252 | :END: |
| 253 | You can also grab individual sets of data from MySQL if you prefer to | ||
| 254 | join them after extraction. I have marked the queries below with | ||
| 255 | =SELECT ...= and excluded most =WHERE= clauses for brevity. You should | ||
| 256 | determine the relevant privileges in-scope and query for those | ||
| 257 | privileges to reduce the length of time to query. | ||
| 246 | 258 | ||
| 247 | ```sql | 259 | #+begin_src sql |
| 248 | -- Global Permissions | 260 | -- Global Permissions |
| 249 | SELECT ... FROM mysql.user; | 261 | SELECT ... FROM mysql.user; |
| 250 | 262 | ||
| @@ -263,4 +275,4 @@ WHERE db = @db_name; | |||
| 263 | -- Password Configuration | 275 | -- Password Configuration |
| 264 | SHOW GLOBAL VARIABLES LIKE 'validate_password%'; | 276 | SHOW GLOBAL VARIABLES LIKE 'validate_password%'; |
| 265 | SHOW VARIABLES LIKE 'validate_password%'; | 277 | SHOW VARIABLES LIKE 'validate_password%'; |
| 266 | ``` | 278 | #+end_src |
blog/2023-10-04-digital-minimalism.org +89 −77
| @@ -1,82 +1,93 @@ | |||
| 1 | +++ | 1 | #+title: Digital Minimalism |
| 2 | date = 2023-10-05T02:10:47+00:00 | 2 | #+date: 2023-10-05 |
| 3 | title = "Digital Minimalism" | 3 | |
| 4 | description = "Some personal reflections on digital minimalism." | 4 | I've written [[/notes/minimalism][a note about minimalism]] before, but I wanted to dedicate some time |
| 5 | +++ | 5 | to reflect on digital minimalism and how I've been able to minimize the impact |
| 6 | 6 | of digital devices in my life. | |
| 7 | I've written [a note about minimalism](/notes/minimalism) before, but I wanted | 7 | |
| 8 | to dedicate some time to reflect on digital minimalism and how I've been able | 8 | #+begin_quote |
| 9 | to minimize the impact of digital devices in my life. | 9 | These changes crept up on us and happened fast, before we had a chance to step |
| 10 | 10 | back and ask what we really wanted out of the rapid advances of the past | |
| 11 | > These changes crept up on us and happened fast, before we had a chance to step | 11 | decade. We added new technologies to the periphery of our experience for minor |
| 12 | > back and ask what we really wanted out of the rapid advances of the past | 12 | reasons, then woke one morning to discover that they had colonized the core of |
| 13 | > decade. We added new technologies to the periphery of our experience for minor | 13 | our daily life. We didn't, in other words, sign up for the digital world in |
| 14 | > reasons, then woke one morning to discover that they had colonized the core | 14 | which we're currently entrenched; we seem to have stumbled backward into |
| 15 | > of our daily life. We didn’t, in other words, sign up for the digital world in | 15 | it. |
| 16 | > which we’re currently entrenched; we seem to have stumbled backward into it. | 16 | |
| 17 | > *(Digital Minimalism, 2019)* | 17 | /(Digital Minimalism, 2019)/ |
| 18 | 18 | #+end_quote | |
| 19 | ## The Principles of Digital Minimalism | 19 | |
| 20 | 20 | ** The Principles of Digital Minimalism | |
| 21 | As noted in Cal Newport's book, *Digital Minimalism*, there are three main | 21 | :PROPERTIES: |
| 22 | :CUSTOM_ID: the-principles-of-digital-minimalism | ||
| 23 | :END: | ||
| 24 | As noted in Cal Newport's book, /Digital Minimalism/, there are three main | ||
| 22 | principles to digital minimalism that I tend to agree with: | 25 | principles to digital minimalism that I tend to agree with: |
| 23 | 26 | ||
| 24 | 1. Clutter is costly. | 27 | 1. Clutter is costly. |
| 25 | - Digital minimalists recognize that cluttering their time and attention | 28 | - Digital minimalists recognize that cluttering their time and attention with |
| 26 | with too many devices, apps, and services creates an overall negative | 29 | too many devices, apps, and services creates an overall negative cost that |
| 27 | cost that can swamp the small benefits that each individual item | 30 | can swamp the small benefits that each individual item provides in |
| 28 | provides in isolation. | 31 | isolation. |
| 29 | 2. Optimization is important. | 32 | 2. Optimization is important. |
| 30 | - Digital minimalists believe that deciding a particular technology | 33 | - Digital minimalists believe that deciding a particular technology supports |
| 31 | supports something they value is only the first step. To truly extract | 34 | something they value is only the first step. To truly extract its full |
| 32 | its full potential benefit, it’s necessary to think carefully about how | 35 | potential benefit, it's necessary to think carefully about how they'll use |
| 33 | they’ll use the technology. | 36 | the technology. |
| 34 | 3. Intentionality is satisfying. | 37 | 3. Intentionality is satisfying. |
| 35 | - Digital minimalists derive significant satisfaction from their general | 38 | - Digital minimalists derive significant satisfaction from their general |
| 36 | commitment to being more intentional about how they engage with new | 39 | commitment to being more intentional about how they engage with new |
| 37 | technologies. This source of satisfaction is independent of the specific | 40 | technologies. This source of satisfaction is independent of the specific |
| 38 | decisions they make and is one of the biggest reasons that minimalism | 41 | decisions they make and is one of the biggest reasons that minimalism tends |
| 39 | tends to be immensely meaningful to its practitioners. | 42 | to be immensely meaningful to its practitioners. |
| 40 | 43 | ||
| 41 | ## Taking Action | 44 | ** Taking Action |
| 42 | 45 | :PROPERTIES: | |
| 43 | In order to put the logic into practice, I've created a few new habits and | 46 | :CUSTOM_ID: taking-action |
| 47 | :END: | ||
| 48 | In order to put the logic into practice, I've created a few new habits and | ||
| 44 | continued performing old habits that are working well: | 49 | continued performing old habits that are working well: |
| 45 | 50 | ||
| 46 | ### Using Devices With Intention | 51 | *** Using Devices With Intention |
| 47 | 52 | :PROPERTIES: | |
| 48 | - I already rarely use "social media", mostly limited to forums such as Hacker | 53 | :CUSTOM_ID: using-devices-with-intention |
| 49 | News and Tildes, so I've just tweaked my behavior to stop looking for content in | 54 | :END: |
| 50 | those places when I'm bored. | 55 | - I already rarely use "social media", mostly limited to forums such as Hacker |
| 51 | - Use devices with intention. Each time I pick up a digital device, there should | 56 | News and Tildes, so I've just tweaked my behavior to stop looking for content |
| 52 | be an intention to use the device to improve my current situation. No more | 57 | in those places when I'm bored. |
| 53 | endless scrolling or searching for something to interest me. | 58 | - Use devices with intention. Each time I pick up a digital device, there should |
| 54 | 59 | be an intention to use the device to improve my current situation. No more | |
| 55 | ### Prevent Distractions | 60 | endless scrolling or searching for something to interest me. |
| 56 | 61 | ||
| 57 | - Disable (most) notifications on all devices. I spent 15-30 minutes going | 62 | *** Prevent Distractions |
| 58 | through the notifications on my phone, watch, and computer to ensure that only a | 63 | :PROPERTIES: |
| 59 | select few apps have the ability to interrupt me: Calendar, Messages, Phone, | 64 | :CUSTOM_ID: prevent-distractions |
| 60 | Reminders, & Signal. | 65 | :END: |
| 66 | - Disable (most) notifications on all devices. I spent 15-30 minutes going | ||
| 67 | through the notifications on my phone, watch, and computer to ensure that only | ||
| 68 | a select few apps have the ability to interrupt me: Calendar, Messages, Phone, | ||
| 69 | Reminders, & Signal. | ||
| 61 | - Disable badges for any apps except the ones mentioned in the bullet above. | 70 | - Disable badges for any apps except the ones mentioned in the bullet above. |
| 62 | - Set-up focus profiles across devices so that I can enable different modes, | 71 | - Set-up focus profiles across devices so that I can enable different modes, |
| 63 | such as Personal when I only want to see notifications from people I care about | 72 | such as Personal when I only want to see notifications from people I care |
| 64 | or Do Not Disturb, where absolutely nothing can interrupt me. | 73 | about or Do Not Disturb, where absolutely nothing can interrupt me. |
| 65 | - Clean up my home screens. This one was quite easy as I already maintain a | 74 | - Clean up my home screens. This one was quite easy as I already maintain a |
| 66 | minimalist set-up, but I went extreme by limiting my phone to just eight apps on | 75 | minimalist set-up, but I went extreme by limiting my phone to just eight apps |
| 67 | the home screen and four in the dock. If I need another app, I'll have to search | 76 | on the home screen and four in the dock. If I need another app, I'll have to |
| 68 | or use the app library. | 77 | search or use the app library. |
| 69 | - Remove the work profile from my phone. This was a tough decision as having my | 78 | - Remove the work profile from my phone. This was a tough decision as having my |
| 70 | work profile on my device definitely makes my life easier at times, but it also | 79 | work profile on my device definitely makes my life easier at times, but it |
| 71 | has quite a negative effect when I'm "always online" and can see the | 80 | also has quite a negative effect when I'm "always online" and can see the |
| 72 | notifications and team activity 24/7. I believe creating a distinct barrier | 81 | notifications and team activity 24/7. I believe creating a distinct barrier |
| 73 | between my work and personal devices will be beneficial in the end. | 82 | between my work and personal devices will be beneficial in the end. |
| 74 | 83 | ||
| 75 | ### Creating Alternative Activities | 84 | *** Creating Alternative Activities |
| 76 | 85 | :PROPERTIES: | |
| 77 | This is the most difficult piece, as most of my hobbies and interests lie in the | 86 | :CUSTOM_ID: creating-alternative-activities |
| 78 | digital world. However, I'm making a concerted effort to put devices down unless | 87 | :END: |
| 79 | necessary and force myself to perform other activities in the physical world | 88 | This is the most difficult piece, as most of my hobbies and interests lie in the |
| 89 | digital world. However, I'm making a concerted effort to put devices down unless | ||
| 90 | necessary and force myself to perform other activities in the physical world | ||
| 80 | instead. | 91 | instead. |
| 81 | 92 | ||
| 82 | I've started with a few basics that are always readily available to me: | 93 | I've started with a few basics that are always readily available to me: |
| @@ -87,12 +98,13 @@ I've started with a few basics that are always readily available to me: | |||
| 87 | - Participate in a hobby, such as photography, birding, disc golf, etc. | 98 | - Participate in a hobby, such as photography, birding, disc golf, etc. |
| 88 | - Let yourself be bored and wander into creativity. | 99 | - Let yourself be bored and wander into creativity. |
| 89 | 100 | ||
| 90 | ## Making Progress | 101 | ** Making Progress |
| 91 | 102 | :PROPERTIES: | |
| 92 | I'll be taking notes as I continue down this journey and hope to see positive | 103 | :CUSTOM_ID: making-progress |
| 93 | trends. I've always been a minimalist in the physical world and it feels | 104 | :END: |
| 94 | refreshing to filter out the clutter that has come to dominate my digital life | 105 | I'll be taking notes as I continue down this journey and hope to see positive |
| 106 | trends. I've always been a minimalist in the physical world and it feels | ||
| 107 | refreshing to filter out the clutter that has come to dominate my digital life | ||
| 95 | over the years. | 108 | over the years. |
| 96 | 109 | ||
| 97 | I'm excited to see where this journey leads. | 110 | I'm excited to see where this journey leads. |
| 98 | |||
blog/2023-10-11-self-hosting-authelia.org +264 −247
| @@ -1,54 +1,59 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting Authelia |
| 2 | date = 2023-10-11T02:53:29+00:00 | 2 | #+date: 2023-10-11 |
| 3 | title = "Self-Hosting Authelia" | 3 | |
| 4 | description = "A simple tutorial to get Authelia running on a self-hosted server." | 4 | ** Overview |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: overview | |
| 7 | ## Overview | 7 | :END: |
| 8 | 8 | [[https://www.authelia.com/][Authelia]] is an open-source authentication service that allows you to place a | |
| 9 | [Authelia](https://www.authelia.com/) is an open-source authentication service | 9 | portal between end users on the internet and self-hosted services on your |
| 10 | that allows you to place a portal between end users on the internet and | 10 | server. |
| 11 | self-hosted services on your server. | 11 | |
| 12 | 12 | You can require one factor (username+password) or two factor authentication for | |
| 13 | You can require one factor (username+password) or two factor authentication for | ||
| 14 | any such user before allowing them to access a specific service on your domain. | 13 | any such user before allowing them to access a specific service on your domain. |
| 15 | 14 | ||
| 16 | This guide will walk through a standard installation of Authelia for | 15 | This guide will walk through a standard installation of Authelia for |
| 17 | `example.com`, using `auth.example.com` as Authelia's authentication domain and | 16 | =example.com=, using =auth.example.com= as Authelia's authentication domain and |
| 18 | `teddit.example.com` as the website we want to protect behind the authentication | 17 | =teddit.example.com= as the website we want to protect behind the authentication |
| 19 | portal. | 18 | portal. |
| 20 | 19 | ||
| 21 | ## Prerequisites | 20 | ** Prerequisites |
| 22 | 21 | :PROPERTIES: | |
| 22 | :CUSTOM_ID: prerequisites | ||
| 23 | :END: | ||
| 23 | This guide assumes you have the following already set-up: | 24 | This guide assumes you have the following already set-up: |
| 24 | 25 | ||
| 25 | - A registered domain with DNS pointing to your server. | 26 | - A registered domain with DNS pointing to your server. |
| 26 | - A subdomain for Authelia (`auth.example.com`) and a subdomain to protect via | 27 | - A subdomain for Authelia (=auth.example.com=) and a subdomain to |
| 27 | Authelia (`app.example.com`). | 28 | protect via Authelia (=app.example.com=). |
| 28 | - A working Nginx web server. | 29 | - A working Nginx web server. |
| 29 | - Docker and docker-compose installed. | 30 | - Docker and docker-compose installed. |
| 30 | 31 | ||
| 31 | ## Installation | 32 | ** Installation |
| 32 | 33 | :PROPERTIES: | |
| 33 | This guide will walk through each installation step one-by-one, starting with | 34 | :CUSTOM_ID: installation |
| 34 | the container and finishing by cleaning up external access via an Nginx reverse | 35 | :END: |
| 36 | This guide will walk through each installation step one-by-one, starting with | ||
| 37 | the container and finishing by cleaning up external access via an Nginx reverse | ||
| 35 | proxy. | 38 | proxy. |
| 36 | 39 | ||
| 37 | ### Docker-Compose | 40 | *** Docker-Compose |
| 38 | 41 | :PROPERTIES: | |
| 39 | To start, create a directory for Authelia and create a `docker-compose.yml` | 42 | :CUSTOM_ID: docker-compose |
| 40 | file. | 43 | :END: |
| 44 | To start, create a directory for Authelia and create a | ||
| 45 | =docker-compose.yml= file. | ||
| 41 | 46 | ||
| 42 | ```sh | 47 | #+begin_src sh |
| 43 | mkdir ~/authelia | 48 | mkdir ~/authelia |
| 44 | nano ~/authelia/docker-compose.yml | 49 | nano ~/authelia/docker-compose.yml |
| 45 | ``` | 50 | #+end_src |
| 46 | 51 | ||
| 47 | Within this file, paste the following content. If you prefer a different local | 52 | Within this file, paste the following content. If you prefer a different local |
| 48 | port, modify the port on the left side of the colon on the `9091:9091` line. Be | 53 | port, modify the port on the left side of the colon on the =9091:9091= line. Be |
| 49 | sure to modify the `TZ` variable to your timezone. | 54 | sure to modify the =TZ= variable to your timezone. |
| 50 | 55 | ||
| 51 | ```yml | 56 | #+begin_src yml |
| 52 | version: '3.3' | 57 | version: '3.3' |
| 53 | 58 | ||
| 54 | services: | 59 | services: |
| @@ -61,50 +66,52 @@ services: | |||
| 61 | - 9091:9091 | 66 | - 9091:9091 |
| 62 | environment: | 67 | environment: |
| 63 | - TZ=America/Chicago | 68 | - TZ=America/Chicago |
| 64 | ``` | 69 | #+end_src |
| 65 | 70 | ||
| 66 | Start the container with docker-compose: | 71 | Start the container with docker-compose: |
| 67 | 72 | ||
| 68 | ```sh | 73 | #+begin_src sh |
| 69 | sudo docker-compose up -d | 74 | sudo docker-compose up -d |
| 70 | ``` | 75 | #+end_src |
| 71 | 76 | ||
| 72 | After the first start, the container will automatically exit and require you to | 77 | After the first start, the container will automatically exit and require you to |
| 73 | modify the app's configuration files before continuing. Read on to learn more. | 78 | modify the app's configuration files before continuing. Read on to learn more. |
| 74 | 79 | ||
| 75 | ### Authelia Configuration | 80 | *** Authelia Configuration |
| 81 | :PROPERTIES: | ||
| 82 | :CUSTOM_ID: authelia-configuration | ||
| 83 | :END: | ||
| 84 | To configure Authelia before we restart the container, we need to open the | ||
| 85 | =config= directory and modify the files. Start by editing the | ||
| 86 | =configuration.yml= file, where all of Authelia's settings are stored. | ||
| 76 | 87 | ||
| 77 | To configure Authelia before we restart the container, we need to open the | 88 | My personal preference is to copy the original configuration file to a backup |
| 78 | `config` directory and modify the files. Start by editing the | ||
| 79 | `configuration.yml` file, where all of Authelia's settings are stored. | ||
| 80 | |||
| 81 | My personal preference is to copy the original configuration file to a backup | ||
| 82 | file and edit a fresh copy. | 89 | file and edit a fresh copy. |
| 83 | 90 | ||
| 84 | ```sh | 91 | #+begin_src sh |
| 85 | sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk | 92 | sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk |
| 86 | sudo nano ~/authelia/config/configuration.yml | 93 | sudo nano ~/authelia/config/configuration.yml |
| 87 | ``` | 94 | #+end_src |
| 88 | 95 | ||
| 89 | Within the blank `configuration.yml` file, paste the following information. You | 96 | Within the blank =configuration.yml= file, paste the following information. You |
| 90 | will need to make quite a few updates, so be sure to read each line carefully | 97 | will need to make quite a few updates, so be sure to read each line carefully |
| 91 | and modify as necessary. | 98 | and modify as necessary. |
| 92 | 99 | ||
| 93 | The major required changes are: | 100 | The major required changes are: |
| 94 | 101 | ||
| 95 | - Any instances of `example.com` should be replaced by your domain. | 102 | - Any instances of =example.com= should be replaced by your domain. |
| 96 | - `jwt_secret` - Use the `pwgen 40 1` command to generate a secret for yourself. | 103 | - =jwt_secret= - Use the =pwgen 40 1= command to generate a secret for yourself. |
| 97 | - `access_control` - Set the Authelia domain to bypass here, as well as any | 104 | - =access_control= - Set the Authelia domain to bypass here, as well as any |
| 98 | subdomains you want to protect. | 105 | subdomains you want to protect. |
| 99 | - `session` > `secret` - Use the `pwgen 40 1` command to generate a secret for | 106 | - =session= > =secret= - Use the =pwgen 40 1= command to generate a secret for |
| 100 | yourself. | 107 | yourself. |
| 101 | - `regulation` - Set the variables here to restrict login attempts and bans. | 108 | - =regulation= - Set the variables here to restrict login attempts and bans. |
| 102 | - `storage` > `encryption_key` - Use the `pwgen 40 1` command to generate a | 109 | - =storage= > =encryption_key= - Use the =pwgen 40 1= command to generate a |
| 103 | secret for yourself. | 110 | secret for yourself. |
| 104 | - `smtp` - If you have access to an SMTP service, set up the information here to | 111 | - =smtp= - If you have access to an SMTP service, set up the information here to |
| 105 | active outgoing emails. | 112 | active outgoing emails. |
| 106 | 113 | ||
| 107 | ```yml | 114 | #+begin_src yml |
| 108 | # yamllint disable rule:comments-indentation | 115 | # yamllint disable rule:comments-indentation |
| 109 | --- | 116 | --- |
| 110 | ############################################################################### | 117 | ############################################################################### |
| @@ -193,25 +200,27 @@ notifier: | |||
| 193 | skip_verify: false | 200 | skip_verify: false |
| 194 | minimum_version: TLS1.2 | 201 | minimum_version: TLS1.2 |
| 195 | ... | 202 | ... |
| 196 | ``` | 203 | #+end_src |
| 197 | |||
| 198 | ### Authelia Users | ||
| 199 | 204 | ||
| 205 | *** Authelia Users | ||
| 206 | :PROPERTIES: | ||
| 207 | :CUSTOM_ID: authelia-users | ||
| 208 | :END: | ||
| 200 | Next, create the users file for authentication. | 209 | Next, create the users file for authentication. |
| 201 | 210 | ||
| 202 | ```sh | 211 | #+begin_src sh |
| 203 | sudo nano ~/authelia/config/users_database.yml | 212 | sudo nano ~/authelia/config/users_database.yml |
| 204 | ``` | 213 | #+end_src |
| 205 | 214 | ||
| 206 | Within the file, you will need to create an entry for each user that needs | 215 | Within the file, you will need to create an entry for each user that needs |
| 207 | access to Authelia. The `my_username` entry will be the username used on the | 216 | access to Authelia. The =my_username= entry will be the username used on the |
| 208 | login page. | 217 | login page. |
| 209 | 218 | ||
| 210 | To generate the password, go to [Argon2 Hash Generator](https://argon2.online), | 219 | To generate the password, go to [[https://argon2.online][Argon2 Hash Generator]], generate a random salt, |
| 211 | generate a random salt, and make sure the rest of the settings match the | 220 | and make sure the rest of the settings match the =authentication_backend= |
| 212 | `authentication_backend` section of `configuration.yml` file. | 221 | section of =configuration.yml= file. |
| 213 | 222 | ||
| 214 | ```yml | 223 | #+begin_src yml |
| 215 | users: | 224 | users: |
| 216 | my_username: | 225 | my_username: |
| 217 | displayname: "My User" | 226 | displayname: "My User" |
| @@ -222,225 +231,233 @@ users: | |||
| 222 | groups: | 231 | groups: |
| 223 | - admins | 232 | - admins |
| 224 | - dev | 233 | - dev |
| 225 | ``` | 234 | #+end_src |
| 226 | 235 | ||
| 227 | Once the app is configured, restart the container from scratch. | 236 | Once the app is configured, restart the container from scratch. |
| 228 | 237 | ||
| 229 | ```sh | 238 | #+begin_src sh |
| 230 | cd ~/authelia | 239 | cd ~/authelia |
| 231 | sudo docker-compose down && sudo docker-compose up -d | 240 | sudo docker-compose down && sudo docker-compose up -d |
| 232 | ``` | 241 | #+end_src |
| 233 | |||
| 234 | ### Nginx: Authelia Domain | ||
| 235 | 242 | ||
| 236 | Once the container is running and configured, the final step is to configure | 243 | *** Nginx: Authelia Domain |
| 244 | :PROPERTIES: | ||
| 245 | :CUSTOM_ID: nginx-authelia-domain | ||
| 246 | :END: | ||
| 247 | Once the container is running and configured, the final step is to configure | ||
| 237 | external access to the server via Nginx reverse proxy. | 248 | external access to the server via Nginx reverse proxy. |
| 238 | 249 | ||
| 239 | Start by creating the Authelia domain. | 250 | Start by creating the Authelia domain. |
| 240 | 251 | ||
| 241 | ```sh | 252 | #+begin_src sh |
| 242 | sudo nano /etc/nginx/sites-available/auth | 253 | sudo nano /etc/nginx/sites-available/auth |
| 243 | ``` | 254 | #+end_src |
| 244 | 255 | ||
| 245 | Within this file, paste the following information and be sure to update | 256 | Within this file, paste the following information and be sure to update |
| 246 | `example.com` to your domain. Make sure the `$upstream_authelia` variable | 257 | =example.com= to your domain. Make sure the =$upstream_authelia= variable |
| 247 | matches the location of your Authelia container. | 258 | matches the location of your Authelia container. |
| 248 | 259 | ||
| 249 | ```conf | 260 | #+begin_src conf |
| 250 | server { | 261 | server { |
| 251 | if ($host ~ ^[^.]+\.example\.com$) { | 262 | if ($host ~ ^[^.]+\.example\.com$) { |
| 252 | return 301 https://$host$request_uri; | 263 | return 301 https://$host$request_uri; |
| 253 | } | 264 | } |
| 254 | 265 | ||
| 255 | listen [::]:80; | 266 | listen [::]:80; |
| 256 | listen 80; | 267 | listen 80; |
| 257 | server_name auth.example.com; | 268 | server_name auth.example.com; |
| 258 | return 404; | 269 | return 404; |
| 259 | } | 270 | } |
| 260 | 271 | ||
| 261 | server { | 272 | server { |
| 262 | listen [::]:443 ssl http2; | 273 | listen [::]:443 ssl http2; |
| 263 | listen 443 ssl http2; | 274 | listen 443 ssl http2; |
| 264 | server_name auth.example.com; | 275 | server_name auth.example.com; |
| 265 | access_log /var/log/nginx/auth.access.log; | 276 | access_log /var/log/nginx/auth.access.log; |
| 266 | error_log /var/log/nginx/auth.error.log; | 277 | error_log /var/log/nginx/auth.error.log; |
| 267 | 278 | ||
| 268 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; | 279 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; |
| 269 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; | 280 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; |
| 270 | include /etc/letsencrypt/options-ssl-nginx.conf; | 281 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 271 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 282 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 272 | 283 | ||
| 273 | location / { | 284 | location / { |
| 274 | set $upstream_authelia http://127.0.0.1:9091; | 285 | set $upstream_authelia http://127.0.0.1:9091; |
| 275 | proxy_pass $upstream_authelia; | 286 | proxy_pass $upstream_authelia; |
| 276 | client_body_buffer_size 128k; | 287 | client_body_buffer_size 128k; |
| 277 | 288 | ||
| 278 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; | 289 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; |
| 279 | 290 | ||
| 280 | send_timeout 5m; | 291 | send_timeout 5m; |
| 281 | proxy_read_timeout 360; | 292 | proxy_read_timeout 360; |
| 282 | proxy_send_timeout 360; | 293 | proxy_send_timeout 360; |
| 283 | proxy_connect_timeout 360; | 294 | proxy_connect_timeout 360; |
| 284 | 295 | ||
| 285 | proxy_set_header Host $host; | 296 | proxy_set_header Host $host; |
| 286 | proxy_set_header X-Real-IP $remote_addr; | 297 | proxy_set_header X-Real-IP $remote_addr; |
| 287 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 298 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 288 | proxy_set_header X-Forwarded-Proto $scheme; | 299 | proxy_set_header X-Forwarded-Proto $scheme; |
| 289 | proxy_set_header X-Forwarded-Host $http_host; | 300 | proxy_set_header X-Forwarded-Host $http_host; |
| 290 | proxy_set_header X-Forwarded-Uri $request_uri; | 301 | proxy_set_header X-Forwarded-Uri $request_uri; |
| 291 | proxy_set_header X-Forwarded-Ssl on; | 302 | proxy_set_header X-Forwarded-Ssl on; |
| 292 | proxy_redirect http:// $scheme://; | 303 | proxy_redirect http:// $scheme://; |
| 293 | proxy_http_version 1.1; | 304 | proxy_http_version 1.1; |
| 294 | proxy_set_header Connection ""; | 305 | proxy_set_header Connection ""; |
| 295 | proxy_cache_bypass $cookie_session; | 306 | proxy_cache_bypass $cookie_session; |
| 296 | proxy_no_cache $cookie_session; | 307 | proxy_no_cache $cookie_session; |
| 297 | proxy_buffers 64 256k; | 308 | proxy_buffers 64 256k; |
| 298 | } | 309 | } |
| 299 | 310 | ||
| 300 | } | 311 | } |
| 301 | ``` | 312 | #+end_src |
| 302 | 313 | ||
| 303 | Next, symlink the file and restart Nginx. If there are errors, be sure to | 314 | Next, symlink the file and restart Nginx. If there are errors, be sure to |
| 304 | resolve those before moving on. | 315 | resolve those before moving on. |
| 305 | 316 | ||
| 306 | ```sh | 317 | #+begin_src sh |
| 307 | sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth | 318 | sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth |
| 308 | sudo systemctl restart nginx.service | 319 | sudo systemctl restart nginx.service |
| 309 | ``` | 320 | #+end_src |
| 310 | 321 | ||
| 311 | ### Nginx: Protected Domain(s) | 322 | *** Nginx: Protected Domain(s) |
| 323 | :PROPERTIES: | ||
| 324 | :CUSTOM_ID: nginx-protected-domains | ||
| 325 | :END: | ||
| 326 | Now that Authelia is accessible externally, you need to configure the domain you | ||
| 327 | intend to protect with Authelia. In this example, I'm protecting | ||
| 328 | =teddit.example.com=. | ||
| 312 | 329 | ||
| 313 | Now that Authelia is accessible externally, you need to configure the domain you | 330 | Similar to the process above, paste the content and update the relevant |
| 314 | intend to protect with Authelia. In this example, I'm protecting | ||
| 315 | `teddit.example.com`. | ||
| 316 | |||
| 317 | Similar to the process above, paste the content and update the relevant | ||
| 318 | variables. | 331 | variables. |
| 319 | 332 | ||
| 320 | ```sh | 333 | #+begin_src sh |
| 321 | sudo nano /etc/nginx/sites-available/teddit | 334 | sudo nano /etc/nginx/sites-available/teddit |
| 322 | ``` | 335 | #+end_src |
| 323 | 336 | ||
| 324 | ```conf | 337 | #+begin_src conf |
| 325 | server { | 338 | server { |
| 326 | if ($host ~ ^[^.]+\.example\.com$) { | 339 | if ($host ~ ^[^.]+\.example\.com$) { |
| 327 | return 301 https://$host$request_uri; | 340 | return 301 https://$host$request_uri; |
| 328 | } | 341 | } |
| 329 | 342 | ||
| 330 | listen [::]:80; | 343 | listen [::]:80; |
| 331 | listen 80; | 344 | listen 80; |
| 332 | server_name teddit.example.com; | 345 | server_name teddit.example.com; |
| 333 | return 404; | 346 | return 404; |
| 334 | } | 347 | } |
| 335 | 348 | ||
| 336 | server { | 349 | server { |
| 337 | listen [::]:443 ssl http2; | 350 | listen [::]:443 ssl http2; |
| 338 | listen 443 ssl http2; | 351 | listen 443 ssl http2; |
| 339 | server_name teddit.example.com; | 352 | server_name teddit.example.com; |
| 340 | access_log /var/log/nginx/teddit.access.log; | 353 | access_log /var/log/nginx/teddit.access.log; |
| 341 | error_log /var/log/nginx/teddit.error.log; | 354 | error_log /var/log/nginx/teddit.error.log; |
| 342 | 355 | ||
| 343 | add_header X-Content-Type-Options "nosniff"; | 356 | add_header X-Content-Type-Options "nosniff"; |
| 344 | add_header X-XSS-Protection "1; mode=block"; | 357 | add_header X-XSS-Protection "1; mode=block"; |
| 345 | add_header X-Frame-Options "DENY"; | 358 | add_header X-Frame-Options "DENY"; |
| 346 | add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; | 359 | add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; |
| 347 | add_header Referrer-Policy "no-referrer"; | 360 | add_header Referrer-Policy "no-referrer"; |
| 348 | 361 | ||
| 349 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; | 362 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; |
| 350 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; | 363 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; |
| 351 | include /etc/letsencrypt/options-ssl-nginx.conf; | 364 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 352 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 365 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 353 | 366 | ||
| 354 | location /authelia { | 367 | location /authelia { |
| 355 | internal; | 368 | internal; |
| 356 | set $upstream_authelia http://127.0.0.1:9091/api/verify; | 369 | set $upstream_authelia http://127.0.0.1:9091/api/verify; |
| 357 | proxy_pass_request_body off; | 370 | proxy_pass_request_body off; |
| 358 | proxy_pass $upstream_authelia; | 371 | proxy_pass $upstream_authelia; |
| 359 | proxy_set_header Content-Length ""; | 372 | proxy_set_header Content-Length ""; |
| 360 | 373 | ||
| 361 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; | 374 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; |
| 362 | client_body_buffer_size 128k; | 375 | client_body_buffer_size 128k; |
| 363 | proxy_set_header Host $host; | 376 | proxy_set_header Host $host; |
| 364 | proxy_set_header X-Original-URL $scheme://$http_host$request_uri; | 377 | proxy_set_header X-Original-URL $scheme://$http_host$request_uri; |
| 365 | proxy_set_header X-Real-IP $remote_addr; | 378 | proxy_set_header X-Real-IP $remote_addr; |
| 366 | proxy_set_header X-Forwarded-For $remote_addr; | 379 | proxy_set_header X-Forwarded-For $remote_addr; |
| 367 | proxy_set_header X-Forwarded-Proto $scheme; | 380 | proxy_set_header X-Forwarded-Proto $scheme; |
| 368 | proxy_set_header X-Forwarded-Host $http_host; | 381 | proxy_set_header X-Forwarded-Host $http_host; |
| 369 | proxy_set_header X-Forwarded-Uri $request_uri; | 382 | proxy_set_header X-Forwarded-Uri $request_uri; |
| 370 | proxy_set_header X-Forwarded-Ssl on; | 383 | proxy_set_header X-Forwarded-Ssl on; |
| 371 | proxy_redirect http:// $scheme://; | 384 | proxy_redirect http:// $scheme://; |
| 372 | proxy_http_version 1.1; | 385 | proxy_http_version 1.1; |
| 373 | proxy_set_header Connection ""; | 386 | proxy_set_header Connection ""; |
| 374 | proxy_cache_bypass $cookie_session; | 387 | proxy_cache_bypass $cookie_session; |
| 375 | proxy_no_cache $cookie_session; | 388 | proxy_no_cache $cookie_session; |
| 376 | proxy_buffers 4 32k; | 389 | proxy_buffers 4 32k; |
| 377 | 390 | ||
| 378 | send_timeout 5m; | 391 | send_timeout 5m; |
| 379 | proxy_read_timeout 240; | 392 | proxy_read_timeout 240; |
| 380 | proxy_send_timeout 240; | 393 | proxy_send_timeout 240; |
| 381 | proxy_connect_timeout 240; | 394 | proxy_connect_timeout 240; |
| 382 | } | 395 | } |
| 383 | 396 | ||
| 384 | location / { | 397 | location / { |
| 385 | set $upstream_teddit http://127.0.0.1:8686; | 398 | set $upstream_teddit http://127.0.0.1:8686; |
| 386 | proxy_pass $upstream_teddit; | 399 | proxy_pass $upstream_teddit; |
| 387 | 400 | ||
| 388 | auth_request /authelia; | 401 | auth_request /authelia; |
| 389 | auth_request_set $target_url https://$http_host$request_uri; | 402 | auth_request_set $target_url https://$http_host$request_uri; |
| 390 | auth_request_set $user $upstream_http_remote_user; | 403 | auth_request_set $user $upstream_http_remote_user; |
| 391 | auth_request_set $email $upstream_http_remote_email; | 404 | auth_request_set $email $upstream_http_remote_email; |
| 392 | auth_request_set $groups $upstream_http_remote_groups; | 405 | auth_request_set $groups $upstream_http_remote_groups; |
| 393 | proxy_set_header Remote-User $user; | 406 | proxy_set_header Remote-User $user; |
| 394 | proxy_set_header Remote-Email $email; | 407 | proxy_set_header Remote-Email $email; |
| 395 | proxy_set_header Remote-Groups $groups; | 408 | proxy_set_header Remote-Groups $groups; |
| 396 | 409 | ||
| 397 | error_page 401 =302 https://auth.example.com/?rd=$target_url; | 410 | error_page 401 =302 https://auth.example.com/?rd=$target_url; |
| 398 | 411 | ||
| 399 | client_body_buffer_size 128k; | 412 | client_body_buffer_size 128k; |
| 400 | 413 | ||
| 401 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; | 414 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; |
| 402 | 415 | ||
| 403 | send_timeout 5m; | 416 | send_timeout 5m; |
| 404 | proxy_read_timeout 360; | 417 | proxy_read_timeout 360; |
| 405 | proxy_send_timeout 360; | 418 | proxy_send_timeout 360; |
| 406 | proxy_connect_timeout 360; | 419 | proxy_connect_timeout 360; |
| 407 | 420 | ||
| 408 | proxy_set_header Host $host; | 421 | proxy_set_header Host $host; |
| 409 | proxy_set_header Upgrade $http_upgrade; | 422 | proxy_set_header Upgrade $http_upgrade; |
| 410 | proxy_set_header Connection upgrade; | 423 | proxy_set_header Connection upgrade; |
| 411 | proxy_set_header Accept-Encoding gzip; | 424 | proxy_set_header Accept-Encoding gzip; |
| 412 | proxy_set_header X-Real-IP $remote_addr; | 425 | proxy_set_header X-Real-IP $remote_addr; |
| 413 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 426 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 414 | proxy_set_header X-Forwarded-Proto $scheme; | 427 | proxy_set_header X-Forwarded-Proto $scheme; |
| 415 | proxy_set_header X-Forwarded-Host $http_host; | 428 | proxy_set_header X-Forwarded-Host $http_host; |
| 416 | proxy_set_header X-Forwarded-Uri $request_uri; | 429 | proxy_set_header X-Forwarded-Uri $request_uri; |
| 417 | proxy_set_header X-Forwarded-Ssl on; | 430 | proxy_set_header X-Forwarded-Ssl on; |
| 418 | proxy_redirect http:// $scheme://; | 431 | proxy_redirect http:// $scheme://; |
| 419 | proxy_http_version 1.1; | 432 | proxy_http_version 1.1; |
| 420 | proxy_set_header Connection ""; | 433 | proxy_set_header Connection ""; |
| 421 | proxy_cache_bypass $cookie_session; | 434 | proxy_cache_bypass $cookie_session; |
| 422 | proxy_no_cache $cookie_session; | 435 | proxy_no_cache $cookie_session; |
| 423 | proxy_buffers 64 256k; | 436 | proxy_buffers 64 256k; |
| 424 | } | 437 | } |
| 425 | } | 438 | } |
| 426 | ``` | 439 | #+end_src |
| 427 | 440 | ||
| 428 | Same as before, symlink the file and restart Nginx. | 441 | Same as before, symlink the file and restart Nginx. |
| 429 | 442 | ||
| 430 | ```sh | 443 | #+begin_src sh |
| 431 | sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit | 444 | sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit |
| 432 | sudo systemctl restart nginx.service | 445 | sudo systemctl restart nginx.service |
| 433 | ``` | 446 | #+end_src |
| 434 | |||
| 435 | ## Results | ||
| 436 | 447 | ||
| 437 | When visiting the protected domain, you will now be redirected to your | 448 | ** Results |
| 449 | :PROPERTIES: | ||
| 450 | :CUSTOM_ID: results | ||
| 451 | :END: | ||
| 452 | When visiting the protected domain, you will now be redirected to your | ||
| 438 | authentication domain and presented with the Authelia login portal. | 453 | authentication domain and presented with the Authelia login portal. |
| 439 | 454 | ||
| 440 |  | 455 | #+caption: Authelia Portal |
| 456 | [[https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png]] | ||
| 441 | 457 | ||
| 442 | Once you've successfully authenticated, you can visit your authentication domain | 458 | Once you've successfully authenticated, you can visit your authentication domain |
| 443 | directly and see that you're currently authenticated to any domain protected by | 459 | directly and see that you're currently authenticated to any domain protected by |
| 444 | Authelia. | 460 | Authelia. |
| 445 | 461 | ||
| 446 |  | 462 | #+caption: Authelia Success |
| 463 | [[https://img.cleberg.net/blog/20231010-authelia/authelia_success.png]] | ||
blog/2023-10-15-alpine-ssh-hardening.org +54 −40
| @@ -1,60 +1,74 @@ | |||
| 1 | +++ | 1 | #+title: SSH Hardening for Alpine Linux |
| 2 | date = 2023-10-16T00:20:45+00:00 | 2 | #+date: 2023-10-16 |
| 3 | title = "SSH Hardening for Alpine Linux" | ||
| 4 | description = "A quick guide to hardening SSH on Alpine Linux." | ||
| 5 | +++ | ||
| 6 | |||
| 7 | ## Overview | ||
| 8 | 3 | ||
| 4 | ** Overview | ||
| 5 | :PROPERTIES: | ||
| 6 | :CUSTOM_ID: overview | ||
| 7 | :END: | ||
| 9 | This guide follows the standard | 8 | This guide follows the standard |
| 10 | [ssh-audit](https://www.ssh-audit.com/hardening_guides.html) hardening guide, | 9 | [[https://www.ssh-audit.com/hardening_guides.html][ssh-audit]] hardening |
| 11 | tweaked for Alpine Linux. | 10 | guide, tweaked for Alpine Linux. |
| 12 | |||
| 13 | ## Hardening Guide | ||
| 14 | 11 | ||
| 15 | These steps must be performed as root. You can try to use `doas` or `sudo`, | 12 | ** Hardening Guide |
| 16 | but there may be issues. | 13 | :PROPERTIES: |
| 14 | :CUSTOM_ID: hardening-guide | ||
| 15 | :END: | ||
| 16 | These steps must be performed as root. You can try to use =doas= or | ||
| 17 | =sudo=, but there may be issues. | ||
| 17 | 18 | ||
| 18 | 1. Re-generate the RSA and ED25519 keys | 19 | 1. Re-generate the RSA and ED25519 keys |
| 19 | ```sh | 20 | |
| 21 | #+begin_src sh | ||
| 20 | rm /etc/ssh/ssh_host_* | 22 | rm /etc/ssh/ssh_host_* |
| 21 | ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" | 23 | ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" |
| 22 | ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" | 24 | ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" |
| 23 | ``` | 25 | #+end_src |
| 24 | 2. Remove small Diffie-Hellman moduli | 26 | |
| 25 | ```sh | 27 | 2. [@2] Remove small Diffie-Hellman moduli |
| 28 | |||
| 29 | #+begin_src sh | ||
| 26 | awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe | 30 | awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe |
| 27 | mv /etc/ssh/moduli.safe /etc/ssh/moduli | 31 | mv /etc/ssh/moduli.safe /etc/ssh/moduli |
| 28 | ``` | 32 | #+end_src |
| 29 | 3. Enable the RSA and ED25519 HostKey directives in the /etc/ssh/sshd_config | 33 | |
| 30 | file | 34 | 3. [@3] Enable the RSA and ED25519 HostKey directives in the |
| 31 | ```sh | 35 | /etc/ssh/sshd_config file |
| 36 | |||
| 37 | #+begin_src sh | ||
| 32 | sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config | 38 | sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config |
| 33 | ``` | 39 | #+end_src |
| 34 | 4. Restrict supported key exchange, cipher, and MAC algorithms | 40 | |
| 35 | ```sh | 41 | 4. [@4] Restrict supported key exchange, cipher, and MAC algorithms |
| 42 | |||
| 43 | #+begin_src sh | ||
| 36 | echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf | 44 | echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf |
| 37 | ``` | 45 | #+end_src |
| 38 | 5. Include the /etc/ssh/sshd_config.d directory | 46 | |
| 39 | ```sh | 47 | 5. [@5] Include the /etc/ssh/sshd_config.d directory |
| 48 | |||
| 49 | #+begin_src sh | ||
| 40 | echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config | 50 | echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config |
| 41 | ``` | 51 | #+end_src |
| 42 | 6. Restart OpenSSH server | 52 | |
| 43 | ```sh | 53 | 6. [@6] Restart OpenSSH server |
| 44 | rc-service sshd restart | ||
| 45 | ``` | ||
| 46 | 54 | ||
| 47 | ## Testing SSH | 55 | #+begin_src sh |
| 56 | rc-service sshd restart | ||
| 57 | #+end_src | ||
| 48 | 58 | ||
| 49 | You can test the results with the `ssh-audit` python script. | 59 | ** Testing SSH |
| 60 | :PROPERTIES: | ||
| 61 | :CUSTOM_ID: testing-ssh | ||
| 62 | :END: | ||
| 63 | You can test the results with the =ssh-audit= python script. | ||
| 50 | 64 | ||
| 51 | ```sh | 65 | #+begin_src sh |
| 52 | pip3 install ssh-audit | 66 | pip3 install ssh-audit |
| 53 | ssh-audit localhost | 67 | ssh-audit localhost |
| 54 | ``` | 68 | #+end_src |
| 55 | 69 | ||
| 56 | If everything succeeded, the results will show as all green. If anything is | 70 | If everything succeeded, the results will show as all green. If anything |
| 57 | yellow, orange, or red, you may need to tweak additional settings. | 71 | is yellow, orange, or red, you may need to tweak additional settings. |
| 58 | 72 | ||
| 59 |  | 74 | [[https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png%20%22ssh-audit%22]] |
blog/2023-10-17-self-hosting-anonymousoverflow.org +98 −93
| @@ -1,33 +1,37 @@ | |||
| 1 | +++ | 1 | #+title: Self-Hosting AnonymousOverflow |
| 2 | date = 2023-10-17T15:44:45.601627917+00:00 | 2 | #+date: 2023-10-17 |
| 3 | title = "Self-Hosting AnonymousOverflow" | 3 | |
| 4 | description = "A quick guide to self-hosting AnonymousOverflow." | 4 | ** Overview |
| 5 | +++ | 5 | :PROPERTIES: |
| 6 | 6 | :CUSTOM_ID: overview | |
| 7 | ## Overview | 7 | :END: |
| 8 | 8 | I recently launched an instance of AnonymousOverflow at [[https://ao.cleberg.net][ao.cleberg.net]] and | |
| 9 | I recently launched an instance of AnonymousOverflow at | 9 | wanted to write a brief post on how easy it is to install with Docker Compose |
| 10 | [ao.cleberg.net](https://ao.cleberg.net) and wanted to write a brief post on how | 10 | and Nginx. |
| 11 | easy it is to install with Docker Compose and Nginx. | 11 | |
| 12 | 12 | This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse | |
| 13 | This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse proxy. | 13 | proxy. |
| 14 | 14 | ||
| 15 | ## Installation | 15 | ** Installation |
| 16 | 16 | :PROPERTIES: | |
| 17 | ### Docker Compose | 17 | :CUSTOM_ID: installation |
| 18 | 18 | :END: | |
| 19 | To install AnonymousOverflow, start by creating a directory for the application | 19 | *** Docker Compose |
| 20 | and create its `docker-compose.yml` file. | 20 | :PROPERTIES: |
| 21 | 21 | :CUSTOM_ID: docker-compose | |
| 22 | ```sh | 22 | :END: |
| 23 | To install AnonymousOverflow, start by creating a directory for the | ||
| 24 | application and create its =docker-compose.yml= file. | ||
| 25 | |||
| 26 | #+begin_src sh | ||
| 23 | mkdir ~/anonymousoverflow && cd ~/anonymousoverflow | 27 | mkdir ~/anonymousoverflow && cd ~/anonymousoverflow |
| 24 | nano docker-compose.yml | 28 | nano docker-compose.yml |
| 25 | ``` | 29 | #+end_src |
| 26 | 30 | ||
| 27 | Within this file, paste the following information. Be sure to change the | 31 | Within this file, paste the following information. Be sure to change the |
| 28 | `APP_URL`, `JWT_SIGNING_SECRET`, and `ports` to match your needs. | 32 | =APP_URL=, =JWT_SIGNING_SECRET=, and =ports= to match your needs. |
| 29 | 33 | ||
| 30 | ```yaml | 34 | #+begin_src yaml |
| 31 | version: '3' | 35 | version: '3' |
| 32 | 36 | ||
| 33 | services: | 37 | services: |
| @@ -40,92 +44,93 @@ services: | |||
| 40 | ports: | 44 | ports: |
| 41 | - '9380:8080' | 45 | - '9380:8080' |
| 42 | restart: 'always' | 46 | restart: 'always' |
| 43 | ``` | 47 | #+end_src |
| 44 | 48 | ||
| 45 | Save and exit the file when complete. You can now launch the container and | 49 | Save and exit the file when complete. You can now launch the container |
| 46 | access it via your local network. | 50 | and access it via your local network. |
| 47 | 51 | ||
| 48 | ```sh | 52 | #+begin_src sh |
| 49 | sudo docker-compose up -d | 53 | sudo docker-compose up -d |
| 50 | ``` | 54 | #+end_src |
| 51 | |||
| 52 | ### Nginx Reverse Proxy | ||
| 53 | 55 | ||
| 54 | If you want to access this service outside the local network, I recommend using | 56 | *** Nginx Reverse Proxy |
| 55 | Nginx as a reverse proxy. | 57 | :PROPERTIES: |
| 58 | :CUSTOM_ID: nginx-reverse-proxy | ||
| 59 | :END: | ||
| 60 | If you want to access this service outside the local network, I | ||
| 61 | recommend using Nginx as a reverse proxy. | ||
| 56 | 62 | ||
| 57 | Let's start by creating a configuration file. | 63 | Let's start by creating a configuration file. |
| 58 | 64 | ||
| 59 | ```sh | 65 | #+begin_src sh |
| 60 | sudo nano /etc/nginx/sites-available/ao | 66 | sudo nano /etc/nginx/sites-available/ao |
| 61 | ``` | 67 | #+end_src |
| 62 | 68 | ||
| 63 | Within this file, paste the following content and repace `ao.example.com` with | 69 | Within this file, paste the following content and repace |
| 64 | your URL. You may need to update the SSL certificate statements if your | 70 | =ao.example.com= with your URL. You may need to update the SSL |
| 65 | certificates are in a different location. | 71 | certificate statements if your certificates are in a different location. |
| 66 | 72 | ||
| 67 | ```conf | 73 | #+begin_src conf |
| 68 | server { | 74 | server { |
| 69 | if ($host ~ ^[^.]+\.cleberg\.net$) { | 75 | if ($host ~ ^[^.]+\.cleberg\.net$) { |
| 70 | return 301 https://$host$request_uri; | 76 | return 301 https://$host$request_uri; |
| 71 | } | 77 | } |
| 72 | 78 | ||
| 73 | listen [::]:80; | 79 | listen [::]:80; |
| 74 | listen 80; | 80 | listen 80; |
| 75 | server_name ao.example.com; | 81 | server_name ao.example.com; |
| 76 | return 404; | 82 | return 404; |
| 77 | } | 83 | } |
| 78 | 84 | ||
| 79 | server { | 85 | server { |
| 80 | listen [::]:443 ssl http2; | 86 | listen [::]:443 ssl http2; |
| 81 | listen 443 ssl http2; | 87 | listen 443 ssl http2; |
| 82 | server_name ao.example.com; | 88 | server_name ao.example.com; |
| 83 | access_log /var/log/nginx/ao.access.log; | 89 | access_log /var/log/nginx/ao.access.log; |
| 84 | error_log /var/log/nginx/ao.error.log; | 90 | error_log /var/log/nginx/ao.error.log; |
| 85 | 91 | ||
| 86 | add_header X-Content-Type-Options "nosniff"; | 92 | add_header X-Content-Type-Options "nosniff"; |
| 87 | add_header X-XSS-Protection "1; mode=block"; | 93 | add_header X-XSS-Protection "1; mode=block"; |
| 88 | add_header X-Frame-Options "DENY"; | 94 | add_header X-Frame-Options "DENY"; |
| 89 | add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; | 95 | add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; |
| 90 | add_header Referrer-Policy "no-referrer"; | 96 | add_header Referrer-Policy "no-referrer"; |
| 91 | 97 | ||
| 92 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; | 98 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; |
| 93 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; | 99 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; |
| 94 | include /etc/letsencrypt/options-ssl-nginx.conf; | 100 | include /etc/letsencrypt/options-ssl-nginx.conf; |
| 95 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | 101 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 96 | 102 | ||
| 97 | location / { | 103 | location / { |
| 98 | set $upstream_ao http://127.0.0.1:9380; | 104 | set $upstream_ao http://127.0.0.1:9380; |
| 99 | proxy_pass $upstream_ao; | 105 | proxy_pass $upstream_ao; |
| 100 | 106 | ||
| 101 | proxy_set_header Host $host; | 107 | proxy_set_header Host $host; |
| 102 | proxy_set_header Upgrade $http_upgrade; | 108 | proxy_set_header Upgrade $http_upgrade; |
| 103 | proxy_set_header Connection upgrade; | 109 | proxy_set_header Connection upgrade; |
| 104 | proxy_set_header Accept-Encoding gzip; | 110 | proxy_set_header Accept-Encoding gzip; |
| 105 | proxy_set_header X-Real-IP $remote_addr; | 111 | proxy_set_header X-Real-IP $remote_addr; |
| 106 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 112 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 107 | proxy_set_header X-Forwarded-Proto $scheme; | 113 | proxy_set_header X-Forwarded-Proto $scheme; |
| 108 | proxy_set_header X-Forwarded-Host $http_host; | 114 | proxy_set_header X-Forwarded-Host $http_host; |
| 109 | proxy_set_header X-Forwarded-Uri $request_uri; | 115 | proxy_set_header X-Forwarded-Uri $request_uri; |
| 110 | proxy_set_header X-Forwarded-Ssl on; | 116 | proxy_set_header X-Forwarded-Ssl on; |
| 111 | proxy_redirect http:// $scheme://; | 117 | proxy_redirect http:// $scheme://; |
| 112 | proxy_http_version 1.1; | 118 | proxy_http_version 1.1; |
| 113 | proxy_set_header Connection ""; | 119 | proxy_set_header Connection ""; |
| 114 | proxy_cache_bypass $cookie_session; | 120 | proxy_cache_bypass $cookie_session; |
| 115 | proxy_no_cache $cookie_session; | 121 | proxy_no_cache $cookie_session; |
| 116 | proxy_buffers 64 256k; | 122 | proxy_buffers 64 256k; |
| 117 | } | 123 | } |
| 118 | } | 124 | } |
| 119 | ``` | 125 | #+end_src |
| 120 | 126 | ||
| 121 | Save and exit the file when complete. On Ubuntu, you will need to symlink the | 127 | Save and exit the file when complete. On Ubuntu, you will need to symlink the |
| 122 | configuration file before it will be recognized by Nginx. Once complete, | 128 | configuration file before it will be recognized by Nginx. Once complete, simply |
| 123 | simply restart the web server. | 129 | restart the web server. |
| 124 | 130 | ||
| 125 | ```sh | 131 | #+begin_src sh |
| 126 | sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao | 132 | sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao |
| 127 | sudo systemctl restart nginx.service | 133 | sudo systemctl restart nginx.service |
| 128 | ``` | 134 | #+end_src |
| 129 | 135 | ||
| 130 | The website will now be available publicly. Visit | 136 | The website will now be available publicly. Visit [[https://ao.cleberg.net][my instance]] for an example. |
| 131 | [my instance](https://ao.cleberg.net) for an example. | ||
blog/2023-11-08-scli.org +100 −93
| @@ -1,146 +1,153 @@ | |||
| 1 | +++ | 1 | #+title: Installing scli on Alpine Linux (musl) |
| 2 | date = 2023-11-08T21:37:00.307616604+00:00 | 2 | #+date: 2023-11-08 |
| 3 | title = "Installing scli on Alpine Linux (musl)" | ||
| 4 | description = "" | ||
| 5 | +++ | ||
| 6 | |||
| 7 | [scli](https://github.com/isamert/scli) is a command-line tool that allows you | ||
| 8 | to connect to your Signal messenger account. This program utilizes a two-pane | ||
| 9 | display that shows you chats on the left and the focused conversation on the | ||
| 10 | right. | ||
| 11 | |||
| 12 | This guide will show you how to install `scli` and its dependencies on Alpine | ||
| 13 | Linux, which requires some extra work due to musl. | ||
| 14 | |||
| 15 | If you're using a non-musl system, you can likely following the `scli` README | ||
| 16 | and download the packaged binaries for an easier installation process. | ||
| 17 | 3 | ||
| 18 | ## Dependencies | 4 | [[https://github.com/isamert/scli][scli]] is a command-line tool that allows you to connect to your Signal messenger |
| 5 | account. This program utilizes a two-pane display that shows you chats on the | ||
| 6 | left and the focused conversation on the right. | ||
| 19 | 7 | ||
| 20 | In order to use `scli`, you need a few dependencies: | 8 | This guide will show you how to install =scli= and its dependencies on Alpine |
| 21 | 9 | Linux, which requires some extra work due to musl. | |
| 22 | - `openjdk17-jre` - Used as a dependency for the `signal-cli` tool. Version may | ||
| 23 | vary. | ||
| 24 | - `signal-cli` - Used as the backbone of the `scli` tool. | ||
| 25 | - `findutils` - Replaces the standard Busybox version of `xargs`. | ||
| 26 | - `urwid` - A console user interface library for Python. | ||
| 27 | - `urwid-readline` - For GNU emacs-like keybinds on the input line. | ||
| 28 | - `qrencode` - Displays a QR code in the terminal to link the device using your | ||
| 29 | phone. Not necessary if you're only linking on desktop and can copy/paste the | ||
| 30 | connection URL. | ||
| 31 | 10 | ||
| 32 | Let's start by installing the packages available via Alpine's repositories. Be | 11 | If you're using a non-musl system, you can likely following the =scli= README |
| 33 | sure to install the latest version of `openjdk`. If you run into Java-related | 12 | and download the packaged binaries for an easier installation process. |
| 34 | issues, uninstall `openjdk` and install an older version. | ||
| 35 | 13 | ||
| 36 | ```sh | 14 | ** Dependencies |
| 15 | :PROPERTIES: | ||
| 16 | :CUSTOM_ID: dependencies | ||
| 17 | :END: | ||
| 18 | In order to use =scli=, you need a few dependencies: | ||
| 19 | |||
| 20 | - =openjdk17-jre= - Used as a dependency for the =signal-cli= tool. | ||
| 21 | Version may vary. | ||
| 22 | - =signal-cli= - Used as the backbone of the =scli= tool. | ||
| 23 | - =findutils= - Replaces the standard Busybox version of =xargs=. | ||
| 24 | - =urwid= - A console user interface library for Python. | ||
| 25 | - =urwid-readline= - For GNU emacs-like keybinds on the input line. | ||
| 26 | - =qrencode= - Displays a QR code in the terminal to link the device | ||
| 27 | using your phone. Not necessary if you're only linking on desktop and | ||
| 28 | can copy/paste the connection URL. | ||
| 29 | |||
| 30 | Let's start by installing the packages available via Alpine's | ||
| 31 | repositories. Be sure to install the latest version of =openjdk=. If you | ||
| 32 | run into Java-related issues, uninstall =openjdk= and install an older | ||
| 33 | version. | ||
| 34 | |||
| 35 | #+begin_src sh | ||
| 37 | doas apk add openjdk17-jre findutils qrencode | 36 | doas apk add openjdk17-jre findutils qrencode |
| 38 | ``` | 37 | #+end_src |
| 39 | 38 | ||
| 40 | Next, let's install `signal-cli`. Be sure to export the version of `signal-cli` | 39 | Next, let's install =signal-cli=. Be sure to export the version of |
| 41 | that you want. I use version `0.12.4` below, but that may be outdated by the | 40 | =signal-cli= that you want. I use version =0.12.4= below, but that may |
| 42 | time you're reading this. | 41 | be outdated by the time you're reading this. |
| 43 | 42 | ||
| 44 | ```sh | 43 | #+begin_src sh |
| 45 | export VERSION="0.12.4" | 44 | export VERSION="0.12.4" |
| 46 | wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz | 45 | wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz |
| 47 | doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt | 46 | doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt |
| 48 | doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin | 47 | doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin |
| 49 | ``` | 48 | #+end_src |
| 50 | 49 | ||
| 51 | Finally, install the `urwid` packages using the Python packaging utility. | 50 | Finally, install the =urwid= packages using the Python packaging |
| 51 | utility. | ||
| 52 | 52 | ||
| 53 | ```sh | 53 | #+begin_src sh |
| 54 | pip3 install urwid urwid-readline | 54 | pip3 install urwid urwid-readline |
| 55 | ``` | 55 | #+end_src |
| 56 | |||
| 57 | ## Installation | ||
| 58 | 56 | ||
| 59 | Now that we have all of the dependencies we need, we can install `scli`. Start | 57 | ** Installation |
| 60 | by simply cloning the repository. | 58 | :PROPERTIES: |
| 59 | :CUSTOM_ID: installation | ||
| 60 | :END: | ||
| 61 | Now that we have all of the dependencies we need, we can install =scli=. | ||
| 62 | Start by simply cloning the repository. | ||
| 61 | 63 | ||
| 62 | ```sh | 64 | #+begin_src sh |
| 63 | git clone https://github.com/isamert/scli | 65 | git clone https://github.com/isamert/scli |
| 64 | ``` | 66 | #+end_src |
| 65 | 67 | ||
| 66 | When I cloned this repository on 2023-11-08, I found a bug in the logic that | 68 | When I cloned this repository on 2023-11-08, I found a bug in the logic |
| 67 | required a fix. You must edit the `scli` file and replace the one instance of | 69 | that required a fix. You must edit the =scli= file and replace the one |
| 68 | `RLIMIT_OFILE` with `RLIMIT_NOFILE`. | 70 | instance of =RLIMIT_OFILE= with =RLIMIT_NOFILE=. |
| 69 | 71 | ||
| 70 | ```sh | 72 | #+begin_src sh |
| 71 | cd scli | 73 | cd scli |
| 72 | nano scli | 74 | nano scli |
| 73 | ``` | 75 | #+end_src |
| 74 | 76 | ||
| 75 | Once complete, you can move this program to anywhere on your `$PATH`. I chose | 77 | Once complete, you can move this program to anywhere on your =$PATH=. I |
| 76 | the following directory. | 78 | chose the following directory. |
| 77 | 79 | ||
| 78 | ```sh | 80 | #+begin_src sh |
| 79 | doas mv scli /usr/local/bin/scli | 81 | doas mv scli /usr/local/bin/scli |
| 80 | ``` | 82 | #+end_src |
| 81 | 83 | ||
| 82 | ## Initial Setup | 84 | ** Initial Setup |
| 85 | :PROPERTIES: | ||
| 86 | :CUSTOM_ID: initial-setup | ||
| 87 | :END: | ||
| 88 | Now that everything is installed, we can login and configure the client. | ||
| 89 | Start by generating a connection link. | ||
| 83 | 90 | ||
| 84 | Now that everything is installed, we can login and configure the client. Start | 91 | #+begin_src sh |
| 85 | by generating a connection link. | ||
| 86 | |||
| 87 | ```sh | ||
| 88 | signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8) | 92 | signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8) |
| 89 | ``` | 93 | #+end_src |
| 90 | 94 | ||
| 91 | This will generate a connection link and related QR code for you to use to link | 95 | This will generate a connection link and related QR code for you to use |
| 92 | the devices together. Once complete, **wait patiently** for the connection | 96 | to link the devices together. Once complete, *wait patiently* for the |
| 93 | process to finish. | 97 | connection process to finish. |
| 94 | 98 | ||
| 95 | Once it completes, it will exit and return you to the prompt. From here, you | 99 | Once it completes, it will exit and return you to the prompt. From here, |
| 96 | need to perform an initial `receive` command to start things off. The `USERNAME` | 100 | you need to perform an initial =receive= command to start things off. |
| 97 | variable should be your phone number, such as `+15551237890`. | 101 | The =USERNAME= variable should be your phone number, such as |
| 102 | =+15551237890=. | ||
| 98 | 103 | ||
| 99 | ```sh | 104 | #+begin_src sh |
| 100 | signal-cli -u USERNAME receive | 105 | signal-cli -u USERNAME receive |
| 101 | ``` | 106 | #+end_src |
| 102 | 107 | ||
| 103 | Also be sure to test the daemon to ensure it works properly. If no errors occur, | 108 | Also be sure to test the daemon to ensure it works properly. If no |
| 104 | it's working. If you run into errors because you're not running a DBUS session, | 109 | errors occur, it's working. If you run into errors because you're not |
| 105 | see my notes below. | 110 | running a DBUS session, see my notes below. |
| 106 | 111 | ||
| 107 | ```sh | 112 | #+begin_src sh |
| 108 | signal-cli -u USERNAME daemon | 113 | signal-cli -u USERNAME daemon |
| 109 | ``` | 114 | #+end_src |
| 110 | 115 | ||
| 111 | Once the initial reception is complete, you are ready to use `scli`. | 116 | Once the initial reception is complete, you are ready to use =scli=. |
| 112 | 117 | ||
| 113 | This process will differ depending on your desktop environment (DE). If you are | 118 | This process will differ depending on your desktop environment (DE). If |
| 114 | running a DE, you likely have a DBUS session running already and can simply | 119 | you are running a DE, you likely have a DBUS session running already and |
| 115 | launch the program. | 120 | can simply launch the program. |
| 116 | 121 | ||
| 117 | However, if you're like me and running your computer straight on the TTY without | 122 | However, if you're like me and running your computer straight on the TTY |
| 118 | a DE, you'll need to start a DBUS session for this program. | 123 | without a DE, you'll need to start a DBUS session for this program. |
| 119 | 124 | ||
| 120 | ```sh | 125 | #+begin_src sh |
| 121 | # If you're not running a DBUS session yet, you need to start one for scli | 126 | # If you're not running a DBUS session yet, you need to start one for scli |
| 122 | dbus-run-session -- scli | 127 | dbus-run-session -- scli |
| 123 | 128 | ||
| 124 | # OR - If you're already running a DBUS session, simply run scli | 129 | # OR - If you're already running a DBUS session, simply run scli |
| 125 | scli | 130 | scli |
| 126 | ``` | 131 | #+end_src |
| 127 | |||
| 128 | ## Configuration | ||
| 129 | 132 | ||
| 130 | Lastly, there are a number of configuration options that you can pass via the | 133 | ** Configuration |
| 131 | command or in the `~/.config/sclirc` file. See the Github README for more | 134 | :PROPERTIES: |
| 132 | information on configuration options. | 135 | :CUSTOM_ID: configuration |
| 136 | :END: | ||
| 137 | Lastly, there are a number of configuration options that you can pass | ||
| 138 | via the command or in the =~/.config/sclirc= file. See the Github README | ||
| 139 | for more information on configuration options. | ||
| 133 | 140 | ||
| 134 | ```sh | 141 | #+begin_src sh |
| 135 | nano ~/.config/sclirc | 142 | nano ~/.config/sclirc |
| 136 | ``` | 143 | #+end_src |
| 137 | 144 | ||
| 138 | ```conf | 145 | #+begin_src conf |
| 139 | # ~/.config/sclirc | 146 | # ~/.config/sclirc |
| 140 | 147 | ||
| 141 | wrap-at = 80 | 148 | wrap-at = 80 |
| 142 | enable-notifications = true | 149 | enable-notifications = true |
| 143 | ``` | 150 | #+end_src |
| 144 | 151 | ||
| 145 | That's it! Following this guide, I have a functional `scli` program that | 152 | That's it! Following this guide, I have a functional =scli= program that |
| 146 | successfully sends messages to my contacts and myself! | 153 | successfully sends messages to my contacts and myself! |