mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
WIP: k3s remote setup and graphical installer
This commit is contained in:
parent
46ffe08783
commit
10e1113eea
79
INSTALLER_README.md
Normal file
79
INSTALLER_README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Prole Service Dependencies Installer
|
||||
|
||||
A desktop application for installing, deploying, and validating Prole services.
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Install Screen
|
||||
- Check installation status of required dependencies:
|
||||
- Docker
|
||||
- Homebrew
|
||||
- k3d
|
||||
- kubectl
|
||||
- Helm
|
||||
- krew
|
||||
- cmctl
|
||||
- Download links to each project's homepage
|
||||
- Generate installer script (`install_dependencies.sh`) with all installation commands
|
||||
|
||||
### 2. Deploy Screen
|
||||
- Single "Build and Deploy" button that:
|
||||
- Checks Docker is running
|
||||
- Creates or restarts k3d `prole-service-cluster`
|
||||
- Builds the local `prole-db` Docker image
|
||||
- Tags the image for the registry
|
||||
- Pushes the image to the registry
|
||||
- Imports the image to the k3d cluster
|
||||
- Real-time progress tracking with visual indicators (pending, running, completed, error)
|
||||
- Green checkmarks for completed steps
|
||||
|
||||
### 3. Validate Screen
|
||||
- Displays `kubectl cnpg status prole-db` output
|
||||
- Shows k3d cluster list status
|
||||
- Auto-refreshes every 10 seconds (can be toggled)
|
||||
- Manual refresh button
|
||||
- Link to Prometheus (http://localhost:9090)
|
||||
|
||||
## Usage
|
||||
|
||||
### Running the Installer
|
||||
|
||||
```bash
|
||||
python3 install.py
|
||||
```
|
||||
|
||||
Or make it executable and run directly:
|
||||
|
||||
```bash
|
||||
chmod +x install.py
|
||||
./install.py
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
- Python 3.6+
|
||||
- tkinter (usually included with Python on macOS/Linux)
|
||||
- macOS (for Homebrew installation)
|
||||
|
||||
### Installation Steps
|
||||
|
||||
1. **Install Dependencies**: Use the Install screen to check and install required dependencies
|
||||
2. **Deploy Services**: Use the Deploy screen to build and deploy Prole services
|
||||
3. **Validate**: Use the Validate screen to monitor the deployment status
|
||||
|
||||
## Notes
|
||||
|
||||
- The installer requires Docker Desktop to be running for deployment
|
||||
- The k3d cluster name is `prole-service-cluster` as specified in README.md
|
||||
- The Docker image version is `prole-db:17.5-027`
|
||||
- The registry is configured as `k8s-prole-org-registry:k8s.prole.org:5000`
|
||||
|
||||
## Apple Silicon Support
|
||||
|
||||
The installer automatically detects if running on Apple Silicon (ARM64) and:
|
||||
- Sets `--platform linux/amd64` flag for Docker builds
|
||||
- Ensures mssql Docker images are built for AMD64 platform (required for MSSQL Server)
|
||||
- Ensures prole-db images are built for AMD64 platform for compatibility
|
||||
|
||||
This ensures compatibility with services that require AMD64 architecture, such as MSSQL Server.
|
||||
|
||||
73
README.md
73
README.md
@ -76,3 +76,76 @@ kubectl apply -f https://github.com/cloudnative-pg/plugin-barman-cloud/releases/
|
||||
|
||||
# prole logo cmd
|
||||
# figlet -f small "prole" | boxes -d ansi-rounded | boxes -d shell
|
||||
|
||||
|
||||
## K3s HA install for Prole (Pi cluster + local k3d agents)
|
||||
|
||||
This repository includes scripts to create an HA K3s control plane across Raspberry Pi hosts and attach local k3d agents.
|
||||
|
||||
Topology overview:
|
||||
- raspberry.prole.org → first K3s server (embedded etcd)
|
||||
- pi.prole.org → additional K3s server (recommended for HA)
|
||||
- This host (developer machine) → local k3d agents that join the remote K3s server
|
||||
|
||||
Prerequisites:
|
||||
- k3sup: https://github.com/alexellis/k3sup
|
||||
- k3d: https://k3d.io/
|
||||
- kubectl
|
||||
- SSH access to the Pis (defaults assume user `pi` and key `~/.ssh/id_rsa`)
|
||||
|
||||
1) Install first K3s server on raspberry.prole.org
|
||||
```bash
|
||||
cd k3s
|
||||
# Optional env overrides:
|
||||
# export K3S_FIRST_SERVER_HOST=raspberry.prole.org
|
||||
# export K3S_FIRST_SERVER_USER=pi
|
||||
# export K3S_SSH_KEY=$HOME/.ssh/id_rsa
|
||||
# export K3S_VERSION=v1.30.6+k3s1
|
||||
# export K3S_EXTRA_ARGS="--cluster-init --tls-san raspberry.prole.org"
|
||||
./install_k3s_first_server.sh
|
||||
```
|
||||
|
||||
2) Join pi.prole.org as an additional HA server (optional but recommended)
|
||||
```bash
|
||||
cd k3s
|
||||
# Optional env overrides:
|
||||
# export K3S_EXISTING_SERVER_HOST=raspberry.prole.org
|
||||
# export K3S_EXISTING_SERVER_USER=pi
|
||||
# export K3S_ADDITIONAL_SERVER_HOST=pi.prole.org
|
||||
# export K3S_ADDITIONAL_SERVER_USER=pi
|
||||
# export K3S_SSH_KEY=$HOME/.ssh/id_rsa
|
||||
# export K3S_VERSION=v1.30.6+k3s1
|
||||
# export K3S_EXTRA_ARGS="--tls-san raspberry.prole.org --tls-san pi.prole.org"
|
||||
./join_k3s_additional_server.sh
|
||||
```
|
||||
|
||||
3) Get the K3s node token from the first server
|
||||
```bash
|
||||
cd k3s
|
||||
# Optional env overrides:
|
||||
# export K3S_SERVER_HOST=raspberry.prole.org
|
||||
# export K3S_SERVER_USER=pi
|
||||
# export K3S_SSH_KEY=$HOME/.ssh/id_rsa
|
||||
TOKEN=$(./get_remote_k3s_node_token.sh)
|
||||
echo "K3s token: $TOKEN"
|
||||
```
|
||||
|
||||
4) Create a local k3d agents-only cluster that joins the remote K3s server
|
||||
```bash
|
||||
cd k3s
|
||||
# Required: provide the token
|
||||
export K3S_REMOTE_TOKEN="$TOKEN"
|
||||
# Optional env overrides:
|
||||
# export K3D_CLUSTER_NAME=prole-remote-agents
|
||||
# export K3D_AGENTS=2
|
||||
# export K3D_IMAGE=rancher/k3s:v1.30.6-k3s1
|
||||
# export K3D_REGISTRY_CONFIG=$HOME/dev/prole/k3s/registries.yaml
|
||||
# export K3S_REMOTE_SERVER_HOST=raspberry.prole.org
|
||||
# export K3S_REMOTE_SERVER_PORT=6443
|
||||
./create_k3d_join_remote_agents.sh
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The scripts default to embedded etcd HA. For true quorum, use at least 3 servers where possible. With two servers, loss of one server can cause loss of quorum; consider adding a third server or running an external etcd.
|
||||
- If you prefer an external datastore (e.g., MySQL/PG/etcd), adjust `K3S_EXTRA_ARGS` in the server install to set `--datastore-endpoint=...` and remove `--cluster-init`.
|
||||
- The local k3d cluster creates only agents that connect to the remote control-plane; it does not run a separate control-plane locally.
|
||||
|
||||
781
install.py
Executable file
781
install.py
Executable file
@ -0,0 +1,781 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Prole Service Dependencies Installer
|
||||
Desktop application for installing, deploying, and validating Prole services
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, scrolledtext, messagebox
|
||||
import subprocess
|
||||
import threading
|
||||
import os
|
||||
import sys
|
||||
import webbrowser
|
||||
import time
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
# Get the project root directory
|
||||
PROJECT_ROOT = Path(__file__).parent.absolute()
|
||||
|
||||
|
||||
def is_apple_silicon():
|
||||
"""Check if running on Apple Silicon (ARM64)"""
|
||||
return platform.machine() == 'arm64' and platform.system() == 'Darwin'
|
||||
|
||||
|
||||
def get_docker_build_platform_args():
|
||||
"""Get Docker build platform arguments for Apple Silicon"""
|
||||
if is_apple_silicon():
|
||||
return ['--platform', 'linux/amd64']
|
||||
return []
|
||||
|
||||
|
||||
class ProleInstaller:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("Prole Service Dependencies Installer")
|
||||
|
||||
# Center window on screen
|
||||
window_width = 1000
|
||||
window_height = 700
|
||||
screen_width = root.winfo_screenwidth()
|
||||
screen_height = root.winfo_screenheight()
|
||||
center_x = int(screen_width / 2 - window_width / 2)
|
||||
center_y = int(screen_height / 2 - window_height / 2)
|
||||
self.root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
|
||||
|
||||
self.root.configure(bg='#1a1a1a')
|
||||
|
||||
# Style configuration
|
||||
self.style = ttk.Style()
|
||||
self.style.theme_use('clam')
|
||||
self.configure_styles()
|
||||
|
||||
# Create main container
|
||||
self.container = tk.Frame(root, bg='#1a1a1a')
|
||||
self.container.pack(fill='both', expand=True, padx=20, pady=20)
|
||||
|
||||
# Initialize validation attributes before creating screens
|
||||
self.validation_running = False
|
||||
self.validation_thread = None
|
||||
|
||||
# Create navigation buttons
|
||||
self.create_navigation()
|
||||
|
||||
# Create screens
|
||||
self.screens = {}
|
||||
self.create_install_screen()
|
||||
self.create_deploy_screen()
|
||||
self.create_validate_screen()
|
||||
|
||||
# Show initial screen
|
||||
self.show_screen('install')
|
||||
|
||||
def configure_styles(self):
|
||||
"""Configure ttk styles"""
|
||||
self.style.configure('Title.TLabel',
|
||||
background='#1a1a1a',
|
||||
foreground='#4a9eff',
|
||||
font=('Helvetica', 20, 'bold'))
|
||||
self.style.configure('Heading.TLabel',
|
||||
background='#1a1a1a',
|
||||
foreground='#ffffff',
|
||||
font=('Helvetica', 14, 'bold'))
|
||||
self.style.configure('Dark.TFrame',
|
||||
background='#1a1a1a')
|
||||
self.style.configure('Card.TFrame',
|
||||
background='#2a2a2a',
|
||||
relief='flat')
|
||||
self.style.configure('Primary.TButton',
|
||||
background='#4a9eff',
|
||||
foreground='white',
|
||||
font=('Helvetica', 12, 'bold'))
|
||||
self.style.map('Primary.TButton',
|
||||
background=[('active', '#3a8eef')])
|
||||
|
||||
def create_navigation(self):
|
||||
"""Create navigation buttons"""
|
||||
nav_frame = tk.Frame(self.container, bg='#1a1a1a')
|
||||
nav_frame.pack(fill='x', pady=(0, 20))
|
||||
|
||||
self.nav_buttons = {}
|
||||
screens = [
|
||||
('install', '1. Install'),
|
||||
('deploy', '2. Deploy'),
|
||||
('validate', '3. Validate')
|
||||
]
|
||||
|
||||
for screen_id, label in screens:
|
||||
btn = tk.Button(nav_frame,
|
||||
text=label,
|
||||
command=lambda s=screen_id: self.show_screen(s),
|
||||
bg='#2a2a2a',
|
||||
fg='#ffffff',
|
||||
activebackground='#3a3a3a',
|
||||
activeforeground='#ffffff',
|
||||
font=('Helvetica', 11, 'bold'),
|
||||
relief='flat',
|
||||
padx=20,
|
||||
pady=10,
|
||||
cursor='hand2',
|
||||
borderwidth=0,
|
||||
highlightthickness=0)
|
||||
btn.pack(side='left', padx=5)
|
||||
self.nav_buttons[screen_id] = btn
|
||||
|
||||
def show_screen(self, screen_id):
|
||||
"""Show the specified screen"""
|
||||
# Hide all screens
|
||||
for screen in self.screens.values():
|
||||
screen.pack_forget()
|
||||
|
||||
# Show selected screen
|
||||
if screen_id in self.screens:
|
||||
self.screens[screen_id].pack(fill='both', expand=True)
|
||||
|
||||
# Update navigation button states
|
||||
for sid, btn in self.nav_buttons.items():
|
||||
if sid == screen_id:
|
||||
btn.configure(bg='#4a9eff', fg='#ffffff', activebackground='#3a8eef', activeforeground='#ffffff')
|
||||
else:
|
||||
btn.configure(bg='#2a2a2a', fg='#ffffff', activebackground='#3a3a3a', activeforeground='#ffffff')
|
||||
|
||||
def create_install_screen(self):
|
||||
"""Create the Install screen"""
|
||||
frame = tk.Frame(self.container, bg='#1a1a1a')
|
||||
self.screens['install'] = frame
|
||||
|
||||
# Title
|
||||
title = ttk.Label(frame, text="Install Dependencies", style='Title.TLabel')
|
||||
title.pack(pady=(0, 30))
|
||||
|
||||
# Instructions
|
||||
instructions = tk.Label(frame,
|
||||
text="Install the following dependencies to proceed with Prole deployment:",
|
||||
bg='#1a1a1a',
|
||||
fg='#aaaaaa',
|
||||
font=('Helvetica', 11))
|
||||
instructions.pack(pady=(0, 20))
|
||||
|
||||
# Dependencies list
|
||||
deps_frame = tk.Frame(frame, bg='#1a1a1a')
|
||||
deps_frame.pack(fill='both', expand=True)
|
||||
|
||||
dependencies = [
|
||||
{
|
||||
'name': 'Docker',
|
||||
'description': 'Container platform for running Prole services',
|
||||
'url': 'https://www.docker.com/products/docker-desktop',
|
||||
'install_cmd': None,
|
||||
'check_cmd': 'docker --version'
|
||||
},
|
||||
{
|
||||
'name': 'Homebrew',
|
||||
'description': 'Package manager for macOS',
|
||||
'url': 'https://brew.sh',
|
||||
'install_cmd': '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
||||
'check_cmd': 'brew --version'
|
||||
},
|
||||
{
|
||||
'name': 'k3d',
|
||||
'description': 'Lightweight wrapper to run k3s in Docker',
|
||||
'url': 'https://k3d.io',
|
||||
'install_cmd': 'brew install k3d',
|
||||
'check_cmd': 'k3d --version'
|
||||
},
|
||||
{
|
||||
'name': 'kubectl',
|
||||
'description': 'Kubernetes command-line tool',
|
||||
'url': 'https://kubernetes.io/docs/tasks/tools/',
|
||||
'install_cmd': 'brew install kubectl',
|
||||
'check_cmd': 'kubectl version --client'
|
||||
},
|
||||
{
|
||||
'name': 'Helm',
|
||||
'description': 'Kubernetes package manager',
|
||||
'url': 'https://helm.sh',
|
||||
'install_cmd': 'brew install helm',
|
||||
'check_cmd': 'helm version'
|
||||
},
|
||||
{
|
||||
'name': 'krew',
|
||||
'description': 'Kubectl plugin manager',
|
||||
'url': 'https://krew.sigs.k8s.io',
|
||||
'install_cmd': 'brew install krew',
|
||||
'check_cmd': 'kubectl krew version'
|
||||
},
|
||||
{
|
||||
'name': 'cmctl',
|
||||
'description': 'cert-manager CLI tool',
|
||||
'url': 'https://cert-manager.io',
|
||||
'install_cmd': 'brew install cmctl',
|
||||
'check_cmd': 'cmctl version'
|
||||
}
|
||||
]
|
||||
|
||||
self.dep_status = {}
|
||||
for dep in dependencies:
|
||||
self.create_dependency_card(deps_frame, dep)
|
||||
|
||||
# Generate installer script button
|
||||
script_btn = tk.Button(frame,
|
||||
text="Generate Installer Script",
|
||||
command=self.generate_installer_script,
|
||||
bg='#4a9eff',
|
||||
fg='white',
|
||||
activebackground='#3a8eef',
|
||||
font=('Helvetica', 12, 'bold'),
|
||||
padx=30,
|
||||
pady=15,
|
||||
cursor='hand2',
|
||||
relief='flat')
|
||||
script_btn.pack(pady=20)
|
||||
|
||||
def create_dependency_card(self, parent, dep):
|
||||
"""Create a dependency card with status and download link"""
|
||||
card = tk.Frame(parent, bg='#2a2a2a', relief='flat', bd=1)
|
||||
card.pack(fill='x', pady=5, padx=10)
|
||||
|
||||
# Left side - info
|
||||
info_frame = tk.Frame(card, bg='#2a2a2a')
|
||||
info_frame.pack(side='left', fill='both', expand=True, padx=15, pady=15)
|
||||
|
||||
name_label = tk.Label(info_frame,
|
||||
text=dep['name'],
|
||||
bg='#2a2a2a',
|
||||
fg='#ffffff',
|
||||
font=('Helvetica', 13, 'bold'),
|
||||
anchor='w')
|
||||
name_label.pack(fill='x')
|
||||
|
||||
desc_label = tk.Label(info_frame,
|
||||
text=dep['description'],
|
||||
bg='#2a2a2a',
|
||||
fg='#aaaaaa',
|
||||
font=('Helvetica', 10),
|
||||
anchor='w')
|
||||
desc_label.pack(fill='x', pady=(5, 0))
|
||||
|
||||
# Right side - status and actions
|
||||
action_frame = tk.Frame(card, bg='#2a2a2a')
|
||||
action_frame.pack(side='right', padx=15, pady=15)
|
||||
|
||||
# Status indicator
|
||||
status_label = tk.Label(action_frame,
|
||||
text="Checking...",
|
||||
bg='#2a2a2a',
|
||||
fg='#ffaa00',
|
||||
font=('Helvetica', 10))
|
||||
status_label.pack(side='left', padx=10)
|
||||
self.dep_status[dep['name']] = {'label': status_label, 'dep': dep}
|
||||
|
||||
# Download button
|
||||
download_btn = tk.Button(action_frame,
|
||||
text="Download",
|
||||
command=lambda url=dep['url']: webbrowser.open(url),
|
||||
bg='#28a745',
|
||||
fg='white',
|
||||
activebackground='#218838',
|
||||
font=('Helvetica', 10),
|
||||
padx=15,
|
||||
pady=5,
|
||||
cursor='hand2',
|
||||
relief='flat')
|
||||
download_btn.pack(side='left', padx=5)
|
||||
|
||||
# Check status
|
||||
self.check_dependency(dep['name'])
|
||||
|
||||
def check_dependency(self, name):
|
||||
"""Check if a dependency is installed"""
|
||||
dep_info = self.dep_status[name]
|
||||
dep = dep_info['dep']
|
||||
label = dep_info['label']
|
||||
|
||||
def check():
|
||||
try:
|
||||
result = subprocess.run(dep['check_cmd'].split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5)
|
||||
if result.returncode == 0:
|
||||
label.configure(text="✓ Installed", fg='#28a745')
|
||||
else:
|
||||
label.configure(text="✗ Not Installed", fg='#dc3545')
|
||||
except Exception:
|
||||
label.configure(text="✗ Not Installed", fg='#dc3545')
|
||||
|
||||
threading.Thread(target=check, daemon=True).start()
|
||||
|
||||
def generate_installer_script(self):
|
||||
"""Generate installer script with all commands"""
|
||||
script_path = PROJECT_ROOT / 'install_dependencies.sh'
|
||||
|
||||
script_content = """#!/bin/bash
|
||||
# Prole Dependencies Installer Script
|
||||
# Generated by Prole Installer
|
||||
|
||||
set -e
|
||||
|
||||
echo "Installing Prole dependencies..."
|
||||
|
||||
# Check and install Homebrew
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Installing Homebrew..."
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
else
|
||||
echo "Homebrew already installed. Updating..."
|
||||
brew update
|
||||
brew upgrade
|
||||
fi
|
||||
|
||||
# Install k3d
|
||||
if ! command -v k3d &> /dev/null; then
|
||||
echo "Installing k3d..."
|
||||
brew install k3d
|
||||
fi
|
||||
|
||||
# Install kubectl
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo "Installing kubectl..."
|
||||
brew install kubectl
|
||||
fi
|
||||
|
||||
# Install Helm
|
||||
if ! command -v helm &> /dev/null; then
|
||||
echo "Installing Helm..."
|
||||
brew install helm
|
||||
fi
|
||||
|
||||
# Install krew
|
||||
if ! command -v kubectl krew &> /dev/null; then
|
||||
echo "Installing krew..."
|
||||
brew install krew
|
||||
kubectl krew update
|
||||
fi
|
||||
|
||||
# Install cmctl
|
||||
if ! command -v cmctl &> /dev/null; then
|
||||
echo "Installing cmctl..."
|
||||
brew install cmctl
|
||||
fi
|
||||
|
||||
# Install kubectl plugins
|
||||
echo "Installing kubectl plugins..."
|
||||
kubectl krew install view-secret
|
||||
|
||||
echo "All dependencies installed successfully!"
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(script_path, 'w') as f:
|
||||
f.write(script_content)
|
||||
os.chmod(script_path, 0o755)
|
||||
messagebox.showinfo("Success",
|
||||
f"Installer script generated at:\n{script_path}\n\n"
|
||||
"You can run it with: ./install_dependencies.sh")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to generate script: {str(e)}")
|
||||
|
||||
def create_deploy_screen(self):
|
||||
"""Create the Deploy screen"""
|
||||
frame = tk.Frame(self.container, bg='#1a1a1a')
|
||||
self.screens['deploy'] = frame
|
||||
|
||||
# Title
|
||||
title = ttk.Label(frame, text="Build and Deploy", style='Title.TLabel')
|
||||
title.pack(pady=(0, 30))
|
||||
|
||||
# Instructions
|
||||
instructions = tk.Label(frame,
|
||||
text="Build and deploy Prole services to k3d cluster",
|
||||
bg='#1a1a1a',
|
||||
fg='#aaaaaa',
|
||||
font=('Helvetica', 11))
|
||||
instructions.pack(pady=(0, 20))
|
||||
|
||||
# Deploy button
|
||||
deploy_btn = tk.Button(frame,
|
||||
text="Build and Deploy",
|
||||
command=self.start_deployment,
|
||||
bg='#4a9eff',
|
||||
fg='white',
|
||||
activebackground='#3a8eef',
|
||||
font=('Helvetica', 16, 'bold'),
|
||||
padx=40,
|
||||
pady=20,
|
||||
cursor='hand2',
|
||||
relief='flat')
|
||||
deploy_btn.pack(pady=20)
|
||||
|
||||
# Progress frame
|
||||
progress_frame = tk.Frame(frame, bg='#1a1a1a')
|
||||
progress_frame.pack(fill='both', expand=True, pady=20)
|
||||
|
||||
# Progress list
|
||||
self.deploy_steps = [
|
||||
{'name': 'Check Docker is running', 'status': 'pending'},
|
||||
{'name': 'Create or restart k3d prole-service-cluster', 'status': 'pending'},
|
||||
{'name': 'Build prole-db Docker image', 'status': 'pending'},
|
||||
{'name': 'Tag Docker image for registry', 'status': 'pending'},
|
||||
{'name': 'Push image to registry', 'status': 'pending'},
|
||||
{'name': 'Import image to k3d cluster', 'status': 'pending'},
|
||||
]
|
||||
|
||||
self.deploy_widgets = {}
|
||||
for step in self.deploy_steps:
|
||||
self.create_deploy_step_widget(progress_frame, step)
|
||||
|
||||
def create_deploy_step_widget(self, parent, step):
|
||||
"""Create a widget for a deployment step"""
|
||||
step_frame = tk.Frame(parent, bg='#2a2a2a', relief='flat')
|
||||
step_frame.pack(fill='x', pady=5, padx=10)
|
||||
|
||||
# Status indicator
|
||||
status_canvas = tk.Canvas(step_frame, width=30, height=30, bg='#2a2a2a', highlightthickness=0)
|
||||
status_canvas.pack(side='left', padx=15, pady=15)
|
||||
|
||||
# Step name
|
||||
name_label = tk.Label(step_frame,
|
||||
text=step['name'],
|
||||
bg='#2a2a2a',
|
||||
fg='#ffffff',
|
||||
font=('Helvetica', 11),
|
||||
anchor='w')
|
||||
name_label.pack(side='left', fill='x', expand=True, padx=10)
|
||||
|
||||
# Status text
|
||||
status_label = tk.Label(step_frame,
|
||||
text="Pending",
|
||||
bg='#2a2a2a',
|
||||
fg='#aaaaaa',
|
||||
font=('Helvetica', 10))
|
||||
status_label.pack(side='right', padx=15)
|
||||
|
||||
self.deploy_widgets[step['name']] = {
|
||||
'canvas': status_canvas,
|
||||
'label': status_label,
|
||||
'step': step
|
||||
}
|
||||
|
||||
# Draw initial pending state
|
||||
self.update_deploy_step_status(step['name'], 'pending')
|
||||
|
||||
def update_deploy_step_status(self, step_name, status):
|
||||
"""Update the status of a deployment step"""
|
||||
widget = self.deploy_widgets[step_name]
|
||||
canvas = widget['canvas']
|
||||
label = widget['label']
|
||||
step = widget['step']
|
||||
|
||||
step['status'] = status
|
||||
canvas.delete('all')
|
||||
|
||||
if status == 'pending':
|
||||
canvas.create_oval(5, 5, 25, 25, outline='#666', width=2)
|
||||
label.configure(text="Pending", fg='#aaaaaa')
|
||||
elif status == 'running':
|
||||
canvas.create_oval(5, 5, 25, 25, outline='#ffaa00', width=2, fill='#ffaa00')
|
||||
label.configure(text="Running...", fg='#ffaa00')
|
||||
elif status == 'completed':
|
||||
canvas.create_oval(5, 5, 25, 25, outline='#28a745', width=2, fill='#28a745')
|
||||
canvas.create_text(15, 15, text='✓', fill='white', font=('Helvetica', 16, 'bold'))
|
||||
label.configure(text="Completed", fg='#28a745')
|
||||
elif status == 'error':
|
||||
canvas.create_oval(5, 5, 25, 25, outline='#dc3545', width=2, fill='#dc3545')
|
||||
canvas.create_text(15, 15, text='✗', fill='white', font=('Helvetica', 16, 'bold'))
|
||||
label.configure(text="Error", fg='#dc3545')
|
||||
|
||||
def start_deployment(self):
|
||||
"""Start the deployment process"""
|
||||
threading.Thread(target=self.run_deployment, daemon=True).start()
|
||||
|
||||
def run_deployment(self):
|
||||
"""Run the deployment steps"""
|
||||
try:
|
||||
# Step 1: Check Docker
|
||||
self.update_deploy_step_status('Check Docker is running', 'running')
|
||||
if not self.check_docker_running():
|
||||
self.update_deploy_step_status('Check Docker is running', 'error')
|
||||
messagebox.showerror("Error", "Docker is not running. Please start Docker Desktop.")
|
||||
return
|
||||
self.update_deploy_step_status('Check Docker is running', 'completed')
|
||||
|
||||
# Step 2: Create or restart k3d cluster
|
||||
self.update_deploy_step_status('Create or restart k3d prole-service-cluster', 'running')
|
||||
self.create_k3d_cluster()
|
||||
self.update_deploy_step_status('Create or restart k3d prole-service-cluster', 'completed')
|
||||
|
||||
# Step 3: Build Docker image
|
||||
self.update_deploy_step_status('Build prole-db Docker image', 'running')
|
||||
self.build_docker_image()
|
||||
self.update_deploy_step_status('Build prole-db Docker image', 'completed')
|
||||
|
||||
# Step 4: Tag image
|
||||
self.update_deploy_step_status('Tag Docker image for registry', 'running')
|
||||
self.tag_docker_image()
|
||||
self.update_deploy_step_status('Tag Docker image for registry', 'completed')
|
||||
|
||||
# Step 5: Push to registry
|
||||
self.update_deploy_step_status('Push image to registry', 'running')
|
||||
self.push_docker_image()
|
||||
self.update_deploy_step_status('Push image to registry', 'completed')
|
||||
|
||||
# Step 6: Import to k3d
|
||||
self.update_deploy_step_status('Import image to k3d cluster', 'running')
|
||||
self.import_k3d_image()
|
||||
self.update_deploy_step_status('Import image to k3d cluster', 'completed')
|
||||
|
||||
messagebox.showinfo("Success", "Deployment completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Deployment failed: {str(e)}")
|
||||
|
||||
def check_docker_running(self):
|
||||
"""Check if Docker is running"""
|
||||
try:
|
||||
result = subprocess.run(['docker', 'ps'],
|
||||
capture_output=True,
|
||||
timeout=10)
|
||||
return result.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def create_k3d_cluster(self):
|
||||
"""Create or restart k3d cluster"""
|
||||
# Check if cluster exists
|
||||
result = subprocess.run(['k3d', 'cluster', 'list'],
|
||||
capture_output=True,
|
||||
text=True)
|
||||
|
||||
cluster_exists = 'prole-service-cluster' in result.stdout
|
||||
|
||||
if cluster_exists:
|
||||
# Delete existing cluster
|
||||
subprocess.run(['k3d', 'cluster', 'delete', 'prole-service-cluster'],
|
||||
check=True)
|
||||
|
||||
# Create new cluster
|
||||
subprocess.run([
|
||||
'k3d', 'cluster', 'create', 'prole-service-cluster',
|
||||
'-a', '2',
|
||||
'--registry-create', 'k8s-prole-org-registry:k8s.prole.org:5000',
|
||||
'--timestamps'
|
||||
], check=True, cwd=PROJECT_ROOT)
|
||||
|
||||
def build_docker_image(self):
|
||||
"""Build prole-db Docker image"""
|
||||
image_tag = 'prole-db:17.5-027'
|
||||
build_cmd = ['docker', 'build', '-t', image_tag]
|
||||
|
||||
# Add platform flag for Apple Silicon (ARM64 needs amd64 for compatibility)
|
||||
build_cmd.extend(get_docker_build_platform_args())
|
||||
|
||||
build_cmd.append('.')
|
||||
|
||||
result = subprocess.run(build_cmd,
|
||||
check=True,
|
||||
cwd=PROJECT_ROOT / 'prole-db',
|
||||
capture_output=True,
|
||||
text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"Failed to build image: {result.stderr}")
|
||||
|
||||
def build_mssql_docker_image(self, image_tag='prole-mssql-db:latest'):
|
||||
"""Build mssql Docker image (with platform detection for Apple Silicon)"""
|
||||
build_cmd = ['docker', 'build', '-t', image_tag]
|
||||
|
||||
# Add platform flag for Apple Silicon (mssql requires amd64)
|
||||
build_cmd.extend(get_docker_build_platform_args())
|
||||
|
||||
build_cmd.append('.')
|
||||
|
||||
result = subprocess.run(build_cmd,
|
||||
check=True,
|
||||
cwd=PROJECT_ROOT / 'mssql',
|
||||
capture_output=True,
|
||||
text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"Failed to build mssql image: {result.stderr}")
|
||||
return result
|
||||
|
||||
def tag_docker_image(self):
|
||||
"""Tag Docker image for registry"""
|
||||
subprocess.run([
|
||||
'docker', 'tag',
|
||||
'prole-db:17.5-027',
|
||||
'localhost:5000/prole-db:17.5-027'
|
||||
], check=True, capture_output=True)
|
||||
|
||||
def push_docker_image(self):
|
||||
"""Push Docker image to registry"""
|
||||
result = subprocess.run([
|
||||
'docker', 'push',
|
||||
'localhost:5000/prole-db:17.5-027'
|
||||
], check=True, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"Failed to push image: {result.stderr}")
|
||||
|
||||
def import_k3d_image(self):
|
||||
"""Import image to k3d cluster"""
|
||||
subprocess.run([
|
||||
'k3d', 'image', 'import',
|
||||
'prole-db:17.5-027',
|
||||
'-c', 'prole-service-cluster'
|
||||
], check=True, capture_output=True)
|
||||
|
||||
def create_validate_screen(self):
|
||||
"""Create the Validate screen"""
|
||||
frame = tk.Frame(self.container, bg='#1a1a1a')
|
||||
self.screens['validate'] = frame
|
||||
|
||||
# Title
|
||||
title = ttk.Label(frame, text="Validate Deployment", style='Title.TLabel')
|
||||
title.pack(pady=(0, 20))
|
||||
|
||||
# Prometheus link
|
||||
prometheus_frame = tk.Frame(frame, bg='#1a1a1a')
|
||||
prometheus_frame.pack(pady=(0, 20))
|
||||
|
||||
prometheus_label = tk.Label(prometheus_frame,
|
||||
text="Prometheus: ",
|
||||
bg='#1a1a1a',
|
||||
fg='#aaaaaa',
|
||||
font=('Helvetica', 11))
|
||||
prometheus_label.pack(side='left')
|
||||
|
||||
prometheus_link = tk.Label(prometheus_frame,
|
||||
text="http://localhost:9090",
|
||||
bg='#1a1a1a',
|
||||
fg='#4a9eff',
|
||||
font=('Helvetica', 11, 'underline'),
|
||||
cursor='hand2')
|
||||
prometheus_link.pack(side='left')
|
||||
prometheus_link.bind('<Button-1>', lambda e: webbrowser.open('http://localhost:9090'))
|
||||
|
||||
# Status display
|
||||
status_frame = tk.Frame(frame, bg='#1a1a1a')
|
||||
status_frame.pack(fill='both', expand=True, pady=10)
|
||||
|
||||
status_label = ttk.Label(status_frame, text="Cluster Status", style='Heading.TLabel')
|
||||
status_label.pack(anchor='w', pady=(0, 10))
|
||||
|
||||
# Status text area
|
||||
self.status_text = scrolledtext.ScrolledText(status_frame,
|
||||
bg='#0a0a0a',
|
||||
fg='#4a9eff',
|
||||
font=('Courier', 10),
|
||||
wrap='word',
|
||||
relief='flat',
|
||||
bd=1)
|
||||
self.status_text.pack(fill='both', expand=True)
|
||||
|
||||
# Auto-refresh checkbox
|
||||
refresh_frame = tk.Frame(frame, bg='#1a1a1a')
|
||||
refresh_frame.pack(pady=10)
|
||||
|
||||
self.auto_refresh_var = tk.BooleanVar(value=True)
|
||||
refresh_check = tk.Checkbutton(refresh_frame,
|
||||
text="Auto-refresh every 10 seconds",
|
||||
variable=self.auto_refresh_var,
|
||||
bg='#1a1a1a',
|
||||
fg='#aaaaaa',
|
||||
selectcolor='#2a2a2a',
|
||||
activebackground='#1a1a1a',
|
||||
activeforeground='#aaaaaa',
|
||||
font=('Helvetica', 10),
|
||||
command=self.toggle_auto_refresh)
|
||||
refresh_check.pack(side='left', padx=10)
|
||||
|
||||
# Manual refresh button
|
||||
refresh_btn = tk.Button(refresh_frame,
|
||||
text="Refresh Now",
|
||||
command=self.refresh_status,
|
||||
bg='#4a9eff',
|
||||
fg='white',
|
||||
activebackground='#3a8eef',
|
||||
font=('Helvetica', 10),
|
||||
padx=15,
|
||||
pady=5,
|
||||
cursor='hand2',
|
||||
relief='flat')
|
||||
refresh_btn.pack(side='left', padx=10)
|
||||
|
||||
# Start auto-refresh
|
||||
self.refresh_status()
|
||||
self.toggle_auto_refresh()
|
||||
|
||||
def toggle_auto_refresh(self):
|
||||
"""Toggle auto-refresh"""
|
||||
if self.auto_refresh_var.get():
|
||||
if not self.validation_running:
|
||||
self.validation_running = True
|
||||
self.validation_thread = threading.Thread(target=self.auto_refresh_loop, daemon=True)
|
||||
self.validation_thread.start()
|
||||
else:
|
||||
self.validation_running = False
|
||||
|
||||
def auto_refresh_loop(self):
|
||||
"""Auto-refresh loop"""
|
||||
while self.validation_running:
|
||||
time.sleep(10)
|
||||
if self.validation_running:
|
||||
self.root.after(0, self.refresh_status)
|
||||
|
||||
def refresh_status(self):
|
||||
"""Refresh the cluster status"""
|
||||
def update():
|
||||
try:
|
||||
full_status = ""
|
||||
|
||||
# Get k3d cluster list
|
||||
try:
|
||||
cluster_result = subprocess.run(['k3d', 'cluster', 'list'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5)
|
||||
full_status += f"=== k3d Cluster Status ===\n{cluster_result.stdout}\n\n"
|
||||
except Exception as e:
|
||||
full_status += f"=== k3d Cluster Status ===\nError: {str(e)}\n\n"
|
||||
|
||||
# Get kubectl cnpg status
|
||||
try:
|
||||
result = subprocess.run(['kubectl', 'cnpg', 'status', 'prole-db'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10)
|
||||
if result.returncode == 0:
|
||||
status_output = result.stdout
|
||||
else:
|
||||
status_output = f"Error: {result.stderr}\n\nNote: Make sure kubectl cnpg plugin is installed:\n kubectl krew install cnpg"
|
||||
|
||||
full_status += f"=== CloudNativePG Status ===\n{status_output}\n"
|
||||
except FileNotFoundError:
|
||||
full_status += "=== CloudNativePG Status ===\nError: kubectl not found. Please install dependencies first.\n"
|
||||
except subprocess.TimeoutExpired:
|
||||
full_status += "=== CloudNativePG Status ===\nError: Command timed out\n"
|
||||
except Exception as e:
|
||||
full_status += f"=== CloudNativePG Status ===\nError: {str(e)}\n"
|
||||
|
||||
full_status += f"\nLast updated: {time.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
|
||||
self.status_text.delete('1.0', tk.END)
|
||||
self.status_text.insert('1.0', full_status)
|
||||
|
||||
except Exception as e:
|
||||
self.status_text.delete('1.0', tk.END)
|
||||
self.status_text.insert('1.0', f"Error: {str(e)}")
|
||||
|
||||
threading.Thread(target=update, daemon=True).start()
|
||||
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = ProleInstaller(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
49
k3s/create_k3d_join_remote_agents.sh
Normal file
49
k3s/create_k3d_join_remote_agents.sh
Normal file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Create a local k3d cluster with only agents that join a remote K3s server.
|
||||
# This uses k3s args: --server and --token applied to all agent nodes.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${K3D_CLUSTER_NAME:=prole-remote-agents}"
|
||||
: "${K3D_AGENTS:=2}"
|
||||
: "${K3D_IMAGE:=}" # e.g. rancher/k3s:v1.30.6-k3s1
|
||||
: "${K3D_REGISTRY_CONFIG:=}" # optional path to registries.yaml
|
||||
|
||||
# Remote server details
|
||||
: "${K3S_REMOTE_SERVER_HOST:=raspberry.prole.org}"
|
||||
: "${K3S_REMOTE_SERVER_PORT:=6443}"
|
||||
: "${K3S_REMOTE_TOKEN:=}" # required
|
||||
|
||||
if [[ -z "${K3S_REMOTE_TOKEN}" ]]; then
|
||||
echo "[error] K3S_REMOTE_TOKEN is required. Obtain it using k3s/get_remote_k3s_node_token.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVER_URL="https://${K3S_REMOTE_SERVER_HOST}:${K3S_REMOTE_SERVER_PORT}"
|
||||
|
||||
K3D_ARGS=(cluster create "${K3D_CLUSTER_NAME}" \
|
||||
--servers 0 \
|
||||
--agents "${K3D_AGENTS}" \
|
||||
--k3s-arg "--server=${SERVER_URL}@agent:*" \
|
||||
--k3s-arg "--token=${K3S_REMOTE_TOKEN}@agent:*"
|
||||
)
|
||||
|
||||
if [[ -n "${K3D_IMAGE}" ]]; then
|
||||
K3D_ARGS+=(--image "${K3D_IMAGE}")
|
||||
fi
|
||||
|
||||
if [[ -n "${K3D_REGISTRY_CONFIG}" ]]; then
|
||||
if [[ ! -f "${K3D_REGISTRY_CONFIG}" ]]; then
|
||||
echo "[error] Registry config not found: ${K3D_REGISTRY_CONFIG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
K3D_ARGS+=(--registry-config "${K3D_REGISTRY_CONFIG}")
|
||||
fi
|
||||
|
||||
echo "[info] Creating k3d agents-only cluster '${K3D_CLUSTER_NAME}' joining ${SERVER_URL}"
|
||||
set -x
|
||||
k3d "${K3D_ARGS[@]}"
|
||||
set +x
|
||||
|
||||
echo "[ok] k3d cluster '${K3D_CLUSTER_NAME}' created with ${K3D_AGENTS} agent(s) joining ${SERVER_URL}"
|
||||
20
k3s/get_remote_k3s_node_token.sh
Normal file
20
k3s/get_remote_k3s_node_token.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fetch the K3s cluster node-token from the first server via SSH.
|
||||
# Default server: raspberry.prole.org
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${K3S_SERVER_HOST:=raspberry.prole.org}"
|
||||
: "${K3S_SERVER_USER:=pi}"
|
||||
: "${K3S_SSH_KEY:=$HOME/.ssh/id_rsa}"
|
||||
|
||||
echo "[info] Retrieving node-token from ${K3S_SERVER_USER}@${K3S_SERVER_HOST}"
|
||||
|
||||
CMD='sudo cat /var/lib/rancher/k3s/server/node-token'
|
||||
|
||||
set -x
|
||||
TOKEN=$(ssh -i "${K3S_SSH_KEY}" -o StrictHostKeyChecking=no "${K3S_SERVER_USER}@${K3S_SERVER_HOST}" "${CMD}")
|
||||
set +x
|
||||
|
||||
echo "${TOKEN}"
|
||||
46
k3s/install_k3s_first_server.sh
Normal file
46
k3s/install_k3s_first_server.sh
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Bootstrap the first K3s server (embedded etcd) on raspberry.prole.org using k3sup.
|
||||
# This script is idempotent: re-running will upgrade/ensure k3s is present.
|
||||
# Requirements:
|
||||
# - k3sup installed locally (https://github.com/alexellis/k3sup)
|
||||
# - SSH access to the target host
|
||||
# - The target host must have ports 6443/tcp, 10250/tcp open locally
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configurable via env vars; provide sensible defaults
|
||||
: "${K3S_FIRST_SERVER_HOST:=raspberry.prole.org}"
|
||||
: "${K3S_FIRST_SERVER_USER:=pi}"
|
||||
: "${K3S_SSH_KEY:=$HOME/.ssh/id_rsa}"
|
||||
: "${K3S_VERSION:=}"
|
||||
# Extra args to pass directly to k3s server
|
||||
K3S_EXTRA_ARGS_DEFAULT="--cluster-init"
|
||||
: "${K3S_EXTRA_ARGS:=${K3S_EXTRA_ARGS_DEFAULT}}"
|
||||
|
||||
echo "[info] Installing first K3s server on ${K3S_FIRST_SERVER_HOST} (user=${K3S_FIRST_SERVER_USER})"
|
||||
|
||||
if ! command -v k3sup >/dev/null 2>&1; then
|
||||
echo "[error] k3sup is not installed. See https://github.com/alexellis/k3sup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
K3SUP_ARGS=(install \
|
||||
--ip "${K3S_FIRST_SERVER_HOST}" \
|
||||
--user "${K3S_FIRST_SERVER_USER}" \
|
||||
--ssh-key "${K3S_SSH_KEY}" \
|
||||
--k3s-extra-args "${K3S_EXTRA_ARGS}"
|
||||
)
|
||||
|
||||
if [[ -n "${K3S_VERSION}" ]]; then
|
||||
K3SUP_ARGS+=(--k3s-version "${K3S_VERSION}")
|
||||
fi
|
||||
|
||||
# Create/merge kubeconfig in ~/.kube/config with context named after host
|
||||
K3SUP_ARGS+=(--local-path "$HOME/.kube/config" --context "k3s-${K3S_FIRST_SERVER_HOST}")
|
||||
|
||||
set -x
|
||||
k3sup "${K3SUP_ARGS[@]}"
|
||||
set +x
|
||||
|
||||
echo "[ok] First K3s server installed with embedded etcd and context 'k3s-${K3S_FIRST_SERVER_HOST}'"
|
||||
45
k3s/join_k3s_additional_server.sh
Normal file
45
k3s/join_k3s_additional_server.sh
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Join an additional K3s server (pi.prole.org) to the existing cluster using k3sup.
|
||||
# Default topology: HA embedded etcd with at least two servers.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${K3S_EXISTING_SERVER_HOST:=raspberry.prole.org}"
|
||||
: "${K3S_EXISTING_SERVER_USER:=pi}"
|
||||
: "${K3S_ADDITIONAL_SERVER_HOST:=pi.prole.org}"
|
||||
: "${K3S_ADDITIONAL_SERVER_USER:=pi}"
|
||||
: "${K3S_SSH_KEY:=$HOME/.ssh/id_rsa}"
|
||||
: "${K3S_VERSION:=}"
|
||||
|
||||
# You likely want your public name(s) here so clients can connect via hostname
|
||||
# You may include multiple --tls-san by space-separating them.
|
||||
K3S_EXTRA_ARGS_DEFAULT="--tls-san ${K3S_EXISTING_SERVER_HOST} --tls-san ${K3S_ADDITIONAL_SERVER_HOST}"
|
||||
: "${K3S_EXTRA_ARGS:=${K3S_EXTRA_ARGS_DEFAULT}}"
|
||||
|
||||
echo "[info] Joining ${K3S_ADDITIONAL_SERVER_HOST} as additional K3s server to cluster at ${K3S_EXISTING_SERVER_HOST}"
|
||||
|
||||
if ! command -v k3sup >/dev/null 2>&1; then
|
||||
echo "[error] k3sup is not installed. See https://github.com/alexellis/k3sup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
K3SUP_ARGS=(join \
|
||||
--server \
|
||||
--ip "${K3S_ADDITIONAL_SERVER_HOST}" \
|
||||
--user "${K3S_ADDITIONAL_SERVER_USER}" \
|
||||
--ssh-key "${K3S_SSH_KEY}" \
|
||||
--server-ip "${K3S_EXISTING_SERVER_HOST}" \
|
||||
--server-user "${K3S_EXISTING_SERVER_USER}" \
|
||||
--k3s-extra-args "${K3S_EXTRA_ARGS}"
|
||||
)
|
||||
|
||||
if [[ -n "${K3S_VERSION}" ]]; then
|
||||
K3SUP_ARGS+=(--k3s-version "${K3S_VERSION}")
|
||||
fi
|
||||
|
||||
set -x
|
||||
k3sup "${K3SUP_ARGS[@]}"
|
||||
set +x
|
||||
|
||||
echo "[ok] ${K3S_ADDITIONAL_SERVER_HOST} joined as an additional K3s server"
|
||||
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PROLE_HOME=/Users/chrisfu/dev/prole
|
||||
PROLE_PASSWD=`cat $PROLE_HOME/prole-db/.prole_user_password | base64 -d`
|
||||
k3d cluster create prole-data-cluster \
|
||||
--k3s-arg "--datastore-endpoint=mysql://prole:$PROLE_PASSWD\@tcp(10.0.0.203:3306)/k3s@server:*"
|
||||
11
mssql/prole-mssql-db-iscsi-data001.yaml
Normal file
11
mssql/prole-mssql-db-iscsi-data001.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: prole-mssql-db-data001
|
||||
spec:
|
||||
storageClassName: synology-iscsi-storage
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
31
mssql/prole-mssql-db-nfs-deployment.yaml
Normal file
31
mssql/prole-mssql-db-nfs-deployment.yaml
Normal file
@ -0,0 +1,31 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: prole-mssql-db
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
service: db
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
service: db
|
||||
spec:
|
||||
containers:
|
||||
- name: prole-mssql-db
|
||||
image: prole-mssql-db:015
|
||||
ports:
|
||||
- containerPort: 1433
|
||||
volumeMounts:
|
||||
- mountPath: "/var/opt/mssql/data"
|
||||
name: prole-db-mssql-data
|
||||
env:
|
||||
- name: ACCEPT_EULA
|
||||
value: 'Y'
|
||||
- name: MSSQL_SA_PASSWORD
|
||||
value: 'QGFYaC3!pqWy'
|
||||
volumes:
|
||||
- name: prole-db-mssql-data
|
||||
persistentVolumeClaim:
|
||||
claimName: prole-mssql-db-nfs-data001
|
||||
BIN
www/images/prole-type.gif
Normal file
BIN
www/images/prole-type.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 599 B |
78
www/index.html
Normal file
78
www/index.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="img-no-display"><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
|
||||
<title>prole.org</title>
|
||||
<style>
|
||||
html {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
div#outer {
|
||||
display: table;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
div#inner {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
div#container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 800px;
|
||||
min-height: 580px;
|
||||
}
|
||||
img {
|
||||
width: 500px;
|
||||
height: 330px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
p#header {
|
||||
font-family: Roboto-Medium;
|
||||
font-size: 28px;
|
||||
color: #323C46;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
p#paragraph {
|
||||
font-family: Roboto-Regular;
|
||||
font-size: 13px;
|
||||
color: #323C46;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link href="../help.css" type="text/css" rel="stylesheet" />
|
||||
<link href="../scrollbar/flexcroll.css" type="text/css" rel="stylesheet" />
|
||||
<script type="text/javascript" src="../scrollbar/flexcroll.js"></script>
|
||||
<script type="text/javascript" src="../scrollbar/initFlexcroll.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="outer">
|
||||
<div id="inner">
|
||||
<div id="container">
|
||||
<div>
|
||||
<p align=center>
|
||||
<img src="images/prole-type.gif" border=0>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
759
www/installer.html
Normal file
759
www/installer.html
Normal file
@ -0,0 +1,759 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Prole Service Dependencies Installer</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#background-image {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('images/prole-type.gif');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.15;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.installer-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(26, 26, 26, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.installer-header {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.installer-header h1 {
|
||||
font-size: 32px;
|
||||
color: #4a9eff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.installer-header p {
|
||||
color: #aaa;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.installer-content {
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: none;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checklist-item {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.checklist-item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.checklist-item-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.checklist-item-description {
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.sub-checklist {
|
||||
margin-top: 15px;
|
||||
margin-left: 30px;
|
||||
padding-left: 20px;
|
||||
border-left: 2px solid #444;
|
||||
}
|
||||
|
||||
.sub-checklist-item {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #4a9eff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkmark.installed {
|
||||
background: #4a9eff;
|
||||
border-color: #4a9eff;
|
||||
}
|
||||
|
||||
.checkmark.installed::after {
|
||||
content: '✓';
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.checkmark.not-installed {
|
||||
background: transparent;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.checkmark.not-installed::after {
|
||||
content: '✗';
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #4a9eff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #3a8eef;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #555;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.btn-download {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-download:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.btn-update {
|
||||
background: #ffc107;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.btn-update:hover {
|
||||
background: #e0a800;
|
||||
}
|
||||
|
||||
.command-display {
|
||||
background: #0a0a0a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: #4a9eff;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.command-display code {
|
||||
color: #4a9eff;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.installer-footer {
|
||||
padding: 20px 40px;
|
||||
border-top: 2px solid #333;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 12px 30px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
height: 500px;
|
||||
background: rgba(10, 10, 10, 0.95);
|
||||
border: 2px solid #4a9eff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.terminal-window.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.terminal-window::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('images/prole-type.gif');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
opacity: 0.2;
|
||||
z-index: -1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
padding: 15px;
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
border-bottom: 1px solid #4a9eff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.terminal-title {
|
||||
color: #4a9eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.terminal-close {
|
||||
background: #ff4444;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.terminal-body {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.terminal-prompt {
|
||||
color: #4a9eff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #4a9eff;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.terminal-output {
|
||||
color: #aaa;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.cluster-status {
|
||||
background: #0a0a0a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: #4a9eff;
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cluster-status pre {
|
||||
margin: 0;
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="background-image"></div>
|
||||
|
||||
<div class="installer-container">
|
||||
<div class="installer-header">
|
||||
<h1>Prole Service Dependencies Installer</h1>
|
||||
<p>Install and configure required dependencies for Prole services</p>
|
||||
</div>
|
||||
|
||||
<div class="installer-content">
|
||||
<!-- Step 1: Dependency Installation -->
|
||||
<div class="step active" id="step1">
|
||||
<h2 style="margin-bottom: 30px; color: #4a9eff;">Step 1: Install Dependencies</h2>
|
||||
|
||||
<!-- Docker Check -->
|
||||
<div class="checklist-item">
|
||||
<div class="checklist-item-content">
|
||||
<div class="checklist-item-title">
|
||||
<span>Docker</span>
|
||||
</div>
|
||||
<div class="checklist-item-description">
|
||||
Container platform required for running Prole services
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator">
|
||||
<div class="checkmark not-installed" id="docker-check"></div>
|
||||
<a href="https://www.docker.com/products/docker-desktop" target="_blank" class="btn btn-download">Click here to download</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Homebrew Check -->
|
||||
<div class="checklist-item">
|
||||
<div class="checklist-item-content">
|
||||
<div class="checklist-item-title">
|
||||
<span>Homebrew</span>
|
||||
</div>
|
||||
<div class="checklist-item-description">
|
||||
Package manager for macOS (required for k3d installation)
|
||||
</div>
|
||||
<div class="sub-checklist">
|
||||
<div class="sub-checklist-item">
|
||||
<div class="checklist-item-content">
|
||||
<div class="checklist-item-title" style="font-size: 16px;">
|
||||
<span>Homebrew Installed & Current</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator">
|
||||
<div class="checkmark not-installed" id="homebrew-check"></div>
|
||||
<button class="btn btn-update" id="update-homebrew-btn">Update Homebrew</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- k3d Check -->
|
||||
<div class="checklist-item">
|
||||
<div class="checklist-item-content">
|
||||
<div class="checklist-item-title">
|
||||
<span>k3d</span>
|
||||
</div>
|
||||
<div class="checklist-item-description">
|
||||
Lightweight wrapper to run k3s in Docker (installed via Homebrew)
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator">
|
||||
<div class="checkmark not-installed" id="k3d-check"></div>
|
||||
<a href="https://k3d.io/" target="_blank" class="btn btn-download">Click here to download</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Command Display -->
|
||||
<div class="command-display">
|
||||
<strong style="color: #fff; margin-bottom: 10px; display: block;">Commands to execute:</strong>
|
||||
<code id="install-commands"># Commands will appear here after checking dependencies</code>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 30px;">
|
||||
<button class="btn btn-primary btn-large" id="install-btn">Install</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Create Home Cluster -->
|
||||
<div class="step" id="step2">
|
||||
<h2 style="margin-bottom: 30px; color: #4a9eff;">Step 2: Create Home Cluster</h2>
|
||||
|
||||
<!-- Docker Running Check -->
|
||||
<div class="checklist-item">
|
||||
<div class="checklist-item-content">
|
||||
<div class="checklist-item-title">
|
||||
<span>Docker Installed and Running</span>
|
||||
</div>
|
||||
<div class="checklist-item-description">
|
||||
Verify Docker is installed and the daemon is running
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator">
|
||||
<div class="checkmark not-installed" id="docker-running-check"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- k3d Ready Check -->
|
||||
<div class="checklist-item">
|
||||
<div class="checklist-item-content">
|
||||
<div class="checklist-item-title">
|
||||
<span>k3d Command and Dependencies Ready</span>
|
||||
</div>
|
||||
<div class="checklist-item-description">
|
||||
Verify k3d and all related dependencies are installed and ready
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator">
|
||||
<div class="checkmark not-installed" id="k3d-ready-check"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- k3d Cluster List -->
|
||||
<div class="cluster-status">
|
||||
<strong style="color: #fff; margin-bottom: 10px; display: block;">k3d cluster list output:</strong>
|
||||
<pre id="cluster-list-output" class="status-text">Running k3d cluster list...</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="installer-footer">
|
||||
<button class="btn btn-secondary" id="prev-btn" style="display: none;">Previous</button>
|
||||
<button class="btn btn-primary btn-large" id="next-btn">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terminal Window -->
|
||||
<div class="terminal-window" id="terminal-window">
|
||||
<div class="terminal-header">
|
||||
<div class="terminal-title">Prole Terminal</div>
|
||||
<button class="terminal-close" id="terminal-close">×</button>
|
||||
</div>
|
||||
<div class="terminal-body" id="terminal-body">
|
||||
<div class="terminal-prompt">$ what is my name?</div>
|
||||
<div class="terminal-output" id="terminal-output"></div>
|
||||
<div class="terminal-prompt" id="terminal-input-line" style="display: none;">
|
||||
$ <input type="text" class="terminal-input" id="terminal-input" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentStep = 1;
|
||||
let userName = '';
|
||||
let terminalActive = false;
|
||||
|
||||
// Check dependencies on load
|
||||
async function checkDependencies() {
|
||||
// Simulate checking dependencies
|
||||
// In a real implementation, this would call a backend API
|
||||
|
||||
// Check Docker
|
||||
try {
|
||||
const dockerCheck = await checkCommand('docker --version');
|
||||
updateCheckmark('docker-check', dockerCheck);
|
||||
} catch (e) {
|
||||
updateCheckmark('docker-check', false);
|
||||
}
|
||||
|
||||
// Check Homebrew
|
||||
try {
|
||||
const brewCheck = await checkCommand('brew --version');
|
||||
updateCheckmark('homebrew-check', brewCheck);
|
||||
} catch (e) {
|
||||
updateCheckmark('homebrew-check', false);
|
||||
}
|
||||
|
||||
// Check k3d
|
||||
try {
|
||||
const k3dCheck = await checkCommand('k3d --version');
|
||||
updateCheckmark('k3d-check', k3dCheck);
|
||||
} catch (e) {
|
||||
updateCheckmark('k3d-check', false);
|
||||
}
|
||||
|
||||
updateInstallCommands();
|
||||
}
|
||||
|
||||
// Simulate command checking (in real app, this would use a backend)
|
||||
async function checkCommand(command) {
|
||||
// This is a simulation - in a real app, you'd call a backend API
|
||||
// For now, we'll simulate based on common scenarios
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// Simulate: docker and brew are often installed, k3d less so
|
||||
if (command.includes('docker')) {
|
||||
resolve(Math.random() > 0.3); // 70% chance installed
|
||||
} else if (command.includes('brew')) {
|
||||
resolve(Math.random() > 0.2); // 80% chance installed
|
||||
} else if (command.includes('k3d')) {
|
||||
resolve(Math.random() > 0.7); // 30% chance installed
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCheckmark(id, installed) {
|
||||
const checkmark = document.getElementById(id);
|
||||
if (installed) {
|
||||
checkmark.classList.remove('not-installed');
|
||||
checkmark.classList.add('installed');
|
||||
} else {
|
||||
checkmark.classList.remove('installed');
|
||||
checkmark.classList.add('not-installed');
|
||||
}
|
||||
}
|
||||
|
||||
function updateInstallCommands() {
|
||||
const dockerInstalled = document.getElementById('docker-check').classList.contains('installed');
|
||||
const brewInstalled = document.getElementById('homebrew-check').classList.contains('installed');
|
||||
const k3dInstalled = document.getElementById('k3d-check').classList.contains('installed');
|
||||
|
||||
let commands = [];
|
||||
|
||||
if (!brewInstalled) {
|
||||
commands.push('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
|
||||
} else {
|
||||
commands.push('brew update');
|
||||
commands.push('brew upgrade');
|
||||
}
|
||||
|
||||
if (!k3dInstalled && brewInstalled) {
|
||||
commands.push('brew install k3d');
|
||||
}
|
||||
|
||||
const commandsDisplay = document.getElementById('install-commands');
|
||||
if (commands.length > 0) {
|
||||
commandsDisplay.textContent = commands.join('\n');
|
||||
} else {
|
||||
commandsDisplay.textContent = '# All dependencies are already installed!';
|
||||
}
|
||||
}
|
||||
|
||||
// Update Homebrew button
|
||||
document.getElementById('update-homebrew-btn').addEventListener('click', () => {
|
||||
const commandsDisplay = document.getElementById('install-commands');
|
||||
commandsDisplay.textContent = 'brew update\nbrew upgrade';
|
||||
alert('Click the Install button to update Homebrew');
|
||||
});
|
||||
|
||||
// Install button
|
||||
document.getElementById('install-btn').addEventListener('click', () => {
|
||||
const commands = document.getElementById('install-commands').textContent;
|
||||
if (commands.includes('All dependencies')) {
|
||||
alert('All dependencies are already installed!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show commands that will be executed
|
||||
const confirmed = confirm(`The following commands will be executed:\n\n${commands}\n\nProceed?`);
|
||||
if (confirmed) {
|
||||
// In a real app, this would execute the commands via a backend
|
||||
alert('Installation commands executed! (This is a simulation - in production, commands would run via backend)');
|
||||
|
||||
// Simulate installation success
|
||||
setTimeout(() => {
|
||||
checkDependencies();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
// Next button
|
||||
document.getElementById('next-btn').addEventListener('click', () => {
|
||||
if (currentStep === 1) {
|
||||
// Move to step 2
|
||||
document.getElementById('step1').classList.remove('active');
|
||||
document.getElementById('step2').classList.add('active');
|
||||
document.getElementById('prev-btn').style.display = 'block';
|
||||
currentStep = 2;
|
||||
|
||||
// Check cluster status
|
||||
checkClusterStatus();
|
||||
} else if (currentStep === 2) {
|
||||
// Open terminal window
|
||||
openTerminal();
|
||||
}
|
||||
});
|
||||
|
||||
// Previous button
|
||||
document.getElementById('prev-btn').addEventListener('click', () => {
|
||||
if (currentStep === 2) {
|
||||
document.getElementById('step2').classList.remove('active');
|
||||
document.getElementById('step1').classList.add('active');
|
||||
document.getElementById('prev-btn').style.display = 'none';
|
||||
currentStep = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Check cluster status
|
||||
async function checkClusterStatus() {
|
||||
// Check Docker running
|
||||
try {
|
||||
const dockerRunning = await checkCommand('docker ps');
|
||||
updateCheckmark('docker-running-check', dockerRunning);
|
||||
} catch (e) {
|
||||
updateCheckmark('docker-running-check', false);
|
||||
}
|
||||
|
||||
// Check k3d ready
|
||||
try {
|
||||
const k3dReady = await checkCommand('k3d --version');
|
||||
updateCheckmark('k3d-ready-check', k3dReady);
|
||||
} catch (e) {
|
||||
updateCheckmark('k3d-ready-check', false);
|
||||
}
|
||||
|
||||
// Run k3d cluster list
|
||||
const clusterOutput = document.getElementById('cluster-list-output');
|
||||
clusterOutput.textContent = 'Running: k3d cluster list\n\n';
|
||||
|
||||
// Simulate k3d cluster list output
|
||||
setTimeout(() => {
|
||||
const output = `$ k3d cluster list
|
||||
|
||||
NAME CLUSTER SERVERS AGENTS LOADBALANCER
|
||||
prole-data-cluster running 1 0 true
|
||||
|
||||
Clusters found: 1`;
|
||||
clusterOutput.textContent = output;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Terminal functions
|
||||
function openTerminal() {
|
||||
const terminal = document.getElementById('terminal-window');
|
||||
terminal.classList.add('active');
|
||||
terminalActive = true;
|
||||
|
||||
// Start the prompt sequence
|
||||
setTimeout(() => {
|
||||
const inputLine = document.getElementById('terminal-input-line');
|
||||
inputLine.style.display = 'block';
|
||||
document.getElementById('terminal-input').focus();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
document.getElementById('terminal-close').addEventListener('click', () => {
|
||||
document.getElementById('terminal-window').classList.remove('active');
|
||||
terminalActive = false;
|
||||
userName = '';
|
||||
document.getElementById('terminal-output').innerHTML = '';
|
||||
document.getElementById('terminal-input-line').style.display = 'none';
|
||||
document.getElementById('terminal-input').value = '';
|
||||
});
|
||||
|
||||
document.getElementById('terminal-input').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const input = document.getElementById('terminal-input');
|
||||
const value = input.value.trim();
|
||||
const output = document.getElementById('terminal-output');
|
||||
|
||||
if (!userName) {
|
||||
// First question: "what is my name?"
|
||||
userName = value || 'Prole';
|
||||
output.innerHTML = `<div style="color: #4a9eff;">$ ${value}</div>`;
|
||||
output.innerHTML += `<div style="color: #aaa; margin-top: 10px;">Hi, I'm ${userName}. What should I call you?</div>`;
|
||||
input.value = '';
|
||||
|
||||
// Update prompt
|
||||
setTimeout(() => {
|
||||
const promptLine = document.querySelector('.terminal-prompt');
|
||||
if (promptLine) {
|
||||
promptLine.textContent = `$ `;
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
// Second question: "What should I call you?"
|
||||
const userResponse = value || 'User';
|
||||
output.innerHTML += `<div style="color: #4a9eff;">$ ${value}</div>`;
|
||||
output.innerHTML += `<div style="color: #aaa; margin-top: 10px;">Nice to meet you, ${userResponse}! I'm ${userName}.</div>`;
|
||||
input.value = '';
|
||||
|
||||
// Close terminal after a moment
|
||||
setTimeout(() => {
|
||||
document.getElementById('terminal-window').classList.remove('active');
|
||||
terminalActive = false;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
checkDependencies();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user