From 533cbed8d315ae4fe64313c5d57ccf7c26233887 Mon Sep 17 00:00:00 2001 From: capitano Date: Wed, 18 Mar 2026 22:20:20 +0100 Subject: [PATCH] Add Linux App Store Web Portal with Kubernetes deployment - React-style static web portal with Tailwind CSS - OS detection (Debian/Ubuntu, Fedora, Arch, Generic) - Dynamic install commands (apt://, appstream://, flatpak) - 9 pre-configured applications - Kubernetes: 2 replicas, LoadBalancer service, Nginx ingress - GitLab Container Registry (git.giaco.net/capitano/webapplinux) - Namespace: linuxwebapp - Added Dockerfile, nginx.conf, deploy.sh, docker-compose.yml - Updated README.md with deployment instructions --- Dockerfile | 9 +++ Dockerfile.simple | 9 +++ README.md | 62 +++++++++----- deploy.sh | 18 +++++ docker-compose.yml | 11 +++ frontend/apps.json | 112 ++++++++++++++++++++++++++ frontend/index.html | 49 +++++++++++ frontend/script.js | 192 ++++++++++++++++++++++++++++++++++++++++++++ k8s/deployment.yaml | 47 +++++++++++ k8s/ingress.yaml | 22 +++++ k8s/namespace.yaml | 6 ++ k8s/service.yaml | 16 ++++ nginx.conf | 26 ++++++ 13 files changed, 560 insertions(+), 19 deletions(-) create mode 100644 Dockerfile create mode 100644 Dockerfile.simple create mode 100755 deploy.sh create mode 100644 docker-compose.yml create mode 100644 frontend/apps.json create mode 100644 frontend/index.html create mode 100644 frontend/script.js create mode 100644 k8s/deployment.yaml create mode 100644 k8s/ingress.yaml create mode 100644 k8s/namespace.yaml create mode 100644 k8s/service.yaml create mode 100644 nginx.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..24f8c6f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM nginx:alpine + +COPY frontend/ /usr/share/nginx/html/ + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/Dockerfile.simple b/Dockerfile.simple new file mode 100644 index 0000000..24f8c6f --- /dev/null +++ b/Dockerfile.simple @@ -0,0 +1,9 @@ +FROM nginx:alpine + +COPY frontend/ /usr/share/nginx/html/ + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 7e311c7..7476b5a 100644 --- a/README.md +++ b/README.md @@ -4,58 +4,74 @@ ### Prerequisiti -- Cluster Kubernetes attivo +- Cluster Kubernetes attivo (es. `10.66.200.x/24`) - kubectl configurato -- Container registry accessibile (Docker Hub o privato) +- Container registry privato (GitLab: `git.giaco.net`) +- Personal Access Token per il registry ### Build e Deploy ```bash -# Build l'immagine -docker build -t linux-app-store:latest . +# Login al registry GitLab +echo "" | docker login git.giaco.net -u capitano --password-stdin -# Tagga per il push -docker tag linux-app-store:latest /linux-app-store:latest +# Build l'immagine +docker build -t git.giaco.net/capitano/webapplinux:latest . # Push su registry -docker push /linux-app-store:latest +docker push git.giaco.net/capitano/webapplinux:latest # Deploy su Kubernetes +kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml kubectl apply -f k8s/ingress.yaml # Verifica -kubectl get pods -kubectl get svc -kubectl get ingress +kubectl get pods -n linuxwebapp +kubectl get svc -n linuxwebapp +kubectl get ingress -n linuxwebapp # Log -kubectl logs -l app=linux-app-store -f +kubectl logs -l app=linux-app-store -n linuxwebapp -f ``` +### Accesso al portale + +Il portale è accessibile tramite ingress nginx all'indirizzo: +- **URL**: `http://apps.local/` + +Per raggiungerlo localmente, aggiungi una entry nel file `/etc/hosts`: +``` +10.66.200.211 apps.local +``` +(sostituisci `10.66.200.211` con l'IP del tuo Ingress controller) + ### File struttura ``` -infrastructure/ -├── Dockerfile # Dockerfile multi-stage (Nginx) -├── Dockerfile.simple # Dockerfile semplificato +webapplinux/ +├── Dockerfile # Dockerfile Nginx +├── docker-compose.yml # Compose per sviluppo locale ├── nginx.conf # Configurazione Nginx +├── README.md # Questo file ├── frontend/ │ ├── index.html # Pagina principale -│ ├── script.js # Logica dell'app +│ ├── script.js # Logica OS detection & install │ └── apps.json # Database applicazioni └── k8s/ - ├── deployment.yaml # Kubernetes Deployment (2.repliche) + ├── namespace.yaml # Namespace linuxwebapp + ├── deployment.yaml # Deployment (2 repliche, git registry) ├── service.yaml # Service (ClusterIP) └── ingress.yaml # Ingress (dominio apps.local) ``` ### Customizzazione -1. Modificare `frontend/apps.json` per aggiungere rimuovere applicazioni -2. Aggiornare `nginx.conf` per personalizzare le regole di routing -3. Modificare `k8s/deployment.yaml` per aggiornare le risorse o le immagini +1. **Applicazioni**: Modifica `frontend/apps.json` per aggiungere/rimuovere app +2. **Nginx**: Aggiorna `nginx.conf` per routing personalizzato +3. **Risorse**: Modifica `k8s/deployment.yaml` per cpu/memory +4. **Immagine**: Cambia `image` nel deployment per usare un altro registry ### Rimozione @@ -63,4 +79,12 @@ infrastructure/ kubectl delete -f k8s/ingress.yaml kubectl delete -f k8s/service.yaml kubectl delete -f k8s/deployment.yaml +kubectl delete -f k8s/namespace.yaml ``` + +### CI/CD + +Usa il file `.gitlab-ci.yml` (da creare) per automatizzare: +1. Build dell'immagine +2. Push su GitLab registry +3. Deploy automatico in Kubernetes diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..2a21f12 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +REGISTRY="git.giaco.net" +IMAGE_NAME="capitano/webapplinux" +TAG="latest" + +echo "Building image..." +docker build -t $REGISTRY/$IMAGE_NAME:$TAG /home/capitano/kubernetes/infrastructure/webapplinux/ + +echo "Pushing to registry..." +docker push $REGISTRY/$IMAGE_NAME:$TAG + +echo "Deploying to Kubernetes..." +kubectl apply -f k8s/namespace.yaml 2>/dev/null || true +kubectl apply -f k8s/ + +echo "Check status with: kubectl get pods -n linuxwebapp" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..32e477e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + web: + build: . + ports: + - "8080:80" + volumes: + - ./frontend:/usr/share/nginx/html + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + environment: + - NGINX_HOST=apps.local + - NGINX_PORT=80 diff --git a/frontend/apps.json b/frontend/apps.json new file mode 100644 index 0000000..cac6311 --- /dev/null +++ b/frontend/apps.json @@ -0,0 +1,112 @@ +{ + "apps": [ + { + "id": "vlc", + "name": "VLC Media Player", + "description": "The most complete media player ever created. Play all your favorite media formats.", + "icon": "fa-film", + "colors": "from-purple-500 to-indigo-600", + "packages": { + "apt": "vlc", + "appstream": "org.videolan.VLC", + "flatpak": "https://flathub.org-repo/vlc.flatpakref" + } + }, + { + "id": "gimp", + "name": "GIMP", + "description": "GNU Image Manipulation Program - professional-grade photo editing tool.", + "icon": "fa-image", + "colors": "from-blue-500 to-cyan-600", + "packages": { + "apt": "gimp", + "appstream": "org.gimp.GIMP", + "flatpak": "https://flathub.org-repo/gimp.flatpakref" + } + }, + { + "id": "vscode", + "name": "VS Code", + "description": "Visual Studio Code - the code editor redefined for modern development.", + "icon": "fa-code", + "colors": "from-blue-600 to-blue-800", + "packages": { + "apt": "code", + "appstream": "com.visualstudio.code", + "flatpak": "https://flathub.org-repo/code.flatpakref" + } + }, + { + "id": "thunderbird", + "name": "Thunderbird", + "description": "Email client that puts control back in your hands with powerful features.", + "icon": "fa-envelope", + "colors": "from-orange-500 to-red-600", + "packages": { + "apt": "thunderbird", + "appstream": "org.mozilla.thunderbird", + "flatpak": "https://flathub.org-repo/thunderbird.flatpakref" + } + }, + { + "id": "libreoffice", + "name": "LibreOffice", + "description": "The-free-and-open-source productivity suite for documents, spreadsheets, and presentations.", + "icon": "fa-file-alt", + "colors": "from-teal-500 to-green-600", + "packages": { + "apt": "libreoffice", + "appstream": "org.libreoffice.LibreOffice", + "flatpak": "https://flathub.org-repo/libreoffice.flatpakref" + } + }, + { + "id": "firefox", + "name": "Firefox", + "description": "The web browser built for privacy, speed, and customization.", + "icon": "fa-globe", + "colors": "from-orange-600 to-red-700", + "packages": { + "apt": "firefox", + "appstream": "org.mozilla.firefox", + "flatpak": "https://flathub.org-repo/firefox.flatpakref" + } + }, + { + "id": "inkscape", + "name": "Inkscape", + "description": "Professional vector graphics editor for illustrations, diagrams, and logos.", + "icon": "fa-pen-nib", + "colors": "from-blue-400 to-indigo-500", + "packages": { + "apt": "inkscape", + "appstream": "org.inkscape.Inkscape", + "flatpak": "https://flathub.org-repo/inkscape.flatpakref" + } + }, + { + "id": "discord", + "name": "Discord", + "description": "All-in-one voice and text chat for gamers and communities.", + "icon": "fa-comments", + "colors": "from-indigo-500 to-purple-600", + "packages": { + "apt": "discord", + "appstream": "com.discordapp.Discord", + "flatpak": "https://flathub.org-repo/discord.flatpakref" + } + }, + { + "id": "github-desktop", + "name": "GitHub Desktop", + "description": "Simple collaboration from your desktop - git made easy.", + "icon": "fa-github", + "colors": "from-gray-600 to-gray-800", + "packages": { + "apt": "github-desktop", + "appstream": "com.githubDesktop.githubDesktop", + "flatpak": "https://flathub.org-repo/github-desktop.flatpakref" + } + } + ] +} diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..ee7a4a6 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,49 @@ + + + + + + Linux App Store + + + + + + + +
+
+

Discovery

+

Discover and install applications directly on your Linux system

+
+ +
+
+
+ + + + diff --git a/frontend/script.js b/frontend/script.js new file mode 100644 index 0000000..b474f89 --- /dev/null +++ b/frontend/script.js @@ -0,0 +1,192 @@ +const OS_TYPE = { + DEB: 'deb', + RPM: 'rpm', + ARCH: 'arch', + GENERIC: 'generic' +}; + +function detectOS() { + const userAgent = navigator.userAgent.toLowerCase(); + + if (userAgent.includes('ubuntu') || + userAgent.includes('debian') || + userAgent.includes('linuxmint') || + userAgent.includes('pop!_os')) { + return { type: OS_TYPE.DEB, name: 'Debian/Ubuntu-based' }; + } + + if (userAgent.includes('fedora') || + userAgent.includes('rhel') || + userAgent.includes('redhat') || + userAgent.includes('centos') || + userAgent.includes('opensuse') || + userAgent.includes('suse')) { + return { type: OS_TYPE.RPM, name: 'Fedora/RedHat-based' }; + } + + if (userAgent.includes('arch') || + userAgent.includes('endeavouros') || + userAgent.includes('manjaro')) { + return { type: OS_TYPE.ARCH, name: 'Arch Linux' }; + } + + return { type: OS_TYPE.GENERIC, name: 'Generic Linux' }; +} + +function renderApps(apps) { + const container = document.getElementById('apps-container'); + + apps.forEach(app => { + const appCard = document.createElement('div'); + appCard.className = 'bg-gray-850 rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-shadow duration-300 border border-gray-700'; + + appCard.innerHTML = ` +
+
+
+
+ +
+

${app.name}

+
+
+ +

${app.description}

+ +
+
Installazione:
+
Caricamento...
+
+ + +
+ `; + + container.appendChild(appCard); + }); +} + +function updateInstallLink(appId, aptPackage, appstreamId, flatpakUrl) { + const osInfo = detectOS(); + const installDiv = document.getElementById(`install-${appId}`); + + let installCommand = ''; + let protocol = ''; + + switch (osInfo.type) { + case OS_TYPE.DEB: + protocol = 'apt://'; + installCommand = `${protocol}${aptPackage}`; + break; + case OS_TYPE.RPM: + protocol = 'appstream://'; + installCommand = `${protocol}${appstreamId}`; + break; + case OS_TYPE.ARCH: + protocol = 'appstream://'; + installCommand = `${protocol}${appstreamId} (pacman -S ${aptPackage})`; + break; + case OS_TYPE.GENERIC: + protocol = 'flatpak'; + installCommand = `Download: ${flatpakUrl}`; + break; + } + + installDiv.innerHTML = ` ${installCommand}`; + + const installBtn = document.querySelector(`button[onclick="installApp('${appId}', '${aptPackage}', '${appstreamId}', '${flatpakUrl}')"]`); + + installBtn.onclick = function() { + handleInstall(appId, aptPackage, appstreamId, flatpakUrl); + }; +} + +function handleInstall(appId, aptPackage, appstreamId, flatpakUrl) { + const osInfo = detectOS(); + let url = ''; + + switch (osInfo.type) { + case OS_TYPE.DEB: + url = `apt://${aptPackage}`; + break; + case OS_TYPE.RPM: + url = `appstream://${appstreamId}`; + break; + case OS_TYPE.ARCH: + url = `appstream://${appstreamId}`; + break; + case OS_TYPE.GENERIC: + url = flatpakUrl; + break; + } + + if (url.startsWith('apt://') || url.startsWith('appstream://')) { + const protocol = url.split(':')[0]; + let command = url.replace(`${protocol}://`, ''); + + if (protocol === 'apt') { + command = `sudo apt install ${command}`; + } else { + command = `${protocol} install ${command}`; + } + + const notification = createNotification(`Comando generato per ${osInfo.name}:
${command}`, 'success'); + document.body.appendChild(notification); + + setTimeout(() => notification.remove(), 5000); + } else if (url) { + window.open(url, '_blank'); + } +} + +function createNotification(message, type) { + const notification = document.createElement('div'); + notification.className = `fixed top-4 right-4 bg-gray-800 border-l-4 border-${type === 'success' ? 'green' : 'red'}-500 text-white px-6 py-4 rounded shadow-lg z-50 transition-all duration-500 transform translate-x-full`; + notification.innerHTML = ` +
+ +
${message}
+
+ `; + + setTimeout(() => { + notification.classList.remove('translate-x-full'); + }, 100); + + return notification; +} + +function highlightOS(osInfo) { + const osDisplay = document.getElementById('os-display'); + if (osDisplay) { + osDisplay.innerHTML = `${osInfo.name}`; + osDisplay.classList.remove('hidden'); + } +} + +function init() { + fetch('apps.json') + .then(response => response.json()) + .then(data => { + const osInfo = detectOS(); + highlightOS(osInfo); + renderApps(data.apps); + + data.apps.forEach(app => { + updateInstallLink(app.id, app.packages.apt, app.packages.appstream, app.packages.flatpak); + }); + }) + .catch(error => { + console.error('Error loading apps:', error); + const container = document.getElementById('apps-container'); + container.innerHTML = `
+ +

Errore nel caricamento delle applicazioni

+
`; + }); +} + +document.addEventListener('DOMContentLoaded', init); diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..97703a4 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: linux-app-store + namespace: linuxwebapp + labels: + app: linux-app-store +spec: + replicas: 2 + selector: + matchLabels: + app: linux-app-store + template: + metadata: + labels: + app: linux-app-store + spec: + containers: + - name: linux-app-store + image: git.giaco.net/capitano/webapplinux:latest + imagePullPolicy: Always + ports: + - containerPort: 80 + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "200m" + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + env: + - name: NGINX_HOST + value: "apps.local" + - name: NGINX_PORT + value: "80" diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..aa28344 --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,22 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: linux-app-store-ingress + namespace: linuxwebapp + labels: + app: linux-app-store + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: apps.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: linux-app-store-service + port: + number: 80 diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..638400f --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: linuxwebapp + labels: + name: linuxwebapp diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000..272d5da --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: linux-app-store-service + namespace: linuxwebapp + labels: + app: linux-app-store +spec: + type: LoadBalancer + selector: + app: linux-app-store + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..ef16f18 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,26 @@ +server { + listen 80; + server_name apps.local; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /apps.json { + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + } + + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml application/json; + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +}