refactor: functional separation with deploy/, recipe-generator/, and bin/

- deploy/: deployment bash scripts (4 files)
- recipe-generator/: recipe generation tool (Python + YAML configs)
- bin/: executable wrappers (single generate-recipe entry point)
- Updated deploy.sh, README.md, DEVELOPMENT.md, DEVELOPMENT_QUICK.md
- Updated tests/test_recipe_generator.py and tests/integration/test_integration.py
- Updated CI workflow and Makefile paths
- All 41 tests pass
This commit is contained in:
Lukas Greve
2026-03-25 09:34:00 +01:00
parent 00c4356ff3
commit 3782777058
25 changed files with 55 additions and 51 deletions
+39
View File
@@ -0,0 +1,39 @@
.PHONY: help generate-recipes validate-recipes test test-integration test-container clean install-deps
help:
@echo "Phyllome OS Recipe Generator"
@echo ""
@echo "Available targets:"
@echo " generate-recipes - Generate all recipes from manifest"
@echo " validate-recipes - Validate existing recipes"
@echo " test - Run pytest test suite (unit + integration)"
@echo " test-integration - Run integration tests only"
@echo " test-container - Run all tests in container"
@echo " clean - Remove generated recipes"
@echo " install-deps - Install Python dependencies"
install-deps:
pip install -r requirements.txt
generate-recipes:
python generate_recipe.py \
--manifest recipes_manifest.yaml \
--output-dir ../recipes/
validate-recipes:
python generate_recipe.py \
--validate ../recipes/*.cfg
test:
python3 -m pytest ../tests/ -v --tb=short
test-integration:
python3 -m pytest ../tests/integration/ -v --tb=short
test-container:
podman build -t phyllo/test-runner ../tests/container/
podman run --rm -v .:/phyllomeos:ro phyllo/test-runner
clean:
rm -f ../recipes/*.cfg
@echo "Generated recipes removed. Edit recipes_manifest.yaml and run 'make generate-recipes' to regenerate."
+644
View File
@@ -0,0 +1,644 @@
#!/usr/bin/env python3
"""
Recipe Generator for Phyllome OS Kickstart Files
Generates .cfg recipe files from templates and YAML manifest.
"""
import argparse
import sys
import yaml
from pathlib import Path
from typing import Dict, List, Optional, Any
# Deprecated/removed command mappings for Fedora 43 (F42) and rawhide
DEPRECATED_COMMANDS: Dict[str, Dict[str, str]] = {
'authconfig': {
'status': 'removed',
'removed_in': 'F34',
'alternative': 'authselect',
'message': 'authconfig was removed in Fedora 34. Use authselect instead.'
},
'keyboard': {
'status': 'deprecated',
'deprecated_in': 'F18',
'alternative': 'keyboard --vckeymap',
'message': 'keyboard command is deprecated. Use keyboard --vckeymap instead.'
},
'langsupport': {
'status': 'deprecated',
'deprecated_in': 'F21',
'alternative': 'lang',
'message': 'langsupport is deprecated. Use lang command instead.'
},
'nfs': {
'status': 'deprecated',
'deprecated_in': 'F23',
'alternative': 'repo --name=nfs',
'message': 'nfs command is deprecated. Use repo command instead.'
},
'parted': {
'status': 'deprecated',
'deprecated_in': 'F13',
'alternative': 'part',
'message': 'parted command is deprecated. Use part command instead.'
},
}
def _import_pykickstart():
"""Import pykickstart modules, returns None if not available."""
try:
from pykickstart.parser import KickstartParser
from pykickstart.version import makeVersion
from pykickstart.version import DEVEL
from pykickstart.errors import KickstartParseError, KickstartError
return {
'parser': KickstartParser,
'makeVersion': makeVersion,
'DEVEL': DEVEL,
'KickstartParseError': KickstartParseError,
'KickstartError': KickstartError
}
except ImportError:
return None
class RecipeGenerator:
"""Generate kickstart recipes from templates and modifiers."""
def __init__(self, ingredients_dir: Path, templates_file: Path):
self.project_root = Path(__file__).parent.parent
self.ingredients_dir = self.project_root / ingredients_dir
self.templates = self.load_templates(templates_file)
def get_ksversion(self, version: str) -> Optional[str]:
"""Map Phyllome OS version to pykickstart version string."""
if version == 'rawhide':
return None
else:
return f'F{int(version) - 1}'
def load_templates(self, path: Path) -> Dict:
"""Load recipe templates from YAML file."""
try:
# Template path could be:
# - Absolute path (already resolved)
# - Relative path (resolve relative to project root)
if path.is_absolute():
template_path = path
else:
template_path = self.project_root / path
with open(template_path) as f:
data = yaml.safe_load(f)
return data['templates']
except FileNotFoundError:
print(f"Error: Templates file not found: {template_path}", file=sys.stderr)
sys.exit(2)
except yaml.YAMLError as e:
print(f"Error: Invalid YAML in {template_path}: {e}", file=sys.stderr)
sys.exit(2)
def validate_template(self, template: Dict) -> List[str]:
"""Validate template structure and fragment existence."""
errors = []
# Check required keys
required_keys = ['description', 'base', 'required']
for key in required_keys:
if key not in template:
errors.append(f"Missing required key: {key}")
# Validate base ingredient exists (for compatibility with old ingredients)
if 'base' in template:
base_path = self.ingredients_dir / f"{template['base']}.cfg"
if not base_path.exists():
errors.append(f"Base ingredient not found: {template['base']}.cfg")
# Validate required fragments exist
for item in template.get('required', []):
if isinstance(item, dict):
fragment_path = list(item.values())[0]
else:
continue
# Handle both absolute fragment paths and old ingredient names
if fragment_path.startswith('fragments/'):
full_path = self.project_root / fragment_path
else:
full_path = self.ingredients_dir / f"{fragment_path}.cfg"
if not full_path.exists():
errors.append(f"Required fragment not found: {fragment_path}")
# Validate optional fragment values
for opt_key, opt_config in template.get('optional', {}).items():
if isinstance(opt_config, dict):
for value, fragment_path in opt_config.items():
if fragment_path.startswith('fragments/'):
full_path = self.project_root / fragment_path
elif fragment_path is None:
continue
else:
full_path = self.ingredients_dir / f"{fragment_path}.cfg"
if not full_path.exists():
errors.append(f"Optional fragment not found: {fragment_path} (for {opt_key}={value})")
elif isinstance(opt_config, list):
for fragment_path in opt_config:
if fragment_path.startswith('fragments/'):
full_path = self.project_root / fragment_path
else:
full_path = self.ingredients_dir / f"{fragment_path}.cfg"
if not full_path.exists():
errors.append(f"Optional fragment not found: {fragment_path} (in list)")
return errors
def validate_manifest(self, manifest: Dict) -> List[str]:
"""Validate manifest structure."""
errors = []
if 'recipes' not in manifest:
errors.append("Manifest missing 'recipes' key")
return errors
for recipe_config in manifest['recipes']:
if 'name' not in recipe_config:
errors.append("Recipe config missing 'name' key")
if 'variants' not in recipe_config:
errors.append(f"Recipe '{recipe_config.get('name', 'unnamed')}' missing 'variants' key")
continue
for variant in recipe_config['variants']:
if 'version' not in variant:
errors.append(f"Recipe '{recipe_config['name']}' variant missing 'version'")
return errors
def generate_recipe(self, recipe_type: str, version: str, **modifiers) -> str:
"""Generate a recipe from template with modifiers."""
if recipe_type not in self.templates:
print(f"Error: Unknown recipe type: {recipe_type}", file=sys.stderr)
sys.exit(1)
template = self.templates[recipe_type]
# Validate template
errors = self.validate_template(template)
if errors:
print(f"Error: Invalid template '{recipe_type}':", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
sys.exit(1)
lines = self.build_header(template['description'], recipe_type, version, modifiers)
lines.extend(self.build_includes(template, version, modifiers))
return '\n'.join(lines)
def build_header(self, description: str, recipe_type: str,
version: str, modifiers: Dict) -> List[str]:
"""Build the ASCII art header and description."""
header = [
"# __ ____ ____ _____",
"# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \\/ ___/",
"# / __ \\/ __ \\/ / / / / / __ \\/ __ `__ \\/ _ \\ / / / /\\__ \\",
"# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ /",
"# / .___/_/ /_/\\__, /_/_/\\____/_/ /_/ /_/\\___/ \\____//____/",
"# /_/ /____/",
"",
f"# {description}",
"",
]
return header
def build_includes(self, template: Dict, version: str, modifiers: Dict) -> List[str]:
"""Build %ksappend lines from template and modifiers."""
includes = []
seen = set() # Track to prevent duplicates
# Add version to modifiers for template processing
modifiers = modifiers.copy()
modifiers['version'] = version
# Add required includes
for item in template.get('required', []):
if isinstance(item, dict):
fragment_path = list(item.values())[0]
else:
continue
if fragment_path not in seen:
includes.append(f"%ksappend {fragment_path}")
seen.add(fragment_path)
# Add optional includes based on modifiers
for opt_key, opt_config in template.get('optional', {}).items():
if opt_key in modifiers:
value = modifiers[opt_key]
if isinstance(opt_config, dict):
if value in opt_config:
fragment_path = opt_config[value]
if fragment_path not in seen:
includes.append(f"%ksappend {fragment_path}")
seen.add(fragment_path)
elif isinstance(opt_config, list) and value is True:
for fragment_path in opt_config:
if fragment_path not in seen:
includes.append(f"%ksappend {fragment_path}")
seen.add(fragment_path)
# Handle special modifiers (CPU, GPU)
for mod_key, mod_value in modifiers.items():
if mod_key in template.get('modifiers', {}):
mod_config = template['modifiers'][mod_key]
if isinstance(mod_config, dict) and mod_value in mod_config:
fragment_path = mod_config[mod_value]
if fragment_path and fragment_path not in seen:
includes.append(f"%ksappend {fragment_path}")
seen.add(fragment_path)
return includes
def validate_recipe(self, content: str) -> List[str]:
"""Validate recipe content, return list of warnings/errors."""
issues = []
includes = [line for line in content.split('\n') if line.startswith('%ksappend')]
# Check for duplicate includes
seen = set()
for inc in includes:
parts = inc.split()
if len(parts) < 2:
continue
path = parts[1]
if path in seen:
issues.append(f"Duplicate include: {path}")
seen.add(path)
# Check fragment existence (relative to project root)
for inc in includes:
parts = inc.split()
if len(parts) < 2:
continue
path = parts[1]
fragment_path = self.project_root / path
if not fragment_path.exists():
issues.append(f"Missing fragment: {path}")
return issues
def validate_recipe_semantic(self, content: str, version: str) -> List[str]:
"""Validate recipe using pykickstart parser with version-specific checks."""
issues = []
modules = _import_pykickstart()
if modules is None:
issues.append("Warning: pykickstart not installed, skipping semantic validation")
return issues
KickstartParser = modules['parser']
makeVersion = modules['makeVersion']
KickstartParseError = modules['KickstartParseError']
KickstartError = modules['KickstartError']
ks_version_str = self.get_ksversion(version)
if ks_version_str:
ks_version = makeVersion(ks_version_str)
else:
ks_version = makeVersion(modules['DEVEL'])
try:
parser = KickstartParser(ks_version)
parser.readKickstartFromString(content)
except KickstartParseError as e:
issues.append(f"Syntax error line {e.lineno}: {e.message}")
except KickstartError as e:
issues.append(f"Validation error: {str(e)}")
except Exception as e:
issues.append(f"Unexpected error during parsing: {str(e)}")
# Check for deprecated commands in the content
issues.extend(self._check_deprecated_commands(content))
return issues
def _check_deprecated_commands(self, content: str) -> List[str]:
"""Check for deprecated and removed commands with suggestions."""
issues = []
for line_num, line in enumerate(content.split('\n'), start=1):
# Skip comments and empty lines
stripped = line.strip()
if not stripped or stripped.startswith('#'):
continue
# Extract command (first word after % if in section, or just the first word)
if stripped.startswith('%'):
continue # Skip section headers
parts = stripped.split()
if not parts:
continue
cmd = parts[0]
if cmd in DEPRECATED_COMMANDS:
cmd_info = DEPRECATED_COMMANDS[cmd]
status = cmd_info['status']
msg = cmd_info['message']
if status == 'removed':
issues.append(f"ERROR: Line {line_num}: {msg}")
else:
issues.append(f"Warning: Line {line_num}: {msg}")
return issues
def extract_version(self, content: str, filename: str) -> Optional[str]:
"""Extract Fedora version from recipe content or filename."""
import re
filename_match = re.search(r'(?:_|-)(43|rawhide)(?:_|-|.cfg|.yaml|$)', filename)
if filename_match:
return filename_match.group(1)
for line in content.split('\n'):
# Check for old %include references
if 'core-fedora-repo-43' in line:
return '43'
elif 'core-fedora-repo-rawhide' in line:
return 'rawhide'
# Check for new %ksappend references
if 'generic-43/repo' in line:
return '43'
elif 'generic-rawhide/repo' in line:
return 'rawhide'
return None
def generate_filename(self, recipe_type: str, version: str, **modifiers) -> str:
"""Generate recipe filename from parameters."""
# Map modifiers to filename components
parts = [recipe_type.replace('_', '-')]
# Add CPU/GPU first (hypervisors)
if modifiers.get('cpu') and modifiers.get('cpu') != 'generic':
parts.append(modifiers['cpu'])
if modifiers.get('gpu') and modifiers.get('gpu') != 'none':
parts.append(modifiers['gpu'])
# Add desktop (non-GNOME only, since GNOME is default)
if modifiers.get('desktop') and modifiers['desktop'] != 'gnome':
parts.append(modifiers['desktop'])
# Add version
parts.append(str(version))
# Add hypervisor suffix (only when hypervisor is enabled)
if modifiers.get('hypervisor'):
parts.append('hypervisor')
# Add security suffix (devel only, since secure is default)
if modifiers.get('security') == 'devel':
parts.append('devel')
# Add storage suffix (encrypted only, since standard is default)
if modifiers.get('storage') == 'encrypted':
parts.append('encrypted')
return '_'.join(parts) + '.cfg'
def main():
parser = argparse.ArgumentParser(
description='Generate Phyllome OS kickstart recipes from templates',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# Global options
SCRIPTS_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = SCRIPTS_DIR.parent
parser.add_argument('--ingredients', '-i',
type=Path, default=PROJECT_ROOT / 'ingredients',
help='Ingredients directory (default: parent/ingredients)')
parser.add_argument('--templates', '-t',
type=Path, default=SCRIPTS_DIR / 'recipe_templates.yaml',
help='Templates YAML file (default: ./recipe_templates.yaml)')
# Batch mode
parser.add_argument('--manifest', '-m',
type=Path, help='Manifest YAML for batch generation')
parser.add_argument('--output-dir', '-d',
type=Path, default=SCRIPTS_DIR / 'recipes',
help='Output directory (batch generation, default: ./recipes)')
parser.add_argument('--dry-run', '-n',
action='store_true',
help='Show what would be generated without writing files')
# Single generation mode
parser.add_argument('--type', '-T',
help='Recipe type (e.g., virtual-desktop)')
parser.add_argument('--output', '-o',
type=Path, help='Output file (single generation)')
# Recipe parameters
parser.add_argument('--version', '-v',
choices=['43', 'rawhide'], default='rawhide',
help='Fedora version (default: rawhide)')
parser.add_argument('--desktop',
choices=['gnome', 'labwc'],
help='Desktop environment (default: gnome)')
parser.add_argument('--storage',
choices=['standard', 'encrypted'],
help='Storage type (default: standard)')
parser.add_argument('--security',
choices=['secure', 'devel'],
help='Security mode (default: secure)')
parser.add_argument('--cpu',
choices=['generic', 'amdcpu', 'intelcpu'],
help='CPU optimization')
parser.add_argument('--gpu',
choices=['none', 'intelgpu'],
default='none',
help='GPU passthrough (default: none)')
# Validation mode
parser.add_argument('--validate', '-V',
nargs='+',
help='Validate recipe files')
# Strict mode for CI
parser.add_argument('--strict',
action='store_true',
help='Treat warnings as errors (CI mode)')
args = parser.parse_args()
# Initialize generator
generator = RecipeGenerator(args.ingredients, args.templates)
# Validation mode
if args.validate:
all_issues = []
for recipe_path in args.validate:
try:
with open(recipe_path) as f:
content = f.read()
issues = generator.validate_recipe(content)
# Extract version and perform semantic validation
filename = Path(recipe_path).stem
version = generator.extract_version(content, filename)
if version:
semantic_issues = generator.validate_recipe_semantic(content, version)
issues.extend(semantic_issues)
else:
issues.append("Warning: Could not determine version, skipping semantic validation")
if issues:
all_issues.append((recipe_path, issues))
except FileNotFoundError:
print(f"Error: Recipe not found: {recipe_path}", file=sys.stderr)
sys.exit(2)
if all_issues:
print("=== Recipe Validation Report ===", file=sys.stderr)
for path, issues in all_issues:
print(f"\n{path}:", file=sys.stderr)
error_count = sum(1 for i in issues if 'ERROR' in i)
warning_count = sum(1 for i in issues if 'Warning:' in i)
if error_count > 0:
for issue in issues:
if 'ERROR' in issue:
print(f" {issue}", file=sys.stderr)
if warning_count > 0:
for issue in issues:
if 'Warning:' in issue:
print(f" {issue}", file=sys.stderr)
if error_count == 0 and warning_count == 0:
print(f" No issues found (file exists)", file=sys.stderr)
print(f"\nSummary:", file=sys.stderr)
print(f" - {len(all_issues)} recipe(s) checked", file=sys.stderr)
total_errors = sum(len([i for i in issues if 'ERROR' in i]) for _, issues in all_issues)
total_warnings = sum(len([i for i in issues if 'Warning:' in i]) for _, issues in all_issues)
print(f" - {total_errors} error(s), {total_warnings} warning(s)", file=sys.stderr)
# Strict mode: treat warnings as errors
if args.strict and total_warnings > 0:
print("\nStrict mode: Warnings treated as errors", file=sys.stderr)
sys.exit(1)
if total_errors > 0:
sys.exit(1)
else:
print("All recipes validated successfully")
sys.exit(0)
# Batch generation mode
if args.manifest:
try:
with open(args.manifest) as f:
manifest = yaml.safe_load(f)
except FileNotFoundError:
print(f"Error: Manifest file not found: {args.manifest}", file=sys.stderr)
sys.exit(2)
except yaml.YAMLError as e:
print(f"Error: Invalid YAML in manifest: {e}", file=sys.stderr)
sys.exit(2)
# Validate manifest
errors = generator.validate_manifest(manifest)
if errors:
print(f"Error: Invalid manifest:", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
sys.exit(1)
# Generate all recipes
for recipe_config in manifest.get('recipes', []):
recipe_type = recipe_config['name']
if recipe_type not in generator.templates:
print(f"Error: Unknown recipe type in manifest: {recipe_type}", file=sys.stderr)
sys.exit(1)
for variant in recipe_config.get('variants', []):
version = variant['version']
modifiers = {k: v for k, v in variant.items() if k not in ['version']}
content = generator.generate_recipe(recipe_type, version, **modifiers)
if args.validate and not args.dry_run:
issues = generator.validate_recipe(content)
semantic_issues = generator.validate_recipe_semantic(content, version)
all_issues = issues + semantic_issues
if all_issues:
print(f"Validation issues for {recipe_type} {version}:", file=sys.stderr)
for issue in issues:
print(f" - {issue}", file=sys.stderr)
sys.exit(1)
filename = generator.generate_filename(recipe_type, version, **modifiers)
output_path = args.output_dir / filename
if args.dry_run:
print(f"Would generate: {output_path}")
else:
print(f"Generating: {output_path}")
with open(output_path, 'w') as f:
f.write(content)
sys.exit(0)
# Single generation mode
if args.type:
modifiers = {
'desktop': args.desktop if args.desktop and args.desktop != 'gnome' else None,
'storage': args.storage if args.storage != 'standard' else None,
'security': args.security if args.security != 'secure' else None,
'cpu': args.cpu if args.cpu and args.cpu != 'generic' else None,
'gpu': args.gpu if args.gpu and args.gpu != 'none' else None,
}
# Filter out None/False values
modifiers = {k: v for k, v in modifiers.items() if v is not None}
content = generator.generate_recipe(args.type, args.version, **modifiers)
if args.validate:
issues = generator.validate_recipe(content)
semantic_issues = generator.validate_recipe_semantic(content, args.version)
all_issues = issues + semantic_issues
if all_issues:
print("Validation issues:", file=sys.stderr)
for issue in issues:
print(f" - {issue}", file=sys.stderr)
sys.exit(1)
else:
print("Validation passed")
if args.output:
if args.dry_run:
print(f"Would write to: {args.output}")
else:
with open(args.output, 'w') as f:
f.write(content)
print(f"Generated: {args.output}")
else:
print(content)
sys.exit(0)
# No mode specified, show help
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
@@ -0,0 +1,42 @@
import subprocess
import re
# Get all package names from your config file
packages = []
with open('core-packages-hardware-support.cfg', 'r') as f:
for line in f:
if line.strip() and not line.startswith('#') and '#' not in line:
# Extract package name (everything before the first space or #)
package = line.strip().split('#')[0].strip()
if package and not package.startswith('%'):
packages.append(package)
# Get summaries using dnf info
summaries = {}
for package in packages:
try:
result = subprocess.run(['dnf', 'info', package],
capture_output=True, text=True, timeout=30)
if result.returncode == 0:
# Extract summary line
summary_match = re.search(r'Summary\s*:\s*(.+)', result.stdout)
if summary_match:
summaries[package] = summary_match.group(1).strip()
except Exception as e:
print(f"Error getting info for {package}: {e}")
# Now you can generate your updated package list with summaries
with open('core-packages-hardware-support.cfg', 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if line.strip() and not line.startswith('#') and not line.startswith('%'):
package = line.strip().split('#')[0].strip()
if package in summaries:
line = f"{package} # {summaries[package]}\n"
new_lines.append(line)
# Write back to file (or save as new file)
with open('updated-packages.cfg', 'w') as f:
f.writelines(new_lines)
+186
View File
@@ -0,0 +1,186 @@
# Recipe Templates for Phyllome OS Kickstart Generator
# Each template defines the structure for a recipe type
# Modifiers allow variant creation without duplicating files
# Fragment paths use relative paths from project root
templates:
# Virtual desktop recipe
# Modifiers: desktop (gnome|labwc), storage (standard|encrypted),
# security (secure|devel), version (43|rawhide), post (true|false)
virtual-desktop:
description: "A recipe for a virtual desktop"
base: core
required:
- core: fragments/shared/core/base.ks
- storage: fragments/shared/storage/standard.ks
- bootloader: fragments/platform/generic-43/bootloader/grub.ks
- locale: fragments/shared/core/locale.ks
- services: fragments/shared/core/services.ks
- network: fragments/shared/core/network.ks
- packages: fragments/shared/packages/core-group.ks
- fedora-remix: fragments/shared/packages/fedora-remix.ks
- hand-picked: fragments/shared/packages/hand-picked.ks
- initial-setup: fragments/shared/initial-setup/server/config.ks
optional:
security:
secure: fragments/shared/core/security/enabled.ks
devel: fragments/shared/core/security/disabled.ks
version:
"43": fragments/platform/generic-43/repo/fedora-mirrors.ks
"rawhide": fragments/platform/generic-rawhide/repo/rawhide-mirrors.ks
desktop:
gnome: fragments/shared/desktop/gnome/packages.ks
labwc: fragments/shared/desktop/labwc/config.ks
post: fragments/shared/section-data/post/base.ks
extras:
- fragments/shared/guest-agents/packages.ks
modifiers:
storage:
standard: fragments/shared/storage/standard.ks
encrypted: fragments/shared/storage/encrypted.ks
bootloader:
systemd-boot: fragments/platform/generic-43/bootloader/systemd-boot.ks
# Virtual server recipe
# Modifiers: security (secure|devel), version (43|rawhide), post (true|false)
virtual-server:
description: "A recipe for a virtual server"
base: core
required:
- core: fragments/shared/core/base.ks
- storage: fragments/shared/storage/standard.ks
- bootloader: fragments/platform/generic-43/bootloader/grub.ks
- locale: fragments/shared/core/locale.ks
- services: fragments/shared/core/services.ks
- network: fragments/shared/core/network.ks
- packages: fragments/shared/packages/core-group.ks
- fedora-remix: fragments/shared/packages/fedora-remix.ks
- hand-picked: fragments/shared/packages/hand-picked.ks
- initial-setup: fragments/shared/initial-setup/server/config.ks
optional:
security:
secure: fragments/shared/core/security/enabled.ks
devel: fragments/shared/core/security/disabled.ks
version:
"43": fragments/platform/generic-43/repo/fedora-mirrors.ks
"rawhide": fragments/platform/generic-rawhide/repo/rawhide-mirrors.ks
post: fragments/shared/section-data/post/base.ks
extras:
- fragments/shared/guest-agents/packages.ks
modifiers:
storage:
standard: fragments/shared/storage/standard.ks
encrypted: fragments/shared/storage/encrypted.ks
bootloader:
systemd-boot: fragments/platform/generic-43/bootloader/systemd-boot.ks
# Desktop hypervisor recipe
# Modifiers: cpu (generic|amdcpu|intelcpu), gpu (none|intelgpu),
# security (secure|devel), version (rawhide), post (true|false)
desktop-hypervisor:
description: "A recipe for a desktop hypervisor"
base: core
required:
- core: fragments/shared/core/base.ks
- storage: fragments/shared/storage/standard.ks
- bootloader: fragments/platform/generic-rawhide/bootloader/grub.ks
- locale: fragments/shared/core/locale.ks
- services: fragments/shared/core/services.ks
- network: fragments/shared/core/network.ks
- packages: fragments/shared/packages/core-group.ks
- fedora-remix: fragments/shared/packages/fedora-remix.ks
- hand-picked: fragments/shared/packages/hand-picked.ks
- hardware-support: fragments/shared/packages/hardware-support.ks
- initial-setup: fragments/shared/initial-setup/desktop/config.ks
- desktop: fragments/shared/desktop/gnome/config.ks
- desktop-gnome-packages: fragments/shared/desktop/gnome/packages.ks
- desktop-gnome-post: fragments/shared/desktop/gnome/post-scripts.ks
- vmm: fragments/shared/desktop/vmm/packages.ks
- vmm-post: fragments/shared/desktop/vmm/post-scripts.ks
- repo: fragments/platform/generic-rawhide/repo/rawhide-mirrors.ks
- hypervisor-base-services: fragments/shared/hypervisor/base/services.ks
- hypervisor-base-packages: fragments/shared/hypervisor/base/packages.ks
- hypervisor-base-post: fragments/shared/hypervisor/base/post-scripts.ks
optional:
security:
secure: fragments/shared/core/security/enabled.ks
devel: fragments/shared/core/security/disabled.ks
cpu:
generic: fragments/shared/hypervisor/base/services.ks
amdcpu: fragments/shared/hypervisor/amdcpu.ks
intelcpu: fragments/shared/hypervisor/intelcpu.ks
gpu:
intelgpu: fragments/shared/hypervisor/intelgpu.ks
post: fragments/shared/section-data/post/base.ks
modifiers:
cpu:
generic: fragments/shared/hypervisor/base/services.ks
amdcpu: fragments/shared/hypervisor/amdcpu.ks
intelcpu: fragments/shared/hypervisor/intelcpu.ks
gpu:
none: null
intelgpu: fragments/shared/hypervisor/intelgpu.ks
storage:
standard: fragments/shared/storage/standard.ks
encrypted: fragments/shared/storage/encrypted.ks
# Live desktop recipe
# Modifiers: desktop (gnome|none), security (secure|devel), version (rawhide)
live-desktop:
description: "A recipe for a live desktop"
base: live-core
required:
- live-core: fragments/shared/live/core/base.ks
- storage: fragments/shared/live/core/storage.ks
- bootloader: fragments/platform/generic-rawhide/bootloader/grub.ks
- locale: fragments/shared/core/locale.ks
- services: fragments/shared/core/services.ks
- network: fragments/shared/core/network.ks
- packages: fragments/shared/live/core/packages.ks
- mandatory: fragments/shared/live/core/packages.ks
- post: fragments/shared/live/post/base.ks
- session: fragments/shared/live/post/session.ks
- initial-setup: fragments/shared/initial-setup/desktop/config.ks
- desktop: fragments/shared/desktop/gnome/config.ks
- desktop-gnome-packages: fragments/shared/desktop/gnome/packages.ks
- desktop-gnome-post: fragments/shared/desktop/gnome/post-scripts.ks
- repo: fragments/platform/generic-rawhide/repo/rawhide-mirrors.ks
optional:
security:
secure: fragments/shared/core/security/enabled.ks
devel: fragments/shared/core/security/disabled.ks
packages-hw: fragments/shared/packages/hardware-support.ks
extras: fragments/shared/guest-agents/packages.ks
modifiers:
storage:
standard: fragments/shared/live/core/storage.ks
encrypted: fragments/shared/live/core/storage.ks
# Live server recipe
# Modifiers: security (secure|devel), version (rawhide), hypervisor (true|false)
live-server:
description: "A recipe for a live server"
base: live-core
required:
- live-core: fragments/shared/live/core/base.ks
- storage: fragments/shared/live/core/storage.ks
- bootloader: fragments/platform/generic-rawhide/bootloader/grub.ks
- locale: fragments/shared/core/locale.ks
- services: fragments/shared/core/services.ks
- network: fragments/shared/core/network.ks
- packages: fragments/shared/live/core/packages.ks
- mandatory: fragments/shared/live/core/packages.ks
- post: fragments/shared/live/post/base.ks
- session: fragments/shared/live/post/session.ks
- initial-setup: fragments/shared/initial-setup/server/config.ks
- repo: fragments/platform/generic-rawhide/repo/rawhide-mirrors.ks
optional:
security:
secure: fragments/shared/core/security/enabled.ks
devel: fragments/shared/core/security/disabled.ks
packages-hw: fragments/shared/packages/hardware-support.ks
hypervisor: fragments/shared/live/hypervisor.ks
modifiers:
storage:
standard: fragments/shared/live/core/storage.ks
encrypted: fragments/shared/live/core/storage.ks
+87
View File
@@ -0,0 +1,87 @@
# Recipe Manifest for Phyllome OS
# Define all recipe variants using generators from recipe_templates.yaml
# Edit this file to add new variants, then run: make generate-recipes
recipes:
# Virtual Desktop variants
- name: virtual-desktop
variants:
- version: 43
desktop: gnome
storage: standard
security: secure
extras: true
- version: 43
desktop: gnome
storage: standard
security: devel
extras: true
- version: 43
desktop: labwc
storage: standard
security: secure
extras: true
- version: rawhide
desktop: gnome
storage: standard
security: secure
extras: true
- version: rawhide
desktop: gnome
storage: encrypted
security: secure
extras: true
# Virtual Server variants
- name: virtual-server
variants:
- version: 43
security: secure
extras: true
post: true
- version: 43
security: devel
extras: true
post: false
- version: rawhide
security: secure
extras: true
post: true
- version: rawhide
security: devel
extras: true
post: false
# Desktop Hypervisor variants
- name: desktop-hypervisor
variants:
- version: rawhide
security: secure
- version: rawhide
cpu: amdcpu
security: secure
- version: rawhide
cpu: intelcpu
security: secure
- version: rawhide
cpu: intelcpu
gpu: intelgpu
security: secure
# Live Desktop variants
- name: live-desktop
variants:
- version: rawhide
security: secure
packages-hw: true
# Live Server variants
- name: live-server
variants:
- version: rawhide
security: secure
packages-hw: true
- version: rawhide
security: secure
packages-hw: true
hypervisor: true
+2
View File
@@ -0,0 +1,2 @@
PyYAML>=6.0
pytest>=7.0