4.2 KiB
4.2 KiB
Project Guidelines — OTS Signs Orchestrator
Architecture
Layered MVVM desktop app for provisioning and managing Xibo CMS instances on Docker Swarm.
- OTSSignsOrchestrator.Core — class library: services, EF Core data access, models, configuration. Reusable across UIs.
- OTSSignsOrchestrator.Desktop — Avalonia 11 UI: views, view models, DI setup. References Core.
- templates/ — Docker Compose + PHP templates with
{{PLACEHOLDER}}substitution.
Key patterns
- Services injected via
IServiceProvider(registered inApp.axaml.cs→ConfigureServices()) - Singletons: stateful services (SSH connections, Docker CLI). Transient: stateless logic.
- Configuration via
IOptions<T>bound fromappsettings.json(seeAppOptions.csfor all sections). - Bitwarden Secrets Manager is the source of truth for all sensitive config.
SettingsServicecaches in-memory. - Local SQLite DB (
otssigns-desktop.db) stores SSH hosts + operation logs. Credentials encrypted via Data Protection API.
External integrations
Xibo CMS REST API (OAuth2), Authentik SAML IdP, Bitwarden Secrets SDK, Docker Swarm (via SSH), Git (LibGit2Sharp), MySQL 8.4, NFS volumes, Pangolin/Newt VPN.
Build and Test
# Build
dotnet build OTSSignsOrchestrator.Desktop/OTSSignsOrchestrator.Desktop.csproj
# Run
dotnet run --project OTSSignsOrchestrator.Desktop/OTSSignsOrchestrator.Desktop.csproj
# No test suite currently — no xUnit/NUnit projects
- .NET 9.0, Avalonia 11.2.3, CommunityToolkit.Mvvm 8.4
- Runtime identifiers:
linux-x64,win-x64,osx-x64,osx-arm64 - EF Core migrations in
OTSSignsOrchestrator.Core/Migrations/
Conventions
ViewModels
- Inherit
ObservableObject(CommunityToolkit.Mvvm). Use[ObservableProperty]for bindable fields and[RelayCommand]for commands. - React to changes via
partial void OnXxxChanged(T value)methods generated by the toolkit. - Resolve services from
IServiceProviderin constructors. Navigation viaMainWindowViewModel.CurrentView. - Confirmation dialogs use
Func<string, string, Task<bool>> ConfirmAsyncproperty — wired by the View.
Views (Avalonia XAML)
- Compiled bindings enabled (
x:CompileBindings="True"). DataTemplates inMainWindow.axamlmap ViewModel types to View UserControls. - Layout: DockPanel with status bar (bottom), sidebar nav (left), dynamic ContentControl (center).
- Style: Fluent theme, dark palette (
#0C0C14accents).
Services
- Interface + implementation pattern for testable services (
IXiboApiService,IDockerCliService, etc.). SshDockerCliServiceis a singleton — must callSetHost(host)before each operation in loops.- All long operations are
async Task. UseIsBusy+StatusMessageproperties for UI feedback.
Naming
- Customer abbreviation: exactly 3 lowercase letters (
^[a-z]{3}$). - Stack name:
{abbrev}-cms-stack. Service:{abbrev}-web. DB:{abbrev}_cms_db. - Secret names built via
AppConstantshelpers (e.g.,CustomerMysqlPasswordSecretName(abbrev)). AppConstants.SanitizeName()filters to[a-z0-9_-].
Data layer
- Entities in
Core/Models/Entities/, DTOs inCore/Models/DTOs/. XiboContextapplies unique index onSshHost.Labeland encrypts credential fields.- Add new migrations via:
dotnet ef migrations add <Name> --project OTSSignsOrchestrator.Core --startup-project OTSSignsOrchestrator.Desktop
Pitfalls
- SSH singleton state:
SshDockerCliService.SetHost()must be called before each host operation — stale host causes silent failures. - Bitwarden cache: After creating secrets during deployment, call
FlushCacheAsync()before reading them back. - Data Protection keys: Stored in
%APPDATA%/OTSSignsOrchestrator/keys. If lost, encrypted SSH passwords are unrecoverable. - Docker volumes are sticky: Failed deploys leave volumes with old NFS driver options. Use
PurgeStaleVolumes: trueto force fresh volumes (causes data loss). - No saga/rollback: Instance creation spans Git → MySQL → Docker → Xibo. Partial failures leave orphaned resources; cleanup is manual via
OperationLog. - Template CIFS→NFS compat: Old
{{CIFS_*}}tokens still render correctly as NFS equivalents.