1. Pierre_Lebeaupin
  2. Curtain



Curtain ReadMe


Curtain is a packaging and deployment engine for desktop-like web apps. You provide the bits of your app: HTML head, style, body, the JavaScript code, and necessary resources, along with their description in a "resource_descriptions.json" file, and Curtain handles the business of generating the app, and deploying it on the server such that it supports offline use.

Curtain supports many use cases, such as:

  • deployment over a previous version such that the user never gets an inconsistent version, even if she accesses the web app during the deployment
  • deployment without offline support, for testing purposes for instance
  • quick deployment to a random server folder that gets deleted after use, for use an the develop/deploy/test cycle.


Curtain can deploy from any current desktop operating system: Windows, Mac OS X, Linux, Unix, etc.

You will need the following on your build machine:

  • Python 2.7
  • version control software. Not all of them are supported; currently, these systems are supported:
    • Mercurial
  • (optional) scp and paramiko Python modules (strongly recommended in order not to depend on ftp upload, which is insecure)

Server dependencies:

  • Web server. Not all of them are supported; currently, these servers are supported:
    • Apache

Runtime dependencies:

  • JavaScript
  • Modernizr
  • JSON
  • Blob constructor
  • XHR2 and Blob as a XHR responseType
  • (optional) offline support, currently only through AppCache

Curtain does not rely on any server-side programming, be it PHP, Perl, Ruby, Python, etc.: it deploys fully static sites.


Once set up, a simple deployment using Curtain is performed by running the following command in a Terminal window:


This uploads to a new folder (with a randomly generated name) on the server, and Curtain will print the URL for you to test; once you are done testing this URL, you hit return to end the script, which will delete the folder on the server.

For non-temporary deployments, Curtain works by generating a deployment script (another Python script) that you deploy as a separate step. By capturing the details of the deployment in a script, this ensures that the same deployment can be made to different destinations, e.g. staging and production.

Generating a script for an initial deployment is done with the -i option:

Curtain/Curtain.py -i -o initial_deployment_script

The actual deployment, here to server folder beta, is then performed by called the generated script directly:

./initial_deployment_script beta

You can call this script multiple times with different destination folders:

./initial_deployment_script prod

Note that the folder name can be "./", in order not to create a new subfolder on the server but instead directly use the parent folder. With -i, you specify this is an initial deployment, where anything that was previously on the server at this location is removed before the deployment is performed. This also works for rolling back to a known good version in case the version on the server is fatally flawed.

For posterior deployments to the same server folder, remove the -i but add the server folder (necessary as the generated content depends on the resources that are being replaced) to the command that generates the deployment script:

Curtain/Curtain.py -o deployment_script beta

Invoke the actual deployment as previously:

./deployment_script beta

This will handle the upgrade from the version already on the server, so that the user always gets a consistent set of resources, even if she loads the entry point 2 seconds before you deploy, but a network hiccup on her side causes dependent resources to be loaded 2 seconds after you deployed.

The server folder name need not be the same as that used when generating the script:

./deployment_script prod

The deployment script, however, will check the current state of the server folder corresponds to its expectations before performing the deployment.

When generating the deployment script, the Curtain command can take one or more of these options:

  • -m (originally stood for manifest) allows disabling offline support: the entry point omits any kind of offline support, and nothing related to offline support is uploaded (if there was such support on the server, it is removed)
  • -u url_file specifies the file containing the base URL to upload to; Curtain uses this option to locate the records describing the previously deployed resources, which it uses when generating a posterior deployment. If the option is not specified, the default file name is "upload_url".

When calling the generated deployment script, the following options are accepted:

  • -u url_file specifies the file containing the base URL to upload to, with any subfolder being created in this location; any folder in this URL must already exist on the server; if the option is not specified, the default file name is "upload_url"; will also look for a file named url_file + "_deployment", which the script reads to know the corresponding web URL so that it can give you the URL to which the app has been uploaded; if the option is not specified, the default name for that file is "base_url".
  • -p causes a prompt to appear at each interesting step, so as to open more widely usually very tight race windows

All options and their parameters must be provided before the folder parameter, if any.


In order to use Curtain, you need to prepare your code in the way it expects:

  • Curtain expects all source files to be UTF-8.

  • Curtain expects a file named "resource_descriptions.json" in JSON format, where the contents are a dictionary listing your media resources (images, typically) and your JavaScript code:

    • the keys are the resource identifiers
    • the values are dictionaries with these possible key-value options:
      • mime-type->string: Mandatory MIME type of the resource.
      • location->string: Mandatory path to where the resource is found on the build machine, relative to the location of resource_descriptions.json.
      • include-inline->Boolean value: If present and the value is true, the resource will be included inline in the generated HTML document; otherwise, it is uploaded as a separate HTTP resource and the HTML document will point to its URL. Only supported for text/javascript resources at the time being.
      • resBasePath->string: Mandatory unless the resource is included inline; it is the URL, relative to the root of the web app, where the resource will be uploaded.
      • description->string: Mandatory for image resources (and recommended in other cases for self-documentation), will be put as the contents of the alt attribute of img tags using this resource.
      • dimensions->array of two numbers: Mandatory for image resources, will be put as the contents of the width and height attributes, respectively, of img tags using this resource. This avoids unsightly shifting of interface elements when images are progressively loaded, even when JavaScript is not enabled, and allows support of high-resolution displays: simply provide an actual image file of pixel dimensions larger than these dimensions; no support for responsive images is currently provided, as it is generally considered incompatible with offline usage.
      • charset->string: Mandatory for text/html resources, is the charset that will be appended to the mime-type when serving this file.
      • exclude-from-app-cache->Boolean value: If present and the value is true, the resource will be uploaded but otherwise not managed by Curtain: it will not be part of the application package, it will not be versioned, it will not be available for offline use, and references to it from your code will not be processed.
  • Curtain expects a Modernizr build at assets/modernizr/modernizr.js in your client directory, and requires it to contain at least the following detectors: Blob constructor, XHR2, and support for Blob as XHR response type.

  • The HTML code specifying your app's interface needs to be specified as separate "style", "head", and "body" files with these names, the latter two being XML documents in a specific namespace where elements that refer to resources (e.g. the images put in img tags) do not directly refer to a URL. The changes from regular HTML are those:

    • The "head" file must still have "html" as the root element, but it must omit any "body" tag, and must not contain any Content-Type meta tag or style tag: Curtain handles those.
    • The "body" file must omit any "head", and its root "html" tag must have an XML namespace as follows:

      <html xmlns:curtain="http://wanderingcoder.net/projects/curtain">

      Also, the whole content of the body tag must be surrounded by a div with id "curtain" - img tags must not contain any of the usual attributes (src, width, height, title, alt, etc.) and instead have the following ones: - curtain:class, with value imgres - curtain:resid, with the value being the identifier of the resource from "resource_descriptions.json"

      Do not worry, at deployment Curtain will add the necessary src, width, height, alt, etc. attributes from the properties of the resource in "resource_descriptions.json", and remove anything from the curtain namespace so that the result is plain, valid HTML.

  • Your JavaScript code must assign to NET_WANDERINGCODER_PROJECTS_CURTAIN.start a function that will be called once all resources are loaded, and just before the interface is shown: in this function, you can access all resources, and you can perform last-minute adjustments to your interface, so any necessary setup you want to perform at runtime is best performed here. Once this function returns, Curtain will show the interface. This function is not called if any resource fails to load, and the interface remains hidden in that case: the assumption is that the user will simply reload the page if this happens.

  • Curtain and its support files must be at the root of a version-controlled work area, typically called "Curtain", which must be free of local modifications, even unversioned files.

  • "style", "head", and "body" and "resource_descriptions.json" must be at the root of a version-controlled work area (which may be the same as that of Curtain, but this is not recommended), which must be free of local modifications, even unversioned files; any resource described must be inside this work area as well.

  • In a folder that is not under either of the version controlled work area:

    • Create a file named "upload_url" (the name can be changed by using the -u parameter), which will contain the URL to which the app is to be uploaded; "ftp" and "ssh" URLs are supported, with "ssh" being strongly recommended.
    • Create a file named "base_url" (the name can be changed by using the -u parameter), which will contain the http or https that corresponds to the URL pu in "upload_url"; Curtain does not access this URL, and simply uses it to remind you of where the app can be accessed (appending any subfolder, if necessary), so that you only need to copy and paste the URL for you to test the app.
    • Create a file named "client_path", which will contain the path, relative to this folder, of the folder where "resource_descriptions.json" is located (this is a regular file rather than a symbolic link for compatibility with Windows)
    • And now you can use Curtain by invoking it while the folder containing these three files is current.

Note that Curtain will create a folder named "upload_url_folder" (or what you specified as the value to the -u option with "_folder" appended) inside the current folder.

  • Whenever you generate a script for a non-temporary, non-initial deployment, Curtain will read the records stored there in order to handle proper rollover of resources; if the records could not be found, the script generation will fail.

  • Whenever you perform a non-temporary, non-initial deployment, the deployment script will double-check that the records stored there have the same contents as those used when generating the deployment script.

  • Whenever you perform a non-temporary deployment, the deployment script will then write (in the case of an initial deployment) or update (in the other case) inside that folder the records for the uploaded files.