Files
OTSSignsOrchestrator/.github/copilot-instructions.md

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 in App.axaml.csConfigureServices())
  • Singletons: stateful services (SSH connections, Docker CLI). Transient: stateless logic.
  • Configuration via IOptions<T> bound from appsettings.json (see AppOptions.cs for all sections).
  • Bitwarden Secrets Manager is the source of truth for all sensitive config. SettingsService caches 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 IServiceProvider in constructors. Navigation via MainWindowViewModel.CurrentView.
  • Confirmation dialogs use Func<string, string, Task<bool>> ConfirmAsync property — wired by the View.

Views (Avalonia XAML)

  • Compiled bindings enabled (x:CompileBindings="True"). DataTemplates in MainWindow.axaml map ViewModel types to View UserControls.
  • Layout: DockPanel with status bar (bottom), sidebar nav (left), dynamic ContentControl (center).
  • Style: Fluent theme, dark palette (#0C0C14 accents).

Services

  • Interface + implementation pattern for testable services (IXiboApiService, IDockerCliService, etc.).
  • SshDockerCliService is a singleton — must call SetHost(host) before each operation in loops.
  • All long operations are async Task. Use IsBusy + StatusMessage properties 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 AppConstants helpers (e.g., CustomerMysqlPasswordSecretName(abbrev)).
  • AppConstants.SanitizeName() filters to [a-z0-9_-].

Data layer

  • Entities in Core/Models/Entities/, DTOs in Core/Models/DTOs/.
  • XiboContext applies unique index on SshHost.Label and 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: true to 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.