mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-03 07:28:15 +00:00
Merge pull request #4 from AnsoCode/feature/dynamic-templates
feat: Dynamic Templates & Sanitizer
This commit is contained in:
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- **Added:** Dynamic Template Registry URL support via global settings, defaulting to LinuxServer.io templates.
|
||||
- **Fixed:** Smart Volume Sanitizer to automatically rewrite messy Portainer bind mounts into clean, relative paths (Sencho 1:1 path rule).
|
||||
- Git Flow branching strategy and branch protection.
|
||||
- GitHub Actions CI pipeline for automated TypeScript build verification.
|
||||
- **Added:** Automated Docker Hub CI/CD pipeline for the `dev` and `latest` tags.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { DatabaseService } from './DatabaseService';
|
||||
|
||||
|
||||
export interface TemplateEnv {
|
||||
name: string;
|
||||
@@ -38,7 +40,6 @@ export class TemplateService {
|
||||
private cachedTemplates: Template[] = [];
|
||||
private lastFetchTime: number = 0;
|
||||
private readonly CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
private readonly TEMPLATES_URL = 'https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json';
|
||||
|
||||
public async getTemplates(): Promise<Template[]> {
|
||||
const now = Date.now();
|
||||
@@ -47,7 +48,11 @@ export class TemplateService {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get<TemplatesResponse>(this.TEMPLATES_URL);
|
||||
const settings = DatabaseService.getInstance().getGlobalSettings();
|
||||
// Default to a reliable LSIO Portainer v2 template registry if not set
|
||||
const registryUrl = settings.template_registry_url || 'https://raw.githubusercontent.com/technorabilia/portainer-templates/main/lsio/templates/templates-2.0.json';
|
||||
|
||||
const response = await axios.get<TemplatesResponse>(registryUrl);
|
||||
// Filter out templates without images as we are generating compose files from image, ports, etc.
|
||||
this.cachedTemplates = (response.data.templates || []).filter((t: Template) => !!t.image && t.type === 1);
|
||||
this.lastFetchTime = now;
|
||||
@@ -80,14 +85,37 @@ export class TemplateService {
|
||||
if (template.volumes && template.volumes.length > 0) {
|
||||
yaml += ` volumes:\n`;
|
||||
for (const vol of template.volumes) {
|
||||
let hostPath = '';
|
||||
let containerPath = '';
|
||||
let options = '';
|
||||
|
||||
if (typeof vol === 'string') {
|
||||
yaml += ` - ${vol}\n`;
|
||||
const parts = vol.split(':');
|
||||
if (parts.length === 1) {
|
||||
yaml += ` - ${vol}\n`;
|
||||
continue;
|
||||
}
|
||||
hostPath = parts[0];
|
||||
containerPath = parts[1];
|
||||
options = parts.slice(2).join(':');
|
||||
if (options) options = `:${options}`;
|
||||
} else if (vol.container) {
|
||||
// If bind is not provided, we extract the folder name from the container path
|
||||
const containerFolder = vol.container.split('/').pop() || 'data';
|
||||
const localBind = vol.bind ? vol.bind.replace(/^\//, './') : `./${containerFolder}`;
|
||||
yaml += ` - ${localBind}:${vol.container}${vol.readonly ? ':ro' : ''}\n`;
|
||||
containerPath = vol.container;
|
||||
const containerFolder = containerPath.split('/').filter(Boolean).pop() || 'data';
|
||||
hostPath = vol.bind ? vol.bind : `./${containerFolder}`;
|
||||
options = vol.readonly ? ':ro' : '';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hostPath.includes('portainer/Files') || hostPath.includes('/your/') || hostPath.includes('/path/to/')) {
|
||||
const containerFolder = containerPath.split('/').filter(Boolean).pop() || 'data';
|
||||
hostPath = `./${containerFolder}`;
|
||||
} else if (hostPath.startsWith('/')) {
|
||||
hostPath = hostPath.replace(/^\//, './');
|
||||
}
|
||||
|
||||
yaml += ` - ${hostPath}:${containerPath}${options}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user