Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Secure Shell Networks: Hetzner Cloud Ansible Inventory

This repository template provides an ansible inventory to manage cloud server in hetzner cloud (hcloud). It performes some basic linux hardening (unattended upgrades, ssh, fail2ban, ...) and can be extended by roles or tasks to perform whatever you need.

Getting started

To use this template, start by creating a repository that inherits from this template. Next create an account and a new cloud project on hetzner.cloud. Generate a password for the ansible vault and store it in .keys/all. Afterwards create a new ansible vault with this password and add the following to it.

# create random password
cat /dev/urandom | tr -dc A-Za-z0-9 | fold -w 20 | head -n 1 > .keys/all
# create ansible vault using predefined password
ansible-vault create group_vars/all/vault

Add a random password for the worker user on the machine and the api token (see image) to the vault in the following format. Creating an api token in the hetzner cloud console

---
hcloud_api_token: "__YOUR_API_TOKEN__"worker_password: "__RANDOM_SECRET_PASSWORD__"

Next you need to extend your inventory, for example like this:

---
all:
hosts:
server1: # default settings if no configuration givenserver_type: cx23location: hel1image: ubuntu-24.04enable_ipv4: falseenable_ipv6: trueserver2:
server_type: cx33location: fsn1image: debian-13enable_ipv4: trueenable_ipv6: true

After you installed the required ansible and python modules you should be able to use the inventory.

pip3 install ansible ansible-lint
pip3 install -r requirements.txt
ansible-galaxy collection install -r requirements.yaml
ansible-galaxy role install -r requirements.yaml
ansible-playbook playbook.yaml
# you can also limit the playbook to one of your hosts
ansible-playbook playbook.yaml --limit alpha
# tags can be used to run only specific parts of a playbook
ansible-playbook playbook.yaml --list-tasks
ansible-playbook playbook.yaml --limit alpha --tags redis
# to check wether the system picks up the correct variables you can run
ansible-inventory --vars --graph
# make sure to lint your inventory regulary
ansible-lint

Make sure to create a backup of the .keys directory. It contains the key to your vault and the ssh key ansible uses to connect to the cloud servers. For security reasons this directory is excluded from git operations (see .gitignore), so by default it will not be pushed to your git repository!

What about GitOps?

I've tried integrating git ops, but there is one problem: the GitHub actions runner does not support ipv6... So you need an ipv4 address on each vm to use git ops for now.

Create the following github actions variables and secrets in your repository and make sure to commit and push both your inventory.yaml and group_vars/all/vault file:

  • Variable: ENABLE_GITOPS to value 1
  • Secret: SSH_KEY to content of .keys/id_ecdsa
  • Secret: ANSIBLE_KEYS_ALL to the content of .keys/all

Structure

Even though the structure is self explaining here are some comments:

hcloud-ansible
├── .github # github actions workflows
│ └── workflows
│ └── gitops.yaml
├── .keys # ssh and ansible vault keys
│ ├── all # key for ansible vault for group_vars all
│ ├── id_ecdsa
│ └── id_ecdsa.pub
├── ansible.cfg
├── group_vars
│ └── all
│ ├── defaults.yaml
│ ├── vars.yaml # plaintext global variables
│ └── vault # encrypted global variables (e. g. hetzner cloud api token)
├── inventory.yaml
├── playbook.yaml
├── roles # roles to add functionality to your server
│ └── ansible-role-postgresql
├── ssh
│ └── nicof2000.pub
├── tasks
│ ├── linux
│ │ ├── audit-lynis.yaml
│ │ ├── audit-openscap.yaml
│ │ ├── create-users.yaml
│ │ ├── create-worker-user.yaml
│ │ ├── setup-auditd.yaml
│ │ ├── setup-auto-update.yaml
│ │ ├── setup-clamav.yaml
│ │ ├── setup-fail2ban.yaml
│ │ ├── setup-iptables.yaml
│ │ ├── setup-rkhunter.yaml
│ │ └── setup-sshd.yaml
│ └── local
│ ├── ensure-keys-exists.yaml
│ └── hetzner-cloud.yaml # task to manage cloud servers and aquire information to connect
└── templates
├── auditd.rules.j2
├── dnf-automatic.conf.j2
└── fail2ban-sshd.conf.j2

Some examples configurations, e.g. how to use roles, can be found in the examples directory.

Releases

Used by

Contributors

Languages