docs: document provisioning handshake and persistence model
This commit is contained in:
parent
25d71b0b5e
commit
40d9bc77a6
61
adr/0001-postgresql-platform-database.md
Normal file
61
adr/0001-postgresql-platform-database.md
Normal file
@ -0,0 +1,61 @@
|
||||
# ADR-0001: PostgreSQL als persistente Plattformdatenbank
|
||||
|
||||
**Status:** Beschlossen
|
||||
**Datum:** 12.07.2026
|
||||
|
||||
## Kontext
|
||||
|
||||
Tuxflotte verwaltet zunehmend persistente und miteinander verknüpfte Plattformdaten.
|
||||
|
||||
Dazu gehören unter anderem:
|
||||
|
||||
- Organisationen
|
||||
- Geräte
|
||||
- Gerätezuordnungen
|
||||
- Hardware-Snapshots
|
||||
- Netzwerkprofile
|
||||
- Workspaces
|
||||
- Backends
|
||||
- Runtime Blueprints
|
||||
- Secret-Referenzen
|
||||
|
||||
Die bisherige dateibasierte Device Registry diente dem Nachweis des grundlegenden Provisioning-Handshakes.
|
||||
|
||||
Für das langfristige Plattformmodell ist eine strukturierte persistente Datenhaltung erforderlich.
|
||||
|
||||
## Entscheidung
|
||||
|
||||
Tuxflotte verwendet PostgreSQL als persistente Plattformdatenbank.
|
||||
|
||||
Die Datenbank bildet die Beziehungen zwischen Organisationen, Geräten, Hardware-Snapshots, Workspaces, Backends und Gerätezuordnungen ab.
|
||||
|
||||
Die dateibasierte Device Registry wird nicht als dauerhaftes Persistenzmodell weitergeführt.
|
||||
|
||||
Hardwareinformationen werden als zeitbezogene Snapshots gespeichert.
|
||||
|
||||
Die stabile Geräteidentität und der erfasste Hardwarezustand werden getrennt behandelt.
|
||||
|
||||
Secrets werden nicht als Klartextbestandteil der fachlichen Plattformdaten gespeichert.
|
||||
|
||||
Das konkrete Secret-Management wird in einer separaten Architekturentscheidung definiert.
|
||||
|
||||
## Konsequenzen
|
||||
|
||||
Der Provisioning-Server erhält eine PostgreSQL-Anbindung.
|
||||
|
||||
Änderungen am Datenbankschema werden über versionierte Migrationen verwaltet.
|
||||
|
||||
Geräte erhalten eine interne, von Hardwaremerkmalen unabhängige Datenbank-ID.
|
||||
|
||||
Der Hardware-Fingerprint dient der Wiedererkennung eines Geräts während des Provisioning-Handshakes.
|
||||
|
||||
Hardwaredaten werden getrennt von der Geräteidentität als Snapshots gespeichert.
|
||||
|
||||
Ein Gerät kann mehrere Hardware-Snapshots besitzen.
|
||||
|
||||
Netzwerkinterfaces und Massenspeicher können einem Hardware-Snapshot zugeordnet werden.
|
||||
|
||||
Die bestehende dateibasierte Device Registry wird nach erfolgreicher Einführung der Datenbank entfernt.
|
||||
|
||||
Die Provisioning API bleibt von der konkreten Persistenzimplementierung unabhängig.
|
||||
|
||||
51
architecture/00-provisioning-flow.md
Normal file
51
architecture/00-provisioning-flow.md
Normal file
@ -0,0 +1,51 @@
|
||||
Boot
|
||||
↓
|
||||
Preflight
|
||||
↓
|
||||
Netzwerk
|
||||
↓
|
||||
Hardware Identity
|
||||
↓
|
||||
Server Discovery
|
||||
↓
|
||||
Device Registration
|
||||
↓
|
||||
Assignment
|
||||
↓
|
||||
Runtime Blueprint
|
||||
↓
|
||||
Backend Selection
|
||||
↓
|
||||
Backend API
|
||||
↓
|
||||
Native Installer
|
||||
↓
|
||||
First Boot
|
||||
↓
|
||||
Provisioning Agent
|
||||
↓
|
||||
Managed System
|
||||
|
||||
## Aktivierung und Gerätebindung
|
||||
|
||||
Ein neues Gerät authentifiziert den ersten Provisionierungsvorgang mit einem Aktivierungscode.
|
||||
|
||||
Nach erfolgreicher Prüfung wird der vom Installer erzeugte Hardware-Fingerprint an den Provisioning-Server übertragen.
|
||||
|
||||
Der Server bindet den Fingerprint an die Aktivierung beziehungsweise an die zugehörige Organisation.
|
||||
|
||||
Der Aktivierungscode ist damit die initiale Berechtigung.
|
||||
|
||||
Der Hardware-Fingerprint dient anschließend zur Wiedererkennung des Geräts.
|
||||
|
||||
Ablauf:
|
||||
|
||||
Aktivierungscode
|
||||
↓
|
||||
Hardware-Fingerprint
|
||||
↓
|
||||
Geräteregistrierung oder Wiedererkennung
|
||||
↓
|
||||
Profil- beziehungsweise Workspace-Zuordnung
|
||||
↓
|
||||
Runtime Blueprint
|
||||
@ -0,0 +1,66 @@
|
||||
# Runtime Blueprint Model
|
||||
|
||||
**Status:** Stable
|
||||
|
||||
## Zweck dieses Dokuments
|
||||
|
||||
Dieses Dokument beschreibt das Runtime Blueprint als zentralen Integrationspunkt des Provisionierungsprozesses von Tuxflotte.
|
||||
|
||||
Es definiert seine Aufgabe innerhalb der Plattform und beschreibt seine Verantwortung sowie seine Abgrenzung zu den übrigen Komponenten der Architektur.
|
||||
|
||||
---
|
||||
|
||||
## Motivation
|
||||
|
||||
Workspace, Organization und Backend beschreiben jeweils einen eigenständigen Aspekt einer Bereitstellung.
|
||||
|
||||
Erst durch ihr Zusammenführen entsteht die vollständige Beschreibung einer konkreten Bereitstellung.
|
||||
|
||||
Das Runtime Blueprint verbindet diese Informationen und bildet die Grundlage für den weiteren Provisionierungsprozess.
|
||||
|
||||
---
|
||||
|
||||
## Definition
|
||||
|
||||
Das Runtime Blueprint beschreibt den vollständigen Zielzustand einer konkreten Bereitstellung.
|
||||
|
||||
Es wird zentral aus den Informationen von Workspace, Organization und Backend erzeugt und bereitgestellt.
|
||||
|
||||
Jede Bereitstellung erhält ihr eigenes Runtime Blueprint.
|
||||
|
||||
---
|
||||
|
||||
## Provisionierungsprozess
|
||||
|
||||
Der Provisionierungsprozess beginnt mit der Auswahl eines Workspace und endet nicht mit der Installation des Betriebssystems.
|
||||
|
||||
Er beschreibt den Weg von der fachlichen Beschreibung eines Arbeitsplatzes bis zur Umsetzung des gewünschten Zielzustands.
|
||||
|
||||
Während dieses Prozesses wird das Runtime Blueprint zentral erzeugt und den ausführenden Komponenten der Plattform bereitgestellt.
|
||||
|
||||
Der Installer setzt den beschriebenen Zielzustand während der Erstbereitstellung um.
|
||||
|
||||
Nach der Registrierung setzt der Provisioning Agent Änderungen des Zielzustands im weiteren Lebenszyklus um.
|
||||
|
||||
---
|
||||
|
||||
## Verantwortungsbereich
|
||||
|
||||
Das Runtime Blueprint beschreibt insbesondere
|
||||
|
||||
den vollständigen Zielzustand einer konkreten Bereitstellung,
|
||||
die Zusammenführung der Informationen aus Workspace, Organization und Backend,
|
||||
alle für die Umsetzung erforderlichen Informationen sowie
|
||||
die Grundlage für die Umsetzung durch Installer und Provisionierungs-Agent.
|
||||
|
||||
Das Runtime Blueprint beschreibt den Zielzustand.
|
||||
|
||||
Die Umsetzung dieses Zielzustands erfolgt durch die ausführenden Komponenten der Plattform.
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Das Runtime Blueprint verbindet die fachlichen Modelle von Tuxflotte mit ihrer konkreten Umsetzung.
|
||||
|
||||
Es bildet den zentralen Integrationspunkt des Provisionierungsprozesses und beschreibt den vollständigen Zielzustand eines konkreten Systems.
|
||||
56
architecture/07-runtime-layout.md
Normal file
56
architecture/07-runtime-layout.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Runtime Layout
|
||||
|
||||
## Laufzeitverzeichnis
|
||||
|
||||
Alle Installer-Module kommunizieren ausschließlich über:
|
||||
|
||||
/run/tuxflotte/
|
||||
|
||||
Jedes Modul besitzt genau ein eigenes Unterverzeichnis.
|
||||
|
||||
## Verzeichnisstruktur
|
||||
|
||||
/run/tuxflotte/
|
||||
├── network/
|
||||
├── hardware/
|
||||
├── server/
|
||||
├── runtime/
|
||||
├── backend/
|
||||
└── logs/
|
||||
|
||||
## Eigentümer der Verzeichnisse
|
||||
|
||||
network/
|
||||
Besitzer: 05_network.sh
|
||||
|
||||
hardware/
|
||||
Besitzer: 10_hardware.sh
|
||||
|
||||
server/
|
||||
Besitzer: 15_server_handshake.sh
|
||||
|
||||
runtime/
|
||||
Besitzer: Runtime-Builder
|
||||
|
||||
backend/
|
||||
Besitzer: Backend-Orchestrator
|
||||
|
||||
logs/
|
||||
Besitzer: zentraler Installer-Orchestrator
|
||||
|
||||
## Lese- und Schreibregeln
|
||||
|
||||
Ein Modul darf ausschließlich in sein eigenes Verzeichnis schreiben.
|
||||
|
||||
Andere Modulverzeichnisse dürfen nur gelesen werden, wenn dies für den definierten Ablauf erforderlich ist.
|
||||
|
||||
Beispiele:
|
||||
|
||||
- 10_hardware.sh schreibt nur nach /run/tuxflotte/hardware/
|
||||
- 15_server_handshake.sh liest aus /run/tuxflotte/network/ und /run/tuxflotte/hardware/
|
||||
- Der Runtime-Builder liest aus /run/tuxflotte/server/
|
||||
- Der Backend-Orchestrator liest aus /run/tuxflotte/runtime/
|
||||
|
||||
Direkte Funktionsaufrufe zwischen Modulen sind nicht vorgesehen.
|
||||
|
||||
Die Kommunikation erfolgt ausschließlich über Dateien im Runtime-Verzeichnis.
|
||||
142
architecture/08-provisioning-api.md
Normal file
142
architecture/08-provisioning-api.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Provisioning API
|
||||
|
||||
## Zweck
|
||||
|
||||
Die Provisioning API beschreibt die Kommunikation zwischen dem Tuxflotte-Installer und dem Provisioning-Server.
|
||||
|
||||
Sie ist distributionsunabhängig.
|
||||
|
||||
Die Backend API (06-backend-api.md) beginnt erst nach der Erstellung des Runtime Blueprints.
|
||||
|
||||
## Aktivierung
|
||||
|
||||
### Endpoint
|
||||
|
||||
POST /api/v1/activate
|
||||
|
||||
### Zweck
|
||||
|
||||
Der Endpoint autorisiert einen initialen Provisionierungsvorgang und registriert oder erkennt ein Gerät anhand seines Hardware-Fingerprints wieder.
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"activation_code": "LAB-2026-START",
|
||||
"device_fingerprint": "<sha256>",
|
||||
"hostname": "enterprise",
|
||||
"machine_id": "<machine-id>",
|
||||
"client_version": "0.1.0",
|
||||
"hardware": {
|
||||
"schema_version": 1,
|
||||
"identity": {
|
||||
"device_fingerprint": "<sha256>",
|
||||
"system_uuid": "<uuid>",
|
||||
"system_serial": null,
|
||||
"board_serial": "<serial>",
|
||||
"machine_id": "<machine-id>"
|
||||
},
|
||||
"system": {
|
||||
"manufacturer": null,
|
||||
"product_name": null,
|
||||
"product_version": null,
|
||||
"architecture": "x86_64",
|
||||
"cpu": {
|
||||
"model": "<cpu-model>",
|
||||
"logical_count": 8
|
||||
},
|
||||
"memory_bytes": 33446432768
|
||||
},
|
||||
"mainboard": {
|
||||
"vendor": "Intel Corporation",
|
||||
"name": "DH87MC"
|
||||
},
|
||||
"firmware": {
|
||||
"bios_vendor": "Intel Corp.",
|
||||
"bios_version": "<bios-version>",
|
||||
"boot_mode": "bios",
|
||||
"secure_boot": "unsupported"
|
||||
},
|
||||
"security": {
|
||||
"tpm_version": "none"
|
||||
},
|
||||
"network_interfaces": [
|
||||
{
|
||||
"name": "eno1",
|
||||
"type": "ethernet",
|
||||
"mac": "<mac-address>"
|
||||
}
|
||||
],
|
||||
"storage_devices": [
|
||||
{
|
||||
"name": "sda",
|
||||
"model": "<model>",
|
||||
"serial": "<serial>",
|
||||
"size_bytes": 512110190592,
|
||||
"transport": "sata"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Der `hardware`-Block enthält den während der Provisionierung erfassten Hardwarezustand.
|
||||
|
||||
Der Provisioning-Server registriert oder erkennt das Gerät anhand des `device_fingerprint` und speichert den übertragenen Hardwarezustand als zeitbezogenen Hardware-Snapshot.
|
||||
|
||||
Bei einer erneuten erfolgreichen Aktivierung desselben Geräts bleibt die interne Geräte-ID erhalten. Für den aktuellen Aktivierungsvorgang wird ein neuer Hardware-Snapshot erzeugt.
|
||||
|
||||
### Response
|
||||
|
||||
Bei erfolgreicher Aktivierung antwortet der Provisioning-Server mit dem registrierten Gerät, der zugeordneten Organisation und den verfügbaren Provisioning-Profilen.
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"device": {
|
||||
"id": "<device-uuid>",
|
||||
"fingerprint": "<sha256>",
|
||||
"hostname": "enterprise",
|
||||
"created": false,
|
||||
"hardware_snapshot_id": "<hardware-snapshot-uuid>"
|
||||
},
|
||||
"customer": {
|
||||
"id": "default",
|
||||
"organization_id": "<organization-uuid>",
|
||||
"name": "Default Lab"
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"id": "fedora-workstation",
|
||||
"label": "Fedora Workstation",
|
||||
"distribution": "fedora",
|
||||
"version": "40",
|
||||
"description": "Standard-Workstation-Profil für Fedora.",
|
||||
"ansible_repo": "<repository-url>",
|
||||
"installer": {
|
||||
"type": "kickstart",
|
||||
"url": "<kickstart-url>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`device.id` ist die interne, von Hardwaremerkmalen unabhängige Geräte-ID.
|
||||
|
||||
`device.created` gibt an, ob das Gerät während dieses Aktivierungsvorgangs neu registriert wurde.
|
||||
|
||||
`device.hardware_snapshot_id` referenziert den für diesen Aktivierungsvorgang erzeugten Hardware-Snapshot.
|
||||
|
||||
Der Aktivierungscode bestimmt die zugeordnete Organisation und die für den Provisionierungsvorgang verfügbaren Profile.
|
||||
|
||||
### Fehlerantwort
|
||||
|
||||
Ist der Aktivierungscode ungültig, antwortet der Provisioning-Server mit:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "invalid_activation_code",
|
||||
"message": "Der Aktivierungscode ist ungültig."
|
||||
}
|
||||
```
|
||||
246
architecture/09-data-model-v1.md
Normal file
246
architecture/09-data-model-v1.md
Normal file
@ -0,0 +1,246 @@
|
||||
# Tuxflotte Datenmodell v1
|
||||
|
||||
## Zweck
|
||||
|
||||
Das Datenmodell beschreibt die persistenten Kernobjekte der Tuxflotte-Plattform und ihre Beziehungen.
|
||||
|
||||
Die konkrete SQL-Implementierung und das Migrationssystem werden getrennt von diesem fachlichen Modell behandelt.
|
||||
|
||||
## Kernobjekte
|
||||
|
||||
- Organization
|
||||
- Device
|
||||
- Hardware Snapshot
|
||||
- Network Interface
|
||||
- Storage Device
|
||||
- Workspace
|
||||
- Backend
|
||||
- Assignment
|
||||
- Network Profile
|
||||
- Secret Reference
|
||||
|
||||
## Organization
|
||||
|
||||
Eine Organization beschreibt eine organisatorische Einheit innerhalb der Tuxflotte-Plattform.
|
||||
|
||||
Eine Organization besitzt eigene Geräte, Workspaces, Netzwerkprofile und Richtlinien.
|
||||
|
||||
Attribute:
|
||||
|
||||
- id
|
||||
- name
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- besitzt mehrere Devices
|
||||
- besitzt mehrere Workspaces
|
||||
- besitzt mehrere Network Profiles
|
||||
- referenziert Secrets
|
||||
|
||||
## Device
|
||||
|
||||
Ein Device repräsentiert ein dauerhaft bekanntes physisches oder virtuelles Gerät.
|
||||
|
||||
Die interne Geräte-ID ist unabhängig von Hardwaremerkmalen.
|
||||
|
||||
Der Hardware-Fingerprint dient der Wiedererkennung während des Provisioning-Handshakes.
|
||||
|
||||
Attribute:
|
||||
|
||||
- id
|
||||
- organization_id
|
||||
- device_fingerprint
|
||||
- hostname
|
||||
- created_at
|
||||
- last_seen
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- gehört zu genau einer Organization
|
||||
- besitzt mehrere Hardware Snapshots
|
||||
- besitzt optional ein Assignment
|
||||
|
||||
## Hardware Snapshot
|
||||
|
||||
Ein Hardware Snapshot beschreibt den zu einem bestimmten Zeitpunkt erfassten Hardwarezustand eines Devices.
|
||||
|
||||
Hardware Snapshots sind zeitbezogene Inventardaten und nicht Bestandteil der stabilen Geräteidentität.
|
||||
|
||||
Attribute:
|
||||
|
||||
- id
|
||||
- device_id
|
||||
- collected_at
|
||||
- architecture
|
||||
- manufacturer
|
||||
- product_name
|
||||
- product_version
|
||||
- system_uuid
|
||||
- system_serial
|
||||
- board_vendor
|
||||
- board_name
|
||||
- board_serial
|
||||
- bios_vendor
|
||||
- bios_version
|
||||
- boot_mode
|
||||
- secure_boot
|
||||
- tpm_version
|
||||
- cpu_model
|
||||
- cpu_logical_count
|
||||
- memory_bytes
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- gehört zu genau einem Device
|
||||
- besitzt mehrere Network Interfaces
|
||||
- besitzt mehrere Storage Devices
|
||||
|
||||
## Network Interface
|
||||
|
||||
Ein Network Interface beschreibt eine zum Zeitpunkt des Hardware Snapshots vorhandene Netzwerkschnittstelle.
|
||||
|
||||
Attribute:
|
||||
|
||||
- id
|
||||
- hardware_snapshot_id
|
||||
- name
|
||||
- type
|
||||
- mac_address
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- gehört zu genau einem Hardware Snapshot
|
||||
|
||||
## Storage Device
|
||||
|
||||
Ein Storage Device beschreibt einen zum Zeitpunkt des Hardware Snapshots vorhandenen Massenspeicher.
|
||||
|
||||
Attribute:
|
||||
|
||||
- id
|
||||
- hardware_snapshot_id
|
||||
- model
|
||||
- serial
|
||||
- size_bytes
|
||||
- transport
|
||||
- name
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- gehört zu genau einem Hardware Snapshot
|
||||
|
||||
## Relationales Schema v1
|
||||
|
||||
### organizations
|
||||
|
||||
Spalten:
|
||||
|
||||
- id
|
||||
- name
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
### devices
|
||||
|
||||
Spalten:
|
||||
|
||||
- id
|
||||
- organization_id
|
||||
- device_fingerprint
|
||||
- hostname
|
||||
- created_at
|
||||
- last_seen
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- organization_id referenziert organizations.id
|
||||
|
||||
Constraints:
|
||||
|
||||
- device_fingerprint ist eindeutig
|
||||
|
||||
### hardware_snapshots
|
||||
|
||||
Spalten:
|
||||
|
||||
- id
|
||||
- device_id
|
||||
- collected_at
|
||||
- architecture
|
||||
- manufacturer
|
||||
- product_name
|
||||
- product_version
|
||||
- system_uuid
|
||||
- system_serial
|
||||
- board_vendor
|
||||
- board_name
|
||||
- board_serial
|
||||
- bios_vendor
|
||||
- bios_version
|
||||
- boot_mode
|
||||
- secure_boot
|
||||
- tpm_version
|
||||
- cpu_model
|
||||
- cpu_logical_count
|
||||
- memory_bytes
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- device_id referenziert devices.id
|
||||
|
||||
### network_interfaces
|
||||
|
||||
Spalten:
|
||||
|
||||
- id
|
||||
- hardware_snapshot_id
|
||||
- name
|
||||
- type
|
||||
- mac_address
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- hardware_snapshot_id referenziert hardware_snapshots.id
|
||||
|
||||
### storage_devices
|
||||
|
||||
Spalten:
|
||||
|
||||
- id
|
||||
- hardware_snapshot_id
|
||||
- name
|
||||
- model
|
||||
- serial
|
||||
- size_bytes
|
||||
- transport
|
||||
|
||||
Beziehungen:
|
||||
|
||||
- hardware_snapshot_id referenziert hardware_snapshots.id
|
||||
|
||||
## Beziehungskonsequenzen
|
||||
|
||||
Wird eine Organization gelöscht, dürfen zugehörige Devices nicht implizit mitgelöscht werden.
|
||||
|
||||
Wird ein Device gelöscht, werden zugehörige Hardware Snapshots mitgelöscht.
|
||||
|
||||
Wird ein Hardware Snapshot gelöscht, werden zugehörige Network Interfaces und Storage Devices automatisch mitgelöscht.
|
||||
|
||||
Für die relationale Umsetzung gilt daher:
|
||||
|
||||
- devices.organization_id → ON DELETE RESTRICT
|
||||
- hardware_snapshots.device_id → ON DELETE CASCADE
|
||||
- network_interfaces.hardware_snapshot_id → ON DELETE CASCADE
|
||||
- storage_devices.hardware_snapshot_id → ON DELETE CASCADE
|
||||
|
||||
## ID-Erzeugung
|
||||
|
||||
Persistente Plattformobjekte verwenden UUIDs als interne IDs.
|
||||
|
||||
UUIDs werden durch die Anwendung erzeugt.
|
||||
|
||||
Die Datenbank validiert und speichert die IDs, erzeugt sie jedoch nicht selbst.
|
||||
|
||||
Dadurch stehen Objekt-IDs bereits vor dem Datenbank-INSERT zur Verfügung und können innerhalb einer Transaktion für abhängige Datensätze verwendet werden.
|
||||
@ -39,7 +39,7 @@ Ziel: Netzwerkgestützter Installer.
|
||||
|
||||
### Registrierung
|
||||
|
||||
* [ ] Geräteaktivierung
|
||||
* [x] Geräteaktivierung
|
||||
* [ ] Benutzeranmeldung
|
||||
* [ ] Auswahl von OS- und Konfigurationsprofilen
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user