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 sitePAGESHIP_DEFAULT_SITE
)blogs
: http://blogs.localhost:8001blogs/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 sitesadmin
: 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 sitesadmin
: full access to the app
app.defaultSite
: The name of main site (default tomain
).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 deploymentsaccess
: ACL rules controlling access of preview deployments.ttl
: the lifetime of a preview deployment (default to24h
)
app.domains
: Configuration for custom domainsdomain
: The custom domain to usesite
: The site name associated the custom domain
site
section
The site
section defines the site config.
site.public
: The path to site directorysite.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 servesites.<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.