From f43df1b3fef79017b820d3794f55b2c58d52ee8c Mon Sep 17 00:00:00 2001
From: HoneyBearTech <31805425+HoneyBearTech@users.noreply.github.com>
Date: Fri, 23 Oct 2026 19:05:58 +0000
Subject: [PATCH 1/7] Optimize 2 SVG(s)
---
Shoko/shoko.svg | 2 +-
Verdaccio/verdaccio.svg | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Shoko/shoko.svg b/Shoko/shoko.svg
index f63e0b22c4..87ac7aa229 100644
--- a/Shoko/shoko.svg
+++ b/Shoko/shoko.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/Verdaccio/verdaccio.svg b/Verdaccio/verdaccio.svg
index 8a98484a4a..37b7017f1e 100644
--- a/Verdaccio/verdaccio.svg
+++ b/Verdaccio/verdaccio.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 56295249ffa775311f36f324b0fd1300447fcbed Mon Sep 17 00:00:00 2001
From: Sean <31805425+HoneyBearTech@users.noreply.github.com>
Date: Fri, 23 Oct 2026 14:07:56 -0500
Subject: [PATCH 2/7] Create app.json for Steerhouse configuration
Added configuration for Steerhouse application.
---
Steerhouse/app.json | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 Steerhouse/app.json
diff --git a/Steerhouse/app.json b/Steerhouse/app.json
new file mode 100644
index 0000000000..eb5003ab91
--- /dev/null
+++ b/Steerhouse/app.json
@@ -0,0 +1,10 @@
+{
+ "appid": "422c6bf3a19cca6e7c2e59a393b1838cca9bb6e9",
+ "name": "Steerhouse",
+ "website": "https://github.com/HoneyBearTech/Steerhouse",
+ "license": "MIT License",
+ "description": "Self-hosted web UI and API for running Ansible playbooks, with per-project access control, isolated workers and a full audit trail.",
+ "enhanced": true,
+ "tile_background": "dark",
+ "icon": "steerhouse.svg"
+}
From c3e40b3110e1da84a70cd20aa212535b4558e308 Mon Sep 17 00:00:00 2001
From: Sean <31805425+HoneyBearTech@users.noreply.github.com>
Date: Fri, 23 Oct 2026 14:08:42 -0500
Subject: [PATCH 3/7] Add files via upload
---
Steerhouse/Steerhouse.php | 68 ++++++++++++++++++++++++++++++++++
Steerhouse/config.blade.php | 31 ++++++++++++++++
Steerhouse/livestats.blade.php | 14 +++++++
3 files changed, 113 insertions(+)
create mode 100644 Steerhouse/Steerhouse.php
create mode 100644 Steerhouse/config.blade.php
create mode 100644 Steerhouse/livestats.blade.php
diff --git a/Steerhouse/Steerhouse.php b/Steerhouse/Steerhouse.php
new file mode 100644
index 0000000000..6356dda4f3
--- /dev/null
+++ b/Steerhouse/Steerhouse.php
@@ -0,0 +1,68 @@
+url('api/stats'), $this->getAttrs());
+ echo $test->status;
+ }
+
+ public function livestats()
+ {
+ $status = 'inactive';
+ $data = [
+ 'running' => 0,
+ 'queued' => 0,
+ 'failed' => 0,
+ ];
+
+ // GET /api/stats returns counts only, e.g. {"queued": 0, "running": 2, "succeeded_24h": 14,
+ // "failed_24h": 1, "workers_online": 3}. execute() returns null when the connection fails;
+ // a bad key gets a 401 whose body has no counts, so the tile stays at zero and inactive.
+ $res = parent::execute($this->url('api/stats'), $this->getAttrs());
+ if ($res !== null && $res->getStatusCode() === 200) {
+ $body = json_decode($res->getBody());
+ if (isset($body->running, $body->queued, $body->failed_24h)) {
+ $data['running'] = (int) $body->running;
+ $data['queued'] = (int) $body->queued;
+ $data['failed'] = (int) $body->failed_24h;
+ // Refresh every 5 seconds while runs are waiting or in progress, else every 30.
+ if ($data['running'] > 0 || $data['queued'] > 0) {
+ $status = 'active';
+ }
+ }
+ }
+
+ return parent::getLiveStats($status, $data);
+ }
+
+ private function getAttrs()
+ {
+ // A "stats" API key from Projects -> API keys in Steerhouse: it can read these counts only.
+ $attrs = [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Bearer ' . ($this->config->apikey ?? ''),
+ ],
+ ];
+ if (!empty($this->config->ignore_tls)) {
+ $attrs['verify'] = false;
+ }
+ return $attrs;
+ }
+
+ public function url($endpoint)
+ {
+ $api_url = parent::normaliseurl($this->config->url) . $endpoint;
+ return $api_url;
+ }
+}
diff --git a/Steerhouse/config.blade.php b/Steerhouse/config.blade.php
new file mode 100644
index 0000000000..51a673a866
--- /dev/null
+++ b/Steerhouse/config.blade.php
@@ -0,0 +1,31 @@
+
{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')
+
+
+ {{ strtoupper(__('app.url')) }}
+ {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!}
+
+
+ {{ __('app.apps.apikey') }}
+ {!! Form::input('password', 'config[apikey]', isset($item) ? ($item->getconfig()->apikey ?? null) : null, ['placeholder' => __('app.apps.apikey'), 'data-config' => 'apikey', 'class' => 'form-control config-item']) !!}
+
+
+
+ Test
+
+
diff --git a/Steerhouse/livestats.blade.php b/Steerhouse/livestats.blade.php
new file mode 100644
index 0000000000..bb81f043fb
--- /dev/null
+++ b/Steerhouse/livestats.blade.php
@@ -0,0 +1,14 @@
+
+
+ Run
+ {!! $running ?? 0 !!}
+
+
+ Queue
+ {!! $queued ?? 0 !!}
+
+
+ Failed
+ {!! $failed ?? 0 !!}
+
+
From 3d69c0f227aaddad0edfb1971118d9edba8b7e0d Mon Sep 17 00:00:00 2001
From: Sean <31805425+HoneyBearTech@users.noreply.github.com>
Date: Fri, 23 Oct 2026 14:10:44 -0500
Subject: [PATCH 4/7] Add files via upload
---
Steerhouse/steerhouse.svg | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Steerhouse/steerhouse.svg
diff --git a/Steerhouse/steerhouse.svg b/Steerhouse/steerhouse.svg
new file mode 100644
index 0000000000..af2ce05a46
--- /dev/null
+++ b/Steerhouse/steerhouse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
From e66d0d1b5bf23cdc79f297da61a1a30df71edca5 Mon Sep 17 00:00:00 2001
From: HoneyBearTech <31805425+HoneyBearTech@users.noreply.github.com>
Date: Fri, 23 Oct 2026 19:11:01 +0000
Subject: [PATCH 5/7] Optimize 1 SVG(s)
---
Steerhouse/steerhouse.svg | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Steerhouse/steerhouse.svg b/Steerhouse/steerhouse.svg
index af2ce05a46..bc0cc5d3bd 100644
--- a/Steerhouse/steerhouse.svg
+++ b/Steerhouse/steerhouse.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 89f88a90565296e17bfc9c24adb47e5ecd8a6e38 Mon Sep 17 00:00:00 2001
From: Sean <31805425+HoneyBearTech@users.noreply.github.com>
Date: Fri, 23 Oct 2026 14:13:35 -0500
Subject: [PATCH 6/7] Delete Steerhouse directory
---
Steerhouse/Steerhouse.php | 68 ----------------------------------
Steerhouse/app.json | 10 -----
Steerhouse/config.blade.php | 31 ----------------
Steerhouse/livestats.blade.php | 14 -------
Steerhouse/steerhouse.svg | 1 -
5 files changed, 124 deletions(-)
delete mode 100644 Steerhouse/Steerhouse.php
delete mode 100644 Steerhouse/app.json
delete mode 100644 Steerhouse/config.blade.php
delete mode 100644 Steerhouse/livestats.blade.php
delete mode 100644 Steerhouse/steerhouse.svg
diff --git a/Steerhouse/Steerhouse.php b/Steerhouse/Steerhouse.php
deleted file mode 100644
index 6356dda4f3..0000000000
--- a/Steerhouse/Steerhouse.php
+++ /dev/null
@@ -1,68 +0,0 @@
-url('api/stats'), $this->getAttrs());
- echo $test->status;
- }
-
- public function livestats()
- {
- $status = 'inactive';
- $data = [
- 'running' => 0,
- 'queued' => 0,
- 'failed' => 0,
- ];
-
- // GET /api/stats returns counts only, e.g. {"queued": 0, "running": 2, "succeeded_24h": 14,
- // "failed_24h": 1, "workers_online": 3}. execute() returns null when the connection fails;
- // a bad key gets a 401 whose body has no counts, so the tile stays at zero and inactive.
- $res = parent::execute($this->url('api/stats'), $this->getAttrs());
- if ($res !== null && $res->getStatusCode() === 200) {
- $body = json_decode($res->getBody());
- if (isset($body->running, $body->queued, $body->failed_24h)) {
- $data['running'] = (int) $body->running;
- $data['queued'] = (int) $body->queued;
- $data['failed'] = (int) $body->failed_24h;
- // Refresh every 5 seconds while runs are waiting or in progress, else every 30.
- if ($data['running'] > 0 || $data['queued'] > 0) {
- $status = 'active';
- }
- }
- }
-
- return parent::getLiveStats($status, $data);
- }
-
- private function getAttrs()
- {
- // A "stats" API key from Projects -> API keys in Steerhouse: it can read these counts only.
- $attrs = [
- 'headers' => [
- 'Accept' => 'application/json',
- 'Authorization' => 'Bearer ' . ($this->config->apikey ?? ''),
- ],
- ];
- if (!empty($this->config->ignore_tls)) {
- $attrs['verify'] = false;
- }
- return $attrs;
- }
-
- public function url($endpoint)
- {
- $api_url = parent::normaliseurl($this->config->url) . $endpoint;
- return $api_url;
- }
-}
diff --git a/Steerhouse/app.json b/Steerhouse/app.json
deleted file mode 100644
index eb5003ab91..0000000000
--- a/Steerhouse/app.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "appid": "422c6bf3a19cca6e7c2e59a393b1838cca9bb6e9",
- "name": "Steerhouse",
- "website": "https://github.com/HoneyBearTech/Steerhouse",
- "license": "MIT License",
- "description": "Self-hosted web UI and API for running Ansible playbooks, with per-project access control, isolated workers and a full audit trail.",
- "enhanced": true,
- "tile_background": "dark",
- "icon": "steerhouse.svg"
-}
diff --git a/Steerhouse/config.blade.php b/Steerhouse/config.blade.php
deleted file mode 100644
index 51a673a866..0000000000
--- a/Steerhouse/config.blade.php
+++ /dev/null
@@ -1,31 +0,0 @@
-{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')
-
-
- {{ strtoupper(__('app.url')) }}
- {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!}
-
-
- {{ __('app.apps.apikey') }}
- {!! Form::input('password', 'config[apikey]', isset($item) ? ($item->getconfig()->apikey ?? null) : null, ['placeholder' => __('app.apps.apikey'), 'data-config' => 'apikey', 'class' => 'form-control config-item']) !!}
-
-
-
- Test
-
-
diff --git a/Steerhouse/livestats.blade.php b/Steerhouse/livestats.blade.php
deleted file mode 100644
index bb81f043fb..0000000000
--- a/Steerhouse/livestats.blade.php
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
- Run
- {!! $running ?? 0 !!}
-
-
- Queue
- {!! $queued ?? 0 !!}
-
-
- Failed
- {!! $failed ?? 0 !!}
-
-
diff --git a/Steerhouse/steerhouse.svg b/Steerhouse/steerhouse.svg
deleted file mode 100644
index bc0cc5d3bd..0000000000
--- a/Steerhouse/steerhouse.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
From e9eed6717f128aafa7665c0c310f2537d7e2da83 Mon Sep 17 00:00:00 2001
From: Sean <31805425+HoneyBearTech@users.noreply.github.com>
Date: Sun, 25 Oct 2026 19:47:09 -0500
Subject: [PATCH 7/7] Add files via upload
---
USAS/USAS.php | 96 ++++++++++++++++++++++++++++++++++++++++
USAS/app.json | 10 +++++
USAS/config.blade.php | 15 +++++++
USAS/livestats.blade.php | 14 ++++++
USAS/usas.svg | 1 +
5 files changed, 136 insertions(+)
create mode 100644 USAS/USAS.php
create mode 100644 USAS/app.json
create mode 100644 USAS/config.blade.php
create mode 100644 USAS/livestats.blade.php
create mode 100644 USAS/usas.svg
diff --git a/USAS/USAS.php b/USAS/USAS.php
new file mode 100644
index 0000000000..9700c35c98
--- /dev/null
+++ b/USAS/USAS.php
@@ -0,0 +1,96 @@
+ "ISO",
+ "provision" => "Install",
+ "baseline" => "Baseline",
+ "site" => "Site",
+ "keys" => "Keys",
+ ];
+ private const RESULTS = [
+ "succeeded" => "OK",
+ "failed" => "Failed",
+ "stopped" => "Stopped",
+ "interrupted" => "Stopped",
+ ];
+
+ public function __construct()
+ {
+ }
+
+ public function test()
+ {
+ $test = parent::appTest($this->url("api/status"), $this->getAttrs());
+ echo $test->status;
+ }
+
+ public function livestats()
+ {
+ $status = "inactive";
+ $data = [
+ "run_title" => "Run",
+ "run_value" => "-",
+ "vms" => "-",
+ "pending" => "-",
+ ];
+
+ // GET /api/status (docs/interfaces.md#the-status-api in the USAS repository). execute() returns
+ // null when the connection fails and never throws for an error status.
+ $res = parent::execute($this->url("api/status"), $this->getAttrs());
+ if ($res === null || $res->getStatusCode() !== 200) {
+ return parent::getLiveStats($status, $data);
+ }
+ $body = json_decode($res->getBody());
+ if (!is_object($body) || ($body->schema ?? null) !== 1) {
+ return parent::getLiveStats($status, $data);
+ }
+
+ $running = $body->running ?? null;
+ $playbook = is_object($running) && is_string($running->playbook ?? null) ? $running->playbook : "";
+ if (isset(self::PLAYBOOKS[$playbook])) {
+ // Heimdall asks every 5 seconds while the status is active, every 30 otherwise.
+ $status = "active";
+ $data["run_title"] = "Now";
+ $data["run_value"] = self::PLAYBOOKS[$playbook];
+ } else {
+ $last = $body->last_run ?? null;
+ $result = is_object($last) && is_string($last->status ?? null) ? $last->status : "";
+ $data["run_value"] = is_object($last) ? (self::RESULTS[$result] ?? "-") : "None";
+ }
+
+ $vms = $body->vms ?? null;
+ if (is_object($vms) && is_int($vms->running ?? null) && is_int($vms->total ?? null)) {
+ $data["vms"] = $vms->running . "/" . $vms->total;
+ }
+ $servers = $body->servers ?? null;
+ if (is_object($servers) && is_int($servers->pending ?? null)) {
+ $data["pending"] = (string) $servers->pending;
+ }
+
+ return parent::getLiveStats($status, $data);
+ }
+
+ public function url($endpoint)
+ {
+ return parent::normaliseurl($this->config->url) . $endpoint;
+ }
+
+ private function getAttrs()
+ {
+ // The status token, made in USAS on the Account page. It goes only in this header, never in the URL.
+ return [
+ "headers" => [
+ "Accept" => "application/json",
+ "Authorization" => "Bearer " . trim($this->config->password ?? ""),
+ ],
+ ];
+ }
+}
diff --git a/USAS/app.json b/USAS/app.json
new file mode 100644
index 0000000000..5e3c98b735
--- /dev/null
+++ b/USAS/app.json
@@ -0,0 +1,10 @@
+{
+ "appid": "a30b98c03a1facce720e0838722513881066a95f",
+ "name": "USAS",
+ "website": "https://github.com/HoneyBearTech/ubuntu-server-auto-setup",
+ "license": "MIT License",
+ "description": "USAS (Ubuntu Server Auto Setup) installs Ubuntu Server on new Proxmox VE VMs with nobody at the keyboard, loads the admin's GitHub SSH keys and applies a hardened baseline, from a web UI. The tile shows whether a run is going, how the last one ended, the cluster's running VMs and the servers still to install.",
+ "enhanced": true,
+ "tile_background": "dark",
+ "icon": "usas.svg"
+}
diff --git a/USAS/config.blade.php b/USAS/config.blade.php
new file mode 100644
index 0000000000..977587e9fd
--- /dev/null
+++ b/USAS/config.blade.php
@@ -0,0 +1,15 @@
+{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')
+
+
+ {{ strtoupper(__('app.url')) }}
+ {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!}
+
+
+
+ Test
+
+
diff --git a/USAS/livestats.blade.php b/USAS/livestats.blade.php
new file mode 100644
index 0000000000..208b03ef5f
--- /dev/null
+++ b/USAS/livestats.blade.php
@@ -0,0 +1,14 @@
+
+
+ {{ $run_title }}
+ {{ $run_value }}
+
+
+ VMs
+ {{ $vms }}
+
+
+ New
+ {{ $pending }}
+
+
diff --git a/USAS/usas.svg b/USAS/usas.svg
new file mode 100644
index 0000000000..5275abd758
--- /dev/null
+++ b/USAS/usas.svg
@@ -0,0 +1 @@
+
\ No newline at end of file