Files
ProxmoxVE-Local/src/server/services/scriptDownloader.ts
Michel Roegl-Brunner 7258f7bcfd fix: Update local scripts tab to scan /scripts/ct directory
- Add getCtScripts() method to ScriptManager to scan ct subdirectory
- Add getCtScripts tRPC endpoint for local scripts tab
- Update ScriptsList component to use getCtScripts instead of getScripts
- Fix script path construction to use relative paths for terminal
- Local scripts tab now correctly shows scripts from /scripts/ct folder
2025-09-10 15:10:10 +02:00

159 lines
5.4 KiB
TypeScript

import { readFile, writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { env } from '~/env.js';
import type { Script } from '~/types/script';
export class ScriptDownloaderService {
private scriptsDirectory: string;
private repoUrl: string;
constructor() {
this.scriptsDirectory = join(process.cwd(), 'scripts');
this.repoUrl = env.REPO_URL || '';
}
private async ensureDirectoryExists(dirPath: string): Promise<void> {
try {
await mkdir(dirPath, { recursive: true });
} catch (error) {
// Directory might already exist, ignore error
}
}
private async downloadFileFromGitHub(filePath: string): Promise<string> {
if (!this.repoUrl) {
throw new Error('REPO_URL environment variable is not set');
}
const url = `https://raw.githubusercontent.com/${this.extractRepoPath()}/main/${filePath}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
}
return response.text();
}
private extractRepoPath(): string {
const match = this.repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
if (!match) {
throw new Error('Invalid GitHub repository URL');
}
return `${match[1]}/${match[2]}`;
}
private modifyScriptContent(content: string): string {
// Replace the build.func source line
const oldPattern = /source <\(curl -fsSL https:\/\/raw\.githubusercontent\.com\/community-scripts\/ProxmoxVE\/main\/misc\/build\.func\)/g;
const newPattern = 'SCRIPT_DIR="$(dirname "$0")" \nsource "$SCRIPT_DIR/../core/build.func"';
return content.replace(oldPattern, newPattern);
}
async loadScript(script: Script): Promise<{ success: boolean; message: string; files: string[] }> {
try {
const files: string[] = [];
// Ensure directories exist
await this.ensureDirectoryExists(join(this.scriptsDirectory, 'ct'));
await this.ensureDirectoryExists(join(this.scriptsDirectory, 'install'));
// Download and save CT script
if (script.install_methods && script.install_methods.length > 0) {
for (const method of script.install_methods) {
if (method.script && method.script.startsWith('ct/')) {
const scriptPath = method.script;
const fileName = scriptPath.split('/').pop();
if (fileName) {
// Download from GitHub
const content = await this.downloadFileFromGitHub(scriptPath);
// Modify the content
const modifiedContent = this.modifyScriptContent(content);
// Save to local directory
const localPath = join(this.scriptsDirectory, 'ct', fileName);
await writeFile(localPath, modifiedContent, 'utf-8');
files.push(`ct/${fileName}`);
}
}
}
}
// Download and save install script
const installScriptName = `${script.slug}-install.sh`;
try {
const installContent = await this.downloadFileFromGitHub(`install/${installScriptName}`);
const localInstallPath = join(this.scriptsDirectory, 'install', installScriptName);
await writeFile(localInstallPath, installContent, 'utf-8');
files.push(`install/${installScriptName}`);
} catch (error) {
// Install script might not exist, that's okay
console.log(`Install script not found for ${script.slug}: ${error}`);
}
return {
success: true,
message: `Successfully loaded ${files.length} script(s) for ${script.name}`,
files
};
} catch (error) {
console.error('Error loading script:', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to load script',
files: []
};
}
}
async checkScriptExists(script: Script): Promise<{ ctExists: boolean; installExists: boolean; files: string[] }> {
const files: string[] = [];
let ctExists = false;
let installExists = false;
try {
// Check CT script
if (script.install_methods && script.install_methods.length > 0) {
for (const method of script.install_methods) {
if (method.script && method.script.startsWith('ct/')) {
const fileName = method.script.split('/').pop();
if (fileName) {
const localPath = join(this.scriptsDirectory, 'ct', fileName);
try {
await readFile(localPath, 'utf-8');
ctExists = true;
files.push(`ct/${fileName}`);
} catch {
// File doesn't exist
}
}
}
}
}
// Check install script
const installScriptName = `${script.slug}-install.sh`;
const localInstallPath = join(this.scriptsDirectory, 'install', installScriptName);
try {
await readFile(localInstallPath, 'utf-8');
installExists = true;
files.push(`install/${installScriptName}`);
} catch {
// File doesn't exist
}
return { ctExists, installExists, files };
} catch (error) {
console.error('Error checking script existence:', error);
return { ctExists: false, installExists: false, files: [] };
}
}
}
// Singleton instance
export const scriptDownloaderService = new ScriptDownloaderService();