From 43c9cb4d961d6521e0b8bbfdbc9c0eae26b8b0c3 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:06:44 +0200
Subject: [PATCH 01/30] Update variables.tf
added new variable
---
variables.tf | 2 ++
1 file changed, 2 insertions(+)
diff --git a/variables.tf b/variables.tf
index c91fc394..f4069800 100644
--- a/variables.tf
+++ b/variables.tf
@@ -242,6 +242,7 @@ variable "agent_nodepools" {
labels = list(string)
taints = list(string)
longhorn_volume_size = optional(number)
+ longhorn_mount_path = optional(string, "/var/longhorn")
swap_size = optional(string, "")
zram_size = optional(string, "")
kubelet_args = optional(list(string), ["kube-reserved=cpu=50m,memory=300Mi,ephemeral-storage=1Gi", "system-reserved=cpu=250m,memory=300Mi"])
@@ -261,6 +262,7 @@ variable "agent_nodepools" {
labels = optional(list(string))
taints = optional(list(string))
longhorn_volume_size = optional(number)
+ longhorn_mount_path = optional(string, "/var/longhorn")
swap_size = optional(string, "")
zram_size = optional(string, "")
kubelet_args = optional(list(string), ["kube-reserved=cpu=50m,memory=300Mi,ephemeral-storage=1Gi", "system-reserved=cpu=250m,memory=300Mi"])
From 7c15422c694c76813928dd2bc57b29bfd733df30 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:10:09 +0200
Subject: [PATCH 02/30] Update agents.tf
mount path now parameterised
---
agents.tf | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/agents.tf b/agents.tf
index 33c39518..a65fb7da 100644
--- a/agents.tf
+++ b/agents.tf
@@ -192,10 +192,10 @@ resource "null_resource" "configure_longhorn_volume" {
# Start the k3s agent and wait for it to have started
provisioner "remote-exec" {
inline = [
- "mkdir /var/longhorn >/dev/null 2>&1",
- "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} /var/longhorn",
+ "mkdir ${var.longhorn_mount_path} >/dev/null 2>&1",
+ "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path}",
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
- "echo '${hcloud_volume.longhorn_volume[each.key].linux_device} /var/longhorn ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
+ "echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
]
}
From 3336faf2592c4589ddab49553866d287d5578e01 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:25:41 +0200
Subject: [PATCH 03/30] Update locals.tf
added new variable
---
locals.tf | 2 ++
1 file changed, 2 insertions(+)
diff --git a/locals.tf b/locals.tf
index 8ca4c293..fe8417b7 100644
--- a/locals.tf
+++ b/locals.tf
@@ -224,6 +224,7 @@ locals {
nodepool_name : nodepool_obj.name,
server_type : nodepool_obj.server_type,
longhorn_volume_size : coalesce(nodepool_obj.longhorn_volume_size, 0),
+ longhorn_mount_path : nodepool_obj.longhorn_mount_path,
floating_ip : lookup(nodepool_obj, "floating_ip", false),
floating_ip_rdns : lookup(nodepool_obj, "floating_ip_rdns", false),
location : nodepool_obj.location,
@@ -253,6 +254,7 @@ locals {
nodepool_name : nodepool_obj.name,
server_type : nodepool_obj.server_type,
longhorn_volume_size : coalesce(nodepool_obj.longhorn_volume_size, 0),
+ longhorn_mount_path : nodepool_obj.longhorn_mount_path
floating_ip : lookup(nodepool_obj, "floating_ip", false),
floating_ip_rdns : lookup(nodepool_obj, "floating_ip_rdns", false),
location : nodepool_obj.location,
From 574df9e6c46428a558b9f37da5517e7545d9aa54 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:31:08 +0200
Subject: [PATCH 04/30] Update agents.tf
updated with suggested changes
---
agents.tf | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/agents.tf b/agents.tf
index a65fb7da..914ded8e 100644
--- a/agents.tf
+++ b/agents.tf
@@ -192,10 +192,10 @@ resource "null_resource" "configure_longhorn_volume" {
# Start the k3s agent and wait for it to have started
provisioner "remote-exec" {
inline = [
- "mkdir ${var.longhorn_mount_path} >/dev/null 2>&1",
- "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path}",
- "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
- "echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
+ "mkdir -p ${each.value.longhorn_mount_path} >/dev/null 2>&1",
+ "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path}",
+ "${var.longhorn_fstype == \"ext4\" ? \"resize2fs\" : \"xfs_growfs\"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
+ "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
]
}
From 4f77bb5d500924019f4b70edc1e18ef8afd49f15 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:40:48 +0200
Subject: [PATCH 05/30] Update locals.tf
fix syntax
---
locals.tf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/locals.tf b/locals.tf
index fe8417b7..e26fe72f 100644
--- a/locals.tf
+++ b/locals.tf
@@ -254,7 +254,7 @@ locals {
nodepool_name : nodepool_obj.name,
server_type : nodepool_obj.server_type,
longhorn_volume_size : coalesce(nodepool_obj.longhorn_volume_size, 0),
- longhorn_mount_path : nodepool_obj.longhorn_mount_path
+ longhorn_mount_path : nodepool_obj.longhorn_mount_path,
floating_ip : lookup(nodepool_obj, "floating_ip", false),
floating_ip_rdns : lookup(nodepool_obj, "floating_ip_rdns", false),
location : nodepool_obj.location,
From d68210ec6164dd9e6944624548ec0d3821d657e7 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:44:40 +0200
Subject: [PATCH 06/30] Update agents.tf
commit suggestions
---
agents.tf | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/agents.tf b/agents.tf
index 914ded8e..240dd189 100644
--- a/agents.tf
+++ b/agents.tf
@@ -192,8 +192,8 @@ resource "null_resource" "configure_longhorn_volume" {
# Start the k3s agent and wait for it to have started
provisioner "remote-exec" {
inline = [
- "mkdir -p ${each.value.longhorn_mount_path} >/dev/null 2>&1",
- "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path}",
+ "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
+ "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
"${var.longhorn_fstype == \"ext4\" ? \"resize2fs\" : \"xfs_growfs\"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
"grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
]
From 785d476e4b57d91c2b8a3527340518f6e82dc529 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Fri, 20 Jun 2025 22:46:26 +0200
Subject: [PATCH 07/30] Update variables.tf
added variable validation
---
variables.tf | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/variables.tf b/variables.tf
index f4069800..3c8fe224 100644
--- a/variables.tf
+++ b/variables.tf
@@ -305,6 +305,14 @@ variable "agent_nodepools" {
error_message = "Hetzner does not support networks with more than 100 servers."
}
+ validation {
+ condition = (
+ can(regex("^/([a-zA-Z0-9._-]+/?)*$", var.longhorn_mount_path)) &&
+ !endswith(var.longhorn_mount_path, "/") || var.longhorn_mount_path == "/"
+ )
+ error_message = "The longhorn_mount_path must be a valid absolute Linux path without a trailing slash (except root '/'), and contain only alphanumeric characters, dashes, underscores, and dots."
+ }
+
}
variable "cluster_autoscaler_image" {
From fffc5bdc1213a99d7015ab032d4a47277c24c787 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Sun, 27 Jul 2025 22:04:10 +0200
Subject: [PATCH 08/30] Update agents.tf
removed / from provisioner
---
agents.tf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/agents.tf b/agents.tf
index 240dd189..565067b8 100644
--- a/agents.tf
+++ b/agents.tf
@@ -194,7 +194,7 @@ resource "null_resource" "configure_longhorn_volume" {
inline = [
"mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
"mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
- "${var.longhorn_fstype == \"ext4\" ? \"resize2fs\" : \"xfs_growfs\"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
+ "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
"grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
]
}
From e4c33b3090a92c0a708f39ff0bfb822cb48da3cf Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Sun, 27 Jul 2025 22:08:35 +0200
Subject: [PATCH 09/30] Update variables.tf
fixed validation rule
---
variables.tf | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/variables.tf b/variables.tf
index 3c8fe224..5ae30a0c 100644
--- a/variables.tf
+++ b/variables.tf
@@ -306,11 +306,15 @@ variable "agent_nodepools" {
}
validation {
- condition = (
- can(regex("^/([a-zA-Z0-9._-]+/?)*$", var.longhorn_mount_path)) &&
- !endswith(var.longhorn_mount_path, "/") || var.longhorn_mount_path == "/"
- )
- error_message = "The longhorn_mount_path must be a valid absolute Linux path without a trailing slash (except root '/'), and contain only alphanumeric characters, dashes, underscores, and dots."
+ condition = alltrue([
+ for np in var.agent_nodepools : (
+ (
+ can(regex("^/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) &&
+ (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/")
+ )
+ )
+ ])
+ error_message = "Each longhorn_mount_path must be a valid absolute path without trailing slash (except '/')."
}
}
From 8ad21417f6b92c459e6037a373a8e369ce1870f0 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Thu, 7 Aug 2025 11:17:19 +0200
Subject: [PATCH 10/30] Update variables.tf
updated validation rule for longhorn
---
variables.tf | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/variables.tf b/variables.tf
index 5ae30a0c..92a6275c 100644
--- a/variables.tf
+++ b/variables.tf
@@ -306,15 +306,15 @@ variable "agent_nodepools" {
}
validation {
- condition = alltrue([
- for np in var.agent_nodepools : (
- (
- can(regex("^/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) &&
- (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/")
- )
+ condition = alltrue([
+ for np in var.agent_nodepools : (
+ (
+ can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) &&
+ (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/")
)
- ])
- error_message = "Each longhorn_mount_path must be a valid absolute path without trailing slash (except '/')."
+ )
+ ])
+ error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except '/var/')."
}
}
From 94b77e6334fd792f4dd1915e993b2f57b0328b22 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Thu, 7 Aug 2025 15:11:22 +0200
Subject: [PATCH 11/30] Create customize-mount-path-longhorn.md
added new doc
---
docs/customize-mount-path-longhorn.md | 97 +++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
create mode 100644 docs/customize-mount-path-longhorn.md
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
new file mode 100644
index 00000000..1d204411
--- /dev/null
+++ b/docs/customize-mount-path-longhorn.md
@@ -0,0 +1,97 @@
+## Page describes how to use longhorn custom mount path
+
+In order to use nvme and extarnal disks with longhorn, you need to mount extarnal disk to another location under `/var/` folder.
+This gives more storage capacity across cluster, if you didint disable defualt longhorn disks off course.
+
+
+> ⚠️ Note: You can set any mount path but only within `/var/` folder
+
+### How set mount for your external disk differs to ``/var/`` folder ?
+
+1. You must anable longhorn in you module
+2. Set helm values
+```yamllonghorn_values = < node.metadata[0].name
+ }
+ triggers = {
+ always_run = timestamp()
+ }
+ provisioner "local-exec" {
+ command = <<-EOT
+ KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{
+ "spec": {
+ "disks": {
+ "external-ssd": {
+ "path": "/var/lib/longhorn", # Path you set in nodepools variable
+ "allowScheduling": true,
+ "tags": ["ssd"]
+ }
+ }
+ }
+ }'
+ EOT
+ }
+}
+
+resource "kubernetes_manifest" "longhorn_ssd_replica" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "longhorn-ssd-replica"
+ }
+ provisioner = "driver.longhorn.io"
+ parameters = {
+ numberOfReplicas = "1"
+ staleReplicaTimeout = "30"
+ diskSelector = "ssd"
+
+ fromBackup = ""
+ }
+ reclaimPolicy = "Delete"
+ allowVolumeExpansion = true
+ volumeBindingMode = "Immediate"
+ }
+ depends_on = [null_resource.longhorn_patch_external_disk]
+}
+```
From 0ae30f09b936521346048636bcc00465d665bbeb Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Wed, 13 Aug 2025 13:50:30 +0200
Subject: [PATCH 12/30] Update kube.tf.example
added small description to kube.tf.example
---
kube.tf.example | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/kube.tf.example b/kube.tf.example
index 7068e54f..e50d4b5c 100644
--- a/kube.tf.example
+++ b/kube.tf.example
@@ -233,7 +233,9 @@ module "kube-hetzner" {
# Something worth noting is that Volume storage is slower than node storage, which is achieved by not mentioning longhorn_volume_size or setting it to 0.
# So for something like DBs, you definitely want node storage, for other things like backups, volume storage is fine, and cheaper.
# longhorn_volume_size = 20
-
+ # Set any path inside /var/ folder to have ability use additional storage class along with default one which is by default must be set in helm values to
+ # /var/longhorn
+ # longhorn_mount_path = "/var/lib/longhorn"
# Enable automatic backups via Hetzner (default: false)
# backups = true
},
From 54af473a4e7ce793adbf6bc27a58e0c86474cd0e Mon Sep 17 00:00:00 2001
From: garry-t
Date: Fri, 10 Oct 2025 14:37:28 +0200
Subject: [PATCH 13/30] - fix typo
---
docs/customize-mount-path-longhorn.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
index 1d204411..d47cbac0 100644
--- a/docs/customize-mount-path-longhorn.md
+++ b/docs/customize-mount-path-longhorn.md
@@ -8,7 +8,7 @@ This gives more storage capacity across cluster, if you didint disable defualt l
### How set mount for your external disk differs to ``/var/`` folder ?
-1. You must anable longhorn in you module
+1. You must enable longhorn in you module
2. Set helm values
```yamllonghorn_values = <
Date: Sat, 11 Oct 2025 13:37:38 +0200
Subject: [PATCH 14/30] - gemini suggest fix doc
---
docs/customize-mount-path-longhorn.md | 185 ++++++++++++++------------
1 file changed, 98 insertions(+), 87 deletions(-)
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
index d47cbac0..538897e9 100644
--- a/docs/customize-mount-path-longhorn.md
+++ b/docs/customize-mount-path-longhorn.md
@@ -1,97 +1,108 @@
-## Page describes how to use longhorn custom mount path
+## How to use a custom mount path for Longhorn
-In order to use nvme and extarnal disks with longhorn, you need to mount extarnal disk to another location under `/var/` folder.
-This gives more storage capacity across cluster, if you didint disable defualt longhorn disks off course.
-
-
-> ⚠️ Note: You can set any mount path but only within `/var/` folder
-
-### How set mount for your external disk differs to ``/var/`` folder ?
-
-1. You must enable longhorn in you module
-2. Set helm values
-```yamllonghorn_values = < ⚠️ Note: You can set any mount path, but it must be within the `/var/` folder.
+
+### How to set a custom mount path for your external disk?
+
+1. You must enable Longhorn in your module.
+ ```terraform
+ enable_longhorn = true
+ ```
+
+2. Set the Helm values for Longhorn. The `defaultDataPath` is important as this path is automatically created by Longhorn and will be the default storage class pointing to your primary disks (e.g., NVMe).
+ ```yaml
+ longhorn_values = < node.metadata[0].name
- }
- triggers = {
- always_run = timestamp()
- }
- provisioner "local-exec" {
- command = <<-EOT
- KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{
- "spec": {
- "disks": {
- "external-ssd": {
- "path": "/var/lib/longhorn", # Path you set in nodepools variable
- "allowScheduling": true,
- "tags": ["ssd"]
- }
- }
- }
- }'
- EOT
- }
-}
+ for_each = {
+ for node in data.kubernetes_nodes.ssd_nodes.nodes : node.metadata[0].name => node.metadata[0].name
+ }
-resource "kubernetes_manifest" "longhorn_ssd_replica" {
- manifest = {
- apiVersion = "storage.k8s.io/v1"
- kind = "StorageClass"
- metadata = {
- name = "longhorn-ssd-replica"
- }
- provisioner = "driver.longhorn.io"
- parameters = {
- numberOfReplicas = "1"
- staleReplicaTimeout = "30"
- diskSelector = "ssd"
-
- fromBackup = ""
- }
- reclaimPolicy = "Delete"
- allowVolumeExpansion = true
- volumeBindingMode = "Immediate"
- }
- depends_on = [null_resource.longhorn_patch_external_disk]
+ triggers = {
+ always_run = timestamp()
+ }
+
+ provisioner "local-exec" {
+ command = <<-EOT
+ KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{
+ "spec": {
+ "disks": {
+ "external-ssd": {
+ "path": "/var/lib/longhorn", # The path you set in the nodepools variable
+ "allowScheduling": true,
+ "tags": ["ssd"]
+ }
+ }
+ }
+ }'
+ EOT
+ }
}
-```
+
+# Create a new StorageClass for the SSD-backed Longhorn storage
+resource "kubernetes_manifest" "longhorn_ssd_storageclass" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "longhorn-ssd"
+ }
+ provisioner = "driver.longhorn.io"
+ parameters = {
+ numberOfReplicas = "1"
+ staleReplicaTimeout = "30"
+ diskSelector = "ssd"
+ fromBackup = ""
+ }
+ reclaimPolicy = "Delete"
+ allowVolumeExpansion = true
+ volumeBindingMode = "Immediate"
+ }
+
+ depends_on = [null_resource.longhorn_patch_external_disk]
+}
\ No newline at end of file
From 968772bdb5060702096bd3810062b5e7788f3b7f Mon Sep 17 00:00:00 2001
From: garry-t
Date: Sat, 11 Oct 2025 13:39:14 +0200
Subject: [PATCH 15/30] - gemini suggest fix comment
---
kube.tf.example | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kube.tf.example b/kube.tf.example
index e50d4b5c..9ec42726 100644
--- a/kube.tf.example
+++ b/kube.tf.example
@@ -233,7 +233,7 @@ module "kube-hetzner" {
# Something worth noting is that Volume storage is slower than node storage, which is achieved by not mentioning longhorn_volume_size or setting it to 0.
# So for something like DBs, you definitely want node storage, for other things like backups, volume storage is fine, and cheaper.
# longhorn_volume_size = 20
- # Set any path inside /var/ folder to have ability use additional storage class along with default one which is by default must be set in helm values to
+ # Set any path inside /var/ folder to have the ability to use an additional storage class along with the default one, which by default must be set in helm values to
# /var/longhorn
# longhorn_mount_path = "/var/lib/longhorn"
# Enable automatic backups via Hetzner (default: false)
From e720ea9683d8d0c92abc0c9fbb6a98da9242f349 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Sat, 11 Oct 2025 13:46:22 +0200
Subject: [PATCH 16/30] - gemini fix validation
---
variables.tf | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/variables.tf b/variables.tf
index 92a6275c..9876a1ca 100644
--- a/variables.tf
+++ b/variables.tf
@@ -306,15 +306,25 @@ variable "agent_nodepools" {
}
validation {
- condition = alltrue([
- for np in var.agent_nodepools : (
- (
- can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) &&
- (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/")
+ condition = alltrue(flatten([
+ for np in var.agent_nodepools : concat(
+ [
+ (
+ can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) &&
+ (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/")
+ )
+ ],
+ [
+ for node in values(coalesce(np.nodes, {})) : (
+ (
+ can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", node.longhorn_mount_path)) &&
+ (!endswith(node.longhorn_mount_path, "/") || node.longhorn_mount_path == "/var/")
+ )
+ )
+ ]
)
- )
- ])
- error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except '/var/')."
+ ]))
+ error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings."
}
}
From cb6f68cbb7d3fb9c70e672f5d9c49ce280374b62 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Sat, 11 Oct 2025 17:06:32 +0200
Subject: [PATCH 17/30] - gemini yet another fixes
---
variables.tf | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/variables.tf b/variables.tf
index 9876a1ca..e3a884d7 100644
--- a/variables.tf
+++ b/variables.tf
@@ -262,7 +262,7 @@ variable "agent_nodepools" {
labels = optional(list(string))
taints = optional(list(string))
longhorn_volume_size = optional(number)
- longhorn_mount_path = optional(string, "/var/longhorn")
+ longhorn_mount_path = optional(string, null)
swap_size = optional(string, "")
zram_size = optional(string, "")
kubelet_args = optional(list(string), ["kube-reserved=cpu=50m,memory=300Mi,ephemeral-storage=1Gi", "system-reserved=cpu=250m,memory=300Mi"])
@@ -309,17 +309,11 @@ variable "agent_nodepools" {
condition = alltrue(flatten([
for np in var.agent_nodepools : concat(
[
- (
- can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) &&
- (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/")
- )
+ can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path))
],
[
for node in values(coalesce(np.nodes, {})) : (
- (
- can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", node.longhorn_mount_path)) &&
- (!endswith(node.longhorn_mount_path, "/") || node.longhorn_mount_path == "/var/")
- )
+ node.longhorn_mount_path == null || can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path))
)
]
)
From 66d9676dcae4a18bdcde4032ebf6469ac6577cdf Mon Sep 17 00:00:00 2001
From: garry-t
Date: Sat, 11 Oct 2025 17:07:41 +0200
Subject: [PATCH 18/30] - gemini mountpoint checks
---
agents.tf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/agents.tf b/agents.tf
index 565067b8..fc488d19 100644
--- a/agents.tf
+++ b/agents.tf
@@ -193,7 +193,7 @@ resource "null_resource" "configure_longhorn_volume" {
provisioner "remote-exec" {
inline = [
"mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
- "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
+ "mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
"grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
]
From aa01c5df4d62652c54f09ca141cdc0d04f857b5e Mon Sep 17 00:00:00 2001
From: garry-t
Date: Fri, 24 Oct 2025 11:17:19 +0200
Subject: [PATCH 19/30] - gemini added "set -e"
---
agents.tf | 1 +
1 file changed, 1 insertion(+)
diff --git a/agents.tf b/agents.tf
index fc488d19..ab7612b5 100644
--- a/agents.tf
+++ b/agents.tf
@@ -192,6 +192,7 @@ resource "null_resource" "configure_longhorn_volume" {
# Start the k3s agent and wait for it to have started
provisioner "remote-exec" {
inline = [
+ "set -e",
"mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
"mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
From 3b517e2ac8f2cdef30d24e13bc6b991322b22983 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Mon, 27 Oct 2025 20:20:05 +0100
Subject: [PATCH 20/30] - gemini fix review
---
agents.tf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/agents.tf b/agents.tf
index ab7612b5..af5b0902 100644
--- a/agents.tf
+++ b/agents.tf
@@ -196,7 +196,7 @@ resource "null_resource" "configure_longhorn_volume" {
"mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
"mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
- "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
+ "awk '{print $2}' /etc/fstab | grep -qxF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
]
}
From 889ae77736160a275d9819ff5d79b3fa7fcd03c8 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Tue, 28 Oct 2025 09:03:21 +0100
Subject: [PATCH 21/30] - provide idempotent mount command
---
agents.tf | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/agents.tf b/agents.tf
index af5b0902..066b58a9 100644
--- a/agents.tf
+++ b/agents.tf
@@ -196,8 +196,9 @@ resource "null_resource" "configure_longhorn_volume" {
"mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
"mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
- "awk '{print $2}' /etc/fstab | grep -qxF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab"
- ]
+ # Match any non-comment line (^[^#]) with any first field, followed by a space and your mount path in the second column.
+ # This prevents false positives like /data matching /data1.
+ "grep -qE '^[^#[:space:]]+[[:space:]]+${each.value.longhorn_mount_path}[[:space:]]' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' | tee -a /etc/fstab >/dev/null"]
}
connection {
From 42a32cd5fbc71f3f3b9e240353307a070040c214 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Tue, 28 Oct 2025 09:31:30 +0100
Subject: [PATCH 22/30] - fix one more gemini review comment
---
agents.tf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/agents.tf b/agents.tf
index 066b58a9..3b4f8b7a 100644
--- a/agents.tf
+++ b/agents.tf
@@ -193,7 +193,7 @@ resource "null_resource" "configure_longhorn_volume" {
provisioner "remote-exec" {
inline = [
"set -e",
- "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1",
+ "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null",
"mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'",
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
# Match any non-comment line (^[^#]) with any first field, followed by a space and your mount path in the second column.
From 436224a13abd8965cdcc6d5a885a40d48ac2ca9f Mon Sep 17 00:00:00 2001
From: garry-t
Date: Tue, 28 Oct 2025 10:34:56 +0100
Subject: [PATCH 23/30] - improved regex one more time
---
agents.tf | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/agents.tf b/agents.tf
index 3b4f8b7a..5c5c52bc 100644
--- a/agents.tf
+++ b/agents.tf
@@ -198,7 +198,8 @@ resource "null_resource" "configure_longhorn_volume" {
"${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}",
# Match any non-comment line (^[^#]) with any first field, followed by a space and your mount path in the second column.
# This prevents false positives like /data matching /data1.
- "grep -qE '^[^#[:space:]]+[[:space:]]+${each.value.longhorn_mount_path}[[:space:]]' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' | tee -a /etc/fstab >/dev/null"]
+ "awk -v path='${each.value.longhorn_mount_path}' '$0 !~ /^#/ && $2 == path { found=1; exit } END { exit !found }' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' | tee -a /etc/fstab >/dev/null"
+ ]
}
connection {
From b57b8190045d78fa1f3197d4409f88690edc94a0 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Tue, 28 Oct 2025 14:13:38 +0100
Subject: [PATCH 24/30] Update docs/customize-mount-path-longhorn.md
just to make gemini calm
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---
docs/customize-mount-path-longhorn.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
index 538897e9..c62137a7 100644
--- a/docs/customize-mount-path-longhorn.md
+++ b/docs/customize-mount-path-longhorn.md
@@ -94,7 +94,7 @@ resource "kubernetes_manifest" "longhorn_ssd_storageclass" {
}
provisioner = "driver.longhorn.io"
parameters = {
- numberOfReplicas = "1"
+ numberOfReplicas = "3"
staleReplicaTimeout = "30"
diskSelector = "ssd"
fromBackup = ""
From 8fdce2641860996e976509b6fbac7426d31462a1 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Tue, 28 Oct 2025 14:28:08 +0100
Subject: [PATCH 25/30] - improve validation variable
---
variables.tf | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/variables.tf b/variables.tf
index bf7bbd34..84d54558 100644
--- a/variables.tf
+++ b/variables.tf
@@ -309,16 +309,22 @@ variable "agent_nodepools" {
condition = alltrue(flatten([
for np in var.agent_nodepools : concat(
[
- can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path))
+ can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path)) &&
+ !contains(split("/", np.longhorn_mount_path), "..") &&
+ !contains(split("/", np.longhorn_mount_path), ".")
],
[
for node in values(coalesce(np.nodes, {})) : (
- node.longhorn_mount_path == null || can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path))
+ node.longhorn_mount_path == null || (
+ can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path)) &&
+ !contains(split("/", node.longhorn_mount_path), "..") &&
+ !contains(split("/", node.longhorn_mount_path), ".")
+ )
)
]
)
]))
- error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings."
+ error_message = "Each longhorn_mount_path must be a valid, absolute path starting with '/var/', not contain '.' or '..' components, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings."
}
}
From a9dcac186b7fa01c75a3719f742ccddcc4e366db Mon Sep 17 00:00:00 2001
From: garry-t
Date: Tue, 28 Oct 2025 14:31:39 +0100
Subject: [PATCH 26/30] - satisfy gemini
---
docs/customize-mount-path-longhorn.md | 5 -----
1 file changed, 5 deletions(-)
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
index c62137a7..78421166 100644
--- a/docs/customize-mount-path-longhorn.md
+++ b/docs/customize-mount-path-longhorn.md
@@ -62,11 +62,6 @@ resource "null_resource" "longhorn_patch_external_disk" {
for_each = {
for node in data.kubernetes_nodes.ssd_nodes.nodes : node.metadata[0].name => node.metadata[0].name
}
-
- triggers = {
- always_run = timestamp()
- }
-
provisioner "local-exec" {
command = <<-EOT
KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{
From fe22dbd94c32d2550aaa9a8ed8bcce6f97bcd959 Mon Sep 17 00:00:00 2001
From: IT <113095009+garry-t@users.noreply.github.com>
Date: Tue, 28 Oct 2025 15:02:55 +0100
Subject: [PATCH 27/30] Update docs/customize-mount-path-longhorn.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---
docs/customize-mount-path-longhorn.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
index 78421166..c9e4cd75 100644
--- a/docs/customize-mount-path-longhorn.md
+++ b/docs/customize-mount-path-longhorn.md
@@ -68,7 +68,7 @@ resource "null_resource" "longhorn_patch_external_disk" {
"spec": {
"disks": {
"external-ssd": {
- "path": "/var/lib/longhorn", # The path you set in the nodepools variable
+ "path": "/var/lib/longhorn", # IMPORTANT: This path must match the 'longhorn_mount_path' for the nodes selected by the 'storage=ssd' label. This example assumes all selected nodes use the same path.
"allowScheduling": true,
"tags": ["ssd"]
}
From 0d58cedef46324e295396d9a7e32af590eb91e24 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Tue, 28 Oct 2025 15:07:54 +0100
Subject: [PATCH 28/30] - gemini yet another fix
---
variables.tf | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/variables.tf b/variables.tf
index 84d54558..4a181aa7 100644
--- a/variables.tf
+++ b/variables.tf
@@ -309,14 +309,14 @@ variable "agent_nodepools" {
condition = alltrue(flatten([
for np in var.agent_nodepools : concat(
[
- can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path)) &&
+ regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path) &&
!contains(split("/", np.longhorn_mount_path), "..") &&
!contains(split("/", np.longhorn_mount_path), ".")
],
[
for node in values(coalesce(np.nodes, {})) : (
node.longhorn_mount_path == null || (
- can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path)) &&
+ regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path) &&
!contains(split("/", node.longhorn_mount_path), "..") &&
!contains(split("/", node.longhorn_mount_path), ".")
)
From 115988642f59f63b905ae04f7c46eb1e0a0b7569 Mon Sep 17 00:00:00 2001
From: garry-t
Date: Wed, 29 Oct 2025 13:09:09 +0100
Subject: [PATCH 29/30] - gemini yet another validation fix
---
variables.tf | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/variables.tf b/variables.tf
index 4a181aa7..1277291f 100644
--- a/variables.tf
+++ b/variables.tf
@@ -309,14 +309,14 @@ variable "agent_nodepools" {
condition = alltrue(flatten([
for np in var.agent_nodepools : concat(
[
- regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path) &&
+ regex("^/var/[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*$", np.longhorn_mount_path) &&
!contains(split("/", np.longhorn_mount_path), "..") &&
!contains(split("/", np.longhorn_mount_path), ".")
],
[
for node in values(coalesce(np.nodes, {})) : (
node.longhorn_mount_path == null || (
- regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path) &&
+ regex("^/var/[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*$", node.longhorn_mount_path) &&
!contains(split("/", node.longhorn_mount_path), "..") &&
!contains(split("/", node.longhorn_mount_path), ".")
)
@@ -324,7 +324,7 @@ variable "agent_nodepools" {
]
)
]))
- error_message = "Each longhorn_mount_path must be a valid, absolute path starting with '/var/', not contain '.' or '..' components, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings."
+ error_message = "Each longhorn_mount_path must be a valid, absolute path within a subdirectory of '/var/', not contain '.' or '..' components, and not end with a slash. This applies to both nodepool-level and node-level settings."
}
}
From 2befe18ee12c0b2af486f3a268ff5e4ba84fbd7c Mon Sep 17 00:00:00 2001
From: garry-t
Date: Wed, 29 Oct 2025 13:12:22 +0100
Subject: [PATCH 30/30] - gemini fix doc..
---
docs/customize-mount-path-longhorn.md | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md
index c9e4cd75..b4e2aedb 100644
--- a/docs/customize-mount-path-longhorn.md
+++ b/docs/customize-mount-path-longhorn.md
@@ -25,19 +25,24 @@ In order to use NVMe and external disks with Longhorn, you may need to mount an
EOT
```
-3. In the `agent_nodepools` where you want to have a customized mount path, set the `longhorn_mount_path` variable.
+3. In the `agent_nodepools` where you want to have a customized mount path, set the `longhorn_mount_path` variable. It's a good practice to define this path as a local variable to ensure consistency.
+
```terraform
+ locals {
+ custom_longhorn_path = "/var/lib/longhorn"
+ }
+
agent_nodepools = [
{
# ... other nodepool configuration
labels = ["role=monitoring", "storage=ssd"], # Label we use to filter nodes
longhorn_volume_size = 50,
- longhorn_mount_path = "/var/lib/longhorn" # This is the custom path
+ longhorn_mount_path = local.custom_longhorn_path # This is the custom path
}
]
```
-4. Apply the changes. As a result, your external disks will be mounted to `/var/lib/longhorn`.
+4. Apply the changes. As a result, your external disks will be mounted to the path defined in `local.custom_longhorn_path`.
### How to configure Longhorn to use the new path?
@@ -68,7 +73,7 @@ resource "null_resource" "longhorn_patch_external_disk" {
"spec": {
"disks": {
"external-ssd": {
- "path": "/var/lib/longhorn", # IMPORTANT: This path must match the 'longhorn_mount_path' for the nodes selected by the 'storage=ssd' label. This example assumes all selected nodes use the same path.
+ "path": "${local.custom_longhorn_path}", # IMPORTANT: This path must match the 'longhorn_mount_path' for the nodes selected by the 'storage=ssd' label.
"allowScheduling": true,
"tags": ["ssd"]
}