diff --git a/.cursor/plans/persistent-ssh-keys-e1958379.plan.md b/.cursor/plans/persistent-ssh-keys-e1958379.plan.md new file mode 100644 index 0000000..4302c1f --- /dev/null +++ b/.cursor/plans/persistent-ssh-keys-e1958379.plan.md @@ -0,0 +1,110 @@ + +# Persistent SSH Keys with Simplified Authentication + +## Overview + +Simplify SSH authentication to only support password OR key (remove confusing 'both' option). Use persistent key files instead of temporary files. Add on-the-fly key generation with public key viewing/copying. + +## Implementation Steps + +### 1. Create SSH Keys Directory Structure + +- Add `data/ssh-keys/` directory for persistent SSH key files +- Name format: `server_{id}_key` and `server_{id}_key.pub` +- Set permissions: 0700 for directory, 0600 for key files +- Add to `.gitignore` to prevent committing keys + +### 2. Update Database Schema (`src/server/database.js`) + +- Change auth_type CHECK constraint: only allow 'password' or 'key' (remove 'both') +- Add `ssh_key_path` TEXT column for file path +- Add `key_generated` INTEGER (0/1) to track generated vs user-provided keys +- Migration: Convert existing 'both' auth_type to 'key' +- Update `addServer()`: Write key to persistent file, store path +- Update `updateServer()`: Handle key changes (write new, delete old) +- Update `deleteServer()`: Clean up key files + +### 3. SSH Key Generation Feature (`src/server/ssh-service.js`) + +- Add `generateKeyPair(serverId)` method using `ssh-keygen` command +- Command: `ssh-keygen -t ed25519 -f data/ssh-keys/server_{id}_key -N "" -C "pve-scripts-local"` +- Return both private and public key content +- Add `getPublicKey(keyPath)` to extract public key from private key + +### 4. Backend API Endpoints (New Files) + +- `POST /api/servers/generate-keypair` +- Generate temporary key pair (not yet saved to server) +- Return `{ privateKey, publicKey }` +- `GET /api/servers/[id]/public-key` +- Only if `key_generated === 1` +- Return `{ publicKey, serverName, serverIp }` + +### 5. Frontend: ServerForm Component + +- **Remove 'both' from auth_type dropdown** - only show Password/SSH Key +- Add "Generate Key Pair" button (visible when auth_type === 'key') +- On generate: populate SSH key field, show modal with public key +- Add PublicKeyModal component with copy-to-clipboard +- Disable manual key entry when using generated key + +### 6. Frontend: ServerList Component + +- Add "View Public Key" button between Test Connection and Edit +- Only show when `server.key_generated === true` +- Click opens PublicKeyModal with copy button +- Show instructions: "Add this to /root/.ssh/authorized_keys on your server" + +### 7. Update SSH Service (`src/server/ssh-service.js`) + +- **Remove all 'both' auth_type handling** +- Remove temp file creation from `testWithSSHKey()` +- Use persistent `ssh_key_path` from database +- Simplify `testConnection()`: only handle 'password' or 'key' + +### 8. Update SSH Execution Service (`src/server/ssh-execution-service.js`) + +- **Remove `createTempKeyFile()` method** +- **Remove all 'both' auth_type cases** +- Update `buildSSHCommand()`: use `ssh_key_path`, only handle 'password'/'key' +- Update `transferScriptsFolder()`: use `ssh_key_path` in rsync +- Update `executeCommand()`: use `ssh_key_path` +- Remove all temp file cleanup code + +### 9. Files to Create/Modify + +- `src/server/database.js` - Schema changes, persistence +- `src/server/ssh-service.js` - Key generation, remove temp files, remove 'both' +- `src/server/ssh-execution-service.js` - Use persistent keys, remove 'both' +- `src/app/api/servers/generate-keypair/route.ts` - NEW +- `src/app/api/servers/[id]/public-key/route.ts` - NEW +- `src/app/_components/ServerForm.tsx` - Generate button, remove 'both' +- `src/app/_components/ServerList.tsx` - View public key button +- `src/app/_components/PublicKeyModal.tsx` - NEW component +- `.gitignore` - Add `data/ssh-keys/` + +### 11. Migration & Initialization + +- On startup: create `data/ssh-keys/` if missing +- Existing 'both' servers: convert to 'key' auth_type +- Existing ssh_key content: migrate to persistent files on first use +- Set `key_generated = 0` for migrated servers + +## Benefits + +- Simpler auth model (no confusing 'both' option) +- Fixes "error in libcrypto" timing issues +- User-friendly key generation +- Easy public key access for setup +- Less code complexity + +### To-dos + +- [ ] Create data/ssh-keys directory structure and update .gitignore +- [ ] Add ssh_key_path column to database and implement migration +- [ ] Modify addServer/updateServer/deleteServer to handle persistent key files +- [ ] Remove temp key creation from ssh-service.js and use persistent paths +- [ ] Remove createTempKeyFile and update all methods to use persistent keys +- [ ] Test with existing servers that have SSH keys to ensure migration works +- [ ] Modify the HelpModal to reflect the changes Made to the SSH auth +- [ ] Create a fix branch \ No newline at end of file diff --git a/.gitignore b/.gitignore index 82687ac..d4a2f72 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ db.sqlite data/settings.db +# ssh keys (sensitive) +data/ssh-keys/ + # next.js /.next/ /out/ diff --git a/scripts/ct/debian.sh b/scripts/ct/debian.sh new file mode 100644 index 0000000..54b1748 --- /dev/null +++ b/scripts/ct/debian.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(dirname "$0")" +source "$SCRIPT_DIR/../core/build.func" +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.debian.org/ + +APP="Debian" +var_tags="${var_tags:-os}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /var ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_info "Updating $APP LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated $APP LXC" + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" diff --git a/scripts/install/debian-install.sh b/scripts/install/debian-install.sh new file mode 100644 index 0000000..7b00eca --- /dev/null +++ b/scripts/install/debian-install.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.debian.org/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt -y autoremove +$STD apt -y autoclean +$STD apt -y clean +msg_ok "Cleaned" + diff --git a/src/app/_components/HelpModal.tsx b/src/app/_components/HelpModal.tsx index 1cad0ac..2a90531 100644 --- a/src/app/_components/HelpModal.tsx +++ b/src/app/_components/HelpModal.tsx @@ -55,8 +55,15 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings' +
+
SSH Key Features:
+ +
diff --git a/src/app/_components/PublicKeyModal.tsx b/src/app/_components/PublicKeyModal.tsx new file mode 100644 index 0000000..ac6d461 --- /dev/null +++ b/src/app/_components/PublicKeyModal.tsx @@ -0,0 +1,147 @@ +'use client'; + +import { useState } from 'react'; +import { X, Copy, Check, Server, Globe } from 'lucide-react'; +import { Button } from './ui/button'; + +interface PublicKeyModalProps { + isOpen: boolean; + onClose: () => void; + publicKey: string; + serverName: string; + serverIp: string; +} + +export function PublicKeyModal({ isOpen, onClose, publicKey, serverName, serverIp }: PublicKeyModalProps) { + const [copied, setCopied] = useState(false); + + if (!isOpen) return null; + + const handleCopy = async () => { + try { + // Try modern clipboard API first + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(publicKey); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } else { + // Fallback for older browsers or non-HTTPS + const textArea = document.createElement('textarea'); + textArea.value = publicKey; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (fallbackError) { + console.error('Fallback copy failed:', fallbackError); + // If all else fails, show the key in an alert + alert('Please manually copy this key:\n\n' + publicKey); + } + + document.body.removeChild(textArea); + } + } catch (error) { + console.error('Failed to copy to clipboard:', error); + // Fallback: show the key in an alert + alert('Please manually copy this key:\n\n' + publicKey); + } + }; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+

SSH Public Key

+

Add this key to your server's authorized_keys

+
+
+ +
+ + {/* Content */} +
+ {/* Server Info */} +
+
+ + {serverName} +
+
+ + {serverIp} +
+
+ + {/* Instructions */} +
+

Instructions:

+
    +
  1. Copy the public key below
  2. +
  3. SSH into your server: ssh root@{serverIp}
  4. +
  5. Add the key to authorized_keys: echo "<paste-key>" >> ~/.ssh/authorized_keys
  6. +
  7. Set proper permissions: chmod 600 ~/.ssh/authorized_keys
  8. +
+
+ + {/* Public Key */} +
+
+ + +
+