Compare commits

...

61 commits
main ... main

Author SHA1 Message Date
71b1b660f2
Merge pull request 'feat: custom evaluation' (#1) from custom-activation into main
Reviewed-on: DGNum/colmena#1
2024-12-07 12:43:09 +01:00
92c5f5c33f
feat: generic registry and custom evaluation
This PR bring custom evaluation, but does not offer yet custom
activation.

Therefore, you can evaluate your systems and refer to each of them, but
you cannot ask Colmena to build them for you.

Signed-off-by: Ryan Lahfa <ryan@dgnum.eu>
2024-12-07 12:42:58 +01:00
7fa3062cfb
feat: disable key management modules
Let the user opt-in… !

Signed-off-by: Ryan Lahfa <ryan@dgnum.eu>
2024-12-07 12:38:19 +01:00
Zhaofeng Li
e3ad421380
Merge pull request #251 from zhaofengli/renovate/all-minor-patch
fix(deps): update all non-major dependencies
2024-11-13 12:43:22 -07:00
renovate[bot]
f5ffb64491
fix(deps): update all non-major dependencies 2024-11-12 20:50:29 +00:00
Zhaofeng Li
a2193487bc
Merge pull request #249 from zhaofengli/renovate/all-minor-patch
chore(deps): update jamesives/github-pages-deploy-action action to v4.6.9
2024-11-10 07:43:47 -07:00
renovate[bot]
7ccfa7aae1
chore(deps): update jamesives/github-pages-deploy-action action to v4.6.9 2024-11-09 22:38:48 +00:00
Zhaofeng Li
c4d72269af
Merge pull request #248 from zhaofengli/remove-atty
progress: Detect tty with std::io::IsTerminal, remove atty
2024-11-08 12:17:18 -07:00
Zhaofeng Li
68219763dd
Merge pull request #233 from valkum/fix-symlink
resolve TempDir paths before passing them to the flake bin
2024-11-08 12:03:41 -07:00
Zhaofeng Li
e73c6921fd progress: Detect tty with std::io::IsTerminal, remove atty
Fixes https://github.com/advisories/GHSA-g98v-hv3f-hcfr.
2024-11-08 11:44:10 -07:00
Zhaofeng Li
936ed520eb Cargo.lock: Update 2024-11-08 11:44:10 -07:00
Zhaofeng Li
e5c30066c2 Revert ".github: Run workflows on push event only"
Sadly it breaks CI in fork-originated PRs if the author doesn't enable
Actions on the fork itself.

This reverts commit ce1aa41ff4.
2024-11-08 11:27:40 -07:00
Zhaofeng Li
1a07996142
Merge pull request #247 from zhaofengli/readme-badge-branch
README.md: Fix badge branch
2024-11-08 11:22:57 -07:00
Zhaofeng Li
3d9d22bd7b
Merge pull request #239 from zhaofengli/renovate/all-minor-patch
Update all non-major dependencies
2024-11-08 09:08:34 -07:00
Rudi Floren
00fd486d49 resolve TempDir paths before passing them to the flake bin
On MacOS /tmp is a symlink to /private/tmp.
TempDir creates a dir and returns the path as /tmp.
nix is unhappy with symlink dirs when calling nix flake metadata --json

To fix this we canonicalize the path first, resolving any symlinks on the way.

Fixes #190.
2024-11-08 08:57:28 -07:00
Zhaofeng Li
4dc8155712 Update code for validator 0.19.0 2024-11-08 08:26:29 -07:00
Zhaofeng Li
b46e9dbd4c README.md: Fix badge branch
[ci skip]
2024-11-07 21:00:48 -07:00
renovate[bot]
40429b7ed4
Update all non-major dependencies 2024-11-08 03:48:26 +00:00
Zhaofeng Li
03f1a18a6f
Merge pull request #245 from zhaofengli/renovate/docker-setup-qemu-action-3.x
Update docker/setup-qemu-action action to v3
2024-11-07 20:47:23 -07:00
renovate[bot]
926ae650fb
Update docker/setup-qemu-action action to v3 2024-11-08 03:17:33 +00:00
Zhaofeng Li
ec21d8514b
Merge pull request #243 from zhaofengli/renovate/actions-checkout-4.x
Update actions/checkout action to v4
2024-11-07 20:15:40 -07:00
Zhaofeng Li
123028ce09
Merge pull request #244 from zhaofengli/renovate/cachix-cachix-action-15.x
Update cachix/cachix-action action to v15
2024-11-07 20:15:30 -07:00
Zhaofeng Li
e9ee03aeeb
Merge pull request #240 from zhaofengli/renovate/determinatesystems-nix-installer-action-15.x
Update DeterminateSystems/nix-installer-action action to v15
2024-11-07 18:12:36 -07:00
renovate[bot]
bfc3cc8271
Update cachix/cachix-action action to v15 2024-11-08 01:08:05 +00:00
renovate[bot]
b82ee861dd
Update actions/checkout action to v4 2024-11-08 01:08:00 +00:00
Zhaofeng Li
0e0b682d42
Merge pull request #242 from zhaofengli/aarch64-darwin
.github/build: Update macOS runner, add aarch64-darwin
2024-11-07 18:06:46 -07:00
renovate[bot]
c78d024f3a
Update DeterminateSystems/nix-installer-action action to v15 2024-11-08 00:42:22 +00:00
Zhaofeng Li
9469ed3591
Merge pull request #237 from zhaofengli/renovate/configure
chore: Configure Renovate
2024-11-07 17:40:56 -07:00
Zhaofeng Li
b02d2d6ba7
Merge pull request #238 from zhaofengli/srcignore
Exclude more paths from colmena.src to reduce rebuilds
2024-11-07 17:39:17 -07:00
Zhaofeng Li
998f92d869 .github/build: Update macOS runner, add aarch64-darwin
The new hosted GitHub runners are Apple Silicon-based only, and we
build x86_64-darwin packages via Rosetta 2.
2024-11-07 17:19:59 -07:00
Zhaofeng Li
ce1aa41ff4 .github: Run workflows on push event only 2024-11-07 17:08:49 -07:00
Zhaofeng Li
a70411fd1d Exclude more paths from colmena.src to reduce rebuilds 2024-11-07 16:42:45 -07:00
Zhaofeng Li
596b6d883e .editorconfig: Enforce .json indentation 2024-11-07 16:24:44 -07:00
Zhaofeng Li
4cfa0b6092 renovate.json: Group non-major updates together 2024-11-07 16:24:44 -07:00
Zhaofeng Li
ac23318700 renovate.json: Enable Nix & lock file maintenance 2024-11-07 16:24:44 -07:00
renovate[bot]
6db6b30b01 Add renovate.json 2024-11-07 16:24:44 -07:00
Zhaofeng Li
2c95c1766a
Merge pull request #228 from zhaofengli/direct-flake-eval
Add direct flake evaluation support
2024-11-07 16:03:30 -07:00
Zhaofeng Li
a4604f3371 manual/flakes: Document direct flake evaluation 2024-11-07 15:51:20 -07:00
Zhaofeng Li
33c41abd44 Remove garnix config 2024-11-07 15:24:37 -07:00
Zhaofeng Li
f593c24aa2 .github/nix-matrix-job: Maximize build space 2024-11-07 15:24:37 -07:00
Zhaofeng Li
e17c521c15 Run integration tests on GitHub Actions 2024-11-07 15:24:37 -07:00
Zhaofeng Li
0ef98d060c integration-tests/flakes: Fix error message matching 2024-11-07 15:24:37 -07:00
Zhaofeng Li
45ca75bcea integration-tests/flakes: Switch to direct flake evaluation (experimental) 2024-11-07 15:24:37 -07:00
Zhaofeng Li
0a836dc251 integration-tests/tools: Re-enable switch-to-configuration 2024-11-07 15:24:37 -07:00
Zhaofeng Li
dc80345dee Add direct flake evaluation support 2024-11-07 15:24:37 -07:00
Zhaofeng Li
1f669d4c78 Bump schema version to v0.20241006 2024-11-07 15:24:37 -07:00
Zhaofeng Li
2708c9359f Revert "[FIXME] integration-tests: Temproarily pin to Nix 2.18"
This reverts commit 141fe82f44.
2024-11-07 15:24:37 -07:00
Zhaofeng Li
1f7b8ab80f package.nix: Exclude .github from cleaned source 2024-11-07 15:24:37 -07:00
Zhaofeng Li
8a8f47a1b1 flake.nix: Apply nix-eval-jobs overlay on output package set 2024-11-07 15:24:37 -07:00
Zhaofeng Li
524cd45299 flake.lock: Update 2024-11-07 15:24:37 -07:00
Zhaofeng Li
70462312f2 .editorconfig: Ignore Rust
Too rigid :/ Let's just let rustfmt handle it.
2024-11-07 15:24:37 -07:00
Zhaofeng Li
ec25d799ed .gitignore: Ignore .vscode 2024-11-07 15:24:37 -07:00
Zhaofeng Li
b0a62f234f
Merge pull request #224 from peterablehmann/main
Fix describe_node_list remaining nodes special case
2024-10-06 19:14:38 -06:00
Zhaofeng Li
0fca61acc2
Merge pull request #222 from benaryorg/mddoc
lib.mdDoc has been deprecated since 24.05
2024-10-05 20:11:13 -06:00
Zhaofeng Li
43be9effab flake.nix: Patch nix-eval-jobs for NIX_PATH 2024-10-05 19:57:58 -06:00
Zhaofeng Li
141fe82f44 [FIXME] integration-tests: Temproarily pin to Nix 2.18 2024-10-05 19:57:58 -06:00
Zhaofeng Li
87eb6c2f4c integration-tests/tools: Increase deployer memory to 6 GiB
OOMs with nix-eval-jobs
2024-10-05 19:57:58 -06:00
Peter Lehmann
569c914f95
Fix describe_node_list remaining nodes special case
Colmena truncates left over node names when it reaches a certain char limit.
This in it self is a sensible behaivior, but it should only do so if there is more then one node name remaining.
2024-09-02 22:45:43 +02:00
Zhaofeng Li
aeccdba3b4 flake: Update stable to NixOS 24.05 2024-09-01 13:06:00 -04:00
Zhaofeng Li
8f9986a748 flake: Update nixpkgs unstable 2024-09-01 13:06:00 -04:00
benaryorg
7bb23baf40
lib.mdDoc has been deprecated since 24.05
`lib.mdDoc` emits this trace when called since 24.05:

```quote
trace: warning: lib.mdDoc will be removed from nixpkgs in 24.11. Option descriptions are now in Markdown by default; you can remove any remaining uses of lib.mdDoc.
```

Given that this was introduced with 22.11 and made obsolete in 24.05 with all prior versions being EoL at this point it should be safe to revert that change.
In any case the only result will be that anything ≥22.11 and <24.05 will be rendered without markup which I guess isn't the best, but given those are EoL systems it's not the worst.

Either way it gets rid of a *lot* of noise for 24.05 users in the trace, and for 24.11 and up it will remove dead code.

This reverts commit 7602e548a7.

Signed-off-by: benaryorg <binary@benary.org>
2024-08-23 13:37:06 +00:00
32 changed files with 1334 additions and 651 deletions

View file

@ -10,8 +10,7 @@ insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
charset = utf-8 charset = utf-8
# Rust [*.json]
[*.rs]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

View file

@ -13,19 +13,23 @@ jobs:
image: ubuntu-latest image: ubuntu-latest
system: aarch64-linux system: aarch64-linux
- label: x86_64-darwin - label: x86_64-darwin
image: macos-12 image: macos-latest
system: x86_64-darwin
- label: aarch64-darwin
image: macos-latest
system: aarch64-darwin
name: ${{ matrix.label }} name: ${{ matrix.label }}
runs-on: ${{ matrix.image }} runs-on: ${{ matrix.image }}
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v4.2.2
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@9b252454a8d70586c4ee7f163bf4bb1e9de3d763 # v2 uses: DeterminateSystems/nix-installer-action@b92f66560d6f97d6576405a7bae901ab57e72b6a # v15
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0 uses: docker/setup-qemu-action@v3.2.0
if: matrix.system != '' if: matrix.system == 'aarch64-linux'
- name: Generate System Flags - name: Generate System Flags
run: | run: |
@ -39,7 +43,7 @@ jobs:
HOST_SYSTEM: '${{ matrix.system }}' HOST_SYSTEM: '${{ matrix.system }}'
- name: Enable Binary Cache - name: Enable Binary Cache
uses: cachix/cachix-action@v12 uses: cachix/cachix-action@v15
with: with:
name: colmena name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
@ -49,3 +53,60 @@ jobs:
- name: Build manual - name: Build manual
run: nix build .#manual -L run: nix build .#manual -L
nix-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4.2.2
- uses: DeterminateSystems/nix-installer-action@v15
continue-on-error: true # Self-hosted runners already have Nix installed
- name: Enable Binary Cache
uses: cachix/cachix-action@v15
with:
name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- id: set-matrix
name: Generate Nix Matrix
run: |
set -Eeu
matrix="$(nix eval --json '.#githubActions.matrix')"
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
nix-matrix-job:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
needs:
- build
- nix-matrix
strategy:
matrix: ${{ fromJSON(needs.nix-matrix.outputs.matrix) }}
steps:
- name: Maximize build space
uses: easimon/maximize-build-space@master
with:
remove-dotnet: 'true'
build-mount-path: /nix
- name: Set /nix permissions
run: |
sudo chown root:root /nix
- uses: actions/checkout@v4.2.2
- uses: DeterminateSystems/nix-installer-action@v15
continue-on-error: true # Self-hosted runners already have Nix installed
- name: Enable Binary Cache
uses: cachix/cachix-action@v15
with:
name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build ${{ matrix.attr }}
run: |
nix build --no-link --print-out-paths -L '.#${{ matrix.attr }}'

View file

@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v4.2.2
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@9b252454a8d70586c4ee7f163bf4bb1e9de3d763 # v2 uses: DeterminateSystems/nix-installer-action@b92f66560d6f97d6576405a7bae901ab57e72b6a # v15
- name: Enable binary cache - name: Enable binary cache
uses: cachix/cachix-action@v12 uses: cachix/cachix-action@v15
with: with:
name: colmena name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'

View file

@ -16,13 +16,13 @@ jobs:
if: github.repository == 'zhaofengli/colmena' if: github.repository == 'zhaofengli/colmena'
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v4.2.2
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@9b252454a8d70586c4ee7f163bf4bb1e9de3d763 # v2 uses: DeterminateSystems/nix-installer-action@b92f66560d6f97d6576405a7bae901ab57e72b6a # v15
- name: Enable Binary Cache - name: Enable Binary Cache
uses: cachix/cachix-action@v12 uses: cachix/cachix-action@v15
with: with:
name: colmena name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
@ -38,7 +38,7 @@ jobs:
run: nix build .#manual -L run: nix build .#manual -L
- name: Deploy manual - name: Deploy manual
uses: JamesIves/github-pages-deploy-action@4.1.6 uses: JamesIves/github-pages-deploy-action@v4.6.9
with: with:
branch: gh-pages branch: gh-pages
folder: result folder: result
@ -52,7 +52,7 @@ jobs:
if: ${{ env.api_version == env.latest_stable_api }} if: ${{ env.api_version == env.latest_stable_api }}
- name: Deploy redirect farm - name: Deploy redirect farm
uses: JamesIves/github-pages-deploy-action@4.1.6 uses: JamesIves/github-pages-deploy-action@v4.6.9
with: with:
branch: gh-pages branch: gh-pages
folder: result-redirectFarm folder: result-redirectFarm

View file

@ -16,13 +16,13 @@ jobs:
if: github.repository == 'zhaofengli/colmena' if: github.repository == 'zhaofengli/colmena'
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v4.2.2
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@9b252454a8d70586c4ee7f163bf4bb1e9de3d763 # v2 uses: DeterminateSystems/nix-installer-action@b92f66560d6f97d6576405a7bae901ab57e72b6a # v15
- name: Enable Binary Cache - name: Enable Binary Cache
uses: cachix/cachix-action@v12 uses: cachix/cachix-action@v15
with: with:
name: colmena name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
@ -32,7 +32,7 @@ jobs:
run: nix build .#manual -L run: nix build .#manual -L
- name: Deploy manual - name: Deploy manual
uses: JamesIves/github-pages-deploy-action@v4.3.4 uses: JamesIves/github-pages-deploy-action@v4.6.9
with: with:
branch: gh-pages branch: gh-pages
folder: result folder: result
@ -47,7 +47,7 @@ jobs:
run: nix build .#manual.redirectFarm -L run: nix build .#manual.redirectFarm -L
- name: Deploy redirect farm - name: Deploy redirect farm
uses: JamesIves/github-pages-deploy-action@4.1.6 uses: JamesIves/github-pages-deploy-action@v4.6.9
with: with:
branch: gh-pages branch: gh-pages
folder: result-redirectFarm folder: result-redirectFarm

View file

@ -13,15 +13,15 @@ jobs:
name: ${{ matrix.os.label }} name: ${{ matrix.os.label }}
runs-on: ${{ matrix.os.image }} runs-on: ${{ matrix.os.image }}
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@9b252454a8d70586c4ee7f163bf4bb1e9de3d763 # v2 uses: DeterminateSystems/nix-installer-action@b92f66560d6f97d6576405a7bae901ab57e72b6a # v15
- name: Enable Binary Cache - name: Enable Binary Cache
uses: cachix/cachix-action@v12 uses: cachix/cachix-action@v15
with: with:
name: colmena name: colmena
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
result* result*
/target /target
/.direnv /.direnv
/.vscode

18
.srcignore Normal file
View file

@ -0,0 +1,18 @@
# Exclusions from source distribution
#
# Files listed here will not be part of colmena.src
/.github
/CNAME
/renovate.json
/manual
/integration-tests
/nix
/default.nix
/flake-compat.nix
/package.nix
/shell.nix
# vim: set ft=gitignore:

1247
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,18 +9,17 @@ edition = "2021"
[dependencies] [dependencies]
async-stream = "0.3.5" async-stream = "0.3.5"
async-trait = "0.1.68" async-trait = "0.1.68"
atty = "0.2"
clap = { version = "4.3", features = ["derive"] } clap = { version = "4.3", features = ["derive"] }
clap_complete = "4.3" clap_complete = "4.3"
clicolors-control = "1" clicolors-control = "1"
console = "0.15.5" console = "0.15.5"
const_format = "0.2.30" const_format = "0.2.30"
env_logger = "0.10.0" env_logger = "0.11.0"
futures = "0.3.28" futures = "0.3.28"
glob = "0.3.1" glob = "0.3.1"
hostname = "0.3.1" hostname = "0.4.0"
indicatif = "0.17.3" indicatif = "0.17.3"
itertools = "0.11.0" itertools = "0.13.0"
libc = "0.2.144" libc = "0.2.144"
log = "0.4.17" log = "0.4.17"
quit = "2.0.0" quit = "2.0.0"
@ -28,12 +27,12 @@ regex = "1"
serde = { version = "1.0.163", features = ["derive"] } serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
shell-escape = "0.1.5" shell-escape = "0.1.5"
snafu = { version = "0.7.4", features = ["backtrace", "backtraces-impl-backtrace-crate"] } snafu = { version = "0.8.0", features = ["backtrace", "backtraces-impl-backtrace-crate"] }
sys-info = "0.9.1" sys-info = "0.9.1"
tempfile = "3.5.0" tempfile = "3.5.0"
tokio-stream = "0.1.14" tokio-stream = "0.1.14"
uuid = { version = "1.3.2", features = ["serde", "v4"] } uuid = { version = "1.3.2", features = ["serde", "v4"] }
validator = { version = "0.16.0", features = ["derive"] } validator = { version = "0.19.0", features = ["derive"] }
[dev-dependencies] [dev-dependencies]
ntest = "0.9.0" ntest = "0.9.0"

View file

@ -3,7 +3,7 @@
[![Matrix Channel](https://img.shields.io/badge/Matrix-%23colmena%3Anixos.org-blueviolet)](https://matrix.to/#/#colmena:nixos.org) [![Matrix Channel](https://img.shields.io/badge/Matrix-%23colmena%3Anixos.org-blueviolet)](https://matrix.to/#/#colmena:nixos.org)
[![Stable Manual](https://img.shields.io/badge/Manual-Stable-informational)](https://colmena.cli.rs/stable) [![Stable Manual](https://img.shields.io/badge/Manual-Stable-informational)](https://colmena.cli.rs/stable)
[![Unstable Manual](https://img.shields.io/badge/Manual-Unstable-orange)](https://colmena.cli.rs/unstable) [![Unstable Manual](https://img.shields.io/badge/Manual-Unstable-orange)](https://colmena.cli.rs/unstable)
[![Build](https://github.com/zhaofengli/colmena/workflows/Build/badge.svg)](https://github.com/zhaofengli/colmena/actions/workflows/build.yml) [![Build](https://github.com/zhaofengli/colmena/actions/workflows/build.yml/badge.svg)](https://github.com/zhaofengli/colmena/actions/workflows/build.yml)
Colmena is a simple, stateless [NixOS](https://nixos.org) deployment tool modeled after [NixOps](https://github.com/NixOS/nixops) and [morph](https://github.com/DBCDK/morph), written in Rust. Colmena is a simple, stateless [NixOS](https://nixos.org) deployment tool modeled after [NixOps](https://github.com/NixOS/nixops) and [morph](https://github.com/DBCDK/morph), written in Rust.
It's a thin wrapper over Nix commands like `nix-instantiate` and `nix-copy-closure`, and supports parallel deployment. It's a thin wrapper over Nix commands like `nix-instantiate` and `nix-copy-closure`, and supports parallel deployment.

View file

@ -31,13 +31,33 @@
"type": "github" "type": "github"
} }
}, },
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1696019113, "lastModified": 1730785428,
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=", "narHash": "sha256-Zwl8YgTVJTEum+L+0zVAWvXAGbWAuXHax3KzuejaDyo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a", "rev": "4aa36568d413aca0ea84a1684d2d46f55dbabad7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -51,22 +71,23 @@
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"stable": "stable" "stable": "stable"
} }
}, },
"stable": { "stable": {
"locked": { "locked": {
"lastModified": 1696039360, "lastModified": 1730883749,
"narHash": "sha256-g7nIUV4uq1TOVeVIDEZLb005suTWCUjSY0zYOlSBsyE=", "narHash": "sha256-mwrFF0vElHJP8X3pFCByJR365Q2463ATp2qGIrDUdlE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "32dcb45f66c0487e92db8303a798ebc548cadedc", "rev": "dba414932936fde69f0606b4f1d87c5bc0003ede",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.05", "ref": "nixos-24.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View file

@ -3,7 +3,12 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
stable.url = "github:NixOS/nixpkgs/nixos-23.05"; stable.url = "github:NixOS/nixpkgs/nixos-24.05";
nix-github-actions = {
url = "github:nix-community/nix-github-actions";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
@ -13,12 +18,41 @@
}; };
}; };
outputs = { self, nixpkgs, stable, flake-utils, ... } @ inputs: let outputs = {
self,
nixpkgs,
stable,
flake-utils,
nix-github-actions,
...
} @ inputs: let
supportedSystems = [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; supportedSystems = [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
colmenaOptions = import ./src/nix/hive/options.nix; colmenaOptions = import ./src/nix/hive/options.nix;
colmenaModules = import ./src/nix/hive/modules.nix; colmenaModules = import ./src/nix/hive/modules.nix;
# Temporary fork of nix-eval-jobs with changes to be upstreamed
# Mostly for the integration test setup and not needed in most use cases
_evalJobsOverlay = final: prev: let
patched = prev.nix-eval-jobs.overrideAttrs (old: {
version = old.version + "-colmena";
patches = (old.patches or []) ++ [
# Allows NIX_PATH to be honored
(final.fetchpatch {
url = "https://github.com/zhaofengli/nix-eval-jobs/commit/6ff5972724230ac2b96eb1ec355cd25ca512ef57.patch";
hash = "sha256-2NiMYpw27N+X7Ixh2HkP3fcWvopDJWQDVjgRdhOL2QQ";
})
];
});
in {
nix-eval-jobs = patched;
};
in flake-utils.lib.eachSystem supportedSystems (system: let in flake-utils.lib.eachSystem supportedSystems (system: let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = import nixpkgs {
inherit system;
overlays = [
_evalJobsOverlay
];
};
in rec { in rec {
# We still maintain the expression in a Nixpkgs-acceptable form # We still maintain the expression in a Nixpkgs-acceptable form
defaultPackage = self.packages.${system}.colmena; defaultPackage = self.packages.${system}.colmena;
@ -83,11 +117,21 @@
in if pkgs.stdenv.isLinux then import ./integration-tests { in if pkgs.stdenv.isLinux then import ./integration-tests {
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [ self.overlays.default inputsOverlay ]; overlays = [
self.overlays.default
inputsOverlay
_evalJobsOverlay
];
}; };
pkgsStable = import stable { pkgsStable = import stable {
inherit system; inherit system;
overlays = [ self.overlays.default inputsOverlay ]; overlays = [
self.overlays.default
inputsOverlay
_evalJobsOverlay
];
}; };
} else {}; } else {};
}) // { }) // {
@ -104,6 +148,12 @@
inherit rawHive colmenaOptions colmenaModules; inherit rawHive colmenaOptions colmenaModules;
hermetic = true; hermetic = true;
}; };
githubActions = nix-github-actions.lib.mkGithubMatrix {
checks = {
inherit (self.checks) x86_64-linux;
};
};
}; };
nixConfig = { nixConfig = {

View file

@ -1,3 +0,0 @@
builds:
include:
- 'checks.x86_64-linux.*'

View file

@ -8,8 +8,18 @@
apply-local = import ./apply-local { inherit pkgs; }; apply-local = import ./apply-local { inherit pkgs; };
build-on-target = import ./build-on-target { inherit pkgs; }; build-on-target = import ./build-on-target { inherit pkgs; };
exec = import ./exec { inherit pkgs; }; exec = import ./exec { inherit pkgs; };
flakes = import ./flakes { inherit pkgs; };
flakes-streaming = import ./flakes { inherit pkgs; evaluator = "streaming"; }; # FIXME: The old evaluation method doesn't work purely with Nix 2.21+
flakes = import ./flakes {
inherit pkgs;
extraApplyFlags = "--experimental-flake-eval";
};
flakes-impure = import ./flakes {
inherit pkgs;
pure = false;
};
#flakes-streaming = import ./flakes { inherit pkgs; evaluator = "streaming"; };
parallel = import ./parallel { inherit pkgs; }; parallel = import ./parallel { inherit pkgs; };
allow-apply-all = import ./allow-apply-all { inherit pkgs; }; allow-apply-all = import ./allow-apply-all { inherit pkgs; };

View file

@ -1,13 +1,29 @@
{ pkgs { pkgs
, evaluator ? "chunked" , evaluator ? "chunked"
, extraApplyFlags ? ""
, pure ? true
}: }:
let let
inherit (pkgs) lib;
tools = pkgs.callPackage ../tools.nix { tools = pkgs.callPackage ../tools.nix {
targets = [ "alpha" ]; targets = [ "alpha" ];
}; };
applyFlags = "--evaluator ${evaluator} ${extraApplyFlags}"
+ lib.optionalString (!pure) "--impure";
# From integration-tests/nixpkgs.nix
colmenaFlakeInputs = pkgs._inputs;
in tools.runTest { in tools.runTest {
name = "colmena-flakes-${evaluator}"; name = "colmena-flakes-${evaluator}"
+ lib.optionalString (!pure) "-impure";
nodes.deployer = {
virtualisation.additionalPaths =
lib.mapAttrsToList (k: v: v.outPath) colmenaFlakeInputs;
};
colmena.test = { colmena.test = {
bundle = ./.; bundle = ./.;
@ -16,12 +32,13 @@ in tools.runTest {
import re import re
deployer.succeed("sed -i 's @nixpkgs@ path:${pkgs._inputs.nixpkgs.outPath}?narHash=${pkgs._inputs.nixpkgs.narHash} g' /tmp/bundle/flake.nix") deployer.succeed("sed -i 's @nixpkgs@ path:${pkgs._inputs.nixpkgs.outPath}?narHash=${pkgs._inputs.nixpkgs.narHash} g' /tmp/bundle/flake.nix")
deployer.succeed("sed -i 's @colmena@ path:${tools.colmena.src} g' /tmp/bundle/flake.nix")
with subtest("Lock flake dependencies"): with subtest("Lock flake dependencies"):
deployer.succeed("cd /tmp/bundle && nix --extra-experimental-features \"nix-command flakes\" flake lock") deployer.succeed("cd /tmp/bundle && nix --extra-experimental-features \"nix-command flakes\" flake lock")
with subtest("Deploy with a plain flake without git"): with subtest("Deploy with a plain flake without git"):
deployer.succeed("cd /tmp/bundle && ${tools.colmenaExec} apply --on @target --evaluator ${evaluator}") deployer.succeed("cd /tmp/bundle && ${tools.colmenaExec} apply --on @target ${applyFlags}")
alpha.succeed("grep FIRST /etc/deployment") alpha.succeed("grep FIRST /etc/deployment")
with subtest("Deploy with a git flake"): with subtest("Deploy with a git flake"):
@ -29,21 +46,22 @@ in tools.runTest {
# don't put probe.nix in source control - should fail # don't put probe.nix in source control - should fail
deployer.succeed("cd /tmp/bundle && git init && git add flake.nix flake.lock hive.nix tools.nix") deployer.succeed("cd /tmp/bundle && git init && git add flake.nix flake.lock hive.nix tools.nix")
logs = deployer.fail("cd /tmp/bundle && run-copy-stderr ${tools.colmenaExec} apply --on @target --evaluator ${evaluator}") logs = deployer.fail("cd /tmp/bundle && run-copy-stderr ${tools.colmenaExec} apply --on @target ${applyFlags}")
assert re.search(r"probe.nix.*No such file or directory", logs) assert re.search(r"probe.nix.*(No such file or directory|does not exist)", logs), "Expected error message not found in log"
# now it should succeed # now it should succeed
deployer.succeed("cd /tmp/bundle && git add probe.nix") deployer.succeed("cd /tmp/bundle && git add probe.nix")
deployer.succeed("cd /tmp/bundle && ${tools.colmenaExec} apply --on @target --evaluator ${evaluator}") deployer.succeed("cd /tmp/bundle && ${tools.colmenaExec} apply --on @target ${applyFlags}")
alpha.succeed("grep SECOND /etc/deployment") alpha.succeed("grep SECOND /etc/deployment")
'' + lib.optionalString pure ''
with subtest("Check that impure expressions are forbidden"): with subtest("Check that impure expressions are forbidden"):
deployer.succeed("sed -i 's|SECOND|''${builtins.readFile /etc/hostname}|g' /tmp/bundle/probe.nix") deployer.succeed("sed -i 's|SECOND|''${builtins.readFile /etc/hostname}|g' /tmp/bundle/probe.nix")
logs = deployer.fail("cd /tmp/bundle && run-copy-stderr ${tools.colmenaExec} apply --on @target --evaluator ${evaluator}") logs = deployer.fail("cd /tmp/bundle && run-copy-stderr ${tools.colmenaExec} apply --on @target ${applyFlags}")
assert re.search(r"access to absolute path.*forbidden in pure eval mode", logs) assert re.search(r"access to absolute path.*forbidden in pure (eval|evaluation) mode", logs), "Expected error message not found in log"
with subtest("Check that impure expressions can be allowed with --impure"): with subtest("Check that impure expressions can be allowed with --impure"):
deployer.succeed("cd /tmp/bundle && ${tools.colmenaExec} apply --on @target --evaluator ${evaluator} --impure") deployer.succeed("cd /tmp/bundle && ${tools.colmenaExec} apply --on @target ${applyFlags} --impure")
alpha.succeed("grep deployer /etc/deployment") alpha.succeed("grep deployer /etc/deployment")
''; '';
}; };

View file

@ -3,13 +3,15 @@
inputs = { inputs = {
nixpkgs.url = "@nixpkgs@"; nixpkgs.url = "@nixpkgs@";
colmena.url = "@colmena@";
}; };
outputs = { self, nixpkgs }: let outputs = { self, nixpkgs, colmena }: let
pkgs = import nixpkgs { pkgs = import nixpkgs {
system = "x86_64-linux"; system = "x86_64-linux";
}; };
in { in {
colmena = import ./hive.nix { inherit pkgs; }; colmena = import ./hive.nix { inherit pkgs; };
colmenaHive = colmena.lib.makeHive self.outputs.colmena;
}; };
} }

View file

@ -140,7 +140,7 @@ let
nix.settings.substituters = lib.mkForce []; nix.settings.substituters = lib.mkForce [];
virtualisation = { virtualisation = {
memorySize = 4096; memorySize = 6144;
writableStore = true; writableStore = true;
additionalPaths = [ additionalPaths = [
"${pkgs.path}" "${pkgs.path}"
@ -165,6 +165,9 @@ let
exec "$@" 2> >(tee /dev/stderr) exec "$@" 2> >(tee /dev/stderr)
'') '')
]; ];
# Re-enable switch-to-configuration
system.switch.enable = true;
}; };
# Setup for target nodes # Setup for target nodes
@ -180,6 +183,9 @@ let
sshKeys.snakeOilPublicKey sshKeys.snakeOilPublicKey
]; ];
virtualisation.writableStore = true; virtualisation.writableStore = true;
# Re-enable switch-to-configuration
system.switch.enable = true;
}; };
nodes = let nodes = let

View file

@ -90,6 +90,34 @@ To build and deploy to all nodes:
colmena apply colmena apply
``` ```
## Direct Flake Evaluation (Experimental)
By default, Colmena uses `nix-instantiate` to evaluate your flake which does not work purely on Nix 2.21+, necessitating the use of `--impure`.
There is experimental support for evaluating flakes directly with `nix eval`, enabled via `--experimental-flake-eval`.
To use this new evaluation mode, your flake needs to depend on Colmena itself as an input and expose a new output called `colmenaHive`:
```diff
{
inputs = {
+ # ADDED: Colmena input
+ colmena.url = "github:zhaofengli/colmena";
# ... Rest of configuration ...
};
outputs = { self, colmena, ... }: {
+ # ADDED: New colmenaHive output
+ colmenaHive = colmena.lib.makeHive self.outputs.colmena;
# Your existing colmena output
colmena = {
# ... Rest of configuration ...
};
};
}
```
## Next Steps ## Next Steps
- Head to the [Features](../features/index.md) section to see what else Colmena can do. - Head to the [Features](../features/index.md) section to see what else Colmena can do.

View file

@ -1,13 +1,16 @@
{ lib, stdenv, rustPlatform, installShellFiles, nix-eval-jobs }: { lib
, stdenv
, rustPlatform
, nix-gitignore
, installShellFiles
, nix-eval-jobs
}:
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
pname = "colmena"; pname = "colmena";
version = "0.5.0-pre"; version = "0.5.0-pre";
src = lib.cleanSourceWith { src = nix-gitignore.gitignoreSource [ ./.srcignore ] ./.;
filter = name: type: !(type == "directory" && builtins.elem (baseNameOf name) [ "target" "manual" "integration-tests" ]);
src = lib.cleanSource ./.;
};
cargoLock = { cargoLock = {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;

14
renovate.json Normal file
View file

@ -0,0 +1,14 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"group:allNonMajor"
],
"lockFileMaintenance": {
"enabled": true,
"extends": ["schedule:weekly"]
},
"nix": {
"enabled": true
}
}

View file

@ -10,7 +10,7 @@ use env_logger::fmt::WriteStyle;
use crate::{ use crate::{
command::{self, apply::DeployOpts}, command::{self, apply::DeployOpts},
error::ColmenaResult, error::ColmenaResult,
nix::{Hive, HivePath}, nix::{hive::EvaluationMethod, Hive, HivePath},
}; };
/// Base URL of the manual, without the trailing slash. /// Base URL of the manual, without the trailing slash.
@ -137,6 +137,21 @@ This only works when building locally.
value_names = ["NAME", "VALUE"], value_names = ["NAME", "VALUE"],
)] )]
nix_option: Vec<String>, nix_option: Vec<String>,
#[arg(
long,
default_value_t,
help = "Use direct flake evaluation (experimental)",
long_help = r#"If enabled, flakes will be evaluated using `nix eval`. This requires the flake to depend on Colmena as an input and expose a compatible `colmenaHive` output:
outputs = { self, colmena, ... }: {
colmenaHive = colmena.lib.makeHive self.outputs.colmena;
colmena = ...;
};
This is an experimental feature."#,
global = true
)]
experimental_flake_eval: bool,
#[arg( #[arg(
long, long,
value_name = "WHEN", value_name = "WHEN",
@ -262,6 +277,11 @@ async fn get_hive(opts: &Opts) -> ColmenaResult<Hive> {
hive.set_impure(true); hive.set_impure(true);
} }
if opts.experimental_flake_eval {
log::warn!("Using direct flake evaluation (experimental)");
hive.set_evaluation_method(EvaluationMethod::DirectFlakeEval);
}
for chunks in opts.nix_option.chunks_exact(2) { for chunks in opts.nix_option.chunks_exact(2) {
let [name, value] = chunks else { let [name, value] = chunks else {
unreachable!() unreachable!()

View file

@ -64,6 +64,12 @@ pub enum ColmenaError {
#[snafu(display("Don't know how to connect to the node"))] #[snafu(display("Don't know how to connect to the node"))]
NoTargetHost, NoTargetHost,
#[snafu(display(
"Don't know how to deploy node: {} -- does your system type support deployment?",
node_name
))]
UndeployableHost { node_name: String },
#[snafu(display("Node name cannot be empty"))] #[snafu(display("Node name cannot be empty"))]
EmptyNodeName, EmptyNodeName,

View file

@ -874,11 +874,17 @@ fn describe_node_list(nodes: &[NodeName]) -> Option<String> {
} }
let (idx, next) = next.unwrap(); let (idx, next) = next.unwrap();
let remaining = rough_limit - s.len(); let remaining_text = rough_limit - s.len();
let remaining_nodes = total - idx;
if next.len() + other_text.len() >= remaining { if next.len() + other_text.len() >= remaining_text {
write!(s, ", and {} other nodes", total - idx).unwrap(); if remaining_nodes == 1 {
break; write!(s, ", and {}", next.as_str()).unwrap();
break;
} else {
write!(s, ", and {} other nodes", remaining_nodes).unwrap();
break;
}
} }
} }

View file

@ -51,7 +51,10 @@ impl Assets {
// We explicitly specify `path:` instead of letting Nix resolve // We explicitly specify `path:` instead of letting Nix resolve
// automatically, which would involve checking parent directories // automatically, which would involve checking parent directories
// for a git repository. // for a git repository.
let uri = format!("path:{}", temp_dir.path().to_str().unwrap()); let uri = format!(
"path:{}",
temp_dir.path().canonicalize().unwrap().to_str().unwrap()
);
let _ = lock_flake_quiet(&uri).await; let _ = lock_flake_quiet(&uri).await;
let assets_flake = Flake::from_uri(uri).await?; let assets_flake = Flake::from_uri(uri).await?;
assets_flake_uri = Some(assets_flake.locked_uri().to_owned()); assets_flake_uri = Some(assets_flake.locked_uri().to_owned());

View file

@ -38,19 +38,25 @@ let
else if uncheckedHive ? network then uncheckedHive.network else if uncheckedHive ? network then uncheckedHive.network
else {}; else {};
uncheckedRegistries = if uncheckedHive ? registry then uncheckedHive.registry else {};
# The final hive will always have the meta key instead of network. # The final hive will always have the meta key instead of network.
hive = let hive = let
userMeta = (lib.modules.evalModules { userMeta = (lib.modules.evalModules {
modules = [ colmenaOptions.metaOptions uncheckedUserMeta ]; modules = [ colmenaOptions.metaOptions uncheckedUserMeta ];
}).config; }).config;
registry = (lib.modules.evalModules {
modules = [ colmenaOptions.registryOptions { registry = uncheckedRegistries; } ];
}).config.registry;
mergedHive = mergedHive =
assert lib.assertMsg (!(uncheckedHive ? __schema)) '' assert lib.assertMsg (!(uncheckedHive ? __schema)) ''
You cannot pass in an already-evaluated Hive into the evaluator. You cannot pass in an already-evaluated Hive into the evaluator.
Hint: Use the `colmenaHive` output instead of `colmena`. Hint: Use the `colmenaHive` output instead of `colmena`.
''; '';
removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" ]; removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" "registry" ];
meta = { meta = {
meta = meta =
@ -58,7 +64,7 @@ let
then userMeta // { nixpkgs = <nixpkgs>; } then userMeta // { nixpkgs = <nixpkgs>; }
else userMeta; else userMeta;
}; };
in mergedHive // meta; in mergedHive // meta // { inherit registry; };
configsFor = node: let configsFor = node: let
nodeConfig = hive.${node}; nodeConfig = hive.${node};
@ -112,14 +118,23 @@ let
in mkNixpkgs "meta.nixpkgs" nixpkgsConf; in mkNixpkgs "meta.nixpkgs" nixpkgsConf;
lib = nixpkgs.lib; lib = nixpkgs.lib;
reservedNames = [ "defaults" "network" "meta" ]; reservedNames = [ "defaults" "network" "meta" "registry" ];
evalNode = name: configs: let evalNode = name: configs:
# Some help on error messages.
assert (lib.assertMsg (lib.hasAttrByPath [ "deployment" "systemType" ] hive.${name})
"${name} does not have a deployment system type!");
assert (lib.assertMsg (builtins.typeOf hive.registry == "set"))
"The hive's registry is not a set, but of type '${builtins.typeOf hive.registry}'";
assert (lib.assertMsg (lib.hasAttr hive.${name}.deployment.systemType hive.registry)
"${builtins.toJSON (hive.${name}.deployment.systemType)} does not exist in the registry of systems!");
let
# We cannot use `configs` because we need to access to the raw configuration fragment.
inherit (hive.registry.${hive.${name}.deployment.systemType}) evalConfig;
npkgs = npkgs =
if hasAttr name hive.meta.nodeNixpkgs if hasAttr name hive.meta.nodeNixpkgs
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name} then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
else nixpkgs; else nixpkgs;
evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix");
# Here we need to merge the configurations in meta.nixpkgs # Here we need to merge the configurations in meta.nixpkgs
# and in machine config. # and in machine config.
@ -139,17 +154,19 @@ let
in in
lib.optional (!hasTypedConfig && length remainingKeys != 0) lib.optional (!hasTypedConfig && length remainingKeys != 0)
"The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}"; "The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}";
}; } // lib.optionalAttrs (builtins.hasAttr "localSystem" npkgs || builtins.hasAttr "crossSystem" npkgs) {
nixpkgs.localSystem = lib.mkBefore npkgs.localSystem;
nixpkgs.crossSystem = lib.mkBefore npkgs.crossSystem;
};
in evalConfig { in evalConfig {
inherit (npkgs) system; # This doesn't exist for `evalModules` the generic way.
# inherit (npkgs) system;
modules = [ modules = [
nixpkgsModule nixpkgsModule
colmenaModules.assertionModule colmenaModules.assertionModule
colmenaModules.keyChownModule
colmenaModules.keyServiceModule
colmenaOptions.deploymentOptions colmenaOptions.deploymentOptions
hive.defaults (hive.registry.${hive.${name}.deployment.systemType}.defaults or hive.defaults)
] ++ configs; ] ++ configs;
specialArgs = { specialArgs = {
inherit name; inherit name;
@ -179,9 +196,13 @@ let
"allowApplyAll" "allowApplyAll"
]; ];
serializableSystemTypeConfigKeys = [
"supportsDeployment"
];
in rec { in rec {
# Exported attributes # Exported attributes
__schema = "v0"; __schema = "v0.20241006";
nodes = listToAttrs (map (name: { inherit name; value = evalNode name (configsFor name); }) nodeNames); nodes = listToAttrs (map (name: { inherit name; value = evalNode name (configsFor name); }) nodeNames);
toplevel = lib.mapAttrs (_: v: v.config.system.build.toplevel) nodes; toplevel = lib.mapAttrs (_: v: v.config.system.build.toplevel) nodes;
@ -190,5 +211,9 @@ in rec {
evalSelected = names: lib.filterAttrs (name: _: elem name names) toplevel; evalSelected = names: lib.filterAttrs (name: _: elem name names) toplevel;
evalSelectedDrvPaths = names: lib.mapAttrs (_: v: v.drvPath) (evalSelected names); evalSelectedDrvPaths = names: lib.mapAttrs (_: v: v.drvPath) (evalSelected names);
metaConfig = lib.filterAttrs (n: v: elem n metaConfigKeys) hive.meta; metaConfig = lib.filterAttrs (n: v: elem n metaConfigKeys) hive.meta;
introspect = f: f { inherit lib; pkgs = nixpkgs; nodes = uncheckedNodes; }; # We cannot perform a `metaConfigKeys`-style simple check here
# because registry is arbitrarily deep and may evaluate nixpkgs indirectly.
registryConfig = lib.mapAttrs (systemTypeName: systemType:
lib.filterAttrs (n: v: elem n serializableSystemTypeConfigKeys) systemType) hive.registry;
introspect = f: f { inherit lib; pkgs = nixpkgs; inherit nodes; };
} }

View file

@ -7,7 +7,7 @@
outputs = { self, hive }: { outputs = { self, hive }: {
processFlake = let processFlake = let
compatibleSchema = "v0"; compatibleSchema = "v0.20241006";
# Evaluates a raw hive. # Evaluates a raw hive.
# #

View file

@ -8,6 +8,7 @@ use std::convert::AsRef;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use const_format::formatcp;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use validator::Validate; use validator::Validate;
@ -15,13 +16,28 @@ use validator::Validate;
use super::deployment::TargetNode; use super::deployment::TargetNode;
use super::{ use super::{
Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName, Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName,
ProfileDerivation, SerializedNixExpression, StorePath, ProfileDerivation, RegistryConfig, SerializedNixExpression, StorePath,
}; };
use crate::error::{ColmenaError, ColmenaResult}; use crate::error::{ColmenaError, ColmenaResult};
use crate::job::JobHandle; use crate::job::JobHandle;
use crate::util::{CommandExecution, CommandExt}; use crate::util::{CommandExecution, CommandExt};
use assets::Assets; use assets::Assets;
/// The version of the Hive schema we are compatible with.
///
/// Currently we are tied to one specific version.
const HIVE_SCHEMA: &str = "v0.20241006";
/// The snippet to be used for `nix eval --apply`.
const FLAKE_APPLY_SNIPPET: &str = formatcp!(
r#"with builtins; hive: assert (hive.__schema == "{}" || throw ''
The colmenaHive output (schema ${{hive.__schema}}) isn't compatible with this version of Colmena.
Hint: Use the same version of Colmena as in the Flake input.
''); "#,
HIVE_SCHEMA
);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum HivePath { pub enum HivePath {
/// A Nix Flake. /// A Nix Flake.
@ -63,11 +79,33 @@ impl FromStr for HivePath {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EvaluationMethod {
/// Use nix-instantiate and specify the entire Nix expression.
///
/// This is the default method.
///
/// For flakes, we use `builtins.getFlakes`. Pure evaluation no longer works
/// with this method in Nix 2.21+.
NixInstantiate,
/// Use `nix eval --apply` on top of a flake.
///
/// This can be activated with --experimental-flake-eval.
///
/// In this method, we can no longer pull in our bundled assets and
/// the flake must expose a compatible `colmenaHive` output.
DirectFlakeEval,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Hive { pub struct Hive {
/// Path to the hive. /// Path to the hive.
path: HivePath, path: HivePath,
/// Method to evaluate the hive with.
evaluation_method: EvaluationMethod,
/// Path to the context directory. /// Path to the context directory.
/// ///
/// Normally this is directory containing the "hive.nix" /// Normally this is directory containing the "hive.nix"
@ -87,6 +125,8 @@ pub struct Hive {
nix_options: HashMap<String, String>, nix_options: HashMap<String, String>,
meta_config: OnceCell<MetaConfig>, meta_config: OnceCell<MetaConfig>,
registry_config: OnceCell<RegistryConfig>,
} }
struct NixInstantiate<'hive> { struct NixInstantiate<'hive> {
@ -134,12 +174,14 @@ impl Hive {
Ok(Self { Ok(Self {
path, path,
evaluation_method: EvaluationMethod::NixInstantiate,
context_dir, context_dir,
assets, assets,
show_trace: false, show_trace: false,
impure: false, impure: false,
nix_options: HashMap::new(), nix_options: HashMap::new(),
meta_config: OnceCell::new(), meta_config: OnceCell::new(),
registry_config: OnceCell::new(),
}) })
} }
@ -158,6 +200,25 @@ impl Hive {
.await .await
} }
pub fn set_evaluation_method(&mut self, method: EvaluationMethod) {
if !self.is_flake() && method == EvaluationMethod::DirectFlakeEval {
return;
}
self.evaluation_method = method;
}
pub async fn get_registry_config(&self) -> ColmenaResult<&RegistryConfig> {
self.registry_config
.get_or_try_init(|| async {
self.nix_instantiate("hive.registryConfig")
.eval()
.capture_json()
.await
})
.await
}
pub fn set_show_trace(&mut self, value: bool) { pub fn set_show_trace(&mut self, value: bool) {
self.show_trace = value; self.show_trace = value;
} }
@ -201,6 +262,9 @@ impl Hive {
) -> ColmenaResult<HashMap<NodeName, TargetNode>> { ) -> ColmenaResult<HashMap<NodeName, TargetNode>> {
let mut node_configs = None; let mut node_configs = None;
log::info!("Enumerating systems...");
let registry = self.get_registry_config().await?;
log::info!("Enumerating nodes..."); log::info!("Enumerating nodes...");
let all_nodes = self.node_names().await?; let all_nodes = self.node_names().await?;
@ -234,6 +298,24 @@ impl Hive {
self.deployment_info_selected(&selected_nodes).await? self.deployment_info_selected(&selected_nodes).await?
}; };
for node_config in &node_configs {
if let Some(system_type) = node_config.1.system_type.as_ref() {
let Some(system_config) = registry.systems.get(system_type) else {
// TODO: convert me to proper error?
log::warn!("'{:?}' is not a known system type in the registry, double check your expressions!", system_type);
return Err(ColmenaError::Unknown {
message: "unknown system type".to_string(),
});
};
if !system_config.supports_deployment {
return Err(ColmenaError::UndeployableHost {
node_name: node_config.0.to_string(),
});
}
}
}
let mut targets = HashMap::new(); let mut targets = HashMap::new();
let mut n_ssh = 0; let mut n_ssh = 0;
for node in selected_nodes.into_iter() { for node in selected_nodes.into_iter() {
@ -421,7 +503,10 @@ impl Hive {
/// Returns the base expression from which the evaluated Hive can be used. /// Returns the base expression from which the evaluated Hive can be used.
fn get_base_expression(&self) -> String { fn get_base_expression(&self) -> String {
self.assets.get_base_expression() match self.evaluation_method {
EvaluationMethod::NixInstantiate => self.assets.get_base_expression(),
EvaluationMethod::DirectFlakeEval => FLAKE_APPLY_SNIPPET.to_string(),
}
} }
/// Returns whether this Hive is a flake. /// Returns whether this Hive is a flake.
@ -444,6 +529,11 @@ impl<'hive> NixInstantiate<'hive> {
} }
fn instantiate(&self) -> Command { fn instantiate(&self) -> Command {
// TODO: Better error handling
if self.hive.evaluation_method == EvaluationMethod::DirectFlakeEval {
panic!("Instantiation is not supported with DirectFlakeEval");
}
let mut command = Command::new("nix-instantiate"); let mut command = Command::new("nix-instantiate");
if self.hive.is_flake() { if self.hive.is_flake() {
@ -462,17 +552,48 @@ impl<'hive> NixInstantiate<'hive> {
} }
fn eval(self) -> Command { fn eval(self) -> Command {
let mut command = self.instantiate();
let flags = self.hive.nix_flags(); let flags = self.hive.nix_flags();
command
.arg("--eval") match self.hive.evaluation_method {
.arg("--json") EvaluationMethod::NixInstantiate => {
.arg("--strict") let mut command = self.instantiate();
// Ensures the derivations are instantiated
// Required for system profile evaluation and IFD command
.arg("--read-write-mode") .arg("--eval")
.args(flags.to_args()); .arg("--json")
command .arg("--strict")
// Ensures the derivations are instantiated
// Required for system profile evaluation and IFD
.arg("--read-write-mode")
.args(flags.to_args());
command
}
EvaluationMethod::DirectFlakeEval => {
let mut command = Command::new("nix");
let flake = if let HivePath::Flake(flake) = self.hive.path() {
flake
} else {
panic!("The DirectFlakeEval evaluation method only support flakes");
};
let hive_installable = format!("{}#colmenaHive", flake.uri());
let mut full_expression = self.hive.get_base_expression();
full_expression += &self.expression;
command
.arg("eval") // nix eval
.args(["--extra-experimental-features", "flakes nix-command"])
.arg(hive_installable)
.arg("--json")
.arg("--apply")
.arg(&full_expression)
.args(flags.to_args());
command
}
}
} }
async fn instantiate_with_builders(self) -> ColmenaResult<Command> { async fn instantiate_with_builders(self) -> ColmenaResult<Command> {

View file

@ -1,18 +1,17 @@
with builtins; rec { with builtins; rec {
keyType = { lib, name, config, ... }: let keyType = { lib, name, config, ... }: let
inherit (lib) types; inherit (lib) types;
mdDoc = lib.mdDoc or (md: md);
in { in {
options = { options = {
name = lib.mkOption { name = lib.mkOption {
description = mdDoc '' description = ''
File name of the key. File name of the key.
''; '';
default = name; default = name;
type = types.str; type = types.str;
}; };
text = lib.mkOption { text = lib.mkOption {
description = mdDoc '' description = ''
Content of the key. Content of the key.
One of `text`, `keyCommand` and `keyFile` must be set. One of `text`, `keyCommand` and `keyFile` must be set.
''; '';
@ -20,7 +19,7 @@ with builtins; rec {
type = types.nullOr types.str; type = types.nullOr types.str;
}; };
keyFile = lib.mkOption { keyFile = lib.mkOption {
description = mdDoc '' description = ''
Path of the local file to read the key from. Path of the local file to read the key from.
One of `text`, `keyCommand` and `keyFile` must be set. One of `text`, `keyCommand` and `keyFile` must be set.
''; '';
@ -29,7 +28,7 @@ with builtins; rec {
type = types.nullOr types.path; type = types.nullOr types.path;
}; };
keyCommand = lib.mkOption { keyCommand = lib.mkOption {
description = mdDoc '' description = ''
Command to run to generate the key. Command to run to generate the key.
One of `text`, `keyCommand` and `keyFile` must be set. One of `text`, `keyCommand` and `keyFile` must be set.
''; '';
@ -39,14 +38,14 @@ with builtins; rec {
in types.nullOr nonEmptyList; in types.nullOr nonEmptyList;
}; };
destDir = lib.mkOption { destDir = lib.mkOption {
description = mdDoc '' description = ''
Destination directory on the host. Destination directory on the host.
''; '';
default = "/run/keys"; default = "/run/keys";
type = types.path; type = types.path;
}; };
path = lib.mkOption { path = lib.mkOption {
description = mdDoc '' description = ''
Full path to the destination. Full path to the destination.
''; '';
default = "${config.destDir}/${config.name}"; default = "${config.destDir}/${config.name}";
@ -54,28 +53,28 @@ with builtins; rec {
internal = true; internal = true;
}; };
user = lib.mkOption { user = lib.mkOption {
description = mdDoc '' description = ''
The group that will own the file. The group that will own the file.
''; '';
default = "root"; default = "root";
type = types.str; type = types.str;
}; };
group = lib.mkOption { group = lib.mkOption {
description = mdDoc '' description = ''
The group that will own the file. The group that will own the file.
''; '';
default = "root"; default = "root";
type = types.str; type = types.str;
}; };
permissions = lib.mkOption { permissions = lib.mkOption {
description = mdDoc '' description = ''
Permissions to set for the file. Permissions to set for the file.
''; '';
default = "0600"; default = "0600";
type = types.str; type = types.str;
}; };
uploadAt = lib.mkOption { uploadAt = lib.mkOption {
description = mdDoc '' description = ''
When to upload the keys. When to upload the keys.
- pre-activation (default): Upload the keys before activating the new system profile. - pre-activation (default): Upload the keys before activating the new system profile.
@ -94,12 +93,19 @@ with builtins; rec {
# Largely compatible with NixOps/Morph. # Largely compatible with NixOps/Morph.
deploymentOptions = { name, lib, ... }: let deploymentOptions = { name, lib, ... }: let
inherit (lib) types; inherit (lib) types;
mdDoc = lib.mdDoc or (md: md);
in { in {
options = { options = {
deployment = { deployment = {
targetHost = lib.mkOption { systemType = lib.mkOption {
description = mdDoc '' description = mdDoc ''
System type used for this node, e.g. NixOS.
'';
default = "nixos";
# TODO: enum among all registered systems?
type = types.str;
};
targetHost = lib.mkOption {
description = ''
The target SSH node for deployment. The target SSH node for deployment.
By default, the node's attribute name will be used. By default, the node's attribute name will be used.
@ -109,7 +115,7 @@ with builtins; rec {
default = name; default = name;
}; };
targetPort = lib.mkOption { targetPort = lib.mkOption {
description = mdDoc '' description = ''
The target SSH port for deployment. The target SSH port for deployment.
By default, the port is the standard port (22) or taken By default, the port is the standard port (22) or taken
@ -119,7 +125,7 @@ with builtins; rec {
default = null; default = null;
}; };
targetUser = lib.mkOption { targetUser = lib.mkOption {
description = mdDoc '' description = ''
The user to use to log into the remote node. If set to null, the The user to use to log into the remote node. If set to null, the
target user will not be specified in SSH invocations. target user will not be specified in SSH invocations.
''; '';
@ -127,7 +133,7 @@ with builtins; rec {
default = "root"; default = "root";
}; };
allowLocalDeployment = lib.mkOption { allowLocalDeployment = lib.mkOption {
description = mdDoc '' description = ''
Allow the configuration to be applied locally on the host running Allow the configuration to be applied locally on the host running
Colmena. Colmena.
@ -144,7 +150,7 @@ with builtins; rec {
default = false; default = false;
}; };
buildOnTarget = lib.mkOption { buildOnTarget = lib.mkOption {
description = mdDoc '' description = ''
Whether to build the system profiles on the target node itself. Whether to build the system profiles on the target node itself.
When enabled, Colmena will copy the derivation to the target When enabled, Colmena will copy the derivation to the target
@ -164,7 +170,7 @@ with builtins; rec {
default = false; default = false;
}; };
tags = lib.mkOption { tags = lib.mkOption {
description = mdDoc '' description = ''
A list of tags for the node. A list of tags for the node.
Can be used to select a group of nodes for deployment. Can be used to select a group of nodes for deployment.
@ -173,7 +179,7 @@ with builtins; rec {
default = []; default = [];
}; };
keys = lib.mkOption { keys = lib.mkOption {
description = mdDoc '' description = ''
A set of secrets to be deployed to the node. A set of secrets to be deployed to the node.
Secrets are transferred to the node out-of-band and Secrets are transferred to the node out-of-band and
@ -183,7 +189,7 @@ with builtins; rec {
default = {}; default = {};
}; };
replaceUnknownProfiles = lib.mkOption { replaceUnknownProfiles = lib.mkOption {
description = mdDoc '' description = ''
Allow a configuration to be applied to a host running a profile we Allow a configuration to be applied to a host running a profile we
have no knowledge of. By setting this option to false, you reduce have no knowledge of. By setting this option to false, you reduce
the likelyhood of rolling back changes made via another Colmena user. the likelyhood of rolling back changes made via another Colmena user.
@ -199,7 +205,7 @@ with builtins; rec {
default = true; default = true;
}; };
privilegeEscalationCommand = lib.mkOption { privilegeEscalationCommand = lib.mkOption {
description = mdDoc '' description = ''
Command to use to elevate privileges when activating the new profiles on SSH hosts. Command to use to elevate privileges when activating the new profiles on SSH hosts.
This is used on SSH hosts when `deployment.targetUser` is not `root`. This is used on SSH hosts when `deployment.targetUser` is not `root`.
@ -209,7 +215,7 @@ with builtins; rec {
default = [ "sudo" "-H" "--" ]; default = [ "sudo" "-H" "--" ];
}; };
sshOptions = lib.mkOption { sshOptions = lib.mkOption {
description = mdDoc '' description = ''
Extra SSH options to pass to the SSH command. Extra SSH options to pass to the SSH command.
''; '';
type = types.listOf types.str; type = types.listOf types.str;
@ -218,32 +224,77 @@ with builtins; rec {
}; };
}; };
}; };
# Options for a registered system type
systemTypeOptions = { name, lib, ... }: let
inherit (lib) types;
mdDoc = lib.mdDoc or lib.id;
in
{
options = {
evalConfig = lib.mkOption {
description = mdDoc ''
Evaluation function which share the same interface as `nixos/lib/eval-config.nix`
which can be tailored to your own usecases or to target another type of system,
e.g. nix-darwin.
'';
type = types.functionTo types.unspecified;
};
supportsDeployment = lib.mkOption {
description = mdDoc ''
Whether this system type supports deployment or not.
If it supports deployment, it needs to have appropriate activation code,
refer to how to write custom activators.
'';
default = name == "nixos";
defaultText = "If a NixOS system, then true, otherwise false by default";
};
defaults = lib.mkOption {
description = mdDoc ''
Default configuration for that system type.
'';
type = types.functionTo types.unspecified;
default = _: {};
};
};
};
registryOptions = { lib, ... }: let
inherit (lib) types;
mdDoc = lib.mdDoc or lib.id;
in
{
options.registry = lib.mkOption {
description = mdDoc ''
A registry of all system types.
'';
type = types.attrsOf (types.submodule systemTypeOptions);
};
};
# Hive-wide options # Hive-wide options
metaOptions = { lib, ... }: let metaOptions = { lib, ... }: let
inherit (lib) types; inherit (lib) types;
mdDoc = lib.mdDoc or (md: md);
in { in {
options = { options = {
name = lib.mkOption { name = lib.mkOption {
description = mdDoc '' description = ''
The name of the configuration. The name of the configuration.
''; '';
type = types.str; type = types.str;
default = "hive"; default = "hive";
}; };
description = lib.mkOption { description = lib.mkOption {
description = mdDoc '' description = ''
A short description for the configuration. A short description for the configuration.
''; '';
type = types.str; type = types.str;
default = "A Colmena Hive"; default = "A Colmena Hive";
}; };
nixpkgs = lib.mkOption { nixpkgs = lib.mkOption {
description = mdDoc '' description = ''
The pinned Nixpkgs package set. Accepts one of the following: The pinned Nixpkgs package set. Accepts one of the following:
- A path to a Nixpkgs checkout - A path to a Nixpkgs checkout
- The Nixpkgs lambda (e.g., import \<nixpkgs\>) - The Nixpkgs lambda (e.g., import <nixpkgs>)
- An initialized Nixpkgs attribute set - An initialized Nixpkgs attribute set
This option must be specified when using Flakes. This option must be specified when using Flakes.
@ -252,21 +303,21 @@ with builtins; rec {
default = null; default = null;
}; };
nodeNixpkgs = lib.mkOption { nodeNixpkgs = lib.mkOption {
description = mdDoc '' description = ''
Node-specific Nixpkgs pins. Node-specific Nixpkgs pins.
''; '';
type = types.attrsOf types.unspecified; type = types.attrsOf types.unspecified;
default = {}; default = {};
}; };
nodeSpecialArgs = lib.mkOption { nodeSpecialArgs = lib.mkOption {
description = mdDoc '' description = ''
Node-specific special args. Node-specific special args.
''; '';
type = types.attrsOf types.unspecified; type = types.attrsOf types.unspecified;
default = {}; default = {};
}; };
machinesFile = lib.mkOption { machinesFile = lib.mkOption {
description = mdDoc '' description = ''
Use the machines listed in this file when building this hive configuration. Use the machines listed in this file when building this hive configuration.
If your Colmena host has nix configured to allow for remote builds If your Colmena host has nix configured to allow for remote builds
@ -290,7 +341,7 @@ with builtins; rec {
type = types.nullOr types.path; type = types.nullOr types.path;
}; };
specialArgs = lib.mkOption { specialArgs = lib.mkOption {
description = mdDoc '' description = ''
A set of special arguments to be passed to NixOS modules. A set of special arguments to be passed to NixOS modules.
This will be merged into the `specialArgs` used to evaluate This will be merged into the `specialArgs` used to evaluate
@ -300,7 +351,7 @@ with builtins; rec {
type = types.attrsOf types.unspecified; type = types.attrsOf types.unspecified;
}; };
allowApplyAll = lib.mkOption { allowApplyAll = lib.mkOption {
description = mdDoc '' description = ''
Whether to allow deployments without a node filter set. Whether to allow deployments without a node filter set.
If set to false, a node filter must be specified with `--on` when If set to false, a node filter must be specified with `--on` when

View file

@ -87,14 +87,14 @@ pub struct Key {
#[serde(flatten)] #[serde(flatten)]
source: KeySource, source: KeySource,
#[validate(custom = "validate_dest_dir")] #[validate(custom(function = "validate_dest_dir"))]
#[serde(rename = "destDir")] #[serde(rename = "destDir")]
dest_dir: PathBuf, dest_dir: PathBuf,
#[validate(custom = "validate_unix_name")] #[validate(custom(function = "validate_unix_name"))]
user: String, user: String,
#[validate(custom = "validate_unix_name")] #[validate(custom(function = "validate_unix_name"))]
group: String, group: String,
permissions: String, permissions: String,

View file

@ -55,6 +55,9 @@ pub struct NodeName(#[serde(deserialize_with = "NodeName::deserialize")] String)
#[derive(Debug, Clone, Validate, Deserialize)] #[derive(Debug, Clone, Validate, Deserialize)]
pub struct NodeConfig { pub struct NodeConfig {
#[serde(rename = "systemType")]
system_type: Option<String>,
#[serde(rename = "targetHost")] #[serde(rename = "targetHost")]
target_host: Option<String>, target_host: Option<String>,
@ -81,7 +84,7 @@ pub struct NodeConfig {
#[serde(rename = "sshOptions")] #[serde(rename = "sshOptions")]
extra_ssh_options: Vec<String>, extra_ssh_options: Vec<String>,
#[validate(custom = "validate_keys")] #[validate(custom(function = "validate_keys"))]
keys: HashMap<String, Key>, keys: HashMap<String, Key>,
} }
@ -94,6 +97,18 @@ pub struct MetaConfig {
pub machines_file: Option<String>, pub machines_file: Option<String>,
} }
#[derive(Debug, Clone, Validate, Deserialize)]
pub struct SystemTypeConfig {
#[serde(rename = "supportsDeployment")]
pub supports_deployment: bool,
}
#[derive(Debug, Clone, Validate, Deserialize)]
pub struct RegistryConfig {
#[serde(flatten)]
pub systems: HashMap<String, SystemTypeConfig>,
}
/// Nix CLI flags. /// Nix CLI flags.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct NixFlags { pub struct NixFlags {

View file

@ -7,6 +7,8 @@
pub mod plain; pub mod plain;
pub mod spinner; pub mod spinner;
use std::io::IsTerminal;
use async_trait::async_trait; use async_trait::async_trait;
use tokio::sync::mpsc::{self, UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender}; use tokio::sync::mpsc::{self, UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
@ -90,7 +92,7 @@ pub enum LineStyle {
impl SimpleProgressOutput { impl SimpleProgressOutput {
pub fn new(verbose: bool) -> Self { pub fn new(verbose: bool) -> Self {
let tty = atty::is(atty::Stream::Stdout); let tty = std::io::stdout().is_terminal();
if verbose || !tty { if verbose || !tty {
Self::Plain(PlainOutput::new()) Self::Plain(PlainOutput::new())