1 Commits

Author SHA1 Message Date
946987862e Update documentation/start.txt 2025-08-19 17:47:25 +00:00
12 changed files with 24 additions and 521 deletions

View File

@ -15,21 +15,8 @@ jobs:
PM_API_TOKEN_SECRET: ${{ secrets.PM_API_TOKEN_SECRET }}
BW_EMAIL: ${{ secrets.BW_EMAIL }}
BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
BW_CLIENTID: ${{ secrets.BW_CLIENTID }}
BW_CLIENTSECRET: ${{ secrets.BW_CLIENTSECRET }}
steps:
- name: Verificar se commit pede por [deploy-opentofu]
if: "!contains(gitea.event.head_commit.message, '[deploy-opentofu]')"
run: |
echo "Commit não contém [deploy-opentofu], a pipeline será ignorada."
exit 1
- name: Executar deploy opentofu
run: echo "Commit contém [deploy-opentofu], a pipeline será executada."
- name: Updating apt-get
run: |
apt-get update -y
@ -39,146 +26,28 @@ jobs:
with:
path: infra/iac
#- name: cloning iac secrets repository
# uses: actions/checkout@v4
# with:
# repository: fenix-gitea-admin/iac-opentofu-private-secrets
# token: ${{ secrets.GGITEA_TOKEN }}
# path: infra/secrets
- name: cloning iac secrets repository
uses: actions/checkout@v4
with:
repository: fenix-gitea-admin/iac-teste-secrets
token: ${{ secrets.GGITEA_TOKEN }}
path: infra/secrets
- name: Install cloudflare prerequisites
run: |
apt-get install -y curl ca-certificates jq openssh-client net-tools iproute2
- name: Install cloudflared
run: |
# pacote .deb oficial - funcionará numa runner Ubuntu x86_64
curl -L -o cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
dpkg -i cloudflared.deb
cloudflared --version
- name: Install dante-server
run: |
apt-get install -y dante-server
- name: Configure dante-server
run: |
cat <<EOF | tee /etc/danted.conf
logoutput: stderr
internal: 127.0.0.1 port = 1080
external: lo
method: none
clientmethod: none
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect disconnect
}
# encaminhar tudo para o listener TCP do cloudflared
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
command: connect udpassociate bind
log: connect disconnect
}
EOF
- name: Start cloudflared Access TCP -> SOCKS5 (background)
env:
CF_SVC_ID: ${{ secrets.CF_SVC_ID }}
CF_SVC_SECRET: ${{ secrets.CF_SVC_SECRET }}
HOSTNAME: "proxmox-ssh.fenix-dev.com"
run: |
# Inicia cloudflared access tcp/ssh com service token e listener socks local
# O binário 'cloudflared' tem variações de flags entre versões; estes flags funcionam nas versões recentes.
nohup cloudflared access tcp \
--hostname "$HOSTNAME" \
--listener "tcp://127.0.0.1:1081" \
--service-token-id "$CF_SVC_ID" \
--service-token-secret "$CF_SVC_SECRET" \
> cloudflared.log 2>&1 &
# espera a porta do listener estar pronta (timeout 30s)
for i in $(seq 1 30); do
ss -tnl | grep -q ":1081" && break
sleep 1
done
if ! ss -tnl | grep -q ":1081"; then
echo "SOCKS listener not ready after 30s, printing cloudflared.log"
tail -n +1 cloudflared.log
cat cloudflared.log
exit 1
fi
echo "cloudflared socks listener ready at $SOCKS_LISTENER"
sleep 1
# opcional: ver primeiros logs
tail -n 50 cloudflared.log || true
- name: Start dante-server
run: |
pkill danted || true
danted -f /etc/danted.conf -D > dante.log 2>&1 &
sleep 3
cat dante.log
#- name: vaultwarden login
# working-directory: infra/iac
# run: |
# bw config server https://vaultwarden.fenix-dev.com
# #BW_SESSION=$(bw login)
# bw login --apikey
# BW_SESSION=$(bw unlock "$BW_PASSWORD" --raw)
- name: vaultwarden getsecrets
- name: vaultwarden login
working-directory: infra/iac
run: |
bw config server https://vaultwarden.fenix-dev.com
bw login --apikey
BW_SESSION=$(bw unlock "$BW_PASSWORD" --raw)
# Ler o arquivo de referência
for secret in $(jq -c '.secrets[]' secrets/vault-secrets-map.json); do
name=$(echo "$secret" | jq -r '.name')
type=$(echo "$secret" | jq -r '.type')
output=$(echo "$secret" | jq -r '.output')
echo "$name $type $output"
item_id=$(bw get item "$name" | jq -r '.id')
echo "$item_id"
mkdir -p "$(dirname "$output")"
echo "dir made"
if [ "$type" == "attachment" ]; then
echo "attachment get"
bw get attachment "$output" --itemid "$item_id" --output "$output" --session "$BW_SESSION"
elif [ "$type" == "note" ]; then
echo "note get"
bw get item "$name" --session "$BW_SESSION" | jq -r '.notes' > "$output"
fi
done
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install ruamel.yaml
BW_SESSION=$(bw login)
- name: Init OpenTofu
working-directory: infra/iac
run: |
tofu init
run: tofu init
- name: Plan
working-directory: infra/iac
run: |
# Find all .tfvars in the secrets folder
# Find all .auto.tfvars in the secrets folder
VAR_FILES=""
ls -la ../secrets/secrets
for f in ../secrets/secrets/*.tfvars; do
echo "found - $f"
for f in ../secrets/secrets/*.auto.tfvars; do
VAR_FILES="$VAR_FILES -var-file=$f"
done
tofu plan $VAR_FILES -out=./tfplan | tee ./tfplan.txt

View File

@ -1,19 +0,0 @@
locale: "pt_PT.UTF-8"
keyboard:
layout: "pt"
variant: "nodeadkeys"
users:
- default
ssh_pwauth: true
package_update: true
packages:
- qemu-guest-agent
- net-tools
- curl
runcmd:
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "done" > /tmp/cloud-config.done

View File

@ -1,36 +0,0 @@
package_update: true
%{ if length(each.value.extra_packages) > 0 ~}
packages:
%{ for pkg in each.value.extra_packages ~}
- ${pkg}
%{ endfor ~}
%{ endif ~}
%{ if length(each.value.extra_users) > 0 ~}
users:
%{ for u in each.value.extra_users ~}
- name: ${u.name}
groups:
%{ if length(u.groups) > 0 ~}
%{ for g in u.groups ~}
- ${g}
%{ endfor ~}
%{ endif ~}
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
%{ endfor ~}
chpasswd:
list: ${"|"}
%{ for u in each.value.extra_users ~}
${u.name}:${u.password}
%{ endfor ~}
expire: false
%{ endif ~}
%{ if length(each.value.extra_runcmd) > 0 ~}
runcmd:
%{ for cmd in each.value.extra_runcmd ~}
- ${cmd}
%{ endfor ~}
%{ endif ~}

View File

@ -1,2 +0,0 @@
#cloud-config
${content}

View File

@ -1,23 +0,0 @@
FROM ghcr.io/opentofu/opentofu:1.9-minimal AS tofu
FROM ubuntu:24.04
# Copy the tofu binary
COPY --from=tofu /usr/local/bin/tofu /usr/local/bin/tofu
# Install dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
nodejs \
npm \
unzip \
&& rm -rf /var/lib/apt/lists/*
RUN curl -L -o /tmp/bw.zip https://github.com/bitwarden/cli/releases/download/v1.22.1/bw-linux-1.22.1.zip \
&& unzip /tmp/bw.zip -d /usr/local/bin \
&& chmod +x /usr/local/bin/bw \
&& rm /tmp/bw.zip
WORKDIR /workspace

View File

@ -1,9 +1,8 @@
https://spacelift.io/blog/opentofu-tutorial - explaining language of opentofu
https://spacelift.io/blog/opentofu-tutorial - explaining language of opentofu
https://opentofu.org/docs/intro/ - quick start and explaning who to work in team
https://opentofu.org/docs/intro/ - CICD for opentofu explained
tofu init

12
main.tf
View File

@ -1,5 +1,9 @@
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
vaultwarden = {
source = "ottramst/vaultwarden"
version = "0.4.4"
@ -7,10 +11,10 @@ terraform {
bitwarden = {
source = "maxlaverse/bitwarden"
version = ">= 0.15.0"
}
}
proxmox = {
source = "bpg/proxmox"
version = "= 0.75.0"
source = "telmate/proxmox"
version = "3.0.2-rc03"
}
}
backend "consul" {
@ -20,3 +24,5 @@ terraform {
lock = true # enable state locking
}
}
provider "random" {}

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python3
from ruamel.yaml import YAML
import sys
import json
from collections.abc import Mapping
def deep_merge_yaml(dict1, dict2):
result = dict1.copy()
for key, value in dict2.items():
if key in result:
if isinstance(result[key], list) and isinstance(value, list):
result[key] = result[key] + value
elif isinstance(result[key], Mapping) and isinstance(value, Mapping):
result[key] = deep_merge_yaml(result[key], value)
else:
result[key] = value
else:
result[key] = value
return result
def main():
input_data = json.load(sys.stdin)
file1 = input_data["file1"]
file2 = input_data["file2"]
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
with open(file1, "r") as f1, open(file2, "r") as f2:
yaml1 = yaml.load(f1)
yaml2 = yaml.load(f2)
merged = deep_merge_yaml(yaml1, yaml2)
from io import StringIO
output = StringIO()
yaml.dump(merged, output)
print(json.dumps({
"merged_yaml": output.getvalue()
}))
if __name__ == "__main__":
main()

View File

@ -1,179 +1,3 @@
provider "proxmox" {
endpoint = var.proxmox_server
api_token = var.proxmox_apikey
ssh {
agent = true
username = var.proxmox_username_ssh
socks5_server = var.proxmox_server_ssh
password = var.proxmox_password_ssh
node {
name = "fenix"
address = "127.0.0.1"
port = 1081
}
}
}
resource "proxmox_virtual_environment_download_file" "latest_ubunto_cloud_img" {
content_type = "iso"
datastore_id = "local"
node_name = "fenix"
url = "https://cloud-images.ubuntu.com/jammy/20250725/jammy-server-cloudimg-amd64.img"
file_name = "jammyservercloudimgamd64.img"
}
resource "proxmox_virtual_environment_file" "cloud_init_yaml" {
node_name = "fenix"
datastore_id = "local-snippets"
content_type = "snippets"
source_raw {
file_name = "user-data-cloud-config.yaml"
data = file("${path.module}/cloud-init-base.yaml")
}
}
locals {
rendered_yaml_per_vm ={
for vm in var.proxmox_k8s_vms : vm.name =>
templatefile("${path.module}/cloud-init-vm.yaml.tftpl",
{
each = {
value = vm
}
})
}
}
data "external" "merged_yaml" {
for_each = { for vm in var.proxmox_k8s_vms : vm.name => vm }
depends_on = [local_file.rendered_yaml_file]
program = ["python3", "${path.module}/merge_yaml.py"]
query = {
file1 = "${path.module}/cloud-init-base.yaml"
file2 = "${path.module}/cloud-init-extra-${each.key}.yaml"
}
}
resource "local_file" "rendered_yaml_file" {
for_each = { for vm in var.proxmox_k8s_vms : vm.name => vm }
content = local.rendered_yaml_per_vm[each.key]
filename = "${path.module}/cloud-init-extra-${each.key}.yaml"
}
# Gerar um snippet cloud-init por VM
resource "proxmox_virtual_environment_file" "vm_user_data" {
for_each = { for vm in var.proxmox_k8s_vms : vm.name => vm }
node_name = "fenix"
datastore_id = "local-snippets"
content_type = "snippets"
source_raw {
file_name = "cloud-init-iac-k8s-${each.key}.yaml"
data = templatefile("${path.module}/cloud-init-wrapper.yaml.tftpl", {
content = data.external.merged_yaml[each.key].result["merged_yaml"]
})
}
}
resource "proxmox_virtual_environment_vm" "proxmox-kubernetes-VM-template" {
depends_on = [proxmox_virtual_environment_download_file.latest_ubunto_cloud_img, proxmox_virtual_environment_file.cloud_init_yaml]
name = "proxmox-kubernetes-VM-template"
node_name = "fenix"
vm_id = 1002
template = true
started = false
agent {
enabled = true
}
tags = ["opentofu", "kubernetes", "fedora"]
machine = "q35"
bios = "seabios"
description = "kubernetes VM Template created via iac"
cpu {
cores = 2
}
memory {
dedicated = 4096
}
disk {
datastore_id = "local-lvm"
file_id = proxmox_virtual_environment_download_file.latest_ubunto_cloud_img.id
interface = "scsi0"
file_format = "qcow2"
}
# Configuração da interface de rede
network_device {
bridge = "vmbr0"
}
initialization {
dns {
servers = ["1.1.1.1"]
}
ip_config {
ipv4 {
address = "dhcp"
}
}
user_data_file_id = proxmox_virtual_environment_file.cloud_init_yaml.id
}
}
resource "proxmox_virtual_environment_vm" "k8s_vms" {
for_each = { for vm in var.proxmox_k8s_vms : vm.name => vm }
depends_on = [proxmox_virtual_environment_vm.proxmox-kubernetes-VM-template]
name = each.value.name
node_name = each.value.node_name
vm_id = each.value.vm_id
clone {
vm_id = proxmox_virtual_environment_vm.proxmox-kubernetes-VM-template.id
}
cpu {
cores = each.value.cores
}
memory {
dedicated = each.value.memory
}
disk {
datastore_id = each.value.data_store
size = each.value.disk_size
interface = "scsi1"
}
initialization {
ip_config {
ipv4 {
address = each.value.ip
gateway = each.value.gateway
}
}
user_data_file_id = proxmox_virtual_environment_file.vm_user_data[each.key].id
}
agent {
enabled = true
}
}
pm_api_url = var.proxmox_server
}

View File

@ -3,54 +3,3 @@ variable "proxmox_server" {
type = string
sensitive = false
}
variable "proxmox_server_ssh" {
description = "Proxmox server ssh url"
type = string
sensitive = false
}
variable "proxmox_username_ssh" {
description = "Proxmox server ssh username"
type = string
sensitive = false
}
variable "proxmox_password_ssh" {
description = "Proxmox server ssh password"
type = string
sensitive = true
}
variable "proxmox_apikey" {
description = "Proxmox server api key"
type = string
sensitive = true
}
variable "node_name" {
default = "fenix"
}
# Lista de VMs (override de valores específicos)
variable "proxmox_k8s_vms" {
type = list(object({
name = string
vm_id = number
node_name = string
ip = string
cores = optional(number)
memory = optional(number)
data_store = optional(string)
gateway = string
disk_size = optional(number)
extra_users = optional(list(object({
name = string
password = string
groups = list(string)
})))
extra_packages = optional(list(string))
extra_runcmd = optional(list(string))
}))
}

View File

@ -1,19 +0,0 @@
{
"secrets": [
{
"name": "iac.opentofu.consul.secrets",
"type": "note",
"output": "../secrets/secrets/consul.secrets.tfvars"
},
{
"name": "iac.opentofu.proxmox.secrets",
"type": "note",
"output": "../secrets/secrets/proxmox.secrets.tfvars"
},
{
"name": "iac.opentofu.vaultwarden.secrets",
"type": "note",
"output": "../secrets/secrets/vaultwarden.secrets.tfvars"
}
]
}

View File

@ -3,4 +3,4 @@ vaultwarden_email = "admin@example.com"
vaultwarden_master_password = "SuperSecretMasterPassword"
vaultwarden_admin_token = "tokenadmin"
vaultwarden_client_id = "clientid"
vaultwarden_client_secret = "clientsecret"
vaultwarden_client_secret = "clientsecret"