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