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