From 395cda2cc789f7d4cc64b7d6e769b20585c86c6c Mon Sep 17 00:00:00 2001 From: Gerald Pinder <gmpinder@gmail.com> Date: Sat, 18 Jan 2025 12:42:31 -0500 Subject: [PATCH] feat: Create a Nushell script for 'files' module --- .github/workflows/build-individual.yml | 2 + .github/workflows/build-unified.yml | 2 + modules/files/files.tsp | 38 ++++++++++---- modules/files/{ => v1}/README.md | 0 modules/files/{ => v1}/files.sh | 0 modules/files/v2/README.md | 46 ++++++++++++++++ modules/files/v2/files.nu | 72 ++++++++++++++++++++++++++ 7 files changed, 150 insertions(+), 10 deletions(-) rename modules/files/{ => v1}/README.md (100%) rename modules/files/{ => v1}/files.sh (100%) create mode 100644 modules/files/v2/README.md create mode 100644 modules/files/v2/files.nu diff --git a/.github/workflows/build-individual.yml b/.github/workflows/build-individual.yml index 8fc3ffb6..a01cd00d 100644 --- a/.github/workflows/build-individual.yml +++ b/.github/workflows/build-individual.yml @@ -1,6 +1,8 @@ name: build-individual on: push: + branches: + - main paths-ignore: # don't rebuild if only documentation has changed - "**.md" pull_request: diff --git a/.github/workflows/build-unified.yml b/.github/workflows/build-unified.yml index 15a4c721..547056c1 100644 --- a/.github/workflows/build-unified.yml +++ b/.github/workflows/build-unified.yml @@ -1,6 +1,8 @@ name: build-unified on: push: + branches: + - main paths-ignore: # don't rebuild if only documentation has changed - "**.md" pull_request: diff --git a/modules/files/files.tsp b/modules/files/files.tsp index f6a826a0..93279f9f 100644 --- a/modules/files/files.tsp +++ b/modules/files/files.tsp @@ -2,15 +2,33 @@ import "@typespec/json-schema"; using TypeSpec.JsonSchema; @jsonSchema("/modules/files.json") -model FilesModule { - /** Copy files to your image at build time - * https://blue-build.org/reference/modules/files/ - */ - type: "files"; +union FilesModule { + FilesV1, + FilesV2, +} + +model FilesV1 { + /** Copy files to your image at build time + * https://blue-build.org/reference/modules/files/ + */ + type: "files@v1"; + + /** List of files / folders to copy. */ + files: Array<Record<string>> | Array<{ + source: string; + destination: string; + }>; +} + +model FilesV2 { + /** Copy files to your image at build time + * https://blue-build.org/reference/modules/files/ + */ + type: "files@v2" | "files@latest" | "files"; - /** List of files / folders to copy. */ - files: Array<Record<string>> | Array<{ - source: string; - destination: string; - }>; + /** List of files / folders to copy. */ + files: Array<{ + source: string; + destination: string; + }>; } diff --git a/modules/files/README.md b/modules/files/v1/README.md similarity index 100% rename from modules/files/README.md rename to modules/files/v1/README.md diff --git a/modules/files/files.sh b/modules/files/v1/files.sh similarity index 100% rename from modules/files/files.sh rename to modules/files/v1/files.sh diff --git a/modules/files/v2/README.md b/modules/files/v2/README.md new file mode 100644 index 00000000..70e9b854 --- /dev/null +++ b/modules/files/v2/README.md @@ -0,0 +1,46 @@ +# `files` + +The `files` module can be used to copy directories from `files/` to +any location in your image at build-time, as long as the location exists at +build-time (e.g. you can't put files in `/home/<username>/`, because users +haven't been created yet prior to first boot). + +:::note +In run-time, `/usr/etc/` is the directory for "system" +configuration templates on atomic Fedora distros, whereas `/etc/` is meant for +manual overrides and editing by the machine's admin *after* installation. + +In build-time, as a custom-image maintainer, you want to copy files to `/etc/`, +as those are automatically moved to system directory `/usr/etc/` during atomic Fedora image deployment. +Check out this blog post for more details about this: +https://blue-build.org/blog/preferring-system-etc/ +::: + +:::caution +The `files` module **cannot write to directories that will later be symlinked +to point to other places (typically `/var/`) by `rpm-ostree`**. + +This is because it doesn't make sense for a directory to be both a symlink and +a real directory that has had actual files directly copied to it, so the +`files` module copying files to one of those directories (thereby instantiating +it as a real directory) and `rpm-ostree`'s behavior regarding them will +necessarily conflict. + +For reference, according to the [official Fedora +documentation](https://docs.fedoraproject.org/en-US/fedora-silverblue/technical-information/#filesystem-layout), +here is a list of the directories that `rpm-ostree` symlinks to other +locations: + +- `/home/` → `/var/home/` +- `/opt/` → `/var/opt/` +- `/srv/` → `/var/srv/` +- `/root/` → `/var/roothome/` +- `/usr/local/` → `/var/usrlocal/` +- `/mnt/` → `/var/mnt/` +- `/tmp/` → `/sysroot/tmp/` + +So don't use `files` to copy any files to any of the directories on the left, +because at runtime `rpm-ostree` will want to link them to the ones on the +right, which will cause a conflict as explained above. + +::: diff --git a/modules/files/v2/files.nu b/modules/files/v2/files.nu new file mode 100644 index 00000000..8bd728b9 --- /dev/null +++ b/modules/files/v2/files.nu @@ -0,0 +1,72 @@ +#!/usr/bin/env nu + +def determine_file_dir []: nothing -> string { + let config_dir = $env.CONFIG_DIRECTORY + + if $config_dir == '/tmp/config' { + $config_dir | path join 'files' + } else { + $config_dir + } +} + +def main [config: string]: nothing -> nothing { + let config = $config | from json + let files: list = $config.files + let list_is_empty = $files | is-empty + + if $list_is_empty { + return (error make { + msg: $"(ansi red_bold)At least one entry is required in property(ansi reset) `(ansi cyan)files(ansi reset)`:\n($config | to yaml)" + label: { + text: 'Checks for empty list' + span: (metadata $list_is_empty).span + } + }) + } + + let config_dir = determine_file_dir + + for $file in $files { + let file = $file | merge { source: ($config_dir | path join $file.source) } + let source = $file.source + let destination = $file.destination + let source_exists = not ($source | path exists) + let is_dir = ($destination | path exists) and ($destination | path type) == 'file' + + if $source_exists { + return (error make { + msg: $"(ansi red_bold)The path (ansi cyan)`($source)`(ansi reset) (ansi red_bold)does not exist(ansi reset):\n($config | to yaml)" + label: { + text: 'Checks for source' + span: (metadata $source_exists).span + } + }) + } + + if $is_dir { + return (error make { + msg: $"(ansi red_bold)The destination path (ansi cyan)`($destination)`(ansi reset) (ansi red_bold)should be a directory(ansi reset):\n($config | to yaml)" + label: { + text: 'Checks destination is directory' + span: (metadata $is_dir).span + } + }) + } + + print $'Copying (ansi cyan)($source)(ansi reset) to (ansi cyan)($destination)(ansi reset)' + mkdir $destination + + if ($source | path type) == 'dir' { + cp -rfv ($source | path join * | into glob) $destination + } else { + cp -fv $source $destination + } + + let git_keep = $destination | path join '.gitkeep' + + if ($git_keep | path exists) { + rm -f $git_keep + } + } +}