Feat: Node orchestration UX improvements (#6848)

* improve nginx playbook

* improve configure-vm script

* improve initialise-vm script

* expand config naming options

* provide args docs

* syntax fix

* address rabbitai comments

* cleanup ansible

* document ansible changes

* fix review comments

* update scraed data

* fix max comment review
This commit is contained in:
import this
2026-06-04 12:59:50 +02:00
committed by GitHub
parent 4ad00dba3d
commit c7780d2d34
18 changed files with 376 additions and 268 deletions
+30 -40
View File
@@ -1,21 +1,4 @@
---
ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
cli_url: "https://github.com/nymtech/nym/releases/download/nym-binaries-{{ nym_version }}/nym-cli"
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
###############################################################################
## GLOBAL VARS
## These values will be used globally unless overwritten per node in inventory/all
###############################################################################
ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
email: "<EMAIL>" # used in certbot, description.toml and landing page
website: "<WEBSITE>" # it is used in the description.toml
description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
# operator_name: "<OPERATOR_NAME>" # used in landing page if provided
###############################################################################
## GLOBAL VARS
## These values will be used globally unless overwritten per node in inventory/all
@@ -23,16 +6,41 @@ description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
## Per node changes in inventory/all will overwrite these global vars
###############################################################################
# moniker: "<MONIKER>" # if not setup here not in inventory/all it get's derived from the hostname
# mode: <MODE> # entry-gateway/exit-gateway/mixnode
# wireguard_enabled: <WIREGUARD_ENABLED> # true/false
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
## MANDATORY - uncomment & define
## --SSH--
#ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
# ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
## --Operator info--
# email: "<EMAIL>" # used in certbot, description.toml and landing page
# website: "<WEBSITE>" # it is used in the description.toml
# description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
# moniker: "<MONIKER>"
## --Node defaults (can override per node in inventory/all)--
# accept_operator_terms: true # controls --accept-operator-terms-and-conditions, read: https://nym.com/docs/operators/nodes/nym-node/setup#terms--conditions
# mode: exit-gateway # entry-gateway/exit-gateway/mixnode
# wireguard_enabled: true # true/false
hostname: "" # keep this fallback, keep it and setup hostname per node in inventory/all
## OPTIONAL - uncomment & define
# operator_name: "<OPERATOR_NAME>" # used in landing page if provided
# nym_version: "nym-binaries-v2026.7-tola" # to use particular version instead of Latest, provide in such form:
## alternative SSH key var setting, instead of a hardcoded path
## useful if the playbook is shared in a repo by more admins with each having own local key
# ansible_ssh_private_key_file: "{{ lookup('env', '<YOUR_ANSIBLE SSH_KEY_ENV_VAR>') }}"
###############################################################################
## GLOBAL PACKAGES
## GLOBAL PACKAGES & URLs
## These will be installed during deployment
###############################################################################
nym_cli_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-cli"
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
packages:
- tmux
@@ -50,24 +58,6 @@ packages:
- ufw
###############################################################################
## OPTIONAL OVERRIDES
## All values below already have defaults in the playbook/roles
## Uncomment only if you want to override them
###############################################################################
###############################################################################
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
###############################################################################
# To use particular version instead of Latest, provide in such form:
# nym_version: "nym-binaries-v2026.7-tola"
## NOTE:
## if you want to pin Nym to a specific version instead of using the
## latest release from GitHub in /tasks/main.yml then
## uncomment the line above and set the tag
###############################################################################
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
###############################################################################
+28 -23
View File
@@ -1,34 +1,39 @@
[nym_nodes]
# READ CONFIGURATION GUIDE:
# https://nym.com//docs/operators/orchestration/ansible#configuration
## READ CONFIGURATION GUIDE:
## https://nym.com/docs/operators/orchestration/ansible#configuration
# VARIABLES INFO
# required vars to set values per node:
# `ansible_host`, `hostname`, `location`
##############
## TEMPLATE ##
##############
## uncomment and exchange the <VARIABLES> with your real values for each node without the <> brackets
# global vars can be set in the group_vars/all.yml, for example:
# `email`, `ansible_user`, `moniker`, `description`, `mode`, `wireguard_enabled`
# othersise they must be set per node!
############
# TEMPLATE #
############
# node1 ansible_host=<YOUR_SERVER_IP> ansible_user=<USER> hostname=<HOSTNAME> location=<LOCATION> email=<EMAIL> mode=<MODE> wireguard_enabled=<true/false> moniker=<MONIKER> description=<DESCRIPTION>
# remove all comments and exchange the <VARIABLES> with your real values for each node
# without <> brackets
####################
## VARIABLES INFO ##
####################
# PRIORITY ORDER
# anything setup globaly can be overwritten in this file per node
# if provided here, it takes priority over the global setting
## --REQUIRED VARS--
## required per node:
## ansible_host, hostname, location
# EXAMPLES
# exit + wireguard gateway:
## --OPTIONAL VARS--
## can be set in the group_vars/all.yml or per node here:
## email, ansible_user, moniker, description, mode, wireguard_enabled
## --PRIORITY ORDER--
## anything setup globaly can be overwritten in this file per node
## if provided here, it takes priority over the global setting
## --EXAMPLES--
## exit + wireguard gateway:
# node2 ansible_host=11.12.13.14 hostname=nym-exit.ch-1.mydomain.net mode=exit-gateway location=CH wireguard_enabled=true
# entry gateway, no wireguard:
## entry gateway, no wireguard:
# node3 ansible_host=12.13.14.15 hostname=nym-entry.ch-2.mydomain.net mode=entry-gateway location=CH wireguard_enabled=false
# NOTE:
# all examples above don't have defined user, email nor description as we use the definition from group_vars/main.yml without an attempt of overwriting it
# all examples above don't have moniker defined as there is a function in /templates/description.toml.j2 deriving it from the hostname
## mixnode (comment out tunnel+quic roles in deploy.yml for these)
# mix-de-1 ansible_host=13.14.15.16 hostname=nym-mix.de-1.example.net location=DE mode=mixnode wireguard_enabled=false
## NOTE:
## all examples above don't have defined user, email nor description as we use global vars from playbooks/group_vars/all.yml
+5 -11
View File
@@ -89,7 +89,6 @@
loop:
- "/etc/nginx/sites-enabled/{{ hostname }}-ssl"
- "/etc/nginx/sites-enabled/nym-wss-config"
when: not le_cert.stat.exists
notify: Restart nginx
- name: Ensure nginx is enabled and running (needed for ACME http-01)
@@ -111,18 +110,13 @@
- name: Obtain/renew certificate
command:
cmd: >-
{% if le_cert.stat.exists %}
certbot certonly --webroot
-w /var/www/{{ hostname }}
certbot certonly --nginx
--non-interactive --agree-tos --keep-until-expiring
-m {{ email }} -d {{ hostname }}
{% else %}
certbot --nginx
--non-interactive --agree-tos --redirect
-m {{ email }} -d {{ hostname }}
{% endif %}
register: certbot_result
failed_when: false
failed_when: false
# re-check cert after certbot attempt
- name: Re-check whether certificate exists after certbot
@@ -170,4 +164,4 @@
changed_when: false
- name: Flush handlers (apply restart after successful tests)
meta: flush_handlers
meta: flush_handlers
+2 -2
View File
@@ -10,7 +10,7 @@ mixnet_bind_address: "0.0.0.0:1789" # maps to --mixnet-bind-address
landing_page_assets_base_dir: "/var/www"
# Flag toggles
# accept_operator_terms: true # controls --accept-operator-terms-and-conditions
accept_operator_terms: false # override in group_vars or inventory
nym_write_flag: true # controls -w
nym_init_only_flag: true # controls --init-only
wss_port: 9001 # controlls --announce-wss-port
@@ -18,7 +18,7 @@ wss_port: 9001 # controlls --announce-wss-port
# Optional: extra flags if you want to append more later
nym_extra_flags: ""
# CLI URL (nym_version can be set elsewhere / via GitHub API)
# CLI URL
nym_cli_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-cli"
# UFW
@@ -5,7 +5,7 @@
},
"mixmining_reserve": {
"denom": "unym",
"amount": "166613455567357"
"amount": "164623226345363"
},
"vesting_tokens": {
"denom": "unym",
@@ -13,6 +13,6 @@
},
"circulating_supply": {
"denom": "unym",
"amount": "833386544432643"
"amount": "835376773654637"
}
}
@@ -1,6 +1,6 @@
{
"nodes": 682,
"locations": 78,
"mixnodes": 242,
"exit_gateways": 432
"locations": 76,
"mixnodes": 239,
"exit_gateways": 435
}
@@ -1 +1 @@
833_386_544
835_376_773
@@ -1 +1 @@
61_194_673
61_340_814
@@ -1 +1 @@
61_194_672
61_340_813
@@ -1,7 +1,7 @@
| **Item** | **Description** | **Amount in NYM** |
|:-------------------|:------------------------------------------------------|--------------------:|
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
| Mixmining Reserve | Tokens releasing for operators rewards | 166_613_455 |
| Mixmining Reserve | Tokens releasing for operators rewards | 164_623_226 |
| Vesting Tokens | Tokens locked outside of circulation for future claim | 0 |
| Circulating Supply | Amount of unlocked tokens | 833_386_544 |
| Stake Saturation | Optimal size of node self-bond + delegation | 254_977 |
| Circulating Supply | Amount of unlocked tokens | 835_376_773 |
| Stake Saturation | Optimal size of node self-bond + delegation | 255_586 |
@@ -1,10 +1,10 @@
{
"interval": {
"reward_pool": "166613455567357.391753168447944888",
"staking_supply": "61194672938727.325886595653401629",
"reward_pool": "164623226345363.285226429762152046",
"staking_supply": "61340813321050.793375219026221616",
"staking_supply_scale_factor": "0.07342892",
"epoch_reward_budget": "4628151543.537705326476901331",
"stake_saturation_point": "254977803911.363857860815222506",
"epoch_reward_budget": "4572867398.482313478511937837",
"stake_saturation_point": "255586722171.04497239674594259",
"sybil_resistance": "0.3",
"active_set_work_factor": "10",
"interval_pool_emission": "0.02"
@@ -1 +1 @@
Wednesday, May 27th 2026, 11:42:58 UTC
Thursday, June 4th 2026, 09:20:29 UTC
@@ -291,17 +291,26 @@ KVM network is gone.
##### 3. Setup KVM public bridge for new VMs
To create a KVM network bridge on Ubuntu, edit a config file located in `/etc/netplan/` either called `00-installer.yaml` or `00-installer-config.yaml` and add the bridge details.
To create a KVM network bridge on Ubuntu, edit a config file located in `/etc/netplan/` often called `50-cloud-init.yaml` or `00-installer.yaml` or `00-installer-config.yaml` and add the bridge details.
- Before you edit the file, make a backup to stay on the save side:
- Before you edit the file, make a backup to stay on the save side, the file should be named something like this:
```bash
cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak
cp /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak
# or
cp /etc/netplan/00-installer.yaml /etc/netplan/00-installer.yaml.bak
# or
cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak
```
- Open `00-installer-config.yaml` or `00-installer.yaml.`config in a text editor:
- If none of these files existed, simply check the name of your config by this command and apply the same backup logic:
```sh
ls /etc/netplan/
```
- Open the original config in a text editor:
```bash
nano /etc/netplan/50-cloud-init.yaml
# or
nano /etc/netplan/00-installer.yaml
# or
nano /etc/netplan/00-installer-config.yaml
@@ -380,12 +389,13 @@ netplan --debug apply
```bash
apt install yamllint -y
yamllint /etc/netplan/50-cloud-init.yaml
# or
yamllint /etc/netplan/00-installer.yaml
# or
yamllint /etc/netplan/00-installer-config.yaml
```
- Apply correct permissions:
```bash
chmod 600 /etc/netplan/00-installer.yaml
@@ -410,9 +420,12 @@ systemctl enable --now systemd-networkd
- If things went wrong, you can always revert from the backed up file:
```bash
cp /etc/netplan/50-cloud-init.yaml.bak /etc/netplan/50-cloud-init.yaml
# or
cp /etc/netplan/00-installer-config.yaml.bak /etc/netplan/00-installer-config.yaml
# or
cp /etc/netplan/00-installer.yaml.bak /etc/netplan/00-installer.yaml
# and
netplan apply
```
@@ -535,19 +548,50 @@ Using the scripts is a two-step process. First, initialisation part is done from
<Steps>
##### 1. Initialise VM from the host machine
- Log in to your host as `root`
- Run this block and follow the prompts carefully:
- Get the script:
```bash
wget "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/kvm-setup/initialise-vm.sh"
chmod +x ./initialise-vm.sh
./initialise-vm.sh
```
- Run this block and follow the prompts carefully or provide with arguments:
```sh
# interactive CLI
./initialise-vm.sh
# arguments - see --help menu
./initialise-vm.sh --help
# example
# ./initialise-vm.sh --name ubuntu1 --password topsecretrootpassword --cpus 4 --ram 8192 --size 60
```
##### 2. Configure VM from within
- After logging into your VM run this block and follow the prompts carefully:
- After logging into your VM, using `login: root` and `password: <YOUR_PASSWORD>`, get second script to configure the VM from within:
```bash
wget "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/kvm-setup/configure-vm.sh"
chmod +x ./configure-vm.sh
```
- Likely your connection won't work and therefore you need to create the script manually, open a text editor:
```sh
nano configure-vm.sh
```
- Paste there [this content](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/kvm-setup/configure-vm.sh)
- Save and exit
- Run this block and follow the prompts carefully, or provide with arguments:
```sh
# interactive CLI
./configure-vm.sh
# arguments - see --help menu
./configure-vm.sh --help
# example
# ./configure-vm.sh --interface enp1s0 --ipv4 192.168.1.100 --gateway4 192.168.1.1 --ipv6 2001:db8::1 --gateway6 2001:db8::fffe
```
</ Steps>
@@ -3,6 +3,7 @@ import { Tabs } from 'nextra/components';
import { Steps } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
# Orchestrating Nym Nodes with Ansible
@@ -95,14 +96,13 @@ Before starting Ansible, ensure that your `A` and `AAAA` records are pointed to
<Steps>
###### 1. Configure global variables:
- Open `playbooks/group_vars/all.yml`
- Setup any variables which you want to have propagated on all your nodes globally
- Define all values under the section labeled as `## MANDATORY - uncomment & define` - these values will be propagated on all your nodes globally
- Optionally define values of your choice under the section `## OPTIONAL - uncomment & define`
- Note that in the next step we will be setting up a node inventory, where each of the variable can be configured per node, taking priority over the global ones.
- Setup a correct path for your SSH kety to `ansible_ssh_private_key_file:`
- Use these variables or comment them out with `#`:
- `ansible_user`
- `email`
- `website`
- `description`
- Setup a correct path for your SSH kety to `ansible_ssh_private_key_file:`, alternatively export your SSH key as an env var and use this:
```sh
ansible_ssh_private_key_file: "{{ lookup('env', '<YOUR_ANSIBLE SSH_KEY_ENV_VAR>') }}"
```
- Keep `hostname=""` as a fallback for nodes without a hostname
###### 2. Create node inventory:
@@ -111,11 +111,11 @@ Before starting Ansible, ensure that your `A` and `AAAA` records are pointed to
```sh
node1 ansible_host=<YOUR_SERVER_IP> ansible_user=<USER> hostname=<HOSTNAME> location=<LOCATION> email=<EMAIL> mode=<MODE> wireguard_enabled=<true/false> moniker=<MONIKER> description=<DESCRIPTION>
```
- These are mandatory values specific for each node - must be defined in the inventory:
- Mandatory values specific for each node - must be defined in the inventory:
- `ansible_host`: IPv4 host address
- `hostname`: node domain, otherwise fallbacks to `""` for nodes without domain
- `location`: node server location
- These are mandatory values which can be setup per node or in `group_vars/all` globally:
- Mandatory values which can be setup per node or in `group_vars/all` globally:
- `ansible_user`
- `email`
- `website`
@@ -123,6 +123,7 @@ node1 ansible_host=<YOUR_SERVER_IP> ansible_user=<USER> hostname=<HOSTNAME> loca
- `description`
- `mode`
- `wireguard_enabled`
- `accept_operator_terms` - **Make sure to read [Nym Operators Terms & Conditions](/operators/nodes/nym-node/setup#terms--conditions) first!**
###### 3. Test your setup
Run this command to check if everything is configured correctly in your inventory:
@@ -131,17 +132,12 @@ cd playbooks
ansible-inventory --graph
```
###### 4. Configure `nym-node run` command arguments
###### 4. Optional: remove some of `nym-node run` command arguments
These variables are read by the main task for `nym-node` installation: `roles/nym/tasks/config.yaml`
Open `roles/nym/defaults/main.yml` and have a look on the variables used:
- If you agree with [Terms and conditions](/operators/nodes/nym-node/setup#terms--conditions) uncomment the line: `accept_operator_terms: true` without which your node can never take part in Nym Network.
- The rest is up to your configuration but generally these flags workflows
These variables are read by the main task for `nym-node` installation: `roles/nym/tasks/config.yaml`
- Open that yaml and have a look on the flags
- In case of not needing some of the, delete them (ie when running `--mode mixnode` you can delete everything from `--hostname` to `--announce-wss-port`)
###### 5. Configure `deploy.yml` playbook
###### 5. Optional: Configure `deploy.yml` playbook
Open `playbooks/deploy.yml` and comment out `tunnel` and `quic` roles in case of running your playbook for nodes in a mode `mixnode`.
Save all the files and test with:
@@ -159,11 +155,13 @@ This chapter describes fundamental commands for using Ansible playbooks in relat
### Logic
<AccordionTemplate name="See Ansible logic description">
The main logic of the playbook flow when running with a basic command and playbook like this:
```sh
ansible-playbook <PLAYBOOK>.yml
```
<Steps>
<Steps>
###### 1. Read inventory
Ansible parses `inventory/all` and performs the playbook on all entries in it, unless specified otherwise
@@ -175,7 +173,8 @@ Ansible parses `group_vars/all.yml` and asigns global variables to all inventory
###### 3. Follow roles in the playbook
Ansible reads the roles defined in `<PLAYBOOK>.yml` passed with the command and executes the tasks defined under each role
</ Steps>
</ Steps>
</AccordionTemplate>
### Usage
@@ -237,6 +236,17 @@ cd playbooks
ansible-playbook system-maintenance.yml
```
###### 5. Mitigate kernel CVE
This playbook is to mitigate some of the Kernel issues found in April and May 2026.
This playbook will run roles on all the inventory entries in parallel by default.
```sh
cd playbooks
ansible-playbook mitigate_kernel_CVE.yml
```
</ Steps>
@@ -248,7 +258,7 @@ ansible-playbook system-maintenance.yml
<Steps>
###### One node at a time
###### Node limit
To test new configuration, it's advised to try it on one server at first. Of course you can comment out any entries in the inventory, but there are easier ways to do it.
- Provide flag `-l` followed by inventory entry and Ansible will change state only of that entry:
@@ -261,11 +271,14 @@ ansible-playbook upgrade.yml -l node1
# point to multiple entries
ansible-playbook upgrade.yml -l "node1,node2"
# use regex
# use regex - ie all exit nodes
ansible-playbook upgrade.yml -l "*exit*"
# use group in inventory labeled as [group]
ansible-playbook deploy.yml -l new_nodes
```
###### Role limit
###### Tag selection
<Callout>
To update your exit policy by using the newest [NTM](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh) via Ansible, just run:
+103 -62
View File
@@ -1,36 +1,101 @@
#!/bin/bash
# detect active network interface
INTERFACE=$(ip -o link show | awk -F': ' '{print $2}' | grep -v lo | head -n 1)
usage() {
local code="${1:-0}"
cat <<EOF
Usage: $0 [OPTIONS]
echo "Detected active network interface: $INTERFACE"
read -p "Is this correct? (y/n): " CONFIRM
if [[ "$CONFIRM" != "y" ]]; then
echo "Exiting. Please manually specify the correct network interface."
Options:
-i, --interface Network interface (optional; auto-detected if omitted)
-4, --ipv4 IPv4 address for the VM (optional)
-6, --ipv6 IPv6 address for the VM (optional)
-g, --gateway4 IPv4 gateway of the host server (optional)
-G, --gateway6 IPv6 gateway of the host server (optional)
-y, --yes Skip all confirmation prompts (auto-confirm)
-h, --help Show this help message
Example:
$0 --ipv4 192.168.1.100 --gateway4 192.168.1.1 --ipv6 2001:db8::1 --gateway6 2001:db8::fffe
$0 --ipv4 192.168.1.100 --gateway4 192.168.1.1 --yes
EOF
exit "$code"
}
# --- parse flags ---
INTERFACE=""
IPv4_VM=""
IPv6_VM=""
IPv4_GATEWAY_HOST_SERVER=""
IPv6_GATEWAY_HOST_SERVER=""
AUTO_YES=false
while [[ $# -gt 0 ]]; do
case "$1" in
-i|--interface)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --interface requires a value."; exit 1; }
INTERFACE="$2"; shift 2 ;;
-4|--ipv4)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --ipv4 requires a value."; exit 1; }
IPv4_VM="$2"; shift 2 ;;
-6|--ipv6)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --ipv6 requires a value."; exit 1; }
IPv6_VM="$2"; shift 2 ;;
-g|--gateway4)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --gateway4 requires a value."; exit 1; }
IPv4_GATEWAY_HOST_SERVER="$2"; shift 2 ;;
-G|--gateway6)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --gateway6 requires a value."; exit 1; }
IPv6_GATEWAY_HOST_SERVER="$2"; shift 2 ;;
-y|--yes) AUTO_YES=true; shift ;;
-h|--help) usage ;;
*)
echo "Error: Unknown option: $1"
usage 1
;;
esac
done
# at least one of IPv4 or IPv6 must be provided
if [[ -z "$IPv4_VM" && -z "$IPv6_VM" ]]; then
echo "Error: At least one of --ipv4 or --ipv6 must be provided."
echo "Run '$0 --help' for usage."
exit 1
fi
# prompt for network settings
read -p "Enter IPv4 address for VM (leave blank if not used): " IPv4_VM
read -p "Enter IPv6 address for VM (leave blank if not used): " IPv6_VM
read -p "Enter IPv4 gateway (host server, leave blank if not used): " IPv4_GATEWAY_HOST_SERVER
read -p "Enter IPv6 gateway (host server, leave blank if not used): " IPv6_GATEWAY_HOST_SERVER
confirm() {
local prompt="$1"
if $AUTO_YES; then
echo "${prompt} [Y/n] (auto-confirmed)"
return 0
fi
read -p "${prompt} [Y/n]: " REPLY
[[ -z "$REPLY" || "$REPLY" == "y" || "$REPLY" == "Y" ]]
}
# resize partition
# --- detect or validate interface ---
if [[ -z "$INTERFACE" ]]; then
INTERFACE=$(ip -o link show | awk -F': ' '{print $2}' | grep -v lo | head -n 1)
echo "Detected active network interface: $INTERFACE"
if ! confirm "Is this correct?"; then
echo "Exiting. Re-run with --interface <name> to specify one manually."
exit 1
fi
else
echo "Using network interface: $INTERFACE"
fi
# --- resize partition ---
echo "Expanding partition and resizing filesystem..."
growpart /dev/vda 1
resize2fs /dev/vda1
df -h
# ask before continuing
read -p "Continue with network configuration? (y/n): " CONTINUE
if [[ "$CONTINUE" != "y" ]]; then
if ! confirm "Continue with network configuration?"; then
echo "Exiting."
exit 1
fi
# generate Netplan config
# --- generate Netplan config ---
NETPLAN_CONFIG="/etc/netplan/01-network-config.yaml"
echo "Creating Netplan configuration at $NETPLAN_CONFIG..."
@@ -45,28 +110,19 @@ network:
addresses:
EOF
# append IPv4 address if provided
if [[ -n "$IPv4_VM" ]]; then
echo " - $IPv4_VM/24" >> $NETPLAN_CONFIG
fi
[[ -n "$IPv4_VM" ]] && echo " - $IPv4_VM/24" >> $NETPLAN_CONFIG
[[ -n "$IPv6_VM" ]] && echo " - $IPv6_VM/64" >> $NETPLAN_CONFIG
# append IPv6 address if provided
if [[ -n "$IPv6_VM" ]]; then
echo " - $IPv6_VM/64" >> $NETPLAN_CONFIG
fi
echo " routes:" >> $NETPLAN_CONFIG
# append IPv4 route if provided
if [[ -n "$IPv4_GATEWAY_HOST_SERVER" ]]; then
echo " - to: default" >> $NETPLAN_CONFIG
echo " via: $IPv4_GATEWAY_HOST_SERVER" >> $NETPLAN_CONFIG
fi
# append IPv6 route if provided
if [[ -n "$IPv6_GATEWAY_HOST_SERVER" ]]; then
echo " - to: default" >> $NETPLAN_CONFIG
echo " via: $IPv6_GATEWAY_HOST_SERVER" >> $NETPLAN_CONFIG
if [[ -n "$IPv4_GATEWAY_HOST_SERVER" || -n "$IPv6_GATEWAY_HOST_SERVER" ]]; then
echo " routes:" >> $NETPLAN_CONFIG
if [[ -n "$IPv4_GATEWAY_HOST_SERVER" ]]; then
echo " - to: default" >> $NETPLAN_CONFIG
echo " via: $IPv4_GATEWAY_HOST_SERVER" >> $NETPLAN_CONFIG
fi
if [[ -n "$IPv6_GATEWAY_HOST_SERVER" ]]; then
echo " - to: default" >> $NETPLAN_CONFIG
echo " via: $IPv6_GATEWAY_HOST_SERVER" >> $NETPLAN_CONFIG
fi
fi
cat <<EOF >> $NETPLAN_CONFIG
@@ -78,58 +134,43 @@ cat <<EOF >> $NETPLAN_CONFIG
- 2001:4860:4860::8888 # Google IPv6 DNS
EOF
# secure Netplan config
chmod 600 $NETPLAN_CONFIG
# generate Netplan configuration
netplan generate
# ask before applying Netplan
read -p "Apply Netplan changes? (y/n): " CONTINUE
if [[ "$CONTINUE" != "y" ]]; then
if ! confirm "Apply Netplan changes?"; then
echo "Exiting."
exit 1
fi
# apply Netplan and verify settings
netplan --debug apply
# show IP configurations
ip -4 a
ip -6 a
ip -4 r
ip -6 r
# test network connectivity
echo "Testing IPv4 connectivity for 10 seconds..."
timeout 10 ping -4 google.com
echo "Testing IPv6 connectivity for 10 seconds..."
timeout 10 ping -6 google.com
# ask before updating system
read -p "Proceed with system update and upgrade? (y/n): " CONTINUE
if [[ "$CONTINUE" != "y" ]]; then
echo "Skipping updates."
else
if confirm "Proceed with system update and upgrade?"; then
apt update && apt upgrade -y
else
echo "Skipping updates."
fi
# generate SSH host keys without password
# --- SSH setup ---
echo "Generating SSH host keys..."
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ""
ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ""
ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N ""
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ""
ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ""
ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N ""
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
# restart SSH service
systemctl restart ssh.service
# ensure ~/.ssh directory exists
mkdir -p ~/.ssh
# Open authorized_keys file for user input
echo "# Add your admin SSH keys here, save and exit!" > ~/.ssh/authorized_keys
nano ~/.ssh/authorized_keys
echo "Setup complete! Try to ping and ssh from the outside before killing this console"
echo "Setup complete! Try to ping and ssh from the outside before killing this console"
+101 -80
View File
@@ -1,6 +1,86 @@
#!/bin/bash
# check if noble-server-cloudimg-amd64.img is in working dir - if not, wget it
usage() {
local code="${1:-0}"
cat <<EOF
Usage: $0 [OPTIONS]
Options:
-n, --name VM name (required)
-p, --password Root password (required)
-c, --cpus Number of vCPUs (required)
-r, --ram RAM in MB (required); e.g. 2048, 4096, 8192
-s, --size Disk size to add in GB (required); e.g. 50, 100, 200
-h, --help Show this help message
Example:
$0 --name myvm --password secret --cpus 4 --ram 8192 --size 100
EOF
exit "$code"
}
# --- parse flags ---
VM_NAME=""
PASSWORD=""
VCPUS=""
RAM=""
SIZE=""
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--name)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --name requires a value."; exit 1; }
VM_NAME="$2"; shift 2 ;;
-p|--password)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --password requires a value."; exit 1; }
PASSWORD="$2"; shift 2 ;;
-c|--cpus)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --cpus requires a value."; exit 1; }
VCPUS="$2"; shift 2 ;;
-r|--ram)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --ram requires a value."; exit 1; }
RAM="$2"; shift 2 ;;
-s|--size)
[[ -n "${2:-}" && "${2:0:1}" != "-" ]] || { echo "Error: --size requires a value."; exit 1; }
SIZE="$2"; shift 2 ;;
-h|--help) usage ;;
*)
echo "Error: Unknown option: $1"
usage 1
;;
esac
done
# --- validate required flags ---
MISSING=()
[[ -z "$VM_NAME" ]] && MISSING+=("--name")
[[ -z "$PASSWORD" ]] && MISSING+=("--password")
[[ -z "$VCPUS" ]] && MISSING+=("--cpus")
[[ -z "$RAM" ]] && MISSING+=("--ram")
[[ -z "$SIZE" ]] && MISSING+=("--size")
if [[ ${#MISSING[@]} -gt 0 ]]; then
echo "Error: Missing required flags: ${MISSING[*]}"
echo "Run '$0 --help' for usage."
exit 1
fi
if [[ ! "$VCPUS" =~ ^[0-9]+$ || "$VCPUS" -lt 1 ]]; then
echo "Error: --cpus must be a positive integer."
exit 1
fi
if [[ ! "$RAM" =~ ^[0-9]+$ || "$RAM" -lt 256 ]]; then
echo "Error: --ram must be a positive integer (minimum 256 MB)."
exit 1
fi
if [[ ! "$SIZE" =~ ^[0-9]+$ || "$SIZE" -lt 1 ]]; then
echo "Error: --size must be a positive integer in GB."
exit 1
fi
# --- check / download base image ---
if [[ ! -f noble-server-cloudimg-amd64.img ]]; then
echo "Base image not found. Downloading noble-server-cloudimg-amd64.img..."
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
@@ -10,99 +90,40 @@ if [[ ! -f noble-server-cloudimg-amd64.img ]]; then
fi
fi
# prompt for VM_NAME
read -p "Enter VM name: " VM_NAME
if [[ -z "$VM_NAME" ]]; then
echo "Error: VM_NAME cannot be empty. Exiting."
exit 1
fi
# prompt for PASSWORD w silent input
read -s -p "Enter password for the VM: " PASSWORD
echo
if [[ -z "$PASSWORD" ]]; then
echo "Error: PASSWORD cannot be empty. Exiting."
exit 1
fi
# prompt for number of vCPUs
read -p "Enter the number of vCPUs for the VM: " VCPUS
if [[ -z "$VCPUS" || ! "$VCPUS" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid number of vCPUs. Exiting."
exit 1
fi
# prompt for RAM size with suggestions
DEFAULT_RAM=4096
HALF_RAM=$((DEFAULT_RAM / 2))
DOUBLE_RAM=$((DEFAULT_RAM * 2))
TRIPLE_RAM=$((DEFAULT_RAM * 3))
FOUR_TIMES_RAM=$((DEFAULT_RAM * 4))
SIX_TIMES_RAM=$((DEFAULT_RAM * 6))
EIGHT_TIMES_RAM=$((DEFAULT_RAM * 8))
echo "Choose the amount of RAM for the VM:"
echo "1) $HALF_RAM MB"
echo "2) $DEFAULT_RAM MB (recommended)"
echo "3) $DOUBLE_RAM MB"
echo "4) $TRIPLE_RAM MB"
echo "5) $FOUR_TIMES_RAM MB"
echo "6) $SIX_TIMES_RAM MB"
echo "7) $EIGHT_TIMES_RAM MB"
read -p "Enter your choice (1-7) or specify a custom amount in MB: " RAM_CHOICE
case $RAM_CHOICE in
1) RAM=$HALF_RAM ;;
2) RAM=$DEFAULT_RAM ;;
3) RAM=$DOUBLE_RAM ;;
4) RAM=$TRIPLE_RAM ;;
5) RAM=$FOUR_TIMES_RAM ;;
6) RAM=$SIX_TIMES_RAM ;;
7) RAM=$EIGHT_TIMES_RAM ;;
*)
if [[ "$RAM_CHOICE" =~ ^[0-9]+$ ]]; then
RAM=$RAM_CHOICE
else
echo "Invalid choice. Exiting."
exit 1
fi
;;
esac
# define image path
IMAGE_PATH="/var/lib/libvirt/images/${VM_NAME}.img"
# copy the base image
if [[ -e "$IMAGE_PATH" ]]; then
echo "Error: $IMAGE_PATH already exists. Choose a different --name or remove the old image first."
exit 1
fi
echo "Copying the base image to $IMAGE_PATH..."
cp noble-server-cloudimg-amd64.img "$IMAGE_PATH"
# install guestfs-tools if missing
echo "Checking and installing guestfs-tools if needed..."
if ! dpkg -l | grep -q guestfs-tools; then
sudo apt update && sudo apt install guestfs-tools -y
fi
# set root password inside the image
echo "Setting root password inside the VM image..."
virt-customize -a "$IMAGE_PATH" --root-password password:"$PASSWORD"
# resize the image
echo "Resizing the image by +100G..."
qemu-img resize "$IMAGE_PATH" +100G
echo "Resizing the image by +${SIZE}G..."
qemu-img resize "$IMAGE_PATH" +${SIZE}G
# install the VM and run log in prompt
echo "Starting VM installation..."
virt-install \
--name "$VM_NAME" \
--ram="$RAM" \
--vcpus="$VCPUS" \
--cpu host \
--hvm \
--disk bus=virtio,path="$IMAGE_PATH" \
--network bridge=br0 \
--graphics none \
--console pty,target_type=serial \
--osinfo ubuntunoble \
--import
--name "$VM_NAME" \
--ram="$RAM" \
--vcpus="$VCPUS" \
--cpu host \
--hvm \
--disk bus=virtio,path="$IMAGE_PATH" \
--network bridge=br0 \
--graphics none \
--console pty,target_type=serial \
--osinfo ubuntunoble \
--import
echo "VM $VM_NAME has been successfully installed!"
echo "VM $VM_NAME has been successfully installed!"