Compare commits
245 Commits
feature/as
...
265d3f8fd6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
265d3f8fd6 | ||
|
|
b61d19cb91 | ||
|
|
00be18b1f1 | ||
|
|
6c7ec507ef | ||
|
|
63b0bc72fe | ||
|
|
02af5d26dc | ||
|
|
3eb38b74bd | ||
|
|
2b95acb8cc | ||
|
|
62e9f13a45 | ||
|
|
4cb87a57ad | ||
|
|
d2eaddfd11 | ||
|
|
0e741aab38 | ||
|
|
9ebd19ab52 | ||
|
|
e47cbf2044 | ||
|
|
ce632e88b9 | ||
|
|
11d8796764 | ||
|
|
d974c75d7c | ||
|
|
a5433dcb5b | ||
|
|
e0eb47f5ce | ||
|
|
a8822f0778 | ||
| bedf87b492 | |||
|
|
b6f7791c98 | ||
|
|
1a48e60afd | ||
|
|
c26b19793b | ||
|
|
dfe81a5c20 | ||
|
|
4afb05e56b | ||
|
|
46b49259d2 | ||
|
|
3f3ce68e18 | ||
|
|
e44805c9b6 | ||
|
|
1aa6b4234a | ||
|
|
4cde540e70 | ||
|
|
69fb5f5641 | ||
|
|
430552a0b1 | ||
|
|
18bb111843 | ||
|
|
712425ee17 | ||
|
|
1d77821e5f | ||
|
|
7ad40bb509 | ||
|
|
3950a2b069 | ||
|
|
d20fd80798 | ||
|
|
308ee553c3 | ||
|
|
56110d52bd | ||
|
|
1f07fdff45 | ||
|
|
317816558d | ||
|
|
ea22e4e407 | ||
|
|
490c483924 | ||
|
|
bc34a1f915 | ||
|
|
64e690737e | ||
|
|
0693fdcd26 | ||
| 19a807899f | |||
| 5bacf9fbca | |||
| 1da9bfd43c | |||
| 0bc9b2e788 | |||
| dcfb6825e8 | |||
| 0b9ac4dc74 | |||
|
|
aabf758c91 | ||
|
|
489b8aeb35 | ||
|
|
002d6799b1 | ||
|
|
150cef1aca | ||
|
|
a30ad99ee4 | ||
|
|
d2b6d95a49 | ||
|
|
adc415e95a | ||
|
|
e8303d5129 | ||
|
|
f37021346b | ||
|
|
5a99928c6f | ||
|
|
59c83c296b | ||
|
|
b6cb031edb | ||
|
|
cb5ffc16d8 | ||
|
|
f375c9567f | ||
|
|
f0400c02b6 | ||
|
|
d1d7331238 | ||
|
|
459dbc5d18 | ||
| a4a68eeb5a | |||
|
|
99958979d6 | ||
|
|
0e4d229df2 | ||
|
|
53883108d8 | ||
|
|
fcbf6ce092 | ||
|
|
cf21863b0f | ||
|
|
653a923fa8 | ||
|
|
13c819e66c | ||
|
|
28a653b203 | ||
|
|
cf7ab7fbe6 | ||
|
|
12d0a75b3a | ||
|
|
668e86d7c2 | ||
| 0fb593a313 | |||
|
|
b5dc130207 | ||
|
|
0efa1e5125 | ||
|
|
34e05725dd | ||
|
|
b6b1bee25c | ||
|
|
3b3461fd3c | ||
|
|
7cbed63c92 | ||
|
|
a008e766f2 | ||
|
|
543394a825 | ||
|
|
23d6d75117 | ||
|
|
86433a58d0 | ||
|
|
3344e24a48 | ||
|
|
2621bc9f36 | ||
|
|
72de87c8c4 | ||
| 0ef9703757 | |||
|
|
d77d213d89 | ||
|
|
e793794fdd | ||
|
|
d1ae5ba7a0 | ||
|
|
84e30c8ee2 | ||
|
|
644128cd3f | ||
|
|
6912f5c55d | ||
|
|
fc0e39b9c7 | ||
|
|
8627b00ed8 | ||
|
|
0212f0fdd2 | ||
|
|
edfe594e7e | ||
|
|
791f13fca5 | ||
|
|
c3248fde1f | ||
|
|
c137ea0881 | ||
|
|
818b6505dd | ||
|
|
4a1958876f | ||
|
|
6bdb536848 | ||
|
|
6023ee25e1 | ||
|
|
e5d24f557a | ||
|
|
f8e137b67b | ||
|
|
76241e75a8 | ||
|
|
d5ce6ff96a | ||
|
|
44b1a2fb33 | ||
|
|
7dc1999928 | ||
|
|
63d480927d | ||
|
|
fa86fa4c9c | ||
|
|
444b597ade | ||
|
|
c81a9b7704 | ||
|
|
6cab6519b1 | ||
|
|
ce992ca743 | ||
|
|
092d1ac209 | ||
|
|
6acf2f9944 | ||
|
|
8d18f42b2e | ||
|
|
152f10ed8b | ||
|
|
d99ebca829 | ||
| df91305e13 | |||
|
|
6f2b6e0290 | ||
|
|
fc34833472 | ||
|
|
7f37211a8b | ||
|
|
ccc956f70b | ||
|
|
511f32e521 | ||
|
|
87a3e84f5b | ||
|
|
1743145e9f | ||
|
|
d561ac6e04 | ||
|
|
99bc31dee9 | ||
|
|
ac8e7acbd4 | ||
|
|
0ad5dbe741 | ||
|
|
7d9b054340 | ||
|
|
4b1e8a7cac | ||
|
|
8f190eb188 | ||
|
|
95ae6919b0 | ||
|
|
52e97f3a7c | ||
|
|
461aa1bc54 | ||
|
|
c38461a6e8 | ||
|
|
6bcb6fa93f | ||
|
|
f4181349f8 | ||
|
|
9d860367cc | ||
|
|
f654c59dd5 | ||
|
|
d22ac5cef2 | ||
|
|
0fd69e0b90 | ||
|
|
e8d87ff092 | ||
|
|
23612a38f2 | ||
|
|
bd100c15e7 | ||
|
|
dc5392446c | ||
|
|
f8cf139b10 | ||
|
|
ac955d327f | ||
|
|
9e6339037a | ||
|
|
b647f6afee | ||
|
|
aabb3d5009 | ||
|
|
4ed64ab91c | ||
|
|
f57e0bef02 | ||
|
|
9ed7466fd8 | ||
|
|
4d7766d1b1 | ||
|
|
09ae954085 | ||
|
|
8fd9fd5b20 | ||
|
|
dcb764eca7 | ||
|
|
b6a4ad6816 | ||
|
|
bbaaf655fa | ||
|
|
7228dc6e11 | ||
|
|
8953702608 | ||
|
|
0be33cb8db | ||
|
|
0f0b5db29b | ||
|
|
009f244739 | ||
|
|
2f87039f17 | ||
|
|
72fa38e928 | ||
|
|
9e68802090 | ||
|
|
d05cfcf317 | ||
|
|
80f810fb0c | ||
|
|
9153324795 | ||
|
|
91b5817e5f | ||
|
|
1dfa7889ab | ||
|
|
08d7da0c35 | ||
|
|
9ebeb42023 | ||
|
|
075f34b1fb | ||
|
|
210c89c2c7 | ||
|
|
b93a6e50ab | ||
|
|
85cc1f8c6a | ||
|
|
8e781b0c54 | ||
|
|
d8ad35b8e9 | ||
|
|
a9973d1e0f | ||
| 4113011f63 | |||
|
|
018782d986 | ||
|
|
3681e8c03e | ||
|
|
8f377e4cf3 | ||
|
|
419acaa40d | ||
|
|
42b204bf8a | ||
|
|
1d7dcb7d82 | ||
|
|
d63ca0b4f9 | ||
|
|
e9440327aa | ||
|
|
dcc7e282c7 | ||
|
|
6d5fc7c5c6 | ||
| 312fdf9986 | |||
|
|
4e0b4fa049 | ||
|
|
cf7c2a1436 | ||
|
|
3dc6555ad1 | ||
|
|
27ff9286b4 | ||
|
|
6e50461999 | ||
|
|
3f1c3a40cf | ||
|
|
0116ec4cc3 | ||
|
|
f962d0a6d7 | ||
|
|
dc3c0d7cb1 | ||
|
|
b05f9fad09 | ||
|
|
8650995926 | ||
|
|
e5469d2cb8 | ||
|
|
702698ddcd | ||
|
|
d233d582d4 | ||
|
|
3839fac162 | ||
|
|
b01dac85da | ||
|
|
37a49824d0 | ||
|
|
0127016ab2 | ||
|
|
e0b6fcb24a | ||
|
|
e7d9a8fec5 | ||
|
|
1a9addc537 | ||
|
|
401f25b1c4 | ||
|
|
ece522074e | ||
|
|
c30c0074f1 | ||
|
|
fa51dc2c4d | ||
|
|
c1810fde8a | ||
| a781ef8b14 | |||
|
|
92b2a9d609 | ||
|
|
9250b0f193 | ||
| 24869f47ee | |||
| bb5a57e909 | |||
| 9f3d81729d | |||
| 064d3e8b3d | |||
| ef7e3c61ed | |||
| 64951e1e5e | |||
| 58931732f7 |
160
COUCHDB-ERLANGCOOKIE-FIX.md
Normal file
160
COUCHDB-ERLANGCOOKIE-FIX.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# CouchDB erlangCookie Fix - Implementation Guide
|
||||
|
||||
## Summary
|
||||
|
||||
**Problem**: CouchDB deployment fails because `erlangCookie` is missing from the ExternalSecret configuration.
|
||||
|
||||
**Decision**: Externalize `erlangCookie` to 1Password (pragmatic approach)
|
||||
|
||||
**Rationale**:
|
||||
- ExternalSecret architecture requires ownership of the entire secret
|
||||
- Mixing externalized and chart-generated fields in the same secret is not supported
|
||||
- Single-node deployment makes erlangCookie rotation unnecessary
|
||||
- This is an acceptable deviation from the pure Harbor pattern given the architectural constraints
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Generate erlangCookie Value
|
||||
|
||||
```bash
|
||||
openssl rand -hex 20
|
||||
```
|
||||
|
||||
Example output: `f4e3c2b1a9d8e7f6c5b4a3d2e1f0a9b8c7d6e5f4`
|
||||
|
||||
### 2. Add to 1Password
|
||||
|
||||
- **Vault**: `mk-labs`
|
||||
- **Item**: `couchdb`
|
||||
- **Field Name**: `erlang-cookie`
|
||||
- **Field Type**: password (concealed)
|
||||
- **Value**: `<paste generated value from step 1>`
|
||||
|
||||
### 3. Update ExternalSecret Configuration
|
||||
|
||||
File: `cluster/applications/couchdb/externalsecret.yaml`
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: couchdb-credentials
|
||||
namespace: couchdb
|
||||
labels:
|
||||
app.kubernetes.io/name: couchdb
|
||||
app.kubernetes.io/part-of: mk-labs
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: onepassword-connect
|
||||
target:
|
||||
name: couchdb-admin
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
engineVersion: v2
|
||||
data:
|
||||
adminUsername: "admin"
|
||||
adminPassword: "{{ .adminPassword }}"
|
||||
cookieAuthSecret: "{{ .cookieAuthSecret }}"
|
||||
erlangCookie: "{{ .erlangCookie }}" # ← ADD THIS LINE
|
||||
data:
|
||||
- secretKey: adminPassword
|
||||
remoteRef:
|
||||
key: couchdb
|
||||
property: admin-password
|
||||
- secretKey: cookieAuthSecret
|
||||
remoteRef:
|
||||
key: couchdb
|
||||
property: cookie-auth-secret
|
||||
- secretKey: erlangCookie # ← ADD THIS BLOCK
|
||||
remoteRef:
|
||||
key: couchdb
|
||||
property: erlang-cookie
|
||||
```
|
||||
|
||||
### 4. Update values.yaml Documentation (Optional)
|
||||
|
||||
File: `cluster/applications/couchdb/values.yaml`
|
||||
|
||||
Update the comment block at line 9-10:
|
||||
|
||||
```yaml
|
||||
# Admin credentials managed via ExternalSecret
|
||||
# See externalsecret.yaml for 1Password integration
|
||||
#
|
||||
# NOTE: erlangCookie is externalized to 1Password for architectural
|
||||
# simplicity (ExternalSecret ownership model). In a pure Harbor pattern,
|
||||
# this would be chart-generated, but single-node deployment makes this
|
||||
# acceptable. The erlangCookie is treated as an immutable infrastructure
|
||||
# secret (generate once, never rotate).
|
||||
createAdminSecret: false
|
||||
extraSecretName: "couchdb-admin"
|
||||
```
|
||||
|
||||
### 5. Commit and Push
|
||||
|
||||
```bash
|
||||
cd ~/git/homelab
|
||||
git add cluster/applications/couchdb/externalsecret.yaml
|
||||
git add cluster/applications/couchdb/values.yaml # if modified
|
||||
git commit -m "fix(couchdb): add erlangCookie to ExternalSecret from 1Password"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 6. Verify Deployment
|
||||
|
||||
```bash
|
||||
# Watch ExternalSecret sync
|
||||
kubectl get externalsecret -n couchdb couchdb-credentials -w
|
||||
# Wait for: SecretSynced
|
||||
|
||||
# Verify secret created with all four keys
|
||||
kubectl get secret -n couchdb couchdb-admin -o yaml
|
||||
# Should contain: adminUsername, adminPassword, cookieAuthSecret, erlangCookie
|
||||
|
||||
# Watch ArgoCD sync
|
||||
kubectl get application -n argocd couchdb -w
|
||||
# Wait for: Healthy/Synced
|
||||
|
||||
# Watch pod startup
|
||||
kubectl get pods -n couchdb -w
|
||||
# Wait for: Running
|
||||
|
||||
# Test CouchDB access
|
||||
kubectl port-forward -n couchdb svc/couchdb-svc-couchdb 5984:5984 &
|
||||
curl http://localhost:5984/
|
||||
# Expected: {"couchdb":"Welcome","version":"3.5.1"}
|
||||
```
|
||||
|
||||
## Why Not Follow Harbor Pattern Exactly?
|
||||
|
||||
**Harbor Pattern**: Only user-facing credentials externalized, internal secrets chart-generated.
|
||||
|
||||
**CouchDB Constraint**: ExternalSecret uses `creationPolicy: Owner`, which takes full ownership of the target secret. This prevents the Helm chart from adding auto-generated fields to the same secret.
|
||||
|
||||
**Options Considered**:
|
||||
1. ✅ **Externalize erlangCookie** (SELECTED) - Works with current architecture
|
||||
2. ❌ Chart auto-generation - Conflicts with ExternalSecret ownership
|
||||
3. ❌ Dual-secret approach - Requires Helm chart customization
|
||||
4. ❌ Disable ExternalSecret - Loses 1Password integration for admin password
|
||||
|
||||
**Decision**: Pragmatic approach wins. erlangCookie is treated as an infrastructure secret (generate once, never rotate), which is acceptable for a single-node deployment.
|
||||
|
||||
## Secret Classification
|
||||
|
||||
| Secret | Type | 1Password? | Rationale |
|
||||
|------------------|---------------|------------|------------------------------------|
|
||||
| adminUsername | User-facing | No* | Static value, hardcoded in template |
|
||||
| adminPassword | User-facing | ✅ YES | User login credential |
|
||||
| cookieAuthSecret | Gray area | ✅ YES | Session security, periodic rotation |
|
||||
| erlangCookie | Internal | ✅ YES** | Architectural constraint |
|
||||
|
||||
\* Hardcoded in ExternalSecret template (not fetched from 1Password)
|
||||
\*\* Pragmatic deviation from Harbor pattern due to ExternalSecret architecture
|
||||
|
||||
## References
|
||||
|
||||
- Full analysis: `/home/hermes/couchdb-erlangcookie-analysis.txt`
|
||||
- Harbor pattern: `/home/hermes/harbor-simplification-complete.txt`
|
||||
- CouchDB Helm chart: `apache/couchdb` v4.6.3
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
# (pathspec) Colon-separated paths in which Ansible will search for collections content. Collections must be in nested *subdirectories*, not directly in these directories. For example, if ``COLLECTIONS_PATHS`` includes ``'{{ ANSIBLE_HOME ~ "/collections" }}'``, and you want to add ``my.collection`` to that directory, it must be saved as ``'{{ ANSIBLE_HOME} ~ "/collections/ansible_collections/my/collection" }}'``.
|
||||
|
||||
;collections_path=/Users/rblundon/.ansible/collections:/usr/share/ansible/collections
|
||||
collections_path=/opt/ansible-collections:/usr/share/ansible/collections
|
||||
|
||||
# (boolean) A boolean to enable or disable scanning the sys.path for installed collections.
|
||||
;collections_scan_sys_path=True
|
||||
@@ -209,7 +209,7 @@ private_key_file=~/.ssh/ansible
|
||||
remote_user=wed
|
||||
|
||||
# (pathspec) Colon-separated paths in which Ansible will search for Roles.
|
||||
roles_path=/opt/git/homelab/ansible/playbooks/roles:/Users/rblundon/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
|
||||
roles_path=./roles
|
||||
|
||||
# (string) Set the main callback used to display Ansible output. You can only have one at a time.
|
||||
# You can have many other callbacks, but just one can be in charge of stdout.
|
||||
|
||||
6
ansible/create_jarvis_user.yml
Normal file
6
ansible/create_jarvis_user.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Create jarvis user and deploy SSH key
|
||||
hosts: all
|
||||
become: true
|
||||
roles:
|
||||
- jarvis_user
|
||||
154
ansible/group_vars/all/semaphore.yml
Normal file
154
ansible/group_vars/all/semaphore.yml
Normal file
@@ -0,0 +1,154 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Semaphore configuration-as-code
|
||||
# ============================================================================
|
||||
# Drives a freshly-deployed Semaphore instance into its desired state via
|
||||
# the Semaphore REST API. Idempotent: every object is checked first; only
|
||||
# missing ones are created. Existing objects are left alone.
|
||||
#
|
||||
# Loaded from group_vars/all/semaphore.yml so that the configuration is
|
||||
# version-controlled in the homelab repo and survives a wipe-and-redeploy
|
||||
# of the Semaphore VM.
|
||||
# ============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# API connection (defaults to the local Traefik-fronted service-name URL).
|
||||
# Override semaphore_api_url to point at a specific instance if needed.
|
||||
# ---------------------------------------------------------------------------
|
||||
semaphore_api_url: "https://semaphore.local.mk-labs.cloud/api"
|
||||
semaphore_api_validate_certs: true
|
||||
semaphore_api_token: "{{ vault_semaphore_api_token }}"
|
||||
|
||||
# Feature flag — keeps day1_deploy_semaphore.yml deploy-only by default.
|
||||
# Set true to also run the configuration pass.
|
||||
semaphore_configure: false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Declarative configuration of the Semaphore instance.
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Top-level shape:
|
||||
#
|
||||
# semaphore_config:
|
||||
# project: single dict — the lab uses one project ("mk-labs")
|
||||
# keys: list of credentials Semaphore stores
|
||||
# repositories: git repos Semaphore can clone
|
||||
# inventories: Ansible inventories from those repos
|
||||
# environments: env-var bundles
|
||||
# templates: task templates that tie everything together
|
||||
#
|
||||
# Each list element has a unique "name" used as the natural identity key.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
semaphore_config:
|
||||
project:
|
||||
name: mk-labs
|
||||
alert: false
|
||||
max_parallel_tasks: 0 # 0 = unlimited
|
||||
|
||||
keys:
|
||||
# The ansible-vault password. login_password type with empty login
|
||||
# — only the password field is consumed by Semaphore at runtime.
|
||||
- name: ansible-vault-pass
|
||||
type: login_password
|
||||
login: ""
|
||||
password: "{{ vault_ansible_vault_password }}"
|
||||
|
||||
# SSH key for the gitea deploy access (clone the homelab repo).
|
||||
- name: gitea-deploy
|
||||
type: ssh
|
||||
ssh_login: git
|
||||
ssh_private_key: "{{ vault_gitea_deploy_key }}"
|
||||
|
||||
# SSH key for the universal automation account 'wed' — pre-baked in
|
||||
# every mk-labs VM template. This is the canonical user Semaphore
|
||||
# uses to reach the fleet.
|
||||
- name: wed-ssh
|
||||
type: ssh
|
||||
ssh_login: wed
|
||||
ssh_private_key: "{{ vault_wed_ssh_private_key }}"
|
||||
|
||||
# SSH key Semaphore can use to reach the fleet as jarvis (admin
|
||||
# account provisioned by linux-baseline). Retained for jobs that
|
||||
# specifically need jarvis-level access; the default is wed-ssh.
|
||||
- name: jarvis-ssh
|
||||
type: ssh
|
||||
ssh_login: jarvis
|
||||
ssh_private_key: "{{ vault_jarvis_ssh_private_key }}"
|
||||
|
||||
repositories:
|
||||
- name: homelab
|
||||
git_url: "ssh://git@gitea.mk-labs.cloud:2221/rblundon/homelab.git"
|
||||
git_branch: main
|
||||
ssh_key: gitea-deploy
|
||||
|
||||
inventories:
|
||||
- name: production
|
||||
type: file
|
||||
inventory_file: ansible/inventory.yml
|
||||
repository: homelab
|
||||
# wed is the universal automation account pre-baked in every VM
|
||||
# template. Semaphore uses it for fleet-wide jobs.
|
||||
ssh_key: wed-ssh
|
||||
# become_key is Semaphore's sudo PASSWORD slot, not a second SSH
|
||||
# key. wed has passwordless sudo on every host, so reference the
|
||||
# built-in "None" key. (Semaphore rejects an SSH-type key here.)
|
||||
become_key: None
|
||||
|
||||
environments:
|
||||
- name: default
|
||||
env:
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
ANSIBLE_FORCE_COLOR: "True"
|
||||
# Semaphore runs ansible-playbook from the cloned REPO ROOT (not
|
||||
# from the playbook's directory as I first assumed). Path is
|
||||
# therefore relative to repo root, not playbook dir.
|
||||
ANSIBLE_ROLES_PATH: "ansible/roles"
|
||||
# Collections are installed by the semaphore role into a host-side
|
||||
# directory bind-mounted into the container at this path.
|
||||
ANSIBLE_COLLECTIONS_PATH: "/opt/ansible-collections"
|
||||
|
||||
templates:
|
||||
- name: "day0_linux_baseline"
|
||||
description: "Apply the mk-labs Linux baseline to one or more hosts."
|
||||
app: ansible
|
||||
playbook: ansible/playbooks/day0_linux_baseline.yml
|
||||
inventory: production
|
||||
repository: homelab
|
||||
environment: default
|
||||
vault_password: ansible-vault-pass
|
||||
arguments: '["--diff"]'
|
||||
survey_vars:
|
||||
- name: target
|
||||
title: "Target host or group"
|
||||
description: "Inventory target (e.g. figment, semaphore_server, all)"
|
||||
required: true
|
||||
type: TextVar
|
||||
default_value: "all"
|
||||
|
||||
- name: "day1_deploy_semaphore"
|
||||
description: "Re-deploy Semaphore + PostgreSQL on figment."
|
||||
app: ansible
|
||||
playbook: ansible/playbooks/day1_deploy_semaphore.yml
|
||||
inventory: production
|
||||
repository: homelab
|
||||
environment: default
|
||||
vault_password: ansible-vault-pass
|
||||
arguments: '["--diff"]'
|
||||
|
||||
- name: "day0_linux_baseline_check"
|
||||
description: "Dry-run the baseline — shows diffs, applies nothing."
|
||||
app: ansible
|
||||
playbook: ansible/playbooks/day0_linux_baseline.yml
|
||||
inventory: production
|
||||
repository: homelab
|
||||
environment: default
|
||||
vault_password: ansible-vault-pass
|
||||
arguments: '["--check","--diff"]'
|
||||
survey_vars:
|
||||
- name: target
|
||||
title: "Target host or group"
|
||||
description: "Inventory target (e.g. figment, semaphore_server, all)"
|
||||
required: true
|
||||
type: TextVar
|
||||
default_value: "all"
|
||||
@@ -19,6 +19,15 @@ terraform_server: "infra01"
|
||||
# Traefik variables
|
||||
traefik_server: "lightning-lane"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# JARVIS automation account
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public key for the 'jarvis' user provisioned by the linux-baseline role on
|
||||
# every host. Public keys are not secret; the matching private key lives on
|
||||
# the JARVIS command centre (carousel-of-progress) and, when needed, in
|
||||
# group_vars/all/vault as vault_jarvis_ssh_private_key.
|
||||
jarvis_ssh_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID5sym5ajFvDyzw395BkHv7qVb66XPTx/OF1p19MGuNo jarvis@mk-labs"
|
||||
|
||||
step_ca_principal_mappings:
|
||||
- local_user: wed
|
||||
principals:
|
||||
@@ -30,3 +39,9 @@ step_ca_principal_mappings:
|
||||
- ryan.blundon@protonmail.com
|
||||
- ryan.blundon
|
||||
- ryanblundon
|
||||
|
||||
# Leviton My Leviton API
|
||||
leviton_email: "{{ vault_leviton_email }}"
|
||||
leviton_password: "{{ vault_leviton_password }}"
|
||||
|
||||
jmri_vnc_password: "{{ vault_jmri_vnc_password }}"
|
||||
|
||||
@@ -1,306 +1,361 @@
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
33333438373661633337383161343862383963623736636136653339363064386538386437323135
|
||||
6436666330363032616365656434356161396530363363610a333864313064633232366133366232
|
||||
37316636366334346536663337663065303635626638666264666435393933343832653061323237
|
||||
6663333736386636350a653931626665346234323832393334373563303130626262646565653231
|
||||
30326263623965356639386438636531306430343239313162383065386135653534636134376333
|
||||
64616432303732646535643530353963363734663538393539633034326535643539356561656632
|
||||
36363330346131393262326561356631343631356434666264616134643136646238376336373665
|
||||
65346361636435316437353235393263313766623438336662353532306463353134366435326364
|
||||
36613865343264616364333631353238663238313338333232626262313637633662633163366638
|
||||
61323166393739656536656435336538303439636561363961353832313431666432393463636537
|
||||
62323939376265353330356334303334303532643136376531343765613738656562386665336132
|
||||
36346232313432396431666535663362333332613037623531636135376439393936353261653863
|
||||
65373061316461303866363338376131343736323933383636626338616431656533363833663266
|
||||
62363935313262393733333566386337633630666332353263666462373164346362313034636239
|
||||
33656462396238343065613965613232623132343562323364653366313231323531326364376631
|
||||
65643961303862653030343364326533356336393031636437393465636263626236303633326139
|
||||
65643432663037333332346261323663613031386563383533336538383133373332343363326530
|
||||
66643231666636366435623639643166333863376533633537363364396663363732333932663333
|
||||
62333239613264353233303064333931326236396538323130353061366139623362393734323434
|
||||
36343537393363343861633131346561353637646336313233376162656561623362653565623339
|
||||
63386337613461343534643964323932356661383436303966346339386131336133643132376330
|
||||
38656535353335303966636235643666306336396435636434363733313665366234393032333065
|
||||
37633231376134636435343233363032666231666134623365653238373836326237373831326262
|
||||
66303234303164303961316166623062616139353264343864363938313733653563663661633535
|
||||
37326636346435626362336437666364623264363565653935336438386262376663303230313265
|
||||
62663665666132313436663330616230383235346333623563313262376530356535613263316436
|
||||
35313766363763386234333733636464326134633136346532346632623534646365623136343363
|
||||
30316435613031333036383835656230353430636135343961356363613139353763343233633834
|
||||
38656431363139396535653565633631393462316334666464333839343035616531643466643531
|
||||
35613733373331643961353830376266376433363938373563653833343464633465663766333066
|
||||
34633364313762633034633461623232656537653762623532613332386132333365383062653136
|
||||
63323234626266336462333534636236653230343530336532653831313339613531343731313237
|
||||
39653737663665373361326433643364656434393838326339636238303964366336363961653366
|
||||
36643938633635313432663765316130326238306163346566313062626439626263633033623731
|
||||
63323532346366653238663931653334613734633963363632643462316130393138363436666532
|
||||
32373833323330356164626332646232623031326365653765336261323661616137373732356164
|
||||
63383633353764643233323430333233306337623834353934643864636339646239636430316166
|
||||
33643030333261323461643031613730343131343830353463313766353035326436306566343161
|
||||
35643534323763316165343832346433356364343061383036306434386631356537363331303035
|
||||
34303430393034393763343964613935383739656662643066363633333839396465386231383837
|
||||
30336331393365346636363732616661633635326364366435366466383639353839343731616365
|
||||
65326633636263653438343561633632646234316461616666363630333966326230356331373335
|
||||
31353665626233663433633563623765343431376639316232346666663065656462616438663333
|
||||
62663662336662346563333935333261626635343764356631356363356362346231306535376466
|
||||
36393932666662653934626134613065623461303834393231616332363461306534303666353632
|
||||
35646363333065356631393863396536633838343365343238636332303862306631346639656166
|
||||
30373735336361653863326331373834663164393863666631623866353338366561326635323132
|
||||
35306535626564376666623837666435663337613164623966306138353161313239373436346239
|
||||
36656266323233636233326139326266346464353665323465663666646264613466636132626464
|
||||
37353762306538643233663338613062353134323832653139663036666337643131613062643936
|
||||
37633363376365356333353433613839653130323036643737633163336531323032393937333432
|
||||
39343238396534353330346664346135326333333638666261396463656663323935643337313462
|
||||
32396137613432623664323134356361323861306230353165663162663732326563626162346630
|
||||
34626536393962393764393764393234373138656432346332633963393135386238313563353761
|
||||
34366438353337396264373032336166663937343931633635666166343638386237396333626133
|
||||
38383766616566336163363931393438366566386565663938356630366430393830653037363738
|
||||
30666465326266363834653934643434666466326632366463303965303366326364316430366561
|
||||
32636631663232373163636535366333623261366466623262393631626334626263656534386536
|
||||
66323833353264623538663232376138346361646362313565333534363535663031643833373064
|
||||
38313062346339636335633962323933383230623634303431383236626166636535326465316439
|
||||
38646636336130373763646635386265373065326263616438646565313439613830316135616435
|
||||
62326430336337643265306261343036653938633634626561333664313035393933646533326661
|
||||
62626437306432353938356338616462663130646530623636393233373837336138383963323165
|
||||
35643231613565313434386163643637633130626435376561376235346438303234386637383835
|
||||
37356238616263646665336435306233396436666664396532373063626162346237376662393639
|
||||
35373665373638396434353062666430633362653163633639623836346438323632643164653638
|
||||
62623463343465303236346562386338646536656438633966643430343266313935323630616633
|
||||
62313531343764343966333439343866613965303036616634363162303434643533366465313562
|
||||
62393666313838313938356265633531326431333730626632343139666338656465306431636162
|
||||
66343532346238393464366134656334363964626264623263353537633638346239396539653538
|
||||
37303339613036306533353132326534306433353364613536316263326363323134333639346236
|
||||
39376263313431666462383863336638306631663563313364333235333338323364613361633532
|
||||
38313263353539633662353533646333353262356436353938303036663130306362303935356136
|
||||
63313234363138383864613664666635613464653765636461383738356135623662313866383036
|
||||
64626437343465333563346434323762313232643230323336366539326631396463303961666461
|
||||
61656338643532616337653362636362373837373836666535633762343335623033316264366261
|
||||
62633137356132306430623537616135613965393737323263343463633839393165646630383331
|
||||
36616339346534346636396532333836633436323232323364303963313030656330386336633538
|
||||
34383534313236396461653462306130376462396138303561636331663062303864326238303335
|
||||
65663832333631613963326639386665643135366632393438666361376334636233336239633837
|
||||
36383761616364363262306535653065303536363066316431303464373230326436336661396231
|
||||
66333438376664333731333630383031366237396638326534636532343535303964653436633438
|
||||
64616263366561653334346138393866353437393037343930623237326231303261383733336232
|
||||
37646533393439653965386563333232656565663531396663303334313839323361363864396332
|
||||
61656638303130303433666561646663323139643337326561333832373538306236326538363637
|
||||
38666534633330653564653066643335623938373331376339393338343533366436333330313165
|
||||
65366562386366666632653064663637343536313462326562626436356363306134306237613265
|
||||
32366338643237333739626331393763323934396265383138633035646533353536633866313432
|
||||
38336137383036373434363530353833653938663035306337643638316463373762613931373038
|
||||
32373361326136616331663733616133623936663530356566383461336334663937383230386462
|
||||
38656332613763653339363565613762383735333163633831356364333563363630623666616430
|
||||
30353237653730613361643233386139303639666137653733353334326530306431376666663664
|
||||
34633531393261343165663330373961646665396139323762313663376639366464306533623333
|
||||
62326432623334336166656565656230313063656163636561326632666438633966323930316264
|
||||
66373866663461616336336264356561373232313737353836646632383438333162383832616662
|
||||
30636437613163613631623165366631306438643765383263626133653231333361383633363136
|
||||
36653831623562653838633133646633393038666164346461333531373034356435363336636338
|
||||
62623464376264363537306166613461396630303039373239656664396564333435303164613235
|
||||
61346238386233653462616632303337663036626465663361636130333566303337313237626237
|
||||
65636633316366343064663636383362316665633263633136323563363935346138356635653036
|
||||
36383835323838366363363335323731333066393334373265653563336533616362633163343661
|
||||
66326630353537653633316631343332303231373630363635623136386537666434383032323165
|
||||
33333633643064376239346139633664633330323966366430353135616633633465363030386665
|
||||
36353164623564343934323063313763316566373830643163616633643937323732333561353832
|
||||
66623364666335316230346665353631663465633034323265666663653866623037353935393262
|
||||
61623065656637636666383239343661616233396663333038393963366537326136653863333235
|
||||
65633137626134383763663366396438366634313465363965343830333966373930333134353339
|
||||
35396538303537316636333339613731623636646230323662613663306537326566656666323434
|
||||
65346531623533336535393530373963343864313963666335636334396663393837396665373565
|
||||
39646138356134313732666633663061646330643438393736653433313238373135656636643337
|
||||
39396362383363333434376336613933613438653636663334336337653633613433623336623461
|
||||
32373565306435646438306365343465653863656434663937653938393839333739363639326536
|
||||
64303534383961333333386237366239386330386533343939613664363439373135333731393739
|
||||
39363166306636333766333332393265613133356262356666343039666237663664636266626664
|
||||
65316339323638353333653631363365356562623535666235313234613236326130616261663830
|
||||
34663063626539363734313562656233656131326265626637353239636638396564313561383761
|
||||
62663638623135343333316464346236353364616239633566393039633339306334623234616331
|
||||
34366435663938346335323932633330366639653636336636373562306561656536653363366233
|
||||
39383064623435356338636335343662343435336564363965646439393964323162363736346637
|
||||
65323765333364353831396433666561376437333834313334316165386631616332633533666336
|
||||
37313533623535313934393230353439363231623063663961376537393638636237623134323337
|
||||
30393032333530316330333736616463633262616631353238323933303766646137386465643366
|
||||
33356665396437623732373437373537303034353431353031313162323730316566396238653038
|
||||
30366234663037653736353137376264363934646366353761323364663538346662323934343266
|
||||
30373662613461663561623466633939396439653663353365346133623933613462623231616562
|
||||
66643738643661643766323266626266313330633936313964366536323263373666303130623738
|
||||
32323230356665323439646532343930333936373536626466656566363161646663616266326133
|
||||
32393137663562613531323832313565346632303131646432363961613331346636653830323136
|
||||
36323436373964313737313131346131353630636561636639316464323666636336656265343565
|
||||
65303936353165326137613638366532303265356537343337656165366663383961643134656435
|
||||
61383565306265333939613561613035616663336361396536343939343761663833346134646365
|
||||
34323233666439623436323261616432363838616437386138393735383364393164646638373630
|
||||
33386632346239383037633132636431366264383633333365306338333661303662363533376339
|
||||
62633663366362303038616666336335346332316366353136323666346531393938666236343163
|
||||
31663665643839313439663839353431343061386465366331663637373532613266383065393639
|
||||
62613565626135363133623261333061373738633333623337396631356332643039306638303434
|
||||
65663762373630323263333239613939346133626534613632396433376234663636363062656537
|
||||
62386664343462346334663662663865653766373738306438316533643962356136653565373761
|
||||
62393437326138323065396630653838326138653865643937353431656137393732346361353734
|
||||
31326634653331353232633061303230393462363065613832303630383237366463363666613864
|
||||
37643135303034326335326563353064343835323863313134616262386564313235653762376332
|
||||
36636533646235343138396537623266333566633466393764353731393935306432313266636133
|
||||
63326362653033613264333139633638643638326532313239646239386332323330306538366362
|
||||
37646432363535396161336465376363343430333261396536346464356135343462663462613136
|
||||
36666238633663326230373834303431336566366666666534343537313635366537396262656236
|
||||
36646639643730393134613662383238623835626230313833653932303832323366343765343434
|
||||
34363033323836663035363166313630313062663162313165636138373261386636623831666632
|
||||
33373962626237666130373031633437653966386561363232363332333633303961323864333263
|
||||
39666230383332376537313137643635623336316336646537333137633564376432356432326639
|
||||
61383565313233376435636366366531363462306535636131636163636130663732383161616364
|
||||
37343064333264663533343739356139353365386565383833363165326536306431393764303632
|
||||
30653865623566363965306636366530303261616663306433393135656366383138356337383365
|
||||
36323736643035383231663034626564643235646237623632383164633062326132363331353634
|
||||
61653761346439326665366565383534393635616365353361353863363763383664303166383434
|
||||
33393732313837373336616564343864633330663566653933356237613538653366663838383132
|
||||
33303437323438313837616134616466626562313262653866313034383764656566373331343738
|
||||
30613062303630663365636230396231313065356662376163353632613564363066316239636434
|
||||
66356535386263353737383838653538316366343935323337386561353533353161636538646363
|
||||
63643739376366326536333261306263653563353331393133323964653764373932663136316335
|
||||
36333535626231373864316634613366353535393932373039646161646466363663626561373033
|
||||
64333133353931303361363466346666376431363336323166356162363537633536336638653838
|
||||
35323562653138393833383130353232616564393738326638366565616233633634336438303632
|
||||
30353765623565336330386335306162336662643466663335623836383238316161633339373137
|
||||
32393165323735643034633464313337386362363331353432363233396538393835383933313637
|
||||
65656330383732633537646361666439303266653165313233636464313964383639346635376538
|
||||
64633861623439626331643431326336646135656539313836323135373862316438393237663564
|
||||
31373135356135356662336265633862343435356530393835376430666566653036373338636332
|
||||
64663230323361356262356537656262396665663538386664363735646234306231613339636338
|
||||
34366433326535353965376236643661646339366566663531653864316533356466653864373633
|
||||
33363330333765373662646634326236313434383433653563383436316635353461386662646165
|
||||
31626566616163373137326165323066646535663766636265386435633965326165303831323633
|
||||
39613139623835316165326138386431383530383035653931616334353235636138303761383037
|
||||
39336163373332346365333935656563343939623335313461363138656239666263366236663464
|
||||
32303732663937363964346639323236623166373334386239613564643834316536363634326461
|
||||
64623939363635613863613065656363303734643766306564326437663037623839303734393036
|
||||
64326363626562363934313930383766383462663766336666663338653765656137393362643366
|
||||
36326336323638393161353464396637633566383262333234656233653334623131323532376637
|
||||
62613064626532616631646232386631623839326665346431333232333463373031316237363462
|
||||
30643731653666643331643839343865646437613135346363646131383262396134376363303335
|
||||
34303164303236383139326264623462363062313139653465346435336163653863633764613631
|
||||
33623666316530353163396539323238663935643764643939333966336137353034663164643733
|
||||
34393132323339626331626532643266336435653636353930393738363134353661653636643263
|
||||
30313138393263383837333438356463636162383938396137363035646433663932613066386138
|
||||
37316535373939326131643930353435383232623366393630663463633233393232373730333631
|
||||
37623837373533383462323431323335316231356436303239313535633837353931613730303363
|
||||
31613765626466636636613366346166393766373834643337353764666532376335376139316138
|
||||
38646133643736636363363434306334373933633264366530373534666637393835366266306633
|
||||
66653966613136653936313732396366323830376236626530306139333030323966383035643733
|
||||
36353833323539393136303035393866366563323430636336356639623734656135356566373062
|
||||
34666433316139316664643234623530363732373338623162363636303463346463323131333065
|
||||
64656566353837336266386233323439623237366364343635306137663038326434366531623062
|
||||
64613365323339613465326464653965613439653766623762363139366264663765363862323839
|
||||
63346539323961343532626465363266626234363964313531353662636635623165376130616536
|
||||
64363361383766636333626366616531343833633033643965626435313130653938316430313532
|
||||
64363731633139383236326231653434663132313463326563393739393132663765396337643035
|
||||
32373963393830356362366130376135353935396631393533333430363531636234333832373939
|
||||
36656233343663623262346337323034653036373432303266353333626434353137353939346665
|
||||
37633432303162323538343339643635636464353333313063363662663033396630303162333639
|
||||
31633537373364666234653938633461343738333464653736316331343038393333643936646462
|
||||
36373232373962653031333730363166393463313232616631383233386533373464366465623335
|
||||
65393462353638633931353331313233356230303565313030663739626230323766666466326339
|
||||
35303361383065353333666161306433383139366439633730666364383130316637633464646336
|
||||
66643065303934663765623233333261313939373236396136366566306438626439393163636430
|
||||
30656334663361396230303239663338373664346362623934313130343064353435626439356562
|
||||
35653638613037656133313765313165333066353732363361386234353635626563323932356237
|
||||
31363333326536353536323931643739333764323637356164663566343563633761333462383735
|
||||
66383134646139373130386366316532656139336231303931656265636130643130616531366530
|
||||
62383132643236623835323362383866613765343762333536333365336338373264633130656438
|
||||
36343331666431326530626137333665623836636136303031333965356366396239396534663263
|
||||
35343730303534313830623430666566386564653036356635623038303133623231616635396463
|
||||
65626137633033663631373366373238353766323533643462396562633164633730633135396266
|
||||
36316362353964306463336237383232306530613539653462326137383938333638353038396132
|
||||
66343265643162363735663235353161393732326530373735363662633165316265323065396163
|
||||
31663761303637616566396262346238366631663562323566323364663464373930373831323630
|
||||
39333165336264336539353634633936396561626363353466303931343436306636356663346438
|
||||
64373561666638656439353964626436613164643663336336333337393332346132313830316430
|
||||
37336139333539316639646337383737373530643533336133633135333936343165303764623366
|
||||
35346432656263396439663635653066303861643938633033393334336336396133626237336432
|
||||
31393061653839653964613239353336653666366163616163616230396661313233613934326339
|
||||
36333733663032613633613037343337646237666565316239303231313163383264376636336338
|
||||
37656532373634383132346163316337336534316465383531316465353861633062336235376631
|
||||
33333963313730653164313866323461353334653231333762336232366434333631396135333466
|
||||
36643362653137323934323966303734316334306133656139613763336431323236643264663530
|
||||
38633730346531396435666530316563386566323737343766653135386537393165356264356534
|
||||
62323962306332393063313062313230333334353833393362656166353638323366353934656263
|
||||
38633531306438663134616262323332323338653361356336353962376331323537623734316532
|
||||
35306434383336633436656437663765626530626535383464356532613536356533646332646135
|
||||
39613030323432643161366363313262633261366566613931303663636262353466313933303162
|
||||
66373535353934626135326439653630303437666535396266303532626636613563616633623237
|
||||
38623938323137393065616136376663363234633735363264356438353534616338393832386337
|
||||
66663339313062613766666435626364353935643932633233643630323464336135336339376437
|
||||
30323536373639343738613463613466396335626339343135306666663931656564393664643761
|
||||
65396336613465313035396134636438636134316236636566333031646666383939383264303437
|
||||
38656337326233363866336363363161333236326137393465633935626165663136316661316131
|
||||
66393764313130373766386235663134376239323037373365333730643336656532316434663265
|
||||
35646664383338326366386536616339393938336539373762643839613763323434363037326330
|
||||
66353738643735656165396138343262653563353934376237366564636638623630393238373530
|
||||
39336335303564616232393165653639326166333536343966353933633365326539383632646639
|
||||
37636238376137646239353731343933346562613630656130653865633834626363656231373162
|
||||
65333662653962373139633466383834626566336234316161633038653137393266393564376439
|
||||
62343365666637396639326438326631613664666662653935346230316632396361663038663239
|
||||
63336335643665353162393330623635663839666162333561376265363330383530653733333239
|
||||
37373665646562343562313764386534663239636130646564333433313661363338326439363865
|
||||
63353064613161366461383862623363333637633430376361366566383332636634646630363139
|
||||
65303139396539323336313666336362626339326666343061303431356361343438313365353431
|
||||
30303266363664373233303231346166643839343564306363613236316665306437333636343131
|
||||
61303234666664323138376335623639393939633836353664343232316630393263323233343366
|
||||
32393736336166323166616139323066613062646139656236343233623630616262343034383631
|
||||
61653662383537646364363764353335333630353431633534313731633265626263303863326565
|
||||
37616534303164316162353165396236656565303532326235393237366434323739633662663261
|
||||
33383635653734303233356438636132343731383439623463633732373833303339353662386435
|
||||
63316138353766303962616435383138373739383835353662613666356565366237353932393263
|
||||
34326561613234356537343234303166613534356134643766383332646263623430653632386365
|
||||
61376165656366366532346465343932313032643163313362373633373066653036346238343938
|
||||
66626133306163353966386662363039323166353330633239386264306337386361653430633838
|
||||
66366636616661313939316562373330313564383033643035393738353939613066316138643238
|
||||
35376336633238636463376234376364386464396461636336313562616361396435323639666433
|
||||
34653139613962623235613035363634383735326564333332333632656237333238356566646536
|
||||
62346435303932363231306634643037623935653235613330666666656433396463656231643364
|
||||
34313235613064646432633935656231643162363530313761303339323830653564303963643532
|
||||
39326163396461613161363031616336666564326630323465663831653431353566643661656435
|
||||
35636238633663346139343732353233663930353664623636303134303633356232313063346566
|
||||
30633762653239356131613539396435396235326431633062616436613266353266636133656531
|
||||
63346533383764393733396436306136383830313265656539376136343536353737313365333865
|
||||
61653665393037346231656663363432666332383837393264656231386435323333376633333265
|
||||
39633563653161663531303330393237316132653339653531316538316534353334383161623833
|
||||
63656534643833656366623031326132343732663536393636393537383338613462336638626334
|
||||
39326264623032393661303335356235343736613964636261633038333236623232613633343263
|
||||
34363865306233346433393664363038303464363834313463636363366333313433333930336634
|
||||
65396232663031323965343336366339623239386566323362396564333932336134336231646430
|
||||
34376236636536626264663532323530656634333137363431313339643164333236303337373639
|
||||
31326131636161386665623838303939616337636666346363616465323263373061633832363839
|
||||
39343131386333666231643931353330343136363732396465383336303866623733316235646234
|
||||
65613531613333653231666234353532313738363266353336303564613061393639323933646339
|
||||
64396334396431313563326136656539613535663334323330326566613264393965623065616634
|
||||
32313333663535313531343363656230656334346430643365363838656430616366653766333136
|
||||
32346137656136666432383934313865613535303962333464316465313464333233646162376165
|
||||
37363265316139643332393761663164346537663064346133323661393030393533396633646134
|
||||
39383539333364626263323364353764316539323162346434656234393562326133613632633137
|
||||
34323537393838393534643861356365376461636235386638376661623439303832643231616431
|
||||
32663264306166366336396130326435346432343732323764346232356439633361663937313233
|
||||
62636130663238333433396335356331653166366130626632663162653736656238343134353134
|
||||
39373663633437626661393734363264313137663138643665353633366663366562386337396561
|
||||
38383464333531306138316266626461326531616364303732343337386464366464633834383439
|
||||
66303334333234306166396235666164313833333164356561663063326563303934373933313334
|
||||
32326466343761333666373936373332663939643662363665323534333638316637666665326564
|
||||
63616134623530333739633265343637626561383831663836626538313539653536373165636133
|
||||
62396432376332356133336461633664336264616566376664643861363838313766393833343366
|
||||
31386636343835663062396232646663363935396366343361633462643734373437666331383035
|
||||
30373963323834343138653063313635643062396130373861643134363062363135623730663335
|
||||
34373935623239363866393932363966366233643732646461343964383563393064396331353331
|
||||
31646662323364376639316562666465356438386637393130616330663563633839326661643239
|
||||
62346633353963336239633738336431326638626232313965336361636430323739653734636436
|
||||
39316363316137366233616431346639663335393737653562356532363634346237336266333132
|
||||
31343836326538653164653163653738656238326366313532613433333337383263646439326661
|
||||
65306135363138646665363463636436303864316432663734393130313932643833356161373139
|
||||
65376334376136313237393336636630643933373165336432643233306366633663333930396364
|
||||
31393739333730326434313639376365356432376338386430363133666436643837633533616333
|
||||
36363464633233343130656163353137663165343064373039363439346631393436393430383361
|
||||
61353563346531353561303935363531333235313731303237326334363763646131653961613334
|
||||
33306534663238343433363237393234643236393931343230656438353866313638636633396464
|
||||
61636561333562383964663261316135373235313234613564333333336438646636303561616634
|
||||
63363033613238323463646165373361613834353230653138623132303731393436386139663533
|
||||
38393263306139303231626363353931646163323364666161653861643739353262313561393165
|
||||
6262
|
||||
63623430346462366164333638633439646431326632346630383066333138313438643039373630
|
||||
6230333230356433343761666335323934656463653737360a376337346163316334333266346639
|
||||
30366436623165323462623330633935313438303761343666393162303466373762623235333161
|
||||
3835323539376536300a623962323438326336343537316630626262336339353730316162623935
|
||||
64666430626164316163633064643263396238353231373438643432343535633966636430386632
|
||||
35653437383361373938353034306264633139373663353062363163623634333538623831633831
|
||||
64643261376662613763306664353566643462306431643963633261643732386131333565366539
|
||||
35663062633730326166616366323865396536363532643931386637626430373562626531376430
|
||||
39376634363634656337363038666336663365363562356664333139633833323562386562396166
|
||||
33636437346130326539623364343030326330646137653131383739663166666334623566386631
|
||||
64616239656662393536633134643163663537626435653561323066336662613536306363623536
|
||||
33396466383537613135353930353832653765313435666435313266336361656633356131653766
|
||||
30326132366535323638393137633466333337306466333265353635333762613731396663313839
|
||||
36363830653032373564306435626333646530656535396362346132663937336337376339633366
|
||||
65373630323631303062656339613536303932656135363437633934333764613939343830353432
|
||||
34313665666634336465646363356237306562653534613863353064336236343963393664323738
|
||||
38653237656534616562303439616332303134626565646261343131393162343433636232393665
|
||||
62343431343535646431363861643964643234343665343366363930303663303565636161393033
|
||||
63626638333763343139396465363165363632393664623838623065623466336136373333393263
|
||||
34316439656232653261653538396231346462653339353034313063633661376266363265636432
|
||||
64316336613736396266333937353833363230653132383339626635386563626131663063396633
|
||||
36333061396639653238373965613831653935333533386633613035643530376465366331373832
|
||||
32643936373630363539666463353839333335663763653530623037326263306636383233613037
|
||||
39376266323064633366326163666264393265306438616330613631663137303963353666353231
|
||||
30343565633235643731326136643132376135376639366139383466323333333732396231373534
|
||||
62366438376232613463653231376164643930356137336337323763633063613861636266666262
|
||||
31323934356237373964316531353537366236633162303661663964356464653561333166643432
|
||||
61376631653335633264393762636435376535316663333065313638343361366439383432656138
|
||||
38353737353865396233366231373439333863386161323532373537333934346431373664633138
|
||||
30646437643737663031613332636536313039313663653262633665633564363130643738333031
|
||||
33666232333734646531326266393934636430626332356266333461326238633262316364356336
|
||||
65326238316336323037633236383765323332633936633062353266623238633738336132383432
|
||||
61343432343035386130363462663239613166626661613332393163386232356237383464383762
|
||||
31323739643235326264316134306430356539613865353532663133313830303061396339353566
|
||||
35373062316237373232616662383663636236353365626665386532333336313864383837383035
|
||||
62653638306136316137663738616663636434303533336433373739313331616631343131353265
|
||||
37353430386330333562366132393537613465343363623838393165666538623131653164376434
|
||||
61376533353366613735626165353562626431313031633238343966623962323231626265366330
|
||||
34353165623864343031623565363235323763646638393762646565393465396534343937666335
|
||||
62643530643435343931666563396661653662396265303362663932333666383534656539663033
|
||||
61663533353333323733613933363433383033346463306430353634633466396564623837346133
|
||||
38636131393738636666313765353835343666353663396539623236313237303437666665303966
|
||||
38323364376665613731336334383535373835303138373036613665336636306364623464303963
|
||||
39393830646133363263653330306531633863333064663466346135316161376362653862313132
|
||||
61333830303765386138303865323766353663656531643965356331633532343435633461366333
|
||||
61343534613166656639643363333231653738353936393938376665616136626361653432326531
|
||||
64633861653738616237313061393132383264313964643764366534303263653233393035363435
|
||||
31376164356466323431383836326334616130363264393139316362333130643262643766376331
|
||||
35666130303636363666363932616134306665313339323231306666353236353965356332343430
|
||||
36636263353565396535383862336464646134346566626635363063613761353333326137646662
|
||||
32656535356332626166636432643635353734393564376161316134613561303931663366616366
|
||||
35393166393632326564393763366161663636336362303566316162326631663937336437623637
|
||||
39613832666462613963643834313932323837393337633735633833646265666666653339353832
|
||||
37636332633063636133663938306234316666393735643634303833666537363932323363613466
|
||||
37626235626633383639663262653138613038363737323639306234366562666236326332323932
|
||||
63323336623633303736306362326237636465666465616361666130363538643135616237316234
|
||||
37623562396363326237663665636539346166323566386365376665616236653735646637363939
|
||||
64656662353233323065303438306436623735643934613839333133356564363438653133333037
|
||||
33656238663733663665373463393038303865613838366538356538383539396262656561353563
|
||||
31666333316564336262366435306261613563376462356338613063343232393766653332643237
|
||||
34636639333962393663333566643863386261666630393362356138623732323435666534376561
|
||||
61366363313861376566316235326633383630316338346333623432303766663130333130303363
|
||||
66616336613364666165393964666665383330316232316465663561613239386434663135613231
|
||||
62623334383433373461383037343037663236646463393632636161336562663533636235663039
|
||||
65383330373039386361633363613035636135373765353331636139313838353231353731346336
|
||||
63323736636130383962363762343437383237373533396433343464383235393030373862346233
|
||||
36646238623165353264323162316362376338383463663664636132373963623938373537373037
|
||||
30353262626363666565386536626163393130306634343836663137653537393066333637313639
|
||||
31376336373565313630636436616635623633396333313036366530613630663332323134613961
|
||||
36666438656636626561303637646539656166373861626232663266386533376531346166653834
|
||||
34656530363464343131303666616237373030393863326132313764373966633365313834316438
|
||||
34396239613235623233373136313464626661303666303438613337366662306363636439613332
|
||||
33626635613039363864343233323930333731376665346138626363396336373862636565613061
|
||||
30613863356637396133656663653232366265373766636237356539623639333133346361363361
|
||||
63343639353734313163346333356138343237383238623733343331376631636632373231363834
|
||||
62626432303732663435353762346161363330373266353031633862366539333133313931396166
|
||||
38373330323661616366343063363437333436626438376665333866376338626232383735613434
|
||||
64656363633332646533376535326566336566306439373835363264373335306564616337383465
|
||||
38353966636434653064326265643933653661363439363334383537623563303066346133353431
|
||||
62343662646434633761653332323430636339303666646262636334316461383865366436383231
|
||||
61313133316332363337343963306431356231306534636231643539383733383265303662373430
|
||||
34626666333830353934626139663532663332353938393038303631633335636236393133623064
|
||||
37306337386464656339383261323464303934373337333235343635326164653762613838333030
|
||||
39653063316131616566613365383961383435636639616536316662333736396238376661386533
|
||||
34643833323164303930656165396235353531326535313165663361653939656263633638633337
|
||||
36353435613335633239316435313965613439663061313834356661376365653763323837643561
|
||||
34333833626663353438623161366537326336646630613536653831306664663636343963313165
|
||||
65323332353633323931346634336331636465396337393035613834633962343662393930646337
|
||||
36346330353031636633326532616362646465386633363333613134653261323234636539633734
|
||||
63343965356433366233646536326333313366366537343934383239383439393633396461633166
|
||||
65633439336462363061316435396235346366386335666233343963383163393465633766643731
|
||||
62326362613266653238356435633032373036623839306132333136653537306365626165616638
|
||||
64316134316134393463313165376536383031366132363831333432323466653538376433333033
|
||||
65366438353935613863356562666264643063623439383335396531613730353861313664663234
|
||||
36626533366134643164353138306234333765623634316533383264303636353039643130366433
|
||||
38313133333632663666303862323939633463633634653963323866376137376137633930313963
|
||||
61336431656332333636373638316237663861336136393036653636366566316661303034623566
|
||||
31323537356338363762643265613534316466386263666236633038386634333139636561656331
|
||||
63613636316439393435383931306530306163386266373161666139373235653935393632313261
|
||||
36343066653561363333363830313136653538366639333464376562316334343839363332643137
|
||||
39336463613631346161616534613933613234656435383234313831366366643961393633633035
|
||||
39663935626134626138383038636266356434393535353165626632633430313635623532646165
|
||||
31363166373335326433363336326662663561306166313862373161633739643662376461346266
|
||||
61663730366337376165653033353466653761613938366532653633363530616134393464303138
|
||||
66353637623135626431643835613239633033386433663735356264333734613162343234633566
|
||||
64643039616137393863396433353138373862646138633838356365373863623235376638383439
|
||||
36373732373031346132363738383635653865346637626233663537613636613862323666616438
|
||||
34333336346235636234653062613466376637623165336337643933623535353033383438646530
|
||||
36653133336637346432343136336331336464633439383938353862323365343864666533316633
|
||||
66646262343534373165366266656135666339633333626133643238386139353537353866616631
|
||||
38306335663165363138303635383164303535386536626431663661396664376433656339373934
|
||||
38396130313438653539393263323265646163633939653431393035356332336664396234383636
|
||||
32323863366237633462316536626639343335626131323536373364393266386163633433356263
|
||||
36366665373432313132363761383632343532306361346134313464653763393533613565363839
|
||||
62623938346530636562383937393534653963636365303837336165343361313734303666623739
|
||||
30653834616163636538313038636261343434326164616264356261336239313039373661396535
|
||||
34346631643337666234373933383362656266346266393035363332303636363035656538636363
|
||||
39396466353565346264353663316562623239373564616162326439383334663730326435353236
|
||||
37366238653131356339306566326534616561653637613438666430336334323031373661353636
|
||||
64356131343763653639313531303866636334326637313838623138303836666466623736623237
|
||||
39363838653431323665366634303632666337363663386261326538313635313638383764383765
|
||||
34363832366135333436303635373761653265653165316135633135373365346266356635663533
|
||||
37303134613131653563356662363965353234643765373438313835343063313931626464636562
|
||||
36313434356434656138616433666266353538306666333363363431356530313262356438623537
|
||||
33303365393061393161626665316332626238353438623466373831303939663734663038346462
|
||||
39323462663732653335373261306635303231636563373465343761613262396466623135323736
|
||||
61316638313535653031633230333638343737633163636335343864336336373437643131643335
|
||||
63653766623436323039303065646162626331336637346237656132353039623063396631666535
|
||||
36316634623034326664313766326464383337663230643530623563326333336232666334323139
|
||||
33346563353734346338333432366437353665323764366335336232353163373563326265346234
|
||||
36383437383332366234393636323034376434336461336532613663316533653463633263343261
|
||||
62333535653062663663313534303237386562623533636230626531646461613233653330386466
|
||||
32623137633136323363386232393466303537643331656464663234636336393532313831313961
|
||||
37623939323963643364636439663032386630336235623037643065356631376637333062313762
|
||||
31653661316562643633643135616634353631623463353637616131343531633266393766336232
|
||||
30616138616437323138383732306537326562666239636561383631333534633937396463613765
|
||||
62313064653838623537393839386464653539326132393732393131633335393063393636376235
|
||||
64306566343762626238386632626136396331336564326361303835613732623531396631363564
|
||||
32616631333034313563396532396564663262613733626362663632333938336331633664376638
|
||||
34626466613239353935376230653732366236643830353963653662613338616631346561623063
|
||||
61616565393965613730326530353935323432306465656464626238623438636263666530336531
|
||||
64333864636131343566643164653934306135316437383738323738616362643636383761383433
|
||||
65353762663930636366376365623535333636613766666465316134303131623637343864643461
|
||||
33663934656631333032353734333361363339336266343631363937306632323635663832643266
|
||||
30383633323735333430376662663765303561643563383466333764636131363732636134376462
|
||||
37646564386564373038393135653761373937313039623737623938363631393632346565326166
|
||||
64376637323432303865366436356264633066343466333838303164373234646661346133646561
|
||||
62383838336461306638383266373462326165313161383832316462363835366361386663353935
|
||||
35616433393963613363333965666138363365666366646163336163613237386263343737636335
|
||||
36623864346566383632643035393361616533396538356562343364386330336636383433303538
|
||||
65653630303435373336366639656137386363303439333164633634323730393337333764633461
|
||||
32396161303864333362663466303161366465386338396332333462366339353461373638343961
|
||||
31343466663537636330663962636431366235646164643332343433393063633435646565333863
|
||||
37626239396264653434363538623133366561623436633739646335383534323839663465316339
|
||||
63653731636266343130383035323962333062623730343033613663366431386366623038373238
|
||||
32666537323237336236386135626339363861636439346664376561666538636532396562333435
|
||||
32383366373437303765653664396436623861333164396563623639636131656366316138396131
|
||||
61316432663239346539343261363461643632363432653161383233643431303034353136396465
|
||||
35623836393435396234336261626162326433356134636139303261653430363835333364656161
|
||||
34326533613636366134633332393064356535373432666165386163613961613539333932643963
|
||||
32343639343931623366633764363966636164323933623734333935613462303466656439306331
|
||||
36323261623639303164646435346533353966616138326230363739303063323366323964356534
|
||||
30316534656364373633663464366161373566626639363963356131353936333366326334663438
|
||||
32646463653664656635353564323230343763303538316135643838363134393634363031646530
|
||||
37323838386134316136663139626262396239643263353432613162666235616133623937383261
|
||||
31353138396238313066643964633536636337386238616637383561383563313936373538616334
|
||||
62363233623738613064313762303463303266346538333937343566343936326363323037623032
|
||||
30346434366661643930623761666134366132373936336630623963636331653532363765363263
|
||||
66306133303634343231386164343131333234376662313663393130326664386262363035353565
|
||||
37633863353935363635303039333662366462383235636564313666333162666563376666633966
|
||||
34666130613034393439346337383862643231666632663463393330356263646165316266613936
|
||||
62303536333339303431373531303065613865633339303264323137323732653036333366373664
|
||||
64623330313464356461376662663266333430633161393437363862633564396263353333316662
|
||||
64636262333832646135626534363737656336663562333365666631646261303665333964326337
|
||||
35643964386362323161666464363037623266386539383732626230303830303662366161653063
|
||||
36383539626334376238663539336637343033343037346136383733316265383430396133336139
|
||||
64616135623230613531313733626537366430626262623836386432363561626238303132613830
|
||||
36323533656164376334643436373730653531396236633933393663656435656566626165626164
|
||||
39366465646535383532616466313665333533386230656631343332376166613033343364393361
|
||||
62656237316466663131333661333565643335646166333332393263626463633639306264656666
|
||||
36343330666462373061363466636233333165663662393933343763633333373161626235363138
|
||||
64346433663661636561613636366662376562363638356231646438653532633434623132306330
|
||||
65656433353262623661636263393737623337316535326332373861393331626563326434383266
|
||||
65383931393032376137623833303031303934636135373135626562396364383635623661303431
|
||||
65623132393961396336363763616366633064366331316330396130373239336137373964356136
|
||||
37653162383030653736626564343739383030636531346534386639313063323938363236306537
|
||||
36646132626138643462636466656363353436633863613165613362616264613434303936633661
|
||||
38376661306439313036333431363664616662646533333963656165393939303865616661313864
|
||||
65656338633864663665303961613165386335646661343438333730313138616535363164336463
|
||||
35363563356362396265333334393964356534613763656362323631363237656438653962316336
|
||||
62633330616530376537333961383461323432303161343136326363313137333333323835313065
|
||||
64666132356632613365393137363061363165623733353534303638353432393739346164346535
|
||||
39363464313239386564336436663339303535336633666364373436666635613638643061316235
|
||||
61656266623934633339643063306363633131373335333538643338666339396166626262646434
|
||||
65623962396464343532366639393339306237653633623763333165303636613834333531306235
|
||||
36636430633864396236363638393165653431313139343966316561663138313761653630623436
|
||||
33363037303334383363646337303065343239343537343737616438613165323965333334613234
|
||||
62623931343365336230343037346230366134396435383236643931366631326335626431306366
|
||||
31646339323562653737303465353738343939623037616666656432313833653233373636326130
|
||||
30613434646237306636396239363439383961376539613131333164373333313131343062366638
|
||||
34333963313163303239333439393765386234313534303834636637393965323439366235613666
|
||||
65316336393335656332626165643261316464383232396136336238656662336132646366373263
|
||||
65396534353764616134663964376135623035316432623939376231373731343136313265323862
|
||||
61646238353761646434313430663362633066663763623735343730633036333533356339393665
|
||||
30313763346166653738636234663232323033636332373861373164393737353962323066363238
|
||||
34613066323862353831653438633564633561653034356263663430366432643663633836653764
|
||||
63333964663765353135636234323330623130633736653263336238396631356436346361366463
|
||||
65393239356432386163396135663161626664626137353465333733626635373537396137353437
|
||||
64626139393164313866616132386132303837303437306365336164383265346361383661323130
|
||||
62396333333666613538363332323032653037646563396436613166323166323666363934333064
|
||||
66356665306465653661323331376337653561613761643765666636396531663930333634316230
|
||||
35343733613638626230623464663133343432663764663864343363376339333566633963326630
|
||||
36313264636638396566623334333230363137353262346531623138656364653038373034353137
|
||||
31383533303234636333346332343333336531326363636161623764333762653835396632366436
|
||||
31646566363334633336366231363163643837633564363162656130323331626464333236303637
|
||||
31663439636234623534336535393835653838643430356364353762613966346461313032353939
|
||||
38613932386564623935363135663462626663313130663737373234316231343461616133626264
|
||||
31356163363234383132373730663436333738633562336435383532366138653764306165643863
|
||||
36383362633137643931333735333264656666336363663161643862323337613536383034336462
|
||||
35333165386332366436666439323236346539366530383136626637363539656230336637333432
|
||||
35336235363065306534643762346263393935333238383533646331653332643234386539653230
|
||||
30376237666166323638303465373737383031373864373266306537373335356139646230656264
|
||||
36316536363738323362643230363064363965636563346165356565643265353332386136346435
|
||||
66313937346164313661656364373465656230613261333833323335653133396132356334353237
|
||||
61353832373063666331646664623565353030663530636135336365633366623533653462386237
|
||||
65373462336538663835303831313336396638646534343839343933383032646664306434613230
|
||||
38323333666165363662376430356435363463303936396234313931303734666333376431376562
|
||||
65636132636435396562376637396464356165363937613435626235303730636239626465306237
|
||||
62613335396161303737396231666536353230656533653239353364623733623131333861626637
|
||||
64306437633564623565333664653030366166663835353063316631393864663338656663633736
|
||||
31346166656636396565333663353562613032373462366433323266396135363430623139373963
|
||||
32393864646438326366343036386231353530356233393539646562643339643366346563393162
|
||||
39393830646565326666333063396531356336653162623465313365616564653430663264363164
|
||||
62306562313037646466366463396432373536303631653338343133393262663865636133343464
|
||||
61383237386164633536663230346461383861626666623562313737373138633239333266366132
|
||||
32306135313464363461653363636462366662393938396566333661306366383838636438353738
|
||||
32356666613238356135353536656130346134663666346362353735613338666234626538373139
|
||||
65613262636233366532323630613438663334616164656166623236353531303835326266343663
|
||||
66366364333437373039306634383737663963373663366131396566613030386661656235616662
|
||||
35663533623864303431343631386362623036343135663665383039333363336534613063313534
|
||||
61373939306536376337303561373030343337326437393636366166376630346430666539646266
|
||||
34616435346139313033353130303639393530373662616432343330653762393339396432643935
|
||||
65313638383330613464363666633965393035643037626162616339306563646130306566613064
|
||||
35656130653035363163323831323730633236633736663965653833623131383037323736663066
|
||||
35343235356164623038643230373666366265363437396231613365303935366135636135613265
|
||||
31393163323837663332623636316462656338646566363361333863336365373562666434366530
|
||||
37626236343731313065353037346437356264363233363638393238373063386530646434663564
|
||||
35353562396564316535663436376335393136613338643864323132326431633066373462323330
|
||||
36306263646434333532316365613239376237303232383531343238623833626363346437386235
|
||||
66666565656635306162646430393738666132633065376330393162633066333534383561353336
|
||||
33313162666664613636303962656438626133373137336639356133363563653431646461313538
|
||||
37316433663263333536386632646536616564316362363238626231613162383966333063636132
|
||||
31623436346138613030396462626663616233326662663634633765643864373262393634353937
|
||||
32376437363737616337373333313832373135313535666430386536303236356563613166366332
|
||||
65323332626363663535633234323364663838333862643232376237653031303137303136633462
|
||||
32356431306534626135353462643736373939666130623732303562393932636231373534626132
|
||||
32343464353538356231303535396365633465333230353261346533333063303135303135393465
|
||||
34656333613966663966646162386633333830323234613836633236626134316162663139656530
|
||||
63623666313138666130326139386636646230353637353561323736656437616438633839613837
|
||||
62353931313562383830393433313232653461363436353064653039313738396138333263633965
|
||||
32316236363233613831303436323835616332646262343336353037323362313265336634393261
|
||||
35393761396537373636313665373830326463643136303961316238336238623466396334343664
|
||||
61396234663862343937633131623939306135646231373633653534353363383439656539333331
|
||||
30303536353462666433626236343163653139333038656662666664626439613639636564393835
|
||||
30363165616263656535303439636563643663633434613665613339313162363234623131306630
|
||||
34653932376634363264343335343064343838626464666661313533383534383833633863386463
|
||||
37633664396537363735353161346238656336396565383637313230636561626362656232333036
|
||||
32386538623063613331623662666531626362616430386335333664646234613965643733656236
|
||||
31303766326536326461353632353730303434333866353730383562383533623338626232346330
|
||||
64396163613663363562633635656566313263383262656666393537653233363166656335653034
|
||||
32376335623633373431376266663036616635343231663963363936353137376263656133623735
|
||||
65656435393733613762376638613034643466313632646431656461313362363561623463393238
|
||||
34373830373633373835376234366532363763316632613766366161613130633762313839386363
|
||||
61396632333533663964373463323464323035386236376364646332663431383039623830333137
|
||||
62313062393236363338616433613033306563323264316332366161346235356539323232653931
|
||||
66313862383935316235653663336666666339343534376336336138366639633538643937303665
|
||||
39386262306566386464363337336130633038633837663166333639343161373234303230306338
|
||||
30323063393365336332663531316365356531386365343836656437613337346666653637633639
|
||||
38653465393236633962373739323331623565643938373563376364333863343030333136333863
|
||||
64663132336165616462336232323865636630373533643063383730626565666337636539373136
|
||||
32333861656163646332323239653364326163613361373435323966323964313831663138353063
|
||||
34653131383463623965386365326531616265653837363462653037386633376361383639656163
|
||||
39663338383939323535366135396465656333323331386462353630396237396261316137386165
|
||||
38383664613165356535356632376138623233386266336665386330376539373963323162613336
|
||||
62396364373031656238356163656464616430316131386537356236663536366461326462373236
|
||||
62396363303861303533643438343765356437326663316239613939386134333830303739386464
|
||||
63623834366565366163383533393532383061646163666133396136643462376537393935613039
|
||||
37623134313439393737626664663637343365373563376261666133346332373033613335316164
|
||||
39313733653931383665663238303532353165613131323731306362666138643737346337623064
|
||||
34666261323664363864373061666233613163336633383837323766386137323431626337623562
|
||||
36336234656635633830366635363463313439646165353261323030323261633737346664373738
|
||||
39633836366133633635303638366134393435393663366664623837336164323630353739333462
|
||||
36366663643931653230653562326633353637383564623266313936656239393535646131373261
|
||||
61356661663534393166383530373739343834313136663366316237333464326563336636656232
|
||||
66373035623866383931393238666132386331356530616535313164366466353563653865663832
|
||||
39353364396366663462366333306135343065373532663330643435336239353435303762326132
|
||||
65343630343532656638653462383266383937663466633034373661643862663933333838653032
|
||||
33656261396638666334626366356362623663613461303936323130353436363536663638383162
|
||||
63663931313335666435616561353266376134633132633233356435313137653132373935613931
|
||||
36623461396139633439396332393037616361316562613137646466613330653234656535623230
|
||||
66306434653462633834643036656263376366623835653163326461313831633934366663646566
|
||||
65393966373466326137353261383763346666386337666130646133643666656464616438383038
|
||||
31306130356238366261616231363933666162373639363463303962303833626135366666303962
|
||||
33316161343936393634303964396566653963333237393631353938386262623739393432383339
|
||||
37653430336265323133363937303362343336373230633333323764346139326662343434396566
|
||||
66383035613262366430303636323236663937393933646566373838383562316334633836633737
|
||||
65346537353439306363633431396338396632336565386434363530346365333765363930613063
|
||||
65333166393136633665663539656331663037353138366239383332373363366562613237373234
|
||||
64303963666462323330653364396464383464666434343831313338633764636337363761616337
|
||||
63313462303339643364383361376364366232326361613137326135333263313964616634383464
|
||||
61333539373962653338313835656632663261363766346535323735633437323736323632343434
|
||||
61643662613261613835366331303439313062313033623065323262356466656533623735643235
|
||||
61643332363333363032326434363065303566616661386261386165383066306161376531356334
|
||||
63323634356566333065383763333965613366656661313931386532623963383265613131666231
|
||||
61363465343462313531313464323731356637393061663133336665363965653035333030383062
|
||||
33653335363536656333333738356437316332313361633136396535643836633035303830323232
|
||||
65633761333135633739323061613436386166383762353564326430363965623461306235386132
|
||||
66366632643763306338373064613935643630616661353233663834396331643130396465636534
|
||||
33303830383139343836633163313737313738636365346639396162343561383965323636306466
|
||||
63343038363330313363663566656464383264326538303466323862663463353930376165326466
|
||||
66383630376465376139646562633065383434333365376362643038643064656364363538356661
|
||||
35303432646332623337353465656362656234333465383663353735663537643630616266383763
|
||||
30363239363132333166363563376665633936656535663164656532393666336265393964636333
|
||||
35323539383835366162326362333537363666386162376561373464323630303032613037636636
|
||||
37663135623933643938396635323763353931336531313732636530643865663430646530666465
|
||||
34383962353137366431633530376134323635336433653361316363316132383834663030393666
|
||||
66303935343839303337636630313138336239393062666265646163386332376536336439616463
|
||||
66316165393963333235353931326461626130306435373061626233616430343064303537373839
|
||||
63396464306164613863333434373063396534623739373730303135626639663731366666343733
|
||||
32313862306431626231373963396461656637313364343661663235323237316432353762383330
|
||||
32353736643766323964643035653661363532336261353334363936333766613739623638333861
|
||||
30343933613161643862623263623838623866356534323362623838656133663363353366633764
|
||||
38323931346630616664316365363463653637663762633138323230356263636634396165613337
|
||||
32656666363137623833366561643563613938623565636132313539383130633637313236333365
|
||||
32623663333164653733643063396230313033323636323164643534376333346637636436303935
|
||||
34346539663366623137326133383533396535376131626631326463323866353363656636313538
|
||||
33393731356632313263663533633762313534346435326533346635356162326466316662323230
|
||||
62376666666339623638356138353461313431333366633833323836313338643333366161306139
|
||||
35383163373139636137356235656564303666346531663663326134656165616435616163323361
|
||||
61346131613666653431656464353636303663653934393835373332663432303762393538616465
|
||||
63363566396464333861386135336132623539626432383762313762316135623566373937643532
|
||||
64653934383662663263303833383034366436613138373863316334376564616235313462363637
|
||||
62313166623261626537323631626432393033626233353137366262613134333565666666326332
|
||||
62616662653334323535373235616433653233336265343661363765623066363762353836666364
|
||||
35373865363165363234333038323437313561646338343930316661633365623938623665623434
|
||||
66383562653564623732356130326138346236343766306535386362373664623162393833656230
|
||||
35643535353238336361646534313133303835363837353966326538313161353737363939633961
|
||||
39626137366133373536326535613362313866303232396233313263363736646139366363303463
|
||||
65363266323565303733613065366237636233306137346233333839646438623038343764346462
|
||||
35306434336363333562356566636335613263643738313766636534316232653137346166363932
|
||||
32386336653265663665623736663330623036323762623562346164373631313533396437613438
|
||||
63373632633731333361366462653466346338366139353632616661626630306633363061373937
|
||||
36386635653537346535363030326566366165393839653534383065336638303862373366633263
|
||||
33633461663764356330333534373036343565313335363731646432323931613737353231636435
|
||||
38626361393264363039303831633535643366636365656538343763646538336138623961303064
|
||||
31343431616337316661313935333735613662383235663730373632336333346338623432623731
|
||||
39303261326363656638333165646661333735316130363234343661393463346132333333633533
|
||||
36393635396234623065626463646536633139333662396635303537653338313532313062653339
|
||||
65656639646364346265386361623330333666326338386334376366306339653230633334373438
|
||||
36333735393664626136
|
||||
|
||||
16
ansible/host_vars/carousel-of-progress/vars.yml
Normal file
16
ansible/host_vars/carousel-of-progress/vars.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/host_vars/astro_orbiter/vars.yml
|
||||
# HOST: astro-orbiter (10.1.71.130)
|
||||
# ROLE: Ollama inference host with AMD RX 5700 GPU passthrough
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
ansible_host: 10.1.71.131
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
# LVM root expansion — xlarge template uses sda3 partition, standard VG/LV names
|
||||
common_expand_root_lvm: true
|
||||
common_root_pv: /dev/sda3
|
||||
common_root_vg: ubuntu-vg
|
||||
common_root_lv: ubuntu-lv
|
||||
16
ansible/host_vars/main-street-station/main.yml
Normal file
16
ansible/host_vars/main-street-station/main.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
# Host-specific vars for main-street-station (JMRI headless server)
|
||||
# LCRR - Lake Country Railroad, Milwaukee Road Oct 1956, HO scale
|
||||
|
||||
# JMRI profile ID — find with: ls ~/.jmri/profiles/ on the old box
|
||||
# Format: <name>.<8-char-hex> e.g. LCRR.3d3f1dfc
|
||||
# TODO: fill in after restoring config from GitHub backup
|
||||
jmri_profile_id: ""
|
||||
|
||||
# USB serial device for NCE command station
|
||||
# Verify after install: ls -la /dev/ttyUSB* /dev/ttyACM*
|
||||
jmri_serial_device: /dev/ttyUSB0
|
||||
|
||||
# Path to JMRI config backup for restore task (leave empty to skip)
|
||||
# Point at a local checkout of the LCRR GitHub repo
|
||||
jmri_config_src: ""
|
||||
10
ansible/host_vars/main-street-station/vars.yml
Normal file
10
ansible/host_vars/main-street-station/vars.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# main-street-station — JMRI / LCRR server
|
||||
jmri_profile_id: "Lake_Country_Railroad.3e8b1d4b"
|
||||
jmri_lcrr_repo: "ssh://git@gitea.mk-labs.cloud:2221/rblundon/LCRR.git"
|
||||
jmri_lcrr_branch: "clean-profile"
|
||||
jmri_leviton_email: "{{ leviton_email }}"
|
||||
jmri_leviton_password: "{{ leviton_password }}"
|
||||
jmri_ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINnSM/9fO8rz/amqkyoGUzUKNNzzmtSXPwOCr1O9zKNO ansible"
|
||||
jmri_ssh_authorized_keys_extra:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6HaK4Y21UwPRbAZ986L7I9QnUdyq53114+9kO8X4bL rblundon@laptop"
|
||||
@@ -50,8 +50,9 @@ nextcloud_server:
|
||||
|
||||
semaphore_server:
|
||||
hosts:
|
||||
imagineering:
|
||||
figment:
|
||||
ansible_host: 10.1.71.37
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
n8n_server:
|
||||
@@ -65,6 +66,27 @@ ollama_server:
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
hermes_server:
|
||||
hosts:
|
||||
carousel-of-progress:
|
||||
ansible_host: 10.1.71.131
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
honcho_server:
|
||||
hosts:
|
||||
lincoln:
|
||||
ansible_host: 10.1.71.132
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
jmri_server:
|
||||
hosts:
|
||||
main-street-station:
|
||||
ansible_host: 192.168.10.40
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
papermc_server:
|
||||
# ansible-galaxy role install engonzal.papermc
|
||||
hosts:
|
||||
@@ -73,6 +95,10 @@ papermc_server:
|
||||
dev_servers:
|
||||
hosts:
|
||||
scrim:
|
||||
backstage:
|
||||
ansible_host: 10.1.71.133
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
# dhcp_server:
|
||||
# hosts:
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#
|
||||
# 1. Syncs boilerplates/traefik/dynamic/ to lightning-lane
|
||||
# 2. Scans the directory for service configs
|
||||
# 3. Creates CNAME records for each service -> lightning-lane
|
||||
# 3. Extracts all hostnames from Host() rules (supports multi-host)
|
||||
# 4. Creates CNAME records for each hostname -> lightning-lane
|
||||
#
|
||||
# PREREQUISITES:
|
||||
# - Service dynamic config YAML committed to boilerplates/traefik/dynamic/
|
||||
@@ -14,15 +15,6 @@
|
||||
#
|
||||
# USAGE:
|
||||
# ansible-playbook -i inventory.yml playbooks/add_service_route.yml
|
||||
#
|
||||
# ADDING A NEW SERVICE:
|
||||
# 1. Create boilerplates/traefik/dynamic/<service>.yml
|
||||
# 2. Commit and push
|
||||
# 3. Run this playbook
|
||||
#
|
||||
# EXCLUDING FILES:
|
||||
# Files that are not service routes (e.g., default.yml for middleware
|
||||
# definitions) should be added to the exclude_configs list below.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Sync Traefik routes and ensure DNS records
|
||||
@@ -33,13 +25,12 @@
|
||||
vars:
|
||||
base_domain: "local.mk-labs.cloud"
|
||||
dns_server: "monorail"
|
||||
traefik_host: "10.1.71.35"
|
||||
traefik_host: "lightning-lane.local.mk-labs.cloud"
|
||||
traefik_user: "wed"
|
||||
traefik_dynamic_path: "/opt/docker/traefik/dynamic/"
|
||||
dynamic_config_dir: "{{ playbook_dir }}/../../boilerplates/traefik/dynamic"
|
||||
|
||||
# Files in the dynamic directory that are NOT service routes
|
||||
# (middleware definitions, TLS options, etc.)
|
||||
exclude_configs:
|
||||
- default.yml
|
||||
|
||||
@@ -53,38 +44,52 @@
|
||||
register: sync_result
|
||||
changed_when: "'sending incremental file list' in sync_result.stdout"
|
||||
|
||||
# ── Step 2: Discover service configs ──
|
||||
# ── Step 2: Discover hostnames from Traefik router rules ──
|
||||
- name: Find all dynamic config files
|
||||
ansible.builtin.find:
|
||||
paths: "{{ dynamic_config_dir }}"
|
||||
patterns: "*.yml"
|
||||
register: config_files
|
||||
|
||||
- name: Build service list from config filenames
|
||||
ansible.builtin.set_fact:
|
||||
service_names: >-
|
||||
{{ config_files.files
|
||||
| map(attribute='path')
|
||||
| map('basename')
|
||||
| reject('in', exclude_configs)
|
||||
| map('regex_replace', '\.yml$', '')
|
||||
| list }}
|
||||
- name: Read config files
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ item.path }}"
|
||||
register: slurped_configs
|
||||
loop: "{{ config_files.files }}"
|
||||
when: item.path | basename not in exclude_configs
|
||||
|
||||
- name: Display services to route
|
||||
- name: Extract all hostnames from Host() rules
|
||||
ansible.builtin.set_fact:
|
||||
hostnames: >-
|
||||
{% set hosts = [] -%}
|
||||
{% for result in slurped_configs.results if result.content is defined -%}
|
||||
{% set content = result.content | b64decode -%}
|
||||
{% for match in content | regex_findall('Host\(`([^`]+)`\)') -%}
|
||||
{% for h in match.split(' || ') -%}
|
||||
{% set h = h | regex_replace('`', '') | trim -%}
|
||||
{% if h.endswith('.local.mk-labs.cloud') and h not in hosts -%}
|
||||
{% set _ = hosts.append(h) -%}
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
{% endfor -%}
|
||||
{% endfor -%}
|
||||
{{ hosts | unique | list }}
|
||||
|
||||
- name: Display hostnames to create
|
||||
ansible.builtin.debug:
|
||||
msg: "Services found: {{ service_names }}"
|
||||
msg: "Hostnames found: {{ hostnames }}"
|
||||
|
||||
# ── Step 3: Create DNS CNAME records ──
|
||||
- name: Create DNS CNAME record for each service
|
||||
- name: Create DNS CNAME record for each hostname
|
||||
effectivelywild.technitium_dns.technitium_dns_add_record:
|
||||
api_url: "http://{{ dns_server }}.{{ base_domain }}"
|
||||
api_token: "{{ vault_technitium_api_key }}"
|
||||
zone: "{{ base_domain }}"
|
||||
name: "{{ item }}.{{ base_domain }}"
|
||||
name: "{{ item }}"
|
||||
type: "CNAME"
|
||||
cname: "lightning-lane.{{ base_domain }}"
|
||||
ttl: 360
|
||||
validate_certs: false
|
||||
loop: "{{ service_names }}"
|
||||
loop: "{{ hostnames }}"
|
||||
loop_control:
|
||||
label: "{{ item }}.{{ base_domain }}"
|
||||
label: "{{ item }}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
- name: Apply common role
|
||||
- name: Apply day0 baseline
|
||||
hosts: "{{ target | default('all') }}"
|
||||
become: true
|
||||
roles:
|
||||
- common
|
||||
- day0-baseline
|
||||
32
ansible/playbooks/day0_expand_root_lv.yml
Normal file
32
ansible/playbooks/day0_expand_root_lv.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# day0_expand_root_lv.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Reclaims unallocated PE on the root volume group, extending the root LV
|
||||
# to fill the VG and resizing the underlying filesystem (ext4 or xfs).
|
||||
#
|
||||
# Belongs to the day0 host-provisioning lifecycle. The Ubuntu Server
|
||||
# autoinstall template ships with the root LV at ~half the disk size by
|
||||
# default; this playbook is the canonical one-shot fix-up for that.
|
||||
#
|
||||
# Idempotent and safe to re-run. Hosts without LVM are no-op'd cleanly.
|
||||
#
|
||||
# Opt-out: set `expand_root_lv_skip: true` in host_vars/<host>.yml for
|
||||
# hosts where free PE should NOT be claimed by root (e.g. hosts with a
|
||||
# planned second LV in the same VG for application data).
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook playbooks/day0_expand_root_lv.yml
|
||||
# ansible-playbook playbooks/day0_expand_root_lv.yml -e target=lincoln
|
||||
# ansible-playbook playbooks/day0_expand_root_lv.yml -e target=honcho_server
|
||||
# ============================================================================
|
||||
|
||||
- name: Expand root logical volume to fill VG
|
||||
hosts: "{{ target | default('all') }}"
|
||||
become: true
|
||||
gather_facts: true
|
||||
tasks:
|
||||
- name: Apply expand_root_lv role unless host opts out
|
||||
ansible.builtin.include_role:
|
||||
name: expand_root_lv
|
||||
when: not (expand_root_lv_skip | default(false) | bool)
|
||||
23
ansible/playbooks/day0_linux_baseline.yml
Normal file
23
ansible/playbooks/day0_linux_baseline.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# day0_linux_baseline.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Applies the mk-labs Linux baseline (linux-baseline role) to one or more
|
||||
# hosts. Idempotent and safe to re-run.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml -e target=figment
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml -e target=semaphore_server
|
||||
#
|
||||
# To trigger an opt-in full system upgrade:
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml \
|
||||
# -e target=figment -e 'baseline_features={"full_upgrade": true}'
|
||||
# ============================================================================
|
||||
|
||||
- name: Apply mk-labs Linux baseline
|
||||
hosts: "{{ target | default('all') }}"
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- linux-baseline
|
||||
30
ansible/playbooks/day0_provision.yml
Normal file
30
ansible/playbooks/day0_provision.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# day0_provision.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Umbrella day0 playbook. Runs the full host-provisioning lifecycle in
|
||||
# the correct order against newly-built VMs, so the operator runs ONE
|
||||
# command per new host rather than chaining day0 steps manually.
|
||||
#
|
||||
# Order matters:
|
||||
# 1. linux-baseline — timezone, NTP, packages, SSH hardening, jarvis user
|
||||
# 2. expand_root_lv — reclaim PE left unallocated by the Ubuntu
|
||||
# autoinstall template default
|
||||
#
|
||||
# Idempotent: every step is safe to re-run. Suitable to apply periodically
|
||||
# from Semaphore as a baseline-drift check.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook playbooks/day0_provision.yml -e target=lincoln
|
||||
# ansible-playbook playbooks/day0_provision.yml -e target=honcho_server
|
||||
#
|
||||
# For finer control over a single phase, the constituent playbooks are:
|
||||
# playbooks/day0_linux_baseline.yml
|
||||
# playbooks/day0_expand_root_lv.yml
|
||||
# ============================================================================
|
||||
|
||||
- name: Import day0 linux baseline
|
||||
ansible.builtin.import_playbook: day0_linux_baseline.yml
|
||||
|
||||
- name: Import day0 expand root LV
|
||||
ansible.builtin.import_playbook: day0_expand_root_lv.yml
|
||||
79
ansible/playbooks/day1_deploy_hermes.yml
Normal file
79
ansible/playbooks/day1_deploy_hermes.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# day1_deploy_hermes.yml
|
||||
# Deploy Hermes Agent (Nous Research) on carousel-of-progress (10.1.71.131)
|
||||
#
|
||||
# FIRST-RUN WORKFLOW:
|
||||
# 1. Run this playbook:
|
||||
# ansible-playbook playbooks/day1_deploy_hermes.yml
|
||||
#
|
||||
# 2. SSH to the host and run the setup wizard as the hermes user:
|
||||
# ssh wed@carousel-of-progress.local.mk-labs.cloud
|
||||
# sudo -u hermes hermes setup
|
||||
#
|
||||
# 3. Once configured, start and verify the service:
|
||||
# sudo systemctl start hermes
|
||||
# sudo systemctl status hermes
|
||||
# sudo journalctl -u hermes -f
|
||||
#
|
||||
# VARIABLES:
|
||||
# hermes_skip_browser: true — set to skip Playwright/Chromium install
|
||||
# (saves ~300MB if browser automation not needed)
|
||||
# =============================================================================
|
||||
|
||||
- name: Deploy Hermes Agent on carousel-of-progress
|
||||
hosts: carousel-of-progress
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Verify target is carousel-of-progress
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- inventory_hostname == "carousel-of-progress"
|
||||
fail_msg: >
|
||||
This playbook is scoped to carousel-of-progress only.
|
||||
Got: {{ inventory_hostname }}
|
||||
|
||||
- name: Confirm OS is Ubuntu
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_distribution == "Ubuntu"
|
||||
fail_msg: >
|
||||
This playbook requires Ubuntu. Found: {{ ansible_distribution }}.
|
||||
(If running Fedora, swap apt tasks for dnf and adjust Playwright deps.)
|
||||
|
||||
roles:
|
||||
- role: hermes
|
||||
vars:
|
||||
hermes_skip_browser: false # set true to skip Chromium install
|
||||
|
||||
post_tasks:
|
||||
- name: Verify hermes binary is accessible system-wide
|
||||
ansible.builtin.command: hermes --version
|
||||
register: hermes_version_check
|
||||
changed_when: false
|
||||
failed_when: hermes_version_check.rc != 0
|
||||
|
||||
- name: Print hermes version
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ hermes_version_check.stdout }}"
|
||||
|
||||
- name: Print post-install instructions
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "============================================================"
|
||||
- "Hermes installed on carousel-of-progress (10.1.71.131)"
|
||||
- "============================================================"
|
||||
- "Next steps:"
|
||||
- " 1. SSH to the host:"
|
||||
- " ssh wed@carousel-of-progress.local.mk-labs.cloud"
|
||||
- " 2. Run the setup wizard as the hermes user:"
|
||||
- " sudo -u hermes hermes setup"
|
||||
- " 3. After config, start the service:"
|
||||
- " sudo systemctl start hermes"
|
||||
- " 4. Verify:"
|
||||
- " sudo systemctl status hermes"
|
||||
- " sudo journalctl -u hermes -f"
|
||||
- "============================================================"
|
||||
- "Service is ENABLED but NOT STARTED — config required first."
|
||||
- "============================================================"
|
||||
18
ansible/playbooks/day1_deploy_honcho.yml
Normal file
18
ansible/playbooks/day1_deploy_honcho.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# day1_deploy_honcho.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Deploys Honcho + pgvector PostgreSQL on the `lincoln` host. Assumes day0
|
||||
# host provisioning (linux-baseline + expand_root_lv) is already complete.
|
||||
#
|
||||
# Run via:
|
||||
# ansible-playbook -i inventory.yml playbooks/day0_provision.yml -e target=lincoln
|
||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_honcho.yml
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy Honcho on lincoln
|
||||
hosts: honcho_server
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- honcho
|
||||
24
ansible/playbooks/day1_deploy_jmri.yml
Normal file
24
ansible/playbooks/day1_deploy_jmri.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# day1_deploy_jmri.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Deploys JMRI JmriFaceless headless server on main-street-station.
|
||||
# Applies linux-baseline first, then the jmri role.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook playbooks/day1_deploy_jmri.yml
|
||||
# ansible-playbook playbooks/day1_deploy_jmri.yml -e target=main-street-station
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Host is in inventory under jmri_server group
|
||||
# 2. jmri_profile_id is set in host_vars/main-street-station.yml
|
||||
# 3. SSH access as 'wed' with sudo
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy JMRI headless server
|
||||
hosts: "{{ target | default('jmri_server') }}"
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- linux-baseline
|
||||
- jmri
|
||||
@@ -1,22 +1,17 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: playbooks/day1_deploy_semaphore.yml
|
||||
# DESCRIPTION: Deploys Semaphore on imagineering
|
||||
# Runs: common → docker-host → semaphore
|
||||
# ============================================================================
|
||||
# day1_deploy_semaphore.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Deploys SemaphoreUI + PostgreSQL on the imagineering host (figment).
|
||||
# Run AFTER day0_linux_baseline.yml has been applied to the target.
|
||||
#
|
||||
# USAGE:
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_semaphore.yml
|
||||
#
|
||||
# SECRETS REQUIRED IN VAULT (group_vars/all/vault):
|
||||
# vault_semaphore_database_password
|
||||
# vault_semaphore_admin_password
|
||||
# vault_semaphore_access_key_encryption
|
||||
# ------------------------------------------------------------------------------
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy Semaphore
|
||||
- name: Deploy SemaphoreUI on imagineering
|
||||
hosts: semaphore_server
|
||||
become: true
|
||||
|
||||
gather_facts: true
|
||||
roles:
|
||||
- docker-host
|
||||
- semaphore
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/tasks/main.yml
|
||||
# DESCRIPTION: Baseline configuration applied to all managed Ubuntu hosts.
|
||||
# Handles hostname, timezone, core packages, NTP, ansible user,
|
||||
# and optional LVM root volume expansion.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Set hostname
|
||||
hostname:
|
||||
name: "{{ inventory_hostname | replace('_', '-') }}"
|
||||
|
||||
- name: Set timezone
|
||||
timezone:
|
||||
name: "{{ common_timezone }}"
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Install base utility packages
|
||||
apt:
|
||||
name: "{{ common_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Install chrony
|
||||
apt:
|
||||
name: chrony
|
||||
state: present
|
||||
|
||||
- name: Configure chrony to use sundial
|
||||
template:
|
||||
src: chrony.conf.j2
|
||||
dest: /etc/chrony/chrony.conf
|
||||
mode: '0644'
|
||||
notify: restart chrony
|
||||
|
||||
- name: Ensure chrony is enabled and running
|
||||
systemd:
|
||||
name: chrony
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Ensure ansible user has passwordless sudo
|
||||
lineinfile:
|
||||
path: /etc/sudoers.d/{{ ansible_user }}
|
||||
line: "{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL"
|
||||
create: yes
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# LVM root volume expansion
|
||||
# Extends the root PV to the full disk size and grows the LV + filesystem.
|
||||
# This codifies what was previously done manually after provisioning.
|
||||
# Runs only when common_expand_root_lvm is true (default: true).
|
||||
# Safe to re-run — pvresize and lvextend are idempotent when already at max.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Expand root PV to full disk size
|
||||
command: pvresize {{ common_root_pv }}
|
||||
register: pvresize_result
|
||||
changed_when: "'changed' in pvresize_result.stdout or pvresize_result.rc == 0"
|
||||
when: common_expand_root_lvm | bool
|
||||
|
||||
- name: Extend root LV to 100% of free VG space
|
||||
lvol:
|
||||
vg: "{{ common_root_vg }}"
|
||||
lv: "{{ common_root_lv }}"
|
||||
size: +100%FREE
|
||||
resizefs: yes
|
||||
when:
|
||||
- common_expand_root_lvm | bool
|
||||
ignore_errors: yes
|
||||
# ignore_errors because lvextend returns non-zero when already at max size.
|
||||
# resizefs: yes handles the resize2fs call inline — no separate task needed.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
# file: roles/semaphore/defaults/main.yml
|
||||
|
||||
semaphore_compose_dir: /opt/docker/semaphore
|
||||
semaphore_ssh_key_file: "~/.ssh/ansible"
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# file: roles/semaphore/handlers/main.yml
|
||||
|
||||
- name: Restart Semaphore
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ semaphore_compose_dir }}"
|
||||
state: restarted
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
# file: roles/semaphore/tasks/main.yml
|
||||
|
||||
- name: Create Semaphore directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ semaphore_compose_dir }}"
|
||||
state: directory
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy Compose file from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/semaphore/compose.yaml"
|
||||
dest: "{{ semaphore_compose_dir }}/compose.yaml"
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0644'
|
||||
|
||||
- name: Deploy Semaphore .env file
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ semaphore_compose_dir }}/.env"
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0600'
|
||||
|
||||
- name: Copy SSH key for Ansible authentication
|
||||
ansible.builtin.copy:
|
||||
src: "{{ semaphore_ssh_key_file }}"
|
||||
dest: "{{ semaphore_compose_dir }}/ansible_key"
|
||||
owner: "1001"
|
||||
group: "1001"
|
||||
mode: '0600'
|
||||
|
||||
- name: Start Semaphore containers
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ semaphore_compose_dir }}"
|
||||
state: present
|
||||
register: semaphore_compose
|
||||
|
||||
- name: Wait for Semaphore to be ready
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/ping"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: semaphore_health
|
||||
retries: 48
|
||||
delay: 5
|
||||
until: semaphore_health.status == 200
|
||||
@@ -1,3 +0,0 @@
|
||||
DATABASE_PASSWORD={{ vault_semaphore_database_password }}
|
||||
SEMAPHORE_ADMIN_PASSWORD={{ vault_semaphore_admin_password }}
|
||||
SEMAPHORE_ACCESS_KEY_ENCRYPTION={{ vault_semaphore_access_key_encryption }}
|
||||
@@ -1,21 +1,6 @@
|
||||
# requirements.yml
|
||||
---
|
||||
collections:
|
||||
- name: community.general
|
||||
version: 11.4.1
|
||||
|
||||
# - name: nccurry.openshift
|
||||
# version: 1.4.0
|
||||
|
||||
- name: somaz94.ansible_k8s_iac_tool
|
||||
version: 1.1.6
|
||||
|
||||
# - name: prometheus.prometheus
|
||||
# version: 0.27.0
|
||||
|
||||
- name: kubernetes.core
|
||||
|
||||
- name: community.proxmox
|
||||
version: 1.4.0
|
||||
|
||||
- name: effectivelywild.technitium_dns
|
||||
- name: containers.podman
|
||||
version: ">=1.10.0"
|
||||
- name: effectivelywild.technitium_dns
|
||||
version: ">=1.1.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ common_timezone: America/Chicago
|
||||
common_ntp_server: "sundial.local.mk-labs.cloud"
|
||||
|
||||
common_packages:
|
||||
- acl
|
||||
- curl
|
||||
- wget
|
||||
- vim
|
||||
21
ansible/roles/common/tasks/main.yml
Normal file
21
ansible/roles/common/tasks/main.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
- name: Create cast group
|
||||
ansible.builtin.group:
|
||||
name: cast
|
||||
state: present
|
||||
system: true
|
||||
|
||||
- name: Create cast user
|
||||
ansible.builtin.user:
|
||||
name: cast
|
||||
group: cast
|
||||
shell: /bin/bash
|
||||
create_home: true
|
||||
state: present
|
||||
|
||||
- name: Ensure cast user has passwordless sudo
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/sudoers.d/cast
|
||||
line: "cast ALL=(ALL) NOPASSWD:ALL"
|
||||
create: yes
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
12
ansible/roles/day0-baseline/defaults/main.yml
Normal file
12
ansible/roles/day0-baseline/defaults/main.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
# Timezone
|
||||
timezone: "UTC"
|
||||
|
||||
# Packages to ensure are present
|
||||
baseline_packages:
|
||||
- chrony
|
||||
- vim
|
||||
- htop
|
||||
- curl
|
||||
- wget
|
||||
- rsync
|
||||
6
ansible/roles/day0-baseline/handlers/main.yml
Normal file
6
ansible/roles/day0-baseline/handlers/main.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart sshd
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
state: restarted
|
||||
become: true
|
||||
32
ansible/roles/day0-baseline/tasks/main.yml
Normal file
32
ansible/roles/day0-baseline/tasks/main.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Set timezone
|
||||
community.general.timezone:
|
||||
name: "{{ timezone }}"
|
||||
become: true
|
||||
|
||||
- name: Enable and start chronyd
|
||||
ansible.builtin.systemd:
|
||||
name: chronyd
|
||||
state: started
|
||||
enabled: true
|
||||
become: true
|
||||
|
||||
- name: Update all packages
|
||||
ansible.builtin.package:
|
||||
name: "*"
|
||||
state: latest
|
||||
become: true
|
||||
|
||||
- name: Install baseline packages
|
||||
ansible.builtin.package:
|
||||
name: "{{ baseline_packages }}"
|
||||
state: present
|
||||
become: true
|
||||
|
||||
- name: Disable root SSH login
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^PermitRootLogin'
|
||||
line: "PermitRootLogin no"
|
||||
become: true
|
||||
notify: Restart sshd
|
||||
70
ansible/roles/expand_root_lv/README.md
Normal file
70
ansible/roles/expand_root_lv/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# expand-root-lv role
|
||||
|
||||
Idempotent role that extends the root LVM logical volume to fill its
|
||||
volume group and grows the underlying filesystem (ext4 or xfs).
|
||||
|
||||
## Where this runs in the lifecycle
|
||||
|
||||
Part of the **day0** host-provisioning lifecycle. The canonical entry
|
||||
points are:
|
||||
|
||||
```
|
||||
playbooks/day0_expand_root_lv.yml # standalone
|
||||
playbooks/day0_provision.yml # umbrella (baseline + expand_root_lv)
|
||||
```
|
||||
|
||||
Day1 application-deploy playbooks should NOT include this role —
|
||||
day0 is assumed complete before day1 begins.
|
||||
|
||||
## Why this role exists
|
||||
|
||||
The Ubuntu Server autoinstall template (used by the mk-labs `wed`-baked
|
||||
VM templates) provisions the root LV at roughly half the available disk
|
||||
size — a longstanding installer default that surprises every operator
|
||||
who hasn't been bitten by it before. ~90% of mk-labs VMs need this
|
||||
fix-up before they're fully useful.
|
||||
|
||||
## Idempotency
|
||||
|
||||
- If `vg_free_count == 0`, the `lvextend` step is skipped and the
|
||||
filesystem-grow step is also skipped (nothing to resize against).
|
||||
- If the target volume group doesn't exist on the host (e.g. a non-LVM
|
||||
layout), the role exits cleanly via `meta: end_play`.
|
||||
- Safe to leave in a recurring playbook so future disk expansions
|
||||
(Proxmox-side disk grow → reboot → run role) are picked up
|
||||
automatically.
|
||||
|
||||
## Opt-out for multi-LV hosts
|
||||
|
||||
If a host will have a **second logical volume in the same VG** (e.g. a
|
||||
dedicated `/var/lib/postgresql` LV for a database server), this role's
|
||||
"grow root to fill VG" behavior is wrong — it will consume the free PE
|
||||
that was being reserved for the second LV.
|
||||
|
||||
Set in `host_vars/<host>.yml`:
|
||||
|
||||
```yaml
|
||||
expand_root_lv_skip: true
|
||||
```
|
||||
|
||||
The day0 playbook checks this flag and skips the role cleanly.
|
||||
|
||||
## Defaults
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|-------------------------------|---------------|-----------------------------------------------------|
|
||||
| `expand_root_lv_vg_name` | `ubuntu-vg` | LVM volume group name (Ubuntu installer default). |
|
||||
| `expand_root_lv_lv_name` | `ubuntu-lv` | LVM logical volume name (Ubuntu installer default). |
|
||||
| `expand_root_lv_mountpoint` | `/` | Mountpoint of the filesystem to grow. |
|
||||
|
||||
Override the VG/LV names in `host_vars/<host>.yml` for hosts that use a
|
||||
different LVM layout.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Does not extend the underlying partition. If the operator grows the
|
||||
Proxmox disk and the partition itself needs to grow before lvextend
|
||||
can claim the new space, run `growpart /dev/sda 3` (or equivalent)
|
||||
first. A future enhancement could automate this via `cloud-utils`'
|
||||
`growpart` package, but it's out of scope for the initial template
|
||||
fix-up case where the partition already covers the whole disk.
|
||||
26
ansible/roles/expand_root_lv/defaults/main.yml
Normal file
26
ansible/roles/expand_root_lv/defaults/main.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# expand-root-lv role defaults
|
||||
# ============================================================================
|
||||
# Extends the root LVM logical volume to fill its volume group, then grows
|
||||
# the underlying filesystem to match. Idempotent: when there's no free PE
|
||||
# in the VG (i.e. the LV already fills the VG), the lvextend step is a
|
||||
# no-op and resize2fs/xfs_growfs simply confirms the filesystem is at
|
||||
# capacity.
|
||||
#
|
||||
# Designed for Ubuntu cloud-image-style installations where the autoinstall
|
||||
# template provisions an LV at half the disk size (the Ubuntu Server
|
||||
# installer's longstanding default). Run once after VM provisioning to
|
||||
# reclaim the unallocated PE; safe to leave in a day1 playbook so future
|
||||
# disk expansions are picked up automatically.
|
||||
# ============================================================================
|
||||
|
||||
# The LV and VG names follow the Ubuntu Server installer's convention.
|
||||
# Override per-host if your template differs.
|
||||
expand_root_lv_vg_name: ubuntu-vg
|
||||
expand_root_lv_lv_name: ubuntu-lv
|
||||
|
||||
# Mount point we expect to be backed by the target LV. Used purely for
|
||||
# the resize2fs / xfs_growfs decision — the role inspects this path's
|
||||
# filesystem type and dispatches to the correct grow command.
|
||||
expand_root_lv_mountpoint: /
|
||||
23
ansible/roles/expand_root_lv/meta/main.yml
Normal file
23
ansible/roles/expand_root_lv/meta/main.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: expand_root_lv
|
||||
author: JARVIS
|
||||
description: >-
|
||||
Idempotent role that extends the root LVM logical volume to fill its
|
||||
volume group and grows the underlying filesystem (ext4 or xfs). Fixes
|
||||
the half-disk LV that the Ubuntu Server autoinstall template ships
|
||||
with by default.
|
||||
license: MIT
|
||||
min_ansible_version: "2.14"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- noble
|
||||
- jammy
|
||||
galaxy_tags:
|
||||
- lvm
|
||||
- cloud-init
|
||||
- homelab
|
||||
- storage
|
||||
|
||||
dependencies: []
|
||||
70
ansible/roles/expand_root_lv/tasks/main.yml
Normal file
70
ansible/roles/expand_root_lv/tasks/main.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# expand-root-lv / main
|
||||
# ----------------------------------------------------------------------------
|
||||
# 1. Confirm the target VG exists (skip role cleanly on non-LVM hosts).
|
||||
# 2. Read free physical-extent count for the VG.
|
||||
# 3. Extend the LV to +100%FREE only when free_pe > 0.
|
||||
# 4. Grow the filesystem on the mountpoint (ext4 -> resize2fs, xfs -> xfs_growfs).
|
||||
# Each step is idempotent and skips when there's nothing to do.
|
||||
# ============================================================================
|
||||
|
||||
- name: Gather LVM facts
|
||||
ansible.builtin.command:
|
||||
cmd: "vgs --noheadings --nosuffix --units b -o vg_name,vg_free_count {{ expand_root_lv_vg_name }}"
|
||||
register: vg_info
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Skip role when target VG is absent
|
||||
ansible.builtin.meta: end_play
|
||||
when: vg_info.rc != 0
|
||||
|
||||
- name: Parse free PE count
|
||||
ansible.builtin.set_fact:
|
||||
expand_root_lv_free_pe: "{{ (vg_info.stdout.split() | last | int) if vg_info.stdout | length > 0 else 0 }}"
|
||||
|
||||
- name: Extend LV to fill VG (only if free PE > 0)
|
||||
ansible.builtin.command:
|
||||
cmd: "lvextend -l +100%FREE /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}"
|
||||
register: lvextend_result
|
||||
when: expand_root_lv_free_pe | int > 0
|
||||
changed_when: lvextend_result.rc == 0
|
||||
|
||||
- name: Detect filesystem type at mountpoint
|
||||
ansible.builtin.command:
|
||||
cmd: "findmnt {{ expand_root_lv_mountpoint }} -no FSTYPE"
|
||||
register: fstype_result
|
||||
changed_when: false
|
||||
|
||||
- name: Set filesystem type fact
|
||||
ansible.builtin.set_fact:
|
||||
expand_root_lv_fstype: "{{ fstype_result.stdout | trim }}"
|
||||
|
||||
- name: Grow ext4 filesystem
|
||||
ansible.builtin.command:
|
||||
cmd: "resize2fs /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}"
|
||||
register: resize_result
|
||||
when:
|
||||
- expand_root_lv_fstype == "ext4"
|
||||
- lvextend_result.changed | default(false)
|
||||
changed_when: resize_result.rc == 0
|
||||
|
||||
- name: Grow xfs filesystem
|
||||
ansible.builtin.command:
|
||||
cmd: "xfs_growfs {{ expand_root_lv_mountpoint }}"
|
||||
register: xfs_result
|
||||
when:
|
||||
- expand_root_lv_fstype == "xfs"
|
||||
- lvextend_result.changed | default(false)
|
||||
changed_when: xfs_result.rc == 0
|
||||
|
||||
- name: Report current root size
|
||||
ansible.builtin.command:
|
||||
cmd: "df -h {{ expand_root_lv_mountpoint }}"
|
||||
register: df_result
|
||||
changed_when: false
|
||||
|
||||
- name: Show post-resize disk usage
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ df_result.stdout_lines }}"
|
||||
26
ansible/roles/hermes/defaults/main.yml
Normal file
26
ansible/roles/hermes/defaults/main.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# Hermes service user
|
||||
hermes_user: hermes
|
||||
hermes_group: hermes
|
||||
hermes_home: /home/hermes
|
||||
|
||||
# Install flags
|
||||
# Set to true if you don't need browser automation (skips Playwright/Chromium)
|
||||
hermes_skip_browser: false
|
||||
|
||||
# systemd service name (gateway)
|
||||
hermes_service_name: hermes
|
||||
|
||||
# Path where hermes binary will be accessible system-wide
|
||||
hermes_bin_symlink: /usr/local/bin/hermes
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Hermes Web UI (dashboard) — fronted by Traefik on lightning-lane via the
|
||||
# 'jarvis' service. Binds to 0.0.0.0 so Traefik can reach it from upstream;
|
||||
# --insecure is acceptable because TLS + auth are terminated at Traefik.
|
||||
# -----------------------------------------------------------------------------
|
||||
hermes_dashboard_enabled: true
|
||||
hermes_dashboard_service_name: hermes-dashboard
|
||||
hermes_dashboard_host: 0.0.0.0
|
||||
hermes_dashboard_port: 9119
|
||||
hermes_dashboard_insecure: true
|
||||
9
ansible/roles/hermes/handlers/main.yml
Normal file
9
ansible/roles/hermes/handlers/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: restart hermes
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ hermes_service_name }}"
|
||||
state: restarted
|
||||
199
ansible/roles/hermes/tasks/main.yml
Normal file
199
ansible/roles/hermes/tasks/main.yml
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. System prerequisites (run as root via become)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Install system packages required by Hermes installer
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- git
|
||||
- curl
|
||||
- ffmpeg
|
||||
- ripgrep
|
||||
state: present
|
||||
update_cache: true
|
||||
become: true
|
||||
|
||||
- name: Install Node.js 22 (required for browser automation and WhatsApp bridge)
|
||||
block:
|
||||
- name: Download NodeSource setup script
|
||||
ansible.builtin.get_url:
|
||||
url: https://deb.nodesource.com/setup_22.x
|
||||
dest: /tmp/nodesource_setup.sh
|
||||
mode: "0755"
|
||||
|
||||
- name: Run NodeSource setup script
|
||||
ansible.builtin.command: bash /tmp/nodesource_setup.sh
|
||||
args:
|
||||
creates: /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
- name: Install nodejs
|
||||
ansible.builtin.apt:
|
||||
name: nodejs
|
||||
state: present
|
||||
update_cache: true
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Install Playwright system deps for Chromium (root-only step)
|
||||
# Per docs: this is the one thing that genuinely needs root.
|
||||
# Skipped entirely if hermes_skip_browser is true.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Install Playwright Chromium system dependencies
|
||||
ansible.builtin.command: npx --yes playwright install-deps chromium
|
||||
become: true
|
||||
when: not hermes_skip_browser
|
||||
changed_when: true
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Create dedicated hermes service user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Create hermes group
|
||||
ansible.builtin.group:
|
||||
name: "{{ hermes_group }}"
|
||||
state: present
|
||||
system: true
|
||||
become: true
|
||||
|
||||
- name: Create hermes user
|
||||
ansible.builtin.user:
|
||||
name: "{{ hermes_user }}"
|
||||
group: "{{ hermes_group }}"
|
||||
home: "{{ hermes_home }}"
|
||||
shell: /bin/bash
|
||||
system: true
|
||||
create_home: true
|
||||
comment: "Hermes Agent service account"
|
||||
become: true
|
||||
|
||||
- name: Grant hermes user passwordless sudo
|
||||
ansible.builtin.copy:
|
||||
content: "hermes ALL=(ALL) NOPASSWD:ALL\n"
|
||||
dest: /etc/sudoers.d/hermes
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
become: true
|
||||
|
||||
- name: Ensure hermes home directory has correct permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ hermes_home }}"
|
||||
owner: "{{ hermes_user }}"
|
||||
group: "{{ hermes_group }}"
|
||||
mode: "0750"
|
||||
state: directory
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Run the Hermes installer as the hermes user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Check if hermes is already installed
|
||||
ansible.builtin.stat:
|
||||
path: "{{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes"
|
||||
register: hermes_binary
|
||||
|
||||
- name: Run Hermes installer as hermes user
|
||||
ansible.builtin.shell: |
|
||||
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh \
|
||||
| bash -s -- --skip-setup{% if hermes_skip_browser %} --skip-browser{% endif %}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ hermes_user }}"
|
||||
environment:
|
||||
HOME: "{{ hermes_home }}"
|
||||
PATH: "{{ hermes_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
when: not hermes_binary.stat.exists
|
||||
changed_when: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Symlink hermes binary into system PATH
|
||||
# Per docs: service accounts often lack ~/.local/bin in PATH; symlink
|
||||
# into /usr/local/bin so hermes is always accessible.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Symlink hermes binary to system PATH
|
||||
ansible.builtin.file:
|
||||
src: "{{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes"
|
||||
dest: "{{ hermes_bin_symlink }}"
|
||||
state: link
|
||||
force: true
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Install systemd service unit
|
||||
# NOTE: The service starts in 'gateway' mode for persistent operation.
|
||||
# You must run 'sudo -u hermes hermes setup' interactively on first boot
|
||||
# to configure your LLM provider and any messaging gateways before
|
||||
# enabling the service.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy hermes systemd service unit
|
||||
ansible.builtin.template:
|
||||
src: hermes.service.j2
|
||||
dest: /etc/systemd/system/{{ hermes_service_name }}.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
become: true
|
||||
notify:
|
||||
- reload systemd
|
||||
|
||||
- name: Flush handlers to reload systemd now
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Enable hermes service (but do not start — config required first)
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ hermes_service_name }}"
|
||||
enabled: true
|
||||
state: stopped
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Install systemd service unit for the Hermes Web UI (dashboard)
|
||||
# Separate unit from the gateway so the UI can be restarted / disabled
|
||||
# independently. Fronted upstream by Traefik (jarvis service) on
|
||||
# lightning-lane, so binding 0.0.0.0 with --insecure is intentional.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy hermes-dashboard systemd service unit
|
||||
ansible.builtin.template:
|
||||
src: hermes-dashboard.service.j2
|
||||
dest: /etc/systemd/system/{{ hermes_dashboard_service_name }}.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
become: true
|
||||
register: hermes_dashboard_unit
|
||||
when: hermes_dashboard_enabled
|
||||
|
||||
- name: Reload systemd to pick up hermes-dashboard unit changes
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
become: true
|
||||
when:
|
||||
- hermes_dashboard_enabled
|
||||
- hermes_dashboard_unit is changed
|
||||
|
||||
- name: Enable and start hermes-dashboard service
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ hermes_dashboard_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
become: true
|
||||
when: hermes_dashboard_enabled
|
||||
|
||||
- name: Restart hermes-dashboard on unit change
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ hermes_dashboard_service_name }}"
|
||||
state: restarted
|
||||
become: true
|
||||
when:
|
||||
- hermes_dashboard_enabled
|
||||
- hermes_dashboard_unit is changed
|
||||
24
ansible/roles/hermes/templates/hermes-dashboard.service.j2
Normal file
24
ansible/roles/hermes/templates/hermes-dashboard.service.j2
Normal file
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=Hermes Dashboard (Web UI)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ hermes_user }}
|
||||
Group={{ hermes_group }}
|
||||
WorkingDirectory={{ hermes_home }}
|
||||
Environment="HOME={{ hermes_home }}"
|
||||
Environment="PATH={{ hermes_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="HERMES_HOME={{ hermes_home }}/.hermes"
|
||||
ExecStart={{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes dashboard --host {{ hermes_dashboard_host }} --port {{ hermes_dashboard_port }}{% if hermes_dashboard_insecure %} --insecure{% endif %}
|
||||
|
||||
TimeoutStopSec=30
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=hermes-dashboard
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
23
ansible/roles/hermes/templates/hermes.service.j2
Normal file
23
ansible/roles/hermes/templates/hermes.service.j2
Normal file
@@ -0,0 +1,23 @@
|
||||
[Unit]
|
||||
Description=Hermes Agent (Nous Research)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ hermes_user }}
|
||||
Group={{ hermes_group }}
|
||||
WorkingDirectory={{ hermes_home }}
|
||||
Environment="HOME={{ hermes_home }}"
|
||||
Environment="PATH={{ hermes_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="HERMES_HOME={{ hermes_home }}/.hermes"
|
||||
ExecStart={{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes gateway
|
||||
TimeoutStopSec=200
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=hermes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
118
ansible/roles/honcho/README.md
Normal file
118
ansible/roles/honcho/README.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Honcho role
|
||||
|
||||
Deploys [Honcho](https://honcho.dev) — Plastic Labs' memory + theory-of-mind
|
||||
layer for stateful agents — on a single mk-labs VM (`lincoln`) using
|
||||
rootful Podman + Quadlet.
|
||||
|
||||
## Topology
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ lightning-lane (Traefik) │
|
||||
│ hall-of-presidents.l... │
|
||||
└─────────────┬────────────────┘
|
||||
│
|
||||
│ HTTP :8000
|
||||
▼
|
||||
┌──────────────────────────────── lincoln ───────────────────────────────┐
|
||||
│ │
|
||||
│ ┌───────────────┐ ┌───────────────┐ ┌──────────────────────┐ │
|
||||
│ │ honcho-api │ │ honcho-deriver│ │ honcho-postgres │ │
|
||||
│ │ (FastAPI:8000)│◄──►│ (worker loop) │◄──►│ pgvector/pgvector:pg16│ │
|
||||
│ └──────┬────────┘ └──────┬────────┘ └──────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ └────────┬───────────┘ │
|
||||
│ ▼ │
|
||||
│ Anthropic Claude API (outbound, east-west to Internet) │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## What this role does
|
||||
|
||||
1. Installs Podman + ensures the rootful Quadlet drop-in dir exists.
|
||||
2. Creates a user-defined Podman network (`honcho-net`).
|
||||
3. Creates a named volume for Postgres data (`honcho_postgres_data`).
|
||||
4. Deploys a pgvector-enabled Postgres 16 container and waits for it to be
|
||||
ready.
|
||||
5. Deploys the Honcho API container (`honcho-api`) — FastAPI on :8000.
|
||||
6. Deploys the Honcho deriver worker container (`honcho-deriver`) — **stopped
|
||||
and disabled by default** (see below).
|
||||
7. Verifies the API answers on `/docs`.
|
||||
|
||||
## Deriver service (disabled by default)
|
||||
|
||||
The `honcho-deriver` service is **created but not started** by default
|
||||
(`honcho_deriver_autostart: false`). This prevents autonomous token burn.
|
||||
|
||||
**Why disabled:**
|
||||
The deriver runs background reasoning loops that consume LLM tokens
|
||||
continuously, independent of the Hermes client-side `contextCadence` and
|
||||
`dialecticCadence` settings. Even with both cadences set to zero in
|
||||
`~/.hermes/honcho.json`, the deriver polls Postgres every second and fires
|
||||
Claude API calls autonomously.
|
||||
|
||||
In production, this discovered behavior cost $10 USD overnight before the
|
||||
service was stopped.
|
||||
|
||||
**Manual start (if needed for testing):**
|
||||
|
||||
```bash
|
||||
# On lincoln.local.mk-labs.cloud as jarvis or with sudo:
|
||||
sudo systemctl start honcho-deriver.service
|
||||
|
||||
# Check status:
|
||||
sudo systemctl status honcho-deriver.service
|
||||
|
||||
# Stop again:
|
||||
sudo systemctl stop honcho-deriver.service
|
||||
```
|
||||
|
||||
**To enable permanently:**
|
||||
Override `honcho_deriver_autostart: true` in `group_vars` or inventory, then
|
||||
re-run the playbook.
|
||||
|
||||
## Required vault entries
|
||||
|
||||
Add to `group_vars/all/vault` (ansible-vault encrypted):
|
||||
|
||||
```yaml
|
||||
vault_honcho_database_password: "<strong random>"
|
||||
vault_honcho_jwt_secret: "<32+ byte random>"
|
||||
vault_honcho_anthropic_api_key: "sk-ant-..."
|
||||
```
|
||||
|
||||
## LLM provider switching
|
||||
|
||||
This role starts with Anthropic Claude. To swap to a local
|
||||
OpenAI-compatible endpoint later (e.g. astro-orbiter once we have the
|
||||
GPU running llama.cpp/Ollama in OpenAI-compatible mode), override these
|
||||
in `group_vars/honcho_server` or via a playbook variable:
|
||||
|
||||
```yaml
|
||||
honcho_llm_transport: "openai"
|
||||
honcho_deriver_model: "qwen3:8b"
|
||||
honcho_summary_model: "qwen3:8b"
|
||||
honcho_dialectic_model: "qwen3:8b"
|
||||
# and add LLM_OPENAI_API_KEY (or set the base URL override in the template)
|
||||
```
|
||||
|
||||
## Traefik route
|
||||
|
||||
The Traefik file-provider YAML for `hall-of-presidents.local.mk-labs.cloud`
|
||||
lives at `boilerplates/traefik/dynamic/honcho.yml`. It points at
|
||||
`http://lincoln.local.mk-labs.cloud:8000`.
|
||||
|
||||
## Known Quadlet pitfall (carried from the semaphore role)
|
||||
|
||||
Quadlet regenerates the systemd unit on `.container` file change but does
|
||||
NOT restart the running container. This role explicitly handles that by
|
||||
gating a `state: restarted` task on the template's `changed` status —
|
||||
the same pattern documented in `homelab-application-deployment`'s
|
||||
quadlet-patterns reference.
|
||||
|
||||
## Honcho version pinning
|
||||
|
||||
The `honcho_image` default is `ghcr.io/plastic-labs/honcho:latest`.
|
||||
Move to a digest-pinned tag once we've validated the deployment works
|
||||
against a known-good build.
|
||||
159
ansible/roles/honcho/defaults/main.yml
Normal file
159
ansible/roles/honcho/defaults/main.yml
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# honcho role defaults
|
||||
# ============================================================================
|
||||
# Deploys Honcho (https://honcho.dev — plastic-labs/honcho) as a rootful
|
||||
# Podman + Quadlet service on a single VM. Three containers:
|
||||
#
|
||||
# honcho-postgres pgvector-enabled PostgreSQL backing store
|
||||
# honcho-api FastAPI server on :8000 (theory-of-mind read/write)
|
||||
# honcho-deriver background worker that consumes the queue and calls
|
||||
# out to the configured LLM provider for derivations
|
||||
#
|
||||
# All three share a user-defined podman network. State persists to named
|
||||
# volumes. Traefik on lightning-lane terminates TLS and routes the
|
||||
# `hall-of-presidents.local.mk-labs.cloud` host to honcho-api:8000.
|
||||
#
|
||||
# LLM provider for the first deployment is Anthropic Claude. Switching
|
||||
# providers is a single env-var change (see DERIVER_MODEL_CONFIG__TRANSPORT
|
||||
# / SUMMARY_MODEL_CONFIG__TRANSPORT and the LLM_*_API_KEY variables).
|
||||
# ============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Image pinning
|
||||
# ---------------------------------------------------------------------------
|
||||
# Honcho does not yet publish a Docker Hub image we trust; we use the
|
||||
# GitHub Container Registry build. Pin to a digest-stable tag.
|
||||
honcho_image: "ghcr.io/plastic-labs/honcho:latest"
|
||||
# pgvector/pgvector image follows the upstream postgres version channel.
|
||||
honcho_postgres_image: "docker.io/pgvector/pgvector:pg16"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Container & network identifiers
|
||||
# ---------------------------------------------------------------------------
|
||||
honcho_container_name: honcho-api
|
||||
honcho_deriver_container_name: honcho-deriver
|
||||
honcho_postgres_container_name: honcho-postgres
|
||||
honcho_network_name: honcho-net
|
||||
|
||||
# Named podman volumes
|
||||
honcho_postgres_volume: honcho_postgres_data
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Networking
|
||||
# ---------------------------------------------------------------------------
|
||||
# Honcho's FastAPI default port is 8000. Bind to 0.0.0.0 so Traefik on
|
||||
# lightning-lane can reach it; auth is enabled (JWT) so the open port is
|
||||
# not an open door.
|
||||
honcho_listen_address: "0.0.0.0"
|
||||
honcho_listen_port: 8000
|
||||
|
||||
# Public-facing URL used for absolute links / OIDC callbacks.
|
||||
# Leave EMPTY to make Honcho host-agnostic — FastAPI emits relative URLs.
|
||||
# Set to a fully-qualified URL only if a specific feature requires it.
|
||||
honcho_web_url: ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PostgreSQL configuration
|
||||
# ---------------------------------------------------------------------------
|
||||
honcho_db_user: honcho
|
||||
honcho_db_name: honcho
|
||||
# honcho_db_password sourced from vault below
|
||||
|
||||
# pgvector-enabled Postgres uses the same env vars as the stock image
|
||||
honcho_postgres_initdb_args: "--encoding=UTF8 --locale=C"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LLM provider configuration
|
||||
# ---------------------------------------------------------------------------
|
||||
# Supported transports: openai, anthropic, gemini. We start with anthropic
|
||||
# and can flip to a local OpenAI-compatible endpoint later by changing
|
||||
# these knobs plus the API-key env-var name.
|
||||
#
|
||||
# Honcho has THREE distinct LLM subsystems, each with its own settings
|
||||
# class and env-var prefix:
|
||||
#
|
||||
# DERIVER — generates observations from messages (background worker)
|
||||
# SUMMARY — periodic session summaries
|
||||
# DIALECTIC— answers theory-of-mind queries (the `peers/{peer}/chat` API)
|
||||
#
|
||||
# Honcho's dialectic subsystem uses PER-LEVEL config (minimal/low/medium/
|
||||
# high/max) under DIALECTIC_LEVELS__<level>__MODEL_CONFIG__*. Defaults
|
||||
# point at OpenAI, so any honcho.dev install without an OpenAI key will
|
||||
# fail dialectic queries with "Missing API key for openai model config".
|
||||
# We override all five levels to Anthropic below in the API template.
|
||||
honcho_llm_transport: "anthropic"
|
||||
honcho_deriver_model: "claude-sonnet-4-5"
|
||||
honcho_summary_model: "claude-sonnet-4-5"
|
||||
honcho_dialectic_model: "claude-sonnet-4-5"
|
||||
|
||||
# Deriver tuning. Honcho batches representation tasks until the per-batch
|
||||
# token threshold is reached. For homelab use with one chatty operator,
|
||||
# that means short bursts of conversation can sit unprocessed forever.
|
||||
# DERIVER_FLUSH_ENABLED=true bypasses the batching threshold so each
|
||||
# message is processed promptly.
|
||||
#
|
||||
# honcho_deriver_enabled controls whether the deriver Quadlet is created.
|
||||
# honcho_deriver_autostart controls whether the service starts on boot.
|
||||
# Set autostart=false to create the service but leave it disabled — useful
|
||||
# after discovering the deriver burns tokens autonomously despite client-side
|
||||
# cadence=0 settings.
|
||||
honcho_deriver_enabled: true
|
||||
honcho_deriver_autostart: false
|
||||
honcho_deriver_workers: 1
|
||||
honcho_deriver_polling_seconds: "1.0"
|
||||
honcho_deriver_flush_enabled: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Embeddings
|
||||
# ---------------------------------------------------------------------------
|
||||
# Anthropic does not provide an embedding API. Honcho defaults to
|
||||
# OpenAI text-embedding-3-small, which fails without an OpenAI key.
|
||||
# Three options going forward:
|
||||
# 1. Set EMBED_MESSAGES=false — disables semantic search/vector recall
|
||||
# but theory-of-mind derivation still works. Default for now.
|
||||
# 2. Provide an OpenAI API key purely for embeddings (cheap; embeddings
|
||||
# are ~$0.02 per million tokens). Set honcho_embed_messages=true and
|
||||
# add vault_honcho_openai_api_key.
|
||||
# 3. Stand up a local BGE/nomic embedding endpoint (e.g. on astro-orbiter
|
||||
# once GPU is ready) and point Honcho at it via
|
||||
# EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL.
|
||||
honcho_embed_messages: true
|
||||
|
||||
# Embedding provider (transport must be openai-compatible). The default
|
||||
# OpenAI endpoint requires honcho_openai_api_key. To swap to a local
|
||||
# OpenAI-compatible embedder (e.g. Ollama), set honcho_embedding_base_url
|
||||
# to the upstream /v1 URL and the API key can be any non-empty string.
|
||||
honcho_embedding_transport: "openai"
|
||||
honcho_embedding_model: "text-embedding-3-small"
|
||||
honcho_embedding_base_url: ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Auth
|
||||
# ---------------------------------------------------------------------------
|
||||
# AUTH_USE_AUTH=true means clients must present a JWT signed with
|
||||
# AUTH_JWT_SECRET. The secret lives in vault.
|
||||
honcho_auth_enabled: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vault inputs (defined in group_vars/all/vault, encrypted with ansible-vault)
|
||||
# ---------------------------------------------------------------------------
|
||||
# vault_honcho_database_password - postgres role password
|
||||
# vault_honcho_jwt_secret - 32+ byte random string for JWT signing
|
||||
# vault_honcho_anthropic_api_key - Anthropic API key for Claude calls
|
||||
# vault_honcho_openai_api_key - OpenAI API key, embeddings-only
|
||||
honcho_db_password: "{{ vault_honcho_database_password }}"
|
||||
honcho_jwt_secret: "{{ vault_honcho_jwt_secret }}"
|
||||
honcho_anthropic_api_key: "{{ vault_honcho_anthropic_api_key }}"
|
||||
honcho_openai_api_key: "{{ vault_honcho_openai_api_key }}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Health check
|
||||
# ---------------------------------------------------------------------------
|
||||
honcho_health_check_retries: 30
|
||||
honcho_health_check_delay: 2
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Quadlet location (rootful)
|
||||
# ---------------------------------------------------------------------------
|
||||
honcho_quadlet_dir: /etc/containers/systemd
|
||||
27
ansible/roles/honcho/meta/main.yml
Normal file
27
ansible/roles/honcho/meta/main.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: honcho
|
||||
author: JARVIS
|
||||
description: >-
|
||||
Deploys Honcho (plastic-labs/honcho) — a memory + theory-of-mind layer
|
||||
for stateful agents — with a pgvector-enabled PostgreSQL backing store,
|
||||
via rootful Podman Quadlet on Ubuntu. Designed for the mk-labs
|
||||
`lincoln` host. Fronted by Traefik at hall-of-presidents.local.mk-labs.cloud.
|
||||
license: MIT
|
||||
min_ansible_version: "2.14"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- noble
|
||||
- jammy
|
||||
galaxy_tags:
|
||||
- honcho
|
||||
- memory
|
||||
- llm
|
||||
- podman
|
||||
- quadlet
|
||||
- homelab
|
||||
|
||||
# linux-baseline is applied separately as a day0 playbook. We don't depend
|
||||
# on it here so this role stays composable.
|
||||
dependencies: []
|
||||
34
ansible/roles/honcho/tasks/api.yml
Normal file
34
ansible/roles/honcho/tasks/api.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Honcho API container (Quadlet)
|
||||
# ----------------------------------------------------------------------------
|
||||
# Runs `fastapi run src/main.py` via the image's default CMD. The
|
||||
# entrypoint.sh in the image runs the DB migration first; safe to do on
|
||||
# every restart (idempotent).
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy Honcho API Quadlet
|
||||
ansible.builtin.template:
|
||||
src: honcho-api.container.j2
|
||||
dest: "{{ honcho_quadlet_dir }}/{{ honcho_container_name }}.container"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: honcho_api_quadlet
|
||||
|
||||
- name: Reload systemd to pick up Quadlet changes
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
when: honcho_api_quadlet.changed
|
||||
|
||||
- name: Ensure Honcho API container is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ honcho_container_name }}.service"
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Restart Honcho API on Quadlet change
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ honcho_container_name }}.service"
|
||||
state: restarted
|
||||
when: honcho_api_quadlet.changed
|
||||
36
ansible/roles/honcho/tasks/deriver.yml
Normal file
36
ansible/roles/honcho/tasks/deriver.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Honcho deriver worker container (Quadlet)
|
||||
# ----------------------------------------------------------------------------
|
||||
# Same image as the API container, but runs the deriver script instead of
|
||||
# the FastAPI server. Pulls jobs off the in-database queue and calls the
|
||||
# configured LLM provider for theory-of-mind derivations.
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy Honcho deriver Quadlet
|
||||
ansible.builtin.template:
|
||||
src: honcho-deriver.container.j2
|
||||
dest: "{{ honcho_quadlet_dir }}/{{ honcho_deriver_container_name }}.container"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: honcho_deriver_quadlet
|
||||
|
||||
- name: Reload systemd to pick up Quadlet changes
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
when: honcho_deriver_quadlet.changed
|
||||
|
||||
- name: Ensure Honcho deriver service exists but is disabled by default
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ honcho_deriver_container_name }}.service"
|
||||
state: "{{ 'started' if honcho_deriver_autostart | bool else 'stopped' }}"
|
||||
enabled: "{{ honcho_deriver_autostart | bool }}"
|
||||
|
||||
- name: Restart Honcho deriver on Quadlet change (only if autostart enabled)
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ honcho_deriver_container_name }}.service"
|
||||
state: restarted
|
||||
when:
|
||||
- honcho_deriver_quadlet.changed
|
||||
- honcho_deriver_autostart | bool
|
||||
42
ansible/roles/honcho/tasks/main.yml
Normal file
42
ansible/roles/honcho/tasks/main.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# honcho / main entrypoint
|
||||
# ----------------------------------------------------------------------------
|
||||
# Order matters:
|
||||
# 1. Podman + Quadlet support installed and ready
|
||||
# 2. Network created (containers reference it by name)
|
||||
# 3. Volumes created (postgres data)
|
||||
# 4. Postgres started (Honcho API + deriver depend on it being ready)
|
||||
# 5. Honcho API started
|
||||
# 6. Honcho deriver worker started
|
||||
# 7. Verify reachable on listen port
|
||||
# ============================================================================
|
||||
|
||||
- name: Install Podman and dependencies
|
||||
ansible.builtin.import_tasks: podman.yml
|
||||
tags: [honcho, podman]
|
||||
|
||||
- name: Ensure podman network exists
|
||||
ansible.builtin.import_tasks: network.yml
|
||||
tags: [honcho, network]
|
||||
|
||||
- name: Ensure podman volumes exist
|
||||
ansible.builtin.import_tasks: volumes.yml
|
||||
tags: [honcho, volumes]
|
||||
|
||||
- name: Deploy PostgreSQL (pgvector) container
|
||||
ansible.builtin.import_tasks: postgres.yml
|
||||
tags: [honcho, postgres]
|
||||
|
||||
- name: Deploy Honcho API container
|
||||
ansible.builtin.import_tasks: api.yml
|
||||
tags: [honcho, api]
|
||||
|
||||
- name: Deploy Honcho deriver worker
|
||||
ansible.builtin.import_tasks: deriver.yml
|
||||
when: honcho_deriver_enabled | bool
|
||||
tags: [honcho, deriver]
|
||||
|
||||
- name: Verify Honcho is reachable
|
||||
ansible.builtin.import_tasks: verify.yml
|
||||
tags: [honcho, verify]
|
||||
17
ansible/roles/honcho/tasks/network.yml
Normal file
17
ansible/roles/honcho/tasks/network.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Podman network — single user-defined network shared by api/deriver/postgres
|
||||
# ============================================================================
|
||||
|
||||
- name: Check if honcho network exists
|
||||
ansible.builtin.command:
|
||||
cmd: "podman network exists {{ honcho_network_name }}"
|
||||
register: honcho_network_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Create honcho podman network
|
||||
ansible.builtin.command:
|
||||
cmd: "podman network create {{ honcho_network_name }}"
|
||||
when: honcho_network_check.rc != 0
|
||||
changed_when: true
|
||||
24
ansible/roles/honcho/tasks/podman.yml
Normal file
24
ansible/roles/honcho/tasks/podman.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Podman + Quadlet prerequisites
|
||||
# ----------------------------------------------------------------------------
|
||||
# Mirrors the pattern from the semaphore role. Ubuntu 24.04's podman is new
|
||||
# enough that Quadlet ships out of the box (>= 4.4).
|
||||
# ============================================================================
|
||||
|
||||
- name: Ensure Podman is installed
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- podman
|
||||
- podman-compose
|
||||
state: present
|
||||
become: true
|
||||
|
||||
- name: Ensure Quadlet drop-in directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ honcho_quadlet_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
become: true
|
||||
48
ansible/roles/honcho/tasks/postgres.yml
Normal file
48
ansible/roles/honcho/tasks/postgres.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# pgvector PostgreSQL container (Quadlet)
|
||||
# ----------------------------------------------------------------------------
|
||||
# Honcho stores embeddings in pgvector, so we use the pgvector-enabled
|
||||
# image rather than stock postgres. Same env-var surface as the upstream
|
||||
# image; the pgvector extension is created by Honcho's migration script
|
||||
# on first start (scripts/provision_db.py).
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy Honcho PostgreSQL Quadlet
|
||||
ansible.builtin.template:
|
||||
src: honcho-postgres.container.j2
|
||||
dest: "{{ honcho_quadlet_dir }}/{{ honcho_postgres_container_name }}.container"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: postgres_quadlet
|
||||
|
||||
- name: Reload systemd to pick up Quadlet changes
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
when: postgres_quadlet.changed
|
||||
|
||||
- name: Ensure Honcho PostgreSQL container is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ honcho_postgres_container_name }}.service"
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Wait for PostgreSQL to accept connections
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
podman exec {{ honcho_postgres_container_name }}
|
||||
pg_isready -U {{ honcho_db_user }} -d {{ honcho_db_name }}
|
||||
register: pg_ready
|
||||
until: pg_ready.rc == 0
|
||||
retries: 30
|
||||
delay: 2
|
||||
changed_when: false
|
||||
|
||||
# Quadlet does not auto-restart on .container changes — same pitfall as
|
||||
# semaphore role. Force restart only when the template was modified.
|
||||
- name: Restart Honcho PostgreSQL on Quadlet change
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ honcho_postgres_container_name }}.service"
|
||||
state: restarted
|
||||
when: postgres_quadlet.changed
|
||||
27
ansible/roles/honcho/tasks/verify.yml
Normal file
27
ansible/roles/honcho/tasks/verify.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Post-deploy verification
|
||||
# ----------------------------------------------------------------------------
|
||||
# Polls the local Honcho API port until it responds. Fails loudly if the
|
||||
# service doesn't come up — the operator should not get an "all green"
|
||||
# playbook result while Honcho is silently broken.
|
||||
# ============================================================================
|
||||
|
||||
- name: Wait for Honcho HTTP endpoint
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ honcho_listen_port }}/docs"
|
||||
status_code: [200, 307]
|
||||
return_content: false
|
||||
register: honcho_ping
|
||||
until: honcho_ping.status in [200, 307]
|
||||
retries: "{{ honcho_health_check_retries }}"
|
||||
delay: "{{ honcho_health_check_delay }}"
|
||||
|
||||
- name: Report Honcho status
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Honcho API reachable on http://127.0.0.1:{{ honcho_listen_port }}/docs.
|
||||
Traefik should now route hall-of-presidents.local.mk-labs.cloud to
|
||||
this backend. Deriver enabled: {{ honcho_deriver_enabled }};
|
||||
LLM transport: {{ honcho_llm_transport }};
|
||||
deriver model: {{ honcho_deriver_model }}.
|
||||
17
ansible/roles/honcho/tasks/volumes.yml
Normal file
17
ansible/roles/honcho/tasks/volumes.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Named podman volumes
|
||||
# ----------------------------------------------------------------------------
|
||||
# Honcho itself is stateless; only postgres needs durable storage.
|
||||
# ============================================================================
|
||||
|
||||
- name: Ensure named volumes exist
|
||||
ansible.builtin.command:
|
||||
cmd: "podman volume create {{ item }}"
|
||||
register: volume_create
|
||||
changed_when: "'already exists' not in (volume_create.stderr | default(''))"
|
||||
failed_when:
|
||||
- volume_create.rc != 0
|
||||
- "'already exists' not in (volume_create.stderr | default(''))"
|
||||
loop:
|
||||
- "{{ honcho_postgres_volume }}"
|
||||
85
ansible/roles/honcho/templates/honcho-api.container.j2
Normal file
85
ansible/roles/honcho/templates/honcho-api.container.j2
Normal file
@@ -0,0 +1,85 @@
|
||||
# mk-labs Honcho API service (FastAPI on :8000)
|
||||
# Managed by Ansible - honcho role
|
||||
|
||||
[Unit]
|
||||
Description=Honcho API
|
||||
After=network-online.target {{ honcho_postgres_container_name }}.service
|
||||
Wants=network-online.target
|
||||
Requires={{ honcho_postgres_container_name }}.service
|
||||
|
||||
[Container]
|
||||
Image={{ honcho_image }}
|
||||
ContainerName={{ honcho_container_name }}
|
||||
Network={{ honcho_network_name }}
|
||||
PublishPort={{ honcho_listen_address }}:{{ honcho_listen_port }}:8000
|
||||
|
||||
# Run the entrypoint that performs DB migration before starting FastAPI.
|
||||
Exec=/app/docker/entrypoint.sh
|
||||
|
||||
# -- Database ---------------------------------------------------------------
|
||||
Environment=DB_CONNECTION_URI=postgresql+psycopg://{{ honcho_db_user }}:{{ honcho_db_password }}@{{ honcho_postgres_container_name }}:5432/{{ honcho_db_name }}
|
||||
|
||||
# -- Auth (USE_AUTH + JWT secret) -------------------------------------------
|
||||
Environment=AUTH_USE_AUTH={{ honcho_auth_enabled | string | lower }}
|
||||
Environment=AUTH_JWT_SECRET={{ honcho_jwt_secret }}
|
||||
|
||||
# -- LLM provider keys ------------------------------------------------------
|
||||
{% if honcho_llm_transport == 'anthropic' %}
|
||||
Environment=LLM_ANTHROPIC_API_KEY={{ honcho_anthropic_api_key }}
|
||||
{% endif %}
|
||||
|
||||
# OpenAI key — used only by the embedding subsystem. Always present so
|
||||
# that conclusion vectorisation and any later semantic-search path works
|
||||
# regardless of which provider serves LLM completions.
|
||||
{% if honcho_openai_api_key | length > 0 %}
|
||||
Environment=LLM_OPENAI_API_KEY={{ honcho_openai_api_key }}
|
||||
Environment=OPENAI_API_KEY={{ honcho_openai_api_key }}
|
||||
{% endif %}
|
||||
|
||||
# -- Deriver subsystem ------------------------------------------------------
|
||||
Environment=DERIVER_FLUSH_ENABLED={{ honcho_deriver_flush_enabled | string | lower }}
|
||||
Environment=DERIVER_MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DERIVER_MODEL_CONFIG__MODEL={{ honcho_deriver_model }}
|
||||
|
||||
# -- Summary subsystem ------------------------------------------------------
|
||||
Environment=SUMMARY_MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=SUMMARY_MODEL_CONFIG__MODEL={{ honcho_summary_model }}
|
||||
|
||||
# -- Dialectic subsystem (per-level overrides; defaults are openai) ---------
|
||||
Environment=DIALECTIC_LEVELS__minimal__MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DIALECTIC_LEVELS__minimal__MODEL_CONFIG__MODEL={{ honcho_dialectic_model }}
|
||||
Environment=DIALECTIC_LEVELS__low__MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DIALECTIC_LEVELS__low__MODEL_CONFIG__MODEL={{ honcho_dialectic_model }}
|
||||
Environment=DIALECTIC_LEVELS__medium__MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DIALECTIC_LEVELS__medium__MODEL_CONFIG__MODEL={{ honcho_dialectic_model }}
|
||||
Environment=DIALECTIC_LEVELS__high__MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DIALECTIC_LEVELS__high__MODEL_CONFIG__MODEL={{ honcho_dialectic_model }}
|
||||
Environment=DIALECTIC_LEVELS__max__MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DIALECTIC_LEVELS__max__MODEL_CONFIG__MODEL={{ honcho_dialectic_model }}
|
||||
|
||||
# -- Embeddings (text-embedding-3-small via OpenAI by default) --------------
|
||||
Environment=EMBED_MESSAGES={{ honcho_embed_messages | string | lower }}
|
||||
Environment=EMBEDDING__MODEL_CONFIG__TRANSPORT={{ honcho_embedding_transport }}
|
||||
Environment=EMBEDDING__MODEL_CONFIG__MODEL={{ honcho_embedding_model }}
|
||||
{% if honcho_embedding_base_url | length > 0 %}
|
||||
Environment=EMBEDDING__MODEL_CONFIG__BASE_URL={{ honcho_embedding_base_url }}
|
||||
{% endif %}
|
||||
|
||||
# -- Vector store -----------------------------------------------------------
|
||||
Environment=VECTOR_STORE_TYPE=pgvector
|
||||
|
||||
# -- Public-facing URL (empty -> relative URLs) -----------------------------
|
||||
{% if honcho_web_url | length > 0 %}
|
||||
Environment=HONCHO_PUBLIC_URL={{ honcho_web_url }}
|
||||
{% endif %}
|
||||
|
||||
# Timezone matches host baseline
|
||||
Environment=TZ=America/Chicago
|
||||
Environment=LOG_LEVEL=INFO
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=180
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
70
ansible/roles/honcho/templates/honcho-deriver.container.j2
Normal file
70
ansible/roles/honcho/templates/honcho-deriver.container.j2
Normal file
@@ -0,0 +1,70 @@
|
||||
# mk-labs Honcho deriver worker (background queue consumer)
|
||||
# Managed by Ansible - honcho role
|
||||
|
||||
[Unit]
|
||||
Description=Honcho deriver worker
|
||||
After=network-online.target {{ honcho_postgres_container_name }}.service {{ honcho_container_name }}.service
|
||||
Wants=network-online.target
|
||||
Requires={{ honcho_postgres_container_name }}.service
|
||||
|
||||
[Container]
|
||||
Image={{ honcho_image }}
|
||||
ContainerName={{ honcho_deriver_container_name }}
|
||||
Network={{ honcho_network_name }}
|
||||
|
||||
# Invoke the package's __main__.py — src/deriver/deriver.py has no
|
||||
# __main__ guard and exits 0 in ~3s if invoked directly, causing systemd
|
||||
# to crash-loop the unit. python -m src.deriver hits __main__.py and
|
||||
# starts the real queue processor.
|
||||
WorkingDir=/app
|
||||
Exec=/app/.venv/bin/python -m src.deriver
|
||||
|
||||
# -- Database ---------------------------------------------------------------
|
||||
Environment=DB_CONNECTION_URI=postgresql+psycopg://{{ honcho_db_user }}:{{ honcho_db_password }}@{{ honcho_postgres_container_name }}:5432/{{ honcho_db_name }}
|
||||
|
||||
# -- Auth -------------------------------------------------------------------
|
||||
Environment=AUTH_USE_AUTH={{ honcho_auth_enabled | string | lower }}
|
||||
Environment=AUTH_JWT_SECRET={{ honcho_jwt_secret }}
|
||||
|
||||
# -- LLM provider keys ------------------------------------------------------
|
||||
{% if honcho_llm_transport == 'anthropic' %}
|
||||
Environment=LLM_ANTHROPIC_API_KEY=*** honcho_anthropic_api_key }}
|
||||
{% endif %}
|
||||
{% if honcho_openai_api_key | length > 0 %}
|
||||
Environment=LLM_OPENAI_API_KEY=*** honcho_openai_api_key }}
|
||||
Environment=OPENAI_API_KEY=*** honcho_openai_api_key }}
|
||||
{% endif %}
|
||||
|
||||
# -- Deriver runtime --------------------------------------------------------
|
||||
Environment=DERIVER_ENABLED={{ honcho_deriver_enabled | string | lower }}
|
||||
Environment=DERIVER_WORKERS={{ honcho_deriver_workers }}
|
||||
Environment=DERIVER_POLLING_SLEEP_INTERVAL_SECONDS={{ honcho_deriver_polling_seconds }}
|
||||
Environment=DERIVER_FLUSH_ENABLED={{ honcho_deriver_flush_enabled | string | lower }}
|
||||
Environment=DERIVER_MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=DERIVER_MODEL_CONFIG__MODEL={{ honcho_deriver_model }}
|
||||
|
||||
# -- Summary subsystem ------------------------------------------------------
|
||||
Environment=SUMMARY_MODEL_CONFIG__TRANSPORT={{ honcho_llm_transport }}
|
||||
Environment=SUMMARY_MODEL_CONFIG__MODEL={{ honcho_summary_model }}
|
||||
|
||||
# -- Embeddings -------------------------------------------------------------
|
||||
Environment=EMBED_MESSAGES={{ honcho_embed_messages | string | lower }}
|
||||
Environment=EMBEDDING__MODEL_CONFIG__TRANSPORT={{ honcho_embedding_transport }}
|
||||
Environment=EMBEDDING__MODEL_CONFIG__MODEL={{ honcho_embedding_model }}
|
||||
{% if honcho_embedding_base_url | length > 0 %}
|
||||
Environment=EMBEDDING__MODEL_CONFIG__BASE_URL={{ honcho_embedding_base_url }}
|
||||
{% endif %}
|
||||
|
||||
# -- Vector store -----------------------------------------------------------
|
||||
Environment=VECTOR_STORE_TYPE=pgvector
|
||||
|
||||
# Timezone matches host baseline
|
||||
Environment=TZ=America/Chicago
|
||||
Environment=LOG_LEVEL=INFO
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=180
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
35
ansible/roles/honcho/templates/honcho-postgres.container.j2
Normal file
35
ansible/roles/honcho/templates/honcho-postgres.container.j2
Normal file
@@ -0,0 +1,35 @@
|
||||
# {{ ansible_managed }}
|
||||
# mk-labs pgvector-enabled PostgreSQL backing store for Honcho
|
||||
# Managed by Ansible - honcho role
|
||||
|
||||
[Unit]
|
||||
Description=PostgreSQL (pgvector) for Honcho
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image={{ honcho_postgres_image }}
|
||||
ContainerName={{ honcho_postgres_container_name }}
|
||||
Network={{ honcho_network_name }}
|
||||
|
||||
# Persistent data on a named volume. :Z relabels for SELinux; harmless on
|
||||
# Ubuntu's ext4/apparmor stack and portable to any future host.
|
||||
Volume={{ honcho_postgres_volume }}:/var/lib/postgresql/data:Z
|
||||
|
||||
Environment=POSTGRES_USER={{ honcho_db_user }}
|
||||
Environment=POSTGRES_DB={{ honcho_db_name }}
|
||||
Environment=POSTGRES_PASSWORD={{ honcho_db_password }}
|
||||
Environment=POSTGRES_INITDB_ARGS={{ honcho_postgres_initdb_args }}
|
||||
|
||||
# Healthcheck — systemd doesn't act on this, but it's useful diagnostically
|
||||
HealthCmd=pg_isready -U {{ honcho_db_user }} -d {{ honcho_db_name }}
|
||||
HealthInterval=10s
|
||||
HealthTimeout=5s
|
||||
HealthRetries=3
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
5
ansible/roles/jarvis_user/defaults/main.yml
Normal file
5
ansible/roles/jarvis_user/defaults/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
jarvis_user: jarvis
|
||||
jarvis_group: jarvis
|
||||
jarvis_shell: /bin/bash
|
||||
jarvis_sudo_nopasswd: true
|
||||
30
ansible/roles/jarvis_user/tasks/main.yml
Normal file
30
ansible/roles/jarvis_user/tasks/main.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: Create jarvis group
|
||||
ansible.builtin.group:
|
||||
name: "{{ jarvis_group }}"
|
||||
state: present
|
||||
system: false
|
||||
|
||||
- name: Create jarvis user
|
||||
ansible.builtin.user:
|
||||
name: "{{ jarvis_user }}"
|
||||
group: "{{ jarvis_group }}"
|
||||
shell: "{{ jarvis_shell }}"
|
||||
create_home: true
|
||||
state: present
|
||||
|
||||
- name: Deploy SSH public key
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ jarvis_user }}"
|
||||
key: "{{ lookup('file', '~/.ssh/id_jarvis.pub') }}"
|
||||
state: present
|
||||
exclusive: true
|
||||
|
||||
- name: Grant passwordless sudo
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/{{ jarvis_user }}"
|
||||
content: "{{ jarvis_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0440'
|
||||
when: jarvis_sudo_nopasswd | bool
|
||||
57
ansible/roles/jmri/defaults/main.yml
Normal file
57
ansible/roles/jmri/defaults/main.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
# JMRI version to install
|
||||
# Update both jmri_version AND jmri_build_hash together when upgrading.
|
||||
# Find the build hash in the release asset filename on:
|
||||
# https://github.com/JMRI/JMRI/releases
|
||||
jmri_version: "5.16"
|
||||
jmri_build_hash: "909e15189e"
|
||||
jmri_install_dir: /opt/JMRI
|
||||
jmri_download_url: "https://github.com/JMRI/JMRI/releases/download/v{{ jmri_version }}/JMRI.{{ jmri_version }}+R{{ jmri_build_hash }}.tgz"
|
||||
|
||||
# Service user
|
||||
jmri_user: jmri
|
||||
jmri_group: jmri
|
||||
jmri_home: /home/jmri
|
||||
|
||||
# Profile — set per-host in host_vars
|
||||
jmri_profile_id: ""
|
||||
|
||||
# LCRR git repo (set per-host in host_vars; leave blank to skip clone)
|
||||
jmri_lcrr_repo: ""
|
||||
jmri_lcrr_branch: "main"
|
||||
|
||||
# SSH key for jmri user (for jmri-gui X11 access — set per-host in host_vars)
|
||||
jmri_ssh_authorized_key: ""
|
||||
jmri_ssh_authorized_keys_extra: [] # additional keys (e.g. operator laptops)
|
||||
|
||||
# SSH key for jmri user → Gitea
|
||||
jmri_gitea_key: /home/jmri/.ssh/id_ed25519_gitea
|
||||
|
||||
# Config restore source (legacy tar-based restore — leave blank to skip)
|
||||
jmri_config_src: ""
|
||||
|
||||
# Ports (for documentation / firewall rules)
|
||||
jmri_json_port: 12080
|
||||
jmri_withrottle_port: 12090
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 4 — Xpra virtual display
|
||||
# Replaces TigerVNC with rootless Xpra — proper window management,
|
||||
# persistent sessions, SSH-native attach (no VNC client needed).
|
||||
# Connect from macOS/Linux: xpra attach ssh://jmri@main-street-station/100
|
||||
# ---------------------------------------------------------------------------
|
||||
jmri_xpra_display: "100"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 2 — Layout power monitor (Leviton Decora Smart Wi-Fi)
|
||||
# ---------------------------------------------------------------------------
|
||||
jmri_leviton_email: "" # set via group_vars (vault-backed)
|
||||
jmri_leviton_password: "" # set via group_vars (vault-backed)
|
||||
jmri_leviton_switch_name: "Layout"
|
||||
jmri_monitor_poll_interval: 30 # seconds between polls (active hours)
|
||||
jmri_monitor_quiet_start: 1 # hour (24h) to stop polling
|
||||
jmri_monitor_quiet_end: 10 # hour (24h) to resume polling
|
||||
jmri_monitor_stop_delay: 30 # seconds of OFF state before stopping JMRI
|
||||
|
||||
|
||||
|
||||
32
ansible/roles/jmri/handlers/main.yml
Normal file
32
ansible/roles/jmri/handlers/main.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Reload udev
|
||||
ansible.builtin.command: udevadm control --reload-rules
|
||||
changed_when: false
|
||||
|
||||
- name: Trigger udev
|
||||
ansible.builtin.command: udevadm trigger --subsystem-match=tty
|
||||
changed_when: false
|
||||
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart jmri-monitor
|
||||
ansible.builtin.systemd:
|
||||
name: jmri-monitor
|
||||
state: restarted
|
||||
|
||||
- name: Restart jmri
|
||||
ansible.builtin.systemd:
|
||||
name: jmri
|
||||
state: restarted
|
||||
|
||||
- name: Restart jmri-xpra
|
||||
ansible.builtin.systemd:
|
||||
name: jmri-xpra
|
||||
state: restarted
|
||||
|
||||
- name: Restart sshd
|
||||
ansible.builtin.systemd:
|
||||
name: ssh
|
||||
state: restarted
|
||||
13
ansible/roles/jmri/meta/main.yml
Normal file
13
ansible/roles/jmri/meta/main.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: jmri
|
||||
author: JARVIS
|
||||
description: Deploy JMRI JmriFaceless headless server as a systemd service
|
||||
license: MIT
|
||||
min_ansible_version: "2.12"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- jammy
|
||||
- noble
|
||||
dependencies: []
|
||||
388
ansible/roles/jmri/tasks/main.yml
Normal file
388
ansible/roles/jmri/tasks/main.yml
Normal file
@@ -0,0 +1,388 @@
|
||||
---
|
||||
- name: Install Java runtime (full — required for GUI mode)
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- openjdk-21-jre
|
||||
- openjdk-21-jdk
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Create JMRI system group
|
||||
ansible.builtin.group:
|
||||
name: "{{ jmri_group }}"
|
||||
state: present
|
||||
system: true
|
||||
|
||||
- name: Create JMRI service user
|
||||
ansible.builtin.user:
|
||||
name: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
home: "{{ jmri_home }}"
|
||||
shell: /bin/bash
|
||||
system: true
|
||||
create_home: true
|
||||
state: present
|
||||
|
||||
- name: Deploy SSH authorized key for jmri user
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ jmri_user }}"
|
||||
key: "{{ jmri_ssh_authorized_key }}"
|
||||
state: present
|
||||
when: jmri_ssh_authorized_key | length > 0
|
||||
|
||||
- name: Deploy extra SSH authorized keys for jmri user
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ jmri_user }}"
|
||||
key: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ jmri_ssh_authorized_keys_extra }}"
|
||||
|
||||
- name: Add JMRI user to dialout group (serial device access)
|
||||
ansible.builtin.user:
|
||||
name: "{{ jmri_user }}"
|
||||
groups: dialout
|
||||
append: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 1 — Stable USB device symlinks
|
||||
# Creates /dev/jmri/loconet and /dev/jmri/nce via udev ID_SERIAL matching.
|
||||
# ---------------------------------------------------------------------------
|
||||
- name: Deploy udev rules for JMRI USB devices
|
||||
ansible.builtin.template:
|
||||
src: 99-jmri-devices.rules.j2
|
||||
dest: /etc/udev/rules.d/99-jmri-devices.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- Reload udev
|
||||
- Trigger udev
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 2 — LocoNet traffic monitor
|
||||
# Installs jmri-monitor: watches /dev/jmri/loconet, starts/stops jmri.service
|
||||
# ---------------------------------------------------------------------------
|
||||
- name: Install python3-venv (monitor virtualenv support)
|
||||
ansible.builtin.apt:
|
||||
name: python3-venv
|
||||
state: present
|
||||
|
||||
- name: Create virtualenv for jmri-monitor
|
||||
ansible.builtin.command:
|
||||
cmd: python3 -m venv /opt/jmri-monitor
|
||||
creates: /opt/jmri-monitor/bin/python3
|
||||
|
||||
- name: Install decora_wifi into jmri-monitor virtualenv
|
||||
ansible.builtin.pip:
|
||||
name: decora_wifi
|
||||
state: present
|
||||
virtualenv: /opt/jmri-monitor
|
||||
|
||||
- name: Deploy jmri-monitor script
|
||||
ansible.builtin.template:
|
||||
src: jmri-monitor.py.j2
|
||||
dest: /usr/local/bin/jmri-monitor
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
notify: Restart jmri-monitor
|
||||
|
||||
- name: Deploy jmri-monitor systemd unit
|
||||
ansible.builtin.template:
|
||||
src: jmri-monitor.service.j2
|
||||
dest: /etc/systemd/system/jmri-monitor.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart jmri-monitor
|
||||
|
||||
- name: Enable jmri-monitor service
|
||||
ansible.builtin.systemd:
|
||||
name: jmri-monitor
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 2b — LCRR config repo (clone/pull .jmri from Gitea)
|
||||
# ---------------------------------------------------------------------------
|
||||
- name: Ensure jmri .ssh directory exists
|
||||
ansible.builtin.file:
|
||||
path: /home/jmri/.ssh
|
||||
state: directory
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
mode: '0700'
|
||||
|
||||
- name: Deploy jmri SSH config for Gitea
|
||||
ansible.builtin.copy:
|
||||
dest: /home/jmri/.ssh/config
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
mode: '0600'
|
||||
content: |
|
||||
Host gitea.mk-labs.cloud
|
||||
HostName gitea.mk-labs.cloud
|
||||
User git
|
||||
Port 2221
|
||||
IdentityFile {{ jmri_gitea_key }}
|
||||
StrictHostKeyChecking accept-new
|
||||
|
||||
- name: Clone LCRR config repo if not present
|
||||
ansible.builtin.git:
|
||||
repo: "{{ jmri_lcrr_repo }}"
|
||||
dest: "{{ jmri_home }}/LCRR"
|
||||
version: "{{ jmri_lcrr_branch }}"
|
||||
accept_hostkey: true
|
||||
key_file: "{{ jmri_gitea_key }}"
|
||||
update: false
|
||||
become_user: "{{ jmri_user }}"
|
||||
when: jmri_lcrr_repo | length > 0
|
||||
notify: Restart jmri
|
||||
|
||||
- name: Remove auto-generated .jmri dir if it exists (will be replaced by symlink)
|
||||
ansible.builtin.file:
|
||||
path: "{{ jmri_home }}/.jmri"
|
||||
state: absent
|
||||
when:
|
||||
- jmri_lcrr_repo | length > 0
|
||||
|
||||
- name: Link .jmri config from LCRR repo
|
||||
ansible.builtin.file:
|
||||
src: "{{ jmri_home }}/LCRR/.jmri"
|
||||
dest: "{{ jmri_home }}/.jmri"
|
||||
state: link
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
force: true
|
||||
when: jmri_lcrr_repo | length > 0
|
||||
notify: Restart jmri
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# JMRI install / upgrade — version-marker pattern
|
||||
# Writes {{ jmri_install_dir }}/.jmri_installed_version after each install.
|
||||
# On subsequent runs: read the marker, skip everything if it matches
|
||||
# jmri_version. If it differs (or is absent), stop JMRI cleanly, wipe the
|
||||
# old install, download the new archive, extract, and write the new marker.
|
||||
# To upgrade: bump jmri_version + jmri_build_hash in defaults/main.yml (or
|
||||
# host_vars) and re-run the playbook.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Read installed JMRI version marker (if present)
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ jmri_install_dir }}/.jmri_installed_version"
|
||||
register: jmri_version_marker
|
||||
ignore_errors: true
|
||||
|
||||
- name: Determine whether JMRI install/upgrade is needed
|
||||
ansible.builtin.set_fact:
|
||||
jmri_needs_install: >-
|
||||
{{
|
||||
jmri_version_marker is failed or
|
||||
(jmri_version_marker.content | b64decode | trim) != jmri_version
|
||||
}}
|
||||
|
||||
- name: Stop JMRI service before upgrade (if running)
|
||||
ansible.builtin.systemd:
|
||||
name: jmri
|
||||
state: stopped
|
||||
failed_when: false # service may not exist yet on first install
|
||||
when: jmri_needs_install
|
||||
|
||||
- name: Remove existing JMRI install directory (upgrade path)
|
||||
ansible.builtin.file:
|
||||
path: "{{ jmri_install_dir }}"
|
||||
state: absent
|
||||
when: jmri_needs_install
|
||||
|
||||
- name: Remove stale JMRI archive from /tmp (if version changed)
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/JMRI-{{ jmri_version }}.tgz"
|
||||
state: absent
|
||||
when: jmri_needs_install
|
||||
|
||||
- name: Download JMRI release archive
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ jmri_download_url }}"
|
||||
dest: /tmp/JMRI-{{ jmri_version }}.tgz
|
||||
mode: '0644'
|
||||
when: jmri_needs_install
|
||||
|
||||
- name: Create JMRI install directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ jmri_install_dir }}"
|
||||
state: directory
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Extract JMRI archive
|
||||
ansible.builtin.unarchive:
|
||||
src: /tmp/JMRI-{{ jmri_version }}.tgz
|
||||
dest: "{{ jmri_install_dir }}"
|
||||
remote_src: true
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
extra_opts: ['--strip-components=1']
|
||||
when: jmri_needs_install
|
||||
|
||||
- name: Write installed version marker
|
||||
ansible.builtin.copy:
|
||||
content: "{{ jmri_version }}\n"
|
||||
dest: "{{ jmri_install_dir }}/.jmri_installed_version"
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
mode: '0644'
|
||||
when: jmri_needs_install
|
||||
|
||||
- name: Restore JMRI config from backup
|
||||
ansible.builtin.copy:
|
||||
src: "{{ jmri_config_src }}/"
|
||||
dest: "{{ jmri_home }}/.jmri/"
|
||||
owner: "{{ jmri_user }}"
|
||||
group: "{{ jmri_group }}"
|
||||
mode: '0644'
|
||||
directory_mode: '0755'
|
||||
when: jmri_config_src | length > 0
|
||||
notify: Restart jmri
|
||||
|
||||
- name: Deploy JmriFaceless systemd unit
|
||||
ansible.builtin.template:
|
||||
src: jmri.service.j2
|
||||
dest: /etc/systemd/system/jmri.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart jmri
|
||||
|
||||
- name: Enable jmri service (monitor manages start/stop — do not start directly)
|
||||
ansible.builtin.systemd:
|
||||
name: jmri
|
||||
enabled: false
|
||||
daemon_reload: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 3 — X11 remote GUI access (retained for fallback; VNC preferred)
|
||||
# ---------------------------------------------------------------------------
|
||||
- name: Install xauth (required for SSH X11 forwarding fallback)
|
||||
ansible.builtin.apt:
|
||||
name: xauth
|
||||
state: present
|
||||
|
||||
- name: Remove old jmri X11 sshd drop-in if present (renamed)
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config.d/20-jmri-x11.conf
|
||||
state: absent
|
||||
notify: Restart sshd
|
||||
|
||||
- name: Deploy sshd drop-in to enable X11 forwarding (must load before hardening)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/ssh/sshd_config.d/09-jmri-x11.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
content: |
|
||||
# Allow X11 forwarding for JMRI GUI sessions (jmri role)
|
||||
# Must be numbered below 10-mk-labs-hardening.conf — first match wins.
|
||||
X11Forwarding yes
|
||||
X11UseLocalhost yes
|
||||
notify: Restart sshd
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 4 — Xpra virtual display
|
||||
# Replaces TigerVNC. Rootless mode: each JMRI window appears as a native
|
||||
# window on the client. Sessions are persistent across disconnects.
|
||||
# Connect: xpra attach ssh://jmri@main-street-station/{{ jmri_xpra_display }}
|
||||
# ---------------------------------------------------------------------------
|
||||
- name: Remove TigerVNC (replaced by Xpra)
|
||||
ansible.builtin.apt:
|
||||
name: tigervnc-standalone-server
|
||||
state: absent
|
||||
notify: Reload systemd
|
||||
|
||||
- name: Disable and stop jmri-vnc service if present
|
||||
ansible.builtin.systemd:
|
||||
name: jmri-vnc
|
||||
enabled: false
|
||||
state: stopped
|
||||
failed_when: false
|
||||
|
||||
- name: Remove jmri-vnc systemd unit if present
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/jmri-vnc.service
|
||||
state: absent
|
||||
notify: Reload systemd
|
||||
|
||||
- name: Remove jmri VNC password directory if present
|
||||
ansible.builtin.file:
|
||||
path: "{{ jmri_home }}/.vnc"
|
||||
state: absent
|
||||
|
||||
- name: Install xpra.org apt signing key
|
||||
ansible.builtin.get_url:
|
||||
url: https://xpra.org/gpg.asc
|
||||
dest: /usr/share/keyrings/xpra.asc
|
||||
mode: '0644'
|
||||
|
||||
- name: Add xpra.org upstream apt repository
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/sources.list.d/xpra.list
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
content: |
|
||||
deb [arch=amd64 signed-by=/usr/share/keyrings/xpra.asc] https://xpra.org/ noble main
|
||||
|
||||
- name: Install Xpra from upstream repo (v6.x)
|
||||
ansible.builtin.apt:
|
||||
name: xpra
|
||||
state: latest
|
||||
update_cache: true
|
||||
|
||||
- name: Deploy jmri-xpra systemd unit
|
||||
ansible.builtin.template:
|
||||
src: jmri-xpra.service.j2
|
||||
dest: /etc/systemd/system/jmri-xpra.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart jmri-xpra
|
||||
|
||||
- name: Enable and start jmri-xpra service
|
||||
ansible.builtin.systemd:
|
||||
name: jmri-xpra
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
- name: Deploy jmri-gui script
|
||||
ansible.builtin.template:
|
||||
src: jmri-gui.j2
|
||||
dest: /usr/local/bin/jmri-gui
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
|
||||
- name: Deploy sudoers drop-in for jmri-gui (wed can manage jmri service)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/sudoers.d/jmri-gui
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
content: |
|
||||
# jmri user can manage its own service (for jmri-gui interactive sessions)
|
||||
jmri ALL=(root) NOPASSWD: /usr/bin/systemctl start jmri.service
|
||||
jmri ALL=(root) NOPASSWD: /usr/bin/systemctl stop jmri.service
|
||||
jmri ALL=(root) NOPASSWD: /opt/jmri-monitor/bin/python3
|
||||
# wed retains service control for automation/admin use
|
||||
wed ALL=(root) NOPASSWD: /usr/bin/systemctl start jmri.service
|
||||
wed ALL=(root) NOPASSWD: /usr/bin/systemctl stop jmri.service
|
||||
wed ALL=(root) NOPASSWD: /opt/jmri-monitor/bin/python3
|
||||
15
ansible/roles/jmri/templates/99-jmri-devices.rules.j2
Normal file
15
ansible/roles/jmri/templates/99-jmri-devices.rules.j2
Normal file
@@ -0,0 +1,15 @@
|
||||
# JMRI USB device symlinks — managed by Ansible, do not edit manually.
|
||||
# Creates stable /dev/jmri-* symlinks at the top level of /dev so JMRI
|
||||
# can enumerate them alongside real tty devices.
|
||||
|
||||
# LocoNet interface — RR-CirKits LocoBuffer-NG (Microchip CDC)
|
||||
SUBSYSTEM=="tty", ENV{ID_SERIAL}=="RR-CirKits_LocoBuffer-NG_CDC_ACM_SERIAL_DEVICE_AA5700218A", \
|
||||
SYMLINK+="jmri-loconet", MODE="0666"
|
||||
|
||||
# NCE Power Pro command station (FTDI FT232)
|
||||
SUBSYSTEM=="tty", ENV{ID_SERIAL}=="ftdi_usb_serial_converter_ftDYQHZX", \
|
||||
SYMLINK+="jmri-nce", MODE="0666"
|
||||
|
||||
# LCC buffer (Microchip CDC)
|
||||
SUBSYSTEM=="tty", ENV{ID_SERIAL}=="Microchip_Technology_Inc._Simple_CDC_Device_Demo", \
|
||||
SYMLINK+="jmri-lcc", MODE="0666"
|
||||
110
ansible/roles/jmri/templates/jmri-gui.j2
Normal file
110
ansible/roles/jmri/templates/jmri-gui.j2
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/bin/bash
|
||||
# jmri-gui — launch JMRI GUI on Xpra virtual display
|
||||
# Managed by Ansible — do not edit manually.
|
||||
#
|
||||
# Usage (connect as jmri user):
|
||||
# jmri-gui panelpro Launch PanelPro on Xpra display
|
||||
# jmri-gui decoderpro Launch DecoderPro on Xpra display
|
||||
# jmri-gui status Show service status and attach command
|
||||
#
|
||||
# Then attach from macOS/Linux:
|
||||
# xpra attach ssh://jmri@main-street-station/{{ jmri_xpra_display }}
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
JMRI_DIR="{{ jmri_install_dir }}"
|
||||
JMRI_SERVICE="jmri.service"
|
||||
MONITOR_SERVICE="jmri-monitor.service"
|
||||
XPRA_SERVICE="jmri-xpra.service"
|
||||
DISPLAY=":{{ jmri_xpra_display }}"
|
||||
export DISPLAY
|
||||
|
||||
usage() {
|
||||
echo "Usage: jmri-gui <panelpro|decoderpro|status>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
status() {
|
||||
echo "=== JMRI daemon ==="
|
||||
systemctl status "$JMRI_SERVICE" --no-pager -l 2>&1 | head -8
|
||||
echo ""
|
||||
echo "=== Xpra display ==="
|
||||
systemctl status "$XPRA_SERVICE" --no-pager -l 2>&1 | head -5
|
||||
echo ""
|
||||
echo "=== Layout monitor ==="
|
||||
systemctl status "$MONITOR_SERVICE" --no-pager -l 2>&1 | head -5
|
||||
echo ""
|
||||
echo "To attach: xpra attach ssh://jmri@$(hostname -f)/{{ jmri_xpra_display }}"
|
||||
}
|
||||
|
||||
launch() {
|
||||
local app="$1"
|
||||
local binary
|
||||
|
||||
case "$app" in
|
||||
panelpro) binary="PanelPro" ;;
|
||||
decoderpro) binary="DecoderPro" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
# Ensure Xpra display is running
|
||||
if ! systemctl is-active --quiet "$XPRA_SERVICE" 2>/dev/null; then
|
||||
echo "Starting Xpra display..."
|
||||
sudo systemctl start "$XPRA_SERVICE"
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
# Stop the JMRI daemon if running (we're taking over the hardware connections)
|
||||
if systemctl is-active --quiet "$JMRI_SERVICE" 2>/dev/null; then
|
||||
echo "Stopping JMRI daemon..."
|
||||
sudo systemctl stop "$JMRI_SERVICE"
|
||||
fi
|
||||
|
||||
# Force AWT out of headless mode
|
||||
export JMRI_OPTIONS="-Djava.awt.headless=false"
|
||||
|
||||
echo "Launching $binary on Xpra display $DISPLAY..."
|
||||
echo ""
|
||||
echo "Attach from your workstation:"
|
||||
echo " xpra attach ssh://jmri@$(hostname -f)/{{ jmri_xpra_display }}"
|
||||
echo ""
|
||||
|
||||
"$JMRI_DIR/$binary" &
|
||||
|
||||
echo "$binary launched. Attach with xpra to see windows."
|
||||
echo ""
|
||||
|
||||
# Restart daemon if layout switch is still on
|
||||
if systemctl is-active --quiet "$MONITOR_SERVICE" 2>/dev/null; then
|
||||
if sudo /opt/jmri-monitor/bin/python3 - <<'EOF'
|
||||
from decora_wifi import DecoraWiFiSession
|
||||
from decora_wifi.models.residential_account import ResidentialAccount
|
||||
import sys
|
||||
session = DecoraWiFiSession()
|
||||
person = session.login("{{ jmri_leviton_email }}", "{{ jmri_leviton_password }}")
|
||||
perms = person.get_residential_permissions()
|
||||
for perm in perms:
|
||||
acct_id = perm.data.get('residentialAccountId')
|
||||
if not acct_id:
|
||||
continue
|
||||
acct = ResidentialAccount(session, acct_id)
|
||||
acct.refresh()
|
||||
for r in acct.get_residences():
|
||||
for s in r.get_iot_switches():
|
||||
if s.data.get('name') == '{{ jmri_leviton_switch_name }}':
|
||||
sys.exit(0 if s.data.get('power') == 'ON' else 1)
|
||||
sys.exit(1)
|
||||
EOF
|
||||
then
|
||||
echo "Layout is ON — JMRI daemon will restart when GUI is closed."
|
||||
else
|
||||
echo "Layout is OFF — JMRI daemon will not restart."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
panelpro|decoderpro) launch "$1" ;;
|
||||
status) status ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
156
ansible/roles/jmri/templates/jmri-monitor.py.j2
Normal file
156
ansible/roles/jmri/templates/jmri-monitor.py.j2
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/opt/jmri-monitor/bin/python3
|
||||
"""
|
||||
jmri-monitor — Leviton Decora Smart Wi-Fi layout power monitor
|
||||
Managed by Ansible — do not edit manually.
|
||||
|
||||
Polls the Leviton cloud API for the "{{ jmri_leviton_switch_name }}" switch state.
|
||||
- Switch ON after being OFF → systemctl start jmri.service
|
||||
- Switch OFF for {{ jmri_monitor_stop_delay }}s → systemctl stop jmri.service
|
||||
|
||||
Quiet hours {{ jmri_monitor_quiet_start }}:00–{{ jmri_monitor_quiet_end }}:00: no polling (layout assumed off).
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
LEVITON_EMAIL = "{{ jmri_leviton_email }}"
|
||||
LEVITON_PASSWORD = "{{ jmri_leviton_password }}"
|
||||
SWITCH_NAME = "{{ jmri_leviton_switch_name }}"
|
||||
POLL_INTERVAL = {{ jmri_monitor_poll_interval }}
|
||||
QUIET_START = {{ jmri_monitor_quiet_start }}
|
||||
QUIET_END = {{ jmri_monitor_quiet_end }}
|
||||
STOP_DELAY = {{ jmri_monitor_stop_delay }}
|
||||
JMRI_SERVICE = "jmri.service"
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [jmri-monitor] %(levelname)s: %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def systemctl(action):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["systemctl", action, JMRI_SERVICE],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
log.info("systemctl %s %s: OK", action, JMRI_SERVICE)
|
||||
else:
|
||||
log.warning("systemctl %s %s: %s", action, JMRI_SERVICE, result.stderr.strip())
|
||||
except Exception as e:
|
||||
log.error("systemctl %s failed: %s", action, e)
|
||||
|
||||
|
||||
def is_quiet_hours():
|
||||
hour = datetime.now().hour
|
||||
if QUIET_START < QUIET_END:
|
||||
return QUIET_START <= hour < QUIET_END
|
||||
else:
|
||||
# wraps midnight e.g. 23–6
|
||||
return hour >= QUIET_START or hour < QUIET_END
|
||||
|
||||
|
||||
def get_switch_state():
|
||||
"""Returns True if switch is ON, False if OFF, None on error."""
|
||||
try:
|
||||
from decora_wifi import DecoraWiFiSession
|
||||
from decora_wifi.models.residential_account import ResidentialAccount
|
||||
|
||||
session = DecoraWiFiSession()
|
||||
person = session.login(LEVITON_EMAIL, LEVITON_PASSWORD)
|
||||
if not person:
|
||||
log.error("Leviton login failed")
|
||||
return None
|
||||
|
||||
perms = person.get_residential_permissions()
|
||||
for perm in perms:
|
||||
acct_id = perm.data.get('residentialAccountId')
|
||||
if not acct_id:
|
||||
continue
|
||||
acct = ResidentialAccount(session, acct_id)
|
||||
acct.refresh()
|
||||
for residence in acct.get_residences():
|
||||
for switch in residence.get_iot_switches():
|
||||
if switch.data.get('name') == SWITCH_NAME:
|
||||
state = switch.data.get('power', 'OFF')
|
||||
session.call_api('/Person/logout', {}, 'post')
|
||||
return state == 'ON'
|
||||
|
||||
all_names = []
|
||||
for perm in perms:
|
||||
acct_id = perm.data.get('residentialAccountId')
|
||||
if acct_id:
|
||||
acct = ResidentialAccount(session, acct_id)
|
||||
acct.refresh()
|
||||
for r in acct.get_residences():
|
||||
all_names += [s.data.get('name') for s in r.get_iot_switches()]
|
||||
log.warning("Switch '%s' not found — available: %s", SWITCH_NAME, all_names)
|
||||
session.call_api('/Person/logout', {}, 'post')
|
||||
return None
|
||||
|
||||
except ImportError:
|
||||
log.error("decora_wifi not installed")
|
||||
return None
|
||||
except Exception as e:
|
||||
log.error("Error querying Leviton API: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
log.info("Layout power monitor starting")
|
||||
log.info("Switch: '%s' Poll: %ds Quiet: %02d:00–%02d:00 Stop delay: %ds",
|
||||
SWITCH_NAME, POLL_INTERVAL, QUIET_START, QUIET_END, STOP_DELAY)
|
||||
|
||||
layout_on = False
|
||||
off_since = None
|
||||
|
||||
while True:
|
||||
if is_quiet_hours():
|
||||
log.debug("Quiet hours — sleeping 60s")
|
||||
# If layout was on when quiet hours started, stop JMRI
|
||||
if layout_on:
|
||||
log.info("Quiet hours began — stopping JMRI")
|
||||
layout_on = False
|
||||
off_since = None
|
||||
systemctl("stop")
|
||||
time.sleep(60)
|
||||
continue
|
||||
|
||||
state = get_switch_state()
|
||||
|
||||
if state is True:
|
||||
off_since = None
|
||||
if not layout_on:
|
||||
log.info("Layout switch ON — starting JMRI")
|
||||
layout_on = True
|
||||
systemctl("start")
|
||||
|
||||
elif state is False:
|
||||
if layout_on:
|
||||
if off_since is None:
|
||||
off_since = time.monotonic()
|
||||
log.info("Layout switch OFF — waiting %ds before stopping JMRI", STOP_DELAY)
|
||||
elif time.monotonic() - off_since >= STOP_DELAY:
|
||||
log.info("Layout switch OFF for %ds — stopping JMRI", STOP_DELAY)
|
||||
layout_on = False
|
||||
off_since = None
|
||||
systemctl("stop")
|
||||
else:
|
||||
off_since = None
|
||||
|
||||
else:
|
||||
# API error — don't change state, try again next poll
|
||||
log.warning("Could not determine switch state — retrying in %ds", POLL_INTERVAL)
|
||||
|
||||
time.sleep(POLL_INTERVAL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
ansible/roles/jmri/templates/jmri-monitor.service.j2
Normal file
20
ansible/roles/jmri/templates/jmri-monitor.service.j2
Normal file
@@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=JMRI LocoNet Traffic Monitor
|
||||
Documentation=https://www.jmri.org/
|
||||
# Must start after udev has processed devices
|
||||
After=systemd-udev-settle.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Runs as root — needs to call systemctl start/stop jmri.service
|
||||
User=root
|
||||
ExecStart=/usr/local/bin/jmri-monitor
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=jmri-monitor
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
26
ansible/roles/jmri/templates/jmri-xpra.service.j2
Normal file
26
ansible/roles/jmri/templates/jmri-xpra.service.j2
Normal file
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=Xpra virtual display for JMRI (:{{ jmri_xpra_display }})
|
||||
After=network.target
|
||||
# Start before jmri.service so the display is ready when JMRI launches
|
||||
Before=jmri.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ jmri_user }}
|
||||
Group={{ jmri_group }}
|
||||
ExecStart=/usr/bin/xpra start \
|
||||
:{{ jmri_xpra_display }} \
|
||||
--daemon=no \
|
||||
--mdns=no \
|
||||
--notifications=no \
|
||||
--systemd-run=no \
|
||||
--pulseaudio=no \
|
||||
--speaker=off \
|
||||
--microphone=off \
|
||||
--video-encoders=none \
|
||||
--start-via-proxy=no
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
31
ansible/roles/jmri/templates/jmri.service.j2
Normal file
31
ansible/roles/jmri/templates/jmri.service.j2
Normal file
@@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=JMRI Server (JmriFaceless)
|
||||
Documentation=https://www.jmri.org/
|
||||
# jmri-monitor starts and stops this service based on LocoNet traffic.
|
||||
# Do NOT enable this unit directly — it is managed by jmri-monitor.service.
|
||||
After=network.target systemd-udev-settle.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ jmri_user }}
|
||||
Group={{ jmri_group }}
|
||||
WorkingDirectory={{ jmri_install_dir }}
|
||||
ExecStart={{ jmri_install_dir }}/JmriFaceless --profile={{ jmri_profile_id }}
|
||||
|
||||
# Monitor owns lifecycle — do not auto-restart
|
||||
Restart=no
|
||||
|
||||
# Give hardware time to settle on start
|
||||
TimeoutStartSec=30
|
||||
|
||||
# Logging — view with: journalctl -u jmri -f
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=jmri
|
||||
|
||||
# Serial device access
|
||||
SupplementaryGroups=dialout
|
||||
|
||||
[Install]
|
||||
# Intentionally no WantedBy — started only by jmri-monitor
|
||||
WantedBy=
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user