diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index c7a610a..82ec8e1 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -79,6 +79,7 @@ ** xref:args/input_textarea.adoc[Input: Textarea] ** xref:args/suggestions.adoc[Suggestions] ** xref:args/env.adoc[Environment Variables] +** xref:args/templates.adoc[Templates] * xref:dashboards/intro.adoc[Dashboards] ** xref:dashboards/examples.adoc[Examples] ** xref:dashboards/actions.adoc[Actions (Linked)] diff --git a/docs/modules/ROOT/pages/advanced_configuration/config_envs.adoc b/docs/modules/ROOT/pages/advanced_configuration/config_envs.adoc index 296a2b7..5fb6de5 100644 --- a/docs/modules/ROOT/pages/advanced_configuration/config_envs.adoc +++ b/docs/modules/ROOT/pages/advanced_configuration/config_envs.adoc @@ -49,6 +49,6 @@ actions: shellAfterCompleted: "apprise -t 'Backup on {{ .Env.HOSTNAME }}' -b '{{ output }}'" ---- -`.Env` uses the same Go template context as other action variables (e.g. `.Arguments`, `.CurrentEntity`, `.OliveTin`). The map is built from the process environment when OliveTin starts; values are read at template execution time. If a variable is missing, the template engine will report a missing-key error (with `missingkey=error`), so use defaulting when a variable might be unset, e.g. `{{ or .Env.OPTIONAL_VAR "default" }}`. +`.Env` uses the same Go template context as other action variables (e.g. `.Arguments`, `.CurrentEntity`, `.OliveTin`). The map is built from the process environment when OliveTin starts; values are read at template execution time. If a variable is missing, the template engine will report a missing-key error (with `missingkey=error`), so use defaulting when a variable might be unset, e.g. `{{ or .Env.OPTIONAL_VAR "default" }}`. For template functions such as JSON encoding, see xref:args/templates.adoc#json-encoding[JSON encoding with Json]. This feature addresses the need to use environment variables in templates without changing the config loader (see link:https://github.com/OliveTin/OliveTin/issues/840[GitHub issue #840]). diff --git a/docs/modules/ROOT/pages/args/env.adoc b/docs/modules/ROOT/pages/args/env.adoc index e36d27c..65c7f75 100644 --- a/docs/modules/ROOT/pages/args/env.adoc +++ b/docs/modules/ROOT/pages/args/env.adoc @@ -85,11 +85,11 @@ fi To use the *process* environment (the environment OliveTin was started with) inside action template fields such as `shell` or `shellAfterCompleted`, use the `.Env` template variable: `{{ .Env.VAR_NAME }}`. This substitutes the value at execution time. See xref:advanced_configuration/config_envs.adoc#using-env-in-template-replacements[Using .Env in template replacements] for details and examples. +For other template features, including JSON encoding of argument and entity values, see xref:args/templates.adoc[Templates in actions]. + == Notes . Argument names are converted to uppercase for environment variables, `name: filename` becomes `FILENAME`. . OliveTin sets `OLIVETIN=1` in the process environment for every action; see <> above. . The execution request variables are exposed as `OT_USERNAME` and `OT_EXECUTIONTRACKINGID` in the process environment; see <> above. . The environment variables are passed into the execution context which uses a shell (/bin/sh on Linux), so it is also possible to use them with the $ notation in the `shell` line, like this; `shell: echo $FILENAME` for example. - - diff --git a/docs/modules/ROOT/pages/args/intro.adoc b/docs/modules/ROOT/pages/args/intro.adoc index 5945296..e8495a9 100644 --- a/docs/modules/ROOT/pages/args/intro.adoc +++ b/docs/modules/ROOT/pages/args/intro.adoc @@ -3,7 +3,7 @@ Actions and commands that OliveTin runs, without arguments, are generally quite safe - only that command can be run, without modifications. However, many users need the flexibility to set options on that command - normally called command line arguments. In OliveTin, arguments are defined in a shell commands like `echo {{ message }}`, with a bit of extra configuration. -Examples of valid argument names are `{{ personName }}`, `{{ customer_number }}` and `{{ ISBN11_code }}`. +Examples of valid argument names are `{{ personName }}`, `{{ customer_number }}` and `{{ ISBN11_code }}`. * a-z (case insensitive) * _ is allowed @@ -19,5 +19,5 @@ Now that you understand how arguments work, explore the different argument types * xref:args/suggestions.adoc[Argument suggestions] - Add dynamic suggestions to help users * xref:args/regex.adoc[Input validation with regex] - Validate user input with regular expressions * xref:args/env.adoc[Environment variables] - Use arguments to set environment variables +* xref:args/templates.adoc[Templates] - Use Go templates in actions, including JSON encoding * xref:action_examples/intro.adoc[See examples] - View real-world examples using arguments - diff --git a/docs/modules/ROOT/pages/args/templates.adoc b/docs/modules/ROOT/pages/args/templates.adoc new file mode 100644 index 0000000..6f99513 --- /dev/null +++ b/docs/modules/ROOT/pages/args/templates.adoc @@ -0,0 +1,70 @@ +[#templates] += Templates in actions + +OliveTin uses https://pkg.go.dev/text/template[Go text/template] syntax in action fields such as `shell`, `shellAfterCompleted`, entity directory titles, and `enabledExpression`. Template placeholders are written as `{{ ... }}`. + +In OliveTin 3k, use dotted names for template context variables: + +* `{{ .Arguments.NAME }}` — argument values (see xref:args/env.adoc[Environment variables]) +* `{{ .CurrentEntity.property }}` — entity properties (see xref:entities/intro.adoc[Entities]) +* `{{ .Env.VAR_NAME }}` — process environment (see xref:advanced_configuration/config_envs.adoc#using-env-in-template-replacements[Using .Env in template replacements]) +* `{{ .OliveTin.Build.Version }}` and related build/runtime fields + +In OliveTin 2k, argument and execution-request placeholders used the shorter form (for example, `{{ message }}` instead of `{{ .Arguments.message }}`). + +[#json-encoding] +== JSON encoding with `Json` + +The `Json` template function encodes a value as a JSON string. Pipe a template value to it when you need structured data in a command — for example, passing argument or entity state to a script or HTTP client that expects JSON. + +[source,yaml] +---- +actions: + - title: curl my knx thing + shell: curl --json https://knx.example.com/v1/group/global_on/write -d '{{ .Arguments | Json }}' + entity: light + arguments: + - name: value + default: "true" +---- + +After template substitution, `{{ .Arguments | Json }}` becomes a JSON object containing all argument names and values for that execution (including execution-request variables such as `ot_username` and `ot_executionTrackingId`). + +=== Examples + +Encode a single argument value: + +[source,yaml] +---- +shell: echo {{ .Arguments.value | Json }} +---- + +If `value` is `hello`, the substituted command is `echo "hello"`. + +Encode an entity field: + +[source,yaml] +---- +shell: curl -d {{ .CurrentEntity.foo.bar | Json }} +---- + +If `foo.bar` is the string `baz`, the substituted command is `curl -d "baz"`. + +Encode a nested entity object: + +[source,yaml] +---- +shell: curl --json -d {{ .CurrentEntity.payload | Json }} +---- + +If `payload` is `{on: true}`, the substituted command is `curl --json -d {"on":true}`. + +=== Notes + +. `Json` uses Go's `encoding/json` package. Strings, numbers, booleans, objects, and arrays are encoded according to normal JSON rules. +. Argument values in templates are strings (`map[string]string`). A checkbox or boolean argument therefore appears in JSON as a string (for example, `"true"`), not a JSON boolean. +. If the piped value is missing or nil, `Json` produces `null`. +. When embedding JSON in a shell command, quote the substitution if the JSON may contain spaces or shell metacharacters. Prefer single-quoted YAML strings around the template when possible, as shown in the curl example above. +. For HTTP request bodies, pass one JSON-encoded value (or build the JSON structure you need in one template expression). Piping several values with spaces between them does not produce a single valid JSON document. + +See link:https://github.com/OliveTin/OliveTin/issues/829[GitHub issue #829] for the original feature request. diff --git a/docs/modules/dev/pages/signing.adoc b/docs/modules/dev/pages/signing.adoc new file mode 100644 index 0000000..a00ab7f --- /dev/null +++ b/docs/modules/dev/pages/signing.adoc @@ -0,0 +1,78 @@ +# macOS release signing + +Release builds can sign and notarize the `darwin` binaries using [quill](https://github.com/anchore/quill) via GoReleaser. This runs on the existing Linux CI runner; no macOS runner is required. + +Signing is **optional**. If the GitHub secrets below are not all set, GoReleaser skips macOS signing and publishes unsigned binaries (the previous behaviour). + +## Prerequisites + +- An active [Apple Developer Program](https://developer.apple.com/programs/) membership. +- A **Developer ID Application** certificate (not "Apple Development" or "Mac App Distribution"). +- An [App Store Connect API key](https://appstoreconnect.apple.com/access/integrations/api) with at least **Developer** access. + +## One-time setup + +### 1. Create the signing certificate + +1. Open [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list). +2. Create a certificate of type **Developer ID Application**. +3. Download the `.cer` file and double-click it to add it to **Keychain Access** on a Mac. +4. In Keychain Access, export the certificate as a **Personal Information Exchange (`.p12`)** file. You will set an export password — remember it; this becomes `MACOS_SIGN_PASSWORD`. + +### 2. Create the notarization API key + +1. Open [App Store Connect → Users and Access → Integrations → App Store Connect API](https://appstoreconnect.apple.com/access/integrations/api). +2. Create a key with **Developer** role (or Admin). +3. Download the `.p8` file once (it cannot be downloaded again). Note the **Key ID** shown in the portal and the **Issuer ID** at the top of the API keys page. + +### 3. Base64-encode the key files + +Run on a machine that has the files (Linux or macOS): + +```sh +base64 -w0 < ./Certificates.p12 # MACOS_SIGN_P12 +base64 -w0 < ./AuthKey_XXXXXX.p8 # MACOS_NOTARY_KEY +``` + +On macOS without GNU coreutils, use `base64 -i file | tr -d '\n'`. + +### 4. Add GitHub repository secrets + +In **Settings → Secrets and variables → Actions**, create: + +| Secret | Value | +|--------|-------| +| `MACOS_SIGN_P12` | Base64 contents of the `.p12` file | +| `MACOS_SIGN_PASSWORD` | Password used when exporting the `.p12` | +| `MACOS_NOTARY_KEY` | Base64 contents of the `.p8` file | +| `MACOS_NOTARY_KEY_ID` | Key ID from App Store Connect (e.g. `ABC123DEF4`) | +| `MACOS_NOTARY_ISSUER_ID` | Issuer UUID from App Store Connect | + +All five must be present for signing to run. Any missing secret disables signing for that release. + +## Renewal + +| Item | Typical lifetime | What to do | +|------|------------------|------------| +| Developer ID Application certificate | ~5 years | Create a new certificate in the Apple portal, export a new `.p12`, update `MACOS_SIGN_P12` and `MACOS_SIGN_PASSWORD`. | +| App Store Connect API key | Does not expire, but can be revoked | Create a new key if compromised or lost; update `MACOS_NOTARY_KEY`, `MACOS_NOTARY_KEY_ID`, and optionally `MACOS_NOTARY_ISSUER_ID`. | +| Apple Developer Program | Annual subscription | Renew membership before it lapses; existing certificates stop working if the account is inactive. | + +After updating secrets, the next release on `main` (via semantic-release) will use the new credentials automatically. + +## Verifying a signed release + +On a Mac, download a `OliveTin-darwin-*.tar.gz` release artifact and run: + +```sh +tar -xzf OliveTin-darwin-arm64.tar.gz +spctl -a -vv -t execute OliveTin-darwin-arm64/OliveTin +``` + +A signed and notarized binary should report `accepted` with `source=Notarized Developer ID`. + +## Configuration reference + +- GoReleaser: `notarize.macos` in [`.goreleaser.yml`](.goreleaser.yml) +- CI secrets: [`.github/workflows/build-and-release.yml`](.github/workflows/build-and-release.yml) (`release` step) +- [GoReleaser notarization docs](https://goreleaser.com/customization/notarize/)