Switch from KCL to CUE

Although KCL is unquestionably a more powerful language, and maps more
closely to my mental model of how host/environment/application
configuration is defined, the fact that it doesn't work on ARM (issue
982]) makes it a non-starter.  It's also quite slow (owing to how it
compiles a program to evaluate the code) and cumbersome to distribute.
Fortunately, `tmpl` doesn't care how the values it uses were computed,
so we freely change configuration languages, so long as whatever we use
generates JSON/YAML.

CUE is probably a lot more popular than KCL, and is quite a bit simpler.
It's more restrictive (values cannot be overridden once defined), but
still expressive enough for what I am trying to do (so far).
master
Dustin 2024-01-14 22:16:39 -06:00
parent 8f31b0302c
commit 11f9957c11
24 changed files with 239 additions and 244 deletions

View File

@ -6,6 +6,11 @@ end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[**.cue]
max_line_length = 79
indent_style = tab
indent_size = 4
[**.k] [**.k]
max_line_length = 79 max_line_length = 79
indent_style = space indent_style = space
@ -15,3 +20,7 @@ indent_size = 4
max_line_length = 79 max_line_length = 79
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[Containerfile]
indent_style = tab
indent_size = 4

View File

@ -1,5 +1,7 @@
FROM registry.fedoraproject.org/fedora-minimal:39 AS build FROM registry.fedoraproject.org/fedora-minimal:39 AS build
ARG CUE_VERSION=0.7.0
RUN --mount=type=cache,target=/var/cache \ RUN --mount=type=cache,target=/var/cache \
microdnf install -y \ microdnf install -y \
--setopt install_weak_deps=0 \ --setopt install_weak_deps=0 \
@ -10,16 +12,12 @@ RUN --mount=type=cache,target=/var/cache \
x86_64) ARCH=amd64 ;; \ x86_64) ARCH=amd64 ;; \
aarch64) ARCH=arm64 ;; \ aarch64) ARCH=arm64 ;; \
esac \ esac \
&& url="https://github.com/kcl-lang/lib/raw/v0.7.5/lib/linux-${ARCH}/" \ && url="https://github.com/cue-lang/cue/releases/download/v${CUE_VERSION}/cue_v${CUE_VERSION}_linux_${ARCH}.tar.gz" \
&& curl -fsSL "${url}/kclvm_cli" -o /usr/local/bin/kclvm_cli \
&& curl -fsSL "${url}/libkclvm_cli_cdylib.so" \
-o /usr/local/bin/libkclvm_cli_cdylib.so \
&& chmod +x /usr/local/bin/kclvm_cli \
&& url="https://github.com/kcl-lang/cli/releases/download/v0.7.2/kcl-v0.7.2-linux-${ARCH}.tar.gz" \
&& curl -fsSL "${url}" \ && curl -fsSL "${url}" \
| tar -C /usr/local/bin -xz kcl \ | tar -C /usr/local/bin -xz cue \
&& : && :
FROM git.pyrocufflink.net/containerimages/tmpl FROM git.pyrocufflink.net/containerimages/tmpl
RUN --mount=type=cache,target=/var/cache \ RUN --mount=type=cache,target=/var/cache \
@ -27,11 +25,10 @@ RUN --mount=type=cache,target=/var/cache \
microdnf install -y \ microdnf install -y \
--setopt install_weak_deps=0 \ --setopt install_weak_deps=0 \
age \ age \
gcc \ git-core \
git \
&& ln -snf /host/etc/passwd /etc/passwd \ && ln -snf /host/etc/passwd /etc/passwd \
&& ln -snf /host/etc/group /etc/group \ && ln -snf /host/etc/group /etc/group \
&& cp -a /build/usr/local/bin/. /usr/local/bin \ && cp -a /build/usr/local/bin/cue /usr/local/bin/ \
&& for cmd in \ && for cmd in \
systemctl \ systemctl \
systemd-sysusers \ systemd-sysusers \
@ -46,9 +43,5 @@ ENTRYPOINT []
CMD ["/config.sh"] CMD ["/config.sh"]
ENV KCL_GO_DISABLE_ARTIFACT=on
ENV KCL_PKG_PATH=/tmp
ENV KCL_CACHE_PATH=/tmp
LABEL license= \ LABEL license= \
vendor='Dustin C. Hatch' \ vendor='Dustin C. Hatch' \

View File

@ -1,7 +0,0 @@
import pkg.nut
nut: nut.Nut {
users.upsmon = {
upsmon = "primary"
}
}

View File

@ -1,13 +0,0 @@
#!/bin/sh
set -e
: "${DESTDIR=/host}"
env="$1"
kcl nut/base nut/"${env}" -o nut.yaml
mkdir -p "${DESTDIR}"/etc/ups
tera --template nut/templates/ups.conf nut.yaml \
> "${DESTDIR}"/etc/ups/ups.conf
tera --template nut/templates/upsd.users nut.yaml \
> "${DESTDIR}"/etc/ups/upsd.users

View File

@ -1,24 +0,0 @@
import pkg.nut
nut: nut.Nut {
ups = {
apc1500 = {
driver = "usbhid-ups"
port = "auto"
desc = "Back-UPS XS 1500G"
vendorid = "051d"
product = ".*1500M.*"
pollonly = "enabled"
pollinterval = 1
}
apc1300 = {
driver = "usbhid-ups"
port = "auto"
desc = "Back-UPS XS 1300G"
vendorid = "051d"
product = ".*1300G.*"
pollonly = "enabled"
pollinterval = 1
}
}
}

View File

@ -1,23 +0,0 @@
import pkg.nut
nut: nut.Nut {
users.homeassistant.password = """\
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxZUZleGt3emxXdDFtcEtN
Wll3K0hrS2c2M1oyMWh2VlBnMER0bkZOb1VVCnU3aTI2eVJoV1dKNWxEd0VnbVNa
NlMxVWRuZWpNbTJRVUhWR2w3bUlwaEUKLS0tIGZLcGNQRy9LNUF1Y0JzZEZGdXBn
bUJjYlBSSEYwRUpwemlMZ0xCZnpTS2cKUFke27YDeTME9OBgEcQdbJ3jsDZS43km
tK61kLMcexq3lXQb30gx4fzMuYa0MXFygawscTnxTrOrXUd36Iga4A==
-----END AGE ENCRYPTED FILE-----
"""
users.upsmon.password = """\
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzdnZMZ1F1d05RRi84ZENT
SmdRNkFLWDRPMTBFTkdOdFRBMC8xK0gvUVV3CjRhWVljR3ZiU1hwWEJBN2hCcTM1
NWFQWWdmVm1XK1pKUHFnRjJjYXdDNjgKLS0tIERvOU44ellHdGZYVXRDMHN4NkpV
QkhtVlVQUS96UStlQWo2QWJISUlGL2cKc8AC3UujJMIafbV31pjAzniqSHBNwYDw
zhh094auKibUcg6Tbyc=
-----END AGE ENCRYPTED FILE-----
"""
}

View File

@ -1,23 +0,0 @@
import pkg.nut
nut: nut.Nut {
users.dustin.password = """\
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsdWVBOXZpb3Fxclk4SGJv
N0JyeEthbzFOTzQySkw3cWZhSEk2Y1poWm0wCnhEVkJhWWtIdUxnVUFSa2xCSDNU
ak1rbzhaUzN1WUxKajdvOTluVGJwaGsKLS0tIG9NL3ZrMDY5Z2prMnNWL003cDRP
T3NySlREeXZoaVhGcUdTVmxrYXNyUDQKXFvRM9INd7E334OOYlFgp9pu1w4b3szL
yVmqMxpy98iHA6HKvuw=
-----END AGE ENCRYPTED FILE-----
"""
users.upsmon.password = """\
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzdnZMZ1F1d05RRi84ZENT
SmdRNkFLWDRPMTBFTkdOdFRBMC8xK0gvUVV3CjRhWVljR3ZiU1hwWEJBN2hCcTM1
NWFQWWdmVm1XK1pKUHFnRjJjYXdDNjgKLS0tIERvOU44ellHdGZYVXRDMHN4NkpV
QkhtVlVQUS96UStlQWo2QWJISUlGL2cKc8AC3UujJMIafbV31pjAzniqSHBNwYDw
zhh094auKibUcg6Tbyc=
-----END AGE ENCRYPTED FILE-----
"""
}

View File

@ -24,10 +24,10 @@ curl -fsSL \
-o keys.age -o keys.age
age -d -i "${SSH_KEY}" -o keys.txt keys.age age -d -i "${SSH_KEY}" -o keys.txt keys.age
if [ -f host/"${HOSTNAME}".k ] && [ -f host/"${HOSTNAME}".yaml ]; then if [ -f host/"${HOSTNAME}".cue ] && [ -f instructions/"${HOSTNAME}".cue ]; then
kcl run host/"${HOSTNAME}".k -o instructions.yaml || exit cue export host/"${HOSTNAME}".cue -o values.json || exit
kcl run -Y host/"${HOSTNAME}".yaml -o values.yaml || exit cue export instructions/"${HOSTNAME}".cue -o instructions.json || exit
tmpl instructions.yaml values.yaml -d "${DESTDIR}" || exit tmpl instructions.json values.json -d "${DESTDIR}" || exit
fi fi
if [ -f host/"${HOSTNAME}".post.sh ]; then if [ -f host/"${HOSTNAME}".post.sh ]; then

1
cue.mod/module.cue Normal file
View File

@ -0,0 +1 @@
module: "du5t1n.me/cfg"

57
env/prod/nut.cue vendored Normal file
View File

@ -0,0 +1,57 @@
package prod
import n "du5t1n.me/cfg/schema/app/nut"
nut: n.#Nut & {
listen: {
address: "::"
port: 3493
}
ups: {
apc1500: {
driver: "usbhid-ups"
port: "auto"
desc: "Back-UPS XS 1500G"
vendorid: "051d"
product: ".*1500M.*"
pollonly: "enabled"
pollinterval: 1
}
apc1300: {
driver: "usbhid-ups"
port: "auto"
desc: "Back-UPS XS 1300G"
vendorid: "051d"
product: ".*1300G.*"
pollonly: "enabled"
pollinterval: 1
}
}
users: {
homeassistant: {
password: """
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxZUZleGt3emxXdDFtcEtN
Wll3K0hrS2c2M1oyMWh2VlBnMER0bkZOb1VVCnU3aTI2eVJoV1dKNWxEd0VnbVNa
NlMxVWRuZWpNbTJRVUhWR2w3bUlwaEUKLS0tIGZLcGNQRy9LNUF1Y0JzZEZGdXBn
bUJjYlBSSEYwRUpwemlMZ0xCZnpTS2cKUFke27YDeTME9OBgEcQdbJ3jsDZS43km
tK61kLMcexq3lXQb30gx4fzMuYa0MXFygawscTnxTrOrXUd36Iga4A==
-----END AGE ENCRYPTED FILE-----
"""
}
upsmon: {
upsmon: "primary"
password: """
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzdnZMZ1F1d05RRi84ZENT
SmdRNkFLWDRPMTBFTkdOdFRBMC8xK0gvUVV3CjRhWVljR3ZiU1hwWEJBN2hCcTM1
NWFQWWdmVm1XK1pKUHFnRjJjYXdDNjgKLS0tIERvOU44ellHdGZYVXRDMHN4NkpV
QkhtVlVQUS96UStlQWo2QWJISUlGL2cKc8AC3UujJMIafbV31pjAzniqSHBNwYDw
zhh094auKibUcg6Tbyc=
-----END AGE ENCRYPTED FILE-----
"""
}
}
}

40
env/test/nut.cue vendored Normal file
View File

@ -0,0 +1,40 @@
package test
import n "du5t1n.me/cfg/schema/app/nut"
nut: n.#Nut & {
listen: {
address: "::"
port: 3493
}
ups: {
nutdev1: {
driver: "usbhid-ups"
port: "auto"
vendorid: "051D"
productid: "0002"
product: "Back-UPS NS 1100M2 FW:953.e3 .D USB FW:e3"
serial: "3B1810X28282"
vendor: "American Power Conversion"
bus: "001"
}
}
users: {
dustin: {
actions: ["set", "fsd"]
instcmds: ["all"]
password: """
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnZFN1b095cVk3UnhDdEc2
T2RTK0hpSVhGcHhNLzFMZGRvRWRlOERaY3hvCjVvNkxZeXVLcldFRnUwMzdNNU4r
eTRYNlFZeGhNWlo5cTdHR2ExNUpBaTgKLS0tIE5DNlp0cVgvMWRXcWd3cFR5akJm
a3FnMEhZRHpGMWZicUpqMklQcGtxaEkK82i19a+P++lx2dDJ+SBgNkqOg+7GiSPH
gI96Syl48P7V2PP3R6c=
-----END AGE ENCRYPTED FILE-----
"""
}
}
}

View File

@ -0,0 +1,7 @@
package nut0
import (
"du5t1n.me/cfg/env/test"
)
nut: test.nut

View File

@ -1,5 +0,0 @@
import pkg
import pkg.nut
import pkg.common
render: [pkg.RenderInstruction] = common.templates + nut.templates

View File

@ -1,4 +0,0 @@
kcl_cli_configs:
files:
- ../app/nut/base
- ../app/nut/test

View File

@ -0,0 +1,7 @@
package nut0
import (
"du5t1n.me/cfg/schema/app/nut"
)
render: nut.templates

View File

@ -1,5 +0,0 @@
[package]
name = "dch"
edition = "0.0.1"
version = "0.0.1"

View File

View File

@ -1,21 +0,0 @@
templates = [
{
template = "common/reload-udev-rules.path"
dest = "/etc/systemd/system/reload-udev-rules.path"
hooks = {
changed = [
{run = "systemctl daemon-reload"}
{run = "systemctl try-restart reload-udev-rules.path"}
]
}
}
{
template = "common/reload-udev-rules.service"
dest = "/etc/systemd/system/reload-udev-rules.service"
hooks = {
changed = [
{run = "systemctl daemon-reload"}
]
}
}
]

View File

@ -1,17 +0,0 @@
schema Hook:
run: str
immediate?: bool
schema Hooks:
changed?: [Hook]
schema RenderInstruction:
template: str
dest: str
owner?: str
group?: str
mode?: str
hooks?: Hooks
schema Instructions:
render: [RenderInstruction]

View File

@ -1,35 +0,0 @@
schema Ups:
"""Schema for ups.conf sections"""
driver: str
port?: str
desc?: str
vendorid?: str
productid?: str
product?: str
serial?: str
vendor?: str
bus?: str
pollonly?: "enabled" | "disabled"
pollinterval?: int
schema User:
"""Schema for upsd.users sections"""
password: str
actions?: [str]
instcmds?: [str]
upsmon?: "primary" | "secondary"
schema Listen:
"""Schema for listen socket address"""
address: str = "::"
port: int = 3493
schema Nut:
"""Schema for NUT configuration"""
listen = Listen()
ups: {str:Ups} = {}
users: {str:User} = {}

View File

@ -1,49 +0,0 @@
templates = [
{
template = "nut/nut.sysusers"
dest = "/etc/sysusers.d/nut.conf"
hooks = {
changed = [{
run = "systemd-sysusers /etc/sysusers.d/nut.conf"
immediate = True
}]
}
}
{
template = "nut/ups.conf"
dest = "/etc/ups/ups.conf"
hooks = {
changed = [{run = "systemctl try-restart nut-server"}]
}
}
{
template = "nut/upsd.conf"
dest = "/etc/ups/upsd.conf"
owner = "root"
group = "nut"
mode = "u=rw,g=r,o="
hooks = {
changed = [{run = "systemctl try-reload-or-restart nut-server"}]
}
}
{
template = "nut/upsd.users"
dest = "/etc/ups/upsd.users"
owner = "root"
group = "nut"
mode = "u=rw,g=r,o="
hooks = {
changed = [{run = "systemctl try-reload-or-restart nut-server"}]
}
}
{
template = "nut/nut-server.container"
dest = "/etc/containers/systemd/nut-server.container"
hooks = {
changed = [
{run = "systemctl daemon-reload"}
{run = "systemctl restart nut-server"}
]
}
}
]

33
schema/app/nut/nut.cue Normal file
View File

@ -0,0 +1,33 @@
package nut
#Ups: {
driver: string
port?: string
desc?: string
vendorid?: string
productid?: string
product?: string
serial?: string
vendor?: string
bus?: string
pollonly?: "enabled" | "disabled"
pollinterval?: int
}
#User: {
password: string
actions?: [string, ...]
instcmds?: [string, ...]
upsmon?: "primary" | "secondary"
}
#Listen: {
address: string
port: int
}
#Nut: {
listen: #Listen
ups: [string]: #Ups
users: [string]: #User
}

View File

@ -0,0 +1,51 @@
package nut
templates: [
{
template: "nut/nut.sysusers"
dest: "/etc/sysusers.d/nut.conf"
hooks: {
changed: [{
run: "systemd-sysusers /etc/sysusers.d/nut.conf"
immediate: true
}]
}
},
{
template: "nut/ups.conf"
dest: "/etc/ups/ups.conf"
hooks: {
changed: [{run: "systemctl try-restart nut-server"}]
}
},
{
template: "nut/upsd.conf"
dest: "/etc/ups/upsd.conf"
owner: "root"
group: "nut"
mode: "u=rw,g=r,o="
hooks: {
changed: [{run: "systemctl try-reload-or-restart nut-server"}]
}
},
{
template: "nut/upsd.users"
dest: "/etc/ups/upsd.users"
owner: "root"
group: "nut"
mode: "u=rw,g=r,o="
hooks: {
changed: [{run: "systemctl try-reload-or-restart nut-server"}]
}
},
{
template: "nut/nut-server.container"
dest: "/etc/containers/systemd/nut-server.container"
hooks: {
changed: [
{run: "systemctl daemon-reload", immediate: true},
{run: "systemctl restart nut-server"},
]
}
},
]

View File

@ -0,0 +1,23 @@
package instructions
#Hook: {
run: string
immediate?: bool
}
#Hooks: {
changed?: [#Hook]
}
#RenderInstruction: {
template: string
dest: string
owner?: string
group?: string
mode?: string
hooks?: #Hooks
}
#Instructions: {
render: [#RenderInstruction]
}