One of the goals of Calepin is to be a pluggable and transparent service. One aspect of that goal is controlling the URL scheme and domain. If Calepin stops working one day you can publish your files on another service, and granted the new system gives you the same control, the URL's should serve the same content on the same location (no broken URL's!). The URL options are still under development, but custom domains were recently introduced. Users can now change their Calepin domain from example.calepin.co to blog.personaldomain.com.
Requirements
Calepin is served from a single AWS EC2 instance and a single IP address. All traffic directed at that IP address goes through a single NGiNX config. calepin.co and *.calepin.co all resolve to this IP address. The only exception is support.calepin.co which is served by the excellent Freshdesk. Subdomains for non-existing users should redirect to calepin.co.
*.calepin.co user domains should be reserved for custom domain users still, and redirect to the custom domain.
What is Mass Dynamic Hosting?
From my understanding it is the ability to automatically provision new sites within a server setup or webapp. Users' hostname requirements should be handled in automated fashion.
NGiNX
NGiNX is exceptionally high quality software. The configs are concise and readable, and it always feels like a workshop where you only know half the tools but the other half excites you. Thanks to Nilesh Ashra for teaching me the half that I know and helping me with this setup.
The Config
I found the solution to be quite creative, using a mix of cronjobs and the map module to handle user domains.
- Assign a
$usernamevariable if theHostheader matches regexsubdomain.calepin.co. - Assign a Calepin username to
$cname_userif theHostheader matches any of the registered custom domains of Calepin users. - Finally, lookup possible custom domains and assign to
$cnameif a request needs to redirect from a.calepin.codomain to a custom one.
map $http_host $username {
default 0;
~^(?P<subdomain>.+).calepin.co$ $subdomain;
}
map $host $cname_user {
default 0;
include /web/calepin/nginx/cname-username.txt; # migrantroo.com nileshashra
}
map $username $cname {
include /web/calepin/nginx/username-cname.txt; # nileshashra migrantroo.com;
}
I'm generating the two .txt plaintext databases with a psql command. map is pretty efficient and allows some kilobytes in the map config, but at some point you should worry about the limits of the include statement. Make sure you configtest before you run /etc/init.d/nginx reload. On the Calepin server these files are generated every hour (cronjob!).
This server directive below handles anything not matched by server_name in other directives. For this setup I want all custom domains to be processed here (*.calepin.co and calepin.co are easy to match elsewhere). The only snag is to redirect to the front page if no $cname_user was mapped from the Host header.
server {
listen 80 default_server;
if ( $cname_user = 0 ) {
rewrite ^ http://calepin.co/ redirect;
}
root /web/calepin/output/$cname_user;
location / {
try_files $uri $uri/index.html =404;
}
}
This is the webapp itself. A pretty standard uWSGI config.
server {
server_name calepin.co;
root /web/calepin/public;
location @calepin {
uwsgi_pass unix:///tmp/uwsgi.calepin.sock;
include uwsgi_params;
}
location / {
try_files $uri $uri/index.html @calepin;
}
}
And finally the subdomain user config. Because of that last map and username-cname.txt we know if the user in question wants redirects to the custom domain.
server {
server_name *.calepin.co;
root /web/calepin/output/$username;
if ( $host ~ "www.calepin.co" ) {
rewrite ^(.*) http://calepin.co$1 permanent;
}
if ( $cname ) {
# Forward from subdomain to custom domain
rewrite ^(.*) http://$cname$1 permanent;
}
location / {
try_files $uri $uri/index.html =404;
}
}