You've already forked Projects
Pulumi libvirt project commit
Initial commit for pulumi kvm project
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
name: test-node
|
||||||
|
runtime:
|
||||||
|
name: python
|
||||||
|
options:
|
||||||
|
toolchain: uv
|
||||||
|
virtualenv: .venv
|
||||||
|
description: Reusable templates for managing clustered or single libvirt KVM guests.
|
||||||
|
packages:
|
||||||
|
libvirt:
|
||||||
|
source: pulumi/pulumi/terraform-provider
|
||||||
|
version: 1.1.4
|
||||||
|
parameters:
|
||||||
|
- dmacvicar/libvirt
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Instructions for creating projects using these templates
|
||||||
|
***Note: This assumes SSH access to KVM hosts and Pulumi is [installed](https://www.pulumi.com/docs/install/).***
|
||||||
|
|
||||||
|
## Login to the backend
|
||||||
|
Log in to your [pulumi backend](https://www.pulumi.com/docs/iac/concepts/state-and-backends/), in this example a file-based local backend will be used. This will create a state file in your home directory (~/ on Linux, for example)
|
||||||
|
|
||||||
|
`pulumi login --local`
|
||||||
|
|
||||||
|
## Copying and modifying the template
|
||||||
|
Create a new directory with the name of the project. Copy the templates from the `stack_templates` and `python_templates` directories for the specific node type.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
mkdir app-cluster
|
||||||
|
cp stack_templates/cluster_static_pulumi app-cluster/Pulumi.app-cluster.yaml
|
||||||
|
cp python_templates/cluster_static_template.py app-cluster/__main__.py
|
||||||
|
cp Pulumi_yaml_template app-cluster/Pulumi.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit to fit the project. The `Pulumi_yaml_template` is already formatted with the runtime an virtual environment. The name and description should be edited to fit the name and description of the project. If using a different runtime and toolchain, edit accordingly. This assumes the runtime is `python` and the toolchain is `uv`
|
||||||
|
|
||||||
|
## Create the pulumi stack
|
||||||
|
After editing the files, create the pulumi stack
|
||||||
|
```
|
||||||
|
cd app-cluster
|
||||||
|
pulumi stack init --copy-config-from Pulumi.<stack-name>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
A glimpse of the what the directory should look like at this state is found in the [sample-projects](./sample_projects) directory
|
||||||
|
|
||||||
|
and create a bare uv project and add the packages
|
||||||
|
|
||||||
|
```
|
||||||
|
uv init --bare
|
||||||
|
uv add pulumi pyyaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add the terraform-provider for libvirt
|
||||||
|
Pulumi no longer maintains a dedicated provider for libvirt. A local [terraform provider](https://www.pulumi.com/registry/packages/libvirt/) will need to be installed
|
||||||
|
|
||||||
|
`pulumi package add terraform-provider dmacvicar/libvirt`
|
||||||
|
|
||||||
|
This will add the source package to the `pyproject.toml` file created when `uv` initialized the project
|
||||||
|
|
||||||
|
# Add the hashed password
|
||||||
|
A password will need to be set in order to access the console. SSH access is public key/certificate based.
|
||||||
|
|
||||||
|
`pulumi config set --secret kvm:password_hash <value>`
|
||||||
|
|
||||||
|
Ensure the password is stored securely and can be retrieved readily in the event troubleshooting from the console is necessary.
|
||||||
|
|
||||||
|
Preview the stack before deploying
|
||||||
|
|
||||||
|
`pulumi preview`
|
||||||
|
|
||||||
|
And deploy the stack
|
||||||
|
|
||||||
|
`pulumi up`
|
||||||
|
|
||||||
|
The resources are destroyed by executing
|
||||||
|
|
||||||
|
`pulumi destroy`
|
||||||
|
|
||||||
|
# Limitations
|
||||||
|
|
||||||
|
- The major limitation is that Pulumi does not start the KVM guest. The guest will need to manually (or some how programmatically) started for the cloud-init workflow to begin. Once the cloud-init workflow completes, the guest will reboot and will be ready for use.
|
||||||
|
- The `os-variant` that is normally necessary by `virt-install` is not set. This can be set manually after Pulumi deploys the guest or anytime the guest is shutdown.
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import pulumi
|
||||||
|
import pulumi_libvirt as libvirt
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# function to convert GB to bytes for disk size stated in stack config
|
||||||
|
def gb_to_bytes(gb: int) -> int:
|
||||||
|
"""Converts gigabytes to bytes for resource capacity."""
|
||||||
|
return int(gb) * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
config = pulumi.Config("kvm")
|
||||||
|
|
||||||
|
ssh_user = config.get("ssh_user") or "root"
|
||||||
|
base_image_path = config.require("base_image_path")
|
||||||
|
nodes = config.require_object("nodes")
|
||||||
|
secret_hash = config.require_secret("password_hash")
|
||||||
|
|
||||||
|
# Load user data once outside the loop
|
||||||
|
try:
|
||||||
|
with open(config.require("user_data_path"), "r") as f:
|
||||||
|
user_data = pulumi.Output.format(f.read(), password_hash=secret_hash)
|
||||||
|
except Exception as e:
|
||||||
|
# Handle case where file path is wrong or secret fails to resolve
|
||||||
|
print(f"Warning: Could not process user_data. Skipping node deployment. Error: {e}")
|
||||||
|
|
||||||
|
# Dictionary to cache providers per target host
|
||||||
|
providers = {}
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
name = node["name"]
|
||||||
|
target_host = node["host"]
|
||||||
|
bridge_interface = node["bridge"]
|
||||||
|
disk_gb = node["disk_gb"]
|
||||||
|
memory_mb = node["memory_mb"]
|
||||||
|
vcpu = node["vcpu"]
|
||||||
|
|
||||||
|
# Get or cache providers in providers dictionary
|
||||||
|
if target_host not in providers:
|
||||||
|
providers[target_host] = libvirt.Provider(
|
||||||
|
f"prov-{target_host}",
|
||||||
|
uri=f"qemu+sshcmd://{ssh_user}@{target_host}/system"
|
||||||
|
)
|
||||||
|
host_provider = providers[target_host]
|
||||||
|
|
||||||
|
meta_data_yaml = yaml.dump({"instance-id": f"i-{name}", "local-hostname": name})
|
||||||
|
|
||||||
|
# Initialize network payload in V1 format
|
||||||
|
network_config_payload = {
|
||||||
|
"version": 1,
|
||||||
|
"config": [] # Start with an empty list of configurations (The list container)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# physical interface
|
||||||
|
physical_interface = {
|
||||||
|
"type": "physical",
|
||||||
|
"name": "eth0",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"address": node["static_ip"],
|
||||||
|
"gateway": node["gateway"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(physical_interface)
|
||||||
|
|
||||||
|
|
||||||
|
# Append the nameservers to network_config_payload (solves missing nameserver issues)
|
||||||
|
if "dns_servers" in node:
|
||||||
|
nameserver_block = {
|
||||||
|
"type": "nameserver",
|
||||||
|
"address": [str(ip) for ip in node["dns_servers"]] # Create clean python list of strings
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(nameserver_block)
|
||||||
|
|
||||||
|
|
||||||
|
if "dns_search" in node and node["dns_search"]:
|
||||||
|
network_config_payload["config"][0]["subnets"][0]["dns_search"] = [str(s) for s in node["dns_search"]] # Create clean python list of strings
|
||||||
|
|
||||||
|
# complete network config yaml
|
||||||
|
network_config_yaml = yaml.dump(network_config_payload)
|
||||||
|
|
||||||
|
|
||||||
|
# cloud image backing store
|
||||||
|
base_volume = libvirt.Volume(
|
||||||
|
f"base-vol-{name}",
|
||||||
|
name=f"{name}-base",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(10),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": base_image_path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
guest_volume = libvirt.Volume(
|
||||||
|
f"{name}",
|
||||||
|
name=f"{name}",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(disk_gb),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
backing_store={
|
||||||
|
"path": base_volume.id,
|
||||||
|
"format": {
|
||||||
|
"type": "qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
cloud_init_disk = libvirt.CloudinitDisk(
|
||||||
|
f"ci-disk-{name}",
|
||||||
|
meta_data=meta_data_yaml,
|
||||||
|
user_data=user_data,
|
||||||
|
network_config=network_config_yaml, # Passes the fully constructed network payload
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
cloud_init_volume = libvirt.Volume(
|
||||||
|
f"ci-vol-{name}",
|
||||||
|
pool=pool_target,
|
||||||
|
target={
|
||||||
|
"format": {"type": "raw"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": cloud_init_disk.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
guest = libvirt.Domain(
|
||||||
|
f"dom-{name}",
|
||||||
|
name=name,
|
||||||
|
type="kvm",
|
||||||
|
autostart=True,
|
||||||
|
cpu={
|
||||||
|
"mode": "host-passthrough"
|
||||||
|
},
|
||||||
|
memory=memory_mb,
|
||||||
|
memory_unit="MiB",
|
||||||
|
vcpu=vcpu,
|
||||||
|
os={
|
||||||
|
"type": "hvm",
|
||||||
|
"type_arch": "x86_64",
|
||||||
|
"type_machine": "q35",
|
||||||
|
"firmware": "efi",
|
||||||
|
"boot_devices": []
|
||||||
|
},
|
||||||
|
features={
|
||||||
|
"acpi": True
|
||||||
|
},
|
||||||
|
devices={
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": guest_volume.pool,
|
||||||
|
"volume": guest_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "vda",
|
||||||
|
"bus": "virtio"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "qcow2"
|
||||||
|
},
|
||||||
|
"boot": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device": "cdrom",
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": cloud_init_volume.pool,
|
||||||
|
"volume": cloud_init_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "sda",
|
||||||
|
"bus": "sata"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "raw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"type": "bridge",
|
||||||
|
"model": {
|
||||||
|
"type": "virtio"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"bridge": {
|
||||||
|
"bridge": bridge_interface
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wait_for_ip": {
|
||||||
|
"timeout": 300,
|
||||||
|
"source": "lease"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphics": [
|
||||||
|
{
|
||||||
|
"type": "vnc",
|
||||||
|
"vnc": {
|
||||||
|
"autoport": True,
|
||||||
|
"listen": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
pulumi.export("deployed_node", guest.name)
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import pulumi
|
||||||
|
import pulumi_libvirt as libvirt
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# Function to convert GB to bytes for disk size stated in stack config
|
||||||
|
def gb_to_bytes(gb: int) -> int:
|
||||||
|
return int(gb) * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
config = pulumi.Config("kvm")
|
||||||
|
ssh_user = config.get("ssh_user") or "root"
|
||||||
|
target_host = config.require("target_host")
|
||||||
|
guest_name = config.require("guest_name")
|
||||||
|
bridge_interface = config.require("bridge_interface")
|
||||||
|
secret_hash = config.require_secret("password_hash")
|
||||||
|
|
||||||
|
# Load cloud-config
|
||||||
|
with open(config.require("user_data_path"), "r") as f:
|
||||||
|
user_data = pulumi.Output.format(f.read(), password_hash=secret_hash)
|
||||||
|
|
||||||
|
# Set KVM host
|
||||||
|
host_provider = libvirt.Provider(
|
||||||
|
"ssh-provider",
|
||||||
|
uri=f"qemu+sshcmd://{ssh_user}@{target_host}/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
meta_data_yaml = yaml.dump({"instance-id": f"i-{guest_name}", "local-hostname": guest_name})
|
||||||
|
|
||||||
|
# Initialize DNS nameservers and search domain
|
||||||
|
dns_servers = config.require_object("dns_servers")
|
||||||
|
dns_search = config.get_object("dns_search") or []
|
||||||
|
|
||||||
|
# Initialize network config
|
||||||
|
network_config_payload = {
|
||||||
|
"version": 1,
|
||||||
|
"config": []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Append "physical" interface
|
||||||
|
physical_interface = {
|
||||||
|
"type": "physical",
|
||||||
|
"name": "eth0",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"address": config.require("static_ip"),
|
||||||
|
"gateway": config.require("gateway")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(physical_interface)
|
||||||
|
|
||||||
|
# Append nameservers
|
||||||
|
if dns_servers:
|
||||||
|
nameserver_block = {
|
||||||
|
"type": "nameserver",
|
||||||
|
"interface": "eth0",
|
||||||
|
"address": dns_servers
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(nameserver_block)
|
||||||
|
|
||||||
|
|
||||||
|
# Append search domain
|
||||||
|
if dns_search:
|
||||||
|
network_config_payload["config"][0]["subnets"][0]["dns_search"] = list(str(dns_search))
|
||||||
|
|
||||||
|
|
||||||
|
network_config_yaml = yaml.dump(network_config_payload)
|
||||||
|
|
||||||
|
|
||||||
|
base_volume = libvirt.Volume(
|
||||||
|
"base-vol",
|
||||||
|
name=f"{guest_name}-base",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(10),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": base_image_path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
guest_volume = libvirt.Volume(
|
||||||
|
"guest-disk",
|
||||||
|
name=f"{guest_name}",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(config.require_int("disk_gb")),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
backing_store={
|
||||||
|
"path": base_volume.id,
|
||||||
|
"format": {
|
||||||
|
"type": "qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cloud_init_disk = libvirt.CloudinitDisk(
|
||||||
|
"cloudinit-iso",
|
||||||
|
meta_data=meta_data_yaml,
|
||||||
|
user_data=user_data,
|
||||||
|
network_config=network_config_yaml,
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the cloud-init volume with metadata
|
||||||
|
cloud_init_volume = libvirt.Volume(
|
||||||
|
"cloudinit-vol",
|
||||||
|
pool=pool_target,
|
||||||
|
target={
|
||||||
|
"format": {"type": "raw"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": cloud_init_disk.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
guest = libvirt.Domain(
|
||||||
|
"kvm-guest",
|
||||||
|
name=guest_name,
|
||||||
|
type="kvm",
|
||||||
|
autostart=True,
|
||||||
|
cpu={
|
||||||
|
"mode": "host-passthrough"
|
||||||
|
},
|
||||||
|
memory=config.require_int("memory_mb"),
|
||||||
|
memory_unit="MiB",
|
||||||
|
vcpu=config.require_int("vcpu"),
|
||||||
|
os={
|
||||||
|
"type": "hvm",
|
||||||
|
"type_arch": "x86_64",
|
||||||
|
"type_machine": "q35",
|
||||||
|
"firmware": "efi",
|
||||||
|
"boot_devices": []
|
||||||
|
},
|
||||||
|
features={
|
||||||
|
"acpi": True
|
||||||
|
},
|
||||||
|
devices={
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": guest_volume.pool,
|
||||||
|
"volume": guest_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "vda",
|
||||||
|
"bus": "virtio"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "qcow2"
|
||||||
|
},
|
||||||
|
"boot": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device": "cdrom",
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": cloud_init_volume.pool,
|
||||||
|
"volume": cloud_init_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "sda",
|
||||||
|
"bus": "sata"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "raw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"type": "bridge",
|
||||||
|
"model": {
|
||||||
|
"type": "virtio"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"bridge": {
|
||||||
|
"bridge": bridge_interface
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wait_for_ip": {
|
||||||
|
"timeout": 300,
|
||||||
|
"source": "lease"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphics": [
|
||||||
|
{
|
||||||
|
"type": "vnc",
|
||||||
|
"vnc": {
|
||||||
|
"autoport": True,
|
||||||
|
"listen": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
pulumi.export("deployed_node", guest.name)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
config:
|
||||||
|
kvm:ssh_user: "root"
|
||||||
|
kvm:pool_target: "default"
|
||||||
|
kvm:base_image_path: "https://repo.almalinux.org/almalinux/10/cloud/x86_64_v2/images/AlmaLinux-10-GenericCloud-latest.x86_64_v2.qcow2"
|
||||||
|
kvm:user_data_path: "../user-data/alma-node"
|
||||||
|
kvm:nodes:
|
||||||
|
- name: "db-node-01"
|
||||||
|
host: "192.168.1.1"
|
||||||
|
bridge: "br0"
|
||||||
|
vcpu: 8
|
||||||
|
memory_mb: 16384
|
||||||
|
disk_gb: 120
|
||||||
|
static_ip: "192.168.1.100/24"
|
||||||
|
gateway: "192.168.1.254"
|
||||||
|
dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
dns_search:
|
||||||
|
- "search.local"
|
||||||
|
- name: "db-node-02"
|
||||||
|
host: "192.168.1.2"
|
||||||
|
bridge: "br0"
|
||||||
|
vcpu: 8
|
||||||
|
memory_mb: 16384
|
||||||
|
disk_gb: 120
|
||||||
|
static_ip: "192.168.1.101/24"
|
||||||
|
gateway: "192.168.1.254"
|
||||||
|
dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
dns_search:
|
||||||
|
- "search.local"
|
||||||
|
- name: "db-node-03"
|
||||||
|
host: "192.168.1.3"
|
||||||
|
bridge: "br0"
|
||||||
|
vcpu: 8
|
||||||
|
memory_mb: 16384
|
||||||
|
disk_gb: 120
|
||||||
|
static_ip: "192.168.1.102/24"
|
||||||
|
gateway: "192.168.1.254"
|
||||||
|
dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
dns_search:
|
||||||
|
- "search.local"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
name: test-static-cluster
|
||||||
|
runtime:
|
||||||
|
name: python
|
||||||
|
options:
|
||||||
|
toolchain: uv
|
||||||
|
virtualenv: .venv
|
||||||
|
description: Reusable templates for managing clustered or single libvirt KVM guests.
|
||||||
|
packages:
|
||||||
|
libvirt:
|
||||||
|
source: terraform-provider
|
||||||
|
version: 1.1.4
|
||||||
|
parameters:
|
||||||
|
- dmacvicar/libvirt
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import pulumi
|
||||||
|
import pulumi_libvirt as libvirt
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# function to convert GB to bytes for disk size stated in stack config
|
||||||
|
def gb_to_bytes(gb: int) -> int:
|
||||||
|
"""Converts gigabytes to bytes for resource capacity."""
|
||||||
|
return int(gb) * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
config = pulumi.Config("kvm")
|
||||||
|
|
||||||
|
ssh_user = config.get("ssh_user") or "root"
|
||||||
|
base_image_path = config.require("base_image_path")
|
||||||
|
nodes = config.require_object("nodes")
|
||||||
|
secret_hash = config.require_secret("password_hash")
|
||||||
|
|
||||||
|
# Load user data once outside the loop
|
||||||
|
try:
|
||||||
|
with open(config.require("user_data_path"), "r") as f:
|
||||||
|
user_data = pulumi.Output.format(f.read(), password_hash=secret_hash)
|
||||||
|
except Exception as e:
|
||||||
|
# Handle case where file path is wrong or secret fails to resolve
|
||||||
|
print(f"Warning: Could not process user_data. Skipping node deployment. Error: {e}")
|
||||||
|
|
||||||
|
# Dictionary to cache providers per target host
|
||||||
|
providers = {}
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
name = node["name"]
|
||||||
|
target_host = node["host"]
|
||||||
|
bridge_interface = node["bridge"]
|
||||||
|
disk_gb = node["disk_gb"]
|
||||||
|
memory_mb = node["memory_mb"]
|
||||||
|
vcpu = node["vcpu"]
|
||||||
|
|
||||||
|
# Get or cache providers in providers dictionary
|
||||||
|
if target_host not in providers:
|
||||||
|
providers[target_host] = libvirt.Provider(
|
||||||
|
f"prov-{target_host}",
|
||||||
|
uri=f"qemu+sshcmd://{ssh_user}@{target_host}/system"
|
||||||
|
)
|
||||||
|
host_provider = providers[target_host]
|
||||||
|
|
||||||
|
meta_data_yaml = yaml.dump({"instance-id": f"i-{name}", "local-hostname": name})
|
||||||
|
|
||||||
|
# Initialize network payload in V1 format
|
||||||
|
network_config_payload = {
|
||||||
|
"version": 1,
|
||||||
|
"config": [] # Start with an empty list of configurations (The list container)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# physical interface
|
||||||
|
physical_interface = {
|
||||||
|
"type": "physical",
|
||||||
|
"name": "eth0",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"address": node["static_ip"],
|
||||||
|
"gateway": node["gateway"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(physical_interface)
|
||||||
|
|
||||||
|
|
||||||
|
# Append the nameservers to network_config_payload (solves missing nameserver issues)
|
||||||
|
if "dns_servers" in node:
|
||||||
|
nameserver_block = {
|
||||||
|
"type": "nameserver",
|
||||||
|
"address": [str(ip) for ip in node["dns_servers"]] # Create clean python list of strings
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(nameserver_block)
|
||||||
|
|
||||||
|
|
||||||
|
if "dns_search" in node and node["dns_search"]:
|
||||||
|
network_config_payload["config"][0]["subnets"][0]["dns_search"] = [str(s) for s in node["dns_search"]] # Create clean python list of strings
|
||||||
|
|
||||||
|
# complete network config yaml
|
||||||
|
network_config_yaml = yaml.dump(network_config_payload)
|
||||||
|
|
||||||
|
|
||||||
|
# cloud image backing store
|
||||||
|
base_volume = libvirt.Volume(
|
||||||
|
f"base-vol-{name}",
|
||||||
|
name=f"{name}-base",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(10),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": base_image_path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
guest_volume = libvirt.Volume(
|
||||||
|
f"{name}",
|
||||||
|
name=f"{name}",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(disk_gb),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
backing_store={
|
||||||
|
"path": base_volume.id,
|
||||||
|
"format": {
|
||||||
|
"type": "qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
cloud_init_disk = libvirt.CloudinitDisk(
|
||||||
|
f"ci-disk-{name}",
|
||||||
|
meta_data=meta_data_yaml,
|
||||||
|
user_data=user_data,
|
||||||
|
network_config=network_config_yaml, # Passes the fully constructed network payload
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
cloud_init_volume = libvirt.Volume(
|
||||||
|
f"ci-vol-{name}",
|
||||||
|
pool=pool_target,
|
||||||
|
target={
|
||||||
|
"format": {"type": "raw"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": cloud_init_disk.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
guest = libvirt.Domain(
|
||||||
|
f"dom-{name}",
|
||||||
|
name=name,
|
||||||
|
type="kvm",
|
||||||
|
autostart=True,
|
||||||
|
cpu={
|
||||||
|
"mode": "host-passthrough"
|
||||||
|
},
|
||||||
|
memory=memory_mb,
|
||||||
|
memory_unit="MiB",
|
||||||
|
vcpu=vcpu,
|
||||||
|
os={
|
||||||
|
"type": "hvm",
|
||||||
|
"type_arch": "x86_64",
|
||||||
|
"type_machine": "q35",
|
||||||
|
"firmware": "efi",
|
||||||
|
"boot_devices": []
|
||||||
|
},
|
||||||
|
features={
|
||||||
|
"acpi": True
|
||||||
|
},
|
||||||
|
devices={
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": guest_volume.pool,
|
||||||
|
"volume": guest_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "vda",
|
||||||
|
"bus": "virtio"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "qcow2"
|
||||||
|
},
|
||||||
|
"boot": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device": "cdrom",
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": cloud_init_volume.pool,
|
||||||
|
"volume": cloud_init_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "sda",
|
||||||
|
"bus": "sata"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "raw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"type": "bridge",
|
||||||
|
"model": {
|
||||||
|
"type": "virtio"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"bridge": {
|
||||||
|
"bridge": bridge_interface
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wait_for_ip": {
|
||||||
|
"timeout": 300,
|
||||||
|
"source": "lease"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphics": [
|
||||||
|
{
|
||||||
|
"type": "vnc",
|
||||||
|
"vnc": {
|
||||||
|
"autoport": True,
|
||||||
|
"listen": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
pulumi.export("deployed_node", guest.name)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
config:
|
||||||
|
kvm:ssh_user: "root"
|
||||||
|
kvm:pool_target: "default"
|
||||||
|
kvm:base_image_path: "https://repo.almalinux.org/almalinux/10/cloud/x86_64_v2/images/AlmaLinux-10-GenericCloud-latest.x86_64_v2.qcow2"
|
||||||
|
kvm:user_data_path: "../user-data/alma-node.yaml"
|
||||||
|
kvm:guest_name: "prod-app-01"
|
||||||
|
kvm:target_host: "192.168.1.1"
|
||||||
|
kvm:bridge_interface: "br0"
|
||||||
|
kvm:vcpu: 2
|
||||||
|
kvm:memory_mb: 4096
|
||||||
|
kvm:disk_gb: 30
|
||||||
|
kvm:net_device: "eth0"
|
||||||
|
kvm:static_ip: "192.168.1.10/24"
|
||||||
|
kvm:gateway: "192.168.1.254"
|
||||||
|
kvm:dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
kvm:dns_search:
|
||||||
|
- "search.local"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
name: test-static
|
||||||
|
runtime:
|
||||||
|
name: python
|
||||||
|
options:
|
||||||
|
toolchain: uv
|
||||||
|
virtualenv: .venv
|
||||||
|
description: Reusable templates for managing clustered or single libvirt KVM guests.
|
||||||
|
packages:
|
||||||
|
libvirt:
|
||||||
|
source: terraform-provider
|
||||||
|
version: 1.1.4
|
||||||
|
parameters:
|
||||||
|
- dmacvicar/libvirt
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import pulumi
|
||||||
|
import pulumi_libvirt as libvirt
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# Function to convert GB to bytes for disk size stated in stack config
|
||||||
|
def gb_to_bytes(gb: int) -> int:
|
||||||
|
return int(gb) * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
config = pulumi.Config("kvm")
|
||||||
|
ssh_user = config.get("ssh_user") or "root"
|
||||||
|
target_host = config.require("target_host")
|
||||||
|
guest_name = config.require("guest_name")
|
||||||
|
bridge_interface = config.require("bridge_interface")
|
||||||
|
secret_hash = config.require_secret("password_hash")
|
||||||
|
|
||||||
|
# Load cloud-config
|
||||||
|
with open(config.require("user_data_path"), "r") as f:
|
||||||
|
user_data = pulumi.Output.format(f.read(), password_hash=secret_hash)
|
||||||
|
|
||||||
|
# Set KVM host
|
||||||
|
host_provider = libvirt.Provider(
|
||||||
|
"ssh-provider",
|
||||||
|
uri=f"qemu+sshcmd://{ssh_user}@{target_host}/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
meta_data_yaml = yaml.dump({"instance-id": f"i-{guest_name}", "local-hostname": guest_name})
|
||||||
|
|
||||||
|
# Initialize DNS nameservers and search domain
|
||||||
|
dns_servers = config.require_object("dns_servers")
|
||||||
|
dns_search = config.get_object("dns_search") or []
|
||||||
|
|
||||||
|
# Initialize network config
|
||||||
|
network_config_payload = {
|
||||||
|
"version": 1,
|
||||||
|
"config": []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Append "physical" interface
|
||||||
|
physical_interface = {
|
||||||
|
"type": "physical",
|
||||||
|
"name": "eth0",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"address": config.require("static_ip"),
|
||||||
|
"gateway": config.require("gateway")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(physical_interface)
|
||||||
|
|
||||||
|
# Append nameservers
|
||||||
|
if dns_servers:
|
||||||
|
nameserver_block = {
|
||||||
|
"type": "nameserver",
|
||||||
|
"interface": "eth0",
|
||||||
|
"address": dns_servers
|
||||||
|
}
|
||||||
|
network_config_payload["config"].append(nameserver_block)
|
||||||
|
|
||||||
|
|
||||||
|
# Append search domain
|
||||||
|
if dns_search:
|
||||||
|
network_config_payload["config"][0]["subnets"][0]["dns_search"] = list(str(dns_search))
|
||||||
|
|
||||||
|
|
||||||
|
network_config_yaml = yaml.dump(network_config_payload)
|
||||||
|
|
||||||
|
|
||||||
|
base_volume = libvirt.Volume(
|
||||||
|
"base-vol",
|
||||||
|
name=f"{guest_name}-base",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(10),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": base_image_path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
guest_volume = libvirt.Volume(
|
||||||
|
"guest-disk",
|
||||||
|
name=f"{guest_name}",
|
||||||
|
pool=pool_target,
|
||||||
|
capacity=gb_to_bytes(config.require_int("disk_gb")),
|
||||||
|
target={
|
||||||
|
"format": {"type": "qcow2"}
|
||||||
|
},
|
||||||
|
backing_store={
|
||||||
|
"path": base_volume.id,
|
||||||
|
"format": {
|
||||||
|
"type": "qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cloud_init_disk = libvirt.CloudinitDisk(
|
||||||
|
"cloudinit-iso",
|
||||||
|
meta_data=meta_data_yaml,
|
||||||
|
user_data=user_data,
|
||||||
|
network_config=network_config_yaml,
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the cloud-init volume with metadata
|
||||||
|
cloud_init_volume = libvirt.Volume(
|
||||||
|
"cloudinit-vol",
|
||||||
|
pool=pool_target,
|
||||||
|
target={
|
||||||
|
"format": {"type": "raw"}
|
||||||
|
},
|
||||||
|
create={
|
||||||
|
"content": {
|
||||||
|
"url": cloud_init_disk.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
guest = libvirt.Domain(
|
||||||
|
"kvm-guest",
|
||||||
|
name=guest_name,
|
||||||
|
type="kvm",
|
||||||
|
autostart=True,
|
||||||
|
cpu={
|
||||||
|
"mode": "host-passthrough"
|
||||||
|
},
|
||||||
|
memory=config.require_int("memory_mb"),
|
||||||
|
memory_unit="MiB",
|
||||||
|
vcpu=config.require_int("vcpu"),
|
||||||
|
os={
|
||||||
|
"type": "hvm",
|
||||||
|
"type_arch": "x86_64",
|
||||||
|
"type_machine": "q35",
|
||||||
|
"firmware": "efi",
|
||||||
|
"boot_devices": []
|
||||||
|
},
|
||||||
|
features={
|
||||||
|
"acpi": True
|
||||||
|
},
|
||||||
|
devices={
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": guest_volume.pool,
|
||||||
|
"volume": guest_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "vda",
|
||||||
|
"bus": "virtio"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "qcow2"
|
||||||
|
},
|
||||||
|
"boot": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device": "cdrom",
|
||||||
|
"source": {
|
||||||
|
"volume": {
|
||||||
|
"pool": cloud_init_volume.pool,
|
||||||
|
"volume": cloud_init_volume.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"dev": "sda",
|
||||||
|
"bus": "sata"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"type": "raw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"type": "bridge",
|
||||||
|
"model": {
|
||||||
|
"type": "virtio"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"bridge": {
|
||||||
|
"bridge": bridge_interface
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wait_for_ip": {
|
||||||
|
"timeout": 300,
|
||||||
|
"source": "lease"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphics": [
|
||||||
|
{
|
||||||
|
"type": "vnc",
|
||||||
|
"vnc": {
|
||||||
|
"autoport": True,
|
||||||
|
"listen": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
opts=pulumi.ResourceOptions(provider=host_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
pulumi.export("deployed_node", guest.name)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
config:
|
||||||
|
kvm:ssh_user: "root"
|
||||||
|
kvm:pool_target: "default"
|
||||||
|
kvm:base_image_path: "https://repo.almalinux.org/almalinux/10/cloud/x86_64_v2/images/AlmaLinux-10-GenericCloud-latest.x86_64_v2.qcow2"
|
||||||
|
kvm:user_data_path: "../user-data/alma-node"
|
||||||
|
kvm:nodes:
|
||||||
|
- name: "db-node-01"
|
||||||
|
host: "192.168.1.1"
|
||||||
|
bridge: "br0"
|
||||||
|
vcpu: 8
|
||||||
|
memory_mb: 16384
|
||||||
|
disk_gb: 120
|
||||||
|
static_ip: "192.168.1.100/24"
|
||||||
|
gateway: "192.168.1.254"
|
||||||
|
dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
dns_search:
|
||||||
|
- "search.local"
|
||||||
|
- name: "db-node-02"
|
||||||
|
host: "192.168.1.2"
|
||||||
|
bridge: "br0"
|
||||||
|
vcpu: 8
|
||||||
|
memory_mb: 16384
|
||||||
|
disk_gb: 120
|
||||||
|
static_ip: "192.168.1.101/24"
|
||||||
|
gateway: "192.168.1.254"
|
||||||
|
dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
dns_search:
|
||||||
|
- "search.local"
|
||||||
|
- name: "db-node-03"
|
||||||
|
host: "192.168.1.3"
|
||||||
|
bridge: "br0"
|
||||||
|
vcpu: 8
|
||||||
|
memory_mb: 16384
|
||||||
|
disk_gb: 120
|
||||||
|
static_ip: "192.168.1.102/24"
|
||||||
|
gateway: "192.168.1.254"
|
||||||
|
dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
dns_search:
|
||||||
|
- "search.local"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
config:
|
||||||
|
kvm:ssh_user: "root"
|
||||||
|
kvm:pool_target: "default"
|
||||||
|
kvm:base_image_path: "https://repo.almalinux.org/almalinux/10/cloud/x86_64_v2/images/AlmaLinux-10-GenericCloud-latest.x86_64_v2.qcow2"
|
||||||
|
kvm:user_data_path: "../user-data/alma-node.yaml"
|
||||||
|
kvm:guest_name: "prod-app-01"
|
||||||
|
kvm:target_host: "192.168.1.1"
|
||||||
|
kvm:bridge_interface: "br0"
|
||||||
|
kvm:vcpu: 2
|
||||||
|
kvm:memory_mb: 4096
|
||||||
|
kvm:disk_gb: 30
|
||||||
|
kvm:net_device: "eth0"
|
||||||
|
kvm:static_ip: "192.168.1.10/24"
|
||||||
|
kvm:gateway: "192.168.1.254"
|
||||||
|
kvm:dns_servers:
|
||||||
|
- "9.9.9.9"
|
||||||
|
kvm:dns_search:
|
||||||
|
- "search.local"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#cloud-config
|
||||||
|
users:
|
||||||
|
- name: username
|
||||||
|
lock_passwd: false
|
||||||
|
passwd: "{password_hash}"
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- ssh-ed25519 AAAAC..[rest of pubkey]
|
||||||
|
groups: wheel
|
||||||
|
shell: /bin/bash
|
||||||
|
sudo: ['ALL=(ALL) NOPASSWD: ALL']
|
||||||
|
prefer_fqdn_over_hostname: true
|
||||||
|
packages:
|
||||||
|
- bind-utils
|
||||||
|
- plocate
|
||||||
|
- unzip
|
||||||
|
- tar
|
||||||
|
- policycoreutils-python-utils
|
||||||
|
- rsync
|
||||||
|
- nfs-utils
|
||||||
|
- vim
|
||||||
|
- firewalld
|
||||||
|
- curl
|
||||||
|
runcmd:
|
||||||
|
- echo "HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub" >> /etc/ssh/sshd_config
|
||||||
|
- sed -i 's/^#*HostKey \/etc\/ssh\/ssh_host_ed25519_key/HostKey \/etc\/ssh\/ssh_host_ed25519_key/' /etc/ssh/sshd_config
|
||||||
|
- sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
|
||||||
|
- dnf upgrade -y && systemctl reboot
|
||||||
Reference in New Issue
Block a user