BlogTech is a toolkit for managing small to medium websites. It supports HTTPS, virtual hosts, automatic certificate management (via ACME) and a client for remote management of the server.
(If you are coming for the old, single-file version of BlogTech refer to the single_file branch.)
- Quick Start
- Authentication
- Virtual Hosts
- Enabling HTTPS
- Enabling ACME
- Testing the ACME Client
- Configuration Files
- Crash Logger
BlogTech runs on Linux and Windows (with limitations, see the HTTPS section). To compile it on Linux, you need to install the OpenSSL development library:
# Ubuntu/Debian
sudo apt install libssl-dev gccTo compile on Windows, you need to install clang.
Once you're done installing, you can build BlogTech by running
# Linux
./build.sh
# Windows
.\build.batThis will create the blogtech (Linux) or blogtech.exe (Windows) executable.
You can start a BlogTech server by running:
mkdir docroot
# Linux
./blogtech --serve --document-root=docroot --skip-auth-check
# Windows
.\blogtech.exe --serve --document-root=docroot --skip-auth-checkThis will start an HTTP server listening on 127.0.0.1:8080 and serving content from the docroot directory (which, is still empty).
You can view the website by opening a web browser and visiting http://127.0.0.1:8080/. Of course you will get a code 404 since there is nothing there yet!
So let's upload a file! Open a second terminal (to keep the server running) and create an HTML file by running the following:
# Linux
echo "<b>Hello, world</b>" > index.html
# Windows
"<b>Hello, world</b>" | Out-File index.htmlNow you can use BlogTech in "client mode" to upload the file
# Linux
./blogtech --upload --remote=http://127.0.0.1:8080 index.html
# Windows
.\blogtech.exe --upload --remote=http://127.0.0.1:8080 index.htmlYou should now find the text "Hello, world" when visiting http://127.0.0.1:8080/!
Note that even though we ran client and server on the same machine, this would have worked the same if the server was running on a remote machine.
Requests that would modify resources on the server are digitally signed using an HMAC/SHA256 signature. For this to work, client and server must share the same secret key, which is by convention stored in a admin.pwd file.
The password is specified using the --auth-password-file option:
./blogtech --serve --auth-password-file=admin.pwd --document-root=docroot
./blogtech --upload --auth-password-file=admin.pwd --remote=http://127.0.0.1:8080 index.html
# (Use .\blogtech.exe if you are Windows)Without a password file, all requests that would require authentication will be rejected. Also, empty passwords will be rejected.
During development, it's useful to allow all requests to be automatically be regarded as authenticated. You can do so with the --skip-auth-check, in which case the --auth-password-file option will be ignored if present. Of course you should use this with caution!
An authenticated HTTP request looks like this:
PUT /index.html HTTP/1.1\r\n
Host: 127.0.0.1:8080\r\n
Connection: Close\r\n
Content-Length: 22\r\n
X-BlogTech-Nonce: 80I5x5gfNC6yVTigxGf8xMDt+iVg3Nl6gjBi9Z4gaqE=\r\n
X-BlogTech-Timestamp: 1765730409\r\n
X-BlogTech-Expire: 300\r\n
X-BlogTech-Signature: S1jP/V6lTXqdjr2Mfk0N2C+0bCNsOxscifrUtRgJCw0=\r\n
\r\n
I'm a signed request!\n
The X-BlogTech- headers ensure that the request was not forged or used in a replay attack. More specifically:
X-BlogTech-Noncecontains a token chosen at random by the clientX-BlogTech-Timestampcontains the UNIX timestamp of when the request was first signedX-BlogTech-Expirecontains the number of seconds the request is valid from the time it is signedX-BlogTech-Signaturecontains the Base64-encoded HMAC of the request information.
The signature is obtained by calculating the canonical version of the request string:
<method>\n
<path>\n
<host>\n
<unix time>\n
<expiration seconds>\n
<nonce>\n
<content length>\n
<SHA256 hash of the payload encoded as hex>\n
And calculating its HMAC/SHA256 using the authentication password as key. The result is then Base64-encoded (with padding).
Note that the nonces previously seen by the server are stored in-memory. This means that if the server reloads it will forget all previously seen nonces. This allows requests that are not expired to be replayed.
BlogTech allows you to host multiple websites on the same server. For this to work you just need to specify the domain names using the --domain option:
./blogtech --serve --document-root=docroot --domain=websiteA.com --domain=websiteB.comBlogTech will then generate a directory per domain inside the document root
docroot
default
websiteA.com
websiteB.com
Requests directed to a host will refer to the directory associated to that host. If a request isn't associated to a specific folder, it refers to the default one.
For instance, the following commands
./blogtech --upload --remote=http://websiteA.com file1.html
./blogtech --upload --remote=http://websiteB.com file2.html
./blogtech --upload --remote=http://other.com file3.html
Will store the files as:
docroot/websiteA/file1.html
docroot/websiteB/file2.html
docroot/default/file3.html
HTTPS is only supported on Linux as the underlying HTTP library cHTTP implements HTTPS using OpenSSL. If you are on Windows, only HTTP is available for both client and servers.
To enable HTTPS, you need use the --https-enabled flag and load the certificate and private key using the --cert-file and --cert-key-file options.
./blogtech --serve --https-enabled --cert-file=cert.pem --cert-key-file=key.pemBy default, the server will listen on 127.0.0.1:8443, but you can change this using the --https-addr=<addr> and --https-port=<port> flags.
If you're not familiar with this process, the certificate is usually issued by a Certificate Authority (such as Let's Encrypt) after demonstrating the you own a domain. During development, it's useful to use a self-signed certificate, which can be generated with the following commands:
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -x509 -key key.pem -out cert.pem -days 365Note that browser won't allow you to navigate to HTTPS servers that use self-signed certificates, while cURL only allows you to do so with the --insecure flag.
If you happen to need more than one certificate, you can pass additional certificates using the --extra-cert option:
./blogtech --serve --https-enabled --cert-file=cert1.pem --cert-key-file=key1.pem --extra-cert=domain2.com,cert2.pem,key2.pem --extra-cert=domain3.com,cert3.pem,key3.pemThe certificate provided using the --cert-file and --cert-key options is served to clients by default, while the extra certificates are used when clients ask for specific domains (domain2.com and domain3.com in the example).
(Note: the command is quite long, but you can move command-line options to a configuration file)
The ACME protocol allows web servers to automatically request certificate authorities to issue a certificate. You can basically start the server in HTTPS mode without a certificate, and a couple seconds later.. there is the certificate!! It's pretty magic if you ask me.
If you're running this in production, be sure to:
- Run the HTTP server on port 80
- Create a DNS record that associated the domain the machine running the server If you want to run the ACME client locally, refer to the Testing the ACME client section to run an ACME server locally.
Enable ACME using the --acme-enabled flag alongside the HTTPS flags. Unlike regular HTTPS mode, the server expects the certificate not to exist. You also need to provide some values required to compile the certificate, such as the domain to certify, email, country code, organization name, and that you agree to the CA's terms of service:
./blogtech --serve --https-enabled --cert-file=cert.pem --cert-key-file=key.pem --acme-enabled --acme-domain=example.com [email protected] --acme-country=IT --acme-organization=me --acme-agree-tos(Note: the command is quite long, but you can move command-line options to a configuration file)
If all goes well, you should find that the following files are generated:
acme_key.pem: The secret key associated to the ACME accountcert.pem(or whatever you pass to--cert-file): The issued certificatekey.pem(or whatever you pass to--cert-key-file): The private key associated to the certificate
If anything goes wrong, error messages are logged to the acme.log file.
Note that you can let ACME handle multiple domains by simply passing multiple --acme-domain options. The resulting certificate will include all domains.
To test the ACME client on Linux you will need to install Docker and clone the Pebble ACME server.
Modify the docker-compose.yml file to add the following lines:
version: "3"
services:
pebble:
image: ghcr.io/letsencrypt/pebble:latest
command: -config test/config/pebble-config.json -strict
ports:
- 14000:14000 # HTTPS ACME API
- 15000:15000 # HTTPS Management API
networks:
acmenet:
ipv4_address: 10.30.50.2
extra_hosts:
- "local-test-websiteA.com:host-gateway" <--- These ones
- "local-test-websiteB.com:host-gateway" <---
- "local-test-websiteC.com:host-gateway" <---
...And add the following lines to the /etc/hosts file:
127.0.0.1 local-test-websiteA.com
127.0.0.1 local-test-websiteB.com
127.0.0.1 local-test-websiteC.com
Now you can start the ACME server by running
cd pebble
docker compose upYou can then start the BlogTech instance by running:
./blogtech -s --config=misc/pebble_blogtech.confYou should see al interactions with the ACME server being dumped by BlogTech on stdout and, if all goes well, the acme_key.pem, cert.pem and key.pem generated.
If you want to test certificate renewal, you can change the expirations by modifying these fields in pebble/test/config/pebble-config.json:
{
"pebble": {
... other fields ...
"profiles": {
"default": {
"description": "The profile you know and love",
"validityPeriod": 7776000 <--- This one
},
"shortlived": {
"description": "A short-lived cert profile, without actual enforcement",
"validityPeriod": 518400 <--- And this one
}
}
}
}
You can also instruct BlogTech to renew certificates even though they are not expired using the option --acme-force-renewal-period=<maximum duration in ms>.
BlogTech allows you to move any number of command-line arguments to a configuration file.
Say you have the following very verbose command:
./blogtech --serve --https-enabled --cert-file=cert.pem --cert-key-file=key.pem --acme-enabled --acme-domain=example.com [email protected] --acme-country=IT --acme-organization=me --acme-agree-tosYou can run the same command by creating the following blogtech_server.conf file
https-enabled yes
cert-file cert.pem
cert-key-file key.pem
acme-enabled yes
acme-domain example.com
acme-email [email protected]
acme-country IT
acme-organization me
acme-agree-tos yes
and running BlogTech as:
./blogtech --serve --config=blogtech_server.confBlogTech will automatically load a configuration file if named exactly blogtech.conf, which means you can do simply:
./blogtech --serveIf you want to ignore the implicit config file, use the --no-config flag:
./blogtech --serve --no-configWhen BlogTech crashes while in server mode, it will generate crash.bin, a binary file with the location of the crash. The next time the server is started, the crash.bin file will be translated into crash.log, a human-readable stack trace. Note that the translation of addresses to symbol names/line numbers might be a bit wonky.