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](https://oasis.sandstorm.io/appdemo/qn94a65er7m7s3zgwrsnv8hhh81dw5mc8zpt75a8su30dqhv9gt0)** with this sample app ([full source available](https://github.com/paulproteus/sandstorm-sample-static-publishing/)). **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](https://github.com/paulproteus/sandstorm-sample-static-publishing/) includes that C++ code. To include the C++ code in your app, copy [these files](https://github.com/paulproteus/sandstorm-sample-static-publishing/tree/master/sandstorm-integration) 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`: ```bash ### 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: ```bash 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.](https://github.com/sandstorm-io/vagrant-spk/issues/87) ## 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](https://github.com/paulproteus/sandstorm-sample-static-publishing/blob/master/after_publish.php). 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`](https://github.com/sandstorm-io/sandstorm/blob/master/src/sandstorm/hack-session.capnp) is a [Cap'n Proto](https://capnproto.org) 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](https://github.com/sandstorm-io/sandstorm/blob/master/src/sandstorm/sandstorm-http-bridge.capnp) 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: IN CNAME `` sandstorm-www. IN TXT where: * `` is the hostname at which the user wishes to publish their site. * `` is the `` plus the Sandstorm server hostname (as returned by `getPublicId()`). * `` 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](https://sandstorm.io/apps) or from [source code](https://github.com/kentonv/ssjekyll). ## 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](http-apis.md) 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).