#!/usr/bin/env python3 import os import sys import yaml import subprocess import json import shutil from datetime import datetime import random import tempfile import secrets import string # List of adjectives for hostname generation ADJECTIVES = [ # Scientific/Sci-fi 'quantum', 'atomic', 'plasma', 'fusion', 'ionic', 'magnetic', 'cosmic', 'stellar', 'nebular', 'pulsar', 'quasar', 'warp', 'phaser', 'hyper', 'temporal', 'spatial', 'dimensional', 'subspace', 'transwarp', # Cool/Interesting 'abysmal', 'adamant', 'aerial', 'arcane', 'astral', 'azure', 'celestial', 'crimson', 'cryptic', 'crystalline', 'dormant', 'eerie', 'eldritch', 'ethereal', 'fractal', 'frozen', 'ghostly', 'gilded', 'singular', 'hollow', 'infernal', 'lunar', 'mystic', 'nebulous', 'obsidian', 'occult', 'prismatic', 'radiant', 'shadow', 'solar', 'spectral', 'stellar', 'sublime', 'titanic', 'twilight', 'void', 'volcanic' ] NOUNS = [ # Star Trek 'enterprise', 'voyager', 'galaxy', 'intrepid', 'nova', 'warbird', 'falcon', 'aegis', 'nemesis', 'equinox', 'stargazer', 'challenger', 'discovery', 'prometheus', 'odyssey', 'daedalus', 'apollo', 'korolev', 'phoenix', 'orion', # Space 'nebula', 'pulsar', 'quasar', 'nova', 'supernova', 'blackhole', 'wormhole', 'singularity', 'galaxy', 'void', 'rift', 'nexus', # Cool Concepts 'abyss', 'aether', 'anomaly', 'artifact', 'beacon', 'cipher', 'crystal', 'echo', 'enigma', 'essence', 'fractal', 'horizon', 'infinity', 'paradox', 'phoenix', 'prism', 'spectrum', 'tesseract', 'vector', 'vertex', 'vortex', 'zenith' ] def load_config(config_file): """Load and parse YAML config file.""" with open(config_file, 'r') as f: return yaml.safe_load(f) def check_prerequisites(): """Check if required tools are installed.""" required_tools = ['hcloud', 'jq'] for tool in required_tools: if not shutil.which(tool): print(f"Error: {tool} not found. Please install it first.") sys.exit(1) def check_hetzner_token(): """Check if HCLOUD_TOKEN is set.""" if not os.environ.get('HCLOUD_TOKEN'): print("Error: HCLOUD_TOKEN environment variable not set") sys.exit(1) def generate_hostname(): """Generate a unique hostname in adjective-noun-date-time format.""" adjective = random.choice(ADJECTIVES) noun = random.choice(NOUNS) timestamp = datetime.now().strftime('%y%m%d-%H%M') return f"{adjective}-{noun}-{timestamp}" def get_image_id(): """Get the base image ID.""" cmd = [ 'hcloud', 'image', 'list', '--type=snapshot', '--selector=name=fedora-coreos-nullpoint', '--output', 'json' ] result = subprocess.run(cmd, capture_output=True, text=True, check=True) images = json.loads(result.stdout) if not images: print("Error: Base image not found. Run build.py first.") sys.exit(1) return images[0]['id'] def generate_secure_passphrase(length=32): """Generate a secure random passphrase.""" alphabet = string.ascii_letters + string.digits + string.punctuation return ''.join(secrets.choice(alphabet) for _ in range(length)) def create_server(config, hostname, image_id): """Create a new server.""" hetzner = config['hetzner'] # Generate cloud-init config for this specific server cloud_init = { 'hostname': hostname, 'timezone': 'UTC', 'users': [ { 'name': 'admin', 'groups': ['wheel'], 'sudo': 'ALL=(ALL) NOPASSWD:ALL', 'ssh_authorized_keys': [config['admin_ssh_key']] } ], 'package_update': True, 'package_upgrade': True } # Create temporary cloud-init config with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml') as f: yaml.dump(cloud_init, f) f.flush() cmd = [ 'hcloud', 'server', 'create', '--name', hostname, '--type', hetzner['server_type'], '--datacenter', hetzner['datacenter'], '--image', str(image_id), '--ssh-key', hetzner['ssh_key_name'], '--user-data-from-file', f.name ] subprocess.run(cmd, check=True) print(f"\nServer '{hostname}' created successfully!") print("The LUKS passphrase has been saved to /root/luks-passphrase.txt on the server.") print("Please save this passphrase securely - it will be needed if TPM+Tang unlock fails.") print("\nYou can connect using: ssh admin@") def get_server_ip(hostname): """Get the server's IP address.""" cmd = ['hcloud', 'server', 'ip', hostname] result = subprocess.run(cmd, capture_output=True, text=True, check=True) return result.stdout.strip() def main(): """Main entry point.""" # Load config if not os.path.exists('deploy-config.yaml'): print("Error: deploy-config.yaml not found") sys.exit(1) config = load_config('deploy-config.yaml') # Check prerequisites check_prerequisites() check_hetzner_token() # Generate hostname hostname = generate_hostname() # Get image ID image_id = get_image_id() # Create server print(f"Creating server '{hostname}'...") create_server(config, hostname, image_id) # Get server IP server_ip = get_server_ip(hostname) print(f"Server created! IP: {server_ip}") print(f"You can connect using: ssh core@{server_ip}") if __name__ == '__main__': main()