From e94caf8054866f0e6c99467c34bf43c77e7df31a Mon Sep 17 00:00:00 2001 From: Aleksandr Bogomiakov Date: Mon, 12 Oct 2026 11:45:52 +0100 Subject: [PATCH] Migrate Minecraft, RustDesk and Immich database to hexor-lab --- k8s-ng/apps/minecraft/configmaps.yaml | 563 +++++++++++++++++++++ k8s-ng/apps/minecraft/deployments.yaml | 138 +++++ k8s-ng/apps/minecraft/ingress.yaml | 24 + k8s-ng/apps/minecraft/kustomization.yaml | 8 + k8s-ng/apps/minecraft/services.yaml | 40 ++ k8s-ng/apps/rustdesk/deployment.yaml | 151 ++++++ k8s-ng/apps/rustdesk/external-secrets.yaml | 27 + k8s-ng/apps/rustdesk/kustomization.yaml | 8 + k8s-ng/apps/rustdesk/network-policy.yaml | 73 +++ k8s-ng/apps/rustdesk/service.yaml | 57 +++ k8s-ng/core-state/values.yaml | 17 + k8s-ng/core/immich-db/cluster.yaml | 45 ++ k8s-ng/core/immich-db/kustomization.yaml | 6 + k8s-ng/core/immich-db/storage.yaml | 64 +++ 14 files changed, 1221 insertions(+) create mode 100644 k8s-ng/apps/minecraft/configmaps.yaml create mode 100644 k8s-ng/apps/minecraft/deployments.yaml create mode 100644 k8s-ng/apps/minecraft/ingress.yaml create mode 100644 k8s-ng/apps/minecraft/kustomization.yaml create mode 100644 k8s-ng/apps/minecraft/services.yaml create mode 100644 k8s-ng/apps/rustdesk/deployment.yaml create mode 100644 k8s-ng/apps/rustdesk/external-secrets.yaml create mode 100644 k8s-ng/apps/rustdesk/kustomization.yaml create mode 100644 k8s-ng/apps/rustdesk/network-policy.yaml create mode 100644 k8s-ng/apps/rustdesk/service.yaml create mode 100644 k8s-ng/core/immich-db/cluster.yaml create mode 100644 k8s-ng/core/immich-db/kustomization.yaml create mode 100644 k8s-ng/core/immich-db/storage.yaml diff --git a/k8s-ng/apps/minecraft/configmaps.yaml b/k8s-ng/apps/minecraft/configmaps.yaml new file mode 100644 index 0000000..5707f6d --- /dev/null +++ b/k8s-ng/apps/minecraft/configmaps.yaml @@ -0,0 +1,563 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-config + namespace: minecraft +data: + nginx.conf: | + user nginx; + worker_processes 1; + error_log /var/log/nginx/error.log warn; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 80; + + # Custom 502 error page with auto-refresh + error_page 502 /502.html; + location = /502.html { + internal; + return 200 'Server Loading

Server is loading probably...

Please wait a moment and try refreshing the page.

'; + add_header Content-Type text/html; + } + + # Main location - proxy to Minecraft Dynmap + location / { + # Proxy configuration for Dynmap server + proxy_pass http://localhost:8123; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Inject user authentication meta tag into HTML head + sub_filter '' ''; + + # Replace default Dynmap title with custom server name + sub_filter 'Minecraft Dynamic Map' "Hexor's MC server"; + + # Inject custom script before closing body tag + sub_filter "" ''; + + # Apply sub_filter replacements globally (not just once) + sub_filter_once off; + } + + # Serve inject.js and .ps1 scripts inline (no forced download) + location = /clients/inject.js { + alias /mc/clients/inject.js; + default_type application/javascript; + } + + location ~ ^/clients/(.+\.ps1)$ { + alias /mc/clients/$1; + default_type text/plain; + } + + # Static file serving for client downloads + location /clients/ { + alias /mc/clients/; + sendfile on; # Enable efficient file serving + add_header Content-Disposition "attachment"; # Force download + autoindex on; # Enable directory listing + gzip off; # Disable compression for downloads + chunked_transfer_encoding off; # Disable chunked encoding + } + } + } + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: client-scripts + namespace: minecraft +data: + inject.js: | + (function() { + function getUsername() { + var metas = document.querySelectorAll("meta"); + for (var i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === "remote-user") { + return metas[i].getAttribute("content"); + } + } + var jwt = document.cookie.split("; ").find(function(row) { + return row.startsWith("authentik_session="); + }); + if (jwt) { + try { + var token = jwt.split("=")[1]; + var payload = JSON.parse(atob(token.split(".")[1])); + return payload.sub || payload.username || "web-user"; + } catch(e) {} + } + return "web-user"; + } + + var username = getUsername(); + console.log("Username found:", username); + + if (username && username !== "web-user" && + window.location.search.indexOf("playername=") === -1) { + var currentUrl = new URL(window.location.href); + currentUrl.searchParams.set("playername", username); + console.log("Redirecting to:", currentUrl.href); + window.location.href = currentUrl.href; + } + + document.addEventListener("DOMContentLoaded", function() { + // User block + var userBlock = document.createElement("div"); + userBlock.style.cssText = "background-color:#CEC6CB;color:black;padding:8px;text-align:center;font-size:medium;border-radius:4px;position:absolute;top:10px;right:150px;max-width:200px;"; + userBlock.innerHTML = "Logged in as: " + username + ""; + document.body.appendChild(userBlock); + + // Info block + var infoBlock = document.createElement("p"); + infoBlock.style.cssText = "background-color:#CEC6CB;color:black;padding:10px;text-align:center;font-size:large;display:inline-block;border-radius:4px;position:absolute;top:10px;left:150px;"; + infoBlock.innerHTML = 'GEYMERSKIY SOYUZ Server
' + + 'Get Prism Launcher ' + + 'and client.zip for this server. ' + + 'Server address minecraft.hexor.cy:30565
' + + 'Requires Java 8

' + + 'Windows Install Script'; + document.body.appendChild(infoBlock); + + // Modal + var modal = document.createElement("div"); + modal.id = "installModal"; + modal.style.cssText = "display:none;position:fixed;z-index:1000;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);"; + modal.innerHTML = '
' + + '

Windows Installation

' + + '

Copy and paste this command into PowerShell:

' + + '' + + '

' + + '' + + '' + + '
'; + document.body.appendChild(modal); + + // Generate PowerShell command with username + function buildPsCommand() { + var d = "$"; + var q = "'"; + return d + 'f="' + d + 'env:TEMP\\mc-install.ps1"; iwr -useb https://minecraft.hexor.cy/clients/win-install.ps1 -OutFile ' + + d + 'f; powershell -ExecutionPolicy Bypass -File ' + d + 'f -Username ' + q + username + q + '; Remove-Item ' + d + 'f'; + } + + document.getElementById("showInstallBtn").addEventListener("click", function(e) { + e.preventDefault(); + modal.style.display = "block"; + document.getElementById("scriptCommand").value = buildPsCommand(); + }); + + document.getElementById("closeButton").addEventListener("click", function() { + modal.style.display = "none"; + }); + + document.getElementById("copyButton").addEventListener("click", function() { + var textarea = document.getElementById("scriptCommand"); + textarea.select(); + textarea.setSelectionRange(0, 99999); + document.execCommand("copy"); + var btn = document.getElementById("copyButton"); + btn.style.borderColor = "#4CAF50"; + setTimeout(function() { btn.style.borderColor = "#888"; }, 2000); + }); + + modal.addEventListener("click", function(event) { + if (event.target === modal) { + modal.style.display = "none"; + } + }); + }); + })(); + + win-install.ps1: | + # Game Setup Script for PrismLauncher and Minecraft Client + # This script downloads and configures PrismLauncher with Hexor client + + param( + [string]$Username = "", + [string]$InstallPath = "$env:USERPROFILE\Games\PrismLauncher" + ) + + # Enable TLS 1.2 for downloads + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + + # Function to write colored log messages + function Write-Log { + param( + [string]$Message, + [string]$Level = "INFO" + ) + + switch ($Level) { + "INFO" { + Write-Host "[" -NoNewline + Write-Host "INFO" -ForegroundColor Blue -NoNewline + Write-Host "] $Message" + } + "WARN" { + Write-Host "[" -NoNewline + Write-Host "WARN" -ForegroundColor Yellow -NoNewline + Write-Host "] $Message" + } + "ERROR" { + Write-Host "[" -NoNewline + Write-Host "ERROR" -ForegroundColor Red -NoNewline + Write-Host "] $Message" + } + "SUCCESS" { + Write-Host "[" -NoNewline + Write-Host "SUCCESS" -ForegroundColor Green -NoNewline + Write-Host "] $Message" + } + } + } + + # Function to download file with progress + function Download-File { + param( + [string]$Url, + [string]$OutputPath + ) + + try { + Write-Log "Downloading from: $Url" + Write-Log "Saving to: $OutputPath" + + if (Test-Path $OutputPath) { + Remove-Item $OutputPath -Force + Write-Log "Removed existing file to avoid corruption" "WARN" + } + + $webClient = New-Object System.Net.WebClient + $webClient.DownloadFile($Url, $OutputPath) + + if (Test-Path $OutputPath) { + $fileSize = (Get-Item $OutputPath).Length + Write-Log "Download completed successfully ($fileSize bytes)" "SUCCESS" + return $true + } else { + Write-Log "Download failed - file not found" "ERROR" + return $false + } + } + catch { + Write-Log "Download failed: $($_.Exception.Message)" "ERROR" + return $false + } + } + + # Function to extract ZIP archive + function Extract-Archive { + param( + [string]$ArchivePath, + [string]$DestinationPath + ) + + try { + Write-Log "Extracting archive: $ArchivePath" + Write-Log "Destination: $DestinationPath" + + if (!(Test-Path $ArchivePath)) { + Write-Log "Archive file not found: $ArchivePath" "ERROR" + return $false + } + + $fileSize = (Get-Item $ArchivePath).Length + if ($fileSize -lt 1000) { + Write-Log "Archive file too small ($fileSize bytes), probably corrupted" "ERROR" + return $false + } + + if (!(Test-Path $DestinationPath)) { + New-Item -ItemType Directory -Path $DestinationPath -Force | Out-Null + } + + $existingFiles = Get-ChildItem -Path $DestinationPath -File | Where-Object { $_.Extension -ne ".zip" } + $existingDirs = Get-ChildItem -Path $DestinationPath -Directory + + if ($existingFiles.Count -gt 0 -or $existingDirs.Count -gt 0) { + Write-Log "Clearing existing files in destination directory" "WARN" + $existingFiles | Remove-Item -Force -ErrorAction SilentlyContinue + $existingDirs | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + } + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($ArchivePath) + $entryCount = $zip.Entries.Count + $zip.Dispose() + Write-Log "ZIP file validated ($entryCount entries)" "SUCCESS" + } + catch { + Write-Log "ZIP file validation failed: $($_.Exception.Message)" "ERROR" + return $false + } + + [System.IO.Compression.ZipFile]::ExtractToDirectory($ArchivePath, $DestinationPath) + + Write-Log "Archive extracted successfully" "SUCCESS" + return $true + } + catch { + Write-Log "Archive extraction failed: $($_.Exception.Message)" "ERROR" + + if (Test-Path $ArchivePath) { + Write-Log "Removing potentially corrupted archive file" "WARN" + Remove-Item $ArchivePath -Force -ErrorAction SilentlyContinue + } + + return $false + } + } + + # Function to check Java installation + function Check-JavaInstallation { + try { + $javaVersion = java -version 2>&1 | Select-String "version" + if ($javaVersion) { + Write-Log "Java is installed: $($javaVersion.ToString().Trim())" "SUCCESS" + return $true + } + } + catch { + Write-Log "Java is not installed or not in PATH" "WARN" + Write-Log "Please download Java from: https://www.java.com/en/download/manual.jsp" "WARN" + Write-Log "Look for 'Windows Offline (64-bit)' version" "WARN" + + $response = Read-Host "Do you want to continue without Java? (y/n)" + if ($response -eq 'y' -or $response -eq 'Y') { + Write-Log "Continuing without Java verification" "WARN" + return $true + } else { + Write-Log "Installation cancelled. Please install Java first." "ERROR" + return $false + } + } + } + + # Function to generate a random hex string (UUID-like without dashes) + function New-RandomHexId { + param([int]$Length = 32) + $bytes = New-Object byte[] ($Length / 2) + $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() + $rng.GetBytes($bytes) + $rng.Dispose() + return ($bytes | ForEach-Object { $_.ToString("x2") }) -join '' + } + + # Function to create accounts.json file with offline account + function Create-AccountsFile { + param( + [string]$TargetPath, + [string]$PlayerName + ) + + $accountsPath = Join-Path $TargetPath "accounts.json" + $profileId = New-RandomHexId -Length 32 + $clientToken = New-RandomHexId -Length 32 + $iat = [int][double]::Parse((Get-Date -UFormat %s)) + + $accountsObj = @{ + accounts = @( + @{ + entitlement = @{ + canPlayMinecraft = $true + ownsMinecraft = $true + } + "msa-client-id" = "" + type = "MSA" + }, + @{ + active = $true + entitlement = @{ + canPlayMinecraft = $true + ownsMinecraft = $true + } + profile = @{ + capes = @() + id = $profileId + name = $PlayerName + skin = @{ + id = "" + url = "" + variant = "" + } + } + type = "Offline" + ygg = @{ + extra = @{ + clientToken = $clientToken + userName = $PlayerName + } + iat = $iat + token = "0" + } + } + ) + formatVersion = 3 + } + + try { + $accountsObj | ConvertTo-Json -Depth 10 | Out-File -FilePath $accountsPath -Encoding UTF8 + Write-Log "Created accounts.json at: $accountsPath" "SUCCESS" + Write-Log "Player name: $PlayerName" "SUCCESS" + return $true + } + catch { + Write-Log "Failed to create accounts.json: $($_.Exception.Message)" "ERROR" + return $false + } + } + + # Main installation process + Write-Log "Starting PrismLauncher and Minecraft client setup" + Write-Log "Player name: $Username" + Write-Log "Installation path: $InstallPath" + + # Create installation directory and clear if exists (but keep ZIP files) + if (Test-Path $InstallPath) { + Write-Log "Installation directory exists, clearing contents" "WARN" + $existingFiles = Get-ChildItem -Path $InstallPath -File | Where-Object { $_.Extension -ne ".zip" } + $existingDirs = Get-ChildItem -Path $InstallPath -Directory + + $existingFiles | Remove-Item -Force -ErrorAction SilentlyContinue + $existingDirs | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + } else { + New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null + Write-Log "Created installation directory: $InstallPath" + } + + # Step 1: Download PrismLauncher + Write-Log "Step 1: Downloading PrismLauncher..." + $launcherUrl = "https://github.com/PrismLauncher/PrismLauncher/releases/download/8.4/PrismLauncher-Windows-MSVC-Portable-8.4.zip" + $launcherZip = Join-Path $InstallPath "PrismLauncher-8.4.zip" + $launcherExtractPath = Join-Path $InstallPath "PrismLauncher-Windows-MSVC-Portable-8.4" + + if (!(Download-File -Url $launcherUrl -OutputPath $launcherZip)) { + Write-Log "Failed to download PrismLauncher. Exiting." "ERROR" + exit 1 + } + + # Step 2: Extract PrismLauncher + Write-Log "Step 2: Extracting PrismLauncher..." + if (!(Extract-Archive -ArchivePath $launcherZip -DestinationPath $InstallPath)) { + Write-Log "Failed to extract PrismLauncher. Exiting." "ERROR" + exit 1 + } + + # Find the actual extracted directory + $extractedDirs = Get-ChildItem -Path $InstallPath -Directory | Where-Object { $_.Name -like "*PrismLauncher*" } + if ($extractedDirs.Count -gt 0) { + $launcherExtractPath = $extractedDirs[0].FullName + Write-Log "Found PrismLauncher directory: $launcherExtractPath" "SUCCESS" + } else { + Write-Log "Could not find extracted PrismLauncher directory. Checking for direct extraction..." "WARN" + $prismExe = Join-Path $InstallPath "prismlauncher.exe" + if (Test-Path $prismExe) { + $launcherExtractPath = $InstallPath + Write-Log "PrismLauncher extracted directly to: $launcherExtractPath" "SUCCESS" + } else { + Write-Log "Failed to locate PrismLauncher files. Exiting." "ERROR" + exit 1 + } + } + + # Step 3: Create accounts.json in InstallPath (only if username provided) + if ($Username -and $Username -ne "web-user") { + Write-Log "Step 3: Creating accounts configuration for $Username..." + if (!(Create-AccountsFile -TargetPath $InstallPath -PlayerName $Username)) { + Write-Log "Failed to create accounts.json. Exiting." "ERROR" + exit 1 + } + } else { + Write-Log "Step 3: No username provided, skipping accounts.json creation" "WARN" + } + + # Step 4: Check Java installation + Write-Log "Step 4: Checking Java installation..." + if (!(Check-JavaInstallation)) { + Write-Log "Java check failed. Exiting." "ERROR" + exit 1 + } + + # Step 5: Download Minecraft client + Write-Log "Step 5: Downloading Minecraft client..." + $minecraftUrl = "https://minecraft.hexor.cy/clients/1.12.2.zip" + $minecraftZip = Join-Path $InstallPath "minecraft-1.12.2.zip" + $instancesPath = Join-Path $launcherExtractPath "instances" + $hexorPath = Join-Path $instancesPath "Hexor" + + if (!(Download-File -Url $minecraftUrl -OutputPath $minecraftZip)) { + Write-Log "Failed to download Minecraft client. Exiting." "ERROR" + exit 1 + } + + # Step 6: Create instances directory and extract Minecraft client + Write-Log "Step 6: Setting up Minecraft client..." + if (!(Test-Path $instancesPath)) { + New-Item -ItemType Directory -Path $instancesPath -Force | Out-Null + Write-Log "Created instances directory" + } + + if (!(Test-Path $hexorPath)) { + New-Item -ItemType Directory -Path $hexorPath -Force | Out-Null + Write-Log "Created Hexor instance directory" + } + + if (!(Extract-Archive -ArchivePath $minecraftZip -DestinationPath $hexorPath)) { + Write-Log "Failed to extract Minecraft client. Exiting." "ERROR" + exit 1 + } + + # Step 7: Cleanup temporary files + Write-Log "Step 7: Cleaning up temporary files..." + try { + Remove-Item $launcherZip -Force + Remove-Item $minecraftZip -Force + Write-Log "Temporary files cleaned up" "SUCCESS" + } + catch { + Write-Log "Could not remove temporary files: $($_.Exception.Message)" "WARN" + } + + # Final success message + Write-Log "=== INSTALLATION COMPLETED SUCCESSFULLY ===" "SUCCESS" + Write-Log "PrismLauncher location: $launcherExtractPath" "SUCCESS" + Write-Log "Executable: $(Join-Path $launcherExtractPath 'prismlauncher.exe')" "SUCCESS" + Write-Log "Minecraft client installed in: $hexorPath" "SUCCESS" + if ($Username -and $Username -ne "web-user") { + Write-Log "Player name configured: $Username" "SUCCESS" + Write-Log "Accounts file: $(Join-Path $InstallPath 'accounts.json')" "SUCCESS" + } + Write-Log "" + Write-Log "You can now run PrismLauncher and the Hexor instance should be available!" + Write-Log "If Java was not installed, please download it from: https://www.java.com/en/download/manual.jsp" + + # Ask if user wants to launch the game + $launchResponse = Read-Host "Do you want to launch PrismLauncher now? (y/n)" + if ($launchResponse -eq 'y' -or $launchResponse -eq 'Y') { + $launcherExe = Join-Path $launcherExtractPath "prismlauncher.exe" + if (Test-Path $launcherExe) { + Write-Log "Launching PrismLauncher..." "INFO" + Start-Process -FilePath $launcherExe -WorkingDirectory $launcherExtractPath + } else { + Write-Log "Launcher executable not found!" "ERROR" + } + } + + diff --git a/k8s-ng/apps/minecraft/deployments.yaml b/k8s-ng/apps/minecraft/deployments.yaml new file mode 100644 index 0000000..d682b44 --- /dev/null +++ b/k8s-ng/apps/minecraft/deployments.yaml @@ -0,0 +1,138 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: minecraft + namespace: minecraft + labels: + app: minecraft +spec: + selector: + matchLabels: + app: minecraft + replicas: 1 + strategy: + type: Recreate + template: + metadata: + labels: + app: minecraft + spec: + nodeSelector: + kubernetes.io/hostname: music + volumes: + - name: storage + hostPath: + path: /data/k8s/mc-server/ + type: Directory + - name: nginx-config + configMap: + name: nginx-config + - name: client-scripts + configMap: + name: client-scripts + + terminationGracePeriodSeconds: 180 + containers: + - name: minecraft + image: eclipse-temurin:8-jdk-ubi10-minimal@sha256:85c943a7093a276b0f6ae13dee2ffecb98e390ab6a32fc7e4b8b1fc6bb103dd7 + command: ["java"] + args: + - -Xms4G + - -Xmx4G + - -XX:+UseG1GC + - -XX:+ParallelRefProcEnabled + - -XX:MaxGCPauseMillis=200 + - -XX:+UnlockExperimentalVMOptions + - -XX:+DisableExplicitGC + - -XX:+AlwaysPreTouch + - -XX:G1NewSizePercent=30 + - -XX:G1MaxNewSizePercent=40 + - -XX:G1HeapRegionSize=8M + - -XX:G1ReservePercent=20 + - -XX:G1HeapWastePercent=5 + - -XX:G1MixedGCCountTarget=4 + - -XX:InitiatingHeapOccupancyPercent=15 + - -XX:G1MixedGCLiveThresholdPercent=90 + - -XX:G1RSetUpdatingPauseTimePercent=5 + - -XX:SurvivorRatio=32 + - -XX:+PerfDisableSharedMem + - -XX:MaxTenuringThreshold=1 + - -jar + - forge-1.12.2-14.23.5.2854.jar + - nogui + workingDir: /mc/ + resources: + requests: + memory: "8Gi" + cpu: "2000m" + limits: + memory: "12Gi" + cpu: "4000m" + ports: + - name: game + containerPort: 25565 + protocol: TCP + - name: dynmap + containerPort: 8123 + protocol: TCP + - name: webstatus-mod + containerPort: 8080 + protocol: TCP + startupProbe: + tcpSocket: + port: game + periodSeconds: 10 + failureThreshold: 90 + livenessProbe: + tcpSocket: + port: game + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: game + periodSeconds: 10 + timeoutSeconds: 5 + volumeMounts: + - name: storage + mountPath: /mc + - name: nginx + image: nginx@sha256:f9ea18bfa4fad859e1ed38259d711da7ccad2c3516e875cec3351a57c859f571 + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "200m" + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: storage + mountPath: /mc + - name: client-scripts + mountPath: /mc/clients/win-install.ps1 + subPath: win-install.ps1 + - name: client-scripts + mountPath: /mc/clients/inject.js + subPath: inject.js + +--- +apiVersion: v1 +kind: Service +metadata: + name: minecraft-exporter + namespace: minecraft +spec: + selector: + app: minecraft + ports: + - protocol: TCP + port: 19565 + targetPort: 19565 + diff --git a/k8s-ng/apps/minecraft/ingress.yaml b/k8s-ng/apps/minecraft/ingress.yaml new file mode 100644 index 0000000..5a19d8d --- /dev/null +++ b/k8s-ng/apps/minecraft/ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: minecraft + annotations: + cert-manager.io/cluster-issuer: letsencrypt + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: traefik + rules: + - host: minecraft.hexor.cy + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: minecraft-dynmap + port: + number: 80 + tls: + - secretName: minecraft-tls + hosts: + - minecraft.hexor.cy diff --git a/k8s-ng/apps/minecraft/kustomization.yaml b/k8s-ng/apps/minecraft/kustomization.yaml new file mode 100644 index 0000000..c4d1e98 --- /dev/null +++ b/k8s-ng/apps/minecraft/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: minecraft +resources: + - deployments.yaml + - configmaps.yaml + - services.yaml + - ingress.yaml diff --git a/k8s-ng/apps/minecraft/services.yaml b/k8s-ng/apps/minecraft/services.yaml new file mode 100644 index 0000000..990370b --- /dev/null +++ b/k8s-ng/apps/minecraft/services.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: minecraft-dynmap +spec: + selector: + app: minecraft + ports: + - protocol: TCP + port: 80 + targetPort: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: minecraft-webstatus-mod +spec: + selector: + app: minecraft + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + +--- +apiVersion: v1 +kind: Service +metadata: + name: minecraft-game +spec: + type: LoadBalancer + externalTrafficPolicy: Cluster + ports: + - port: 30565 + targetPort: 25565 + selector: + app: minecraft + diff --git a/k8s-ng/apps/rustdesk/deployment.yaml b/k8s-ng/apps/rustdesk/deployment.yaml new file mode 100644 index 0000000..208b8a3 --- /dev/null +++ b/k8s-ng/apps/rustdesk/deployment.yaml @@ -0,0 +1,151 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rustdesk-hbbs + annotations: + reloader.stakater.com/auto: "true" + labels: + app: rustdesk-hbbs +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: rustdesk-hbbs + template: + metadata: + labels: + app: rustdesk-hbbs + spec: + nodeSelector: + kubernetes.io/hostname: music + containers: + - name: hbbs + image: rustdesk/rustdesk-server@sha256:8ecdab65deb7c84652a626380e31d11a8f1fbafd97916d57f95c20628f943c00 + imagePullPolicy: Always + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "750m" + command: ["hbbs"] + args: + - "--relay-servers" + - "rd.hexor.cy:21117" + - "--port" + - "21116" + ports: + - name: registry + containerPort: 21116 + protocol: TCP + - name: registry-udp + containerPort: 21116 + protocol: UDP + - name: nat + containerPort: 21115 + protocol: TCP + readinessProbe: + tcpSocket: + port: registry + volumeMounts: + - name: keys + mountPath: /data + readOnly: true + - name: data + mountPath: /data-persistent + env: + - name: RUST_LOG + value: "info" + - name: DB_URL + value: "/data-persistent/db_v2.sqlite3" + workingDir: /data + volumes: + - name: keys + secret: + secretName: rustdesk-keys + items: + - key: id_ed25519 + path: id_ed25519 + mode: 0600 + - key: id_ed25519.pub + path: id_ed25519.pub + mode: 0644 + - name: data + hostPath: + path: /data/k8s/rustdesk/hbbs + type: Directory + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rustdesk-hbbr + annotations: + reloader.stakater.com/auto: "true" + labels: + app: rustdesk-hbbr +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: rustdesk-hbbr + template: + metadata: + labels: + app: rustdesk-hbbr + spec: + nodeSelector: + kubernetes.io/hostname: music + containers: + - name: hbbr + image: rustdesk/rustdesk-server@sha256:8ecdab65deb7c84652a626380e31d11a8f1fbafd97916d57f95c20628f943c00 + imagePullPolicy: Always + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "750m" + command: ["hbbr"] + args: + - "--port" + - "21117" + ports: + - name: relay + containerPort: 21117 + protocol: TCP + readinessProbe: + tcpSocket: + port: relay + volumeMounts: + - name: keys + mountPath: /data + readOnly: true + - name: data + mountPath: /data-persistent + env: + - name: RUST_LOG + value: "info" + workingDir: /data + volumes: + - name: keys + secret: + secretName: rustdesk-keys + items: + - key: id_ed25519 + path: id_ed25519 + mode: 0600 + - key: id_ed25519.pub + path: id_ed25519.pub + mode: 0644 + - name: data + hostPath: + path: /data/k8s/rustdesk/hbbr + type: Directory diff --git a/k8s-ng/apps/rustdesk/external-secrets.yaml b/k8s-ng/apps/rustdesk/external-secrets.yaml new file mode 100644 index 0000000..3de516a --- /dev/null +++ b/k8s-ng/apps/rustdesk/external-secrets.yaml @@ -0,0 +1,27 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: rustdesk-keys +spec: + refreshInterval: 1h + secretStoreRef: + name: vaultwarden-fields + kind: ClusterSecretStore + target: + name: rustdesk-keys + creationPolicy: Owner + deletionPolicy: Retain + template: + type: Opaque + data: + id_ed25519: "{{ .private_key }}" + id_ed25519.pub: "{{ .public_key }}" + data: + - secretKey: private_key + remoteRef: + key: k8s/rustdesk/secrets + property: Cert + - secretKey: public_key + remoteRef: + key: k8s/rustdesk/secrets + property: Key diff --git a/k8s-ng/apps/rustdesk/kustomization.yaml b/k8s-ng/apps/rustdesk/kustomization.yaml new file mode 100644 index 0000000..b6cd8c6 --- /dev/null +++ b/k8s-ng/apps/rustdesk/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: rustdesk +resources: + - external-secrets.yaml + - deployment.yaml + - service.yaml + - network-policy.yaml diff --git a/k8s-ng/apps/rustdesk/network-policy.yaml b/k8s-ng/apps/rustdesk/network-policy.yaml new file mode 100644 index 0000000..1261c48 --- /dev/null +++ b/k8s-ng/apps/rustdesk/network-policy.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: rustdesk-network-policy +spec: + podSelector: + matchLabels: + app: rustdesk-hbbs + policyTypes: + - Ingress + - Egress + ingress: + # Allow all incoming connections to RustDesk ports + - from: [] + ports: + - protocol: TCP + port: 21115 + - protocol: TCP + port: 21116 + - protocol: UDP + port: 21116 + egress: + # Allow DNS + - to: [] + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + # Allow communication between HBBS and HBBR + - to: + - podSelector: + matchLabels: + app: rustdesk-hbbr + ports: + - protocol: TCP + port: 21117 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: rustdesk-hbbr-network-policy +spec: + podSelector: + matchLabels: + app: rustdesk-hbbr + policyTypes: + - Ingress + - Egress + ingress: + # Allow all incoming connections to relay port + - from: [] + ports: + - protocol: TCP + port: 21117 + # Allow connections from HBBS + - from: + - podSelector: + matchLabels: + app: rustdesk-hbbs + ports: + - protocol: TCP + port: 21117 + egress: + # Allow DNS + - to: [] + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 diff --git a/k8s-ng/apps/rustdesk/service.yaml b/k8s-ng/apps/rustdesk/service.yaml new file mode 100644 index 0000000..0b7190a --- /dev/null +++ b/k8s-ng/apps/rustdesk/service.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: rustdesk-hbbs + labels: + app: rustdesk-hbbs +spec: + type: LoadBalancer + externalTrafficPolicy: Cluster + selector: + app: rustdesk-hbbs + ports: + - name: registry-tcp + port: 21116 + targetPort: 21116 + protocol: TCP + - name: nat + port: 21115 + targetPort: 21115 + protocol: TCP + +--- +apiVersion: v1 +kind: Service +metadata: + name: rustdesk-hbbs-udp + labels: + app: rustdesk-hbbs +spec: + type: LoadBalancer + externalTrafficPolicy: Cluster + selector: + app: rustdesk-hbbs + ports: + - name: registry-udp + port: 21116 + targetPort: 21116 + protocol: UDP + +--- +apiVersion: v1 +kind: Service +metadata: + name: rustdesk-hbbr + labels: + app: rustdesk-hbbr +spec: + type: LoadBalancer + externalTrafficPolicy: Cluster + selector: + app: rustdesk-hbbr + ports: + - name: relay + port: 21117 + targetPort: 21117 + protocol: TCP diff --git a/k8s-ng/core-state/values.yaml b/k8s-ng/core-state/values.yaml index 3748585..36b4e50 100644 --- a/k8s-ng/core-state/values.yaml +++ b/k8s-ng/core-state/values.yaml @@ -92,3 +92,20 @@ apps: namespace: prometheus wave: 5 # after secret-stores; installs monitoring before application workloads skipDryRunOnMissingResource: true # monitoring, cert-manager and ExternalSecret CRDs + - name: immich-db + path: k8s-ng/core/immich-db + namespace: immich + wave: 2 + prune: false + skipDryRunOnMissingResource: true + - name: minecraft + project: games + path: k8s-ng/apps/minecraft + namespace: minecraft + wave: 6 + - name: rustdesk + project: apps + path: k8s-ng/apps/rustdesk + namespace: rustdesk + wave: 6 + skipDryRunOnMissingResource: true diff --git a/k8s-ng/core/immich-db/cluster.yaml b/k8s-ng/core/immich-db/cluster.yaml new file mode 100644 index 0000000..4723c0b --- /dev/null +++ b/k8s-ng/core/immich-db/cluster.yaml @@ -0,0 +1,45 @@ +# Keep PostgreSQL and both vector extensions identical to the source during +# migration. The application/media migration and any upgrades happen later. +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: immich + annotations: + argocd.argoproj.io/sync-options: Prune=false +spec: + description: Immich database migrated from the old cluster; media remain on NAS + imageName: ghcr.io/tensorchord/cloudnative-pgvecto.rs:14-v0.3.0@sha256:df7848916fea2c486faee8155a481dc5009f30ec513b5561d9ab76d1e101a594 + instances: 1 + enableSuperuserAccess: true + bootstrap: + initdb: + database: immich + owner: immich + dataChecksums: true + encoding: UTF8 + localeCType: C + localeCollate: C + postgresUID: 26 + postgresGID: 26 + storage: + size: 55Gi + storageClass: immich-local + pvcTemplate: + accessModes: [ReadWriteOnce] + selector: + matchLabels: + immich.hexor.cy/volume: postgres + affinity: + nodeSelector: + kubernetes.io/hostname: music + postgresql: + shared_preload_libraries: [vectors.so] + parameters: + shared_buffers: 512MB + max_connections: "200" + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + memory: 2Gi diff --git a/k8s-ng/core/immich-db/kustomization.yaml b/k8s-ng/core/immich-db/kustomization.yaml new file mode 100644 index 0000000..5de9df2 --- /dev/null +++ b/k8s-ng/core/immich-db/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: immich +resources: + - storage.yaml + - cluster.yaml diff --git a/k8s-ng/core/immich-db/storage.yaml b/k8s-ng/core/immich-db/storage.yaml new file mode 100644 index 0000000..4142318 --- /dev/null +++ b/k8s-ng/core/immich-db/storage.yaml @@ -0,0 +1,64 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: immich-db-storage-prepare + annotations: + argocd.argoproj.io/hook: PreSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded +spec: + backoffLimit: 3 + template: + spec: + restartPolicy: OnFailure + nodeSelector: + kubernetes.io/hostname: music + containers: + - name: prepare + image: docker.io/busybox:1.37.0 + command: [sh, -ec, "mkdir -p /storage/postgresql; chown 26:26 /storage/postgresql; chmod 0700 /storage/postgresql"] + securityContext: + runAsUser: 0 + allowPrivilegeEscalation: false + volumeMounts: + - name: storage + mountPath: /storage + volumes: + - name: storage + hostPath: + path: /data/k8s/immich + type: DirectoryOrCreate +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: immich-local + annotations: + argocd.argoproj.io/sync-options: Prune=false +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +reclaimPolicy: Retain +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: immich-postgres-music + labels: + immich.hexor.cy/volume: postgres + annotations: + argocd.argoproj.io/sync-options: Prune=false +spec: + capacity: + storage: 55Gi + volumeMode: Filesystem + accessModes: [ReadWriteOnce] + persistentVolumeReclaimPolicy: Retain + storageClassName: immich-local + local: + path: /data/k8s/immich/postgresql + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: [music]