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. Thebin/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 examplepublicId.sandstorm.example.com
. You can calculate this host component of the URL by parsing theautoUrl
. -
The user should set up a TXT record at
sandstorm-www.example.com
containing just thepublicId
. Thebin/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 bygetPublicId()
).<server-host>
is the hostname of the Sandstorm server (as returned bygetPublicId()
).
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).