Kunskapsbas
Idempotens och omkörning
Arkitekturwireguardidempotensochomkörning| Komponent | Beteende vid omkörning | |-----------|----------------------| | **Nyckelpar (wg0/wg1)** | Genereras bara om nyckelfilen inte finns. Omkörning bevarar befintliga nycklar. | | **wg0.conf** | Deployeras bara vid första körningen (stat-check). Onboardade peers bevaras vid omstart av wireguard-rollen. | | **onboard_peer.yml** | Kollar om peer redan finns (grep på pubkey) innan den läggs till. Använder `wg syncconf` istället för restart. | | **UFW-regler** | Idempotenta — UFW ignorerar dubbletter. | | **API-registrering** | Använder upsert — uppdaterar om endpoint redan finns. | > **Viktigt:** Om wg0.conf behöver återskapas (t.ex. vid korrupt fil) måste alla peers onboardas om.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Brandväggsregler (UFW på VM)
Arkitekturwireguardbrandväggsreglerufw``` ┌─────────────────────────────────────────────────────────────┐ │ Default: DENY incoming, ALLOW outgoing │ ├─────────────────────────────────────────────────────────────┤ │ ALLOW 51821/udp ← WireGuard admin (wg1) │ │ ALLOW 51820/udp ← WireGuard kund (wg0) │ │ ALLOW from 10.60.0.0/16 port 22 ← SSH via admin-VPN │ │ ALLOW from 10.60.0.0/16 port 3389 ← RDP via admin-VPN │ │ ALLOW from 10.50.0.0/16 port 3389 ← RDP via kund-VPN │ └─────────────────────────────────────────────────────────────┘ ``` SSH och RDP är **aldrig** tillgängligt från publikt internet.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Databasmodell
Arkitekturwireguarddatabasmodell``` wg_endpoints (per VM, per syfte) ┌──────────────────┬──────────┬─────────┬─────────────┬──────────────┐ │ wg_endpoint_id │ vm_id │ purpose │ listen_port │ public_key │ ├──────────────────┼──────────┼─────────┼─────────────┼──────────────┤ │ uuid-1 │ vm-abc │ 1 (adm) │ 51821 │ WG1_PUBKEY │ │ uuid-2 │ vm-abc │ 2 (cust)│ 51820 │ WG0_PUBKEY │ └──────────────────┴──────────┴─────────┴─────────────┴──────────────┘ wg_peers (klient → VM) ┌──────────────┬──────────┬─────────┬──────────────┬─────────────────┐ │ wg_peer_id │ vm_id │ purpose │ public_key │ peer_wg_ip_cidr │ ├──────────────┼──────────┼─────────┼──────────────┼─────────────────┤ │ uuid-a │ vm-abc │ 1 (cust)│ CLIENT_PUB │ 10.50.0.2/32 │ └──────────────┴──────────┴─────────┴──────────────┴─────────────────┘ ```
Skapad av doc-import, uppdaterad 2026-03-23 14:17IP-adressplan
Arkitekturwireguardadressplan``` 10.50.0.0/16 — Kund-VPN (wg0) ├── 10.50.0.0/24 — VM #1 │ ├── 10.50.0.1 — VM:ens wg0-adress │ ├── 10.50.0.2 — Klient #1 │ ├── 10.50.0.3 — Klient #2 │ └── ... ├── 10.50.1.0/24 — VM #2 │ ├── 10.50.1.1 — VM:ens wg0-adress │ └── ... └── ... 10.60.0.0/16 — Admin-VPN (wg1) ├── 10.60.0.1/32 — VM #1 ├── 10.60.0.2/32 — VM #2 └── 10.60.0.0/16 — Admin-arbetsplats (alla allowed) ```
Skapad av doc-import, uppdaterad 2026-03-23 14:17Nyckelhantering
Arkitekturwireguardnyckelhantering```mermaid graph LR subgraph "VM (Hetzner)" WG1K[wg1.key/pub<br/>cloud-config] WG0K[wg0.key/pub<br/>Ansible] end subgraph "Backend DB" WE1[wg_endpoints<br/>purpose=1 admin] WE2[wg_endpoints<br/>purpose=2 customer] WP[wg_peers<br/>klient-pubkeys] end subgraph "Tunn klient" TCK[wg0.key/pub<br/>onboarding-skript] end WG1K -->|pubkey via cloud-init callback| WE1 WG0K -->|pubkey via Ansible POST| WE2 TCK -->|pubkey via enrollment| WP WE2 -->|serverns pubkey| TCK ``` **Princip:** Privata nycklar lämnar aldrig enheten de skapas på. Bara publika nycklar utbyts via API. | Nyckel | Skapas på | Lagras i DB | Distribueras till | |--------|-----------|-------------|-------------------| | VM wg1 privat | VM (cloud-init) | Aldrig | Stannar på VM | | VM wg1 publik | VM (cloud-init) | `wg_endpoints` (purpose=1) | Admin-klient (manuellt) | | VM wg0 privat | VM (Ansible) | Aldrig | Stannar på VM | | VM wg0 publik | VM (Ansible) | `wg_endpoints` (purpose=2) | Tunna klienter (via enrollment) | | Klient privat | Tunn klient | Aldrig | Stannar på klient | | Klient publik | Tunn klient | `wg_peers` | VM (via AddPeer-kommando) |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Dual-interface design
Arkitekturwireguarddualinterfacedesign``` ┌───────────────────────────────────┐ │ Hetzner VM │ │ │ Admin ─── wg1 ────┤ 10.60.x.x/16 port 51821 │ arbets- (admin) │ Skapas av: cloud-config │ plats │ Nyckel: /etc/wireguard/wg1.key │ │ │ Tunn ─── wg0 ────┤ 10.50.x.x/16 port 51820 │ klient (kund) │ Skapas av: Ansible │ │ Nyckel: /etc/wireguard/wg0.key │ │ │ └───────────────────────────────────┘ ``` ### Varför två interface? | Egenskap | wg1 (admin) | wg0 (kund) | |----------|-------------|------------| | **Syfte** | Admin-åtkomst (SSH, felsökning) | Kundanvändning (RDP) | | **Skapas av** | Cloud-config (vid VM-start) | Ansible (efter provisionering) | | **Subnät** | 10.60.0.0/16 | 10.50.0.0/16 | | **Port** | 51821 | 51820 | | **Peers** | Admin-arbetsplats | Tunna klienter | | **Nyckelpar** | wg1.key / wg1.pub | wg0.key / wg0.pub | | **Livscykel** | Finns från VM-start | Läggs till efter Ansible | Separationen innebär att: - Admin kan alltid nå VM:en via wg1, även om wg0 har problem - Kund-peers kan inte nå admin-nätverket - Nycklarna är oberoende
Skapad av doc-import, uppdaterad 2026-03-23 14:17WireGuard-arkitektur
ArkitekturwireguardarkitekturKvillo använder WireGuard med **två separata interface** per VM för att skilja admin-trafik från kundtrafik.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Partitionslayout
ArkitekturthinclientimagepartitionslayoutSD-kortet har tre partitioner efter installation: | Partition | Mount | Filsystem | Storlek | Syfte | |-----------|-------|-----------|---------|-------| | mmcblk0p1 | /boot/firmware | FAT32 | ~512 MB | Boot-partition (kernel, cmdline.txt, device.conf) | | mmcblk0p2 | / | ext4 | ~12 GB | Root (monteras read-only i produktion) | | mmcblk0p3 | /data | ext4 | ~2 GB | Persistent skrivbar lagring (Chromium-profil, m.m.) | `/data` förblir skrivbar oavsett om root monteras read-only. Används av: - **Chromium** (Speladator-addon): `--user-data-dir=/data/chromium` sparar inloggning och sessioner - Framtida behov av persistent lagring som inte ska ligga på root
Skapad av doc-import, uppdaterad 2026-03-23 14:17Kända problem
Arkitekturthinclientimagekändaproblem| Problem | Lösning | |---------|---------| | Windows → Pi: `\r\n` radbrytningar | `build-image.sh` fixar automatiskt. Manuellt: `sed -i 's/\r$//' install.sh` | | Wifi försvinner efter reboot | install.sh fixar detta med NM dispatcher (power save off) | | Pi hittar inte wifi efter omflash | Wifi-credentials bakes in via Pi Imager — kontrollera att rätt SSID angavs | | SSH kräver lösenord | Kör `install.sh --dev` som installerar admin SSH-nyckel | | Firstboot körs inte igen | Ta bort `/etc/easydesk/.provisioned` innan ny provisionering | | Stor image-fil (16GB+) | Krymp med PiShrink i WSL | | Klockan fel efter strömavbrott | Lösning inbyggd i imagen: clocksave + NTP + WAIT_CLOCK (se ovan) | | Certificate verify failed | Klockan är fel → vänta på NTP-sync (WAIT_CLOCK hanterar detta automatiskt) | | `mount: / is busy` vid remount,ro | SSH-session kan blockera. Löser sig vid reboot | | Root fyller hela SD-kortet | Auto-expand körde vid första boot. Flasha om och ta bort `resize` från cmdline.txt före boot | | `/data` saknas vid boot | Kontrollera att fstab har `LABEL=data /data ext4 defaults,noatime,nofail 0 2` |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Klockhantering (NTP + fake-hwclock)
ArkitekturthinclientimageklockhanteringntpfakeRaspberry Pi 5 saknar batteribackad realtidsklocka (RTC). Vid strömavbrott (t.ex. om sladden dras ur) återställs klockan till en gammal tidpunkt, vilket kan orsaka SSL-certifikatfel vid enrollment och VPN-anslutning. Tre lager skyddar mot detta: ### Lager 1: `easydesk-clocksave.service` Ersätter standardtjänsten `fake-hwclock` (som inte fungerar på skrivskyddat filsystem): - **Vid boot:** Läser sparad tid från `/etc/fake-hwclock.data` → sätter systemklockan till senast kända tid - **Vid avstängning:** Remountar filsystemet som rw → sparar aktuell tid → remountar ro Vid **normal avstängning** (strömknappen) sparas klockan. Vid **strömavbrott** (sladden dras ur) startar klockan från image-byggdatumet — men lager 2 och 3 hanterar det. ### Lager 2: `systemd-timesyncd` (NTP) Aktiveras explicit i install.sh. Synkroniserar klockan via NTP så fort nätverket är uppe (typiskt 2-5 sekunder efter WiFi-anslutning). ### Lager 3: WAIT_CLOCK i klient-appen State machine i `main.py` har ett `WAIT_CLOCK`-tillstånd mellan `CHECK_NETWORK` och `CHECK_ENROLLMENT`. Visar "Synkroniserar klockan..." och väntar tills `time.time() >= 2026-01-01` innan den försöker SSL-anslutningar. ### Värsta fall (strömavbrott utan NTP) 1. Boot startar med image-byggdatum (från `fake-hwclock save` under install) 2. WiFi ansluts 3. WAIT_CLOCK visar splash, pollar varje sekund 4. NTP synkar klockan → WAIT_CLOCK passerar → enrollment/VPN fortsätter normalt
Skapad av doc-import, uppdaterad 2026-03-23 14:17Vad install.sh gör
Arkitekturthinclientimagevadinstallgör| Steg | Beskrivning | |------|-------------| | 0. Data-partition | Skapar `/data`-partition (mmcblk0p3, 2 GB) för persistent lagring (Chromium m.m.). Kräver att auto-expand är inaktiverat | | 1. Paket | Xorg, Openbox, Python 3 + GTK3, FreeRDP3, WireGuard, NM, SSH, UFW, fake-hwclock, Chromium | | 2. Användare | Skapar `easydesk`-användare (kiosk-konto), sätter ägarskap på `/data` | | 3. Autologin | Getty autologin → startx → Openbox → main.py | | 4. Sudoers | Begränsad sudo för wg-quick, reboot, poweroff, mount remount | | 5. Applikation | Kopierar Python-filer, firstboot, bootsplash, clocksave, Openbox-config, SSH-config | | 6. Brandvägg | UFW: neka inkommande, tillåt utgående. Dev: SSH öppet. Prod: SSH via WG | | 7. Wifi | Stänger av wifi power management (förhindrar slumpmässiga bortkopplingar) | | 8. Tjänster | Inaktiverar Bluetooth och Avahi. Strömknapp → ignore. Rensar init= från cmdline.txt | | 9. NetworkManager | Byter från dhcpcd till NetworkManager | | 10. Locale | sv_SE.UTF-8 | | 11. Skrivskyddat root | Volatile journald, fake-hwclock via clocksave-tjänst, ingen swap, timesyncd-symlinks, tmpfs för /tmp | | 12. SSH | Aktiverar SSH-tjänsten | | 13. Support-nycklar | Förbereder authorized_keys för fjärrsupport |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Provisionering av enhet
ArkitekturthinclientimageprovisioneringenhetNär du har en färdig bas-image och vill provisionera en specifik enhet: ### Via Admin-portalen (rekommenderat) 1. **Flasha imagen** till ett SD-kort med Win32DiskImager (Write) 2. Logga in på **Admin-portalen** (admin.easydesk.se) 3. Gå till **Tunna klienter** och klicka **Registrera** — ange inventory tag (t.ex. `ED-PI-002`) 4. Gå till **VM-detalj** och **binda** den tunna klienten till en VM 5. Tillbaka på **Tunna klienter** — klicka **Provision-paket** på enheten 6. Klicka **Ladda ner SD-kortsskript** — en `.bat`-fil laddas ner 7. **Sätt i SD-kortet** i datorn och kör `.bat`-filen: - Skriptet listar tillgängliga enheter (D:, E:, F:, ...) - Ange enhetsbokstaven för SD-kortet - Skriptet skapar `\easydesk\device.conf` på boot-partitionen 8. **Sätt i kortet** i Pi:n och boota 9. Pi:n kör automatiskt: firstboot → enrollment → VPN → RDP > **Onboarding-koden** i provision-paketet har en giltighetstid (default 24h). Om koden löper ut, generera ett nytt provision-paket. ### Manuellt (alternativ) Om du inte vill använda admin-portalen kan du skapa `device.conf` manuellt: 1. Flasha imagen till SD-kortet 2. Skapa mappen `easydesk` på boot-partitionen (t.ex. `E:\easydesk\`) 3. Skapa `device.conf` med LF-radbrytningar: ``` INVENTORY_TAG=ED-PI-001 ONBOARDING_CODE=XXXXXX API_URL=https://api.easydesk.se ``` 4. Sätt i kortet i Pi:n och boota > **OBS:** device.conf måste ha **LF-radbrytningar** (inte CRLF). ### Via PowerShell ```powershell .\Provision-SdCard.ps1 -InventoryTag "ED-PI-001" -BootDir "E:\easydesk" ``` Skriptet loggar in mot API:t, hämtar provision-paket och skriver device.conf automatiskt.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 6: Krympa imagen
ArkitekturthinclientimagestegkrympaimagenKör PiShrink i WSL: ```bash # Om du inte har PiShrink ännu: # git clone https://github.com/Drewsif/PiShrink.git # chmod +x PiShrink/pishrink.sh # Navigera till image-filen (Windows-sökväg i WSL) cd /mnt/c/path/to/images/ # Krympa sudo pishrink.sh kvillo-base-dev-260307.img ``` Imagen krymps från ~16GB till ~3-4GB.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 5: Läs SD-kortet till image-fil
Arkitekturthinclientimagestegläskortet1. Ta ut SD-kortet från Pi:n och sätt i kortläsaren på Windows 2. Starta **Win32DiskImager** 3. Välj enhet (t.ex. `E:`) 4. Ange filnamn: `kvillo-base-dev-YYMMDD.img` (t.ex. `kvillo-base-dev-260307.img`) 5. Klicka **Read** — vänta tills det är klart > **OBS:** Filen blir lika stor som hela SD-kortet (t.ex. 16GB). Steg 6 krymper den.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 4: Rensa inför image-skapande
ArkitekturthinclientimagestegrensainförInnan du stänger av Pi:n, rensa tillfälliga filer så imagen blir ren: ```bash # Ta bort installationsfiler sudo rm -rf /home/pi/easydesk-client # Rensa sparade WiFi-nätverk sudo rm -f /etc/NetworkManager/system-connections/* # Rensa bash-historik sudo rm -f /home/pi/.bash_history /home/easydesk/.bash_history history -c # Ta bort eventuella provisioning-flaggor (om du testat enrollment) sudo rm -f /etc/easydesk/.provisioned /etc/easydesk/.enrolled /etc/easydesk/.verified sudo rm -f /etc/easydesk/enrollment.json /etc/easydesk/device_secret # Rensa WireGuard-nycklar sudo rm -f /etc/wireguard/wg0.key /etc/wireguard/wg0.pub /etc/wireguard/wg0.conf # Rensa apt-cache sudo apt-get clean sudo apt-get autoremove -y # Nollställ machine-id (genereras vid nästa boot) sudo truncate -s 0 /etc/machine-id # Stäng av sudo shutdown -h now ``` > **OBS:** `build-image.sh` gör allt detta automatiskt.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 3b: Manuellt alternativ
ArkitekturthinclientimagestegmanuelltalternativOm du vill köra stegen manuellt: ```bash # Kopiera hela easydesk-client-mappen till Pi:n scp -r EasyDeskSolution/ThinClient/easydesk-client pi@<IP>:~/ ``` SSH in och kör: ```bash ssh pi@<IP> cd ~/easydesk-client # VIKTIGT: Fixa Windows-radbrytningar (CRLF → LF) sudo find . -type f \( -name "*.sh" -o -name "*.py" -o -name "*.conf" \ -o -name "*.service" -o -name "*.xml" -o -name "*.css" \) \ -exec sed -i 's/\r$//' {} + # Kör installationen i utvecklingsläge sudo bash install.sh --dev ``` `--dev` ger: - SSH öppen från LAN (inte bara via WireGuard) - `pi`-användare behålls med NOPASSWD sudo - Admin SSH-nyckel installeras för lösenordsfri åtkomst > **Produktion:** Kör `sudo ./install.sh` (utan `--dev`) för produktions-images där SSH bara är tillgängligt via WireGuard.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 3: Bygg image (automatiserat)
ArkitekturthinclientimagestegbyggDet enklaste sättet att bygga en image — kör från Git Bash på Windows: ```bash cd /c/Privat/repon/Kvillo bash EasyDeskSolution/ThinClient/build-image.sh <PI-IP> ``` > **CRLF-problem:** Om du får `$'\r': command not found` har Git checkat ut skriptet med Windows-radbrytningar. Fix: `sed -i 's/\r$//' EasyDeskSolution/ThinClient/build-image.sh` och kör igen. Alternativt, sätt `git config core.autocrlf input` för att förhindra det i framtiden. Skriptet gör allt automatiskt: 1. Rensar known_hosts för Pi:ns IP 2. Kopierar `easydesk-client/` till Pi:n via SCP 3. Fixar CRLF → LF på alla skript/config-filer 4. Kör `install.sh --dev` 5. Pausar så du kan testa manuellt (SSH in och verifiera) 6. Tryck Enter → rensar och stänger av Pi:n 7. Ta ut SD-kortet → läs med Win32DiskImager → krympa med PiShrink
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 2: Första boot och SSH
Arkitekturthinclientimagestegförstaboot1. Sätt i SD-kortet i Pi:n och starta 2. Vänta ~1 minut tills Pi:n är uppe 3. Hitta Pi:ns IP (router, `nmap`, eller anslut skärm) 4. Om du tidigare SSH:at till en annan Pi på samma IP (vanligt vid omflashning), rensa den gamla nyckeln: ```bash ssh-keygen -R <IP> ``` Annars får du `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED` och SSH vägrar ansluta. 5. SSH in: ```bash ssh pi@<IP> ```
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 1b: Inaktivera auto-expand (VIKTIGT)
ArkitekturthinclientimagesteginaktiveraautoPi OS expanderar automatiskt root-partitionen till hela SD-kortet vid första boot. Detta måste inaktiveras så att `install.sh` kan skapa en separat `/data`-partition. 1. **Sätt i SD-kortet** i datorn efter flashning 2. Öppna **boot-partitionen** (den lilla FAT32-partitionen, t.ex. `D:` eller `E:`) 3. Öppna `cmdline.txt` i en texteditor 4. Ta bort ordet **`resize`** från raden 5. Spara filen (behåll allt på **en rad**) > **Varför:** Root måste vara mindre än hela SD-kortet så att install.sh kan skapa en 2 GB `/data`-partition i slutet. Denna partition förblir skrivbar även när root monteras read-only, och används av Chromium (Speladator) för att spara profiler. > **Om du glömmer:** install.sh detekterar att det inte finns ledigt utrymme och skriver ut tydliga instruktioner. Du behöver då flasha om SD-kortet.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg 1: Flasha Pi OS Lite
Arkitekturthinclientimagestegflashalite1. Starta **Raspberry Pi Imager** 2. Välj **Raspberry Pi OS Lite (64-bit)** (Bookworm eller Trixie) 3. Klicka kugghjulet (inställningar) och konfigurera: - **Hostname:** valfritt (t.ex. `kvillo-image`) - **Användarnamn/lösenord:** `pi` / valfritt lösenord - **WiFi:** ange SSID och lösenord (detta bakes in i imagen) - **Locale:** `sv_SE.UTF-8`, timezone `Europe/Stockholm` - **SSH:** aktivera med lösenordsautentisering 4. Flasha till SD-kortet
Skapad av doc-import, uppdaterad 2026-03-23 14:17Förutsättningar
Arkitekturthinclientimageförutsättningar| Vad | Detaljer | |-----|----------| | Raspberry Pi 5 | 2GB RAM (se [Hårdvara](hardware.md) för specifikation) | | SD-kort | 16GB microSD Kingston Canvas Select Plus | | Raspberry Pi Imager | [raspberrypi.com/software](https://www.raspberrypi.com/software/) | | Win32DiskImager | För att läsa SD → image-fil | | PiShrink | I WSL, för att krympa imagen | | Nätverkskabel eller wifi | Pi:n behöver internet under installation |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Innehåll
Arkitekturthinclientimageinnehåll- [Förutsättningar](#förutsättningar) - [Steg 1: Flasha Pi OS Lite](#steg-1-flasha-pi-os-lite) - [Steg 2: Första boot och SSH](#steg-2-första-boot-och-ssh) - [Steg 3: Bygg image (automatiserat)](#steg-3-bygg-image-automatiserat) - [Steg 3b: Manuellt alternativ](#steg-3b-manuellt-alternativ) - [Steg 4: Rensa inför image-skapande](#steg-4-rensa-inför-image-skapande) - [Steg 5: Läs SD-kortet till image-fil](#steg-5-läs-sd-kortet-till-image-fil) - [Steg 6: Krympa imagen](#steg-6-krympa-imagen) - [Provisionering av enhet](#provisionering-av-enhet) - [Vad install.sh gör](#vad-installsh-gör) - [Klockhantering (NTP + fake-hwclock)](#klockhantering-ntp--fake-hwclock) - [Kända problem](#kända-problem)
Skapad av doc-import, uppdaterad 2026-03-23 14:17Tunn klient — Bas-image
ArkitekturthinclientimagetunnklientbasSteg-för-steg-guide för att skapa och underhålla Kvillo bas-imagen för Raspberry Pi.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Beroendegraf
Arkitektursupportsystemberoendegraf``` 1. DB + API (grund) ├──→ 2. Runner (support-loop) │ └──→ 3. Pi SSH (behövs för TC-kommandon) └──→ 4. Admin UI (supportsida) ```
Skapad av doc-import, uppdaterad 2026-03-23 14:17Pi SSH-härdning
Arkitektursupportsystemsshhärdning- `openssh-server` installerat, UFW `allow from 10.50.0.0/16 to any port 22` - Admin SSH-nyckel inbakad i image (`authorized_keys`) - Sudoers för support-kommandon (NOPASSWD)
Skapad av doc-import, uppdaterad 2026-03-23 14:17Runner-exekvering
Arkitektursupportsystemrunnerexekvering### SSH-vägar ```bash # VM-kommando (direkt) ssh -i $KEY -o ConnectTimeout=10 root@10.60.x.1 "kommando" # Pi-kommando (dubbelhopp med agent forwarding) ssh -A -i $KEY -o ConnectTimeout=10 root@10.60.x.1 \ "ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no easydesk@10.50.0.2 'kommando'" ``` ### Flöde 1. Polla `GET /api/support/jobs/pending?max=1` 2. Hämta detaljer `GET /api/support/jobs/{id}/details` 3. Markera `running` 4. Slå upp kommando i kommandokatalogen 5. SSH till VM (eller dubbelhopp till Pi) 6. Fånga stdout/stderr (max 100 KB) 7. Markera `done` (succeeded/failed) med output
Skapad av doc-import, uppdaterad 2026-03-23 14:17API-endpoints
Arkitektursupportsystemapiendpoints| Metod | Route | Auth | Syfte | |-------|-------|------|-------| | POST | `/api/support/jobs` | Ja (admin) | Skapa supportjobb | | GET | `/api/support/jobs/pending?max=5` | Ja (runner) | Polla väntande jobb | | GET | `/api/support/jobs/{id}/details` | Ja (runner) | Hämta jobb + WG-info | | POST | `/api/support/jobs/{id}/running` | Ja (runner) | Markera start | | POST | `/api/support/jobs/{id}/done` | Ja (runner) | Rapportera resultat | | GET | `/api/support/vms/{vmId}/jobs?limit=20` | Ja (admin) | Historik per VM | | GET | `/api/support/jobs/{id}` | Ja (admin) | Detalj |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Datamodell
Arkitektursupportsystemdatamodell### Tabell: `support_jobs` | Kolumn | Typ | Beskrivning | |--------|-----|-------------| | `support_job_id` | CHAR(36) PK | UUID | | `vm_id` | CHAR(36) FK | Koppling till vm_instances | | `target_type` | VARCHAR(20) | `vm` eller `thin_client` | | `command_key` | VARCHAR(50) | T.ex. `restart_xrdp` | | `payload_json` | JSON | Valfri extra data | | `status` | TINYINT | Pending=1, Running=2, Succeeded=3, Failed=4 | | `requested_by` | VARCHAR(100) | Admin-email | | `output_text` | MEDIUMTEXT | stdout/stderr från kommandot | | `error_message` | TEXT | Felmeddelande vid Failed | | `created_utc` | DATETIME(6) | Skapades | | `started_utc` | DATETIME(6) | Runner påbörjade | | `finished_utc` | DATETIME(6) | Klart/misslyckat | ### Status-enum: `SupportJobStatus` ``` Pending = 1, Running = 2, Succeeded = 3, Failed = 4 ```
Skapad av doc-import, uppdaterad 2026-03-23 14:17Kommandokatalog
Arkitektursupportsystemkommandokatalog### VM-kommandon (`target_type = vm`) | Nyckel | Beskrivning | Kommando | |--------|-------------|----------| | `check_status` | Uptime, disk, minne, inloggade | `uptime && df -h / && free -m && who` | | `restart_xrdp` | Starta om xrdp-tjänst | `systemctl restart xrdp` | | `check_xrdp` | Visa xrdp-status | `systemctl status xrdp --no-pager` | | `check_wg` | WireGuard-status | `wg show` | | `apt_upgrade` | Systemuppdatering | `apt-get update && apt-get upgrade -y` | | `reboot` | Starta om VM | `reboot` | | `check_logs` | Senaste systemloggar | `journalctl --no-pager -n 100` | ### Pi-kommandon (`target_type = thin_client`, dubbelhopp via VM) | Nyckel | Beskrivning | Kommando | |--------|-------------|----------| | `tc_check_status` | Uptime, disk, minne, temperatur | `uptime && df -h / && free -m && vcgencmd measure_temp` | | `tc_check_vpn` | WireGuard-status | `sudo wg show` | | `tc_check_rdp` | Kör xfreerdp? | `pgrep -la xfreerdp` | | `tc_check_logs` | EasyDesk-apploggar | `journalctl -u easydesk* --no-pager -n 100` | | `tc_reboot` | Starta om Pi | `sudo reboot` | | `tc_apt_upgrade` | Systemuppdatering (rw → apt → reboot) | `sudo mount -o remount,rw / && sudo apt-get update && sudo apt-get upgrade -y && sudo reboot` |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Designbeslut
Arkitektursupportsystemdesignbeslut| Beslut | Val | Motivering | |--------|-----|------------| | Åtkomstväg | SSH via befintliga WG-tunnlar | Ingen ny exponering, återanvänder infrastruktur | | Pi SSH | sshd begränsad till wg0 (UFW) | Säkert — SSH bara via VPN, aldrig från LAN | | Exekvering | Befintlig runner (systemd) | Följer ansible_jobs-mönstret, ingen ny tjänst | | Dubbelhopp | SSH agent forwarding | Ingen nyckel lagras på VM | | Datamodell | Ny tabell `support_jobs` | Följer `ansible_jobs`-mönstret |
Skapad av doc-import, uppdaterad 2026-03-23 14:17Bakgrund
ArkitektursupportsystembakgrundI produktion behöver vi kunna felsöka och underhålla kunders VM:ar och tunna klienter (Raspberry Pi). SSH är avstängt på Pi:arna och enda vägen in är via de befintliga VPN-tunnlarna: ``` Admin-server (wg1) → VM (10.60.x.1) → Pi (10.50.0.2 via wg0) ``` Systemet ger admin fördefinierade åtgärder via Admin UI, som exekveras av runnern via SSH genom VPN-tunnlarna.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Steg för steg
Arkitekturprovisioningflowstegför> **Förutsättning:** Ordern måste vara markerad som betald (`POST /api/orders/{id}/mark-paid`) innan auto-provisionering kan köras. ### Steg 1: Beställning Admin skapar en beställning via portalen. API:et lagrar order + order-items. ``` POST /api/orders { "customerId": "abc-123", "currency": "SEK", "items": [{ "planId": "plan-1", "sku": "WS-STD", "name": "Standard", "quantity": 1, "unitPrice": 499.00 }] } ``` ### Steg 2: Auto-provisionering Ett enda API-anrop som skapar VM-request, bygger cloud-config, och skapar Hetzner-server: ``` POST /api/provisioning/auto-provision { "orderId": "order-abc", "customerId": "abc-123", "regionCode": "nbg1", "serverType": "cx22", "image": "ubuntu-22.04", "adminClientPublicKey": "WG_PUB_KEY_HERE", "adminSshPublicKey": "ssh-ed25519 AAAA... admin@machine" } ``` **Vad händer internt:** 1. **vm_request skapas** → Bootstrap-token genereras (kryptografisk, 32 bytes, base64url). SHA256-hashen lagras i DB med 30 min TTL. 2. **Cloud-config byggs** → Tokenplatshållare ersätts med riktiga värden (vmRequestId, bootstrapToken, WG-IP, admin-pubkey, backend-URL). 3. **SSH-nyckel registreras hos Hetzner** → `EnsureSshKeyAsync` kollar om nyckeln redan finns, skapar annars. Returnerar nyckel-ID. 4. **Hetzner-server skapas** → `POST /v1/servers` med cloud-config som `user_data` och `ssh_keys: [keyId]`. Eftersom SSH-nyckel anges sätts inget root-lösenord. 5. **vm_instance skapas** → Hetzner server-ID kopplas till vm_request i databasen. **Response:** ```json { "vmRequestId": "550e8400-...", "vmId": "6ba7b810-...", "providerServerId": 42123456, "serverName": "ed-550e8400", "publicIpv4": "95.216.xx.xx", "status": "provisioning" } ``` > **Manuellt alternativ:** Steg 2 kan göras i två separata anrop (`POST /api/provisioning/vm-requests` + manuellt skapa server hos Hetzner + `POST /api/provisioning/vm-instances`). ### Steg 3: Cloud-init körs på VM När Hetzner-servern startar kör cloud-init automatiskt: ```mermaid graph TD A[passwd -l root — lås root-lösenord] --> B[chage -d 99999 root — ta bort lösenordsbyte-krav] B --> C[Konfigurera sshd: PasswordAuthentication no, PermitRootLogin prohibit-password] C --> D[systemctl restart sshd] D --> E[apt update + upgrade] E --> F[Installera wireguard, ufw, curl] F --> G[Generera WireGuard-nyckelpar wg1.key/wg1.pub] G --> H[Skriv wg1.conf med admin-peer] H --> I[Starta wg-quick@wg1] I --> J{wg1 uppe?} J -->|Ja| K[Lås brandvägg: SSH/RDP bara via VPN] J -->|Nej| L[Fallback: SSH öppet publikt] K --> M[POST /api/vm/register med bootstrap-token] L --> M M --> N[Backend validerar token, sparar WG-endpoint] ``` **Cloud-config detaljer:** - **SSH-härdning:** Lösenordsautentisering inaktiverad (`PasswordAuthentication no`), root-lösenord låst (`passwd -l root`). - **SSH-nyckel injiceras** via cloud-config `users`-sektionen. - **Inget root-lösenord sätts** — SSH-nyckeln registreras med Hetzner vid serverskapandet, vilket gör att Hetzner inte genererar ett root-lösenord. **Registrerings-payload (cloud-init → backend):** ```json { "vmRequestId": "550e8400-...", "hostname": "ed-550e8400", "publicIpv4": "95.216.xx.xx", "wgAdmin": { "interfaceName": "wg1", "listenPort": 51821, "vmWgIpCidr": "10.60.0.1/32", "publicKey": "VM_WG1_PUBLIC_KEY", "allowedCidr": "10.60.0.0/16" } } ``` **Backend validerar:** - Bootstrap-token matchar (SHA256) - Token ej redan använd (engångs) - Token ej utgången (30 min TTL) - Sparar VM:ens publika IP, hostname, och WG admin-endpoint - Markerar vm_request som `ready_for_ansible` ### Steg 4: Ansible provisionerar arbetsytan Ansible körs mot VM:en via admin-VPN (wg1, SSH port 22 via 10.60.0.0/16): ```mermaid graph TD A[base: locale, timezone, apt-paket] --> B[security: SSH-härdning, fail2ban, UFW] B --> C[wireguard: Skapa wg0 kund-VPN] C --> D[mate_xrdp: MATE + XRDP] D --> E[ui_mate: Panellayout, bakgrund, teman] E --> F[chrome: Google Chrome + policies] F --> G[chrome_keyring: Keyring-fix + RDP-optimering] G --> H[thunderbird: valfritt] H --> I[updates_two_phase: systemd-timers för uppdateringar] ``` **WireGuard wg0 (kund-VPN):** - Genererar nytt nyckelpar (`wg0.key` / `wg0.pub`) — bara om nyckeln inte redan finns - Konfigurerar `wg0.conf` med `ListenPort=51820`, `Address=10.50.0.1/24` — bara vid första körningen (stat-check, annars förloras onboardade peers) - Registrerar customer WG-endpoint med backend: `POST /api/vms/{id}/wg/customer-endpoint` **Lösenordslös inloggning (mate_xrdp):** - PAM konfigureras med `pam_permit.so` — VM:en nås bara via WG-tunnel, tunneln ÄR autentiseringen - `.xsession` sätter XDG-variabler och startar MATE - Användarnamnet deriveras från kundnamnet via `UsernameHelper.DeriveUsername()` ### Steg 5: VM redo Efter Ansible är VM:en redo att ta emot tunna klienter. Tillståndet i databasen: | Tabell | Status | |--------|--------| | vm_requests | `ready_for_ansible` → `provisioned` | | vm_instances | `active` | | wg_endpoints (purpose=1) | Admin WG-endpoint (wg1) | | wg_endpoints (purpose=2) | Customer WG-endpoint (wg0) | **Nästa steg:** [Enrollmentflöde](enrollment-flow.md) — koppla en tunn klient. ### Alternativt: Direkt peer-onboarding via API För att lägga till en kund-WG-peer utan enrollment-flödet (t.ex. under utveckling): ``` POST /api/vms/{vmId}/wg/peers { "publicKey": "CLIENT_WG_PUBKEY", "peerName": "klient1" } ``` Returnerar all info klienten behöver för sin WG-konfig: server pubkey, endpoint, tilldelad IP, och **Linux-användarnamn** för RDP-login. Enqueue:ar automatiskt ett `onboard_peer` Ansible-jobb som lägger till peer:en på VM:en.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Översikt
Arkitekturprovisioningflowöversikt```mermaid sequenceDiagram participant Admin as Admin/Portal participant API as Kvillo API participant DB as MySQL participant Hz as Hetzner Cloud participant VM as Hetzner VM participant Ans as Ansible Admin->>API: POST /api/provisioning/auto-provision API->>DB: sp_vm_requests_create (bootstrap token) DB-->>API: vmRequestId, token API->>API: Bygg cloud-config (injicera token + WG-IP) API->>Hz: POST /v1/ssh_keys (ensure key exists) Hz-->>API: sshKeyId API->>Hz: POST /v1/servers (cloud-config + ssh_key) Hz-->>API: serverId, publicIpv4 API->>DB: sp_vm_instances_create_from_request DB-->>API: vmId API-->>Admin: { vmRequestId, vmId, publicIpv4, status: "provisioning" } Note over VM: Servern startar, cloud-init körs VM->>VM: Installera WireGuard, generera wg1-nycklar VM->>VM: Konfigurera brandvägg (UFW) VM->>API: POST /api/vm/register (Bearer: bootstrap-token) API->>DB: sp_vm_register_from_cloudinit DB-->>API: vmId, status: "ready_for_ansible" API-->>VM: 200 OK Note over Ans: Ansible upptäcker jobb eller triggas manuellt Ans->>VM: SSH via wg1 (admin-VPN) Ans->>VM: Installera MATE, XRDP, Chrome, etc. Ans->>VM: Skapa wg0 (kund-VPN) Ans->>API: POST /api/vms/{id}/wg/customer-endpoint API->>DB: sp_wg_endpoints_upsert (purpose=2) ```
Skapad av doc-import, uppdaterad 2026-03-23 14:17Provisioneringsflöde
ArkitekturprovisioningflowprovisioneringsflödeFrån beställning till fungerande arbetsyta. Beskriver steg 1–5 i livscykeln.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Beslut att ta
Arkitekturm365addonbeslutatt- [ ] **Domännamn for M365-info:** Vart hanvisar vi kunder som vill skaffa eget M365? - [ ] **Kvillo Hemmakontor branding:** Vill du ha en egen ikon/splash, eller racker SoftMakers? - [ ] **Upsell-logik i guiden:** Foresla Office NX/Hemmakontor nar kunden valjer M365-genvägar?
Skapad av doc-import, uppdaterad 2026-03-23 14:17Beslut (tagna)
Arkitekturm365addonbesluttagna- [x] **Premium-paketet:** Byter M365 (49 kr) mot Office NX (55 kr). Premium ingar nu Office NX. - [x] **FreeOffice/Hemmakontor:** Enbart for privatkunder. Foretagskunder far Office NX eller inget.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Beroenden mellan faser
Arkitekturm365addonberoendenmellanfaser``` Fas 1 (addon-koder) ← kravs for Fas 2 (Ansible) och Fas 4 (bestallning) Fas 2 (Ansible) ← oberoende av Fas 3/4 Fas 3 (startguide) ← oberoende av Fas 2 (men trevligast om bada ar klara) Fas 4 (webb) ← oberoende av Fas 2/3 Fas 5 (test) ← kravs att Fas 1-4 ar klara ``` Fas 1 + 2 + 3 + 4 kan goras parallellt efter Fas 1.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Kanda gotchas
Arkitekturm365addonkandagotchas1. **SoftMaker apt-repo:** Verifiera att repot fungerar fran Hetzner-VMs (inga brandvaggsblock) 2. **Volymlicens aktivering:** Testa att licensnyckeln kan aktiveras tyst (CLI, inte GUI) 3. **FreeOffice visar "FreeOffice" i programmet:** Informera kunden att det ar ratt — det ar programmet som Kvillo Hemmakontor anvander 4. **M365 genvägar i anvandarens hemkatalog:** `~/.local/share/applications/` — overlever Ansible-korningar, tas inte bort vid addon-andringar 5. **Panellauncher object-11:** Bara en kan vara aktiv (TextMaker NX ELLER TextMaker Free). Ansible-rollen maste hantera detta. 6. **Office NX uppdateringar:** SoftMaker pushar uppdateringar via apt-repo. Daglig `apt-get update` (update_check-rollen) ser dessa. Uppdateras vid nasta `apt_upgrade` support-kommando. 7. **SKU-byte HW-M365:** Kontrollera befintliga ordrar i DB. Migrera om det behovs. 8. **Premium-paketet:** Bytt fran M365 (49 kr/man) till Office NX (55 kr/man). Uppdatera paketbeskrivning + prisberakning i Guide, Priser, BOM.
Skapad av doc-import, uppdaterad 2026-03-23 14:17Fas 5: Deploy + test
Arkitekturm365addonfasdeploytest### Checklista - [ ] Kop SoftMaker volymlicens (Office NX, 5 platser) - [ ] Lagg till `OFFICENX_LICENSE_KEY` i runner `.env` pa prod - [ ] Deploy API (nya addon-koder) - [ ] Deploy Ansible-roller till test-VM - [ ] Deploy startguide till test-VM - [ ] Deploy webb (nya priser + tillval) - [ ] Testa fullstandigt flode: **Office NX:** 1. Aktivera OFFICENX-addon pa testprenumeration 2. Kor addon-install → verifiera att Office NX installeras 3. Oppna TextMaker → verifiera att licens ar aktiverad 4. Oppna startguiden → verifiera att "Du har Office NX" visas 5. Testa avaktivering → program avinstalleras, dokument bevaras **FreeOffice:** 1. Aktivera FREEOFFICE-addon 2. Kor addon-install → verifiera installation 3. Oppna TextMaker Free → fungerar utan licens 4. Startguiden → "Du har Kvillo Hemmakontor" 5. Testa avaktivering **Microsoft 365 (genvägar):** 1. Ingen addon aktiv → startguiden fragar "Vill du anvanda Microsoft Office?" 2. Valj "Ja" → .desktop-filer skapas 3. Klicka pa Word-genvag → Chrome oppnas → Microsoft inloggningssida 4. Logga in med eget Microsoft-konto → Word funkar 5. Stang och oppna igen → fortfarande inloggad **Omsesidig uteslutning:** 1. Aktivera OFFICENX nar FREEOFFICE ar aktivt → FreeOffice avinstalleras 2. Verifiera att bara ett kontorsprogram ar installerat ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Fas 4: Webb + admin — bestallningsflode
Arkitekturm365addonfaswebbadminUppdatera prissidan, guiden och bestallningsformuläret. ### Prissidan (Priser.cshtml) - Ta bort M365 som enskilt tillval (49 kr/man) - Lagg till: - **Kvillo Hemmakontor** — 10 kr/man (TextMaker, PlanMaker, Presentations. Installation + underhall ingar.) - **Office NX** — 55 kr/man (SoftMaker Office NX fullversion. Licensnyckel, installation + underhall ingar.) - Information: "Du kan aven anvanda Microsoft 365 webbappar med ditt eget Microsoft-konto — genvägar skapas i startguiden." ### Guide/Anpassa (Anpassa.cshtml) **Privatkunder** — tillvalssektion "Kontorsprogram" med tre val: - Inga kontorsprogram (standard) - Kvillo Hemmakontor (+10 kr/man) - Office NX (+55 kr/man) **Foretagskunder** — tillvalssektion "Kontorsprogram" med tva val: - Inga kontorsprogram (standard) - Office NX (+55 kr/man per arbetsplats) - Info-text som forklarar skillnaderna - Tips om M365 webbappar (gratis, eget konto) ### Bestallningsformuläret (Bestall.cshtml.cs) - SKU: `ADDON-FREEOFFICE` (10 kr/man) och `ADDON-OFFICENX` (55 kr/man) - Ta bort `HW-M365` ### Admin — CreateOrderDialog - Lagg till val for kontorsprogram-addon ### Filer att andra | Fil | Andring | |-----|---------| | `EasyDesk.Web/Pages/Priser.cshtml` | Ta bort M365, lagg till Hemmakontor + Office NX | | `EasyDesk.Web/Pages/Anpassa.cshtml` | Tillvalssektion kontorsprogram | | `EasyDesk.Web/Pages/Bestall.cshtml.cs` | Nya SKU:er | | `EasyDesk.Admin/Components/Pages/Orders/CreateOrderDialog.razor` | Kontorsprogram-val | | `EasyDesk.Web/Styles/input.css` | Eventuell styling | ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Fas 3: Startguide — kontorsprogram-sida
Arkitekturm365addonfasstartguidekontorsprogramNy sida i startguidens wizard. **Visas for alla kunder**, oavsett addons. ### Flode ``` Sida: "Kontorsprogram" | +-- Om OFFICENX aktivt: | "Du har Office NX installerat!" | Visa: TextMaker (ordbehandling), PlanMaker (kalkyl), Presentations | "Programmen finns i menyn och pa panelen. Allt ar klart." | [Nasta →] | +-- Om FREEOFFICE aktivt: | "Du har Kvillo Hemmakontor installerat!" | Visa: TextMaker (ordbehandling), PlanMaker (kalkyl), Presentations | "Programmen finns i menyn och pa panelen. Allt ar klart." | [Nasta →] | +-- Om inget kontorsaddon: | "Vill du anvanda Microsoft Office?" | | | +-- JA → | | "Vi skapar genvägar till Word, Excel och de andra programmen." | | "Du behover ett Microsoft-konto for att logga in." | | "Om du inte har ett kan du skapa ett gratis pa microsoft.com." | | "OBS: Du behover en egen Microsoft 365-licens for full funktionalitet." | | → Skapar Chrome PWA .desktop-filer | | → Knapp: "Oppna Microsoft 365" (Chrome → office.com) | | [Nasta →] | | | +-- NEJ TACK → [Nasta →] | +-- Info-ruta (visas for alla utan Office NX/FreeOffice): "Visste du? Du kan ocksa lagga till Kvillo Hemmakontor (10 kr/man) eller Office NX (55 kr/man) for riktiga kontorsprogram som fungerar utan inloggning. Kontakta oss sa hjalper vi dig!" ``` ### Microsoft 365-genvägar (skapas av startguiden, inte Ansible) Startguiden skapar .desktop-filer direkt om anvandaren valjer "Ja": ``` ~/.local/share/applications/kvillo-word.desktop ~/.local/share/applications/kvillo-excel.desktop ~/.local/share/applications/kvillo-powerpoint.desktop ~/.local/share/applications/kvillo-outlook.desktop ~/.local/share/applications/kvillo-onedrive.desktop ``` Gor detta i anvandarkatalogen (inte system-wide) eftersom det ar ett anvandarval, inte ett addon. ### .desktop-mall ```ini [Desktop Entry] Type=Application Name=Word Comment=Microsoft Word (webbversion) Exec=google-chrome --app=https://word.office.com Icon=accessories-text-editor Terminal=false Categories=Office; StartupNotify=true ``` Anvand systemikoner som fallback (accessories-text-editor, calc, x-office-presentation) eller ladda ner Microsofts webapp-ikoner vid skapande. ### Filer att andra | Fil | Andring | |-----|---------| | `roles/startup_guide/files/startup_guide/wizard_html.py` | Ny sida `page-office` med tre varianter | | `roles/startup_guide/files/startup_guide/wizard_view.py` | Handler for kontorsval, .desktop-skapande, addon-detektering | | `roles/startup_guide/files/startup_guide/constants.py` | Eventuella nya konstanter | ### Monster (befintligt i wizard_view.py) - `_has_officenx()` / `_has_freeoffice()`: kolla `/etc/easydesk/addons.json` - Vid sidladdning: bestam vilken variant av sidan som visas via JS-injektion - Om "Ja" till M365: skriv .desktop-filer till `~/.local/share/applications/` - Om Office NX/FreeOffice: bara visa info (redan installerat av Ansible) ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Fas 2: Ansible-roller
Arkitekturm365addonfasansibleroller### 2a: Roll `officenx` (SoftMaker Office NX) ``` roles/officenx/ defaults/main.yml # install_officenx: false tasks/main.yml handlers/main.yml ``` **Installation (when `install_officenx`):** 1. Lagg till SoftMaker apt-repo (`https://shop.softmaker.com/repo/apt`) 2. `apt install softmaker-office-2024` (eller senaste) 3. Aktivera volymlicensnyckel: `SoftMaker Office --register <key>` 4. Skapa .desktop-filer i `/usr/share/applications/` om de inte redan finns 5. Lagg till panellauncher for TextMaker (object-11 i dconf) **Avinstallation (when `not install_officenx`):** - `apt remove softmaker-office-2024` - Ta bort panellauncher - Bevar anvandardokument **Licenshantering:** - Volymlicens (5 platser, 35 kr/st) - Licensnyckel lagras som Ansible-variabel (en gemensam nyckel for alla VMs) - Miljo-variabel: `OFFICENX_LICENSE_KEY` i runner `.env` - Runner injicerar `officenx_license_key` i Ansible-variabler ### 2b: Roll `freeoffice` (SoftMaker FreeOffice) ``` roles/freeoffice/ defaults/main.yml # install_freeoffice: false tasks/main.yml handlers/main.yml ``` **Installation (when `install_freeoffice`):** 1. Lagg till SoftMaker apt-repo 2. `apt install softmaker-freeoffice-2024` 3. Ingen licens behovs 4. Skapa/verifiera .desktop-filer 5. Lagg till panellauncher for TextMaker (object-11 i dconf) **Avinstallation (when `not install_freeoffice`):** - `apt remove softmaker-freeoffice-2024` - Ta bort panellauncher - Bevar anvandardokument ### OBS: OFFICENX och FREEOFFICE ar omsesidigt uteslutande Bara ett kan vara aktivt. Rollen bor kontrollera: ```yaml # officenx tasks/main.yml - name: Remove FreeOffice if Office NX is being installed apt: name: softmaker-freeoffice-2024 state: absent when: install_officenx ``` ### Filer att andra for integration | Fil | Andring | |-----|---------| | `Ansible/playbooks/addon-install.yml` | Lagg till officenx + freeoffice roller | | `Ansible/playbooks/workstation.yml` | Lagg till roller (efter valvet, fore update_check) | | `Ansible/runner.sh` (rad ~397-416) | Lagg till `install_officenx` + `install_freeoffice` flaggor + `officenx_license_key` | | `Ansible/roles/ui_mate/templates/dconf_panel_layout.ini.j2` | Villkorligt object-11 for TextMaker | ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Fas 1: Addon-registrering
Arkitekturm365addonfasregistreringRegistrera tva nya addon-koder i systemet: `OFFICENX` och `FREEOFFICE`. ### Filer att andra | Fil | Andring | |-----|---------| | `EasyDeskApiDev/Models/ActivateAddon.cs` | Lagg till `"OFFICENX"` och `"FREEOFFICE"` i `ValidAddonCodes` | | `EasyDeskApiDev/Models/AddSubscriptionAddon.cs` | Samma | | `EasyDesk.Admin/Components/Pages/Orders/DeliverVmDialog.razor` | Lagg till case for `ADDON-OFFICENX` och `ADDON-FREEOFFICE` | | `EasyDesk.Web/Pages/Bestall.cshtml.cs` | Andra `HW-M365` till `ADDON-OFFICENX`, lagg till `ADDON-FREEOFFICE` | ### DeliverVmDialog — nya SKU-mappningar ```csharp case "ADDON-OFFICENX": addons.Add(("OFFICENX", item.UnitPrice)); break; case "ADDON-FREEOFFICE": addons.Add(("FREEOFFICE", item.UnitPrice)); break; ``` ### OBS - Ta bort M365 som eget addon/tillval i bestallningen — det ar inte langre ett tillval utan en gratistjanst i startguiden - Kontrollera om det finns befintliga ordrar med `HW-M365` i databasen ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Oversikt
Arkitekturm365addonoversikt``` Fas 1: Addon-registrering (OFFICENX + FREEOFFICE) Fas 2: Ansible-roller (officenx + freeoffice) Fas 3: Startguide — kontorsprogram-sida Fas 4: Webb + admin — bestallningsflode Fas 5: Deploy + test ``` ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Plan: Kontorsprogram (Office NX, FreeOffice, Microsoft 365-genvägar)
Arkitekturm365addonplankontorsprogramofficeTre nivaer av kontorsprogram pa molndatorn: | Niva | Produkt | Pris | Typ | |------|---------|------|-----| | Microsoft 365 (webb) | Chrome-genvägar till office.com | 0 kr (ingår) | Startguiden fragar alla kunder, skapar genvägar om de vill | | Kvillo Hemmakontor | FreeOffice (TextMaker, PlanMaker, Presentations) | 10 kr/man | Addon — installation + underhall ingar. **Enbart privatkunder.** | | Office NX | SoftMaker Office NX (fullversion) | 55 kr/man | Addon — volymlicens, installation + underhall ingar. Privat + foretag. | **Viktigt:** Tydlig kommunikation sa kunderna forstar skillnaderna: - **Microsoft 365 (webb):** Genvägar till Microsofts webbappar. Kunden behover eget Microsoft-konto. Kvillo hjalper med installationen men kunden skoter licensen sjalv. - **Kvillo Hemmakontor (enbart privat):** Riktiga program som installeras pa datorn. Fungerar direkt utan inloggning. Gratis programvara — kunden betalar for installation och underhall. - **Office NX (privat + foretag):** Riktiga program, fullversion utan begransningar. Licensnyckel ingar. Inga inloggningar eller konton. Ingar i Premium-paketet. ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Anteckningar
Arkitekturhelpdeskescalationanteckningar- Alla DB-queries via stored procedures (sp_-prefix) - Alla use cases följer Result<T>-pattern med ExecuteAsync() - Admin-endpoints kräver [Authorize], VM-endpoints kräver HMAC - Python-appen kommunicerar enbart via HTTP (inga direkta DB-anrop) - Kunskapsfiler i Knowledge/ har YAML frontmatter (title + tags)
Skapad av doc-import, uppdaterad 2026-03-23 14:17Risker & beslutspunkter
Arkitekturhelpdeskescalationriskerbeslutspunkter1. **AI-kvalitet på sammanfattningar** — testa att `create_ticket`-summaries är användbara - Mitigation: admin kan redigera innan svar 2. **Polling vs push** — Python-appen pollar för ärendeuppdateringar - Acceptabelt med 60s intervall, WebSocket kan läggas till senare 3. **Kunskapsbas-storlek** — godkända lösningar kan växa snabbt - Mitigation: admin curar, embedding-sökning skalar bra 4. **Privacy** — lösningsloggen kan innehålla kundspecifik info - Mitigation: admin generaliserar innan KB-tillägg, loggar rensas efter 90 dagar 5. **Ärende-routing** — initialt en supportperson, men om teamet växer? - Löses i Fas 4+ med assignment-fält ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Uppskattad arbetsinsats
Arkitekturhelpdeskescalationuppskattadarbetsinsats| Fas | Beskrivning | Omfattning | |-----|-------------|------------| | 1 | Ärendehantering | DB + 6 use cases + controller + AI-verktyg + Python UI + Admin 2 sidor | | 2 | Lösningslogg | DB + 3 use cases + AI-verktyg + 👍/👎 UI + Admin review-sida | | 3 | Mönsterdetektering | DB + bakgrundsjobb + Admin mönster-sida | | 4 | Polish | E-post + dashboard-stats + historik + SLA | ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Implementeringsordning & beroenden
Arkitekturhelpdeskescalationimplementeringsordningberoenden``` Fas 1 (grund) Fas 2 (lösningar) Fas 3 (mönster) Fas 4 (polish) ───────────── ───────────────── ─────────────── ────────────── 1.1 DB-patch 028 ──→ 2.1 DB-patch 029 ──→ 3.1 DB-patch 030 4.1 E-post 1.2 DTOs ──→ 2.2 log_solution 3.2 Bakgrundsjobb 4.2 Dashboard 1.3 Use Cases ──→ 2.3 👍/👎 knappar 3.3 Admin mönster 4.3 Historik 1.4 AI eskalering 2.4 RAG utökning 3.4 Auto-KB 4.4 SLA 1.5 Python ärende-UI 2.5 Admin review 1.6 Admin ärendekö 1.7 Deploy + test ``` **Fas 1 kan byggas och deployras oberoende.** Fas 2 bygger på Fas 1 (delar category-taxonomi och AI-verktygsinfrastruktur). Fas 3 bygger på Fas 2 (behöver lösningsdata att analysera). Fas 4 kan göras parallellt med Fas 3. ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17Fas 4 — Polish & notifieringar
Arkitekturhelpdeskescalationfaspolishnotifieringar> Mål: Supportpersonen blir notifierad, kunden får bättre upplevelse. ### 4.1 E-postnotifieringar - Nytt ärende → e-post till supportpersonen - Ärende besvarad → e-post till kund (via SmtpEmailService, redan finns) - Mönstervarning → e-post till admin ### 4.2 Ärendestatistik i Admin Dashboard - Antal öppna ärenden - Snitt-lösningstid - Vanligaste kategorier - Kundnöjdhet (👍/👎-ratio) ### 4.3 Kundens ärendehistorik - I Assistenten: "Dina tidigare ärenden" — lista med status - Klick → visa konversation och lösning ### 4.4 SLA-hantering - Ärenden äldre än X timmar → eskalera (markera som High priority) - Visa tid sedan skapande i Admin-kön ---
Skapad av doc-import, uppdaterad 2026-03-23 14:17