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 LoadingServer 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]