1 Commits

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

View File

@ -2,7 +2,7 @@ name: IAC
on: on:
push: push:
branches: [ dev ] branches: [ main ]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -15,185 +15,39 @@ jobs:
PM_API_TOKEN_SECRET: ${{ secrets.PM_API_TOKEN_SECRET }} PM_API_TOKEN_SECRET: ${{ secrets.PM_API_TOKEN_SECRET }}
BW_EMAIL: ${{ secrets.BW_EMAIL }} BW_EMAIL: ${{ secrets.BW_EMAIL }}
BW_PASSWORD: ${{ secrets.BW_PASSWORD }} BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
BW_CLIENTID: ${{ secrets.BW_CLIENTID }}
BW_CLIENTSECRET: ${{ secrets.BW_CLIENTSECRET }}
VAULTWARDEN_LINK: ${{secrets.VAULTWARDEN_LINK }}
steps: 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 - name: Updating apt-get
run: | run: |
apt-get update -y apt-get update -y
- name: Install setup
run: |
apt install -y curl jq
curl -fsSL https://deb.nodesource.com/setup_18.x
- name: Cloning iac repository - name: Cloning iac repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
path: infra/iac path: infra/iac
#- name: cloning iac secrets repository - name: cloning iac secrets repository
# uses: actions/checkout@v4 uses: actions/checkout@v4
# with: with:
# repository: fenix-gitea-admin/iac-opentofu-private-secrets repository: fenix-gitea-admin/iac-teste-secrets
# token: ${{ secrets.GGITEA_TOKEN }} token: ${{ secrets.GGITEA_TOKEN }}
# path: infra/secrets path: infra/secrets
- name: vaultwarden login
- 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 openssl
#libssl1.1
- 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: vaultwarden urls as secrets
run: |
echo "config"
echo "$VAULTWARDEN_LINK"
bw config server $VAULTWARDEN_LINK
echo "login"
bw login --apikey
echo "session"
BW_SESSION=$(bw unlock "$BW_PASSWORD" --raw)
echo "$BW_SESSION"
echo "getting item"
bw get item "iac.proxmox.ssh.link" --session "$BW_SESSION"
bw get item "iac.proxmox.ssh.link" --session "$BW_SESSION" | jq -r '.notes' > "proxmox-ssh-link.txt"
- name: Start cloudflared Access TCP -> SOCKS5 (background)
env:
CF_SVC_ID: ${{ secrets.CF_SVC_ID }}
CF_SVC_SECRET: ${{ secrets.CF_SVC_SECRET }}
run: |
Hostname=$(cat proxmox-ssh-link.txt)
# 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 getsecrets
working-directory: infra/iac working-directory: infra/iac
run: | run: |
BW_SESSION=$(bw unlock "$BW_PASSWORD" --raw) BW_SESSION=$(bw login)
echo "$BW_SESSION"
# 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"
#cat $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
- name: Init OpenTofu - name: Init OpenTofu
working-directory: infra/iac working-directory: infra/iac
run: | run: tofu init
tofu init
- name: Plan - name: Plan
working-directory: infra/iac working-directory: infra/iac
run: | run: |
# Find all .tfvars in the secrets folder # Find all .auto.tfvars in the secrets folder
VAR_FILES="" VAR_FILES=""
ls -la ../secrets/secrets for f in ../secrets/secrets/*.auto.tfvars; do
for f in ../secrets/secrets/*.tfvars; do
echo "found - $f"
VAR_FILES="$VAR_FILES -var-file=$f" VAR_FILES="$VAR_FILES -var-file=$f"
done done
tofu plan $VAR_FILES -out=./tfplan | tee ./tfplan.txt tofu plan $VAR_FILES -out=./tfplan | tee ./tfplan.txt

18
LICENSE
View File

@ -1,18 +0,0 @@
MIT License
Copyright (c) 2025 fenix-gitea-admin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1 +0,0 @@
the most stable branch is main, dev is where tests are made, and the remaining branches are personal and can undergo changes at any time

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,31 +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
# Atualizar pacotes e instalar dependências básicas
RUN apt-get update && apt-get install -y \
curl \
git \
unzip \
jq \
gnupg \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Instalar Node.js 18 via NodeSource
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs
# Verificar versões (opcional para debug)
RUN node -v && npm -v
RUN npm install -g @bitwarden/cli
WORKDIR /workspace

View File

@ -5,7 +5,6 @@ https://opentofu.org/docs/intro/ - quick start and explaning who to work in team
https://opentofu.org/docs/intro/ - CICD for opentofu explained https://opentofu.org/docs/intro/ - CICD for opentofu explained
tofu init tofu init
tofu plan --var-file=opentofu-varfile.json tofu plan --var-file=opentofu-varfile.json
yes yes

12
main.tf
View File

@ -1,16 +1,20 @@
terraform { terraform {
required_providers { required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
vaultwarden = { vaultwarden = {
source = "ottramst/vaultwarden" source = "ottramst/vaultwarden"
version = "0.4.4" version = "0.4.4"
} }
bitwarden = { bitwarden = {
source = "maxlaverse/bitwarden" source = "maxlaverse/bitwarden"
version = ">= 0.16.0" version = ">= 0.15.0"
} }
proxmox = { proxmox = {
source = "bpg/proxmox" source = "telmate/proxmox"
version = "= 0.75.0" version = "3.0.2-rc03"
} }
} }
backend "consul" { backend "consul" {
@ -20,3 +24,5 @@ terraform {
lock = true # enable state locking 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,180 +1,3 @@
provider "proxmox" { provider "proxmox" {
endpoint = var.proxmox_server pm_api_url = 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" # rede de gestão para comunicação com Cluster A
}
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
}
}

View File

@ -3,56 +3,3 @@ variable "proxmox_server" {
type = string type = string
sensitive = false 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
ip2 = string
ip3 = 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

@ -3,56 +3,3 @@ PM_API_TOKEN_ID = "tokenid"
PM_API_TOKEN_SECRET = "tokensecret" PM_API_TOKEN_SECRET = "tokensecret"
# tokenid is read automatically from PM_API_TOKEN_ID # tokenid is read automatically from PM_API_TOKEN_ID
# token is read automatically from PM_API_TOKEN_SECRET # token is read automatically from PM_API_TOKEN_SECRET
proxmox_server = "https://proxmox.example.com:443/api2/json"
proxmox_apikey = "user@pam!token=fdjkdslfjdsflkj"
proxmox_server_ssh = "127.0.0.1:1080"
proxmox_username_ssh = "user"
proxmox_password_ssh = "password"
# tokenid is read automatically from PM_API_TOKEN_ID
# token is read automatically from PM_API_TOKEN_SECRET
proxmox_k8s_vms = [
{
name = "k8s-master-01"
vm_id = 3001
node_name = "node"
ip = "192.168.1.99/24"
cores = 2
memory = 2000
disk_size = 32
data_store = "local-lvm"
gateway = "192.168.1.1"
extra_users = [
{
name = "user"
password = "pass"
groups = ["sudo"]
}
]
extra_packages = []
extra_runcmd = []
},
{
name = "k8s-worker-01"
vm_id = 3002
node_name = "node"
ip = "192.168.1.101/24"
cores = 1
memory = 2000
disk_size = 32
data_store = "local-lvm"
gateway = "192.168.1.1"
extra_users = [
{
name = "user"
password = "pass"
groups = ["sudo"]
}
]
extra_packages = []
extra_runcmd = []
},
]

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

@ -4,5 +4,3 @@ vaultwarden_master_password = "SuperSecretMasterPassword"
vaultwarden_admin_token = "tokenadmin" vaultwarden_admin_token = "tokenadmin"
vaultwarden_client_id = "clientid" vaultwarden_client_id = "clientid"
vaultwarden_client_secret = "clientsecret" vaultwarden_client_secret = "clientsecret"
# email is read automatically from BW_EMAIL
# password is read automatically from BW_PASSWORD

View File

@ -16,13 +16,6 @@ resource "vaultwarden_account_register" "vaultwarden-acount-fenix" {
password = var.vaultwarden_master_password password = var.vaultwarden_master_password
} }
resource "bitwarden_item_login" "administrative-user" {
name = "teste"
username = "teste"
password = "teste"
collection_ids = [vaultwarden_organization_collection.vaultwarden-collection-iac.id]
}
resource "vaultwarden_organization" "vaultwarden-organization-fenix-iac" { resource "vaultwarden_organization" "vaultwarden-organization-fenix-iac" {
name = "fenix-iac" name = "fenix-iac"
} }
@ -33,28 +26,9 @@ resource "vaultwarden_organization_collection" "vaultwarden-collection-iac" {
} }
resource "bitwarden_item_secure_note" "hosts-ini" { resource "bitwarden_item_login" "administrative-user" {
name = "iac.ansible.hosts.ini" name = "teste"
notes = <<EOT username = "teste"
${local.hosts_ini} password = "teste"
EOT
organization_id = vaultwarden_organization.vaultwarden-organization-fenix-iac.id
collection_ids = [vaultwarden_organization_collection.vaultwarden-collection-iac.id] collection_ids = [vaultwarden_organization_collection.vaultwarden-collection-iac.id]
reprompt = true
} }
locals{
hosts_ini = <<EOT
[master]
master1 ansible_host=${split("/", var.proxmox_k8s_vms[0].ip)[0]} ansible_user=${var.proxmox_k8s_vms[0].extra_users[0].name} ansible_ssh_pass=${var.proxmox_k8s_vms[0].extra_users[0].password} ansible_ssh_common_args='-o StrictHostKeyChecking=no'
[workers]
%{ for i, vm in var.proxmox_k8s_vms ~}
%{ if i != 0 }
worker-${replace(split("/", vm.ip)[0], ".", "-")} ansible_host=${split("/", vm.ip)[0]} ansible_user=${vm.extra_users[0].name} ansible_ssh_pass=${vm.extra_users[0].password} ansible_ssh_common_args='-o StrictHostKeyChecking=no'
%{ endif }
%{ endfor }
EOT
}