Install and configure Scriptorium

This commit is contained in:
2026-09-09 14:36:55 +00:00
parent 2cd69a4b2e
commit 90d7f45eaa
11 changed files with 381 additions and 2 deletions

View File

@@ -1,3 +1,78 @@
# ansible-role-scriptorium
# Ansible Role: Scriptorium
Installs and configures [Scriptorium](https://gitea.maximumdirect.net/eric/scriptorium)
from source on Ubuntu 26.04 (`x86_64` or `aarch64`). The role requires gathered
facts and uses privilege escalation only for dependency installation and
root-owned application state.
## Variables
```yaml
scriptorium_version: v0.13.0
scriptorium_repository_url: https://gitea.maximumdirect.net/eric/scriptorium.git
scriptorium_source_dir: /usr/local/src/scriptorium
scriptorium_binary_path: /usr/local/bin/scriptorium
scriptorium_manage_build_dependencies: true
scriptorium_go_snap_channel: 1.26/stable
scriptorium_go_binary: /snap/bin/go
scriptorium_config_path: /usr/local/etc/scriptorium/config.yml
scriptorium_prompt_dir: null
scriptorium_profile_dir: null
scriptorium_schema_dir: null
scriptorium_server_addr: null
scriptorium_server_artifact_root: null
scriptorium_server_max_request_bytes: null
scriptorium_server_max_artifact_bytes: null
scriptorium_server_max_response_bytes: null
scriptorium_defaults_render_format: null
scriptorium_backends: null
```
When `scriptorium_manage_build_dependencies` is `false`, Git and the executable
selected by `scriptorium_go_binary` must already be available. Otherwise, the
role installs Git with APT and the classic Go Snap from
`scriptorium_go_snap_channel`. It checks out `scriptorium_version`
authoritatively, builds `./cmd/scriptorium` directly to
`scriptorium_binary_path`, and rebuilds only when that checkout changes or the
binary is absent. The source tree is recursively owned by `root:root`; the
binary is `root:root` mode `0755`.
Every application configuration variable defaults to `null`, so it and any
empty parent section are omitted and Scriptorium retains ownership of its
built-in defaults. When no values are selected, the role writes `{}` to
`scriptorium_config_path` as `root:root` mode `0644`. Non-null falsey values
are retained. `scriptorium_backends` is the native mapping supplied by the
caller; the role does not merge or rewrite it. For example, inventory can
compose fragments in the ordinary way:
```yaml
scriptorium_backends: "{{ scriptorium_local_backends | combine(scriptorium_remote_backends) }}"
```
Backend entries may name a credential environment variable through
`api_key_env`; credential values themselves belong outside this role. The role
does not install prompt, profile, or schema content and does not manage a
service.
## Example
```yaml
- hosts: scriptorium_hosts
roles:
- role: ansible-role-scriptorium
```
## Validation
The repository-local integration suite installs its pinned collection, runs
syntax checking and production-profile lint, and applies the default role. It
uses an offline native profile inspection with the managed configuration, then
requires clean repeated convergence. It mutates only the managed source,
binary, configuration, and a private temporary directory, then restores the
default deployment during cleanup.
```sh
tests/integration
```
Ansible role to install the Scriptorium application.

2
ansible.cfg Normal file
View File

@@ -0,0 +1,2 @@
[defaults]
roles_path = ..

View File

@@ -0,0 +1,4 @@
---
collections:
- name: community.general
version: 13.3.0

19
defaults/main.yml Normal file
View File

@@ -0,0 +1,19 @@
---
scriptorium_version: v0.13.0
scriptorium_repository_url: https://gitea.maximumdirect.net/eric/scriptorium.git
scriptorium_source_dir: /usr/local/src/scriptorium
scriptorium_binary_path: /usr/local/bin/scriptorium
scriptorium_manage_build_dependencies: true
scriptorium_go_snap_channel: 1.26/stable
scriptorium_go_binary: /snap/bin/go
scriptorium_config_path: /usr/local/etc/scriptorium/config.yml
scriptorium_prompt_dir: null
scriptorium_profile_dir: null
scriptorium_schema_dir: null
scriptorium_server_addr: null
scriptorium_server_artifact_root: null
scriptorium_server_max_request_bytes: null
scriptorium_server_max_artifact_bytes: null
scriptorium_server_max_response_bytes: null
scriptorium_defaults_render_format: null
scriptorium_backends: null

59
meta/argument_specs.yml Normal file
View File

@@ -0,0 +1,59 @@
---
argument_specs:
main:
short_description: Install and configure Scriptorium from source
options:
scriptorium_version:
type: str
required: true
scriptorium_repository_url:
type: str
required: true
scriptorium_source_dir:
type: path
required: true
scriptorium_binary_path:
type: path
required: true
scriptorium_manage_build_dependencies:
type: bool
required: true
scriptorium_go_snap_channel:
type: str
required: true
scriptorium_go_binary:
type: path
required: true
scriptorium_config_path:
type: path
required: true
scriptorium_prompt_dir:
type: path
required: false
scriptorium_profile_dir:
type: path
required: false
scriptorium_schema_dir:
type: path
required: false
scriptorium_server_addr:
type: str
required: false
scriptorium_server_artifact_root:
type: str
required: false
scriptorium_server_max_request_bytes:
type: int
required: false
scriptorium_server_max_artifact_bytes:
type: int
required: false
scriptorium_server_max_response_bytes:
type: int
required: false
scriptorium_defaults_render_format:
type: str
required: false
scriptorium_backends:
type: dict
required: false

18
meta/main.yml Normal file
View File

@@ -0,0 +1,18 @@
---
galaxy_info:
role_name: scriptorium
namespace: rakestraw
author: Eric Rakestraw
description: Install and configure Scriptorium from source
license: CC0-1.0
min_ansible_version: "2.18"
issue_tracker_url: https://gitea.maximumdirect.net/eric/ansible-role-scriptorium/issues
platforms:
- name: Ubuntu
versions:
- resolute
galaxy_tags:
- scriptorium
- promptkit
- processing
dependencies: []

101
tasks/main.yml Normal file
View File

@@ -0,0 +1,101 @@
---
- name: Validate the supported platform
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Ubuntu"
- ansible_facts.distribution_version == "26.04"
- ansible_facts.architecture in ["x86_64", "aarch64"]
fail_msg: Scriptorium supports Ubuntu 26.04 on x86_64 or aarch64.
- name: Install Git
become: true
ansible.builtin.apt:
name: git
state: present
update_cache: true
cache_valid_time: 3600
when: scriptorium_manage_build_dependencies
- name: Install Go
become: true
community.general.snap:
name: go
channel: "{{ scriptorium_go_snap_channel }}"
classic: true
state: present
when: scriptorium_manage_build_dependencies
- name: Create installation parent directories
become: true
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ scriptorium_source_dir | dirname }}"
- "{{ scriptorium_binary_path | dirname }}"
- "{{ scriptorium_config_path | dirname }}"
- name: Check out Scriptorium source
become: true
ansible.builtin.git:
repo: "{{ scriptorium_repository_url }}"
dest: "{{ scriptorium_source_dir }}"
version: "{{ scriptorium_version }}"
force: true
register: scriptorium_checkout
- name: Inspect the installed binary
ansible.builtin.stat:
path: "{{ scriptorium_binary_path }}"
register: scriptorium_binary
- name: Build Scriptorium
become: true
ansible.builtin.command:
argv:
- "{{ scriptorium_go_binary }}"
- build
- -trimpath
- -ldflags
- -s -w
- -o
- "{{ scriptorium_binary_path }}"
- ./cmd/scriptorium
chdir: "{{ scriptorium_source_dir }}"
environment:
CGO_ENABLED: "0"
GOWORK: "off"
GOTOOLCHAIN: local
changed_when: true
when: >-
scriptorium_checkout.changed or
not scriptorium_binary.stat.exists
- name: Converge installed metadata
become: true
ansible.builtin.file:
path: "{{ item.path }}"
state: "{{ item.state }}"
owner: root
group: root
mode: "{{ item.mode | default(omit) }}"
recurse: "{{ item.recurse | default(omit) }}"
loop:
- path: "{{ scriptorium_source_dir }}"
state: directory
recurse: true
- path: "{{ scriptorium_binary_path }}"
state: file
mode: "0755"
- name: Write Scriptorium configuration
become: true
ansible.builtin.template:
src: config.yml.j2
dest: "{{ scriptorium_config_path }}"
owner: root
group: root
mode: "0644"

38
templates/config.yml.j2 Normal file
View File

@@ -0,0 +1,38 @@
{% if scriptorium_prompt_dir is none and scriptorium_profile_dir is none and scriptorium_schema_dir is none and scriptorium_server_addr is none and scriptorium_server_artifact_root is none and scriptorium_server_max_request_bytes is none and scriptorium_server_max_artifact_bytes is none and scriptorium_server_max_response_bytes is none and scriptorium_defaults_render_format is none and scriptorium_backends is none %}
{}
{% else %}
{% if scriptorium_prompt_dir is not none %}
prompt_dir: {{ scriptorium_prompt_dir | to_json }}
{% endif %}
{% if scriptorium_profile_dir is not none %}
profile_dir: {{ scriptorium_profile_dir | to_json }}
{% endif %}
{% if scriptorium_schema_dir is not none %}
schema_dir: {{ scriptorium_schema_dir | to_json }}
{% endif %}
{% if scriptorium_server_addr is not none or scriptorium_server_artifact_root is not none or scriptorium_server_max_request_bytes is not none or scriptorium_server_max_artifact_bytes is not none or scriptorium_server_max_response_bytes is not none %}
server:
{% if scriptorium_server_addr is not none %}
addr: {{ scriptorium_server_addr | to_json }}
{% endif %}
{% if scriptorium_server_artifact_root is not none %}
artifact_root: {{ scriptorium_server_artifact_root | to_json }}
{% endif %}
{% if scriptorium_server_max_request_bytes is not none %}
max_request_bytes: {{ scriptorium_server_max_request_bytes | to_json }}
{% endif %}
{% if scriptorium_server_max_artifact_bytes is not none %}
max_artifact_bytes: {{ scriptorium_server_max_artifact_bytes | to_json }}
{% endif %}
{% if scriptorium_server_max_response_bytes is not none %}
max_response_bytes: {{ scriptorium_server_max_response_bytes | to_json }}
{% endif %}
{% endif %}
{% if scriptorium_defaults_render_format is not none %}
defaults:
render_format: {{ scriptorium_defaults_render_format | to_json }}
{% endif %}
{% if scriptorium_backends is not none %}
backends: {{ scriptorium_backends | to_json }}
{% endif %}
{% endif %}

6
tests/converge.yml Normal file
View File

@@ -0,0 +1,6 @@
---
- name: Converge Scriptorium role
hosts: localhost
gather_facts: true
roles:
- role: ansible-role-scriptorium

56
tests/integration Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
workspace_venv="$repository_root/../.venv"
test_tmp=$(mktemp -d)
chmod 0700 "$test_tmp"
export ANSIBLE_COLLECTIONS_PATH="$test_tmp/collections"
export ANSIBLE_ROLES_PATH="$(dirname "$repository_root")"
inventory="$repository_root/tests/inventory"
converge="$repository_root/tests/converge.yml"
ansible_playbook="$workspace_venv/bin/ansible-playbook"
binary_path=/usr/local/bin/scriptorium
config_path=/usr/local/etc/scriptorium/config.yml
profile_dir="$test_tmp/profiles"
cleanup() {
exit_status=$?
trap - EXIT
"$ansible_playbook" -i "$inventory" "$converge" >/dev/null 2>&1 || true
rm -rf -- "$test_tmp"
exit "$exit_status"
}
trap cleanup EXIT
require_clean_recap() {
grep -Eq 'changed=0 .*failed=0' "$1"
}
run_converge() {
output_file=$1
"$ansible_playbook" -i "$inventory" "$converge" | tee "$output_file"
}
mkdir -p "$profile_dir"
printf '%s\n' \
'id: endpoint-profile' \
'endpoint: http://127.0.0.1:8000/v1' \
'model: offline-fixture' > "$profile_dir/endpoint-profile.yml"
cd "$repository_root"
"$workspace_venv/bin/ansible-galaxy" collection install \
-r collections/requirements.yml \
-p "$ANSIBLE_COLLECTIONS_PATH"
"$ansible_playbook" -i "$inventory" --syntax-check "$converge"
"$workspace_venv/bin/ansible-lint" "$converge"
baseline_log="$test_tmp/baseline.log"
run_converge "$baseline_log"
"$binary_path" inspect profile --config "$config_path" \
--profile-dir "$profile_dir" --profile endpoint-profile
baseline_repeat_log="$test_tmp/baseline-repeat.log"
run_converge "$baseline_repeat_log"
require_clean_recap "$baseline_repeat_log"

1
tests/inventory Normal file
View File

@@ -0,0 +1 @@
localhost ansible_connection=local