diff options
Diffstat (limited to 'content/posts/2023-07-01-bringing-all-of-my-projects-together-under-one-umbrella.md')
| -rw-r--r-- | content/posts/2023-07-01-bringing-all-of-my-projects-together-under-one-umbrella.md | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/content/posts/2023-07-01-bringing-all-of-my-projects-together-under-one-umbrella.md b/content/posts/2023-07-01-bringing-all-of-my-projects-together-under-one-umbrella.md new file mode 100644 index 0000000..9059b00 --- /dev/null +++ b/content/posts/2023-07-01-bringing-all-of-my-projects-together-under-one-umbrella.md | |||
| @@ -0,0 +1,281 @@ | |||
| 1 | --- | ||
| 2 | title: "Bringing all of my projects together under one umbrella" | ||
| 3 | url: bringing-all-of-my-projects-together-under-one-umbrella.html | ||
| 4 | date: 2023-07-01T18:49:07+02:00 | ||
| 5 | type: post | ||
| 6 | draft: false | ||
| 7 | --- | ||
| 8 | |||
| 9 | ## What is the issue anyway? | ||
| 10 | |||
| 11 | Over the years, I have accumulated a bunch of virtual servers on my | ||
| 12 | [DigitalOcean](https://www.digitalocean.com/) account for small experimental | ||
| 13 | projects I dabble in. And this has resulted in quite a bill. I mean, I wouldn't | ||
| 14 | care if these projects were actually being used. But there were just being there | ||
| 15 | unused and wasting resources. Which makes this an unnecessary burden for me. | ||
| 16 | |||
| 17 | Most of them are just small HTML pages that have an endpoint or two to read data | ||
| 18 | from or to, and for that reason I wrote servers left and right. To be honest, | ||
| 19 | all of those things could have been done with [CGI | ||
| 20 | scripts](https://en.wikipedia.org/wiki/Common_Gateway_Interface) and that would | ||
| 21 | have been more than enough. | ||
| 22 | |||
| 23 | Recently, I decided to stop language hopping and focus on a simpler stack which | ||
| 24 | includes C, Go and Lua. And I can accomplish all the things I am interested in. | ||
| 25 | |||
| 26 | ## Finding a web server replacement | ||
| 27 | |||
| 28 | Usually I had [Nginx](https://nginx.org/en/) in front of these small web servers | ||
| 29 | and I had to manage SSL certificates and all that jazz. I am bored with these | ||
| 30 | things. I don't want to manage any of this bullshit anymore. | ||
| 31 | |||
| 32 | So the logical move forward was to find a solid alternative for this. I have | ||
| 33 | ended up on [Caddy server](https://caddyserver.com/). I've used it in the past | ||
| 34 | but kind of forgotten about it. What I really like about it is an ease of use | ||
| 35 | and a bunch of out of the box functionalities that come with it. | ||
| 36 | |||
| 37 | These are the _pitch_ points from their website: | ||
| 38 | |||
| 39 | - **Secure by Default**: Caddy is the only web server that uses HTTPS by | ||
| 40 | default. A hardened TLS stack with modern protocols preserves privacy and | ||
| 41 | exposes MITM attacks. | ||
| 42 | - **Config API**: As its primary mode of configuration, Caddy's REST API makes | ||
| 43 | it easy to automate and integrate with your apps. | ||
| 44 | - **No Dependencies**: Because Caddy is written in Go, its binaries are entirely | ||
| 45 | self-contained and run on every platform, including containers without libc. | ||
| 46 | - **Modular Stack**: Take back control over your compute edge. Caddy can be | ||
| 47 | extended with everything you need using plugins. | ||
| 48 | |||
| 49 | I had just a few requirements: | ||
| 50 | |||
| 51 | - Automatic SSL | ||
| 52 | - Static file server | ||
| 53 | - Basic authentication | ||
| 54 | - CGI script support | ||
| 55 | |||
| 56 | And the vanilla version does all of it, but CGI scripts. But that can easily be | ||
| 57 | fixed with their modular approach. You can do this on their website and build a | ||
| 58 | custom version of the server, or do it with Docker. | ||
| 59 | |||
| 60 | This is a `Dockerfile` I used to build a custom server. | ||
| 61 | |||
| 62 | ```Dockerfile | ||
| 63 | FROM caddy:builder AS builder | ||
| 64 | |||
| 65 | RUN xcaddy build \ | ||
| 66 | --with github.com/aksdb/caddy-cgi | ||
| 67 | |||
| 68 | FROM caddy:latest | ||
| 69 | RUN apk add --no-cache nano | ||
| 70 | |||
| 71 | COPY --from=builder /usr/bin/caddy /usr/bin/caddy | ||
| 72 | ``` | ||
| 73 | |||
| 74 | ## Getting rid of all the unnecessary virtual machines | ||
| 75 | |||
| 76 | The next step was to get a handle on the number of virtual servers I have all | ||
| 77 | over the place. | ||
| 78 | |||
| 79 | I decided to move all the projects and services into two main VMs: | ||
| 80 | |||
| 81 | - personal server (still Nginx) | ||
| 82 | - git server | ||
| 83 | - static file server | ||
| 84 | - personal blog | ||
| 85 | - projects server (Caddy server) | ||
| 86 | - personal experiments | ||
| 87 | - other projects | ||
| 88 | |||
| 89 | I will focus on projects' server in this post since it's more interesting. | ||
| 90 | |||
| 91 | ## Testing CGI scripts | ||
| 92 | |||
| 93 | The first thing I tested was how CGI scripts work under Caddy. This is | ||
| 94 | particularly import to me because almost all of my experiments and mini projects | ||
| 95 | need this to work. | ||
| 96 | |||
| 97 | To configure Caddy server, you must provide the server with a configuration | ||
| 98 | file. By default, it's called `Caaddyfile`. | ||
| 99 | |||
| 100 | ```caddyfile | ||
| 101 | { | ||
| 102 | order cgi before respond | ||
| 103 | } | ||
| 104 | |||
| 105 | examples.mitjafelicijan.com { | ||
| 106 | cgi /bash-test /opt/projects/examples/bash-test.sh | ||
| 107 | cgi /tcl-test /opt/projects/examples/tcl-test.tcl | ||
| 108 | cgi /lua-test /opt/projects/examples/lua-test.lua | ||
| 109 | cgi /python-test /opt/projects/examples/python-test.py | ||
| 110 | |||
| 111 | root * /opt/projects/examples | ||
| 112 | file_server | ||
| 113 | } | ||
| 114 | ``` | ||
| 115 | |||
| 116 | - The order is very important. Make sure that `order cgi before respond` is at | ||
| 117 | the top of the configuration file. | ||
| 118 | - Also, when you run with Caddy v2, make sure you provide `adapter` argument | ||
| 119 | like this `/usr/bin/caddy run --watch --environ --config /etc/caddy/Caddyfile | ||
| 120 | --adapter caddyfile`. Otherwise, Caddy will try to use a different format for | ||
| 121 | config file. | ||
| 122 | |||
| 123 | I did a small batch of tests with [Bash](https://www.gnu.org/software/bash/), | ||
| 124 | [Tcl](https://www.tcl-lang.org/), [Lua](https://www.lua.org/) and | ||
| 125 | [Python](https://www.python.org/). Here is a cheat sheet if you need it. | ||
| 126 | |||
| 127 | Let's get Bash out of the way first. | ||
| 128 | |||
| 129 | ```bash | ||
| 130 | #!/usr/bin/bash | ||
| 131 | |||
| 132 | printf "Content-type: text/plain\n\n" | ||
| 133 | |||
| 134 | printf "Hello from Bash\n\n" | ||
| 135 | printf "PATH_INFO [%s]\n" $PATH_INFO | ||
| 136 | printf "QUERY_STRING [%s]\n" $QUERY_STRING | ||
| 137 | printf "\n" | ||
| 138 | |||
| 139 | for i in {0..9..1}; do | ||
| 140 | printf "> %s\n" $i | ||
| 141 | done | ||
| 142 | |||
| 143 | exit 0 | ||
| 144 | ``` | ||
| 145 | |||
| 146 | This one is for Tcl script. | ||
| 147 | |||
| 148 | ```tcl | ||
| 149 | #!/usr/bin/tclsh | ||
| 150 | |||
| 151 | puts "Content-type: text/plain\n" | ||
| 152 | |||
| 153 | puts "Hello from Tcl\n" | ||
| 154 | puts "PATH_INFO \[$env(PATH_INFO)\]" | ||
| 155 | puts "QUERY_STRING \[$env(QUERY_STRING)\]" | ||
| 156 | puts "" | ||
| 157 | |||
| 158 | for {set i 0} {$i < 10} {incr i} { | ||
| 159 | puts "> $i" | ||
| 160 | } | ||
| 161 | ``` | ||
| 162 | |||
| 163 | And for all you Python enjoyers. | ||
| 164 | |||
| 165 | ```python | ||
| 166 | #!/usr/bin/python3 | ||
| 167 | |||
| 168 | import os | ||
| 169 | |||
| 170 | print("Content-type: text/plain\n") | ||
| 171 | |||
| 172 | print("Hello from Python\n") | ||
| 173 | print("PATH_INFO [{}]".format(os.environ['PATH_INFO'])) | ||
| 174 | print("QUERY_STRING [{}]".format(os.environ['QUERY_STRING'])) | ||
| 175 | print("") | ||
| 176 | |||
| 177 | for i in range(10): | ||
| 178 | print("> {}".format(i)) | ||
| 179 | ``` | ||
| 180 | |||
| 181 | And for the final example, Lua. | ||
| 182 | |||
| 183 | ```lua | ||
| 184 | #!/usr/bin/lua | ||
| 185 | |||
| 186 | print("Content-type: text/plain\n") | ||
| 187 | |||
| 188 | print("Hello from Lua\n") | ||
| 189 | print(string.format("PATH_INFO [%s]", os.getenv("PATH_INFO"))) | ||
| 190 | print(string.format("QUERY_STRING [%s]", os.getenv("QUERY_STRING"))) | ||
| 191 | print() | ||
| 192 | |||
| 193 | for i = 0, 9 do | ||
| 194 | print(string.format("> %d", i)) | ||
| 195 | end | ||
| 196 | ``` | ||
| 197 | |||
| 198 | ## Basic authentication | ||
| 199 | |||
| 200 | One thing was also to have an option for some sort of authentication, and | ||
| 201 | something like [Basic access | ||
| 202 | authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) would | ||
| 203 | be more than enough. | ||
| 204 | |||
| 205 | Thankfully, Caddy supports this out of the box already. Below is an updated | ||
| 206 | example. | ||
| 207 | |||
| 208 | ```Caddyfile | ||
| 209 | { | ||
| 210 | order cgi before respond | ||
| 211 | } | ||
| 212 | |||
| 213 | examples.mitjafelicijan.com { | ||
| 214 | cgi /bash-test /opt/projects/examples/bash-test.sh | ||
| 215 | cgi /tcl-test /opt/projects/examples/tcl-test.tcl | ||
| 216 | cgi /lua-test /opt/projects/examples/lua-test.lua | ||
| 217 | cgi /python-test /opt/projects/examples/python-test.py | ||
| 218 | |||
| 219 | root * /opt/projects/examples | ||
| 220 | file_server | ||
| 221 | |||
| 222 | basicauth * { | ||
| 223 | bob $2a$14$/wCgaf9oMnmQa20txB76u.nI1AldGMBT/1J7fXCfgOiRShwz/JOkK | ||
| 224 | } | ||
| 225 | } | ||
| 226 | ``` | ||
| 227 | |||
| 228 | `basicauth *` matches everything under this domain/sub-domain and protects it | ||
| 229 | with Basic Authentication. | ||
| 230 | |||
| 231 | - `bob` is the username | ||
| 232 | - `hash` is the password | ||
| 233 | |||
| 234 | To generate these passwords, execute `caddy hash-password` and this will prompt | ||
| 235 | you to insert a password twice and spit out a hashed password that you can put | ||
| 236 | in your configuration file. | ||
| 237 | |||
| 238 | Restart the server and you are ready to go. | ||
| 239 | |||
| 240 | ## Making Caddy a service with systemd | ||
| 241 | |||
| 242 | After the tests were successful, I copied `caddy` to `/usr/bin/caddy` and copied | ||
| 243 | `Caddyfile` to `/etc/caddy/Caddyfile`. | ||
| 244 | |||
| 245 | Now off to the systemd. Each systemd service requires you to create a service | ||
| 246 | file. | ||
| 247 | |||
| 248 | - I created a `/etc/systemd/system/caddy.service` and put the following content | ||
| 249 | in the file. | ||
| 250 | |||
| 251 | ```systemd | ||
| 252 | [Unit] | ||
| 253 | Description=Caddy | ||
| 254 | Documentation=https://caddyserver.com/docs/ | ||
| 255 | After=network.target network-online.target | ||
| 256 | Requires=network-online.target | ||
| 257 | |||
| 258 | [Service] | ||
| 259 | Type=notify | ||
| 260 | User=root | ||
| 261 | Group=root | ||
| 262 | ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile --adapter caddyfile | ||
| 263 | ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force --adapter caddyfile | ||
| 264 | TimeoutStopSec=5s | ||
| 265 | LimitNOFILE=1048576 | ||
| 266 | LimitNPROC=512 | ||
| 267 | PrivateTmp=true | ||
| 268 | ProtectSystem=full | ||
| 269 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE | ||
| 270 | |||
| 271 | [Install] | ||
| 272 | WantedBy=multi-user.target | ||
| 273 | ``` | ||
| 274 | |||
| 275 | - You might need to reload systemd with `systemctl daemon-reload`. | ||
| 276 | - Then I enabled the service with `systemctl enable caddy.service`. | ||
| 277 | - And then I started the service with `systemctl start caddy.service`. | ||
| 278 | |||
| 279 | This was about all that I needed to do to get it running. Now I can easily add | ||
| 280 | new subdomains and domains to the main configuration file and be done with | ||
| 281 | it. No manual Let's Encrypt shenanigans needed. | ||
