A Sandstorm app can publish static web content to any domain of the user's choosing. This is useful for content management systems, blogging platforms, and other apps. This guide helps app authors use that feature of the Sandstorm platform.

Overview

In Sandstorm, an app can publish static HTML and other files in a way where Sandstorm will serve the content, rather than the app. This saves on compute time and limits attack surface.

You can try it now with this sample app (full source available).

Store static files in /var/www. Each directory within /var/www should contain a file called index.html.

The grain can ask Sandstorm to enable publishing by requesting the creation of a unique publicId. Once that's done, the files are available on a special subdomain of the Sandstorm install. The subdomain takes the form publicId.sandstorm.example.com.

Users can also make the content available at any domain. To do that, they need to configure a CNAME record pointing at the Sandstorm install; this is how their domain's DNS will resolve to the Sandstorm server. The user also needs a TXT record; this is how Sandstorm determines what publicId this domain corresponds to.

This page explains how to generate a publicId and how to instruct a user of your app to configure their DNS appropriately. This a provisional API; see the note below about how we aim to make this more usable and more secure.

A helper program you can include, to enable publishing & request a publicId

The simplest way to enable static publishing is to embed a small C++ program with your app. The Sample Static Publishing App includes that C++ code.

To include the C++ code in your app, copy these files into a directory of your app called sandstorm-integration.

If you are using vagrant-spk, also add the following to the end of your .sandstorm/setup.sh:

### Download & compile capnproto and the Sandstorm getPublicId helper.

# First, get capnproto from master and install it to
# /usr/local/bin. This requires a C++ compiler. We opt for clang
# because that's what Sandstorm is typically compiled with.
if [ ! -e /usr/local/bin/capnp ] ; then
    sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q clang autoconf pkg-config libtool git
    cd /tmp
    if [ ! -e capnproto ]; then git clone https://github.com/sandstorm-io/capnproto; fi
    cd capnproto
    git checkout master
    cd c++
    autoreconf -i
    ./configure
    make -j2
    sudo make install
fi

# Second, compile the small C++ program within
# /opt/app/sandstorm-integration.
if [ ! -e /opt/app/sandstorm-integration/getPublicId ] ; then
    pushd /opt/app/sandstorm-integration
    make
fi
### All done.

Now, run the improved .sandstorm/setup.sh script by doing:

vagrant-spk vm provision

This should result in a /opt/app/sandstorm-integration/bin/getPublicId binary showing up in your directory tree. When you run it and provide the current X-Sandstorm-Session-Id header as a command-line parameter, it will output a publicId (and other information, see below).

If you prefer to use your own build system, you can use the above as inspiration. If you prefer to call Sandstorm's RPC directly, keep reading.

Note: At the time of writing, some vagrant-spk stacks crash if you run vagrant-spk vm provision a second time. We're working on fixing that.

Show DNS instructions to the user

You should make sure the user knows how to configure their domain's CNAME record (to point at the Sandstorm install) and TXT record (to tell Sandstorm which grain the domain points at).

You can find sample text in the Sandstorm sample app with static publishing. The essentials are:

  • The user can preview their site at the autoUrl link. The bin/getPublicId program prints that as line #3 (lines[2] in 0-indexed programming languages).

  • The user should set their CNAME value to the host component of the autoUrl, for example publicId.sandstorm.example.com. You can calculate this host component of the URL by parsing the autoUrl.

  • The user should set up a TXT record at sandstorm-www.example.com containing just the publicId. The bin/getPublicId program prints that as line 1 (lines[0] in 0-indexed programming languages).

Using the Sandstorm Cap'n Proto APIs directly

You can access the Sandstorm HackSessionContext capability directly if you want more performance or prefer to use the Sandstorm APIs with no overhead.

Start by obtaining a HackSessionContext capability. HackSessionContext is a Cap'n Proto interface. You must obtain an instance of this capability. The way to do this depends on whether your app uses sandstorm-http-bridge (check your sandstorm-pkgdef.capnp to find out).

If you are using the raw Cap'n Proto API without the HTTP Bridge, then the SessionContext capability you receive as a parameter to the UiView.newSession() method can be cast to HackSessionContext. * If you are using sandstorm-http-bridge, you must open a Cap'n Proto connection to unix:/tmp/sandstorm-api, which will give you a SandstormHttpBridge capability. Call getSessionContext() on that capability, using the ID that sandstorm-http-bridge places in the X-Sandstorm-Session-Id header, and cast the result to a HackSessionContext.

Note that HackSessionContext is a temporary API. As described below, we intend to replace this with a better API later.

Then call getPublicId() on the context. The first time you call this, the grain (app instance) is assigned a "public ID", which is a random string that uniquely identifies the grain, but which differs from the "private ID" which appears in the grain's URL. The public ID is not a secret as it grants no authority over the grain, whereas anyone who knows the private ID has full control over the grain.

The method call returns the grain's public ID as well as the hostname at which the server is hosted.

Then, instruct the user on how to set up DNS.

The user will need to set two DNS records:

<user-host> IN CNAME `<autoUrl>`
sandstorm-www.<user-host> IN TXT <public-id>

where:

  • <user-host> is the hostname at which the user wishes to publish their site.
  • <autoUrl> is the <public-id> plus the Sandstorm server hostname (as returned by getPublicId()).
  • <server-host> is the hostname of the Sandstorm server (as returned by getPublicId()).

Raw API example

See the Hacker CMS app, which can be installed from the app list or from source code.

Note: Provisional API

The current Cap'n Proto RPC for web publishing is hacky and not intended to be the long-term solution. In the long term, users will be able to connect domains to their Sandstorm account and then grant them to apps as capabilities through the Powerbox UI. Since the Powerbox and persistent capabilities are not yet implemented -- much less the ability to connect domains -- we are providing a hack so that developers can get started on such apps now. The hack allows a user to designate a Sandstorm app to host their domain via a special TXT record.

Also, the use of a C++ binary that you must embed might not be the most convenient way to expose the Cap'n Proto API. We're considering creating a pure-frontend Javascript API like offer templates or a postMessage-based API, and/or a backend API that is part of sandstorm-http-bridge. Let us know if you have a preference for what you would like to use.

Why only static content?

In order to make per-user application instances cost-effective, a Sandstorm application server normally only runs while a user has the application open. This works very well for things like private documents which have only one or maybe a few users. A public web site, however, is intended to be viewed widely and at all hours of the day. If we had to spin up the application sever for every visit, we'd lose this key advantage.

By restricting web publishing to static content, we can avoid spinning up the application server for regular visits. Only editing the content requires the server to be active.

As of this writing, there is no way to publish dynamic web sites to a custom domain via Sandstorm. In the future, this will become possible via the use of APIs. Once an application can export a public API, then it will be possible for "static" javascript published on a domain to make calls to that API. Such calls will, of course, require spinning up the server to handle, but a well-written app may be able to avoid making API calls except under special circumstances (e.g. when a user clicks to post a comment).