Pageship

Pageship is a self-hosted server for static pages (e.g. SPA, docs sites).

Main features:

  • GitHub Pages-like site hosting
  • Preview deployments
  • Automatic TLS
  • GitHub Actions integration
  • Access Control for sites

Installation

Please refer to the Installation Guide.

Documentation

Please refer to the User Guide.

Development

Please refer to the Development Guide.

License

Apache 2.0

Installation

Binary Release

Download latest binary release from GitHub.

curl -sSL https://raw.githubusercontent.com/oursky/pageship/main/install.sh | sh -s -- -b .
sudo mv ./pageship /usr/local/bin

Runner with Docker

Docker images are available from GitHub Packages:

docker pull ghcr.io/oursky/pageship:v0.4.0
docker pull ghcr.io/oursky/pageship-controller:v0.4.0

Go install

go install github.com/oursky/pageship@v0.4.0

Getting Started

Initialize the configuration

Run pageship init to create an initial configuration file your app.

Test your app

Run pageship serve to test whether the configuration is correct. Open http://localhost:8000 to see your pages in browser.

What's next

See the Setup Server guide to see how to deploy the server component of Pageship.

Setup Server

Pageship supports three modes of deployment:

Single-site mode

Single-site mode is the simplest deployment mode. It hosts a single static site in a server, and pages can be updated by directly copying the files to the data directory.

If you'd like to use the pageship command for deployment, or uses advanced features like preview deployment, please use managed-sites mode.

See the setup guide for single site mode

Unmanaged-sites mode

Unmanaged-sites mode hosts multiple static site on different sub-domains in a server. Static sites can be created by creating a site directory in the data directory, and pages can be updated by copying the files to the site directory.

If you'd like to use the pageship command for deployment, or uses advanced features like preview deployment, please use managed-sites mode.

See the setup guide for unmanaged sites mode

Managed-sites mode

Managed-sites mode provides advanced features like preview deployment, GitHub Actions integration, but is more complex to setup.

You can use pageship command to deploy to server and manage sites using this mode.

See the setup guide for managed sites mode

Single-site mode

Single-site mode hosts a single static site in a server.

Setup

First, prepare a directory to store the site data. The following instruction assumes the data directory is located at /var/pageship.

Before continuing, copy the site files to the data directory. The directory layout should look like this:

/var/pageship
├── pageship.toml
└── public

Docker compose

version: "3"
services:
  pageship:
    image: ghcr.io/oursky/pageship-server
    volumes:
      - /var/pageship:/var/pageship
    environment:
      - PAGESHIP_HOST_PATTERN=http://localhost:8001
    ports:
      - "8001:8001"

What's Next

Unmanaged-sites mode

Unmanaged-sites mode hosts multiple static sites in a server.

Setup

First, prepare a directory to store the site data. The following instruction assumes the data directory is located at /var/pageship.

Before continuing, create a directory for each sites in the data directory. The directory layout should look like this:

/var/pageship
├── main                    # main
│   ├── pageship.toml
│   └── public
└── blogs                   # blogs
    ├── pageship.toml
    ├── public
    └── user                # blogs/user
        ├── pageship.toml
        └── public

Docker compose

version: "3"
services:
  pageship:
    image: ghcr.io/oursky/pageship-server
    volumes:
      - /var/pageship:/var/pageship
    environment:
      - PAGESHIP_HOST_PATTERN=http://*.localhost:8001
      - PAGESHIP_DEFAULT_SITE=main
    ports:
      - "8001:8001"

Configuration

The host pattern (PAGESHIP_HOST_PATTERN) specify how Pageship should map the request host to a site. The wildcard part would be extracted as the site name.

By default, the sites are resolved ad-hoc using the directory layout, and the name of each site directories is used as the site name. A valid site must contain a pageship.toml config file in the site directory.

For example, for the above shown directory layout, the sites would be reachable at:

  • main: http://localhost:8001 (matches default site PAGESHIP_DEFAULT_SITE)
  • blogs: http://blogs.localhost:8001
  • blogs/user: http://user.blogs.localhost:8001

Optionally, a static config file (sites.toml) can be created to specify the available sites. For example:

# /var/pageship/sites.toml

[sites."main"]      # default site: http://localhost:8001
context="main"      # site directory: /var/pageship/main/

[sites."blogs"]         # http://blogs.localhost:8001
context="blogs/main"    # site directory: /var/pageship/blogs/main

[sites."user.blogs"]    # http://user.blogs.localhost:8001
context="blogs/user"    # site directory: /var/pageship/blogs/user

The site directory (context) is resolved relative to the location of config file.

What's Next

Managed-sites mode

Managed-sites mode hosts a multiple static sites in a server. The sites can be managed/deployed through pageship command.

Prerequisites

Pageship in managed-sites mode requires a database to store management metadata, and object storage to store the actual site content.

For database, we supports:

  • SQLite
  • PostgreSQL

For object storage, we supports:

  • Filesystem
  • Azure Blob
  • GCS
  • S3

For simplicity, Pageship uses SQLite with Filesystem storage by default. You may configure alternate database/object storage through configuration.

Docker compose

version: "3"
services:
  controller:
    image: ghcr.io/oursky/pageship-controller
    volumes:
      - data:/var/pageship
    environment:
      - PAGESHIP_MIGRATE=true
      - PAGESHIP_DATABASE_URL=sqlite:///var/pageship/data.db
      - PAGESHIP_STORAGE_URL=file:///var/pageship/storage?create_dir=true
      - PAGESHIP_HOST_PATTERN=http://*.localhost:8001
    ports:
      - "8001:8001"

Configuration

The host pattern (PAGESHIP_HOST_PATTERN) specify how Pageship should map the request host to a site. The wildcard part would be extracted as the site name.

By default, database schema is upgraded automatically on start. To disable it, set PAGESHIP_MIGRATE to false. You may run migration manually using the migrate subcommnad.

The database can be specified by PAGESHIP_DATABASE_URL.

  • For SQLite, provide the path to the database file like sqlite:///var/pageship/data.db.
  • For PostgreSQL: provide the DSN like postgres://postgres:postgres@db:5432/postgres?sslmode=disable.

The object storage can be specified by PAGESHIP_STORAGE_URL. Refer to documentation of gocloud for URL format of different providers.

Refer to Server configuration for detailed reference on configuration.

What's Next

Deploying Sites

Deploying using pageship command

For managed-sites mode, you may deploy a site through pageship command.

Configure app

If it's the first time deploying the app on server, create a new application with the server through pageship apps create command.

$ pageship apps create
API Server: https://api.example.com
App "..." is created.
$

Then apply the configuration file (pageship.toml) through pageship apps configure.

$ pageship apps configure
Configured app "...".

You can reset the client side config using pageship config reset

$ pageship config reset
Reset client config: y
INFO   Client config reset.

Deploy site

By default, each app has a default main site. Other sites can be configured in pageship.toml.

To deploy to a site, use pageship deploy command with site parameter.

$ pageship deploy --site main
Deploy to site "main" of app "...": y
  INFO   Collecting files...
  INFO   69 files found. Tarball size: 1.0 MB
  INFO   Setting up deployment 'tmytb2i'...
uploading 100%
  INFO   Activating deployment...
  INFO   You can access the deployment at: ...
  INFO   Done!

To deploy as a preview deployment, omit the site parameter. For details, refer to Preview Deployment guide.

$ pageship deploy --site main
Deploy to app "...": y
  INFO   Collecting files...
  INFO   69 files found. Tarball size: 1.0 MB
  INFO   Setting up deployment 'ztyflzy'...
  INFO   Site not specified; deployment would not be assigned to site
uploading 100%
  INFO   You can access the deployment at: ...
  INFO   Done!

Deploying single site

For single-site/unmanaged-sites mode, you may deploy a site by copying the site files to the site directory.

You may copy the site files using rsync with SSH access to the server. Assuming the current directory contains the site, and the site directory is located at /var/pageship on the server:

rsync -avh site/ /var/pageship/ --delete

Note that the deployment is not atomic - a visitor of the site may see inconsistent content during the deployment. For atomic deployment, a server in managed sites mode is needed.

Features

Preview Deployment

Pageship supports preview deployments. When a deployment is created without assigning to a site, it is treated as preview deployments.

Preview deployments has limited lifetime, and expires after a period not assigned to a site. This period can be configured in pageship.toml:

[app.deployments]
ttl = "24h"     # expires after 24 hours.

An expired preview deployment is inaccessible and deleted automatically after some time.

Access Control

By default, preview deployments are not accessible. To enable access to preview deployments, setup access control for preview deployment in pageship.toml:

[app.deployments]
access = [
    { ipRange="0.0.0.0/0" }     # preview deployments are accessible to anyone.
]

Automatic TLS

Pageship supports automatic TLS through certmagic library.

Automatic TLS can be activated by passing --tls command line parameter. Certificates would be obtained from Let's Encrypt when a site domain is accessed for the first time. It is recommnded to provide a email to receive notifications from certificate issuer using --tls-acme-email command line parameter.

Certificate Persistence

In single-site & unmanaged-sites mode, certificate data is stored on the default filesystem directory specified by certmagic library (${XDG_DATA_HOME}/certmagic) in plain-text. Care should be taken to secure the key materials.

In managed sites mode, certificate data is stored in database. Optionally, an encryption key can be specified through --tls-protect-key parameter to encrypt the certificate data at rest using NaCL secretbox.

GitHub Actions Integration

Pageship detects if it is running in GitHub Actions environment, and would authenticate with server automatically if possible.

To deploy from GitHub Actions, first configure the app to accept GitHub Actions running in the repo as deployer permission. See Access Control guide for details.

Then, enable OIDC token in GitHub Actions workflow by granting id-token permission to workflow jobs:

jobs:
  <job-name>:
    permissions:
      contents: read
      id-token: write

Finally, install pageship command in workflow and deploy directly.

docker run --rm \
    -e PAGESHIP_API="..." \
    -e ACTIONS_ID_TOKEN_REQUEST_URL="$ACTIONS_ID_TOKEN_REQUEST_URL" \
    -e ACTIONS_ID_TOKEN_REQUEST_TOKEN="$ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
    -v "$PWD:/var/pageship" \
    ghcr.io/oursky/pageship:v0.3.1 \
        deploy /var/pageship --site main -y

Access Control

Users & Credentials

In pageship, users are mostly just ID for internal reference. For purpose of access control, each user/request is associated with a set of credentials, and credentials are matched with ACL for permission check.

Pageship currently recognizes following credentials:

GitHub user

The user/request is associated with a specific GitHub user.

Users can be authenticated as GitHub user through SSH login with pageship login command:

$ pageship login
GitHub user name: *****
SSH key file: *****
Logged in as *****.
$

Github repository actions

The user/request is originated from GitHub Actions running in a specific GitHub repository.

pageship command would authenticate as GitHub repository actions automatically when it detected running in GitHub Actions environment.

IP address

The user/request is originated from a specific IP address. All users/requests are automatically associated with an IP address credential.

Site Access

Site access can be specified through ACL in the access field:

[site]
access = [
    { ipRange="127.0.0.1/32" }
]

App Management Access

App management access can be specified through ACL in the team field:

[app]
team = [
    { githubUser="...", access="admin" },
    { gitHubRepositoryActions="oursky/pageship", access="deployer" }
]

There are three levels of access for management:

  • reader: read-only access to app metadata (e.g. list of deployments/sites)
  • deployer: access neccessary for deploying sites
  • admin: full access to the app

In addition, the creator user of an app is considered as the owner of the app, and always has full access to the app.

API Access Control

The server API may be protected from unwanted access by specifying an ACL file in PAGESHIP_API_ACL environment variable.

[[access]]
githubUser="..."

[[access]]
gitHubRepositoryActions="oursky/pageship"

Custom Domains

Pageship supports custom domains for serving pages for an app. We assume a cooperative model for custom domain association, so domain ownership verification is not required.

To enable custom domain, configure pageship.toml and specify the site to serve from the domain:

# 2 sites for the app: 'main' & 'dev'
[[app.sites]]
name = "main"

[[app.sites]]
name = "dev"

# For 'main' site, serve it at 'example.com'. Traffic to default domain is
# redirected to the configured domain automatically.
[[app.domains]]
domain="example.com"
site="main"

If the domain name is already in-use by other apps, the custom domain would not be activated automatically when first added to the configuration. It can be activated/deactivated manually using pageship domains activate <domain name>/ pageship domains deactivate <domain name> command.

Custom domains of the app can be listed with pageship domains command. Additional setup instruction (e.g. DNS setup) would be shown if provided by server operator.

Configuration file

pageship.toml

pageship.toml is the main configuration file used by Pageship. All paths referenced in pageship.toml is resolved relative to its location.

app section

The app section defines the app config when hosted in managed-sites mode server.

  • app.id: Unique ID of the app in the server.
  • app.team: ACL rules controlling API access of app management.
    • app.team[].access: Access level of the actor matching the rule (highest level applies):
      • reader: read-only access to app metadata (e.g. list of deployments/sites)
      • deployer: access neccessary for deploying sites
      • admin: full access to the app
  • app.defaultSite: The name of main site (default to main).
  • app.sites: The available sites in the app, the main site can be accessed through the app domain, while other sites is accessed through a subdomain. - app.sites[].name: the site name, cannot be used with pattern. - app.sites[].pattern: the site name pattern, cannot be used with name. subdomain.
  • app.deployments: Configuration for preview deployments
    • access: ACL rules controlling access of preview deployments.
    • ttl: the lifetime of a preview deployment (default to 24h)
  • app.domains: Configuration for custom domains
    • domain: The custom domain to use
    • site: The site name associated the custom domain

site section

The site section defines the site config.

  • site.public: The path to site directory
  • site.access: ACL rules controlling access of site

sites.toml

sites.toml is used for unmanaged-sites mode. It defines the location and resolution of multiple apps.

  • sites.<name>: The list of sites to serve
    • sites.<name>.context: directory of the site.

Server configuration

TODO

Access Control

Access control is configured by ACL rules of different types. A request/action passes the access control check if it matches any of the applicable ACL rules.

A typical ACL would looks like this:

access = [
    { githubUser="username" },
    { ipRange="127.0.0.1/32" }
]

Authentication

A GitHub user may authenticate through the pageship login command. Currently, it will connect to the Pageship server through SSH protocol, and verify user's identity through GitHub user's public key.

GitHub Actions jobs would be authenticate automatically when pageship command detected running in CI environment. It authenticates through GitHub Actions OIDC token.

ACL Types

GitHub user

{ githubUser = "username" }

Actions/requests from the specified GitHub user is allowed.

GitHub Actions repository

{ gitHubRepositoryActions = "oursky/pageship" }
{ gitHubRepositoryActions = "oursky/*" }
{ gitHubRepositoryActions = "*" }

Actions/requests from the specified GitHub Action jobs is allowed. Wildcard can be specified for all repository of a user/organization, or any repository.

IP Range

{ ipRange = "127.0.0.1/32" }
{ ipRange = "192.168.0.0/16" }
{ ipRange = "0.0.0.0/0" }
{ ipRange = "::1/128" }

Actions/requests from the specified IP range (CIDR) is allowed. IPv4 is mapped to IPv6 before matching.