Contributing

How to set up new things in this repository and what tooling you will run into while doing it.

Project setup

This project is intended to be used on a system that has access to a nix daemon. On a system that has access to a nix daemon a devShell is provided with tooling to ease development. If you have access to direnv you can have the devShell load itself automatically.

Tools

Version control

Version control is git, with branch protection enforced in two places. Locally, a pre-commit hook and a pre-push hook (wired up automatically by entering the dev shell) refuse to commit or push directly to main — each has an environment-variable escape hatch for the rare case that needs it. These hooks are not intended to be enforcement but instead as guide rails to prevent issues when interacting with quality control validations further down the road.

Quality control takes place on the Forgejo forge via webhooks for when branches have things happen to them. The main branch in particular rejects any commits being added to it unless they come from a pull request or are provided by our automated CI/CD scripts . Before a pull request can be merged into main the branch it represents may be subject to a further set of scripts to validate that it passes some conditions.

See the Version control page to get a tutorial on how to manage commits, branches, remote repositories, and pull requests.

Linter

Linters are static code analysis tools that are used to check for and correct formatting and styling errors automatically. The intention of these tools is to provide a consistent feeling of how the code should be read throughout the project via a set of automated rules being applied to every file.

The particular linter used by this repository is alejandra and is included as a part of the devShell and can be run using the command alejandra . --exclude ./npins or be set up to run automatically as a part of integrated development environment s such as vscode.

This linter was chosen specifically because it is highly opinionated making it so that there should be one canonical way that any given abstract syntax tree can be represented by a stream of tokens that have made their way onto the main branch.

While the linting tools are included as a part of the devShell that this repository provides there is no need to run them manually. Formatting should ideally happen automatically with any code that is merged to main. Pull requests should be unable to merge to main if the changes that they contain are not able to be successfully formatted into a consistent state.

In addition to the nix linter we also have a markdown linter that can be run using this command: mdformat "--wrap" "80" --number .

CI/CD Scripts

The CI/CD scripts used by this repository are not an implementation of this repository. They are instead declared as a part of the configuration that this repository defines. If this project was to stop being hosted on devices that it describes then the tooling itself would move to the configuration of wherever became the new home of the repository.

This means instead of depending on in repo actions we instead set up external scripts to manage continuous integration and continuous delivery. Each one of the CI/CD scripts that are used in this project are dedicated tools to manage a particular task and are set up against their target repositories to listen to webhooks and produce events based on the results of those hooks.

This is the current list of running scripts that we have:

  • lint-daemon - autoformats every branch on every push and pushes the fixup commit straight back, posting a status for it — there’s no separate dry run, and it runs whether or not the branch is headed for a pull request
  • build-daemon - the actual merge gate: on a pull request, builds every nixosConfiguration the repo declares, so neither fork can merge something that breaks the other’s machines
  • sync-daemon - on a push to main, force-pushes it to a sync/from-<owner> branch on each peer fork and opens a pull request there (or updates the one already open), so changes flow both ways and every sync lands through the receiving fork’s own checks

Planned scripts

  • test-daemon - run all of the tests as a part of CI/CD
  • deploy-daemon - run build server and then deploy all configurations to all available hosts
  • auto-update - automatically create and keep up to date PRs bumping npins.

These scripts have secrets provisioned for them so that they are able to log in to accounts on the forge that have been granted the permissions that they need to be able to take the actions required of them.

Test scripts

To try to help make sure that your changes do what you expect them to, as well as to prevent from accidentally shooting yourself in the foot it is best practice to follow a technique called Test Driven Development .

Tests will be automatically run when trying to merge into main and will prevent merging to main if they are failing. If you would like to run the tests manually you can use the command nix-build tests.

build-daemon only checks that every configuration can be built. Actually running the tests before a merge is waiting on the planned test-daemon.

Provisioning new systems

The install script is intended to be used to provision a new device over ssh. You can generate an image that can be used to boot into that already has ssh keys added to it by using the command nix-build -A nixosConfigurations.installer.config.system.build.isoImage. From there you can boot your target device into the image and then use the install command to install the target configuration onto the device.

Dependency management

Dependencies in the repository generally fall under two categories.

  • Configuration dependencies - how the configuration itself is defined
  • Asset dependencies - things used by the configuration to generate the system

Configuration dependencies are managed using npins to pin dependency versioning to a known working, reproducible version. This tool then manages the contents of the npins/ directory and updates sources as needed.

The choice to use npins over the more popular flakes pattern is because flakes try to do too much at one time making them hard to learn, hard to work with, hard to exchange for other parts in the future when better ideas come around, and centralize authority on the “right way” to configure things into one large monolith that leaves little room for external voices to tinker.

Asset dependencies are managed ad hoc wherever needed. This can be things like source code, bundles or binary for a package, or anything else that is statically hosted somewhere that we might need to have as a part of our configuration.

See the administration documentation for how to update dependencies that already exist.

Secret management

Currently secrets are protected using sops-nix and are stored in a separate private repository. While sops does encrypt the secrets in a way where they could be committed as a part of this repository doing so assumes that the encryption being used will continue to be strong in the long term. To protect against Harvest now, decrypt later attacks the encrypted secrets are stored in a separate private repository that is only accessible by users who are trusted to have access to those specific configuration secrets.

The values in the secrets repository are not anything special and the pin for them can be replaced with a different secrets repository for any users who would like to use or extend these configurations on their own system.

When a new system is set up using the install script the keys to decrypt the secrets provided by the secrets repository are shipped to the new system.

As a part of our threat model currently beacon is not allowed to have any secrets on it as we do not want to ship our global keys and encrypted values to a vulnerable system.

In the future it would be nice to replace this method of managing secrets with a more robust portable implementation. See the secrets module documentation for plans on how we will go about doing that.

Private configurations

While ideally all configured values should be able to be public, in practice there are some configurations that are not able to be shared publicly.

Currently those configurations are defined in another repository and are layered under the configuration defined in this repository. Eventually this repository will be reworked to be completely standalone with the minimal private aspects being optional enhancements that can be deployed on top of the configuration.

Ticket tracking

Tickets of work that needs to be done or bugs that have been found are located on a vikunja instance .

Metrics

Currently metrics are not yet implemented. More to come on this in the future. See the metrics module docs for more details on the plans.

Value inspection

To look at what a configuration actually evaluated to, after every module and every override has been applied you can use tooling provided by the devShell or nix command line tooling.

The interactive tool provided as a part of the devShell is nix-inspect . It can be run using the command nix-inspect -p .. From there you can inspect values and view available values and options that exist in the configuration.

If you already know which value you are looking for and want a way to quickly access it, or want to check it from a script nix eval can print it directly. For example nix eval -f . nixosConfigurations.defiant.config.networking.hostName prints the host name for defiant. This can also be useful for evaluating the results of functions which is not easy to do with nix-inspect.


Version Control

How to manage commits, branches, remote repositories, forks, and pull requests.

Module Structure

How fixed points, the nixpkgs module system, and dendrites fit together to structure this configuration.