divoom: add pi deploy artifact manifests
Add source-controlled Puppet/Hiera contracts for edge2 Divoom-as-DM-device without replacing the live flowercore-divoom systemd deployment. Add Divoom TV Pi HDMI systemd/Puppet deployment artifacts, LF shell-script guardrails, and focused lint coverage for the additive non-K8s deploy shape. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
45
apps/fc-divoom-dm-pi-device/README.md
Normal file
45
apps/fc-divoom-dm-pi-device/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# FlowerCore Divoom DM Pi Device
|
||||
|
||||
Source-controlled Puppet/Hiera deployment contract for registering the edge2
|
||||
Divoom MiniToo panel as a FlowerCore DeviceManagement-managed Pi device.
|
||||
|
||||
This is not a Kubernetes application. The live panel remains the existing
|
||||
edge2 `flowercore-divoom.service` managed by `FlowerCore.Puppet`
|
||||
`profile::pi::service::divoom`, with the .NET payload deployed out of band
|
||||
and `/opt/flowercore/divoom/data` plus the Bluetooth shell wrappers preserved.
|
||||
Because edge2 is already Hiera-driven through `profile::pi::service::apps`,
|
||||
the deploy home is additive `profile::pi::service` data/profile source, not
|
||||
`profile::edge::service::apps` and not an ArgoCD/K8s app.
|
||||
|
||||
## Scope
|
||||
|
||||
- Stage DeviceManagement registration metadata for the edge2 Divoom MiniToo.
|
||||
- Stage a separate, disabled-by-default DM Agent executor unit for privileged
|
||||
Bluetooth operations once the DM-RPC lane lands.
|
||||
- Keep `flowercore-divoom.service` and `flowercore-divoom-bt.service`
|
||||
untouched: no service replacement, no restart subscription, no K8s surface.
|
||||
- Preserve the current wrapper contract:
|
||||
`/opt/flowercore/divoom/bt-link.sh`,
|
||||
`/opt/flowercore/divoom/bt-reset.sh`, and
|
||||
`/opt/flowercore/divoom/audio-link.sh`.
|
||||
- Keep FM radio disabled and require visible render proof; device-info echo is
|
||||
not render proof.
|
||||
|
||||
## Artifact Map
|
||||
|
||||
| Path | Use |
|
||||
| --- | --- |
|
||||
| `hiera/edge2-divoom-dm-device.overlay.yaml` | Additive Hiera overlay for edge2. Merge into the existing node YAML without removing `fc-pimanager` or `fc-divoom`. |
|
||||
| `puppet/profile/pi/service/divoom_dm_device.pp` | Puppet profile shape to vendor into `FlowerCore.Puppet` after the DM-RPC executor binary exists. |
|
||||
| `puppet/templates/divoom-device-registration.json.epp` | DM device registration metadata rendered on edge2. |
|
||||
| `puppet/templates/flowercore-divoom-dm-agent.service.epp` | Separate DM Agent systemd unit. Defaults are stopped and disabled until a later cutover. |
|
||||
|
||||
## Rollout Notes
|
||||
|
||||
1. Land these artifacts in bluejay-infra as the deploy contract.
|
||||
2. Vendor the Puppet profile and EPP templates into `FlowerCore.Puppet`.
|
||||
3. Merge the Hiera overlay into `data/nodes/edge2.iamworkin.lan.yaml`.
|
||||
4. Run Puppet in noop first, preferably with a node-local validation directory
|
||||
under `~/.fcv` rather than `/tmp`.
|
||||
5. Only enable the DM Agent service after the DeviceManagement BT executor has
|
||||
landed and passed operator-eyeball render proof.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Merge into FlowerCore.Puppet data/nodes/edge2.iamworkin.lan.yaml.
|
||||
# Additive overlay only: keep the existing fc-pimanager version/tarball entry,
|
||||
# keep fc-divoom enabled, and do not move Divoom into Kubernetes.
|
||||
|
||||
profile::pi::service::apps:
|
||||
fc-pimanager:
|
||||
binary: 'FlowerCore.PiManager.Web'
|
||||
install_dir: '/opt/fc-pimanager'
|
||||
port: 5000
|
||||
environment: 'edge2'
|
||||
version: '2026.05.28.1646'
|
||||
tarball_source: 'puppet:///modules/profile/pi/builds/fc-pimanager.tar.gz'
|
||||
fc-divoom:
|
||||
enabled: true
|
||||
|
||||
profile::pi::service::divoom_dm_device::ensure: 'present'
|
||||
profile::pi::service::divoom_dm_device::service_enabled: false
|
||||
profile::pi::service::divoom_dm_device::service_ensure: 'stopped'
|
||||
profile::pi::service::divoom_dm_device::device_id: 'edge2-divoom-minitoo'
|
||||
profile::pi::service::divoom_dm_device::display_name: 'edge2 Divoom MiniToo'
|
||||
profile::pi::service::divoom_dm_device::host_fqdn: 'edge2.iamworkin.lan'
|
||||
profile::pi::service::divoom_dm_device::dm_web_url: 'https://devicemgmt.iamworkin.lan'
|
||||
profile::pi::service::divoom_dm_device::divoom_install_dir: '/opt/flowercore/divoom'
|
||||
profile::pi::service::divoom_dm_device::agent_install_dir: '/opt/flowercore/devicemanagement-agent'
|
||||
profile::pi::service::divoom_dm_device::bt_candidate_channels:
|
||||
- '1'
|
||||
- '10'
|
||||
profile::pi::service::divoom_dm_device::default_bt_channel: '1'
|
||||
profile::pi::service::divoom_dm_device::a2dp_default_state: 'off'
|
||||
profile::pi::service::divoom_dm_device::fm_radio_enabled: false
|
||||
profile::pi::service::divoom_dm_device::visible_render_proof_required: true
|
||||
@@ -0,0 +1,140 @@
|
||||
# Drop into FlowerCore.Puppet site-modules/profile/manifests/pi/service/divoom_dm_device.pp.
|
||||
# This profile is additive to profile::pi::service::divoom. It must not manage,
|
||||
# restart, replace, or subscribe the existing flowercore-divoom.service.
|
||||
class profile::pi::service::divoom_dm_device (
|
||||
Enum['present', 'absent'] $ensure = 'present',
|
||||
Boolean $service_enabled = false,
|
||||
Enum['running', 'stopped'] $service_ensure = 'stopped',
|
||||
String $service_name = 'flowercore-divoom-dm-agent',
|
||||
String $device_id = 'edge2-divoom-minitoo',
|
||||
String $display_name = 'edge2 Divoom MiniToo',
|
||||
String $host_fqdn = 'edge2.iamworkin.lan',
|
||||
String $dm_web_url = 'https://devicemgmt.iamworkin.lan',
|
||||
String $divoom_install_dir = '/opt/flowercore/divoom',
|
||||
String $agent_install_dir = '/opt/flowercore/devicemanagement-agent',
|
||||
String $agent_binary = 'FlowerCore.DeviceManagement.Agent',
|
||||
Array[String] $bt_candidate_channels = ['1', '10'],
|
||||
String $default_bt_channel = '1',
|
||||
Enum['on', 'off'] $a2dp_default_state = 'off',
|
||||
Boolean $fm_radio_enabled = false,
|
||||
Boolean $visible_render_proof_required = true,
|
||||
) {
|
||||
include profile::workstation::safe_account_exclusion
|
||||
|
||||
$safe_account = $profile::workstation::safe_account_exclusion::safe_account
|
||||
$config_dir = '/etc/flowercore/device-management/devices'
|
||||
$state_dir = '/var/lib/flowercore/divoom-dm-agent'
|
||||
$log_dir = '/var/log/flowercore/divoom-dm-agent'
|
||||
$registration_path = "${config_dir}/${device_id}.json"
|
||||
$agent_binary_path = "${agent_install_dir}/${agent_binary}"
|
||||
$bt_channels_json = inline_template('[<%= @bt_candidate_channels.map { |c| "\"#{c}\"" }.join(", ") %>]')
|
||||
|
||||
if $safe_account {
|
||||
notify { 'fc-divoom-dm-device safe-account exclusion':
|
||||
message => 'SAFE-ACCOUNT-EXCLUSION: Divoom DM Pi device profile refused to apply on operator workstation',
|
||||
}
|
||||
|
||||
if $facts['os']['family'] != 'windows' {
|
||||
ensure_resource('file', '/var/log/flowercore-audit', {
|
||||
'ensure' => 'directory',
|
||||
'owner' => 'root',
|
||||
'group' => 'root',
|
||||
'mode' => '0755',
|
||||
})
|
||||
|
||||
file { '/var/log/flowercore-audit/safe-account-noop-fc-divoom-dm-device.log':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => "noop: divoom dm pi device profile refused to apply on safe-account host\n",
|
||||
require => File['/var/log/flowercore-audit'],
|
||||
}
|
||||
}
|
||||
} elsif $ensure == 'absent' {
|
||||
service { $service_name:
|
||||
ensure => stopped,
|
||||
enable => false,
|
||||
}
|
||||
|
||||
file { [
|
||||
"/etc/systemd/system/${service_name}.service",
|
||||
$registration_path,
|
||||
]:
|
||||
ensure => absent,
|
||||
}
|
||||
|
||||
exec { 'fc-divoom-dm-agent-systemd-reload':
|
||||
command => '/usr/bin/systemctl daemon-reload',
|
||||
refreshonly => true,
|
||||
path => ['/usr/bin', '/bin'],
|
||||
}
|
||||
} else {
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {}
|
||||
default: { fail("profile::pi::service::divoom_dm_device only supports Debian-family OS, got ${facts['os']['family']}") }
|
||||
}
|
||||
|
||||
file { [$config_dir, $state_dir, $log_dir]:
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
}
|
||||
|
||||
file { $registration_path:
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => epp('profile/pi/fc_divoom_dm/divoom-device-registration.json.epp', {
|
||||
'device_id' => $device_id,
|
||||
'display_name' => $display_name,
|
||||
'host_fqdn' => $host_fqdn,
|
||||
'divoom_install_dir' => $divoom_install_dir,
|
||||
'bt_channels_json' => $bt_channels_json,
|
||||
'default_bt_channel' => $default_bt_channel,
|
||||
'a2dp_default_state' => $a2dp_default_state,
|
||||
'fm_radio_enabled' => $fm_radio_enabled,
|
||||
'visible_render_proof_required' => $visible_render_proof_required,
|
||||
}),
|
||||
require => File[$config_dir],
|
||||
}
|
||||
|
||||
file { "/etc/systemd/system/${service_name}.service":
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => epp('profile/pi/fc_divoom_dm/flowercore-divoom-dm-agent.service.epp', {
|
||||
'service_name' => $service_name,
|
||||
'device_id' => $device_id,
|
||||
'dm_web_url' => $dm_web_url,
|
||||
'registration_path' => $registration_path,
|
||||
'divoom_install_dir' => $divoom_install_dir,
|
||||
'agent_install_dir' => $agent_install_dir,
|
||||
'agent_binary_path' => $agent_binary_path,
|
||||
'state_dir' => $state_dir,
|
||||
'log_dir' => $log_dir,
|
||||
}),
|
||||
notify => Exec['fc-divoom-dm-agent-systemd-reload'],
|
||||
require => File[$registration_path],
|
||||
}
|
||||
|
||||
exec { 'fc-divoom-dm-agent-systemd-reload':
|
||||
command => '/usr/bin/systemctl daemon-reload',
|
||||
refreshonly => true,
|
||||
path => ['/usr/bin', '/bin'],
|
||||
}
|
||||
|
||||
service { $service_name:
|
||||
ensure => $service_ensure,
|
||||
enable => $service_enabled,
|
||||
require => [
|
||||
File["/etc/systemd/system/${service_name}.service"],
|
||||
File[$registration_path],
|
||||
Exec['fc-divoom-dm-agent-systemd-reload'],
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"deviceId": "<%= $device_id %>",
|
||||
"displayName": "<%= $display_name %>",
|
||||
"hostFqdn": "<%= $host_fqdn %>",
|
||||
"kind": "DivoomMiniToo",
|
||||
"managedBy": "FlowerCore.DeviceManagement",
|
||||
"executionMode": "Pi",
|
||||
"transport": {
|
||||
"kind": "BluetoothSerial",
|
||||
"candidateChannels": <%= $bt_channels_json %>,
|
||||
"defaultChannel": "<%= $default_bt_channel %>",
|
||||
"deviceInfoIsRenderProof": false,
|
||||
"visibleRenderProofRequired": <%= $visible_render_proof_required %>
|
||||
},
|
||||
"paths": {
|
||||
"divoomInstallDir": "<%= $divoom_install_dir %>",
|
||||
"btLink": "<%= $divoom_install_dir %>/bt-link.sh",
|
||||
"btReset": "<%= $divoom_install_dir %>/bt-reset.sh",
|
||||
"audioLink": "<%= $divoom_install_dir %>/audio-link.sh"
|
||||
},
|
||||
"capabilities": {
|
||||
"supportsBluetoothSerial": true,
|
||||
"supportsBtChannelRedetect": true,
|
||||
"supportsBtHardReset": true,
|
||||
"supportsBtAudioProfileSwitch": true,
|
||||
"a2dpDefaultState": "<%= $a2dp_default_state %>",
|
||||
"fmRadioEnabled": <%= $fm_radio_enabled %>
|
||||
},
|
||||
"safety": {
|
||||
"preserveExistingService": "flowercore-divoom.service",
|
||||
"preserveDataDirectory": "<%= $divoom_install_dir %>/data",
|
||||
"doNotEnableFmRadio": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
[Unit]
|
||||
Description=FlowerCore Divoom DM Agent Bluetooth executor
|
||||
Documentation=https://github.com/astoltz/FlowerCore.Notes/blob/master/docs/standards/divoom-tv-hdmi-multitarget-render-substrate.md
|
||||
Wants=network-online.target
|
||||
After=network-online.target bluetooth.service
|
||||
Requires=bluetooth.service
|
||||
ConditionPathExists=<%= $agent_binary_path %>
|
||||
ConditionPathExists=<%= $registration_path %>
|
||||
ConditionPathExists=<%= $divoom_install_dir %>/bt-link.sh
|
||||
ConditionPathExists=<%= $divoom_install_dir %>/bt-reset.sh
|
||||
ConditionPathExists=<%= $divoom_install_dir %>/audio-link.sh
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=stoltz
|
||||
Group=stoltz
|
||||
WorkingDirectory=<%= $agent_install_dir %>
|
||||
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
Environment=FLOWERCORE_DM_DEVICE_REGISTRATION=<%= $registration_path %>
|
||||
Environment=Divoom__Bluetooth__DeviceInfoIsRenderProof=false
|
||||
Environment=Divoom__Bluetooth__VisibleRenderProofRequired=true
|
||||
Environment=Divoom__Bluetooth__A2dpDefaultState=off
|
||||
ExecStart=<%= $agent_binary_path %> --mode=Pi --device-id=<%= $device_id %> --dm-web-url=<%= $dm_web_url %> --registration=<%= $registration_path %>
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
StartLimitBurst=3
|
||||
StartLimitIntervalSec=300s
|
||||
SupplementaryGroups=bluetooth audio dialout
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=<%= $state_dir %> <%= $log_dir %>
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user