Typhon
Typhon is a Nix-based continuous integration software inspired by Hydra.
Prerequisites
This book assumes that you are familiar with Nix and Nix flakes. Being familiar with Hydra is not necessary.
Disclaimer
Typhon is still in early development and is merely a proof of concept. A lot of core features are still missing and it is full of bugs. Please do not use it for any serious purpose.
Concepts
Note
Core concepts are described with a flake workflow in mind. But Typhon also supports a more traditional workflow, see at the end of this section for details.
Overview
Projects are the central abstraction of Typhon. A project typically corresponds to an under CI repository. Projects define jobsets, which in turn spawn jobs. Jobsets typically correspond to branches of the repository. They are evaluated periodically, typically on push events. These evaluations produce the Nix jobs associated with a commit.
On top of these concepts, taken from Hydra, Typhon adds actions. Actions are user-defined scripts, triggered by Typhon on certain occasions. They can have different purposes, like triggering evaluations, creating new jobsets, setting statuses or deploying something.
Projects
Projects are defined declaratively. This means that almost no configuration is
made in Typhon, everything is done externally via a Nix flake. Concretely, a
project is defined by a flake URL. The referenced flake must expose an output
typhonProject
defining the project settings.
typhonProject
contains two attributes: meta
and actions
. meta
is an
attribute set which defines metadata about the project: a title, a description
and a homepage. actions
is an attribute set of derivations that build actions
for the project and holds encrypted secrets for use by the actions.
A project typically configures CI for a repository, but the declaration can exist in a separate repository. In fact, the declaration of a project is quite sensitive since it defines the way the project's unencrypted secrets are handled. Malicious edits to the declaration can potentially leak these secrets.
Jobsets
A jobset is also a flake URL, referencing a flake that exposes an output
typhonJobs
. typhonJobs
is an attribute set of derivations, called jobs, that
are built by Typhon. Jobsets typically correspond to the branches of the
repository. Their flake URL is locked periodically, creating an evaluation.
Jobsets updates and evaluations are meant to be triggered automatically by
the webhook
action.
Evaluations
An evaluation locks the flake URL of a jobset. It typically corresponds to a
commit on the repository. Once the jobset is locked, the output typhonJobs
is
evaluated and the corresponding jobs are spawned.
Jobs
Jobs are the result of an evaluation, there is one for each derivation defined in the jobset. A job run consists of the build of the derivation and the execution of two actions, one at the beginning and one at the end. These actions are typically used to set statuses on the commit or to do deployment.
Actions
Actions are scripts run by Typhon in isolation from the system, but connected to the internet. They play different roles in Typhon. At the moment there are four actions a project can define:
-
The
jobsets
action is responsible for declaring the jobsets of a project. It is triggered periodically by thewebhook
action, typically when a branch is created on the repository. -
The
begin
andend
actions are run at the beginning and end of all jobs of your project. They are typically used to set statuses on your repository, but can also be used for deployment. -
The
webhook
action is triggered by calls to a specific endpoint of the API. It outputs commands for Typhon to update or evaluate jobsets. It is meant to trigger jobs automatically.
Actions can also expose a secrets
file. This is an age encrypted JSON file
that typically contains tokens for the actions. It must be encrypted with the
project's public key and is decrypted at runtime and passed as input to the
actions.
Thanks to the use of actions, Typhon is forge-agnostic: it has no code specific to any forge. Instead, it is the actions' job to plug Typhon to the user's workflow. The actions can be built using the Nix library that comes with Typhon.
Legacy mode
In legacy mode, flake URLs are still used to declare projects and jobsets, but
the underlying expressions do not need to be flakes. Instead of the output
typhonProject
, a legacy project must expose the expression nix/typhon.nix
,
that will produce the same content as typhonProject
. Similarly, a legacy
jobset must expose nix/jobs.nix
instead of typhonJobs
. These expressions are
functions called without any arguments, and must evaluate purely.
Installation
Nix requirements
Typhon requires Nix >= 2.18 with experimental features "nix-command" and "flakes" enabled.
NixOS
At the moment the preferred way to install Typhon is on NixOS via the exposed module.
Example
Here is a sample NixOS module that deploys a Typhon instance:
{ pkgs, ... }:
let typhon = builtins.getFlake "github:typhon-ci/typhon";
in {
imports = [ typhon.nixosModules.default ];
# enable experimental features
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# install Nix >= 2.18 if necessary
nix.package = pkgs.nixVersions.nix_2_18;
# enable Typhon
services.typhon = {
enable = true;
# path to the argon2id hash of the admin password
# $ SALT=$(cat /dev/urandom | head -c 16 | base64)
# $ echo -n password | argon2 "$SALT" -id -e > /etc/secrets/password.txt
hashedPasswordFile = "/etc/secrets/password.txt";
};
# configure nginx
services.nginx = {
enable = true;
forceSSL = true;
enableACME = true;
virtualHosts."example.com" = {
locations."/" = {
proxyPass = "http://localhost:3000";
recommendedProxySettings = true;
};
};
};
}
Options
Here is a list of options exposed by the NixOS module.
Mandatory:
services.typhon.enable
: a boolean to activate the Typhon instance.services.typhon.hashedPasswordFile
orservices.typhon.hashedPassword
: the Argon2id hash of the admin password in the PHC string format.
Optional:
services.typhon.home
: a string containing the home directory of the Typhon instance.services.typhon.package
: a derivation to override the package used for the Typhon instance.
Usage
This section gives an example of how to use Typhon with a GitHub project. Let's
assume your username is $user
and you have two repositories,
github.com/$user/$project
and github.com/$user/$config
. $project
is the
repository you want to put under CI, $config
is going to contain the Typhon
declaration. These two repositories can actually be the same, but separating the
two can mitigate security concerns. Finally, let's assume your Typhon instance
URL is $typhon_url
(you must have https enabled).
Creating a new Typhon project
Log in to your Typhon instance and create a new project, with an identifier
$id
(typically $id == $project
). Set the declaration to use the flake URL
github:$user/$config
. Once the project is created, a public key is associated
to it, let's call it $pk
.
GitHub settings
We need to generate a token on GitHub and make sure it has permission to update
statuses on $project
, let's call it $token
. Then let's generate a random
string $secret
and add a webhook to $project
with the following settings:
- payload URL:
$typhon_url/api/projects/$id/webhook
- content type:
application/json
- secret:
$secret
- events: Just the
push
event
The configuration flake
Let's create a flake in the $config
repository, then add an output
typhonProject
. We are going to import typhon
as a flake input and use the
github.mkProject
helper function from the library:
{
inputs = {typhon.url = "github:typhon-ci/typhon";};
outputs = {
self,
typhon,
}: {
typhonProject = typhon.lib.github.mkProject {
owner = "$user";
repo = "$project";
secrets = ./secrets.age;
typhonUrl = "$typhon_url";
};
};
}
We need to generate the secrets.age
file. First let's write a secrets.json
file containing the secrets you generated (don't commit it!):
{
"github_token": "$token",
"github_webhook_secret": "$secret"
}
Then, we encrypt the JSON file with age
, using the public key of the project:
nix run nixpkgs#age -- --encrypt -r "$pk" -o secrets.age secrets.json
We also need to generate the lock file:
nix flake lock
Finally, we commit secrets.age
, flake.nix
and flake.lock
.
The project flake
In the $project
repository, we create a flake with a typhonJobs
attribute.
For instance, let's declare GNU hello as your only job:
{
inputs = {nixpkgs.url = "nixpkgs";};
outputs = {
self,
nixpkgs,
}: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
typhonJobs.${system} = {
inherit (pkgs) hello;
};
};
}
We need to generate a lock file and commit flake.nix
and flake.lock
.
Refreshing the project declaration
Let's go to your project's page on Typhon and refresh the declaration. This is
not done automatically on purpose. Always be careful before refreshing: if a
malicious commit was made on $config
, your secrets could be compromised. Once
this is done, your Typhon project is using the settings declared in $config
.
Verifying everything is working
We can now update the jobsets of your project from the project interface. A list of jobsets should appear, one for each branch of your repository. Now, any push to the repository should generate an evaluation in the corresponding jobset and statuses should appear on your repository.
Deployment
Now, let's add a deployment action to push your store paths to Cachix. We will
assume you have a cache named $cache
already set up and an authentication
token that comes with it. First add the token to your secrets as an attribute
called cachix_token
. Then edit $config
as follows:
typhonProject = typhon.lib.github.mkProject {
deploy = [
{
name = "Push to Cachix";
value = typhon.lib.cachix.mkPush {name = "$cache";};
}
];
...
};
Refresh your project on Typhon, then your binaries should be pushed to your cache at the end of every job.
Finally, you can use typhon.lib.compose.match
to run your deployments only on
certain jobsets or jobs.
Hacking
Typhon is written in Rust. It consists of four packages:
typhon-core
is the core logic of Typhontyphon-webapp
is the frontend applicationtyphon-types
is a common library shared between the twotyphon
is the server and the main package
Development environment
This documentation assumes that you are using Nix, so you can simply run
nix-shell
at the root of the project to enter the development environment.
Experimental features "nix-command" and "flakes" need to be enabled in your Nix
configuration for the server to run properly. Nix >= 2.18 is also required but
it is provided by the Nix shell.
The following instructions assume that you are inside the Nix development environment.
Dependencies
Typhon uses Actix for the web server and
Diesel for the database management. The webapp is written
with Leptos. Typhon is built with cargo-leptos
.
Building & Running
If you are building Typhon for the first time, first go to
typhon-webapp/assets
and run npm install
.
Then, to build Typhon, go to the root of the project and run:
build
To run Typhon, create /nix/var/nix/gcroots/typhon/
and make sure that you have
write access to the directory. Then go to the root of the project and run:
watch
The server will be available at http://localhost:3000
, with the admin password
set to password
. The server will be compiled automatically at each
modification of the code.
Formatting
Before submitting changes to Typhon, be sure to format the code using the
format
command.