A Lightweight Access Control for Pijul Repositories

pi-hoole is a collection of tools to enforce access control for self-hosted pijul repositories. It can be seen as a cgit-like solution, for authenticated (public key, SSH) and anonymous accesses. You can grant read and write accesses to a whole repository, or to a determined subset of branches.

pi-hoole is distributed under the terms of the AGPL v3.

Getting Started

Building From Source

It should not be a surprise that pi-hoole is versioned under pijul, with the patch format introduced by pijul-0.9. Fair warning, use pijul-0.10.1 and you should be able to clone the pi-hoole repository.

Under the hood, pi-hoole is implemented in Haskell. We will need stack to build it.

We are using the latest lts available, but pi-hoole has already been built with lts-10 (but does not build with older ones).

This build three executables:

  • pi-hoole-cfg generates a .authorized_keys file to enforce access control for SSH.
  • pi-hoole-shell is called to determine if a authenticated user is allowed to perform a given pijul command.
  • pi-hoole-web is a HTTP proxy to enforce access control for anonymous requests.


In order to use pi-hoole on your server, the first step is to create a new, dedicated user (e.g. pijul). You then need to make pijul and pi-hoole-shell available to this user. Currently, we do not provide any packaging solution to that end, but this might change in the future. Although it is not mandatory, we consider both pi-hoole-cfg and pi-hoole-web have also been made available for the pijul user.


pi-hoole executables assume the configuration files are stored at ${XDG_CONFIG_DIRECTORY}/pi-hoole, that is ~/.config/pi-hoole by default.

In this directory, pi-hoole will scan the keys/ directory, if it exists, to know the list of authorized authenticated users. One user may have as many public key as required. Keys should be saved in file with the following filename scheme: <user>(\.<label>)?\.pub. Thus, lthms.pub, lthms.laptop.pub and lthms.work.pub are three valid public key filenames for the lthms user.

To generate a .ssh/authorized_keys, use pi-hoole-cfg.

Resulting file will be of the forms:

command="pi-hoole-shell \".lthms\" \"${SSH_ORIGINAL_COMMAND}\"",no-port-forwarding,no-x11-forwarding,no-agent-forwarding <ssh_keys_1>
command="pi-hoole-shell \".lthms\" \"${SSH_ORIGINAL_COMMAND}\"",no-port-forwarding,no-x11-forwarding,no-agent-forwarding <ssh_keys_2>

To configure the authorized accesses for the users, you have to set up a valid pi-hoole configuration file. The latter uses the YAML syntax, and is located at ${XDG_CONFIG_DIRECTORY}/pi-hoole/config.yaml. It has two root fields: groups and repositories.

You can define groups of users, and grant certain rights to a group, effectively granting there rights to each users who are member of the group.

Group names are prefixed by +, and user names are prefixed by ..

Then, if you want to create one group ogma which contains the users lgeorget and lthms:

  +ogma: [.lthms, .lgeorget]

You can create as many groups as you want, and one user may be added to several groups. Currently, nested groups are not allowed (i.e. a group being member of another group).

The repositories field allows for defining which repositories are available, and what given users can do with these repositories. The contents of the repositories is a map, where keys are path to the repositories (relative to HOME directory), and values are maps from role to rights.

For instance, given the following configuration:

    .lthms: +w
    +contrib: +r +w[master]
    anon: +r

    .lthms: +r
    .lgeorget: +w

We declare two repositories, one located at ${HOME}/my/first/repo, and another at ${HOME}/my/second/repo. The user lthms (identified with .lthms) can read and write to this repository, to arbitrary branches. The members of the group contrib can read to any branch, but can only write to the master branch. Finally, the anonymous user (through HTTP), can read to any branch.

Read means being able to clone or pull. Write means being able to push.

For the second repository, lthms can read to any branch, when lgeorget can read and write to any branch. Therefore, it is not possible to clone my/second/repo through HTTP, because anon has not been granted any particular rights.

Setting Up HTTP Proxy

pi-hoole-web is a very simple HTTP proxy. It receives HTTP request, ideally issued by a pijul client, and turns them into pijul commands if the anon role has been granted the required rights.

Currently, pi-hoole-web cannot be configured, and listen the port 8080. Also, it does not daemonify itself. The easiest way to set up a pi-hoole-web instance in a reliable way is to use systemd, for instance with the following unit:

Description=HTTP Proxy for Pijul anonymous accesses



For reference, the instance of pi-hoole-web responsible for pijul.lthms.xyz is behind a nginx server. One very straightforward nginx configuration can be:

server {
  listen 80;
  server_name pijul.lthms.xyz;

  access_log /var/log/nginx/pijul.lthms.xyz.access.log;
  error_log /var/log/nginx/pijul.lthms.xyz.error.log;

  location / {
    proxy_pass http://localhost:8080/;


Private Branches

The command pijul patch does not have an argument to specify the branch from which a patch is initially fetched; as a consequence, having read access to a repo, even for another branch where the patch is not applied, is enough. For this reason, if a user who obtains a hash for a patch of a branch they cannot access can fetch the patch. That is, a patch is as private as its hash. Private branches will eventually be supported in a better manner, but for now, private branches should mean separated repositories.