<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Self Hosted &#8211; hackanooga</title>
	<atom:link href="/category/self-hosted/feed/" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>Confessions of a homelab hacker</description>
	<lastBuildDate>Wed, 25 Sep 2024 13:56:04 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>

<image>
	<url>/wp-content/uploads/2024/03/cropped-cropped-avatar-32x32.png</url>
	<title>Self Hosted &#8211; hackanooga</title>
	<link>/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Standing up a Wireguard VPN</title>
		<link>/standing-up-a-wireguard-vpn/</link>
		
		<dc:creator><![CDATA[mikeconrad]]></dc:creator>
		<pubDate>Wed, 25 Sep 2024 13:56:04 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Self Hosted]]></category>
		<category><![CDATA[Software Engineering]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[Blog Post]]></category>
		<guid isPermaLink="false">/?p=619</guid>

					<description><![CDATA[VPN&#8217;s have traditionally been slow, complex and hard to set up and configure. That all changed several years ago when Wireguard was officially merged into the mainline Linux kernel (src). I won&#8217;t go over all the reasons for why you should want to use Wireguard in this article, instead I will be focusing on just [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>VPN&#8217;s have traditionally been slow, complex and hard to set up and configure.  That all changed several years ago when Wireguard was officially merged into the mainline Linux kernel (<a href="https://arstechnica.com/gadgets/2020/03/wireguard-vpn-makes-it-to-1-0-0-and-into-the-next-linux-kernel/" data-type="link" data-id="https://arstechnica.com/gadgets/2020/03/wireguard-vpn-makes-it-to-1-0-0-and-into-the-next-linux-kernel/">src</a>).  I won&#8217;t go over all the reasons for why you should want to use Wireguard in this article, instead I will be focusing on just how easy it is to set up and configure. </p>



<p>For this tutorial we will be using Terraform to stand up a Digital Ocean droplet and then install Wireguard onto that. The Digital Ocean droplet will be acting as our &#8220;server&#8221; in this example and we will be using our own computer as the &#8220;client&#8221;. Of course, you don&#8217;t have to use Terraform, you just need a Linux box to install Wireguard on.  You can find the code for this tutorial on my personal Git server <a href="https://git.hackanooga.com/mikeconrad/wireguard-terraform-digitalocean" data-type="link" data-id="https://git.hackanooga.com/mikeconrad/wireguard-terraform-digitalocean">here</a>.</p>



<h3 class="wp-block-heading">Create Droplet with Terraform</h3>



<p>I have written some very basic Terraform to get us started. The Terraform is very basic and just creates a droplet with a predefined ssh key and a setup script passed as user data.  When the droplet gets created, the script will get copied to the instance and automatically executed.  After a few minutes everything should be ready to go.  If you want to clone the repo above, feel free to, or if you would rather do everything by hand that&#8217;s great too.  I will assume that you are doing everything by hand.  The process of deploying from the repo should be pretty self explainitory.  My reasoning for doing it this way is because I wanted to better understand the process.  </p>



<p>First create our main.tf with the following contents:</p>



<pre class="wp-block-code"><code># main.tf
# Attach an SSH key to our droplet
resource "digitalocean_ssh_key" "default" {
  name       = "Terraform Example"
  public_key = file("./tf-digitalocean.pub")
}

# Create a new Web Droplet in the nyc1 region
resource "digitalocean_droplet" "web" {
  image    = "ubuntu-22-04-x64"
  name     = "wireguard"
  region   = "nyc1"
  size     = "s-2vcpu-4gb"
  ssh_keys = &#91;digitalocean_ssh_key.default.fingerprint]
  user_data = file("setup.sh")
}

output "droplet_output" {
  value = digitalocean_droplet.web.ipv4_address
}</code></pre>



<p>Next create a terraform.tf file in the same directory with the following contents:</p>



<pre class="wp-block-code"><code>terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "2.41.0"
    }
  }
}

provider "digitalocean" {
}
</code></pre>



<p>Now we will need to create the ssh key that we defined in our Terraform code.  </p>



<pre class="wp-block-code"><code>$ ssh-keygen -t rsa -C "WireguardVPN" -f ./tf-digitalocean -q -N ""</code></pre>



<p>Next we need to set an environment variable for our DigitalOcean access token.</p>



<pre class="wp-block-code"><code>$ export DIGITALOCEAN_ACCESS_TOKEN=dop_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</code></pre>



<p>Now we are ready to initialize our Terraform and apply it:</p>



<pre class="wp-block-code"><code>$ terraform init
$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # digitalocean_droplet.web will be created
  + resource "digitalocean_droplet" "web" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + graceful_shutdown    = false
      + id                   = (known after apply)
      + image                = "ubuntu-22-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "wireguard"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = (known after apply)
      + region               = "nyc1"
      + resize_disk          = true
      + size                 = "s-2vcpu-4gb"
      + ssh_keys             = (known after apply)
      + status               = (known after apply)
      + urn                  = (known after apply)
      + user_data            = "69d130f386b262b136863be5fcffc32bff055ac0"
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)
    }

  # digitalocean_ssh_key.default will be created
  + resource "digitalocean_ssh_key" "default" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + name        = "Terraform Example"
      + public_key  = &lt;&lt;-EOT
            ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXOBlFdNqV48oxWobrn2rPt4y1FTqrqscA5bSu2f3CogwbDKDyNglXu8RL4opjfdBHQES+pEqvt21niqes8z2QsBTF3TRQ39SaHM8wnOTeC8d0uSgyrp9b7higHd0SDJVJZT0Bz5AlpYfCO/gpEW51XrKKeud7vImj8nGPDHnENN0Ie0UVYZ5+V1zlr0BBI7LX01MtzUOgSldDX0lif7IZWW4XEv40ojWyYJNQwO/gwyDrdAq+kl+xZu7LmBhngcqd02+X6w4SbdgYg2flu25Td0MME0DEsXKiZYf7kniTrKgCs4kJAmidCDYlYRt43dlM69pB5jVD/u4r3O+erTapH/O1EDhsdA9y0aYpKOv26ssYU+ZXK/nax+Heu0giflm7ENTCblKTPCtpG1DBthhX6Ml0AYjZF1cUaaAvpN8UjElxQ9r+PSwXloSnf25/r9UOBs1uco8VDwbx5cM0SpdYm6ERtLqGRYrG2SDJ8yLgiCE9EK9n3uQExyrTMKWzVAc= WireguardVPN
        EOT
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + droplet_output = (known after apply)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

digitalocean_ssh_key.default: Creating...
digitalocean_ssh_key.default: Creation complete after 1s &#91;id=43499750]
digitalocean_droplet.web: Creating...
digitalocean_droplet.web: Still creating... &#91;10s elapsed]
digitalocean_droplet.web: Still creating... &#91;20s elapsed]
digitalocean_droplet.web: Still creating... &#91;30s elapsed]
digitalocean_droplet.web: Creation complete after 31s &#91;id=447469336]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

droplet_output = "159.223.113.207"
</code></pre>



<p>All pretty standard stuff.  Nice!  It only took about 30 seconds or so on my machine to spin up a droplet and start provisioning it.  It is worth noting that the setup script will take a few minutes to run.  Before we log into our new droplet, let&#8217;s take a quick look at the setup script that we are running.</p>



<pre class="wp-block-code"><code>#!/usr/bin/env sh
set -e
set -u
# Set the listen port used by Wireguard, this is the default so feel free to change it.
LISTENPORT=51820
CONFIG_DIR=/root/wireguard-conf
umask 077
mkdir -p $CONFIG_DIR/client

# Install wireguard
apt update &amp;&amp; apt install -y wireguard

# Generate public/private key for the "server".
wg genkey > $CONFIG_DIR/privatekey
wg pubkey &lt; $CONFIG_DIR/privatekey > $CONFIG_DIR/publickey

# Generate public/private key for the "client"
wg genkey > $CONFIG_DIR/client/privatekey
wg pubkey &lt; $CONFIG_DIR/client/privatekey > $CONFIG_DIR/client/publickey


# Generate server config
echo "&#91;Interface]
Address = 10.66.66.1/24,fd42:42:42::1/64
ListenPort = $LISTENPORT
PrivateKey = $(cat $CONFIG_DIR/privatekey)

### Client config
&#91;Peer]
PublicKey = $(cat $CONFIG_DIR/client/publickey)
AllowedIPs = 10.66.66.2/32,fd42:42:42::2/128
" > /etc/wireguard/do.conf


# Generate client config.  This will need to be copied to your machine.
echo "&#91;Interface]
PrivateKey = $(cat $CONFIG_DIR/client/privatekey)
Address = 10.66.66.2/32,fd42:42:42::2/128
DNS = 1.1.1.1,1.0.0.1

&#91;Peer]
PublicKey = $(cat publickey)
Endpoint = $(curl icanhazip.com):$LISTENPORT
AllowedIPs = 0.0.0.0/0,::/0
" > client-config.conf

wg-quick up do

# Add iptables rules to forward internet traffic through this box
# We are assuming our Wireguard interface is called do and our
# primary public facing interface is called eth0.

iptables -I INPUT -p udp --dport 51820 -j ACCEPT
iptables -I FORWARD -i eth0 -o do -j ACCEPT
iptables -I FORWARD -i do -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
ip6tables -I FORWARD -i do -j ACCEPT
ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# Enable routing on the server
echo "net.ipv4.ip_forward = 1
      net.ipv6.conf.all.forwarding = 1" >/etc/sysctl.d/wg.conf
sysctl --system</code></pre>



<p>As you can see, it is pretty straightforward.  All you really need to do is:</p>



<p>On the &#8220;server&#8221; side:</p>



<ol class="wp-block-list">
<li>Generate a private key and derive a public key from it for both the &#8220;server&#8221; and the &#8220;client&#8221;.</li>



<li>Create a &#8220;server&#8221; config that tells the droplet what address to bind to for the wireguard interface, which private key to use to secure that interface and what port to listen on.</li>



<li>The &#8220;server&#8221; config also needs to know what peers or &#8220;clients&#8221; to accept connections from in the AllowedIPs block.  In this case we are just specifying one.  The &#8220;server&#8221; also needs to know the public key of the &#8220;client&#8221; that will be connecting.</li>
</ol>



<p>On the &#8220;client&#8221; side:</p>



<ol class="wp-block-list">
<li>Create a &#8220;client&#8221; config that tells our machine what address to assign to the wireguard interface (obviously needs to be on the same subnet as the interface on the server side).</li>



<li>The client needs to know which private key to use to secure the interface.</li>



<li>It also needs to know the public key of the server as well as the public IP address/hostname of the &#8220;server&#8221; it is connecting to as well as the port it is listening on.  </li>



<li>Finally it needs to know what traffic to route over the wireguard interface.  In this example we are simply routing all traffic but you could restrict this as you see fit.</li>
</ol>



<p>Now that we have our configs in place, we need to copy the client config to our local machine.  The following command should work as long as you make sure to replace the IP address with the IP address of your newly created droplet:</p>



<pre class="wp-block-code"><code>## Make sure you have Wireguard installed on your local machine as well.
## https://wireguard.com/install

## Copy the client config to our local machine and move it to our wireguard directory.
$ ssh -i tf-digitalocean root@157.230.177.54 -- cat /root/wireguard-conf/client-config.conf| sudo tee /etc/wireguard/do.conf</code></pre>



<p>Before we try to connect, let&#8217;s log into the server and make sure everything is set up correctly:</p>



<pre class="wp-block-code"><code>$ ssh -i tf-digitalocean root@159.223.113.207
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-113-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
 * Management:     https://landscape.canonical.com/
 * Support:        https://ubuntu.com/pro

 System information as of Wed Sep 25 13:19:02 UTC 2024

  System load:  0.03              Processes:             113
  Usage of /:   2.1% of 77.35GB   Users logged in:       0
  Memory usage: 6%                IPv4 address for eth0: 157.230.221.196
  Swap usage:   0%                IPv4 address for eth0: 10.10.0.5

Expanded Security Maintenance for Applications is not enabled.

70 updates can be applied immediately.
40 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status

New release '24.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.


Last login: Wed Sep 25 13:16:25 2024 from 74.221.191.214
root@wireguard:~#

</code></pre>



<p>Awesome!  We are connected.  Now let&#8217;s check the wireguard interface using the <code>wg</code> command.  If our config was correct, we should see an interface line and 1 peer line like so.  If the peer line is missing then something is wrong with the configuration.  Most likely a mismatch between public/private key.:</p>



<pre class="wp-block-code"><code>root@wireguard:~# wg
interface: do
  public key: fTvqo/cZVofJ9IZgWHwU6XKcIwM/EcxUsMw4voeS/Hg=
  private key: (hidden)
  listening port: 51820

peer: 5RxMenh1L+rNJobROkUrub4DBUj+nEUPKiNe4DFR8iY=
  allowed ips: 10.66.66.2/32, fd42:42:42::2/128
root@wireguard:~# </code></pre>



<p>So now we should be ready to go!  On your local machine go ahead and try it out:</p>



<pre class="wp-block-code"><code>## Start the interface with wg-quick up &#91;interface_name]
$ sudo wg-quick up do
&#91;sudo] password for mikeconrad: 
&#91;#] ip link add do type wireguard
&#91;#] wg setconf do /dev/fd/63
&#91;#] ip -4 address add 10.66.66.2/32 dev do
&#91;#] ip -6 address add fd42:42:42::2/128 dev do
&#91;#] ip link set mtu 1420 up dev do
&#91;#] resolvconf -a do -m 0 -x
&#91;#] wg set do fwmark 51820
&#91;#] ip -6 route add ::/0 dev do table 51820
&#91;#] ip -6 rule add not fwmark 51820 table 51820
&#91;#] ip -6 rule add table main suppress_prefixlength 0
&#91;#] ip6tables-restore -n
&#91;#] ip -4 route add 0.0.0.0/0 dev do table 51820
&#91;#] ip -4 rule add not fwmark 51820 table 51820
&#91;#] ip -4 rule add table main suppress_prefixlength 0
&#91;#] sysctl -q net.ipv4.conf.all.src_valid_mark=1
&#91;#] iptables-restore -n

## Check our config
$ sudo wg
interface: do
  public key: fJ8mptCR/utCR4K2LmJTKTjn3xc4RDmZ3NNEQGwI7iI=
  private key: (hidden)
  listening port: 34596
  fwmark: 0xca6c

peer: duTHwMhzSZxnRJ2GFCUCHE4HgY5tSeRn9EzQt9XVDx4=
  endpoint: 157.230.177.54:51820
  allowed ips: 0.0.0.0/0, ::/0
  latest handshake: 1 second ago
  transfer: 1.82 KiB received, 2.89 KiB sent

## Make sure we can ping the outside world
mikeconrad@pop-os:~/projects/wireguard-terraform-digitalocean$ ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=56 time=28.0 ms
^C
--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 27.991/27.991/27.991/0.000 ms

## Verify our traffic is actually going over the tunnel.
$ curl icanhazip.com
157.230.177.54


</code></pre>



<p>We should also be able to ssh into our instance over the VPN using the <code>10.66.66.1</code> address:</p>



<pre class="wp-block-code"><code>$ ssh -i tf-digitalocean root@10.66.66.1
The authenticity of host '10.66.66.1 (10.66.66.1)' can't be established.
ED25519 key fingerprint is SHA256:E7BKSO3qP+iVVXfb/tLaUfKIc4RvtZ0k248epdE04m8.
This host key is known by the following other names/addresses:
    ~/.ssh/known_hosts:130: &#91;hashed name]
Are you sure you want to continue connecting (yes/no/&#91;fingerprint])? yes
Warning: Permanently added '10.66.66.1' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-113-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
 * Management:     https://landscape.canonical.com/
 * Support:        https://ubuntu.com/pro

 System information as of Wed Sep 25 13:32:12 UTC 2024

  System load:  0.02              Processes:             109
  Usage of /:   2.1% of 77.35GB   Users logged in:       0
  Memory usage: 6%                IPv4 address for eth0: 157.230.177.54
  Swap usage:   0%                IPv4 address for eth0: 10.10.0.5

Expanded Security Maintenance for Applications is not enabled.

73 updates can be applied immediately.
40 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status

New release '24.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.


root@wireguard:~# 
</code></pre>



<p>Looks like everything is working!  If you run the script from the repo you will have a fully functioning Wireguard VPN in less than 5 minutes!  Pretty cool stuff!  This article was not meant to be exhaustive but instead a simple primer to get your feet wet.  The setup script I used is heavily inspired by <a href="https://github.com/angristan/wireguard-install">angristan/wireguard-install</a>.  Another great resource is the <a href="https://github.com/pirate/wireguard-docs" data-type="link" data-id="https://github.com/pirate/wireguard-docs">Unofficial docs repo</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>SFTP Server Setup for Daily Inventory File Transfers</title>
		<link>/sftp-server-setup-for-daily-inventory-file-transfers/</link>
		
		<dc:creator><![CDATA[mikeconrad]]></dc:creator>
		<pubDate>Wed, 17 Jul 2024 02:15:23 +0000</pubDate>
				<category><![CDATA[Cloudflare]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Self Hosted]]></category>
		<category><![CDATA[Upwork Project]]></category>
		<guid isPermaLink="false">/?p=599</guid>

					<description><![CDATA[Job Description We are looking for an experienced professional to help us set up an SFTP server that will allow our vendors to send us inventory files on a daily basis. The server should ensure secure and reliable file transfers, allowing our vendors to easily upload their inventory updates. The successful candidate will possess expertise [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading">Job Description</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>We are looking for an experienced professional to help us set up an SFTP server that will allow our vendors to send us inventory files on a daily basis. The server should ensure secure and reliable file transfers, allowing our vendors to easily upload their inventory updates. The successful candidate will possess expertise in SFTP server setup and configuration, as well as knowledge of network security protocols. The required skills for this job include:</p>



<p>&#8211; SFTP server setup and configuration<br>&#8211; Network security protocols<br>&#8211; Troubleshooting and problem-solving skills</p>



<p>If you have demonstrated experience in setting up SFTP servers and ensuring smooth daily file transfers, we would love to hear from you.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p></p>



<h3 class="wp-block-heading">My Role</h3>



<p>I walked the client through the process of setting up a Digital Ocean account.  I created a <code>Ubuntu 22.04</code> VM and installed <a href="https://github.com/drakkan/sftpgo">SFTPGo</a>.  I set the client up with an administrator user so that they could easily login and manage users and shares.  I implemented some basic security practices as well and set the client up with a custom domain and free TLS/SSL certificate from LetsEncrypt.  With the documentation and screenshots I provided the client, they were able to get everything up and running and add users and connect other systems easily and securly.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p></p>



<h2 class="wp-block-heading">Client Feedback</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Rating is 5 out of 5.</p>



<p>Michael was EXTREMELY helpful and great to work with. We really benefited from his support and help with everything.</p>
</blockquote>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Debugging running Nginx config</title>
		<link>/debugging-running-nginx-config/</link>
		
		<dc:creator><![CDATA[mikeconrad]]></dc:creator>
		<pubDate>Wed, 17 Jul 2024 01:42:43 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Networking]]></category>
		<category><![CDATA[Self Hosted]]></category>
		<category><![CDATA[Blog Post]]></category>
		<guid isPermaLink="false">/?p=596</guid>

					<description><![CDATA[I was recently working on project where a client had cPanel/WHM with Nginx and Apache. They had a large number of sites managed by Nginx with a large number of includes. I created a custom config to override a location block and needed to be certain that my changes where actually being picked up. Anytime [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I was recently working on project where a client had cPanel/WHM with Nginx and Apache.  They had a large number of sites managed by Nginx with a large number of includes.  I created a custom config to override a location block and needed to be certain that my changes where actually being picked up.  Anytime I make changes to an Nginx config, I try to be vigilant about running:</p>



<pre class="wp-block-code"><code>nginx -t</code></pre>



<p>to test my configuration and ensure I don&#8217;t have any syntax errors.  I was looking for an easy way to view the actual compiled config and found the <code>-T</code> flag which will test the configuration and dump it to standard out.  This is pretty handy if you have a large number of includes in various locations.  Here is an example from a fresh Nginx Docker container:</p>



<pre class="wp-block-code"><code>root@2771f302dc98:/# nginx -T
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user &#91;$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

# configuration file /etc/nginx/mime.types:

types {
    text/html                                        html htm shtml;
    text/css                                         css;
    text/xml                                         xml;
    image/gif                                        gif;
    image/jpeg                                       jpeg jpg;
    application/javascript                           js;
    application/atom+xml                             atom;
    application/rss+xml                              rss;

    text/mathml                                      mml;
    text/plain                                       txt;
    text/vnd.sun.j2me.app-descriptor                 jad;
    text/vnd.wap.wml                                 wml;
    text/x-component                                 htc;

    image/avif                                       avif;
    image/png                                        png;
    image/svg+xml                                    svg svgz;
    image/tiff                                       tif tiff;
    image/vnd.wap.wbmp                               wbmp;
    image/webp                                       webp;
    image/x-icon                                     ico;
    image/x-jng                                      jng;
    image/x-ms-bmp                                   bmp;

    font/woff                                        woff;
    font/woff2                                       woff2;

    application/java-archive                         jar war ear;
    application/json                                 json;
    application/mac-binhex40                         hqx;
    application/msword                               doc;
    application/pdf                                  pdf;
    application/postscript                           ps eps ai;
    application/rtf                                  rtf;
    application/vnd.apple.mpegurl                    m3u8;
    application/vnd.google-earth.kml+xml             kml;
    application/vnd.google-earth.kmz                 kmz;
    application/vnd.ms-excel                         xls;
    application/vnd.ms-fontobject                    eot;
    application/vnd.ms-powerpoint                    ppt;
    application/vnd.oasis.opendocument.graphics      odg;
    application/vnd.oasis.opendocument.presentation  odp;
    application/vnd.oasis.opendocument.spreadsheet   ods;
    application/vnd.oasis.opendocument.text          odt;
    application/vnd.openxmlformats-officedocument.presentationml.presentation
                                                     pptx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
                                                     xlsx;
    application/vnd.openxmlformats-officedocument.wordprocessingml.document
                                                     docx;
    application/vnd.wap.wmlc                         wmlc;
    application/wasm                                 wasm;
    application/x-7z-compressed                      7z;
    application/x-cocoa                              cco;
    application/x-java-archive-diff                  jardiff;
    application/x-java-jnlp-file                     jnlp;
    application/x-makeself                           run;
    application/x-perl                               pl pm;
    application/x-pilot                              prc pdb;
    application/x-rar-compressed                     rar;
    application/x-redhat-package-manager             rpm;
    application/x-sea                                sea;
    application/x-shockwave-flash                    swf;
    application/x-stuffit                            sit;
    application/x-tcl                                tcl tk;
    application/x-x509-ca-cert                       der pem crt;
    application/x-xpinstall                          xpi;
    application/xhtml+xml                            xhtml;
    application/xspf+xml                             xspf;
    application/zip                                  zip;

    application/octet-stream                         bin exe dll;
    application/octet-stream                         deb;
    application/octet-stream                         dmg;
    application/octet-stream                         iso img;
    application/octet-stream                         msi msp msm;

    audio/midi                                       mid midi kar;
    audio/mpeg                                       mp3;
    audio/ogg                                        ogg;
    audio/x-m4a                                      m4a;
    audio/x-realaudio                                ra;

    video/3gpp                                       3gpp 3gp;
    video/mp2t                                       ts;
    video/mp4                                        mp4;
    video/mpeg                                       mpeg mpg;
    video/quicktime                                  mov;
    video/webm                                       webm;
    video/x-flv                                      flv;
    video/x-m4v                                      m4v;
    video/x-mng                                      mng;
    video/x-ms-asf                                   asx asf;
    video/x-ms-wmv                                   wmv;
    video/x-msvideo                                  avi;
}

# configuration file /etc/nginx/conf.d/default.conf:
server {
    listen       80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;/
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
</code></pre>



<p>As you can see from the output above, we get all of the various Nginx config files in use printed to the console, perfect for <code>grepping</code> or searching/filtering with other tools.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Self hosted package registries with Gitea</title>
		<link>/self-hosted-package-registries-with-gitea/</link>
		
		<dc:creator><![CDATA[mikeconrad]]></dc:creator>
		<pubDate>Thu, 07 Mar 2024 15:07:07 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[OCI]]></category>
		<category><![CDATA[Self Hosted]]></category>
		<category><![CDATA[Blog Post]]></category>
		<guid isPermaLink="false">https://wordpress.hackanooga.com/?p=413</guid>

					<description><![CDATA[I am a big proponent of open source technologies. I have been using Gitea for a couple years now in my homelab. A few years ago I moved most of my code off of Github and onto my self hosted instance. I recently came across a really handy feature that I didn&#8217;t know Gitea had [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I am a big proponent of open source technologies.  I have been using <a href="https://about.gitea.com/" data-type="link" data-id="https://about.gitea.com/">Gitea</a> for a couple years now in my homelab.  A few years ago I moved most of my code off of Github and onto my self hosted instance.  I recently came across a really handy feature that I didn&#8217;t know Gitea had and was pleasantly surprised by: <a href="https://docs.gitea.com/usage/packages/overview?_highlight=packag" data-type="link" data-id="https://docs.gitea.com/usage/packages/overview?_highlight=packag">Package Registry</a>.  You are no doubt familiar with what a package registry is in the broad context.  Here are some examples of package registries you probably use on a regular basis:</p>



<ul class="wp-block-list">
<li>npm</li>



<li>cargo</li>



<li>docker</li>



<li>composer</li>



<li>nuget</li>



<li>helm</li>
</ul>



<p>There are a number of reasons why you would want to self host a registry.  For example, in my home lab I have some <code>Docker</code> images that are specific to my use cases and I don&#8217;t necessarily want them on a public registry.  I&#8217;m also not concerned about losing the artifacts as I can easily recreate them from code.  Gitea makes this really easy to setup, in fact it comes baked in with the installation.  For the sake of this post I will just assume that you already have Gitea installed and setup.  </p>



<p>Since the package registry is baked in and enabled by default, I will demonstrate how easy it is to push a docker image.  We will pull the default <code>alpine</code> image, re-tag it and push it to our internal registry:<br> </p>



<pre class="wp-block-code"><code># Pull the official Alpine image
docker pull alpine:latest

# Re tag the image with our local registry information
docker tag alpine:latest git.hackanooga.com/mikeconrad/alpine:latest

# Login using your gitea user account
docker login git.hackanooga.com

# Push the image to our registry
docker push git.hackanooga.com/mikeconrad/alpine:latest

</code></pre>



<p>Now log into your Gitea instance, navigate to your user account and look for <code>packages</code>.  You should see the newly uploaded alpine image.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="313" src="https://wordpress.hackanooga.com/wp-content/uploads/2024/03/image-6-1024x313.png" alt="" class="wp-image-414" srcset="/wp-content/uploads/2024/03/image-6-1024x313.png 1024w, /wp-content/uploads/2024/03/image-6-300x92.png 300w, /wp-content/uploads/2024/03/image-6-768x235.png 768w, /wp-content/uploads/2024/03/image-6-1536x469.png 1536w, /wp-content/uploads/2024/03/image-6.png 1693w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>You can see that the package type is container.  Clicking on it will give you more information:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="437" src="https://wordpress.hackanooga.com/wp-content/uploads/2024/03/image-7-1024x437.png" alt="" class="wp-image-415" srcset="/wp-content/uploads/2024/03/image-7-1024x437.png 1024w, /wp-content/uploads/2024/03/image-7-300x128.png 300w, /wp-content/uploads/2024/03/image-7-768x328.png 768w, /wp-content/uploads/2024/03/image-7-1536x655.png 1536w, /wp-content/uploads/2024/03/image-7.png 1636w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Traefik with Let&#8217;s Encrypt and Cloudflare (pt 1)</title>
		<link>/traefik-with-lets-encrypt-and-cloudflare-pt-1/</link>
		
		<dc:creator><![CDATA[mikeconrad]]></dc:creator>
		<pubDate>Thu, 01 Feb 2024 19:35:00 +0000</pubDate>
				<category><![CDATA[Cloudflare]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Self Hosted]]></category>
		<category><![CDATA[Traefik]]></category>
		<category><![CDATA[Blog Post]]></category>
		<guid isPermaLink="false">https://wordpress.hackanooga.com/?p=422</guid>

					<description><![CDATA[Recently I decided to rebuild one of my homelab servers. Previously I was using Nginx as my reverse proxy but I decided to switch to Traefik since I have been using it professionally for some time now. One of the reasons I like Traefik is that it is stupid simple to set up certificates and [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Recently I decided to rebuild one of my homelab servers.  Previously I was using Nginx as my reverse proxy but I decided to switch to Traefik since I have been using it professionally for some time now.  One of the reasons I like Traefik is that it is stupid simple to set up certificates and when I am using it with Docker I don&#8217;t have to worry about a bunch of configuration files.  If you aren&#8217;t familiar with how Traefik works with Docker, here is a brief example of a <code>docker-compose.yaml</code></p>



<pre class="wp-block-code"><code>version: '3'

services:
  reverse-proxy:
    # The official v2 Traefik docker image
    image: traefik:v2.11
    # Enables the web UI and tells Traefik to listen to docker
    command:
      - --api.insecure=true
      - --providers.docker=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      # Set up LetsEncrypt
      - --certificatesresolvers.letsencrypt.acme.dnschallenge=true
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
      - --certificatesresolvers.letsencrypt.acme.email=user@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      # Redirect all http requests to https
      - --entryPoints.web.http.redirections.entryPoint.to=websecure
      - --entryPoints.web.http.redirections.entryPoint.scheme=https
      - --entryPoints.web.http.redirections.entrypoint.permanent=true
      - --log=true
      - --log.level=INFO
    # Needed to request certs via lets encrypt
    environment:
      - CF_DNS_API_TOKEN=&#91;redacted]
    ports:
      # The HTTP port
      - "80:80"
      - "443:443"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock:ro
      # Used for storing letsencrypt certificates
      - ./letsencrypt:/letsencrypt
      - ./volumes/traefik/logs:/logs
    networks:
      - traefik
  ots:
    image: luzifer/ots
    container_name: ots
    restart: always
    environment:
      REDIS_URL: redis://redis:6379/0
      SECRET_EXPIRY: "604800"
      STORAGE_TYPE: redis
    depends_on:
      - redis
    labels:
      - traefik.enable=true
      - traefik.http.routers.ots.rule=Host(`ots.example.com`)
      - traefik.http.routers.ots.entrypoints=websecure
      - traefik.http.routers.ots.tls=true
      - traefik.http.routers.ots.tls.certresolver=letsencrypt
      - traefik.http.services.ots.loadbalancer.server.port=3000
    networks:
      - traefik
  redis:
    image: redis:alpine
    restart: always
    volumes:
      - ./redis-data:/data
    networks:
      - traefik
networks:
  traefik:
    external: true


</code></pre>



<p>In part one of this series I will be going over some of the basics of Traefik and how dynamic routing works.  If you want to skip to the good stuff and get everything configured with Cloudflare, you can skip to <a href="https://wordpress.hackanooga.com/traefik-with-lets-encrypt-and-cloudflare-pt-2/" data-type="post" data-id="425">part 2</a>.</p>



<p>This example set&#8217;s up the primary Traefik container which acts as the ingress controller as well as a  handy One Time Secret sharing <a href="https://github.com/Luzifer/ots">service</a> I use.  Traefik handles routing in Docker via labels.  For this to work properly the services that Traefik is trying to route to all need to be on the same Docker network.  For this example we created a network called traefik by running the following:</p>



<pre class="wp-block-code"><code>docker network create traefik
</code></pre>



<p>Let&#8217;s take a look at the labels we applied to the <code>ots</code> container a little closer:</p>



<pre class="wp-block-code"><code>    labels:
      - traefik.enable=true
      - traefik.http.routers.ots.rule=Host(`ots.example.com`)
      - traefik.http.routers.ots.entrypoints=websecure
      - traefik.http.routers.ots.tls=true
      - traefik.http.routers.ots.tls.certresolver=letsencrypt
      - traefik.http.services.ots.loadbalancer.server.port=3000</code></pre>



<p><code>traefik.enable=true</code> &#8211; This should be pretty self explanatory but it tells Traefik that we want it to know about this service.</p>



<p><code>traefik.http.routers.ots.rule=Host('ots.example.com') - This is where some of the magic comes in.</code>  Here we are defining a router called <code>ots</code>.  The name is arbitrary in that it doesn&#8217;t have to match the name of the service but for our example it does.  There are many rules that you can specify but the easiest for this example is host.  Basically we are saying that any request coming in for ots.example.com should be picked up by this router.  You can find more options for routers in the Traefik <a href="https://doc.traefik.io/traefik/routing/routers/">docs</a>.</p>



<p>&#8211; traefik.http.routers.ots.entrypoints=websecure<br>&#8211; traefik.http.routers.ots.tls=true<br>&#8211; traefik.http.routers.ots.tls.certresolver=letsencrypt<br><br>We are using these three labels to tell our router that we want it to use the websecure entrypoint, and that it should use the letsencrypt certresolver to grab it&#8217;s certificates.  websecure is an arbitrary name that we assigned to our :443 interface.  There are multiple ways to configure this, I choose to use the cli format in my traefik config:</p>



<p>&#8220;`</p>



<pre class="wp-block-code"><code>    command:
      - --api.insecure=true
      - --providers.docker=true
      # Our entrypoint names are arbitrary but these are convention.
      # The important part is the port binding that we associate.
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

</code></pre>



<p>These last label is optional depending on your setup but it is important to understand as the documentation is a little fuzzy.</p>



<p>      &#8211; traefik.http.services.ots.loadbalancer.server.port=3000<br><br>Here&#8217;s how it works.  Suppose you have a container that exposes multiple ports.  Maybe one of those is a web ui and another is something that you don&#8217;t want exposed.  By default Traefik will try and guess which port to route requests to.  My understanding is that it will try and use the first exposed port.  However you can override this functionality by using the label above which will tell Traefik specifically which port you want to route to inside the container.<br><br>The service name is derived automatically from the definition in the docker compose file:</p>



<pre class="wp-block-code"><code><br>  ots: # This will become the service name<br>    image: luzifer/ots<br>    container_name: ots<br>    </code></pre>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
