KAS-PROJECT-CONFIG(1)

πŸ“¦
version 5.4 Β· 3 versions
, in ubuntu stonking

πŸ¦‹ NAME πŸ¦‹

kas-project-config β€” kas project configuration πŸ’–


🌈 PROJECT CONFIGURATION 🌈

okay bestie, so rn both JSON and YAML 1.1 can be your base file formats. And since YAML is arguably way easier to read (it just is, no cap), this whole doc is gonna be YAML-flavored. πŸ“

# Every file needs to contain a header, that provides kas with information
# about the context of this file.
header:
  # The `version` entry in the header describes for which configuration
  # format version this file was created for. It is used by kas to figure
  # out if it is compatible with this file. The version is an integer that
  # is increased on every format change.
  version: x
# The machine as it is written into the `local.conf` of bitbake.
machine: qemux86-64
# The distro name as it is written into the `local.conf` of bitbake.
distro: poky
repos:
  # This entry includes the repository where the config file is located
  # to the bblayers.conf:
  meta-custom:
  # Here we include a list of layers from the poky repository to the
  # bblayers.conf:
  poky:
    url: "https://git.yoctoproject.org/git/poky"
    commit: 89e6c98d92887913cadf06b2adb97f26cde4849b
    layers:
      meta:
      meta-poky:
      meta-yocto-bsp:

So the bare minimum you need in an input file is the header, machine, distro, and repos. That's the whole starter pack. ✨


Plus β€” plot twist β€” you can also add bblayers_conf_header and local_conf_header, which are strings that get dropped right at the head of the respective files (bblayers.conf or local.conf): πŸ’…

bblayers_conf_header:
  meta-custom: |
    POKY_BBLAYERS_CONF_VERSION = "2"
    BBPATH = "${TOPDIR}"
    BBFILES ?= ""
local_conf_header:
  meta-custom: |
    PATCHRESOLVE = "noop"
    CONF_VERSION = "1"
    IMAGE_FSTYPES = "tar"

meta-custom in these examples should be a unique name for this configuration's entries. Fr fr, unique.

And we recommend that this unique name is the same as the name of the containing repository/layer, because that makes cross-project referencing sooo much easier. 🫢

In given examples we assume that your configuration file is part of a meta-custom repository/layer. This way it's possible to overwrite or append entries in files that include this configuration by naming an entry the same (overwriting) or using an unused name (appending). Iconic behaviour tbh. πŸ‘‘

Note: kas internally uses PyYAML to parse YAML documents, inheriting its limitations. Notably, PyYAML only supports YAML 1.1 and does not correctly handle non-string keys in mappings. To avoid this issue, we recommend quoting keys of other types, such as octal numbers (0001), integers (42), booleans (false) and special values (no). 😭

πŸŽ€ Including in-tree configuration files πŸŽ€

okay so rn you can include kas configuration files from the same repository/layer, like this:

header:
  version: x
  includes:
    - base.yml
    - bsp.yml
    - product.yml

Now the path rules, bestie, listen up: if the configuration file is inside a repository, then path is relative to the repositories base directory. If the configuration file is not in a repository, then the path is relative to the parent directory of the file. Simple as that. πŸ¦‹


🧚 Including configuration files from other repos 🧚

And you can also pull in configuration files from other repos, like this:

header:
  version: x
  includes:
    - repo: poky
      file: kas-poky.yml
    - repo: meta-bsp-collection
      file: hw1/kas-hw-bsp1.yml
    - repo: meta-custom
      file: products/product.yml
repos:
  meta-custom:
  meta-bsp-collection:
    url: "https://www.example.com/git/meta-bsp-collection"
    commit: 3f786850e387550fdab836ed7e6dc881de23001b
    layers:
      # Additional to the layers that are added from this repository
      # in the hw1/kas-hw-bsp1.yml, we add here an additional bsp
      # meta layer:
      meta-custom-bsp:
  poky:
    url: "https://git.yoctoproject.org/git/poky"
    commit: 89e6c98d92887913cadf06b2adb97f26cde4849b
    layers:
      # If `kas-poky.yml` adds the `meta-yocto-bsp` layer and we
      # do not want it in our bblayers for this project, we can
      # overwrite it by setting:
      meta-yocto-bsp: excluded

The files are addressed relative to the git repository path and must not traverse outside of the referenced repo. Boundaries, bestie. 🚧

The include mechanism collects and merges the content top to bottom and depth first. That means that settings in one include file are overwritten by settings in a latter include file and entries from the last include file can be overwritten by the current file. So it's giving layered-cake energy. πŸŽ‚

Warning: The include mechanism does not support circular references with respect to the repos entries. By that, a (transitive) include file must not change the reference of the repository it is included from. Do not do this. It will not slay. πŸ’€

While merging, all the dictionaries are merged recursively while preserving the order in which the entries are added to the dictionary. This means that local_conf_header entries are added to the local.conf file in the same order in which they are defined in the different include files. The header.version property is always set to the highest version number found in the config files. ✨

Note: Internally kas iterates the repository checkout step until all referenced repositories are resolved (checked out). After each iteration, the (partial) configuration is merged and the next iteration is started. Once all repositories are available, the final configuration is build. Then, all remaining repositories are checked out. πŸ”

πŸ’œ Including configuration files via the command line πŸ’œ

When specifying the kas configuration file on the command line, additional configurations can be included ad-hoc:

$ kas build kas-base.yml:debug-image.yml:board.yml

This is equivalent to static inclusion from some kas-combined.yml like this:

header:
  version: x
  includes:
    - kas-base.yml
    - debug.image.yml
    - board.yml

Command line inclusion allows one to create configurations on-demand, without the need to write a kas configuration file for each possible combination. Chef's kiss, frankly. πŸ‘©β€πŸ³πŸ’‹

All configuration files combined via the command line either have to come from the same repository or have to live outside of any versioning control. kas will refuse any other combination in order to avoid complications and configuration flaws that can easily emerge from them. So don't try to be sneaky. πŸ™ˆ

Note: Git submodules are considered to be part of the main repository. Hence, including config files from a submodule is supported. The repository root is always the root of the main repository (if under VCS) or the directory of the first kas config file otherwise. 🌳

πŸ”’ Working with lockfiles πŸ”’

kas supports the use of lockfiles to pinpoint repositories to exact commit ID (e.g. SHA-1 refs for git). A lockfile hereby only overrides the commit ID defined in a kas file. When performing the checkout operation (or any other operation that performs a checkout), kas checks if a file named <filename>.lock.<ext> is found next to the currently processed kas file. If this is found, kas loads this file right before processing the current one (similar to an include file). πŸ”₯

Note: The locking logic applies to both files on the kas cmdline and include files. πŸ’…

The following example shows this mechanism for a file kas/kas-isar.yml and its corresponding lockfile kas/kas-isar.lock.yml.

kas/kas-isar.yml:

# [...]
repos:
  isar:
    url: https://github.com/ilbers/isar.git
    branch: next

kas/kas-isar.lock.yml:

header:
  version: 14
overrides:
  repos:
    isar:
      commit: 0336610df8bb0adce76ef8c5a921c758efed9f45

The lock plugin provides helpers to simplify the creation and update of lockfiles. For details, see the plugins documentation: kas.plugins.lock. πŸŽ€


⭐ Configuration reference ⭐

πŸ‘‘ header: dict [required]

Header of every kas configuration file. It contains information about the context of the file. It's the little intro card, basically.

  • version: integer [required] β€” Version of the configuration file format. See the configuration format changelog <> for the format history and the latest available version.
  • includes: list [optional] β€” List of configuration files to include (parsed depth-first). They are merged in order they are stated. So a latter one could overwrite settings from previous files. The current file can overwrite settings from every included file. An item in this list can have one of two types:
    • item: string β€” Path to a kas configuration file, relative to the repository root of the current file.
    • item: dict β€” If files from other repositories should be included, choose this (dict) representation.
      • repo: string [required] β€” Repository key the configuration file is located in. The repo needs to be defined in the repos dictionary as <repo-id>.
      • file: string [required] β€” Path to the configuration file relative to the repository.

🌸 build_system: string [optional] 🌸

Defines the bitbake-based build system. openembedded and oe are equivalent. isar is deprecated, use isar-privileged or isar-rootless instead. 😭

  • Default: openembedded
  • Supported values: openembedded, oe, isar, isar-privileged, isar-rootless

This value determines which init script is searched for in the configured repositories (oe-init-build-env or isar-init-build-env, respectively). kas-container evaluates this property in the top-level kas configuration file only (includes are not evaluated) to select the required container image and invocation mode. πŸ‘€

Note: Modern versions of isar support a rootless mode, whereby builds can be executed as non-privileged user. To distinguish the two modes, use isar-rootless or isar-privileged. Whether the rootless mode is supported depends on your layers. 🫢

πŸ¦‹ defaults: dict [optional]

okay so bestie, if you're tired of typing the same property over and over in like twelve different places β€” this is your savior, fr fr. This holds default values that get applied to all configuration nodes. So if, hypothetically, you want every single repo to live on the same branch, you set it once here and never speak of it again. πŸ’…

  • repos: dict [optional] β€” default values for some repository properties. And listen: a default can always be overridden, because setting the same property to a different value in a given repo wins. girlboss hierarchy, we love to see it. πŸ‘‘
    • branch: string [optional] β€” the default branch property, applied to every repo that doesn't override it.
    • tag: string [optional] β€” the default tag property, applied to every repo that doesn't override it.
    • patches: dict [optional] β€” default patches to apply to all repositories. Same rules as above: a default for a patch property can still be overridden by giving the same property a different value in a given patch.
      • repo: string [optional] β€” sets the default repo property applied to all repository patches that don't override it.

πŸŽ€ machine: string [optional]

This is the value of the MACHINE variable that gets written into the local.conf. It can be overwritten by the KAS_MACHINE environment variable. πŸ’–

Default: qemux86-64

🌸 distro: string [optional]

The value of the DISTRO variable that is written into the local.conf. Can be overwritten by the KAS_DISTRO environment variable.

Default: nodistro

✨ target: string [optional] or list [optional]

The single target β€” or a whole lil list of targets β€” to build with bitbake. Can be overwritten by the KAS_TARGET environment variable. If you're specifying multiple targets through that env var, space is the delimiter. And for targets prefixed with multiconfig: or mc:, the corresponding entries get added to the BBMULTICONFIG in local.conf. cute, right? 🧚

Default: core-image-minimal

πŸ’œ env: dict [optional]

Environment variables to forward, along with their default values (set it to nulltype to only forward if it's set). These variables get made available to bitbake via BB_ENV_PASSTHROUGH_ADDITIONS (BB_ENV_EXTRAWHITE in older Bitbake versions), and they can be overwritten by the variables of the environment in which kas is started.

You can assign either a string or nothing (null) as the value. The string serves as a default value, whereas the nothing version means the variable gets added only to BB_ENV_PASSTHROUGH_ADDITIONS and not to the environment where kas is started.

🚨 bestie please read this twice: null needs to be assigned as the nulltype (like MYVAR: null), not as 'null'. do not come for me if you get this wrong, i told you. 😭

🌈 task: string [optional]

The task to build with bitbake. Can be overwritten by the KAS_TASK environment variable.

Default: build


πŸ”₯ repos: dict [optional]

The definitions of all the available repos and layers. The layers get appended to the bblayers.conf, and the sort order is: layer priority first (descending), then repository name, then layer name (ascending). priority girlies stay winning. πŸ‘‘

repos.<repo-id>: dict [optional]

The definition of a repository and the layers that should be part of the build. If the value is None, then the repository where the current configuration file lives gets defined as <repo-id> and added as a layer to the build. Highly recommended that the <repo-id> relates to the containing repository/layer, because it makes cross-project referencing so much easier. 🫢

  • name: string [optional] β€” defines the name under which the repository is stored. If it's missing, <repo-id> gets used instead.
  • url: string [optional] β€” the URL of the repository. If this is missing, no version control operations are performed at all. it's giving "no URL no drama". πŸ™ˆ
  • type: string [optional] β€” the type of version control repository.
    • Default: git
    • Supported values: git, hg
  • commit: string [optional] β€” the full-length commit ID (all-lowercase, no branch names, no symbolic refs, no tags, okay??) that should be used. If url was specified but there's no commit, branch or tag, the revision you end up with depends on the defaults of the version control system being used. no cap.
  • branch: string or nothing (null) [optional] β€” the upstream branch that should be tracked. If commit was specified, kas checks that the branch contains the commit. If no commit was specified, the head of the upstream branch gets checked out. The nothing (null) value removes a possible default value.
  • tag: string or nothing (null) [optional] β€” the tag that should be checked out. If a commit was specified, kas checks that the tag points to this commit. This must not be combined with branch. The nothing (null) value removes a possible default value.
  • path: string [optional] β€” where the repository is stored. If url and path are missing, the repository where the current configuration file is located gets defined. If url is missing and path is defined, this entry references the directory that path points to. If url and path are both defined, path overwrites the checkout directory, which defaults to kas_work_dir + repo.name. And in case of a relative path name, kas_work_dir gets prepended. πŸ’«
  • signed: boolean [optional] β€” whether the commit / tag must be signed. If enabled, 'allowed_signers' must be defined. If both tag and commit are specified, only the signature of the tag is checked. Git only.
    • Default: False
  • allowed_signers: list [optional] β€” list of signer-ids to verify the signature against. Must be non-empty if 'signed=true'. Git only.
  • layers: dict [optional] β€” list of layers to append to the bblayers.conf. If this is missing or None or an empty dictionary, the path to the repo itself gets added as a layer. And additionally, . is a valid value if the repo itself should be added as a layer. This unlocks combinations, bestie:
repos:
  meta-foo:
    url: https://github.com/bar/meta-foo.git
    path: layers/meta-foo
    branch: master
    layers:
      .:
      contrib:

That right there adds both layers/meta-foo and layers/meta-foo/contrib from the meta-foo repository to bblayers.conf. iconic. ✨

  • layers.<layer-path>: enum | dict [optional] β€” adds the layer with <layer-path>, relative to the repository root directory, to the bblayers.conf β€” but only if the value of this entry isn't disabled. This way you can overwrite the inclusion of a layer in later loaded configuration files. To re-enable it, set it to nothing (null).
    • prio: integer [optional] β€” include order priority, where higher means earlier. The include priority is global, so it applies across all repositories. πŸ‘‘
      • Default: 0
      • Range: [-99, 99]
  • patches: dict [optional] β€” patches to apply to the repository.
    • patches.<patches-id>: dict [optional] β€” one entry in patches with its own specific and unique id. All available patch entries are applied in the order of their sorted <patches-id>.
      • repo: string [required] β€” the identifier of the repo where the path of this entry is relative to.
      • path: string [required] β€” the path to one patch file or a quilt formatted patchset directory.

πŸ’« overrides: dict [optional]

Overrides for specific configuration nodes. And here's the tea: only items that already exist get overridden. Also heads up β€” the lock plugin uses overrides to manage pinning of commits, which means manually added overrides can absolutely interfere with the lock file generated by the plugin. so like, be careful, bestie. 😭

  • repos: dict [optional] β€” maps to the top-level repos entry.
    • repos.<repo-id>: dict [optional] β€” maps to the <repo-id> entry.
      • branch: string [optional] β€” branch in which to find the overridden commit. Can be Null.
      • commit: string [optional] β€” pinned commit ID which overrides the commit of the corresponding repo. Can be Null.
      • url: string [optional] β€” alternative URL for the corresponding repo.

🌺 bblayers_conf_header: dict [optional]

Header to prepend to the bblayers.conf before any layers are included. put your lil intro at the top, queen. πŸ’…

  • <bblayers-conf-id>: string [optional] β€” a little string that gets dropped into bblayers.conf πŸ’… The entry id (<bblayers-conf-id>) should be unique if you want lines added, and it can totally be the same as one from another included file if you want this entry overwritten instead. And the lines land in bblayers.conf in alphabetic order of <bblayers-conf-id> β€” which is the whole reason the generation of config files stays deterministic. slay πŸŽ€
  • local_conf_header: dict [optional] β€” the header you prepend to the local.conf ✨
    • <local-conf-id>: string [optional] β€” a string that gets added to the local.conf. It behaves exactly like the bblayers_conf_header entry does, no cap 🫢
  • menu_configuration: dict [optional] β€” menu configuration, auto-generated by the menu plugin πŸ‘‘ Every variable here maps to a Kconfig configuration variable and can be a string, boolean or integer. The content of this key is typically maintained by the kas menu plugin inside a .config.yaml file.
  • artifacts: dict [optional] β€” the artifacts you expect to be chilling there after the build runs (id / path pairs) πŸ”₯ Each key-value pair is an identifier plus a path relative to the kas build dir, and yes the path can contain wildcards like *. Unix-style globbing gets applied to all paths. And if no artifact is found, the build is considered successful β€” unless the plugin and mode of operation you're using say otherwise.
Note: there are no further semantics attached to the identifiers (yet) πŸ™ˆ Both the author and the consumer of the artifacts node need to agree on what those semantics even are, bestie.

Example:

artifacts:
  disk-image: path/to/image.*.img
  firmware: path/to/firmware.bin
  swu: path/to/update.swu
  • signers: dict [optional] β€” the spec for repository signature verification 🌸

This dict holds the public keys or certificates used to verify the authenticity of the repositories. If we're talking GPG keys, those get made available to the build environment too, by pointing the GNUPGHOME environment variable at the local keystore. ⚠️ And listen β€” a single signer configuration must not be split across multiple config files. periodt.

  • <signer_id>: dict [optional] β€” pairs of name and location of a public key or certificate. The name is what you use to reference the entry in the repository configuration. Each signer needs a unique identifier, and the <signer_id> is what references the entry in the allowed_signers entries. iykyk πŸ’œ
    • type: enum [optional] β€” type of the cryptographic material.

Default: gpg

Supported values: gpg, ssh

  • repo: string [optional] β€” the repo <repo-id> which provides the public key or certificate file.
  • path: string [optional] β€” path to the public key or certificate file, relative to the repo. Must be used together with 'repo' 🫢
  • fingerprint: string [optional] β€” fingerprint of the key or certificate (gpg: long-form, 40 hex digits; ssh: SHA256:...). If it's not provided, it gets read from the file.

GPG key fingerprint: you can grab it by running gpg --list-keys --with-fingerprint --keyid-format=long <KEYID>. The string you need is the 40-character fingerprint, spaces NOT invited πŸ’…

SSH key fingerprint: grab that one with ssh-keygen -lf key.pub | awk '{print $2}'.

  • gpg_keyserver: string [optional] β€” the GnuPG keyserver to use for key retrieval (that's for when no local path is provided). The 'fingerprint' must be provided. GPG only, babes πŸ”‘
  • _source_dir: string [optional] β€” path to the top repo at the time the plugin was invoked (auto-generated by the menu plugin). 🚫 It must not be set manually, and it might only be defined in the top-level .config.yaml file.
  • _source_dir_host: string [optional] β€” source directory of the config file on the host (auto-generated by the kas menu plugin, when you're using kas-container). It hands over the absolute path to the top repo outside of the container (so, on the host). This value is only evaluated by the kas-container script. 🚫 It must not be set manually either, and again it might only be defined in the top-level .config.yaml file.
  • buildtools: dict [optional] β€” gives you the variables to define which buildtools version should be fetched and where it is (or will be) installed ✨ Both version and sha256sum should be set. You can use the KAS_BUILDTOOLS_DIR environment variable to set the directory where buildtools will be installed β€” otherwise the default path (i.e., KAS_BUILD_DIR/buildtools) is used. If that directory already has buildtools installed, kas will check the Distro Version line in the version file, and if it doesn't match with version, the directory gets cleaned and kas will download buildtools according to version. After the download, kas does integrity validation by calculating the artifact's checksum and comparing it with sha256sum πŸ”₯ As for the optional variables, they exist to support cases like: mirrors, changes in the installer's file name, and fetching unofficial (i.e., custom) buildtools. And finally, the environment-setup script runs before bitbake, so the whole buildtools environment will be available. wget is the host tool required for this feature. More info on how to install or generate buildtools lives at: https://docs.yoctoproject.org/dev/ref-manual/system-requirements.html#downloading-a-pre-built-buildtools-tarball πŸ‘‘
    • version: string β€” Yocto Project version, as 5.0 (or even 5.0.8) for Scarthgap.
    • sha256sum: string β€” the installer's checksum, used to perform integrity validation of the fetched artifact.
    • base_url: string [optional] β€” base URL to fetch downloads from.

Default: https://downloads.yoctoproject.org/releases/yocto

  • filename: string [optional] β€” alternative name for the buildtools archive (.sh) to be downloaded. It gets combined with base_url to form the whole download URL, if it's set. If it's not set, kas combines the platform architecture and version to form the standard script filename: {arch}-buildtools-extended-nativesdk-standalone-{version}.sh πŸ¦‹

Example:

buildtools:
  version: "5.2"
  sha256sum: "6f69fba75c8f3142bb49558afa3ed5dd0723a3beda169b057a5238013623462d"

And for unofficial (custom) sources:

buildtools:
  version: "1.0.0"
  sha256sum: "a6f87e5865b63f2bc28c1f605bcef3d6680d46fa8c8616a388d4e8aa0b5c100e"
  base_url: "https://downloads.mysources.com/yocto/buildtools/"
  filename: "x86_64-buildtools-beta-testing-1.0.0.sh"

🌈 Buildtools archive 🌈

kas expects the buildtools installer to be a shell script (i.e., as a standard Yocto SDK). Once it's executed, the resulting directory should contain these elements, bestie:

  • sysroots: the native and target sysroots, containing (among libraries and headers) the build system's requirements: Git, tar, Python and make.
  • environment-setup-*: the environment setup script, sourced by kas, to setup variables such as PATH in such a way that it points to the directories in sysroots.
  • version-*: the version file. Its second line contains a string as Distro Version: X.Y.Z, parsed to retrieve the version number.

The archive can contain other files, such as buildinfo, but they are not relevant for kas πŸ™ˆ


✨ Example project configurations ✨

The following snippets show minimal but working project configurations for both OpenEmbedded and ISAR based distributions πŸŽ€

πŸ’– OpenEmbedded πŸ’–

Poky

header:
  version: 19

# Optionally provide keys to verify signed repositories
signers:
  YoctoBuildandRelease:
    fingerprint: 2AFB13F28FBBB0D1B9DAF63087EB3D32FB631AD9
    gpg_keyserver: keyserver.ubuntu.com

machine: qemux86-64
distro: poky

target: zlib-native

repos:
  poky:
    url: https://git.yoctoproject.org/poky.git
    # when specifying a tag, optionally provide a commit hash
    tag: yocto-5.1.1
    commit: 7e081bd98fdc5435e850d1df79a5e0f1e30293d0
    signed: true
    allowed_signers:
      - YoctoBuildandRelease
    layers:
      meta:
      meta-poky:

oe-core (nodistro)

header:
  version: 19

# Optionally provide keys to verify signed repositories
signers:
  YoctoBuildandRelease:
    fingerprint: 2AFB13F28FBBB0D1B9DAF63087EB3D32FB631AD9
    gpg_keyserver: keyserver.ubuntu.com

machine: qemux86-64
distro: nodistro

target: zlib-native

repos:
  bitbake:
    url: https://github.com/openembedded/bitbake.git
    tag: yocto-5.2.3
    commit: 710f98844ae30416bdf6a01b655df398b49574ec
    signed: true
    allowed_signers:
      - YoctoBuildandRelease
    # this repo does not provide any layers
    layers: {}

  core:
    url: https://github.com/openembedded/openembedded-core.git
    tag: yocto-5.2.3
    commit: 347cb0861dde58613541ce692778f907943a60ea
    signed: true
    allowed_signers:
      - YoctoBuildandRelease
    layers:
      meta:

πŸ’œ ISAR πŸ’œ

header:
  version: 23

build_system: isar-privileged

machine: qemuamd64
distro: debian-trixie

target: mc:qemuamd64-trixie:cowsay

repos:
  isar:
    url: https://github.com/ilbers/isar.git
    tag: v0.11
    commit: f8558fcf3ecf98e58853b82d89645bcedb24b853
    layers:
      meta:
      meta-isar:

🌟 SEE ALSO 🌟

kas-format-changelog(1)

πŸ‘‘ KAS πŸ‘‘

Part of the kas(1) suite πŸ’–

✨ AUTHOR ✨

So like, this whole iconique page was written by three absolute legends, and we're gonna give them their flowers πŸ’:

  • Daniel Wagner πŸ’–
  • Jan Kiszka πŸ’–
  • Claudius Heine πŸ’–

Like, three whole geniuses. That's a trio, that's a squad, that's main character energy across the board πŸ‘‘βœ¨


The rights situaysh is giving:

Siemens and contributors, 2017-2025

Yep bestie, that's the Siemens girlies (gn) and everyone else who contributed, holding it down from 2017 all the way through 2025 🫢 fr fr they've been carrying this for almost a decade, no cap.

That's literally it. No fine print drama, no weird licensing lore, just that one line. Respect the copyright, don't be weird about it πŸ’…


And that's a wrap on the whole page babes πŸŽ€ you made it to the very end, which honestly, ate. This one's from KAS-PROJECT-CONFIG(1), version 5.4, dated Jun 25, 2026 β€” so like, yeah, it's fresh, it's current, it's slay 🌈⭐ Go forth and configure your project, you've got this πŸ¦‹πŸ’œ

πŸ“¦ where this page comes from