8 Commits

Author SHA1 Message Date
Mike Hansen
45c70e2c9d Merge pull request #10 from Telecominfraproject/update-ucentral-client-olgV8
Prepare v0.0.3 release with updated ucentral-client image
2026-03-13 11:23:54 -04:00
Mike Hansen
61750ff76d Prepare v0.0.3 release with updated ucentral-client image
- Updated iso-files/ucentral-setup.sh to use mjhnetexp/ucentral-client:olgV8
- Updated ISO version to v0.0.3 in grub.cfg
- Added v0.0.3 changelog entry

The olgV8 image contains:
- Updated ucentral-schema from ra_proposal branch (SDK commit 6491570)
- Latest VyOS integration improvements
- Schema enhancements for NAT and operational commands
2026-03-11 10:49:57 -04:00
Andre Courchesne
9b49f798c0 - Update changelog, bump to v0.0.2 2026-02-12 10:19:48 -05:00
Andre Courchesne
2e5afdc7ec Merge pull request #4 from Telecominfraproject/ucentral-support
Add Ucentral support
2026-02-12 10:15:54 -05:00
NavneetBarwal-RA
a8a7535a6f - Add UCentral support and documentation 2026-02-12 10:15:19 -05:00
Andre Courchesne
6bb1e025cb - Update changelog, bump to v0.0.1 2025-12-17 11:28:28 -05:00
Andre Courchesne
317ec12332 Merge pull request #2 from Telecominfraproject/1-structure-setup-and-first-code
Fixes #1 - Structure setup and first code commit
2025-12-17 11:26:20 -05:00
Andre Courchesne
46950d9ff1 #1 - Structure setup and first code commit 2025-12-16 10:19:53 -05:00
22 changed files with 7451 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
.vagrant
*.iso
box/
output-*/
manifest.json
files/iso_release
images/efiboot.img
*.swp
kickstart/devops_packages.cfg
*packages.yml
*.zip
.DS_Store
loop
firstboot/firstboot

19
CHANGELOG.md Normal file
View File

@@ -0,0 +1,19 @@
# Changelog
All notable changes to this project will be documented in this file.
NOTE: the project follows [Semantic Versioning](http://semver.org/).
## v0.0.3 - March 11th, 2026
- Updated ucentral-client Docker image to olgV8
- Includes updated ucentral-schema from ra_proposal branch (SDK commit 6491570)
- VyOS integration improvements and schema enhancements
## v0.0.2 - February 12th, 2026
- Add Ucentral support and documentation
## v0.0.1 - December 17th 2025
- First release

130
README.md Normal file
View File

@@ -0,0 +1,130 @@
# Installer for OpenLAN Gateway
Project aimed at building ISOs for OpenLAN Gateway
## Requirements
- Docker
- Linux or macOS operating system (sorry not sorry Windows)
## Building an ISO
1. Run `script/build`
The result of this will be an ISO in the project working folder.
## Installing OpenLAN Gateway
### Install from ISO and VyOS VM configuration
- Boot on the ISO, once the install is completed the server will power-off
- Power back the server
- Login to the Linux host with username `olgadm` and password `olgadm`
- Edit `/opt/staging_scripts/setup-config` and adjust the network interface names and if required the VyOS VM sizing parameters
- If you will be using the "Standalone mode", you might need to adjust the VyOS rolling release path. Reference: https://github.com/vyos/vyos-nightly-build/releases
- Run the setup script:
- `sudo /opt/staging_scripts/setup-vyos-bridge.sh` to use the network bridge method
- `sudo /opt/staging_scripts/setup-vyos-hw-passthru.sh` to use the hardware passthru for the network interfaces (WIP, not tested)
- Reboot the host
- Connect to the VyOS console with `virsh console vyos`
- Login with username `vyos` and password `vyos`
- Type `install image` and press Enter.
- Follow the prompts (you can use all defaults)
- Once completed, type `reboot` to reboot the VM
- For some reason the VyOS VM does not reboot after this first `reboot` command. You must restart it manually with `virsh start vyos`
## Configuration options
At this time you can either load a default configuration and use VyOS in a "standalone" mode or have it connected to an OpenWifi Cloud SDK instance.
### OpenWifi Cloud SDK mode
### Setup of Ucentral-Client Container
- SSH to the OLG Ubuntu host
- Run `sudo /opt/staging_scripts/ucentral-setup.sh setup` to setup the ucentral-client container
- You can also use the following parameters for the `ucentral-setup.sh` script:
- `shell`: To get access to ucentral container.
- `cleanup`: To clean the setup.
- Copy your certificates to olg and then to container at `/etc/ucentral` in order to work with your cloud-controller.
- `sudo docker cp cert.pem ucentral-olg:/etc/ucentral/operational.pem`
- `sudo docker cp cas.pem ucentral-olg:/etc/ucentral/operational.ca`
- `sudo docker cp key.pem ucentral-olg:/etc/ucentral/`
- Run `sudo /opt/staging_scripts/ucentral-setup.sh shell` to get shell access to the ucentral container and perform the following tasks:
- Modify `/etc/ucentral/vyos-info.json` and update `host` value to the IP address assigned to the VyOS VM br-wan interface.
- Start the ucentral-client:
- Debug mode: `SERIALNUM=my_olg_serial ; URL=my_cloudsdk_uri ; /usr/sbin/ucentral -S $SERIALNUM -s $URL -P 15002 -d`
- Deamonized mode: `SERIALNUM=my_olg_serial ; URL=my_cloudsdk_uri ; /usr/sbin/ucentral -S $SERIALNUM -s $URL -P 15002 -d &`
- For example `SERIALNUM=74d4ddb965dc` for which certs are generated, `URL=openwifi1.routerarchitects.com`, for your OpenWifi Cloud SDK instance.
> [!WARNING]
> Ucentral Client must be started only after VyOS gets started.
>
> There is a bit of a chicken-and-the-egg scenario if the OLG device was never seen by the OpenWifi Cloud SDK instance. A blank configuration will be pushed to VyOS and the connection with the ucentral client might be broken.
>
> At this time the order of execution should be the following if the OLG device was never seen by the OpenWifi Cloud SDK instance:
> - Stop the VyOS VM with `virsh shutdown vyos`
> - Start the ucentral-client
> - Populate a configuration in the OpenWifi Cloud SDK for the OLG device
> - Restart the VyOS VM with `virsh start vyos`
> - If IP Address of VyOS VM gets changed then reconfigure value of `host` in `/etc/ucentral/vyos-info.json` , in ucentral and restart ucentral client.
### Standalone mode
The factory configuration consists of:
- `eth0` as the WAN interface in DHCP
- `eth1` as the LAN interface
- 3 VLANs:
- VLAN 100 for the switches
- VLAN 101 for the APs
- VLAN 1000 for the guest devices
- Each VLAN has it's own DHCP scope
Here is how to load this configuration:
- Open a console to the VyOS console with `virsh console vyos`
- If required login with your credentials
- Mound the ISO containing the configs
```
sudo mkdir /opt/vyos-configs ; sudo mount /dev/sr1 /opt/vyos-configs
```
- Go in config mode with `config`
- Load the factory config with:
```
source /opt/olg-configs/vyos-factory-config
commit
save
exit
```
## Sample UCentral configurations
Here are some sample configuration(s)
| File path | Description |
|-----------|-------------|
| [mdu.json](sample-configurations/mdu.json) | MDU configuration with two VLAN networks and also adhere to olg ucentral schema enhancement for NAT object as proposed in [olg-ucentral-schema ra_proposal branch](https://github.com/Telecominfraproject/olg-ucentral-schema/tree/ra_proposal) |
## Tested platforms
- MinisForum MS-01
## Contributing
- Create an issue
- Create a branch and an assoiated PR
- Code
- Ask for review and get your changes merged
## Protip
Use the Shipit CLI (https://gitlab.com/intello/shipit-cli-go)
This allows you to create the branch and associated PR in one simple command. The branch and PR will use a standardized naming scheme.
![image](docs/shipit-screenshot.png)

BIN
docs/shipit-screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

12
grub.cfg Normal file
View File

@@ -0,0 +1,12 @@
set timeout=30
insmod all_video
insmod png
loadfont unicode
gfxpayload text
ISO_VERSION="v0.0.3"
menuentry "Install Open LAN Gateway (ISO $ISO_VERSION)" {
linux /casper/vmlinuz autoinstall fsck.mode=skip ds=nocloud\;s=/cdrom/nocloud/ ipv6.disable=1 console=ttyS0,115200n8 console=tty0 network-config=disabled ---
initrd /casper/initrd
}

View File

@@ -0,0 +1,6 @@
network:
version: 2
ethernets:
{{ HOST_ADMIN_PORT }}: # Admin port
dhcp4: true
dhcp6: false

View File

@@ -0,0 +1,20 @@
# Add /opt to the path
PATH=$PATH:/opt
export PATH
# Some useful aliases
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias reboot='shutdown -rf now'
alias ip4='ip -c -o -4'
alias ip6='ip -c -o -6'
# Source global definitions
if [ -f /etc/bashrc ]; then
source /etc/bashrc
fi
# Terminal tweaks
export LS_COLORS="no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=01;32:*.cmd=01;32:*.exe=01;32:*.com=01;32:*.btm=01;32:*.bat=01;32:*.sh=01;32:*.csh=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tz=01;31:*.rpm=01;31:*.cpio=01;31:*.jpg=01;35:*.gif=01;35:*.bmp=01;35:*.xbm=01;35:*.xpm=01;35:*.png=01;35:*.tif=01;35:"
export TERM=xterm-color

115
iso-files/destroy-vyos-vm.sh Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -euo pipefail
VM_NAME="vyos"
IMAGES_DIR="/var/lib/libvirt/images"
ISO_PATH="$IMAGES_DIR/vyos.iso"
DISK_PATH="$IMAGES_DIR/${VM_NAME}.qcow2"
NETPLAN_FILE="/etc/netplan/99-vyos-bridges.yaml"
# ====== Preflight ======
if [[ $EUID -ne 0 ]]; then
echo "Please run as root (sudo $0)"; exit 1
fi
echo "============================================"
echo "VyOS VM Destruction Script"
echo "============================================"
echo ""
echo "This script will:"
echo " 1. Stop and destroy the VyOS VM"
echo " 2. Undefine the VM from libvirt"
echo " 3. Remove VM disk image"
echo " 4. Optionally remove VyOS ISO"
echo " 5. Optionally remove bridge network configuration"
echo ""
# Check if VM exists
if ! virsh dominfo "$VM_NAME" >/dev/null 2>&1; then
echo ">>> VM '$VM_NAME' does not exist or is not defined."
else
# Check if VM is running
if virsh list --state-running | grep -q "$VM_NAME"; then
echo ">>> Stopping VM '$VM_NAME'..."
virsh destroy "$VM_NAME"
echo " VM stopped."
else
echo ">>> VM '$VM_NAME' is not running."
fi
# Undefine the VM
echo ">>> Undefining VM '$VM_NAME'..."
virsh undefine "$VM_NAME" --nvram 2>/dev/null || virsh undefine "$VM_NAME"
echo " VM undefined."
fi
# Remove disk image
if [[ -f "$DISK_PATH" ]]; then
echo ">>> Removing VM disk: $DISK_PATH"
rm -f "$DISK_PATH"
echo " Disk removed."
else
echo ">>> VM disk not found at $DISK_PATH (already removed or never created)."
fi
# Ask about ISO removal
if [[ -f "$ISO_PATH" ]]; then
echo ""
read -p "Remove VyOS ISO at $ISO_PATH? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo ">>> Removing VyOS ISO: $ISO_PATH"
rm -f "$ISO_PATH"
echo " ISO removed."
else
echo ">>> Keeping VyOS ISO at $ISO_PATH"
fi
else
echo ">>> VyOS ISO not found at $ISO_PATH (already removed or never downloaded)."
fi
# Ask about bridge network configuration removal
if [[ -f "$NETPLAN_FILE" ]]; then
echo ""
echo "WARNING: Removing the netplan bridge configuration will restore"
echo " the network interfaces to their previous state, but may"
echo " cause network disruption."
echo ""
read -p "Remove bridge network configuration at $NETPLAN_FILE? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo ">>> Removing netplan bridge configuration: $NETPLAN_FILE"
rm -f "$NETPLAN_FILE"
echo " Configuration file removed."
echo ""
read -p "Apply netplan changes now? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo ">>> Applying netplan..."
netplan apply
echo " Netplan applied."
else
echo ">>> Skipping netplan apply. Run 'netplan apply' manually to restore interfaces."
fi
else
echo ">>> Keeping bridge network configuration at $NETPLAN_FILE"
fi
else
echo ">>> Bridge network configuration not found at $NETPLAN_FILE"
fi
echo ""
echo "============================================"
echo "VyOS VM Cleanup Complete!"
echo "============================================"
echo ""
echo "Summary:"
echo " - VM '$VM_NAME' has been destroyed and undefined"
echo " - VM disk has been removed"
echo ""
echo "Note: If you used PCI passthrough (setup-vyos-hw-passthru.sh),"
echo " the physical network interfaces should automatically return"
echo " to the host when the VM is destroyed. You may need to reload"
echo " the appropriate driver or reboot to fully restore them."
echo ""

764
iso-files/get-docker.sh Normal file
View File

@@ -0,0 +1,764 @@
#!/bin/sh
set -e
# Docker Engine for Linux installation script.
#
# This script is intended as a convenient way to configure docker's package
# repositories and to install Docker Engine, This script is not recommended
# for production environments. Before running this script, make yourself familiar
# with potential risks and limitations, and refer to the installation manual
# at https://docs.docker.com/engine/install/ for alternative installation methods.
#
# The script:
#
# - Requires `root` or `sudo` privileges to run.
# - Attempts to detect your Linux distribution and version and configure your
# package management system for you.
# - Doesn't allow you to customize most installation parameters.
# - Installs dependencies and recommendations without asking for confirmation.
# - Installs the latest stable release (by default) of Docker CLI, Docker Engine,
# Docker Buildx, Docker Compose, containerd, and runc. When using this script
# to provision a machine, this may result in unexpected major version upgrades
# of these packages. Always test upgrades in a test environment before
# deploying to your production systems.
# - Isn't designed to upgrade an existing Docker installation. When using the
# script to update an existing installation, dependencies may not be updated
# to the expected version, resulting in outdated versions.
#
# Source code is available at https://github.com/docker/docker-install/
#
# Usage
# ==============================================================================
#
# To install the latest stable versions of Docker CLI, Docker Engine, and their
# dependencies:
#
# 1. download the script
#
# $ curl -fsSL https://get.docker.com -o install-docker.sh
#
# 2. verify the script's content
#
# $ cat install-docker.sh
#
# 3. run the script with --dry-run to verify the steps it executes
#
# $ sh install-docker.sh --dry-run
#
# 4. run the script either as root, or using sudo to perform the installation.
#
# $ sudo sh install-docker.sh
#
# Command-line options
# ==============================================================================
#
# --version <VERSION>
# Use the --version option to install a specific version, for example:
#
# $ sudo sh install-docker.sh --version 23.0
#
# --channel <stable|test>
#
# Use the --channel option to install from an alternative installation channel.
# The following example installs the latest versions from the "test" channel,
# which includes pre-releases (alpha, beta, rc):
#
# $ sudo sh install-docker.sh --channel test
#
# Alternatively, use the script at https://test.docker.com, which uses the test
# channel as default.
#
# --mirror <Aliyun|AzureChinaCloud>
#
# Use the --mirror option to install from a mirror supported by this script.
# Available mirrors are "Aliyun" (https://mirrors.aliyun.com/docker-ce), and
# "AzureChinaCloud" (https://mirror.azure.cn/docker-ce), for example:
#
# $ sudo sh install-docker.sh --mirror AzureChinaCloud
#
# --setup-repo
#
# Use the --setup-repo option to configure Docker's package repositories without
# installing Docker packages. This is useful when you want to add the repository
# but install packages separately:
#
# $ sudo sh install-docker.sh --setup-repo
#
# Automatic Service Start
#
# By default, this script automatically starts the Docker daemon and enables the docker
# service after installation if systemd is used as init.
#
# If you prefer to start the service manually, use the --no-autostart option:
#
# $ sudo sh install-docker.sh --no-autostart
#
# Note: Starting the service requires appropriate privileges to manage system services.
#
# ==============================================================================
# Git commit from https://github.com/docker/docker-install when
# the script was uploaded (Should only be modified by upload job):
SCRIPT_COMMIT_SHA="f381ee68b32e515bb4dc034b339266aff1fbc460"
# strip "v" prefix if present
VERSION="${VERSION#v}"
# The channel to install from:
# * stable
# * test
DEFAULT_CHANNEL_VALUE="stable"
if [ -z "$CHANNEL" ]; then
CHANNEL=$DEFAULT_CHANNEL_VALUE
fi
DEFAULT_DOWNLOAD_URL="https://download.docker.com"
if [ -z "$DOWNLOAD_URL" ]; then
DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL
fi
DEFAULT_REPO_FILE="docker-ce.repo"
if [ -z "$REPO_FILE" ]; then
REPO_FILE="$DEFAULT_REPO_FILE"
# Automatically default to a staging repo fora
# a staging download url (download-stage.docker.com)
case "$DOWNLOAD_URL" in
*-stage*) REPO_FILE="docker-ce-staging.repo";;
esac
fi
mirror=''
DRY_RUN=${DRY_RUN:-}
REPO_ONLY=${REPO_ONLY:-0}
NO_AUTOSTART=${NO_AUTOSTART:-0}
while [ $# -gt 0 ]; do
case "$1" in
--channel)
CHANNEL="$2"
shift
;;
--dry-run)
DRY_RUN=1
;;
--mirror)
mirror="$2"
shift
;;
--version)
VERSION="${2#v}"
shift
;;
--setup-repo)
REPO_ONLY=1
shift
;;
--no-autostart)
NO_AUTOSTART=1
;;
--*)
echo "Illegal option $1"
;;
esac
shift $(( $# > 0 ? 1 : 0 ))
done
case "$mirror" in
Aliyun)
DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
;;
AzureChinaCloud)
DOWNLOAD_URL="https://mirror.azure.cn/docker-ce"
;;
"")
;;
*)
>&2 echo "unknown mirror '$mirror': use either 'Aliyun', or 'AzureChinaCloud'."
exit 1
;;
esac
case "$CHANNEL" in
stable|test)
;;
*)
>&2 echo "unknown CHANNEL '$CHANNEL': use either stable or test."
exit 1
;;
esac
command_exists() {
command -v "$@" > /dev/null 2>&1
}
# version_gte checks if the version specified in $VERSION is at least the given
# SemVer (Maj.Minor[.Patch]), or CalVer (YY.MM) version.It returns 0 (success)
# if $VERSION is either unset (=latest) or newer or equal than the specified
# version, or returns 1 (fail) otherwise.
#
# examples:
#
# VERSION=23.0
# version_gte 23.0 // 0 (success)
# version_gte 20.10 // 0 (success)
# version_gte 19.03 // 0 (success)
# version_gte 26.1 // 1 (fail)
version_gte() {
if [ -z "$VERSION" ]; then
return 0
fi
version_compare "$VERSION" "$1"
}
# version_compare compares two version strings (either SemVer (Major.Minor.Path),
# or CalVer (YY.MM) version strings. It returns 0 (success) if version A is newer
# or equal than version B, or 1 (fail) otherwise. Patch releases and pre-release
# (-alpha/-beta) are not taken into account
#
# examples:
#
# version_compare 23.0.0 20.10 // 0 (success)
# version_compare 23.0 20.10 // 0 (success)
# version_compare 20.10 19.03 // 0 (success)
# version_compare 20.10 20.10 // 0 (success)
# version_compare 19.03 20.10 // 1 (fail)
version_compare() (
set +x
yy_a="$(echo "$1" | cut -d'.' -f1)"
yy_b="$(echo "$2" | cut -d'.' -f1)"
if [ "$yy_a" -lt "$yy_b" ]; then
return 1
fi
if [ "$yy_a" -gt "$yy_b" ]; then
return 0
fi
mm_a="$(echo "$1" | cut -d'.' -f2)"
mm_b="$(echo "$2" | cut -d'.' -f2)"
# trim leading zeros to accommodate CalVer
mm_a="${mm_a#0}"
mm_b="${mm_b#0}"
if [ "${mm_a:-0}" -lt "${mm_b:-0}" ]; then
return 1
fi
return 0
)
is_dry_run() {
if [ -z "$DRY_RUN" ]; then
return 1
else
return 0
fi
}
is_wsl() {
case "$(uname -r)" in
*microsoft* ) true ;; # WSL 2
*Microsoft* ) true ;; # WSL 1
* ) false;;
esac
}
is_darwin() {
case "$(uname -s)" in
*darwin* ) true ;;
*Darwin* ) true ;;
* ) false;;
esac
}
deprecation_notice() {
distro=$1
distro_version=$2
echo
printf "\033[91;1mDEPRECATION WARNING\033[0m\n"
printf " This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version"
echo " No updates or security fixes will be released for this distribution, and users are recommended"
echo " to upgrade to a currently maintained version of $distro."
echo
printf "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue."
echo
sleep 10
}
get_distribution() {
lsb_dist=""
# Every system that we officially support has /etc/os-release
if [ -r /etc/os-release ]; then
lsb_dist="$(. /etc/os-release && echo "$ID")"
fi
# Returning an empty string here should be alright since the
# case statements don't act unless you provide an actual value
echo "$lsb_dist"
}
start_docker_daemon() {
# Use systemctl if available (for systemd-based systems)
if command_exists systemctl; then
is_dry_run || >&2 echo "Using systemd to manage Docker service"
if (
is_dry_run || set -x
$sh_c systemctl enable --now docker.service 2>/dev/null
); then
is_dry_run || echo "INFO: Docker daemon enabled and started" >&2
else
is_dry_run || echo "WARNING: unable to enable the docker service" >&2
fi
else
# No service management available (container environment)
if ! is_dry_run; then
>&2 echo "Note: Running in a container environment without service management"
>&2 echo "Docker daemon cannot be started automatically in this environment"
>&2 echo "The Docker packages have been installed successfully"
fi
fi
>&2 echo
}
echo_docker_as_nonroot() {
if is_dry_run; then
return
fi
if command_exists docker && [ -e /var/run/docker.sock ]; then
(
set -x
$sh_c 'docker version'
) || true
fi
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
echo
echo "================================================================================"
echo
if version_gte "20.10"; then
echo "To run Docker as a non-privileged user, consider setting up the"
echo "Docker daemon in rootless mode for your user:"
echo
echo " dockerd-rootless-setuptool.sh install"
echo
echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode."
echo
fi
echo
echo "To run the Docker daemon as a fully privileged service, but granting non-root"
echo "users access, refer to https://docs.docker.com/go/daemon-access/"
echo
echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent"
echo " to root access on the host. Refer to the 'Docker daemon attack surface'"
echo " documentation for details: https://docs.docker.com/go/attack-surface/"
echo
echo "================================================================================"
echo
}
# Check if this is a forked Linux distro
check_forked() {
# Check for lsb_release command existence, it usually exists in forked distros
if command_exists lsb_release; then
# Check if the `-u` option is supported
set +e
lsb_release -a -u > /dev/null 2>&1
lsb_release_exit_code=$?
set -e
# Check if the command has exited successfully, it means we're in a forked distro
if [ "$lsb_release_exit_code" = "0" ]; then
# Print info about current distro
cat <<-EOF
You're using '$lsb_dist' version '$dist_version'.
EOF
# Get the upstream release info
lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]')
dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]')
# Print info about upstream distro
cat <<-EOF
Upstream release is '$lsb_dist' version '$dist_version'.
EOF
else
if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then
if [ "$lsb_dist" = "osmc" ]; then
# OSMC runs Raspbian
lsb_dist=raspbian
else
# We're Debian and don't even know it!
lsb_dist=debian
fi
dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
case "$dist_version" in
13|14|forky)
dist_version="trixie"
;;
12)
dist_version="bookworm"
;;
11)
dist_version="bullseye"
;;
10)
dist_version="buster"
;;
9)
dist_version="stretch"
;;
8)
dist_version="jessie"
;;
esac
fi
fi
fi
}
do_install() {
echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA"
if command_exists docker; then
cat >&2 <<-'EOF'
Warning: the "docker" command appears to already exist on this system.
If you already have Docker installed, this script can cause trouble, which is
why we're displaying this warning and provide the opportunity to cancel the
installation.
If you installed the current Docker package using this script and are using it
again to update Docker, you can ignore this message, but be aware that the
script resets any custom changes in the deb and rpm repo configuration
files to match the parameters passed to the script.
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
user="$(id -un 2>/dev/null || true)"
sh_c='sh -c'
if [ "$user" != 'root' ]; then
if command_exists sudo; then
sh_c='sudo -E sh -c'
elif command_exists su; then
sh_c='su -c'
else
cat >&2 <<-'EOF'
Error: this installer needs the ability to run commands as root.
We are unable to find either "sudo" or "su" available to make this happen.
EOF
exit 1
fi
fi
if is_dry_run; then
sh_c="echo"
fi
# perform some very rudimentary platform detection
lsb_dist=$( get_distribution )
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
if is_wsl; then
echo
echo "WSL DETECTED: We recommend using Docker Desktop for Windows."
echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop/"
echo
cat >&2 <<-'EOF'
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
case "$lsb_dist" in
ubuntu)
if command_exists lsb_release; then
dist_version="$(lsb_release --codename | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
fi
;;
debian|raspbian)
dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
case "$dist_version" in
13)
dist_version="trixie"
;;
12)
dist_version="bookworm"
;;
11)
dist_version="bullseye"
;;
10)
dist_version="buster"
;;
9)
dist_version="stretch"
;;
8)
dist_version="jessie"
;;
esac
;;
centos|rhel)
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
*)
if command_exists lsb_release; then
dist_version="$(lsb_release --release | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
esac
# Check if this is a forked Linux distro
check_forked
# Print deprecation warnings for distro versions that recently reached EOL,
# but may still be commonly used (especially LTS versions).
case "$lsb_dist.$dist_version" in
centos.8|centos.7|rhel.7)
deprecation_notice "$lsb_dist" "$dist_version"
;;
debian.buster|debian.stretch|debian.jessie)
deprecation_notice "$lsb_dist" "$dist_version"
;;
raspbian.buster|raspbian.stretch|raspbian.jessie)
deprecation_notice "$lsb_dist" "$dist_version"
;;
ubuntu.focal|ubuntu.bionic|ubuntu.xenial|ubuntu.trusty)
deprecation_notice "$lsb_dist" "$dist_version"
;;
ubuntu.oracular|ubuntu.mantic|ubuntu.lunar|ubuntu.kinetic|ubuntu.impish|ubuntu.hirsute|ubuntu.groovy|ubuntu.eoan|ubuntu.disco|ubuntu.cosmic)
deprecation_notice "$lsb_dist" "$dist_version"
;;
fedora.*)
if [ "$dist_version" -lt 41 ]; then
deprecation_notice "$lsb_dist" "$dist_version"
fi
;;
esac
# Run setup for each distro accordingly
case "$lsb_dist" in
ubuntu|debian|raspbian)
pre_reqs="ca-certificates curl"
apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL"
(
if ! is_dry_run; then
set -x
fi
$sh_c 'apt-get -qq update >/dev/null'
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get -y -qq install $pre_reqs >/dev/null"
$sh_c 'install -m 0755 -d /etc/apt/keyrings'
$sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" -o /etc/apt/keyrings/docker.asc"
$sh_c "chmod a+r /etc/apt/keyrings/docker.asc"
$sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list"
$sh_c 'apt-get -qq update >/dev/null'
)
if [ "$REPO_ONLY" = "1" ]; then
exit 0
fi
pkg_version=""
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
# Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel
pkg_pattern="$(echo "$VERSION" | sed 's/-ce-/~ce~.*/g' | sed 's/-/.*/g')"
search_command="apt-cache madison docker-ce | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst apt-cache madison results"
echo
exit 1
fi
if version_gte "18.09"; then
search_command="apt-cache madison docker-ce-cli | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
echo "INFO: $search_command"
cli_pkg_version="=$($sh_c "$search_command")"
fi
pkg_version="=$pkg_version"
fi
fi
(
pkgs="docker-ce${pkg_version%=}"
if version_gte "18.09"; then
# older versions didn't ship the cli and containerd as separate packages
pkgs="$pkgs docker-ce-cli${cli_pkg_version%=} containerd.io"
fi
if version_gte "20.10"; then
pkgs="$pkgs docker-compose-plugin docker-ce-rootless-extras$pkg_version"
fi
if version_gte "23.0"; then
pkgs="$pkgs docker-buildx-plugin"
fi
if version_gte "28.2"; then
pkgs="$pkgs docker-model-plugin"
fi
if ! is_dry_run; then
set -x
fi
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get -y -qq install $pkgs >/dev/null"
)
if [ "$NO_AUTOSTART" != "1" ]; then
start_docker_daemon
fi
echo_docker_as_nonroot
exit 0
;;
centos|fedora|rhel)
if [ "$(uname -m)" = "s390x" ]; then
echo "Effective v27.5, please consult RHEL distro statement for s390x support."
exit 1
fi
repo_file_url="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
(
if ! is_dry_run; then
set -x
fi
if command_exists dnf5; then
$sh_c "dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core"
$sh_c "dnf5 config-manager addrepo --overwrite --save-filename=docker-ce.repo --from-repofile='$repo_file_url'"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "dnf5 config-manager setopt \"docker-ce-*.enabled=0\""
$sh_c "dnf5 config-manager setopt \"docker-ce-$CHANNEL.enabled=1\""
fi
$sh_c "dnf makecache"
elif command_exists dnf; then
$sh_c "dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core"
$sh_c "rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo"
$sh_c "dnf config-manager --add-repo $repo_file_url"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "dnf config-manager --set-disabled \"docker-ce-*\""
$sh_c "dnf config-manager --set-enabled \"docker-ce-$CHANNEL\""
fi
$sh_c "dnf makecache"
else
$sh_c "yum -y -q install yum-utils"
$sh_c "rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo"
$sh_c "yum-config-manager --add-repo $repo_file_url"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "yum-config-manager --disable \"docker-ce-*\""
$sh_c "yum-config-manager --enable \"docker-ce-$CHANNEL\""
fi
$sh_c "yum makecache"
fi
)
if [ "$REPO_ONLY" = "1" ]; then
exit 0
fi
pkg_version=""
if command_exists dnf; then
pkg_manager="dnf"
pkg_manager_flags="-y -q --best"
else
pkg_manager="yum"
pkg_manager_flags="-y -q"
fi
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
if [ "$lsb_dist" = "fedora" ]; then
pkg_suffix="fc$dist_version"
else
pkg_suffix="el"
fi
pkg_pattern="$(echo "$VERSION" | sed 's/-ce-/\\\\.ce.*/g' | sed 's/-/.*/g').*$pkg_suffix"
search_command="$pkg_manager list --showduplicates docker-ce | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst $pkg_manager list results"
echo
exit 1
fi
if version_gte "18.09"; then
# older versions don't support a cli package
search_command="$pkg_manager list --showduplicates docker-ce-cli | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)"
fi
# Cut out the epoch and prefix with a '-'
pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)"
fi
fi
(
pkgs="docker-ce$pkg_version"
if version_gte "18.09"; then
# older versions didn't ship the cli and containerd as separate packages
if [ -n "$cli_pkg_version" ]; then
pkgs="$pkgs docker-ce-cli-$cli_pkg_version containerd.io"
else
pkgs="$pkgs docker-ce-cli containerd.io"
fi
fi
if version_gte "20.10"; then
pkgs="$pkgs docker-compose-plugin docker-ce-rootless-extras$pkg_version"
fi
if version_gte "23.0"; then
pkgs="$pkgs docker-buildx-plugin docker-model-plugin"
fi
if ! is_dry_run; then
set -x
fi
$sh_c "$pkg_manager $pkg_manager_flags install $pkgs"
)
if [ "$NO_AUTOSTART" != "1" ]; then
start_docker_daemon
fi
echo_docker_as_nonroot
exit 0
;;
sles)
echo "Effective v27.5, please consult SLES distro statement for s390x support."
exit 1
;;
*)
if [ -z "$lsb_dist" ]; then
if is_darwin; then
echo
echo "ERROR: Unsupported operating system 'macOS'"
echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
echo
exit 1
fi
fi
echo
echo "ERROR: Unsupported distribution '$lsb_dist'"
echo
exit 1
;;
esac
exit 1
}
# wrapped up in a function so that we have some protection against only getting
# half the file during "curl | sh"
do_install

60
iso-files/set-hostname Normal file
View File

@@ -0,0 +1,60 @@
#!/bin/bash
echo " * Starting : set_hostname"
if [ ! -e /tmp/previous_install_hostname ]; then
serial=`cat /sys/class/dmi/id/product_serial | tr '[:upper:]' '[:lower:]'`
product_name=`cat /sys/class/dmi/id/product_name`
if [ "$product_name" == "Default string" ] || [ "$product_name" == "" ]; then
product_name=`cat /sys/class/dmi/id/board_name`
fi
if [ "$serial" == "0" ]; then
hostname="olg-vbox-$RANDOM"
elif [ "$product_name" == "FW4A" ] || [ "$product_name" == "FW4B" ] || [ "$product_name" == "Aptio CRB" ]; then
unique_id=`cat /sys/class/net/enp1s0/address | sed 's/://g'`
hostname="olg-$unique_id"
elif [ "$product_name" == "VP2410" ]; then
unique_id=`cat /sys/class/net/enp2s0/address | sed 's/://g'`
hostname="olg-$unique_id"
elif [ "$serial" == "" ] || [ "$serial" == "default string" ]; then
macaddress=`cat /sys/class/net/*/address | grep -v "00:00:00:00:00:00" | sort |head -n 1`
if [ "${macaddress}" != "" ]; then
unique_id=`echo ${macaddress} | sed 's/://g'`
hostname="olg-$unique_id"
else
hostname="olg-unknown-$RANDOM"
fi
elif [ "$product_name" == "VMware Virtual Platform" ]; then
if [ -e /sys/class/net/eth1/address ]; then
unique_id=`cat /sys/class/net/eth1/address | sed 's/://g'`
hostname="olg-vmware-$unique_id"
elif [ -e /sys/class/net/ens33/address ]; then
unique_id=`cat /sys/class/net/ens33/address | sed 's/://g'`
hostname="olg-vmware-$unique_id"
elif [ -e /sys/class/net/ens192/address ]; then
unique_id=`cat /sys/class/net/ens192/address | sed 's/://g'`
hostname="olg-vmware-$unique_id"
else
hostname="olg-vmware-$RANDOM"
fi
else
ipmi_mac=`ipmitool lan print | grep "MAC Address" | awk '{print $4}' | sed 's/://g'`
if [ ! -z ${ipmi_mac} ]; then
hostname="olg-$ipmi_mac"
else
hostname="olg-$serial"
fi
fi
else
hostname=`cat /tmp/previous_install_hostname`
fi
# Remove any spaces
hostname=${hostname//[[:blank:]]/}
hostnamectl set-hostname ${hostname}
echo "Product name : '$product_name'"
echo "DMI Serial : '$serial'"
echo "Got hostname : '$hostname'"
echo "network --hostname='$hostname'"
echo " * Done : set-hostname"

17
iso-files/setup-config Normal file
View File

@@ -0,0 +1,17 @@
VCPUS="4"
RAM_MB="16384"
DISK_GB="50"
WAN_IF="enp2s0f0np0"
LAN_IF="enp2s0f1np1"
ADMIN_IF="enx000ec6f01419"
INTEL_AMT_PORT="enp88s0"
OLG_ISO_PLATFORM="VM" # BAREMETAL or VM
# RouterArchitects ISO (for OpenWifi Cloud SDK mode)
ISO_URL="'https://drive.usercontent.google.com/download?id=14W0hnFhM64b8_jn1CwDWiPybntIrBzlh&confirm=t&uuid=e9d7cc8d-0a8d-483f-a650-af5ca22ace15&at=APcXIO18cq3_AFU_XdJgtU1JXTyd%3A1770305061284'"
ISO_SHA256="3d6ad7bc5b5f51566cf8353cf231c395390a509588c4c855a7bd37df275d104e"
# VyOS official ISO (for stand-alone mode)
#ISO_URL="'https://github.com/vyos/vyos-nightly-build/releases/download/2026.02.03-0027-rolling/vyos-2026.02.03-0027-rolling-generic-amd64.iso'"
#ISO_SHA256="26ed11122794d4e3a457abfbdc54bd77996966e0d7e714bf4c8f66a16f0a6673"

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env bash
set -euo pipefail
# Source our configs
source /opt/staging_scripts/setup-config
# Some statis configs
VM_NAME="vyos"
IMAGES_DIR="/var/lib/libvirt/images"
ISO_PATH="$IMAGES_DIR/vyos.iso"
DISK_PATH="$IMAGES_DIR/${VM_NAME}.qcow2"
BR_WAN="br-wan"
BR_LAN="br-lan"
NETPLAN_FILE="/etc/netplan/99-vyos-bridges.yaml"
# Default: no DHCP on WAN bridge
BR_WAN_DHCP4="false"
# OLG_ISO_PLATFORM is where this ISO is running
# Valid values: BAREMETAL | VM . Extensible (later: CLOUD, etc)
if [[ "${OLG_ISO_PLATFORM}" == "VM" ]]; then
BR_WAN_DHCP4="true"
fi
# Make sure we are running as root
if [[ $EUID -ne 0 ]]; then
echo "Please run as root (sudo $0)"; exit 1
fi
# Make sure we have netplan available
command -v netplan >/dev/null 2>&1 || { echo "netplan not found. This script targets Ubuntu with netplan."; exit 1; }
# Make sure we have all the minimum required interfaces
for IFACE in "$WAN_IF" "$LAN_IF"; do
if ! ip link show "$IFACE" >/dev/null 2>&1; then
echo "Interface $IFACE not found. Adjust WAN_IF/LAN_IF in the script."; exit 1
fi
done
echo ">>> Set the host hostname"
/opt/staging_scripts/set-hostname
echo ">>> Installing virtualization packages..."
apt-get update -y
apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients virtinst bridge-utils cloud-image-utils libguestfs-tools xorriso genisoimage syslinux-utils
echo ">>> Installing Docker..."
sh /opt/staging_scripts/get-docker.sh
# Get VyOS ISO
# All Downloads/Updates should be done on stable network, before network configurations are changed
if [[ ! -f "$ISO_PATH" ]]; then
echo ">>> Downloading VyOS ISO to $ISO_PATH"
echo -e "#!/bin/bash\ncurl -fL $ISO_URL -o $ISO_PATH" >download_iso.sh
chmod +x download_iso.sh
./download_iso.sh
rm -f download_iso.sh
# Verify SHA256 checksum
echo ">>> Verifying SHA256 checksum..."
ACTUAL_SHA256=$(sha256sum "$ISO_PATH" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$ISO_SHA256" ]]; then
echo "ERROR: SHA256 checksum mismatch!"
echo "Expected: $ISO_SHA256"
echo "Actual: $ACTUAL_SHA256"
echo "Removing corrupted ISO file..."
rm -f "$ISO_PATH"
exit 1
fi
echo ">>> SHA256 checksum verified successfully"
else
echo ">>> VyOS ISO already present at $ISO_PATH"
# Verify SHA256 checksum of existing file
echo ">>> Verifying SHA256 checksum of existing ISO..."
ACTUAL_SHA256=$(sha256sum "$ISO_PATH" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$ISO_SHA256" ]]; then
echo "WARNING: SHA256 checksum mismatch for existing ISO!"
echo "Expected: $ISO_SHA256"
echo "Actual: $ACTUAL_SHA256"
echo "Removing existing ISO and re-downloading..."
rm -f "$ISO_PATH"
echo -e "#!/bin/bash\ncurl -fL $ISO_URL -o $ISO_PATH" >download_iso.sh
chmod +x download_iso.sh
./download_iso.sh
rm -f download_iso.sh
# Verify SHA256 checksum of new download
echo ">>> Verifying SHA256 checksum..."
ACTUAL_SHA256=$(sha256sum "$ISO_PATH" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$ISO_SHA256" ]]; then
echo "ERROR: SHA256 checksum mismatch!"
echo "Expected: $ISO_SHA256"
echo "Actual: $ACTUAL_SHA256"
echo "Removing corrupted ISO file..."
rm -f "$ISO_PATH"
exit 1
fi
echo ">>> SHA256 checksum verified successfully"
else
echo ">>> SHA256 checksum verified successfully"
fi
fi
echo ">>> Ensuring libvirtd is running..."
systemctl enable --now libvirtd
mkdir -p "$IMAGES_DIR"
# Configure host bridges via netplan
echo ">>> Writing netplan to create $BR_WAN (via $WAN_IF) and $BR_LAN (via $LAN_IF): $NETPLAN_FILE"
cat > "$NETPLAN_FILE" <<EOF
network:
version: 2
renderer: networkd
ethernets:
${WAN_IF}:
dhcp4: false
dhcp6: false
${LAN_IF}:
dhcp4: false
dhcp6: false
bridges:
${BR_WAN}:
interfaces: [${WAN_IF}]
dhcp4: ${BR_WAN_DHCP4}
dhcp6: false
parameters:
stp: false
forward-delay: 0
${BR_LAN}:
interfaces: [${LAN_IF}]
dhcp4: false
dhcp6: false
parameters:
stp: false
forward-delay: 0
EOF
chmod 600 /etc/netplan/*.yaml
echo ">>> Applying netplan (this may momentarily disrupt links on $WAN_IF/$LAN_IF)..."
netplan apply
# System settings
echo br_netfilter | tee /etc/modules-load.d/br_netfilter.conf
modprobe br_netfilter
tee /etc/sysctl.d/99-bridge-nf-off.conf >/dev/null <<'EOF'
net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-ip6tables=0
net.bridge.bridge-nf-call-arptables=0
EOF
sysctl --system
# Create an ISO with out example config files
mkisofs -joliet -rock -volid "cidata" -output /var/lib/libvirt/boot/vyos-configs.iso /opt/staging_scripts/vyos-configs/vyos-factory-config
# Create VM disk
if [[ ! -f "$DISK_PATH" ]]; then
echo ">>> Creating disk $DISK_PATH (${DISK_GB}G)"
qemu-img create -f qcow2 "$DISK_PATH" "${DISK_GB}G"
else
echo ">>> Disk already exists at $DISK_PATH"
fi
# If a previous domain with the same name exists, define a new one will fail.
if virsh dominfo "$VM_NAME" >/dev/null 2>&1; then
echo ">>> A VM named '$VM_NAME' already exists. Skipping creation."
echo "You can start it with: virsh start $VM_NAME && virsh console $VM_NAME"
exit 0
fi
# Create & start VM
echo ">>> Creating VyOS VM '$VM_NAME'..."
virt-install -n "$VM_NAME" \
--cpu host --ram "$RAM_MB" \
--vcpus "$VCPUS" \
--cdrom "$ISO_PATH" \
--os-variant debian12 \
--network bridge="$BR_WAN",model=virtio \
--network bridge="$BR_LAN",model=virtio \
--graphics vnc \
--hvm \
--virt-type kvm \
--disk path="$DISK_PATH",bus=virtio,size="$DISK_GB" \
--disk /var/lib/libvirt/boot/vyos-configs.iso,device=cdrom \
--noautoconsole
# Set the VM o autostart on host boot
virsh autostart $VM_NAME

View File

@@ -0,0 +1,288 @@
#!/usr/bin/env bash
set -euo pipefail
# Source our configs
source /opt/staging_scripts/setup-config
# Some statis configs
VM_NAME="vyos"
IMAGES_DIR="/var/lib/libvirt/images"
ISO_PATH="$IMAGES_DIR/vyos.iso"
DISK_PATH="$IMAGES_DIR/${VM_NAME}.qcow2"
# Make sure we are running as root
if [[ $EUID -ne 0 ]]; then
echo "Please run as root (sudo $0)"; exit 1
fi
# Make sure we have all the minimum required interfaces
for IFACE in "$WAN_IF" "$LAN_IF"; do
if ! ip link show "$IFACE" >/dev/null 2>&1; then
echo "Interface $IFACE not found. Adjust WAN_IF/LAN_IF in the script."; exit 1
fi
done
echo ">>> Set the host hostname"
/opt/staging_scripts/set-hostname
echo ">>> Installing virtualization packages..."
apt-get update -y
apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients virtinst bridge-utils cloud-image-utils libguestfs-tools xorriso genisoimage syslinux-utils
echo ">>> Ensuring libvirtd is running..."
systemctl enable --now libvirtd
echo ">>> Installing Docker..."
sh /opt/staging_scripts/get-docker.sh
mkdir -p "$IMAGES_DIR"
# Get PCI addresses for interfaces
echo ">>> Detecting PCI addresses for network interfaces..."
WAN_PCI=$(basename $(readlink -f /sys/class/net/$WAN_IF/device))
LAN_PCI=$(basename $(readlink -f /sys/class/net/$LAN_IF/device))
if [[ -z "$WAN_PCI" ]] || [[ -z "$LAN_PCI" ]]; then
echo "ERROR: Could not determine PCI addresses for interfaces"
echo "WAN_IF ($WAN_IF): $WAN_PCI"
echo "LAN_IF ($LAN_IF): $LAN_PCI"
exit 1
fi
echo "WAN interface $WAN_IF is at PCI address: $WAN_PCI"
echo "LAN interface $LAN_IF is at PCI address: $LAN_PCI"
# Parse PCI address (format: 0000:04:00.0 -> domain:bus:slot.function)
WAN_DOMAIN=$(echo $WAN_PCI | cut -d: -f1)
WAN_BUS=$(echo $WAN_PCI | cut -d: -f2)
WAN_SLOT=$(echo $WAN_PCI | cut -d: -f3 | cut -d. -f1)
WAN_FUNC=$(echo $WAN_PCI | cut -d. -f2)
LAN_DOMAIN=$(echo $LAN_PCI | cut -d: -f1)
LAN_BUS=$(echo $LAN_PCI | cut -d: -f2)
LAN_SLOT=$(echo $LAN_PCI | cut -d: -f3 | cut -d. -f1)
LAN_FUNC=$(echo $LAN_PCI | cut -d. -f2)
# Enable IOMMU and VFIO
echo ">>> Checking IOMMU support..."
if ! dmesg | grep -q "IOMMU enabled"; then
echo "WARNING: IOMMU may not be enabled. You may need to:"
echo " 1. Enable VT-d/AMD-Vi in BIOS"
echo " 2. Add 'intel_iommu=on' or 'amd_iommu=on' to kernel parameters"
echo " 3. Reboot the system"
echo ""
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
echo ">>> Configuring VFIO for PCI passthrough..."
# Load VFIO modules
modprobe vfio
modprobe vfio_pci
modprobe vfio_iommu_type1
# Ensure modules load on boot
cat > /etc/modules-load.d/vfio.conf <<EOF
vfio
vfio_pci
vfio_iommu_type_1
EOF
# Get vendor and device IDs for the interfaces
WAN_VENDOR_DEVICE=$(lspci -n -s $WAN_PCI | awk '{print $3}')
LAN_VENDOR_DEVICE=$(lspci -n -s $LAN_PCI | awk '{print $3}')
echo "WAN device ID: $WAN_VENDOR_DEVICE"
echo "LAN device ID: $LAN_VENDOR_DEVICE"
# Unbind interfaces from current driver and bind to vfio-pci
echo ">>> Unbinding interfaces from host..."
for IFACE in "$WAN_IF" "$LAN_IF"; do
if [[ -e "/sys/class/net/$IFACE" ]]; then
ip link set $IFACE down
DRIVER_PATH=$(readlink -f /sys/class/net/$IFACE/device/driver)
if [[ -n "$DRIVER_PATH" ]]; then
DRIVER_NAME=$(basename $DRIVER_PATH)
PCI_ADDR=$(basename $(readlink -f /sys/class/net/$IFACE/device))
echo "Unbinding $IFACE ($PCI_ADDR) from $DRIVER_NAME"
echo "$PCI_ADDR" > /sys/bus/pci/drivers/$DRIVER_NAME/unbind 2>/dev/null || true
fi
fi
done
# Bind to vfio-pci
echo ">>> Binding interfaces to vfio-pci..."
for PCI_ADDR in "$WAN_PCI" "$LAN_PCI"; do
if [[ ! -e "/sys/bus/pci/drivers/vfio-pci/$PCI_ADDR" ]]; then
echo "$PCI_ADDR" > /sys/bus/pci/drivers/vfio-pci/bind 2>/dev/null || {
# If bind fails, try adding the device ID first
VENDOR_DEVICE=$(lspci -n -s $PCI_ADDR | awk '{print $3}')
echo "$VENDOR_DEVICE" > /sys/bus/pci/drivers/vfio-pci/new_id 2>/dev/null || true
echo "$PCI_ADDR" > /sys/bus/pci/drivers/vfio-pci/bind 2>/dev/null || true
}
fi
echo "Bound $PCI_ADDR to vfio-pci"
done
# Get VyOS ISO
if [[ ! -f "$ISO_PATH" ]]; then
echo ">>> Downloading VyOS ISO to $ISO_PATH"
echo -e "#!/bin/bash\ncurl -fL $ISO_URL -o $ISO_PATH" >download_iso.sh
chmod +x download_iso.sh
./download_iso.sh
rm -f download_iso.sh
# Verify SHA256 checksum
echo ">>> Verifying SHA256 checksum..."
ACTUAL_SHA256=$(sha256sum "$ISO_PATH" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$ISO_SHA256" ]]; then
echo "ERROR: SHA256 checksum mismatch!"
echo "Expected: $ISO_SHA256"
echo "Actual: $ACTUAL_SHA256"
echo "Removing corrupted ISO file..."
rm -f "$ISO_PATH"
exit 1
fi
echo ">>> SHA256 checksum verified successfully"
else
echo ">>> VyOS ISO already present at $ISO_PATH"
# Verify SHA256 checksum of existing file
echo ">>> Verifying SHA256 checksum of existing ISO..."
ACTUAL_SHA256=$(sha256sum "$ISO_PATH" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$ISO_SHA256" ]]; then
echo "WARNING: SHA256 checksum mismatch for existing ISO!"
echo "Expected: $ISO_SHA256"
echo "Actual: $ACTUAL_SHA256"
echo "Removing existing ISO and re-downloading..."
rm -f "$ISO_PATH"
echo -e "#!/bin/bash\ncurl -fL $ISO_URL -o $ISO_PATH" >download_iso.sh
chmod +x download_iso.sh
./download_iso.sh
rm -f download_iso.sh
# Verify SHA256 checksum of new download
echo ">>> Verifying SHA256 checksum..."
ACTUAL_SHA256=$(sha256sum "$ISO_PATH" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$ISO_SHA256" ]]; then
echo "ERROR: SHA256 checksum mismatch!"
echo "Expected: $ISO_SHA256"
echo "Actual: $ACTUAL_SHA256"
echo "Removing corrupted ISO file..."
rm -f "$ISO_PATH"
exit 1
fi
echo ">>> SHA256 checksum verified successfully"
else
echo ">>> SHA256 checksum verified successfully"
fi
fi
# Create an ISO with out example config files
mkisofs -joliet -rock -volid "cidata" -output /var/lib/libvirt/boot/vyos-configs.iso /opt/staging_scripts/vyos-configs/vyos-factory-config
# Create VM disk
if [[ ! -f "$DISK_PATH" ]]; then
echo ">>> Creating disk $DISK_PATH (${DISK_GB}G)"
qemu-img create -f qcow2 "$DISK_PATH" "${DISK_GB}G"
else
echo ">>> Disk already exists at $DISK_PATH"
fi
# If a previous domain with the same name exists, define a new one will fail.
if virsh dominfo "$VM_NAME" >/dev/null 2>&1; then
echo ">>> A VM named '$VM_NAME' already exists. Skipping creation."
echo "You can start it with: virsh start $VM_NAME && virsh console $VM_NAME"
exit 0
fi
# Create & start VM with PCI passthrough and host CPU
echo ">>> Creating VyOS VM '$VM_NAME' with host CPU type and PCI passthrough..."
# Create a temporary XML file for the VM
TEMP_XML=$(mktemp)
cat > "$TEMP_XML" <<EOF
<domain type='kvm'>
<name>$VM_NAME</name>
<memory unit='MiB'>$RAM_MB</memory>
<vcpu placement='static'>$VCPUS</vcpu>
<os>
<type arch='x86_64' machine='pc'>hvm</type>
<boot dev='cdrom'/>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
</features>
<cpu mode='host-passthrough' check='none'>
<topology sockets='1' cores='$VCPUS' threads='1'/>
</cpu>
<clock offset='utc'>
<timer name='rtc' tickpolicy='catchup'/>
<timer name='pit' tickpolicy='delay'/>
<timer name='hpet' present='no'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='$DISK_PATH'/>
<target dev='vda' bus='virtio'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='$ISO_PATH'/>
<target dev='hdc' bus='ide'/>
<readonly/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/var/lib/libvirt/boot/vyos-configs.iso'/>
<target dev='sdb' bus='sata'/>
<readonly/>
</disk>
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x$WAN_DOMAIN' bus='0x$WAN_BUS' slot='0x$WAN_SLOT' function='0x$WAN_FUNC'/>
</source>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x$LAN_DOMAIN' bus='0x$LAN_BUS' slot='0x$LAN_SLOT' function='0x$LAN_FUNC'/>
</source>
</hostdev>
<controller type='usb' index='0' model='ich9-ehci1'/>
<controller type='pci' index='0' model='pci-root'/>
<serial type='pty'>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
<graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1'>
<listen type='address' address='127.0.0.1'/>
</graphics>
<video>
<model type='cirrus'/>
</video>
</devices>
</domain>
EOF
echo ">>> Defining VM from XML..."
virsh define "$TEMP_XML"
rm "$TEMP_XML"
# Set the VM o autostart on host boot
virsh autostart $VM_NAME

134
iso-files/ucentral-setup.sh Executable file
View File

@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -e
ACTION="$1"
# ================= CONFIG =================
CONTAINER="ucentral-olg"
IMAGE="mjhnetexp/ucentral-client:olgV8"
BRIDGE="br-wan"
HOST_VETH="veth-${CONTAINER:0:5}-h"
CONT_VETH="veth-${CONTAINER:0:5}-c"
CONT_IF="eth0"
DOCKER_RUN_OPTS="--privileged --network none"
# ==========================================
usage() {
echo "Usage: $0 setup | cleanup | shell"
exit 1
}
[ -z "$ACTION" ] && usage
container_pid() {
docker inspect -f '{{.State.Pid}}' "$CONTAINER" 2>/dev/null
}
container_exists() {
docker inspect "$CONTAINER" &>/dev/null
}
container_running() {
docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null | grep -q true
}
veth_exists() {
ip link show "$HOST_VETH" &>/dev/null
}
attached_to_bridge() {
bridge link show | grep -q "$HOST_VETH"
}
setup() {
echo "[+] Setup container on $BRIDGE"
if ! container_exists; then
echo "[+] Creating container $CONTAINER"
docker run -dit --name "$CONTAINER" $DOCKER_RUN_OPTS "$IMAGE"
fi
if ! container_running; then
echo "[+] Starting container"
docker start "$CONTAINER"
fi
PID=$(container_pid)
[ -z "$PID" ] && { echo "Failed to get container PID"; exit 1; }
if veth_exists && attached_to_bridge; then
echo "[!] Setup already done: container already attached to $BRIDGE"
exit 0
fi
echo "[+] Creating veth pair"
ip link add "$HOST_VETH" type veth peer name "$CONT_VETH"
echo "[+] Attaching host veth to bridge $BRIDGE"
ip link set "$HOST_VETH" master "$BRIDGE"
ip link set "$HOST_VETH" up
echo "[+] Moving container veth into netns"
ip link set "$CONT_VETH" netns "$PID"
echo "[+] Configuring container interface"
nsenter -t "$PID" -n -m -p sh <<EOF
ip link set lo up
ip link set "$CONT_VETH" name "$CONT_IF"
ip link set "$CONT_IF" up
udhcpc -i "$CONT_IF" -b -p /var/run/udhcpc.eth0.pid -s /usr/share/udhcpc/default.script
ubusd &
EOF
echo "[✓] Setup complete"
}
cleanup() {
local did_something=false
if veth_exists; then
echo "[+] Removing veth"
ip link del "$HOST_VETH"
did_something=true
fi
if container_exists; then
echo "[+] Stopping container"
docker stop "$CONTAINER" || true
echo "[+] Removing container"
docker rm "$CONTAINER" || true
did_something=true
fi
if ! $did_something; then
echo "[!] Nothing to cleanup"
else
echo "[✓] Cleanup complete"
fi
}
shell() {
if ! container_exists; then
echo "Container $CONTAINER does not exist"
exit 1
fi
if ! container_running; then
echo "Container $CONTAINER is not running"
exit 1
fi
echo "[+] Opening shell in $CONTAINER"
exec docker exec -it "$CONTAINER" /bin/ash
}
case "$ACTION" in
setup) setup ;;
cleanup) cleanup ;;
shell) shell ;;
*) usage ;;
esac

25
iso-files/user-data.olg Normal file
View File

@@ -0,0 +1,25 @@
#cloud-config
version: 1
identity:
hostname: olg-staging
password: "$y$j9T$Ogm02oaDNP6IIEhxrdLE21$vOQzeg.eQ7PYd0NsBxGhimAQNM6qYo41PIsVratZaV2"
username: olgadm
package_update: false
package_upgrade: false
ssh:
allow-pw: false
authorized-keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAqf+y26ldt63J9/ZfskTrwy9AU5ZXJkDzWYH5rDisZRc5JZ8Bn9Uj2KV0k7j6XLyQP8N6anoUepxaVuA0eqOSbpSgX5pgg2qCqPy0bbIbpPrEn0XESvds7+0hY2ekPHlL6uawoICkUskLo6MKC/69lFaeIrkC5li4omYUzzZhN5wpdk65UUM12z71HycF7sg2RHBmJSKtjrZGFdIZFVk1zj3OmOwk52GLdv4rJ+DPtrnhlvaJqqSTT4aWRJTpkGwRrzLeDVr2JoQ6QY1PPOtrrl2bxmvlmPbrA/Up4SpnE+0ZlMk7Td5qnfP1JQmRA4yHgwuWra8IUNVPNN+0qKXGaw== andre.courchesne@intello.com
install-server: true
late-commands:
- echo "Open LAN Gateway (Installed using ISO {{ ISO_VERSION }})" >/target/root/olg_release
- mkdir /target/opt/staging_scripts
- cp -r /cdrom/olg_files/* /target/opt/staging_scripts
- chmod -R +x /target/opt/staging_scripts/*
- cat /target/opt/staging_scripts/admin-bashrc-additions >> /target/etc/bash.bashrc
- echo 'olgadm ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/olgadm
- curtin in-target --target /target systemctl disable cloud-init
- curtin in-target --target /target /opt/staging_scripts/set-hostname >>/target/root/set-hostname.log
- cp /target/opt/staging_scripts/01-dhcp-host-admin.yaml /target/etc/netplan/01-dhcp-host-admin.yaml
# shut-down the host to avoid an infinite installer loop
- shutdown -h now

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
set interfaces ethernet eth0 address 'dhcp'
set interfaces ethernet eth0 description 'WAN'
set interfaces ethernet eth0 offload gro
set interfaces ethernet eth0 offload gso
set interfaces ethernet eth0 offload sg
set interfaces ethernet eth0 offload tso
set interfaces ethernet eth1 description 'LAN'
set interfaces ethernet eth1 offload gro
set interfaces ethernet eth1 offload gso
set interfaces ethernet eth1 offload sg
set interfaces ethernet eth1 offload tso
set interfaces ethernet eth1 vif 100 address '192.168.100.1/24'
set interfaces ethernet eth1 vif 100 description 'LAN VLAN 100 (Switches)'
set interfaces ethernet eth1 vif 101 address '192.168.101.1/24'
set interfaces ethernet eth1 vif 101 description 'LAN VLAN 101 (APs)'
set interfaces ethernet eth1 vif 1000 address '172.20.0.1/20'
set interfaces ethernet eth1 vif 1000 description 'LAN VLAN 1000 (Guest devices)'
set interfaces loopback lo
set nat source rule 100 description 'NAT out WAN'
set nat source rule 100 outbound-interface name 'eth0'
set nat source rule 100 source address '172.20.0.0/20'
set nat source rule 100 translation address 'masquerade'
set service dhcp-server shared-network-name VLAN100 authoritative
set service dhcp-server shared-network-name VLAN100 subnet 192.168.100.0/24 option default-router '192.168.100.1'
set service dhcp-server shared-network-name VLAN100 subnet 192.168.100.0/24 option name-server '192.168.100.1'
set service dhcp-server shared-network-name VLAN100 subnet 192.168.100.0/24 range 0 start '192.168.100.50'
set service dhcp-server shared-network-name VLAN100 subnet 192.168.100.0/24 range 0 stop '192.168.100.200'
set service dhcp-server shared-network-name VLAN100 subnet 192.168.100.0/24 subnet-id '100'
set service dhcp-server shared-network-name VLAN101 authoritative
set service dhcp-server shared-network-name VLAN101 subnet 192.168.101.0/24 option default-router '192.168.101.1'
set service dhcp-server shared-network-name VLAN101 subnet 192.168.101.0/24 option domain-name 'lan101.local'
set service dhcp-server shared-network-name VLAN101 subnet 192.168.101.0/24 option name-server '192.168.101.1'
set service dhcp-server shared-network-name VLAN101 subnet 192.168.101.0/24 range 0 start '192.168.101.50'
set service dhcp-server shared-network-name VLAN101 subnet 192.168.101.0/24 range 0 stop '192.168.101.200'
set service dhcp-server shared-network-name VLAN101 subnet 192.168.101.0/24 subnet-id '101'
set service dhcp-server shared-network-name VLAN1000 authoritative
set service dhcp-server shared-network-name VLAN1000 subnet 172.20.0.0/20 option default-router '172.20.0.1'
set service dhcp-server shared-network-name VLAN1000 subnet 172.20.0.0/20 option domain-name 'lan200.local'
set service dhcp-server shared-network-name VLAN1000 subnet 172.20.0.0/20 option name-server '172.20.0.1'
set service dhcp-server shared-network-name VLAN1000 subnet 172.20.0.0/20 range 0 start '172.20.0.50'
set service dhcp-server shared-network-name VLAN1000 subnet 172.20.0.0/20 range 0 stop '172.20.15.200'
set service dhcp-server shared-network-name VLAN1000 subnet 172.20.0.0/20 subnet-id '1000'
set service dns forwarding allow-from '192.168.100.0/24'
set service dns forwarding allow-from '192.168.101.0/24'
set service dns forwarding allow-from '172.20.0.0/20'
set service dns forwarding listen-address '192.168.100.1'
set service dns forwarding listen-address '192.168.101.1'
set service dns forwarding listen-address '172.20.0.1'
set service dns forwarding name-server 1.1.1.1
set service dns forwarding name-server 9.9.9.9
set service ntp allow-client address '127.0.0.0/8'
set service ntp allow-client address '169.254.0.0/16'
set service ntp allow-client address '10.0.0.0/8'
set service ntp allow-client address '172.16.0.0/12'
set service ntp allow-client address '192.168.0.0/16'
set service ntp allow-client address '::1/128'
set service ntp allow-client address 'fe80::/10'
set service ntp allow-client address 'fc00::/7'
set service ntp server time1.vyos.net
set service ntp server time2.vyos.net
set service ntp server time3.vyos.net
set service ssh port 22

View File

@@ -0,0 +1,130 @@
{
"interfaces": [
{
"ethernet": [
{
"select-ports": [
"WAN*"
]
}
],
"ipv4": {
"addressing": "dynamic"
},
"name": "WAN",
"role": "upstream",
"services": [
"ssh"
]
},
{
"ethernet": [
{
"select-ports": [
"LAN*"
]
}
],
"ipv4": {
"addressing": "static",
"dhcp": {
"lease-count": 128,
"lease-first": 10,
"lease-time": "6h"
},
"gateway": "192.168.60.1",
"send-hostname": true,
"subnet": "192.168.60.1/24"
},
"name": "LAN",
"role": "downstream",
"services": [
"ssh"
]
},
{
"ethernet": [
{
"isolate": false,
"learning": true,
"multicast": true,
"reverse-path": false,
"select-ports": [
"LAN2"
],
"vlan-tag": "auto"
}
],
"ipv4": {
"addressing": "static",
"dhcp": {
"lease-count": 128,
"lease-first": 10,
"lease-time": "6h"
},
"gateway": "192.168.10.1",
"send-hostname": true,
"subnet": "192.168.10.1/24"
},
"name": "LAN.10",
"role": "downstream",
"vlan": {
"id": 10
}
},
{
"ethernet": [
{
"isolate": false,
"learning": true,
"multicast": true,
"reverse-path": false,
"select-ports": [
"LAN1"
],
"vlan-tag": "auto"
}
],
"ipv4": {
"addressing": "static",
"dhcp": {
"lease-count": 128,
"lease-first": 10,
"lease-time": "6h"
},
"gateway": "192.168.20.1",
"send-hostname": true,
"subnet": "192.168.20.1/24"
},
"name": "LAN.20",
"role": "downstream",
"vlan": {
"id": 20
}
}
],
"nat": {
"source": {
"rule": {
"100": {
"description": "LAN SNAT",
"outbound-interface": {
"name": "br0"
},
"source": {
"address": "192.168.60.0/24"
},
"translation": {
"address": "masquerade"
}
}
}
}
},
"services": {
"ssh": {
"port": 22
}
},
"uuid": 1770703498
}

4
script/build Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
docker pull ubuntu:jammy
docker run --entrypoint "bash" -it --volume=$PWD:/src --workdir=/src ubuntu:jammy /src/script/make-iso

36
script/make-iso Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
if [ ! -e /.dockerenv ]; then
echo "You must run ./build to make the ISO"
exit 99
fi
sourceISO="ubuntu-24.04.3-live-server-amd64.iso"
sourceISOURL="https://mirror.xenyth.net/ubuntu-releases/24.04.3/${sourceISO}"
isoVersion=`cat grub.cfg| grep "^ISO_VERSION" | cut -f 2 -d '"'`
targetISO="olg-${isoVersion}.iso"
apt update
apt -y install wget xorriso curl gpg fdisk git
# Remove any previous ISO
rm -f ${targetISO}
# If the source ISO is missing get it
if [ ! -e ${sourceISO} ]; then
wget ${sourceISOURL}
if [ ! -e ${sourceISO} ]; then
echo "Missing ${sourceISO}"
exit 1
fi
fi
# Build out custom ISO
script/ubuntu-autoinstall-generator.sh -a -u user-data -d ${targetISO} -s ${sourceISO} --no-verify --all-in-one
chmod 777 ${targetISO}
# Remove artifacts
rm -f iso-files/firstboot
rm -f iso-files/*.deb
rm -f *-efi.img
rm -f *-hybrid.img

View File

@@ -0,0 +1,355 @@
#!/bin/bash
# Based on https://github.com/covertsh/ubuntu-autoinstall-generator
set -Eeuo pipefail
function cleanup() {
trap - SIGINT SIGTERM ERR EXIT
if [ -n "${tmpdir+x}" ]; then
rm -rf "$tmpdir"
log "🚽 Deleted temporary working directory $tmpdir"
fi
}
trap cleanup SIGINT SIGTERM ERR EXIT
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
[[ ! -x "$(command -v date)" ]] && echo "💥 date command not found." && exit 1
today=$(date +"%Y-%m-%d")
function log() {
echo >&2 -e "[$(date +"%Y-%m-%d %H:%M:%S")] ${1-}"
}
function die() {
local msg=$1
local code=${2-1} # Bash parameter expansion - default exit status 1. See https://wiki.bash-hackers.org/syntax/pe#use_a_default_value
log "$msg"
exit "$code"
}
usage() {
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-v] [-a] [-e] [-u user-data-file] [-m meta-data-file] [-k] [--focal] [-c] [-r] [-s source-iso-file] [-d destination-iso-file]
💁 This script will create fully-automated Ubuntu Server installation media.
By default, Ubuntu 22.04 images are created, but Ubuntu 20.04 is available as well.
Available options:
-h, --help Print this help and exit
-v, --verbose Print script debug info
-a, --all-in-one Bake user-data and meta-data into the generated ISO. By default you will
need to boot systems with a CIDATA volume attached containing your
autoinstall user-data and meta-data files.
For more information see: https://ubuntu.com/server/docs/install/autoinstall-quickstart
-e, --use-hwe-kernel Force the generated ISO to boot using the hardware enablement (HWE) kernel. Not supported
by early Ubuntu 20.04 release ISOs.
-u, --user-data Path to user-data file. Required if using -a
-m, --meta-data Path to meta-data file. Will be an empty file if not specified and using -a
-k, --no-verify Disable GPG verification of the source ISO file. By default SHA256SUMS-$today and
SHA256SUMS-$today.gpg in ${script_dir} will be used to verify the authenticity and integrity
of the source ISO file. If they are not present the latest daily SHA256SUMS will be
downloaded and saved in ${script_dir}. The Ubuntu signing key will be downloaded and
saved in a new keyring in ${script_dir}
-c, --no-md5 Disable MD5 checksum on boot
--focal Create installation media for Ubuntu 20.04 Focal Fossa
-r, --use-release-iso Use the current release ISO instead of the daily ISO. The file will be used if it already
exists.
-s, --source Source ISO file. By default the latest daily ISO will be downloaded
and saved as ${script_dir}/ubuntu-original-$today.iso
That file will be used by default if it already exists.
-d, --destination Destination ISO file. By default ${script_dir}/ubuntu-autoinstall-$today.iso will be
created, overwriting any existing file.
EOF
exit
}
function parse_params() {
# default values of variables set from params
user_data_file=''
meta_data_file=''
focal=0
download_url="https://cdimage.ubuntu.com/ubuntu-server/jammy/daily-live/current"
download_iso="jammy-live-server-amd64.iso"
original_iso="ubuntu-jammy-original-$today.iso"
source_iso="${script_dir}/${original_iso}"
destination_iso="${script_dir}/ubuntu-jammy-autoinstall-$today.iso"
sha_suffix="-jammy-${today}"
gpg_verify=1
all_in_one=0
use_hwe_kernel=0
md5_checksum=1
use_release_iso=0
while :; do
case "${1-}" in
-h | --help) usage ;;
-v | --verbose) set -x ;;
--focal) focal=1 ;;
-a | --all-in-one) all_in_one=1 ;;
-e | --use-hwe-kernel) use_hwe_kernel=1 ;;
-c | --no-md5) md5_checksum=0 ;;
-k | --no-verify) gpg_verify=0 ;;
-r | --use-release-iso) use_release_iso=1 ;;
-u | --user-data)
user_data_file="${2-}"
shift
;;
-s | --source)
source_iso="${2-}"
shift
;;
-d | --destination)
destination_iso="${2-}"
shift
;;
-m | --meta-data)
meta_data_file="${2-}"
shift
;;
-?*) die "Unknown option: $1" ;;
*) break ;;
esac
shift
done
log "👶 Starting up..."
# check required params and arguments
if [ ${all_in_one} -ne 0 ]; then
[[ -z "${user_data_file}" ]] && die "💥 user-data file was not specified."
[[ ! -f "$user_data_file" ]] && die "💥 user-data file could not be found."
[[ -n "${meta_data_file}" ]] && [[ ! -f "$meta_data_file" ]] && die "💥 meta-data file could not be found."
fi
if [ "${source_iso}" != "${script_dir}/${original_iso}" ]; then
[[ ! -f "${source_iso}" ]] && die "💥 Source ISO file could not be found."
fi
if [ ${focal} -eq 1 ]; then
download_url="https://cdimage.ubuntu.com/ubuntu-server/focal/daily-live/current"
download_iso="focal-live-server-amd64.iso"
original_iso="ubuntu-focal-original-$today.iso"
source_iso="${script_dir}/${original_iso}"
destination_iso="${script_dir}/ubuntu-focal-autoinstall-$today.iso"
sha_suffix="-focal-${today}"
fi
if [ "${use_release_iso}" -eq 1 ]; then
log "🔎 Checking for current release..."
if [ ${focal} -eq 1 ]; then
download_url="https://releases.ubuntu.com/focal"
download_iso=$(curl -sSL "${download_url}" | grep -oP 'ubuntu-20\.04\.\d*-live-server-amd64\.iso' | head -n 1)
else
download_url="https://releases.ubuntu.com/jammy"
download_iso=$(curl -sSL "${download_url}" | grep -oP 'ubuntu-22\.04(\.\d*)?-live-server-amd64\.iso' | head -n 1)
fi
original_iso="${download_iso}"
source_iso="${script_dir}/${download_iso}"
current_release=$(echo "${download_iso}" | cut -f2 -d-)
sha_suffix="${current_release}"
log "💿 Current release is ${current_release}"
fi
destination_iso=$(realpath "${destination_iso}")
source_iso=$(realpath "${source_iso}")
return 0
}
ubuntu_gpg_key_id="843938DF228D22F7B3742BC0D94AA3F0EFE21092"
parse_params "$@"
tmpdir=$(mktemp -d)
if [[ ! "$tmpdir" || ! -d "$tmpdir" ]]; then
die "💥 Could not create temporary working directory."
else
log "📁 Created temporary working directory $tmpdir"
fi
log "🔎 Checking for required utilities..."
[[ ! -x "$(command -v xorriso)" ]] && die "💥 xorriso is not installed. On Ubuntu, install the 'xorriso' package."
[[ ! -x "$(command -v sed)" ]] && die "💥 sed is not installed. On Ubuntu, install the 'sed' package."
[[ ! -x "$(command -v curl)" ]] && die "💥 curl is not installed. On Ubuntu, install the 'curl' package."
[[ ! -x "$(command -v gpg)" ]] && die "💥 gpg is not installed. On Ubuntu, install the 'gpg' package."
if [ ${focal} -eq 1 ]; then
[[ ! -f "/usr/lib/ISOLINUX/isohdpfx.bin" ]] && die "💥 isolinux is not installed. On Ubuntu, install the 'isolinux' package."
else
[[ ! -x "$(command -v fdisk)" ]] && die "💥 fdisk is not installed. On Ubuntu, install the 'fdisk' package."
fi
log "👍 All required utilities are installed."
if [ ! -f "${source_iso}" ]; then
log "🌎 Downloading ISO image ..."
curl -NsSL "${download_url}/${download_iso}" -o "${source_iso}"
log "👍 Downloaded and saved to ${source_iso}"
else
log "☑️ Using existing ${source_iso} file."
if [ ${gpg_verify} -eq 1 ]; then
if [ "${source_iso}" != "${script_dir}/${original_iso}" ]; then
log "⚠️ Automatic GPG verification is enabled. If the source ISO file is not the latest daily or release image, verification will fail!"
fi
fi
fi
if [ ${gpg_verify} -eq 1 ]; then
if [ ! -f "${script_dir}/SHA256SUMS-${sha_suffix}" ]; then
log "🌎 Downloading SHA256SUMS & SHA256SUMS.gpg files..."
curl -NsSL "${download_url}/SHA256SUMS" -o "${script_dir}/SHA256SUMS-${sha_suffix}"
curl -NsSL "${download_url}/SHA256SUMS.gpg" -o "${script_dir}/SHA256SUMS-${sha_suffix}.gpg"
else
log "☑️ Using existing SHA256SUMS-${sha_suffix} & SHA256SUMS-${sha_suffix}.gpg files."
fi
if [ ! -f "${script_dir}/${ubuntu_gpg_key_id}.keyring" ]; then
log "🌎 Downloading and saving Ubuntu signing key..."
gpg -q --no-default-keyring --keyring "${script_dir}/${ubuntu_gpg_key_id}.keyring" --keyserver "hkp://keyserver.ubuntu.com" --recv-keys "${ubuntu_gpg_key_id}"
log "👍 Downloaded and saved to ${script_dir}/${ubuntu_gpg_key_id}.keyring"
else
log "☑️ Using existing Ubuntu signing key saved in ${script_dir}/${ubuntu_gpg_key_id}.keyring"
fi
log "🔐 Verifying ${source_iso} integrity and authenticity..."
gpg -q --keyring "${script_dir}/${ubuntu_gpg_key_id}.keyring" --verify "${script_dir}/SHA256SUMS-${sha_suffix}.gpg" "${script_dir}/SHA256SUMS-${sha_suffix}" 2>/dev/null
if [ $? -ne 0 ]; then
rm -f "${script_dir}/${ubuntu_gpg_key_id}.keyring~"
die "👿 Verification of SHA256SUMS signature failed."
fi
rm -f "${script_dir}/${ubuntu_gpg_key_id}.keyring~"
digest=$(sha256sum "${source_iso}" | cut -f1 -d ' ')
set +e
grep -Fq "$digest" "${script_dir}/SHA256SUMS-${sha_suffix}"
if [ $? -eq 0 ]; then
log "👍 Verification succeeded."
set -e
else
die "👿 Verification of ISO digest failed."
fi
else
log "🤞 Skipping verification of source ISO."
fi
log "🔧 Extracting ISO image..."
xorriso -osirrox on -indev "${source_iso}" -extract / "$tmpdir" &>/dev/null
chmod -R u+w "$tmpdir"
rm -rf "$tmpdir/"'[BOOT]'
log "👍 Extracted to $tmpdir"
if [ ${focal} -eq 0 ]; then
log "🔧 Extracting EFI images from image..."
efi_start=$(fdisk -o Start,Type -l "${source_iso}" | grep -oP '\d+(?=\s+EFI.System)')
efi_length=$(fdisk -o Sectors,Type -l "${source_iso}" | grep -oP '\d+(?=\s+EFI.System)')
dd if=${source_iso} bs=512 skip=${efi_start} count=${efi_length} of=${source_iso}-efi.img
dd if=${source_iso} bs=1 count=432 of=${source_iso}-hybrid.img
log "👍 Extracted EFI images"
fi
if [ ${use_hwe_kernel} -eq 1 ]; then
if grep -q "hwe-vmlinuz" "$tmpdir/boot/grub/grub.cfg"; then
log "☑️ Destination ISO will use HWE kernel."
if [ ${focal} -eq 1 ]; then
sed -i -e 's|/casper/vmlinuz|/casper/hwe-vmlinuz|g' "$tmpdir/isolinux/txt.cfg"
sed -i -e 's|/casper/initrd|/casper/hwe-initrd|g' "$tmpdir/isolinux/txt.cfg"
fi
sed -i -e 's|/casper/vmlinuz|/casper/hwe-vmlinuz|g' "$tmpdir/boot/grub/grub.cfg"
sed -i -e 's|/casper/initrd|/casper/hwe-initrd|g' "$tmpdir/boot/grub/grub.cfg"
sed -i -e 's|/casper/vmlinuz|/casper/hwe-vmlinuz|g' "$tmpdir/boot/grub/loopback.cfg"
sed -i -e 's|/casper/initrd|/casper/hwe-initrd|g' "$tmpdir/boot/grub/loopback.cfg"
else
log "⚠️ This source ISO does not support the HWE kernel. Proceeding with the regular kernel."
fi
fi
log "🧩 Adding autoinstall parameter to kernel command line..."
if [ ${focal} -eq 1 ]; then
sed -i -e 's/---/ autoinstall ---/g' "$tmpdir/isolinux/txt.cfg"
fi
sed -i -e 's/---/ autoinstall fsck.mode=skip ---/g' "$tmpdir/boot/grub/grub.cfg"
sed -i -e 's/---/ autoinstall fsck.mode=skip ---/g' "$tmpdir/boot/grub/loopback.cfg"
log "👍 Added parameter to UEFI and BIOS kernel command lines."
if [ ${all_in_one} -eq 1 ]; then
log "🧩 Adding user-data and meta-data files..."
mkdir "$tmpdir/nocloud"
cp "$user_data_file" "$tmpdir/nocloud/user-data"
if [ -n "${meta_data_file}" ]; then
cp "$meta_data_file" "$tmpdir/nocloud/meta-data"
else
touch "$tmpdir/nocloud/meta-data"
fi
if [ ${focal} -eq 1 ]; then
sed -i -e 's,---, ds=nocloud;s=/cdrom/nocloud/ ---,g' "$tmpdir/isolinux/txt.cfg"
fi
sed -i -e 's,---, ds=nocloud\\\;s=/cdrom/nocloud/ ---,g' "$tmpdir/boot/grub/grub.cfg"
sed -i -e 's,---, ds=nocloud\\\;s=/cdrom/nocloud/ ---,g' "$tmpdir/boot/grub/loopback.cfg"
log "👍 Added data and configured kernel command line."
fi
# Copy some files
mkdir $tmpdir/olg_files
cp -r iso-files/* $tmpdir/olg_files/
# Copy our grub
cp grub.cfg $tmpdir/boot/grub/grub.cfg
# Customize some files
source iso-files/setup-config
sed -i -e "s/{{ HOST_ADMIN_PORT }}/${ADMIN_IF}/" "$tmpdir/olg_files/01-dhcp-host-admin.yaml"
ISO_VERSION=`cat grub.cfg | grep "ISO_VERSION=" | cut -f 2 -d '"'`
sed -i -e "s/{{ ISO_VERSION }}/${ISO_VERSION}/" "$tmpdir/olg_files/user-data.olg"
# If we have an Intel AMT port it must be set in DHCP or at least up fo AMT to work
if [ ! -z "${INTEL_AMT_PORT}" ]; then
echo " ${INTEL_AMT_PORT}: # Intel AMT Port" >>$tmpdir/olg_files/01-dhcp-host-admin.yaml
echo " dhcp4: true" >>$tmpdir/olg_files/01-dhcp-host-admin.yaml
echo " dhcp6: false" >>$tmpdir/olg_files/01-dhcp-host-admin.yaml
fi
if [ ${md5_checksum} -eq 1 ]; then
log "👷 Updating $tmpdir/md5sum.txt with hashes of modified files..."
md5=$(md5sum "$tmpdir/boot/grub/grub.cfg" | cut -f1 -d ' ')
sed -i -e 's,^.*[[:space:]] ./boot/grub/grub.cfg,'"$md5"' ./boot/grub/grub.cfg,' "$tmpdir/md5sum.txt"
md5=$(md5sum "$tmpdir/boot/grub/loopback.cfg" | cut -f1 -d ' ')
sed -i -e 's,^.*[[:space:]] ./boot/grub/loopback.cfg,'"$md5"' ./boot/grub/loopback.cfg,' "$tmpdir/md5sum.txt"
log "👍 Updated hashes."
else
log "🗑️ Clearing MD5 hashes..."
echo > "$tmpdir/md5sum.txt"
log "👍 Cleared hashes."
fi
log "📦 Repackaging extracted files into an ISO image..."
cd "$tmpdir"
if [ ${focal} -eq 1 ]; then
xorriso -as mkisofs -r \
-V "ubuntu-auto-focal-$today" \
-J \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat -no-emul-boot -boot-load-size 4 \
-isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -boot-info-table -input-charset utf-8 \
-eltorito-alt-boot \
-e boot/grub/efi.img -no-emul-boot -isohybrid-gpt-basdat \
-o "${destination_iso}" \
. #&>/dev/null
else
xorriso -as mkisofs -r \
-V "ubuntu-auto-jammy-$today" \
--grub2-mbr "${source_iso}-hybrid.img" \
-partition_offset 16 --mbr-force-bootable \
-append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b "${source_iso}-efi.img" \
-appended_part_as_gpt \
-iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7 \
-c '/boot.catalog' \
-b '/boot/grub/i386-pc/eltorito.img' \
-no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info \
-eltorito-alt-boot -e '--interval:appended_partition_2:::' \
-no-emul-boot \
-o "${destination_iso}" \
. #&>/dev/null
fi
cd "$OLDPWD"
log "👍 Repackaged into ${destination_iso}"
die "✅ Completed." 0

7
user-data Normal file
View File

@@ -0,0 +1,7 @@
#cloud-config
autoinstall:
version: 1
refresh-installer:
update: yes
early-commands:
- cp /cdrom/olg_files/user-data.olg /autoinstall.yaml