diff --git a/appliances/n8n/CHANGELOG.md b/appliances/n8n/CHANGELOG.md new file mode 100644 index 00000000..e5f6cab2 --- /dev/null +++ b/appliances/n8n/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to the n8n appliance will be documented in this file. + +## [1.0.0-1] - 2025-10-01 + +### Added +- Initial release of n8n appliance +- Docker container: n8nio/n8n:latest +- VNC desktop access +- SSH key authentication +- OpenNebula context integration +- Configurable container parameters diff --git a/appliances/n8n/README.md b/appliances/n8n/README.md new file mode 100644 index 00000000..31d430d4 --- /dev/null +++ b/appliances/n8n/README.md @@ -0,0 +1,82 @@ +# n8n Appliance + +n8n is a workflow automation tool that allows you to connect different services and automate tasks. This appliance provides n8n running in a Docker container on Ubuntu 22.04 LTS with VNC access and SSH key authentication. + +## Key Features + +**n8n capabilities:** + - Workflow automation + - Visual workflow editor + - Self-hosted + - Extensible with custom nodes + - REST API + - Webhook support +**This appliance provides:** +- Ubuntu 22.04 LTS base operating system +- Docker Engine CE pre-installed and configured +- n8n container (n8nio/n8n:latest) ready to run +- VNC access for desktop environment +- SSH key authentication from OpenNebula context +- Configurable container parameters (ports, volumes, environment variables) - Web interface on port 5678 + +## Quick Start + +1. **Deploy the appliance** from OpenNebula marketplace +2. **Configure container settings** during VM instantiation: + - Container name: n8n-container + - Port mappings: 5678:5678 + - Environment variables: N8N_HOST=0.0.0.0,N8N_PORT=5678,N8N_PROTOCOL=http,N8N_SECURE_COOKIE=false + - Volume mounts: /data:/home/node/.n8n +3. **Access the VM**: + - VNC: Direct desktop access + - SSH: `ssh root@VM_IP` (using OpenNebula context keys) - Web: n8n interface at http://VM_IP:5678 + +## Container Configuration + +### Port Mappings +Format: `host_port:container_port,host_port2:container_port2` +Default: `5678:5678` + +### Environment Variables +Format: `VAR1=value1,VAR2=value2` +Default: `N8N_HOST=0.0.0.0,N8N_PORT=5678,N8N_PROTOCOL=http,N8N_SECURE_COOKIE=false` + +### Volume Mounts +Format: `/host/path:/container/path,/host/path2:/container/path2` +Default: `/data:/home/node/.n8n` + +## Management Commands + +```bash +# View running containers +docker ps + +# View container logs +docker logs n8n-container + +# Access container shell +docker exec -it n8n-container /bin/bash + +# Restart container +docker restart n8n-container + +# Stop container +docker stop n8n-container + +# Start container +docker start n8n-container +``` + +## Technical Details + +- **Base OS**: Ubuntu 22.04 LTS +- **Container Runtime**: Docker Engine CE +- **Container Image**: n8nio/n8n:latest +- **Default Ports**: 5678:5678 +- **Default Volumes**: /data:/home/node/.n8n +- **Memory Requirements**: 2GB minimum +- **Disk Requirements**: 8GB minimum + +## Version History + +See [CHANGELOG.md](CHANGELOG.md) for detailed version history. diff --git a/appliances/n8n/appliance.sh b/appliances/n8n/appliance.sh new file mode 100755 index 00000000..ab02f486 --- /dev/null +++ b/appliances/n8n/appliance.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +# n8n Appliance Installation Script +# Auto-generated by OpenNebula Docker Appliance Generator +# Docker Image: n8nio/n8n:latest + +set -o errexit -o pipefail + +# List of contextualization parameters +ONE_SERVICE_PARAMS=( + 'ONEAPP_CONTAINER_NAME' 'configure' 'Docker container name' 'O|text' + 'ONEAPP_CONTAINER_PORTS' 'configure' 'Docker container port mappings' 'O|text' + 'ONEAPP_CONTAINER_ENV' 'configure' 'Docker container environment variables' 'O|text' + 'ONEAPP_CONTAINER_VOLUMES' 'configure' 'Docker container volume mappings' 'O|text' +) + +# Configuration from user input +DOCKER_IMAGE="n8nio/n8n:latest" +DEFAULT_CONTAINER_NAME="n8n-container" +DEFAULT_PORTS="5678:5678" +DEFAULT_ENV_VARS="N8N_HOST=0.0.0.0,N8N_PORT=5678,N8N_PROTOCOL=http,N8N_SECURE_COOKIE=false" +DEFAULT_VOLUMES="/data:/home/node/.n8n" +APP_NAME="n8n" +APPLIANCE_NAME="n8n" + +### Appliance metadata ############################################### + +ONE_SERVICE_NAME='n8n' +ONE_SERVICE_VERSION='1.0' # Appliance version +ONE_SERVICE_BUILD=$(date +%s) +ONE_SERVICE_SHORT_DESCRIPTION='n8n Docker Container Appliance' +ONE_SERVICE_DESCRIPTION='n8n running in Docker container' +ONE_SERVICE_RECONFIGURABLE=true + +### Appliance functions ############################################## + +service_cleanup() +{ + : +} + +service_install() +{ + export DEBIAN_FRONTEND=noninteractive + + # Update system + apt-get update + apt-get upgrade -y + + # Install Docker + apt-get install -y ca-certificates curl + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + # Enable and start Docker + systemctl enable docker + systemctl start docker + + # Pull the Docker image + msg info "Pulling Docker image: $DOCKER_IMAGE" + docker pull "$DOCKER_IMAGE" + + # Configure console auto-login + systemctl stop unattended-upgrades 2>/dev/null || true + systemctl disable unattended-upgrades 2>/dev/null || true + + apt-get install -y mingetty + + # Configure auto-login on console + mkdir -p /etc/systemd/system/getty@tty1.service.d + cat > /etc/systemd/system/getty@tty1.service.d/override.conf << 'CONSOLE_EOF' +[Service] +ExecStart= +ExecStart=-/sbin/agetty --noissue --autologin root %I \\$TERM +Type=idle +CONSOLE_EOF + + # Configure serial console and set root password + mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d + cat > /etc/systemd/system/serial-getty@ttyS0.service.d/override.conf << 'SERIAL_EOF' +[Service] +ExecStart= +ExecStart=-/sbin/agetty --noissue --autologin root %I 115200,38400,9600 vt102 +Type=idle +SERIAL_EOF + + echo 'root:opennebula' | chpasswd + systemctl enable getty@tty1.service serial-getty@ttyS0.service + + # Create welcome message + cat > /etc/profile.d/99-$APPLIANCE_NAME-welcome.sh << 'WELCOME_EOF' +#!/bin/bash +case \\$- in + *i*) ;; + *) return;; +esac + +echo "==================================================" +echo " $APP_NAME Appliance" +echo "==================================================" +echo " Docker Image: $DOCKER_IMAGE" +echo " Container: $DEFAULT_CONTAINER_NAME" +echo " Ports: $DEFAULT_PORTS" +echo "" +echo " Commands:" +echo " docker ps - Show running containers" +echo " docker logs $DEFAULT_CONTAINER_NAME - View container logs" +echo " docker exec -it $DEFAULT_CONTAINER_NAME /bin/bash - Access container" +echo "" +EOF + +if [ "$WEB_INTERFACE" = "true" ]; then + cat >> "$REPO_ROOT/appliances/$APPLIANCE_NAME/appliance.sh" << EOF +echo " Web Interface: http://VM_IP:$APP_PORT" +echo "" +EOF +fi + +cat >> "$REPO_ROOT/appliances/$APPLIANCE_NAME/appliance.sh" << 'EOF' +echo " Access Methods:" +echo " SSH: Enabled (password: 'opennebula' + context keys)" +echo " Console: Auto-login as root (via OpenNebula console)" +echo " Serial: Auto-login as root (via serial console)" +echo "==================================================" +WELCOME_EOF + + chmod +x /etc/profile.d/99-$APPLIANCE_NAME-welcome.sh + + # Clean up + apt-get autoremove -y + apt-get autoclean + find /var/log -type f -exec truncate -s 0 {} \; + + sync + + return 0 +} + +service_configure() +{ + msg info "Verifying Docker is running" + + if ! systemctl is-active --quiet docker; then + msg error "Docker is not running" + return 1 + fi + + msg info "Docker is running" + return 0 +} + +service_bootstrap() +{ + msg info "Starting $APP_NAME service bootstrap" + + # Setup and start the container + setup_app_container + + return $? +} + +# Setup container function +setup_app_container() +{ + local container_name="${ONEAPP_CONTAINER_NAME:-$DEFAULT_CONTAINER_NAME}" + local container_ports="${ONEAPP_CONTAINER_PORTS:-$DEFAULT_PORTS}" + local container_env="${ONEAPP_CONTAINER_ENV:-$DEFAULT_ENV_VARS}" + local container_volumes="${ONEAPP_CONTAINER_VOLUMES:-$DEFAULT_VOLUMES}" + + msg info "Setting up $APP_NAME container: $container_name" + + # Stop and remove existing container if it exists + if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then + msg info "Stopping existing container: $container_name" + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + fi + + # Parse port mappings + local port_args="" + if [ -n "$container_ports" ]; then + IFS=',' read -ra PORT_ARRAY <<< "$container_ports" + for port in "${PORT_ARRAY[@]}"; do + port_args="$port_args -p $port" + done + fi + + # Parse environment variables + local env_args="" + if [ -n "$container_env" ]; then + IFS=',' read -ra ENV_ARRAY <<< "$container_env" + for env in "${ENV_ARRAY[@]}"; do + env_args="$env_args -e $env" + done + fi + + # Parse volume mounts + local volume_args="" + if [ -n "$container_volumes" ]; then + IFS=',' read -ra VOL_ARRAY <<< "$container_volumes" + for vol in "${VOL_ARRAY[@]}"; do + local host_path=$(echo "$vol" | cut -d':' -f1) + mkdir -p "$host_path" + # Set ownership to 1000:1000 (common for Docker containers) + chown -R 1000:1000 "$host_path" 2>/dev/null || true + volume_args="$volume_args -v $vol" + done + fi + + # Start the container + msg info "Starting $APP_NAME container with:" + msg info " Ports: $container_ports" + msg info " Environment: ${container_env:-none}" + msg info " Volumes: $container_volumes" + + docker run -d --name "$container_name" --restart unless-stopped $port_args $env_args $volume_args "$DOCKER_IMAGE" + + if [ $? -eq 0 ]; then + msg info "$APP_NAME container started successfully" + docker ps --filter name="$container_name" + return 0 + else + msg error "Failed to start $APP_NAME container" + return 1 + fi +} diff --git a/appliances/n8n/cf81cb32-dde4-4d47-a09c-fc3b4f058f45.yaml b/appliances/n8n/cf81cb32-dde4-4d47-a09c-fc3b4f058f45.yaml new file mode 100644 index 00000000..3aa49411 --- /dev/null +++ b/appliances/n8n/cf81cb32-dde4-4d47-a09c-fc3b4f058f45.yaml @@ -0,0 +1,66 @@ +--- +name: n8n +version: 1.0.0-1 +one-apps_version: 7.0.0-0 +publisher: Pablo de la Rco +publisher_email: pablo@example.com +description: |- + n8n is a workflow automation tool that allows you to connect different services and automate tasks. This appliance provides n8n + running in a Docker container on Ubuntu 22.04 LTS with VNC access and + SSH key authentication. + + **n8n features:** + - Workflow automation + - Visual workflow editor + - Self-hosted + - Extensible with custom nodes + - REST API + - Webhook support + **This appliance provides:** + - Ubuntu 22.04 LTS base operating system + - Docker Engine CE pre-installed and configured + - n8n container (n8nio/n8n:latest) ready to run + - VNC access for desktop environment + - SSH key authentication from OpenNebula context - Web interface on port 5678 + - Configurable container parameters (ports, volumes, environment variables) + + **Access Methods:** + - VNC: Direct access to desktop environment + - SSH: Key-based authentication from OpenNebula - Web: n8n interface at http://VM_IP:5678 + +short_description: n8n with VNC access and SSH key auth +tags: +- n8n +- docker +- ubuntu +- container +- vnc +- ssh-key +format: qcow2 +creation_time: 1759334415 +os-id: Ubuntu +os-release: '22.04' +os-arch: x86_64 +hypervisor: KVM +opennebula_version: 7.0 +opennebula_template: + context: + network: 'YES' + ssh_public_key: $USER[SSH_PUBLIC_KEY] + set_hostname: $USER[SET_HOSTNAME] + cpu: '2' + disk: + image: $FILE[IMAGE_ID] + image_uname: $USER[IMAGE_UNAME] + graphics: + listen: 0.0.0.0 + type: vnc + memory: '2048' + name: n8n + user_inputs: + oneapp_container_name: 'M|text|Container name|n8n-container|n8n-container' + oneapp_container_ports: 'M|text|Container ports (format: host:container)|5678:5678|5678:5678' + oneapp_container_env: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)|N8N_HOST=0.0.0.0,N8N_PORT=5678,N8N_PROTOCOL=http|' + oneapp_container_volumes: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/home/node/.n8n|' + inputs_order: ONEAPP_CONTAINER_NAME,ONEAPP_CONTAINER_PORTS,ONEAPP_CONTAINER_ENV,ONEAPP_CONTAINER_VOLUMES +logo: logos/n8n.png diff --git a/appliances/n8n/context.yaml b/appliances/n8n/context.yaml new file mode 100644 index 00000000..bcac3c2e --- /dev/null +++ b/appliances/n8n/context.yaml @@ -0,0 +1,9 @@ +--- +:tests: + 'n8n': + :image_name: n8n.qcow2 + :type: linux + :microenvs: ['context-kvm'] + :slow: true + :enable_netcfg_common: True + :enable_netcfg_ip_methods: True diff --git a/appliances/n8n/metadata.yaml b/appliances/n8n/metadata.yaml new file mode 100644 index 00000000..58b75a6b --- /dev/null +++ b/appliances/n8n/metadata.yaml @@ -0,0 +1,61 @@ +--- +:app: + :name: n8n + :type: service + :os: + :type: linux + :base: ubuntu2204 + :hypervisor: KVM + :opennebula_version: + - '7.0' + :opennebula_template: + context: + - SSH_PUBLIC_KEY="$USER[SSH_PUBLIC_KEY]" + - SET_HOSTNAME="$USER[SET_HOSTNAME]" + cpu: '2' + memory: '2048' + disk_size: '8192' + graphics: + listen: 0.0.0.0 + type: vnc + inputs_order: 'CONTAINER_NAME,CONTAINER_PORTS,CONTAINER_ENV,CONTAINER_VOLUMES' + logo: logos/n8n.png + user_inputs: + CONTAINER_NAME: 'M|text|Container name|n8n-container|n8n-container' + CONTAINER_PORTS: 'M|text|Container ports (format: host:container)|5678:5678|5678:5678' + CONTAINER_ENV: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)|N8N_HOST=0.0.0.0,N8N_PORT=5678,N8N_PROTOCOL=http,N8N_SECURE_COOKIE=false|' + CONTAINER_VOLUMES: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/home/node/.n8n|' + + :context: + :prefixed: true + :params: + :CONTAINER_NAME: 'n8n-container' + :CONTAINER_PORTS: '5678:5678' + :CONTAINER_ENV: 'N8N_HOST=0.0.0.0,N8N_PORT=5678,N8N_PROTOCOL=http,N8N_SECURE_COOKIE=false' + :CONTAINER_VOLUMES: '/data:/home/node/.n8n' + +:one: + :template: + NAME: base + TEMPLATE: + ARCH: x86_64 + CONTEXT: + NETWORK: 'YES' + SSH_PUBLIC_KEY: "$USER[SSH_PUBLIC_KEY]" + CPU: '2' + CPU_MODEL: + MODEL: host-passthrough + GRAPHICS: + LISTEN: 0.0.0.0 + TYPE: vnc + MEMORY: '2048' + NIC: + NETWORK: vnet + NIC_DEFAULT: + MODEL: virtio + :datastore_name: default + :timeout: '90' + +:infra: + :disk_format: qcow2 + :apps_path: /var/tmp diff --git a/appliances/n8n/tests.yaml b/appliances/n8n/tests.yaml new file mode 100644 index 00000000..f0d96424 --- /dev/null +++ b/appliances/n8n/tests.yaml @@ -0,0 +1,2 @@ +--- +- '00-n8n_basic.rb' diff --git a/appliances/n8n/tests/00-n8n_basic.rb b/appliances/n8n/tests/00-n8n_basic.rb new file mode 100644 index 00000000..07614b12 --- /dev/null +++ b/appliances/n8n/tests/00-n8n_basic.rb @@ -0,0 +1,126 @@ +require_relative '../../../lib/community/app_handler' + +describe 'n8n Appliance' do + include_context('vm_handler') + + before(:all) do + # Wait for contextualization to complete (Docker installation and container startup) + puts "Waiting for contextualization to complete..." + sleep 60 + + # Wait for Docker service to be ready + max_wait = 120 + wait_interval = 5 + elapsed = 0 + + loop do + cmd = 'systemctl is-active docker 2>/dev/null' + result = @info[:vm].ssh(cmd) + + if result.success? + puts "Docker service is active" + break + end + + if elapsed >= max_wait + puts "Warning: Docker service not active after #{max_wait} seconds" + break + end + + puts "Waiting for Docker service... (#{elapsed}s/#{max_wait}s)" + sleep wait_interval + elapsed += wait_interval + end + + # Additional wait for container to start + sleep 10 + end + + # Test Docker installation + it 'docker is installed and running' do + cmd = 'which docker' + @info[:vm].ssh(cmd).expect_success + + cmd = 'systemctl is-active docker' + @info[:vm].ssh(cmd).expect_success + end + + # Test n8n container image is available + it 'n8n container image is available' do + cmd = 'docker images --format "{{.Repository}}:{{.Tag}}" | grep "n8nio/n8n:latest"' + @info[:vm].ssh(cmd).expect_success + end + + # Test data directory exists + it 'data directory exists' do + cmd = 'test -d /data' + @info[:vm].ssh(cmd).expect_success + end + + # Test n8n container is running + it 'n8n container is running' do + cmd = 'docker ps --format "{{.Names}}" | grep "n8n-container"' + @info[:vm].ssh(cmd).expect_success + end + + # Test container is responsive + it 'n8n container is responsive' do + cmd = 'docker exec n8n-container echo "Container is running"' + execution = @info[:vm].ssh(cmd) + expect(execution.success?).to be(true) + expect(execution.stdout.strip).to eq('Container is running') + end + + # Test container has correct restart policy + it 'container has restart policy configured' do + cmd = 'docker inspect n8n-container --format "{{.HostConfig.RestartPolicy.Name}}"' + execution = @info[:vm].ssh(cmd) + expect(execution.success?).to be(true) + expect(execution.stdout.strip).to eq('unless-stopped') + end + + # Test container port mapping + it 'container has port 5678 exposed' do + cmd = 'docker port n8n-container 5678' + @info[:vm].ssh(cmd).expect_success + end + + # Test container volume mapping + it 'container has data volume mounted' do + cmd = "docker inspect n8n-container --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}' | grep '/data:/home/node/.n8n'" + @info[:vm].ssh(cmd).expect_success + end + + # Check if the service framework reports ready + it 'checks oneapps motd' do + cmd = 'cat /etc/motd' + timeout_seconds = 60 + retry_interval_seconds = 5 + + begin + Timeout.timeout(timeout_seconds) do + loop do + execution = @info[:vm].ssh(cmd) + + if execution.exitstatus == 0 && execution.stdout.include?('All set and ready to serve') + expect(execution.exitstatus).to eq(0) + expect(execution.stdout).to include('All set and ready to serve') + break + else + sleep(retry_interval_seconds) + end + end + end + rescue Timeout::Error + fail "Timeout after #{timeout_seconds} seconds: MOTD did not contain 'All set and ready to serve'" + end + end + + # Cleanup: Stop container after tests + after(:all) do + if @info && @info[:vm] + @info[:vm].ssh('docker stop n8n-container || true') + @info[:vm].ssh('docker rm n8n-container || true') + end + end +end diff --git a/apps-code/community-apps/Makefile.config b/apps-code/community-apps/Makefile.config index 6995d7df..f0d71216 100644 --- a/apps-code/community-apps/Makefile.config +++ b/apps-code/community-apps/Makefile.config @@ -7,7 +7,7 @@ VERBOSE := 1 PACKER_LOG := 0 PACKER_HEADLESS := true -SERVICES := lithops lithops_worker rabbitmq ueransim example phoenixrtos srsran openfgs +SERVICES := lithops lithops_worker rabbitmq ueransim example phoenixrtos srsran openfgs n8n n8n .DEFAULT_GOAL := help diff --git a/apps-code/community-apps/packer/n8n/81-configure-ssh.sh b/apps-code/community-apps/packer/n8n/81-configure-ssh.sh new file mode 100755 index 00000000..001ffd6b --- /dev/null +++ b/apps-code/community-apps/packer/n8n/81-configure-ssh.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Configures critical settings for OpenSSH server. + +exec 1>&2 +set -eux -o pipefail + +gawk -i inplace -f- /etc/ssh/sshd_config <<'AWKEOF' +BEGIN { update = "PasswordAuthentication no" } +/^[#\s]*PasswordAuthentication\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +AWKEOF + +gawk -i inplace -f- /etc/ssh/sshd_config <<'AWKEOF' +BEGIN { update = "PermitRootLogin without-password" } +/^[#\s]*PermitRootLogin\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +AWKEOF + +gawk -i inplace -f- /etc/ssh/sshd_config <<'AWKEOF' +BEGIN { update = "UseDNS no" } +/^[#\s]*UseDNS\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +AWKEOF + +sync diff --git a/apps-code/community-apps/packer/n8n/82-configure-context.sh b/apps-code/community-apps/packer/n8n/82-configure-context.sh new file mode 100755 index 00000000..2278ea94 --- /dev/null +++ b/apps-code/community-apps/packer/n8n/82-configure-context.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Configure and enable service context. + +exec 1>&2 +set -eux -o pipefail + +mv /etc/one-appliance/net-90-service-appliance /etc/one-context.d/ +mv /etc/one-appliance/net-99-report-ready /etc/one-context.d/ + +chown root:root /etc/one-context.d/* +chmod u=rwx,go=rx /etc/one-context.d/* + +sync diff --git a/apps-code/community-apps/packer/n8n/common.pkr.hcl b/apps-code/community-apps/packer/n8n/common.pkr.hcl new file mode 120000 index 00000000..3b9ee73c --- /dev/null +++ b/apps-code/community-apps/packer/n8n/common.pkr.hcl @@ -0,0 +1 @@ +../../../one-apps/packer/common.pkr.hcl \ No newline at end of file diff --git a/apps-code/community-apps/packer/n8n/gen_context b/apps-code/community-apps/packer/n8n/gen_context new file mode 100755 index 00000000..92ea59ef --- /dev/null +++ b/apps-code/community-apps/packer/n8n/gen_context @@ -0,0 +1,33 @@ +#!/bin/bash +set -eux -o pipefail + +SCRIPT=$(cat <<'MAINEND' +gawk -i inplace -f- /etc/ssh/sshd_config <<'EOF' +BEGIN { update = "PasswordAuthentication yes" } +/^[#\s]*PasswordAuthentication\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +EOF + +gawk -i inplace -f- /etc/ssh/sshd_config <<'EOF' +BEGIN { update = "PermitRootLogin yes" } +/^[#\s]*PermitRootLogin\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +EOF + +systemctl reload sshd + +echo "nameserver 1.1.1.1" > /etc/resolv.conf +MAINEND +) + +cat< ${var.input_dir}/context/context.sh", + "mkisofs -o ${var.input_dir}/${var.appliance_name}-context.iso -V CONTEXT -J -R ${var.input_dir}/context", + ] + } +} + +# Build VM image +source "qemu" "n8n" { + cpus = 2 + memory = 2048 + accelerator = "kvm" + + iso_url = "../one-apps/export/ubuntu2204.qcow2" + iso_checksum = "none" + + headless = var.headless + + disk_image = true + disk_cache = "unsafe" + disk_interface = "virtio" + net_device = "virtio-net" + format = "qcow2" + disk_compression = false + disk_size = "8000" + + output_directory = var.output_dir + + qemuargs = [ + ["-cpu", "host"], + ["-cdrom", "${var.input_dir}/${var.appliance_name}-context.iso"], + ["-serial", "stdio"], + # MAC addr needs to match ETH0_MAC from context iso + ["-netdev", "user,id=net0,hostfwd=tcp::{{ .SSHHostPort }}-:22"], + ["-device", "virtio-net-pci,netdev=net0,mac=00:11:22:33:44:55"] + ] + + ssh_username = "root" + ssh_password = "opennebula" + ssh_timeout = "900s" + shutdown_command = "poweroff" + vm_name = var.appliance_name +} + +build { + sources = ["source.qemu.n8n"] + + # revert insecure ssh options done by context start_script + provisioner "shell" { + scripts = ["${var.input_dir}/81-configure-ssh.sh"] + } + + provisioner "shell" { + inline_shebang = "/bin/bash -e" + inline = [ + "install -o 0 -g 0 -m u=rwx,g=rx,o= -d /etc/one-appliance/{,service.d/,lib/}", + "install -o 0 -g 0 -m u=rwx,g=rx,o=rx -d /opt/one-appliance/{,bin/}", + ] + } + + provisioner "file" { + sources = [ + "../one-apps/appliances/scripts/net-90-service-appliance", + "../one-apps/appliances/scripts/net-99-report-ready", + ] + destination = "/etc/one-appliance/" + } + provisioner "file" { + sources = [ + "../../lib/common.sh", + "../../lib/functions.sh", + ] + destination = "/etc/one-appliance/lib/" + } + provisioner "file" { + source = "../one-apps/appliances/service.sh" + destination = "/etc/one-appliance/service" + } + provisioner "file" { + sources = ["../../appliances/n8n/appliance.sh"] + destination = "/etc/one-appliance/service.d/" + } + + provisioner "shell" { + scripts = ["${var.input_dir}/82-configure-context.sh"] + } + + provisioner "shell" { + inline_shebang = "/bin/bash -e" + inline = ["/etc/one-appliance/service install && sync"] + } + + post-processor "shell-local" { + execute_command = ["bash", "-c", "{{.Vars}} {{.Script}}"] + environment_vars = [ + "OUTPUT_DIR=${var.output_dir}", + "APPLIANCE_NAME=${var.appliance_name}", + ] + scripts = ["../one-apps/packer/postprocess.sh"] + } +} diff --git a/apps-code/community-apps/packer/n8n/postprocess.sh b/apps-code/community-apps/packer/n8n/postprocess.sh new file mode 100755 index 00000000..58c14885 --- /dev/null +++ b/apps-code/community-apps/packer/n8n/postprocess.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Post-processing script for the appliance + +set -e + +echo "Post-processing appliance..." + +# Add any post-processing steps here +# For example: image optimization, cleanup, etc. + +echo "Post-processing completed" diff --git a/apps-code/community-apps/packer/n8n/variables.pkr.hcl b/apps-code/community-apps/packer/n8n/variables.pkr.hcl new file mode 100644 index 00000000..525227fa --- /dev/null +++ b/apps-code/community-apps/packer/n8n/variables.pkr.hcl @@ -0,0 +1,20 @@ +variable "appliance_name" { + type = string +} + +variable "version" { + type = string +} + +variable "input_dir" { + type = string +} + +variable "output_dir" { + type = string +} + +variable "headless" { + type = bool + default = true +} diff --git a/lib/community/app_handler.rb b/lib/community/app_handler.rb index 0700122f..cd434540 100644 --- a/lib/community/app_handler.rb +++ b/lib/community/app_handler.rb @@ -34,7 +34,7 @@ prefixed = config[:app][:context][:prefixed] - options = "--context #{app_context(APP_CONTEXT_PARAMS, prefixed)} --disk #{APP_IMAGE_NAME}" + options = "--context \"#{app_context(APP_CONTEXT_PARAMS, prefixed)}\" --disk #{APP_IMAGE_NAME}" # Create a new VM by issuing onetemplate instantiate VM_TEMPLATE @info[:vm] = VM.instantiate(VM_TEMPLATE, true, options) @@ -56,12 +56,12 @@ # @return [String] Comma separated list of context parameters ready to be used with --context on CLI template instantiation # def app_context(app_context_params, prefixed = true) - params = [%(SSH_PUBLIC_KEY=\\"\\$USER[SSH_PUBLIC_KEY]\\"), 'NETWORK="YES"'] + params = [%(SSH_PUBLIC_KEY=\\"\\$USER[SSH_PUBLIC_KEY]\\"), %(NETWORK=\\"YES\\")] prefixed == true ? prefix = 'ONEAPP_' : prefix = '' app_context_params.each do |key, value| - params << "#{prefix}#{key}=\"#{value}\"" + params << %(#{prefix}#{key}=\\"#{value}\\") end return params.join(',') diff --git a/logos/n8n.png b/logos/n8n.png new file mode 100644 index 00000000..120bb942 Binary files /dev/null and b/logos/n8n.png differ