mirror of
https://gitea.fenix-dev.com/fenix-gitea-admin/iac-opentofu-private.git
synced 2026-03-22 12:59:51 +00:00
Compare commits
48 Commits
d04cc7477c
...
V01-PROXMO
| Author | SHA1 | Date | |
|---|---|---|---|
| cefab0efa4 | |||
| 31fa88ff4f | |||
| 1447478dcd | |||
| 66b694226f | |||
| 15b709490a | |||
| 44a090d18a | |||
| 6d3258e135 | |||
| 2350a5d3a7 | |||
| 3d221d64ed | |||
| dd024fa9fe | |||
| 973a440b98 | |||
| 9e873c5b66 | |||
| 237fb83745 | |||
| b28e80fa88 | |||
| 8a4384e1db | |||
| b57d6e362e | |||
| 54912d7145 | |||
| d786488ef8 | |||
| 5668ffa755 | |||
| 028f65435e | |||
| d69654b00e | |||
| be3b9024ec | |||
| dc3f135dd3 | |||
| 0e3906dcdd | |||
| 567d26c024 | |||
| de6d6b924d | |||
| 42912412c4 | |||
| 80f5fa299b | |||
| bc307d9abc | |||
| 701b59e3c0 | |||
| 3ae4a8d6e2 | |||
| 172f4ec3bb | |||
| 419c255f24 | |||
| 262bd14507 | |||
| e0d3f63691 | |||
| 892ce5c19f | |||
| ed2f1a4a9d | |||
| d30a053d23 | |||
| df7518f162 | |||
| 0df892ad1e | |||
| 817f80c70b | |||
| 0d9674942e | |||
| 80f8d0ef70 | |||
| 1ad34ea3e4 | |||
| 3495ab1c10 | |||
| 3a3d37030f | |||
| d5827b5909 | |||
| 98c537dcf5 |
@ -15,22 +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 }}
|
||||
VAULTWARDEN_LINK: ${{secrets.VAULTWARDEN_LINK }}
|
||||
|
||||
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
|
||||
@ -40,12 +26,12 @@ 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
|
||||
@ -83,28 +69,16 @@ jobs:
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
- name: vaultwarden urls as secrets
|
||||
working-directory:
|
||||
run: |
|
||||
bw config server $VAULTWARDEN_LINK
|
||||
bw login --apikey
|
||||
BW_SESSION=$(bw unlock "$BW_PASSWORD" --raw)
|
||||
|
||||
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 }}
|
||||
HOSTNAME: "proxmox-ssh.fenix-dev.com"
|
||||
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" \
|
||||
--hostname "$HOSTNAME" \
|
||||
--listener "tcp://127.0.0.1:1081" \
|
||||
--service-token-id "$CF_SVC_ID" \
|
||||
--service-token-secret "$CF_SVC_SECRET" \
|
||||
@ -135,39 +109,10 @@ jobs:
|
||||
sleep 3
|
||||
cat dante.log
|
||||
|
||||
|
||||
- name: vaultwarden getsecrets
|
||||
- name: vaultwarden login
|
||||
working-directory: infra/iac
|
||||
run: |
|
||||
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
|
||||
@ -177,11 +122,9 @@ jobs:
|
||||
- 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
|
||||
|
||||
18
LICENSE
18
LICENSE
@ -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.
|
||||
@ -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
|
||||
@ -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 ~}
|
||||
@ -1,2 +0,0 @@
|
||||
#cloud-config
|
||||
${content}
|
||||
@ -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
|
||||
6
main.tf
6
main.tf
@ -1,5 +1,9 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.6"
|
||||
}
|
||||
vaultwarden = {
|
||||
source = "ottramst/vaultwarden"
|
||||
version = "0.4.4"
|
||||
@ -20,3 +24,5 @@ terraform {
|
||||
lock = true # enable state locking
|
||||
}
|
||||
}
|
||||
|
||||
provider "random" {}
|
||||
@ -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()
|
||||
122
proxmox.tf
122
proxmox.tf
@ -30,58 +30,32 @@ resource "proxmox_virtual_environment_file" "cloud_init_yaml" {
|
||||
|
||||
source_raw {
|
||||
file_name = "user-data-cloud-config.yaml"
|
||||
data = file("${path.module}/cloud-init-base.yaml")
|
||||
}
|
||||
}
|
||||
data = <<-EOF
|
||||
#cloud-config
|
||||
users:
|
||||
- default
|
||||
- name: testeuser
|
||||
groups: sudo
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
|
||||
ssh_pwauth: true
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
chpasswd:
|
||||
list: |
|
||||
testeuser:testepassword
|
||||
expire: false
|
||||
|
||||
|
||||
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"]
|
||||
})
|
||||
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
|
||||
EOF
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,6 +85,13 @@ resource "proxmox_virtual_environment_vm" "proxmox-kubernetes-VM-template" {
|
||||
dedicated = 4096
|
||||
}
|
||||
|
||||
# Configuração do disco rígido
|
||||
disk {
|
||||
datastore_id = "local-lvm"
|
||||
interface = "scsi1"
|
||||
size = 64
|
||||
}
|
||||
|
||||
disk {
|
||||
datastore_id = "local-lvm"
|
||||
file_id = proxmox_virtual_environment_download_file.latest_ubunto_cloud_img.id
|
||||
@ -121,15 +102,13 @@ resource "proxmox_virtual_environment_vm" "proxmox-kubernetes-VM-template" {
|
||||
# Configuração da interface de rede
|
||||
network_device {
|
||||
bridge = "vmbr0"
|
||||
model = "virtio"
|
||||
}
|
||||
|
||||
initialization {
|
||||
dns {
|
||||
servers = ["1.1.1.1"]
|
||||
}
|
||||
ip_config {
|
||||
ipv4 {
|
||||
address = "dhcp"
|
||||
address = "dhcp" # IP estático + máscara de rede
|
||||
}
|
||||
}
|
||||
user_data_file_id = proxmox_virtual_environment_file.cloud_init_yaml.id
|
||||
@ -137,43 +116,18 @@ initialization {
|
||||
}
|
||||
|
||||
|
||||
resource "proxmox_virtual_environment_vm" "k8s_vms" {
|
||||
for_each = { for vm in var.proxmox_k8s_vms : vm.name => vm }
|
||||
resource "proxmox_virtual_environment_vm" "VM-Kubernetes-01" {
|
||||
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
|
||||
name = "VM-Kubernetes-01"
|
||||
node_name = "fenix"
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -32,25 +32,3 @@ variable "proxmox_apikey" {
|
||||
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))
|
||||
}))
|
||||
}
|
||||
@ -3,56 +3,3 @@ PM_API_TOKEN_ID = "tokenid"
|
||||
PM_API_TOKEN_SECRET = "tokensecret"
|
||||
# tokenid is read automatically from PM_API_TOKEN_ID
|
||||
# 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 = []
|
||||
},
|
||||
]
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -4,5 +4,3 @@ vaultwarden_master_password = "SuperSecretMasterPassword"
|
||||
vaultwarden_admin_token = "tokenadmin"
|
||||
vaultwarden_client_id = "clientid"
|
||||
vaultwarden_client_secret = "clientsecret"
|
||||
# email is read automatically from BW_EMAIL
|
||||
# password is read automatically from BW_PASSWORD
|
||||
Reference in New Issue
Block a user