Add action & Tune Tracing (#119)

* Add action

* Checkout so we have actions.yml

* yaml poking

* Handle GITHUB_TOKEN

* Don't ask github to do templating, use directives for logging

* Missing changes

* Fix build error

* Fix yaml even more

* Add shell command

* Add a wait on the socket again

* Print some debugging

* Use more correct env vars

* Correct install url logic

* Use different style for inputs

* Fix yaml errror

* Tweak around local-root

* provision nix-install.sh as well

* Use nix-install.sh path directory in NIX_INSTALL_URL

* Tweak variables to hopefully work

* Call it BINARY_ROOT instead

* Add exec output

* Set no-confirm

* no echo

* Add token to workflow

* Set no-confirm properly

* Add no-confirm back for uninstall

* Correct some env and vars

* CreateDirectory respects existing symlink

* Add a few more checks to the CI

* pass valid yaml...

* Slightly more aggressive cleanup of /nix

* Ensure steam-deck cleans /home/nix

* Add steam-deck check for persistence

* Canonicalize steam-deck persistence

* Ensure absolute path

* Inverted logic sad

* python3 on mac

* Add readme info and fix a extra-conf mistype

* Add unsaved changes

* More fine grained trace logging

* Restore spans we lost in refactor

* BuiltinPlanner can accept settings

* Reflect feedback

* Push actually working code hopefully this time

* Speeling
This commit is contained in:
Ana Hobden 2022-12-16 10:55:28 -08:00 committed by GitHub
parent cc3521a798
commit c4274c93fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 882 additions and 245 deletions

View file

@ -5,14 +5,6 @@ on:
push:
branches: [main]
env:
NIX_INSTALL_FORCE_ALLOW_HTTP: "1"
NIX_INSTALL_UPDATE_ROOT: "http://0.0.0.0:8000"
RUST_BACKTRACE: "full"
HARMONIC_VERBOSITY: "2"
HARMONIC_NO_CONFIRM: "true"
HARMONIC_LOGGER: "pretty"
jobs:
lints:
name: Lints
@ -37,12 +29,6 @@ jobs:
run: nix develop --store ~/.ci-store --command check-nixpkgs-fmt
- name: Check EditorConfig conformance
run: nix develop --store ~/.ci-store --command check-editorconfig
- name: Create artifact for `nix-install.sh`
uses: actions/upload-artifact@v3
with:
name: nix-install
path: |
nix-install.sh
build-x86_64-linux:
name: Build x86_64 Linux
@ -102,29 +88,54 @@ jobs:
runs-on: ubuntu-22.04
needs: [build-x86_64-linux, lints]
steps:
- uses: actions/checkout@v3
- run: sudo apt install fish zsh
- uses: actions/download-artifact@v3
with:
name: harmonic-x86_64-linux
- uses: actions/download-artifact@v3
with:
name: nix-install
- name: Move & set executable
run: |
chmod +x ./harmonic
mv harmonic harmonic-x86_64-linux
mkdir install-root
cp nix-install.sh install-root/nix-install.sh
mv harmonic install-root/harmonic-x86_64-linux
- name: Initial install
run: |
python -m http.server --bind 0.0.0.0 8000 &
timeout 20 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/8000; do echo "Waiting for server..."; sleep 1; done'
curl -L http://0.0.0.0:8000/nix-install.sh | sh -s -- install linux-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}"
uses: ./
with:
local-root: install-root/
logger: pretty
log-directives: harmonic=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Initial uninstall (without a `nix run` first)
run: sudo -E /nix/harmonic uninstall
- name: Repeated install
env:
HARMONIC_NO_CONFIRM: true
HARMONIC_LOGGER: pretty
HARMONIC_LOG_DIRECTIVES: harmonic=trace
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
python -m http.server --bind 0.0.0.0 8000 &
timeout 20 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/8000; do echo "Waiting for server..."; sleep 1; done'
curl -L http://0.0.0.0:8000/nix-install.sh | sh -s -- install linux-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}"
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was still running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was still running"
exit 1
fi
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
- name: Repeated install
uses: ./
with:
local-root: install-root/
logger: pretty
log-directives: harmonic=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`
@ -148,23 +159,42 @@ jobs:
shell: fish --login {0}
- name: Repeated uninstall
run: sudo -E /nix/harmonic uninstall
env:
HARMONIC_NO_CONFIRM: true
HARMONIC_LOGGER: pretty
HARMONIC_LOG_DIRECTIVES: harmonic=trace
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was still running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was still running"
exit 1
fi
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
run-steam-deck:
name: Run Steam Deck (mock)
runs-on: ubuntu-22.04
needs: [build-x86_64-linux, lints]
steps:
- uses: actions/checkout@v3
- run: sudo apt install fish zsh
- uses: actions/download-artifact@v3
with:
name: harmonic-x86_64-linux
- uses: actions/download-artifact@v3
with:
name: nix-install
- name: Move & set executable
run: |
chmod +x ./harmonic
mv harmonic harmonic-x86_64-linux
mkdir install-root
cp nix-install.sh install-root/nix-install.sh
mv harmonic install-root/harmonic-x86_64-linux
- name: Make the CI look like a steam deck
run: |
mkdir -p ~/bin
@ -172,17 +202,50 @@ jobs:
sudo chmod +x /bin/steamos-readonly
sudo useradd -m deck
- name: Initial install
run: |
python -m http.server --bind 0.0.0.0 8000 &
timeout 20 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/8000; do echo "Waiting for server..."; sleep 1; done'
curl -L http://0.0.0.0:8000/nix-install.sh | sh -s -- install steam-deck --persistence `pwd`/ci-test-nix --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}"
uses: ./
with:
local-root: install-root/
logger: pretty
log-directives: harmonic=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
planner: steam-deck
extra-args: --persistence /home/runner/.ci-test-nix-home
- name: Initial uninstall (without a `nix run` first)
run: sudo -E /nix/harmonic uninstall
- name: Repeated install
env:
HARMONIC_NO_CONFIRM: true
HARMONIC_LOGGER: pretty
HARMONIC_LOG_DIRECTIVES: harmonic=trace
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
python -m http.server --bind 0.0.0.0 8000 &
timeout 20 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/8000; do echo "Waiting for server..."; sleep 1; done'
curl -L http://0.0.0.0:8000/nix-install.sh | sh -s -- install steam-deck --persistence `pwd`/ci-test-nix --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}"
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was still running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was still running"
exit 1
fi
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
if [ -e /home/runner/.ci-test-nix-home ]; then
echo "/home/runner/.ci-test-nix-home exists"
exit 1
fi
- name: Repeated install
uses: ./
with:
local-root: install-root/
logger: pretty
log-directives: harmonic=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
planner: steam-deck
extra-args: --persistence /home/runner/.ci-test-nix-home
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`
@ -206,6 +269,29 @@ jobs:
shell: fish --login {0}
- name: Repeated uninstall
run: sudo -E /nix/harmonic uninstall
env:
HARMONIC_NO_CONFIRM: true
HARMONIC_LOGGER: pretty
HARMONIC_LOG_DIRECTIVES: harmonic=trace
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was still running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was still running"
exit 1
fi
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
if [ -e /home/runner/.ci-test-nix-home ]; then
echo "/home/runner/.ci-test-nix-home exists"
exit 1
fi
build-x86_64-darwin:
name: Build x86_64 Darwin
@ -231,29 +317,40 @@ jobs:
runs-on: macos-12
needs: [build-x86_64-darwin, lints]
steps:
- uses: actions/checkout@v3
- run: brew install fish coreutils
- uses: actions/download-artifact@v3
with:
name: harmonic-x86_64-darwin
- uses: actions/download-artifact@v3
with:
name: nix-install
- name: Move & set executable
run: |
chmod +x ./harmonic
mv harmonic harmonic-x86_64-darwin
mkdir install-root
cp nix-install.sh install-root/nix-install.sh
mv harmonic install-root/harmonic-x86_64-darwin
- name: Initial install
run: |
python3 -m http.server --bind 0.0.0.0 8000 &
gtimeout 20 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/8000; do echo "Waiting for server..."; sleep 1; done'
curl -L http://0.0.0.0:8000/nix-install.sh | sh -s -- install darwin-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}"
uses: ./
with:
local-root: install-root/
logger: pretty
log-directives: harmonic=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Initial uninstall (without a `nix run` first)
run: sudo -E /nix/harmonic uninstall
env:
HARMONIC_NO_CONFIRM: true
HARMONIC_LOGGER: pretty
HARMONIC_LOG_DIRECTIVES: harmonic=trace
RUST_BACKTRACE: full
- name: Repeated install
run: |
python -m http.server --bind 0.0.0.0 8000 &
timeout 20 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/8000; do echo "Waiting for server..."; sleep 1; done'
curl -L http://0.0.0.0:8000/nix-install.sh | sh -s -- install darwin-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}"
uses: ./
with:
local-root: install-root/
logger: pretty
log-directives: harmonic=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`
@ -277,4 +374,9 @@ jobs:
shell: fish --login {0}
- name: Repeated uninstall
run: sudo -E /nix/harmonic uninstall
env:
HARMONIC_NO_CONFIRM: true
HARMONIC_LOGGER: pretty
HARMONIC_LOG_DIRECTIVES: harmonic=trace
RUST_BACKTRACE: full

View file

@ -173,4 +173,28 @@ Documentation is also available via `nix` build:
```bash
nix build github:DeterminateSystems/harmonic#harmonic.doc
firefox result-doc/harmonic/index.html
```
## As a Github Action
You can use Harmonic as a Github action like so:
```yaml
on:
pull_request:
push:
branches: [main]
jobs:
lints:
name: Build
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/harmonic@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run `nix build`
run: nix build .
```

199
action.yml Normal file
View file

@ -0,0 +1,199 @@
name: Harmonic
description: Install Nix
inputs:
planner:
description: A planner to use
required: false
extra-args:
description: Extra args to pass to the planner (prefer using structured `with:` arguments unless using a custom planner!)
required: false
github-token:
description: A Github Token so that authenticated requests can be made (set this to `secrets.GITHUB_TOKEN`)
channels:
description: Channel(s) to add (eg `nixpkgs=https://nixos.org/channels/nixpkgs-unstable`)
required: false
modify-profile:
description: Modify the user profile to automatically load nix
required: false
daemon-user-count:
description: Number of build users to create
required: false
nix-build-group-name:
description: The Nix build group name
required: false
nix-build-group-id:
description: The Nix build group GID
required: false
nix-build-user-prefix:
description: The Nix build user prefix (user numbers will be postfixed)
required: false
nix-build-user-base:
description: The Nix build user base UID (ascending)
required: false
nix-package-url:
description: The Nix package URL
required: false
extra-conf:
description: Extra configuration lines for `/etc/nix.conf` (includes `access-tokens` with `secrets.GITHUB_TOKEN` automatically if `github-token` is set)
required: false
mac-encrypt:
description: Force encryption on the volume (Mac only)
required: false
mac-case-sensitive:
description: Use a case sensitive volume (Mac only)
required: false
mac-volume-label:
description: The label for the created APFS volume (Mac only)
required: false
mac-root-disk:
description: The root disk of the target (Mac only)
required: false
nix-install-url:
description: A URL pointing to a harmonic `nix-install.sh` script
required: true
default: https://install.determinate.systems/nix
local-root:
description: A local `harmonic` binary root, overrides the `nix-install` setting (binaries should be named `harmonic-$ARCH`, eg. `harmonic-x86_64-linux`)
required: false
logger:
description: The logger to use for install (eg. `pretty`, `json`, `full`, `compact`)
required: false
log-directives:
description: A list of Tracing directives, comma separated (eg. `harmonic=trace`, see https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)
required: false
backtrace:
description: The setting for `RUST_BACKTRACE` (see https://doc.rust-lang.org/std/backtrace/index.html#environment-variables)
required: false
runs:
using: composite
steps:
- name: Install Nix
shell: bash
run: |
if [ -n "${{ inputs.channels }}" ]; then
export HARMONIC_CHANNELS=${{ inputs.channels }}
echo "Set HARMONIC_CHANNELS=$HARMONIC_CHANNELS"
fi
if [ -n "${{ inputs.modify-profile }}" ]; then
export HARMONIC_MODIFY_PROFILE=${{ inputs.modify-profile }}
echo "Set HARMONIC_MODIFY_PROFILE=$HARMONIC_MODIFY_PROFILE"
fi
if [ -n "${{ inputs.daemon-user-count }}" ]; then
export HARMONIC_DAEMON_USER_COUNT=${{ inputs.daemon-user-count }}
echo "Set HARMONIC_DAEMON_USER_COUNT=$HARMONIC_DAEMON_USER_COUNT"
fi
if [ -n "${{ inputs.nix-build-group-name }}" ]; then
export HARMONIC_NIX_BUILD_GROUP_NAME=${{ inputs.nix-build-group-name }}
echo "Set HARMONIC_NIX_BUILD_GROUP_NAME=$HARMONIC_NIX_BUILD_GROUP_NAME"
fi
if [ -n "${{ inputs.nix-build-group-id }}" ]; then
export HARMONIC_NIX_BUILD_GROUP_ID=${{ inputs.nix-build-group-id }}
echo "Set HARMONIC_NIX_BUILD_GROUP_ID=$HARMONIC_NIX_BUILD_GROUP_ID"
fi
if [ -n "${{ inputs.nix-build-user-prefix }}" ]; then
export HARMONIC_NIX_BUILD_USER_ID_BASE=${{ inputs.nix-build-user-prefix }}
echo "Set HARMONIC_NIX_BUILD_USER_ID_BASE=$HARMONIC_NIX_BUILD_USER_ID_BASE"
fi
if [ -n "${{ inputs.nix-build-user-base }}" ]; then
export HARMONIC_NIX_BUILD_USER_PREFIX=${{ inputs.nix-build-user-base }}
echo "Set HARMONIC_NIX_BUILD_USER_PREFIX=$HARMONIC_NIX_BUILD_USER_PREFIX"
fi
if [ -n "${{ inputs.nix-package-url }}" ]; then
export HARMONIC_NIX_PACKAGE_URL=${{ inputs.nix-package-url }}
echo "Set HARMONIC_NIX_PACKAGE_URL=$HARMONIC_NIX_PACKAGE_URL"
fi
if [ -n "${{ inputs.extra-conf }}" ]; then
if [ -n "${{ inputs.github-token }}" ]; then
export HARMONIC_EXTRA_CONF="${{ inputs.extra-conf }}\naccess-tokens = github.com=${{ inputs.github-token }}"
else
export HARMONIC_EXTRA_CONF="${{ inputs.extra-conf }}"
fi
echo "Set HARMONIC_EXTRA_CONF=$HARMONIC_EXTRA_CONF"
else
if [ -n "${{ inputs.github-token }}" ]; then
export HARMONIC_EXTRA_CONF="access-tokens = github.com=${{ inputs.github-token }}"
echo "Set HARMONIC_EXTRA_CONF=$HARMONIC_EXTRA_CONF"
fi
fi
if [ -n "${{ inputs.mac-encrypt }}" ]; then
export HARMONIC_ENCRYPT=${{ inputs.mac-encrypt }}
echo "Set HARMONIC_ENCRYPT=$HARMONIC_ENCRYPT"
fi
if [ -n "${{ inputs.mac-case-sensitive }}" ]; then
export HARMONIC_CASE_SENSITIVE=${{ inputs.mac-case-sensitive }}
echo "Set HARMONIC_CASE_SENSITIVE=$HARMONIC_CASE_SENSITIVE"
fi
if [ -n "${{ inputs.mac-volume-label }}" ]; then
export HARMONIC_VOLUME_LABEL=${{ inputs.mac-volume-label }}
echo "Set HARMONIC_VOLUME_LABEL=$HARMONIC_VOLUME_LABEL"
fi
if [ -n "${{ inputs.mac-root-disk }}" ]; then
export HARMONIC_ROOT_DISK=${{ inputs.mac-root-disk }}
echo "Set HARMONIC_ROOT_DISK=$HARMONIC_ROOT_DISK"
fi
if [ -n "${{ inputs.local-root }}" ]; then
if [ "$RUNNER_OS" == "macOS" ]; then
export PYTHON="python3"
else
export PYTHON="python"
fi
$PYTHON -m http.server --directory ${{ inputs.local-root }} --bind 0.0.0.0 8000 &
export HTTP_PID=$!
echo "Started simple http server for ${{ inputs.local-root }} on 0.0.0.0:8000"
while (! (: </dev/tcp/localhost/8000) &> /dev/null); do
sleep 1
done
export NIX_INSTALL_FORCE_ALLOW_HTTP="1"
echo "Set NIX_INSTALL_FORCE_ALLOW_HTTP=$NIX_INSTALL_FORCE_ALLOW_HTTP"
export NIX_INSTALL_URL=0.0.0.0:8000/nix-install.sh
echo "Set NIX_INSTALL_URL=$NIX_INSTALL_URL"
export NIX_INSTALL_BINARY_ROOT=http://0.0.0.0:8000/
echo "Set NIX_INSTALL_BINARY_ROOT=$NIX_INSTALL_BINARY_ROOT"
export NIX_INSTALL_FORCE_ALLOW_HTTP=1
echo "Set NIX_INSTALL_FORCE_ALLOW_HTTP=$NIX_INSTALL_FORCE_ALLOW_HTTP"
else
export NIX_INSTALL_URL=${{ inputs.nix-install-url }}
echo "Set NIX_INSTALL_URL=$NIX_INSTALL_URL"
fi
if [ -n "${{ inputs.logger }}" ]; then
export HARMONIC_LOGGER=${{ inputs.logger }}
echo "Set HARMONIC_LOGGER=$HARMONIC_LOGGER"
fi
if [ -n "${{ inputs.log-directives }}" ]; then
export HARMONIC_LOG_DIRECTIVES=${{ inputs.log-directives }}
echo "Set HARMONIC_LOG_DIRECTIVES=$HARMONIC_LOG_DIRECTIVES"
fi
if [ -n "${{ inputs.backtrace }}" ]; then
export RUST_BACKTRACE=${{ inputs.backtrace }}
echo "Set RUST_BACKTRACE=$RUST_BACKTRACE"
fi
export HARMONIC_NO_CONFIRM=true
echo "Set HARMONIC_NO_CONFIRM=$HARMONIC_NO_CONFIRM"
curl --retry 20 -L $NIX_INSTALL_URL | sh -s -- install ${{ inputs.planner }} ${{ inputs.extra-args }}
if [ -n "$HTTP_PID" ]; then
kill $HTTP_PID
fi

View file

@ -21,8 +21,8 @@ fi
set -u
# If NIX_INSTALL_UPDATE_ROOT is unset or empty, default it.
NIX_INSTALL_UPDATE_ROOT="${NIX_INSTALL_UPDATE_ROOT:-https://install.determinate.systems/nix}"
# If NIX_INSTALL_BINARY_ROOT is unset or empty, default it.
NIX_INSTALL_BINARY_ROOT="${NIX_INSTALL_BINARY_ROOT:-https://install.determinate.systems/nix}"
main() {
downloader --check
@ -44,7 +44,7 @@ main() {
;;
esac
local _url="${NIX_INSTALL_OVERRIDE_URL-${NIX_INSTALL_UPDATE_ROOT}/harmonic-${_arch}${_ext}}"
local _url="${NIX_INSTALL_OVERRIDE_URL-${NIX_INSTALL_BINARY_ROOT}/harmonic-${_arch}${_ext}}"
local _dir
if ! _dir="$(ensure mktemp -d)"; then

View file

@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use nix::unistd::{chown, Group, User};
use tokio::fs::{create_dir, remove_dir_all};
use tracing::{span, Span};
use crate::action::{Action, ActionDescription, ActionState};
use crate::action::{ActionError, StatefulAction};
@ -74,16 +75,24 @@ impl Action for CreateDirectory {
format!("Create directory `{}`", self.path.display())
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_directory",
path = tracing::field::display(self.path.display()),
user = self.user,
group = self.group,
mode = self
.mode
.map(|v| tracing::field::display(format!("{:#o}", v))),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
user = self.user,
group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
path,
@ -150,12 +159,7 @@ impl Action for CreateDirectory {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
user = self.user,
group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
path,

View file

@ -1,4 +1,5 @@
use nix::unistd::{chown, Group, User};
use tracing::{span, Span};
use std::path::{Path, PathBuf};
use tokio::{
@ -58,16 +59,31 @@ impl Action for CreateFile {
fn tracing_synopsis(&self) -> String {
format!("Create or overwrite file `{}`", self.path.display())
}
fn tracing_span(&self) -> Span {
let span = span!(
tracing::Level::DEBUG,
"create_file",
path = tracing::field::display(self.path.display()),
user = self.user,
group = self.group,
mode = self
.mode
.map(|v| tracing::field::display(format!("{:#o}", v))),
buf = tracing::field::Empty,
);
if tracing::enabled!(tracing::Level::TRACE) {
span.record("buf", &self.buf);
}
span
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
user = self.user,
group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
path,
@ -78,6 +94,11 @@ impl Action for CreateFile {
force: _,
} = self;
if tracing::enabled!(tracing::Level::TRACE) {
let span = tracing::Span::current();
span.record("buf", &buf);
}
let mut options = OpenOptions::new();
options.create_new(true).write(true).read(true);
@ -135,12 +156,7 @@ impl Action for CreateFile {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
user = self.user,
group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
path,

View file

@ -1,4 +1,5 @@
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::ActionError;
use crate::execute_command;
@ -37,10 +38,16 @@ impl Action for CreateGroup {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
user = self.name,
gid = self.gid,
))]
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_group",
user = self.name,
gid = self.gid,
)
}
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { name, gid } = self;
@ -108,10 +115,7 @@ impl Action for CreateGroup {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
user = self.name,
gid = self.gid,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { name, gid: _ } = self;

View file

@ -1,5 +1,6 @@
use nix::unistd::{chown, Group, User};
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
use std::{
io::SeekFrom,
os::unix::prelude::PermissionsExt,
@ -9,8 +10,7 @@ use tokio::{
fs::{remove_file, OpenOptions},
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
};
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
use tracing::{span, Span};
/** Create a file at the given location with the provided `buf`,
optionally with an owning user, group, and mode.
@ -58,16 +58,30 @@ impl Action for CreateOrAppendFile {
format!("Create or append file `{}`", self.path.display())
}
fn tracing_span(&self) -> Span {
let span = span!(
tracing::Level::DEBUG,
"create_or_append_file",
path = tracing::field::display(self.path.display()),
user = self.user,
group = self.group,
mode = self
.mode
.map(|v| tracing::field::display(format!("{:#o}", v))),
buf = tracing::field::Empty,
);
if tracing::enabled!(tracing::Level::TRACE) {
span.record("buf", &self.buf);
}
span
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
user = self.user,
group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
path,
@ -142,12 +156,7 @@ impl Action for CreateOrAppendFile {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
user = self.user,
group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
path,

View file

@ -1,4 +1,5 @@
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::ActionError;
use crate::execute_command;
@ -38,6 +39,18 @@ impl Action for CreateUser {
self.name, self.uid, self.groupname, self.gid
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_user",
user = self.name,
uid = self.uid,
groupname = self.groupname,
gid = self.gid,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),
@ -47,12 +60,7 @@ impl Action for CreateUser {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
user = self.name,
uid = self.uid,
groupname = self.groupname,
gid = self.gid,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
name,
@ -231,11 +239,7 @@ impl Action for CreateUser {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
user = self.name,
uid = self.uid,
gid = self.gid,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
name,

View file

@ -2,6 +2,7 @@ use std::path::PathBuf;
use bytes::Buf;
use reqwest::Url;
use tracing::{span, Span};
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
@ -31,14 +32,20 @@ impl Action for FetchAndUnpackNix {
format!("Fetch `{}` to `{}`", self.url, self.dest.display())
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"fetch_and_unpack_nix",
url = tracing::field::display(&self.url),
dest = tracing::field::display(self.dest.display()),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
url = %self.url,
dest = %self.dest.display(),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { url, dest } = self;
@ -66,10 +73,7 @@ impl Action for FetchAndUnpackNix {
vec![/* Deliberately empty -- this is a noop */]
}
#[tracing::instrument(level = "debug", skip_all, fields(
url = %self.url,
dest = %self.dest.display(),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { url: _, dest: _ } = self;

View file

@ -1,5 +1,7 @@
use std::path::{Path, PathBuf};
use tracing::{span, Span};
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
const DEST: &str = "/nix/store";
@ -27,6 +29,15 @@ impl Action for MoveUnpackedNix {
"Move the downloaded Nix into `/nix`".to_string()
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"mount_unpacked_nix",
src = tracing::field::display(self.src.display()),
dest = DEST,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Move the downloaded Nix into `/nix`"),
@ -37,10 +48,7 @@ impl Action for MoveUnpackedNix {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
src = %self.src.display(),
dest = DEST,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { src } = self;
@ -73,10 +81,7 @@ impl Action for MoveUnpackedNix {
vec![/* Deliberately empty -- this is a noop */]
}
#[tracing::instrument(level = "debug", skip_all, fields(
src = %self.src.display(),
dest = DEST,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
// Noop
Ok(())

View file

@ -6,6 +6,7 @@ use crate::{
use glob::glob;
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{Action, ActionDescription};
@ -31,13 +32,19 @@ impl Action for SetupDefaultProfile {
"Setup the default Nix profile".to_string()
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"setup_default_profile",
channels = self.channels.join(","),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
channels = %self.channels.join(","),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { channels } = self;
@ -156,9 +163,7 @@ impl Action for SetupDefaultProfile {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
channels = %self.channels.join(","),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
std::env::remove_var("NIX_SSL_CERT_FILE");

View file

@ -10,6 +10,7 @@ use crate::{
};
use reqwest::Url;
use tracing::{span, Instrument, Span};
/**
Configure Nix and start it
@ -68,6 +69,10 @@ impl Action for ConfigureNix {
"Configure Nix".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "configure_nix",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
setup_default_profile,
@ -98,17 +103,67 @@ impl Action for ConfigureNix {
} = self;
if let Some(configure_shell_profile) = configure_shell_profile {
let setup_default_profile_span = tracing::Span::current().clone();
let (
place_nix_configuration_span,
place_channel_configuration_span,
configure_shell_profile_span,
) = (
setup_default_profile_span.clone(),
setup_default_profile_span.clone(),
setup_default_profile_span.clone(),
);
tokio::try_join!(
async move { setup_default_profile.try_execute().await },
async move { place_nix_configuration.try_execute().await },
async move { place_channel_configuration.try_execute().await },
async move { configure_shell_profile.try_execute().await },
async move {
setup_default_profile
.try_execute()
.instrument(setup_default_profile_span)
.await
},
async move {
place_nix_configuration
.try_execute()
.instrument(place_nix_configuration_span)
.await
},
async move {
place_channel_configuration
.try_execute()
.instrument(place_channel_configuration_span)
.await
},
async move {
configure_shell_profile
.try_execute()
.instrument(configure_shell_profile_span)
.await
},
)?;
} else {
let place_channel_configuration_span = tracing::Span::current().clone();
let (setup_default_profile_span, place_nix_configuration_span) = (
place_channel_configuration_span.clone(),
place_channel_configuration_span.clone(),
);
tokio::try_join!(
async move { setup_default_profile.try_execute().await },
async move { place_nix_configuration.try_execute().await },
async move { place_channel_configuration.try_execute().await },
async move {
setup_default_profile
.try_execute()
.instrument(setup_default_profile_span)
.await
},
async move {
place_nix_configuration
.try_execute()
.instrument(place_nix_configuration_span)
.await
},
async move {
place_channel_configuration
.try_execute()
.instrument(place_channel_configuration_span)
.await
},
)?;
};
configure_nix_daemon_service.try_execute().await?;

View file

@ -4,6 +4,7 @@ use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
use nix::unistd::User;
use std::path::{Path, PathBuf};
use tokio::task::JoinSet;
use tracing::{span, Instrument, Span};
// Fish has different syntax than zsh/bash, treat it separate
const PROFILE_FISH_SUFFIX: &str = "conf.d/nix.fish";
@ -141,6 +142,10 @@ impl Action for ConfigureShellProfile {
"Configure the shell profiles".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "configure_shell_profile",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),
@ -166,8 +171,10 @@ impl Action for ConfigureShellProfile {
let span = tracing::Span::current().clone();
let mut create_or_append_file_clone = create_or_append_file.clone();
let _abort_handle = set.spawn(async move {
let _ = span.enter();
create_or_append_file_clone.try_execute().await?;
create_or_append_file_clone
.try_execute()
.instrument(span)
.await?;
Result::<_, ActionError>::Ok((idx, create_or_append_file_clone))
});
}

View file

@ -1,3 +1,5 @@
use tracing::{span, Span};
use crate::action::base::CreateDirectory;
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
@ -45,6 +47,10 @@ impl Action for CreateNixTree {
"Create a directory tree in `/nix`".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "create_nix_tree",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),

View file

@ -6,6 +6,7 @@ use crate::{
settings::CommonSettings,
};
use tokio::task::JoinSet;
use tracing::{span, Instrument, Span};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroups {
@ -62,6 +63,18 @@ impl Action for CreateUsersAndGroups {
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_users_and_group",
daemon_user_count = self.daemon_user_count,
nix_build_group_name = self.nix_build_group_name,
nix_build_group_id = self.nix_build_group_id,
nix_build_user_prefix = self.nix_build_user_prefix,
nix_build_user_id_base = self.nix_build_user_id_base,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
daemon_user_count: _,
@ -91,13 +104,7 @@ impl Action for CreateUsersAndGroups {
vec![ActionDescription::new(self.tracing_synopsis(), explanation)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
daemon_user_count = self.daemon_user_count,
nix_build_group_name = self.nix_build_group_name,
nix_build_group_id = self.nix_build_group_id,
nix_build_user_prefix = self.nix_build_user_prefix,
nix_build_user_id_base = self.nix_build_user_id_base,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
create_users,
@ -129,9 +136,10 @@ impl Action for CreateUsersAndGroups {
let mut set = JoinSet::new();
let mut errors: Vec<Box<ActionError>> = Vec::new();
for (idx, create_user) in create_users.iter_mut().enumerate() {
let span = tracing::Span::current().clone();
let mut create_user_clone = create_user.clone();
let _abort_handle = set.spawn(async move {
create_user_clone.try_execute().await?;
create_user_clone.try_execute().instrument(span).await?;
Result::<_, _>::Ok((idx, create_user_clone))
});
}
@ -188,13 +196,7 @@ impl Action for CreateUsersAndGroups {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
daemon_user_count = self.daemon_user_count,
nix_build_group_name = self.nix_build_group_name,
nix_build_group_id = self.nix_build_group_id,
nix_build_user_prefix = self.nix_build_user_prefix,
nix_build_user_id_base = self.nix_build_user_id_base,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
create_users,
@ -210,9 +212,10 @@ impl Action for CreateUsersAndGroups {
let mut errors = Vec::default();
for (idx, create_user) in create_users.iter().enumerate() {
let span = tracing::Span::current().clone();
let mut create_user_clone = create_user.clone();
let _abort_handle = set.spawn(async move {
create_user_clone.try_revert().await?;
create_user_clone.try_revert().instrument(span).await?;
Result::<_, ActionError>::Ok((idx, create_user_clone))
});
}

View file

@ -2,6 +2,7 @@ use crate::action::base::CreateFile;
use crate::action::ActionError;
use crate::action::{Action, ActionDescription, StatefulAction};
use reqwest::Url;
use tracing::{span, Span};
/**
Place a channel configuration containing `channels` to the `$ROOT_HOME/.nix-channels` file
@ -54,13 +55,24 @@ impl Action for PlaceChannelConfiguration {
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"place_channel_configuration",
channels = self
.channels
.iter()
.map(|(c, u)| format!("{c}={u}"))
.collect::<Vec<_>>()
.join(", "),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
create_file,
@ -82,9 +94,7 @@ impl Action for PlaceChannelConfiguration {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
create_file,

View file

@ -1,3 +1,5 @@
use tracing::{span, Span};
use crate::action::base::{CreateDirectory, CreateFile};
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
@ -50,6 +52,10 @@ impl Action for PlaceNixConfiguration {
format!("Place the Nix configuration in `{NIX_CONF}`")
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "place_nix_configuration",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),

View file

@ -1,3 +1,5 @@
use tracing::{span, Span};
use super::{CreateNixTree, CreateUsersAndGroups};
use crate::{
action::{
@ -48,6 +50,10 @@ impl Action for ProvisionNix {
"Provision Nix".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "provision_nix",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
fetch_nix,

View file

@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
@ -32,13 +33,19 @@ impl Action for BootstrapApfsVolume {
format!("Bootstrap and kickstart `{}`", self.path.display())
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"bootstrap_volume",
path = %self.path.display(),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { path } = self;
@ -70,9 +77,7 @@ impl Action for BootstrapApfsVolume {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { path } = self;

View file

@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
@ -41,15 +42,21 @@ impl Action for CreateApfsVolume {
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_volume",
disk = %self.disk.display(),
name = %self.name,
case_sensitive = %self.case_sensitive,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
disk = %self.disk.display(),
name = %self.name,
case_sensitive = %self.case_sensitive,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
disk,
@ -91,11 +98,7 @@ impl Action for CreateApfsVolume {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
disk = %self.disk.display(),
name = %self.name,
case_sensitive = %self.case_sensitive,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
disk: _,

View file

@ -11,6 +11,7 @@ use std::{
time::Duration,
};
use tokio::process::Command;
use tracing::{span, Span};
pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist";
@ -143,6 +144,15 @@ impl Action for CreateNixVolume {
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_apfs_volume",
disk = tracing::field::display(self.disk.display()),
name = self.name
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
disk: _, name: _, ..
@ -150,7 +160,7 @@ impl Action for CreateNixVolume {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(destination,))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
disk: _,
@ -213,7 +223,7 @@ impl Action for CreateNixVolume {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(disk, name))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
disk: _,

View file

@ -1,4 +1,5 @@
use tokio::process::Command;
use tracing::{span, Span};
use crate::execute_command;
@ -22,6 +23,10 @@ impl Action for CreateSyntheticObjects {
"Create objects defined in `/etc/synthetic.conf`".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "create_synthetic_objects",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),
@ -29,7 +34,7 @@ impl Action for CreateSyntheticObjects {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields())]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
execute_command(
@ -59,7 +64,7 @@ impl Action for CreateSyntheticObjects {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields())]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
execute_command(

View file

@ -2,6 +2,7 @@ use std::io::Cursor;
use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
@ -34,13 +35,19 @@ impl Action for EnableOwnership {
format!("Enable ownership on {}", self.path.display())
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"enable_ownership",
path = %self.path.display(),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { path } = self;
@ -79,9 +86,7 @@ impl Action for EnableOwnership {
vec![]
}
#[tracing::instrument(level = "debug", skip_all, fields(
path = %self.path.display(),
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
// noop
Ok(())

View file

@ -7,6 +7,7 @@ use crate::{
use rand::Rng;
use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::{span, Span};
/**
Encrypt an APFS volume
@ -43,6 +44,14 @@ impl Action for EncryptApfsVolume {
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"encrypt_volume",
disk = tracing::field::display(self.disk.display()),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}

View file

@ -1,4 +1,5 @@
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
@ -28,13 +29,19 @@ impl Action for KickstartLaunchctlService {
format!("Kickstart the launchctl unit `{unit}`")
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"kickstart_launchctl_service",
unit = %self.unit,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
unit = %self.unit,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { unit } = self;
@ -56,9 +63,7 @@ impl Action for KickstartLaunchctlService {
vec![]
}
#[tracing::instrument(level = "debug", skip_all, fields(
unit = %self.unit,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
// noop
Ok(())

View file

@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
@ -34,14 +35,20 @@ impl Action for UnmountApfsVolume {
format!("Unmount the `{}` APFS volume", self.name)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"unmount_volume",
disk = tracing::field::display(self.disk.display()),
name = self.name,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
disk = %self.disk.display(),
name = %self.name,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { disk: _, name } = self;
@ -62,10 +69,7 @@ impl Action for UnmountApfsVolume {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
disk = %self.disk.display(),
name = %self.name,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { disk: _, name } = self;

View file

@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use target_lexicon::OperatingSystem;
use tokio::fs::remove_file;
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
@ -50,6 +51,11 @@ impl Action for ConfigureNixDaemonService {
fn tracing_synopsis(&self) -> String {
"Configure Nix daemon related settings with systemd".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "configure_nix_daemon",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),

View file

@ -1,4 +1,5 @@
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, ActionState, StatefulAction};
use crate::execute_command;
@ -32,13 +33,19 @@ impl Action for StartSystemdUnit {
format!("Enable (and start) the systemd unit {}", self.unit)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"start_systemd_unit",
unit = %self.unit,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
unit = %self.unit,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { unit, .. } = self;
@ -64,9 +71,7 @@ impl Action for StartSystemdUnit {
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
unit = %self.unit,
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { unit, .. } = self;

View file

@ -45,6 +45,7 @@ A custom [`Action`] can be created then used in a custom [`Planner`](crate::plan
```rust,no_run
use std::{error::Error, collections::HashMap};
use tracing::{Span, span};
use harmonic::{
InstallPlan,
settings::{CommonSettings, InstallSettingsError},
@ -71,13 +72,19 @@ impl Action for MyAction {
"My action".to_string()
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"my_action",
// Tracing fields here ...
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
// Tracing fields...
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
// Execute steps ...
Ok(())
@ -87,9 +94,7 @@ impl Action for MyAction {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
// Tracing fields...
))]
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
// Revert steps...
Ok(())
@ -159,6 +164,7 @@ mod stateful;
pub use stateful::{ActionState, StatefulAction};
use std::error::Error;
use tokio::task::JoinError;
use tracing::Span;
use crate::error::HasExpectedErrors;
@ -172,6 +178,14 @@ use crate::error::HasExpectedErrors;
pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
/// A synopsis of the action for tracing purposes
fn tracing_synopsis(&self) -> String;
/// A tracing span suitable for the action
///
/// It should be [`tracing::Level::DEBUG`] and contain the same name as the [`typetag::serde`] entry.
///
/// It may contain any fields, and will be attached in the [`StatefulAction::try_execute`] and [`StatefulAction::try_revert`] functions.
///
/// See [`tracing::Span`] for more details.
fn tracing_span(&self) -> Span;
/// A description of what this action would do during execution
///
/// If this action calls sub-[`Action`]s, care should be taken to use [`StatefulAction::describe_execute`] on those actions, not [`execute_description`][Action::execute_description].

View file

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use tracing::{Instrument, Span};
use super::{Action, ActionDescription, ActionError};
@ -26,6 +27,10 @@ impl StatefulAction<Box<dyn Action>> {
pub fn tracing_synopsis(&self) -> String {
self.action.tracing_synopsis()
}
/// A tracing span suitable for the action
pub fn tracing_span(&self) -> Span {
self.action.tracing_span()
}
/// A description of what this action would do during execution
pub fn describe_execute(&self) -> Vec<ActionDescription> {
match self.state {
@ -47,6 +52,7 @@ impl StatefulAction<Box<dyn Action>> {
/// Perform any execution steps
///
/// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing
#[tracing::instrument(level = "debug", skip_all)]
pub async fn try_execute(&mut self) -> Result<(), ActionError> {
match self.state {
ActionState::Completed => {
@ -73,6 +79,7 @@ impl StatefulAction<Box<dyn Action>> {
/// Perform any revert steps
///
/// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing
#[tracing::instrument(level = "debug", skip_all)]
pub async fn try_revert(&mut self) -> Result<(), ActionError> {
match self.state {
ActionState::Uncompleted => {
@ -106,6 +113,11 @@ where
self.action.tracing_synopsis()
}
/// A tracing span suitable for the action
pub fn tracing_span(&self) -> Span {
self.action.tracing_span()
}
pub fn inner(&self) -> &A {
&self.action
}
@ -137,24 +149,34 @@ where
///
/// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing
pub async fn try_execute(&mut self) -> Result<(), ActionError> {
let span = self.action.tracing_span();
match self.state {
ActionState::Completed => {
tracing::trace!(
parent: &span,
"Completed: (Already done) {}",
self.action.tracing_synopsis()
);
Ok(())
},
ActionState::Skipped => {
tracing::trace!("Skipped: {}", self.action.tracing_synopsis());
tracing::trace!(parent: &span, "Skipped: {}", self.action.tracing_synopsis());
Ok(())
},
_ => {
self.state = ActionState::Progress;
tracing::debug!("Executing: {}", self.action.tracing_synopsis());
self.action.execute().await?;
tracing::debug!(
parent: &span,
"Executing: {}",
self.action.tracing_synopsis()
);
self.action.execute().instrument(span.clone()).await?;
self.state = ActionState::Completed;
tracing::debug!("Completed: {}", self.action.tracing_synopsis());
tracing::debug!(
parent: &span,
"Completed: {}",
self.action.tracing_synopsis()
);
Ok(())
},
}
@ -163,23 +185,33 @@ where
///
/// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing
pub async fn try_revert(&mut self) -> Result<(), ActionError> {
let span = self.action.tracing_span();
match self.state {
ActionState::Uncompleted => {
tracing::trace!(
parent: &span,
"Reverted: (Already done) {}",
self.action.tracing_synopsis()
);
Ok(())
},
ActionState::Skipped => {
tracing::trace!("Skipped: {}", self.action.tracing_synopsis());
tracing::trace!(parent: &span, "Skipped: {}", self.action.tracing_synopsis());
Ok(())
},
_ => {
self.state = ActionState::Progress;
tracing::debug!("Reverting: {}", self.action.tracing_synopsis());
self.action.revert().await?;
tracing::debug!("Reverted: {}", self.action.tracing_synopsis());
tracing::debug!(
parent: &span,
"Reverting: {}",
self.action.tracing_synopsis()
);
self.action.revert().instrument(span.clone()).await?;
tracing::debug!(
parent: &span,
"Reverted: {}",
self.action.tracing_synopsis()
);
self.state = ActionState::Uncompleted;
Ok(())
},

View file

@ -2,7 +2,9 @@ use atty::Stream;
use eyre::WrapErr;
use std::error::Error;
use tracing_error::ErrorLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use tracing_subscriber::{
filter::Directive, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter,
};
#[derive(Clone, Default, Debug, clap::ValueEnum)]
pub enum Logger {
@ -33,6 +35,11 @@ pub struct Instrumentation {
/// Which logger to use
#[clap(long, env = "HARMONIC_LOGGER", default_value_t = Default::default(), global = true)]
pub logger: Logger,
/// Tracing directives
///
/// See https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
#[clap(long = "log-directive", global = true, env = "HARMONIC_LOG_DIRECTIVES", value_delimiter = ',', num_args = 0..)]
pub log_directives: Vec<Directive>,
}
impl<'a> Instrumentation {
@ -120,7 +127,7 @@ impl<'a> Instrumentation {
}
pub fn filter_layer(&self) -> eyre::Result<EnvFilter> {
let filter_layer = match EnvFilter::try_from_default_env() {
let mut filter_layer = match EnvFilter::try_from_default_env() {
Ok(layer) => layer,
Err(e) => {
// Catch a parse error and report it, ignore a missing env.
@ -134,6 +141,11 @@ impl<'a> Instrumentation {
},
};
for directive in &self.log_directives {
let directive_clone = directive.clone();
filter_layer = filter_layer.add_directive(directive_clone);
}
Ok(filter_layer)
}
}

View file

@ -9,6 +9,7 @@ use crate::{
error::HasExpectedErrors,
plan::RECEIPT_LOCATION,
planner::Planner,
settings::CommonSettings,
BuiltinPlanner, InstallPlan,
};
use clap::{ArgAction, Parser};
@ -30,6 +31,9 @@ pub struct Install {
)]
pub no_confirm: bool,
#[clap(flatten)]
pub settings: CommonSettings,
#[clap(
long,
env = "HARMONIC_EXPLAIN",
@ -47,12 +51,13 @@ pub struct Install {
#[async_trait::async_trait]
impl CommandExecute for Install {
#[tracing::instrument(level = "debug", skip_all, fields())]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(self) -> eyre::Result<ExitCode> {
let Self {
no_confirm,
plan,
planner,
settings,
explain,
} = self;
@ -97,7 +102,7 @@ impl CommandExecute for Install {
serde_json::from_str(&install_plan_string)?
},
(None, None) => {
let builtin_planner = BuiltinPlanner::default()
let builtin_planner = BuiltinPlanner::from_common_settings(settings)
.await
.map_err(|e| eyre::eyre!(e))?;
let res = builtin_planner.plan().await;

View file

@ -42,7 +42,7 @@ pub struct Uninstall {
#[async_trait::async_trait]
impl CommandExecute for Uninstall {
#[tracing::instrument(level = "debug", skip_all, fields())]
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(self) -> eyre::Result<ExitCode> {
let Self {
no_confirm,

View file

@ -11,6 +11,7 @@ use crate::{
Action, BuiltinPlanner,
};
use std::{collections::HashMap, path::Path};
use tokio::process::Command;
/// A planner for Linux multi-user installs
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@ -37,7 +38,7 @@ impl Planner for LinuxMulti {
}
// For now, we don't try to repair the user's Nix install or anything special.
if let Ok(_) = tokio::process::Command::new("nix-env")
if let Ok(_) = Command::new("nix-env")
.arg("--version")
.stdin(std::process::Stdio::null())
.status()

View file

@ -101,6 +101,11 @@ impl Planner for SteamDeck {
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
let persistence = &self.persistence;
if !persistence.is_absolute() {
return Err(PlannerError::Custom(Box::new(
SteamDeckError::AbsolutePathRequired(self.persistence.clone()),
)));
};
let nix_directory_buf = format!(
"\
@ -247,3 +252,9 @@ impl Into<BuiltinPlanner> for SteamDeck {
BuiltinPlanner::SteamDeck(self)
}
}
#[derive(thiserror::Error, Debug)]
enum SteamDeckError {
#[error("`{0}` is not a path that can be canonicalized into an absolute path, bind mounts require an absolute path")]
AbsolutePathRequired(PathBuf),
}

View file

@ -82,7 +82,7 @@ use std::collections::HashMap;
use crate::{
action::{ActionError, StatefulAction},
error::HasExpectedErrors,
settings::InstallSettingsError,
settings::{CommonSettings, InstallSettingsError},
Action, HarmonicError, InstallPlan,
};
@ -144,6 +144,16 @@ impl BuiltinPlanner {
}
}
pub async fn from_common_settings(settings: CommonSettings) -> Result<Self, PlannerError> {
let mut built = Self::default().await?;
match &mut built {
BuiltinPlanner::LinuxMulti(inner) => inner.settings = settings,
BuiltinPlanner::DarwinMulti(inner) => inner.settings = settings,
BuiltinPlanner::SteamDeck(inner) => inner.settings = settings,
}
Ok(built)
}
pub async fn plan(self) -> Result<InstallPlan, HarmonicError> {
match self {
BuiltinPlanner::LinuxMulti(planner) => InstallPlan::plan(planner).await,

View file

@ -32,14 +32,16 @@ Settings which only apply to certain [`Planner`](crate::planner::Planner)s shoul
pub struct CommonSettings {
/// Channel(s) to add
#[cfg_attr(
feature = "cli",clap(
long,
value_parser,
name = "channel",
action = clap::ArgAction::Append,
env = "HARMONIC_CHANNEL",
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
))]
feature = "cli",
clap(
long,
value_parser,
name = "channel",
action = clap::ArgAction::Append,
env = "HARMONIC_CHANNELS",
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
)
)]
pub(crate) channels: Vec<ChannelValue>,
/// Modify the user profile to automatically load nix
@ -59,26 +61,44 @@ pub struct CommonSettings {
/// Number of build users to create
#[cfg_attr(
feature = "cli",
clap(long, default_value = "32", env = "HARMONIC_DAEMON_USER_COUNT")
clap(
long,
default_value = "32",
env = "HARMONIC_DAEMON_USER_COUNT",
global = true
)
)]
pub(crate) daemon_user_count: usize,
/// The Nix build group name
#[cfg_attr(
feature = "cli",
clap(long, default_value = "nixbld", env = "HARMONIC_NIX_BUILD_GROUP_NAME")
clap(
long,
default_value = "nixbld",
env = "HARMONIC_NIX_BUILD_GROUP_NAME",
global = true
)
)]
pub(crate) nix_build_group_name: String,
/// The Nix build group GID
#[cfg_attr(
feature = "cli",
clap(long, default_value_t = 3000, env = "HARMONIC_NIX_BUILD_GROUP_ID")
clap(
long,
default_value_t = 3000,
env = "HARMONIC_NIX_BUILD_GROUP_ID",
global = true
)
)]
pub(crate) nix_build_group_id: usize,
/// The Nix build user prefix (user numbers will be postfixed)
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_NIX_BUILD_USER_PREFIX"))]
#[cfg_attr(
feature = "cli",
clap(long, env = "HARMONIC_NIX_BUILD_USER_PREFIX", global = true)
)]
#[cfg_attr(
all(target_os = "macos", feature = "cli"),
clap(default_value = "_nixbld")
@ -90,7 +110,10 @@ pub struct CommonSettings {
pub(crate) nix_build_user_prefix: String,
/// The Nix build user base UID (ascending)
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_NIX_BUILD_USER_ID_BASE"))]
#[cfg_attr(
feature = "cli",
clap(long, env = "HARMONIC_NIX_BUILD_USER_ID_BASE", global = true)
)]
#[cfg_attr(all(target_os = "macos", feature = "cli"), clap(default_value_t = 300))]
#[cfg_attr(
all(target_os = "linux", feature = "cli"),
@ -99,7 +122,10 @@ pub struct CommonSettings {
pub(crate) nix_build_user_id_base: usize,
/// The Nix package URL
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_NIX_PACKAGE_URL"))]
#[cfg_attr(
feature = "cli",
clap(long, env = "HARMONIC_NIX_PACKAGE_URL", global = true)
)]
#[cfg_attr(
all(target_os = "macos", target_arch = "x86_64", feature = "cli"),
clap(
@ -127,7 +153,7 @@ pub struct CommonSettings {
pub(crate) nix_package_url: Url,
/// Extra configuration lines for `/etc/nix.conf`
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_EXTRA_CONF"))]
#[cfg_attr(feature = "cli", clap(long, action = ArgAction::Set, num_args = 0.., value_delimiter = ',', env = "HARMONIC_EXTRA_CONF", global = true))]
pub extra_conf: Vec<String>,
/// If Harmonic should forcibly recreate files it finds existing