Compare commits

...
109 Commits
Author SHA1 Message Date
Dmitry 4bafa7d09e Archive Legacy Setup And Add Ansible Control Plane 2026-07-08 12:47:37 +03:00
Dmitry 841d56380b Update docker-compose.yml
Deploy mini-pc / deploy (push) Failing after 3s
2026-07-04 11:05:53 +03:00
Dmitry 827f8de6d3 Update docker-compose.yml
Deploy mini-pc / deploy (push) Failing after 3s
2026-07-04 10:56:33 +03:00
Dmitry 1a0b38d066 Update docker-compose.yml
Deploy mini-pc / deploy (push) Failing after 2s
2026-07-04 10:51:47 +03:00
Dmitry 046d3bc236 Update docker-compose.yml
Deploy mini-pc / deploy (push) Failing after 2s
2026-07-04 10:51:08 +03:00
Dmitry b2d6c9a436 Create docker-compose.yml
Deploy mini-pc / deploy (push) Failing after 3s
2026-07-04 10:49:36 +03:00
Dmitry f8ccc7ec35 Update Caddyfile
Deploy mini-pc / deploy (push) Successful in 32s
2026-06-10 21:10:44 +03:00
Dmitry c935564ed8 Update config.yaml
Deploy ru-vps / deploy (push) Successful in 23s
2026-06-09 06:32:49 +03:00
Dmitry 59a7749b18 Убрать всё, кроме mihomo
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 23:05:05 +03:00
Dmitry 74c3c54feb Update mtg-config.toml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 23:02:45 +03:00
Dmitry e3d7c8ccbf Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 22:53:09 +03:00
Dmitry f153bcdbd1 Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 32s
2026-06-08 22:52:23 +03:00
Dmitry 6529b49e1f Update mtg-config.toml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 22:51:41 +03:00
Dmitry 638f043a49 Update mtg-config.toml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 22:46:30 +03:00
Dmitry 928878e057 dfddd
Deploy ru-vps / deploy (push) Successful in 33s
2026-06-08 22:37:16 +03:00
Dmitry 92ce67732c Update mtg-config.toml
Deploy ru-vps / deploy (push) Failing after 24s
2026-06-08 22:33:36 +03:00
Dmitry 41404085f5 mtg
Deploy ru-vps / deploy (push) Failing after 24s
2026-06-08 22:27:13 +03:00
Dmitry 0e96d60ece Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 24s
2026-06-08 21:59:29 +03:00
Dmitry 2e829d9380 fff
Deploy ru-vps / deploy (push) Failing after 1s
2026-06-08 20:46:26 +03:00
Dmitry 2cd106b84c Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 31s
2026-06-08 20:44:28 +03:00
Dmitry 46824776ef Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 9s
2026-06-08 20:43:16 +03:00
Dmitry d5b56f1a20 Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 20:39:22 +03:00
Dmitry 65ef3341a4 Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 59s
2026-06-08 20:35:57 +03:00
Dmitry db7c95845b Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 21s
2026-06-08 20:34:33 +03:00
Dmitry 5cf7f11b88 ыыы
Deploy ru-vps / deploy (push) Successful in 33s
2026-06-08 20:28:10 +03:00
Dmitry 857e63ca68 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 9s
2026-06-08 20:21:06 +03:00
Dmitry 9c7ef29a43 Update config.yaml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 20:19:40 +03:00
Dmitry 81174047bd Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 56s
2026-06-08 20:09:56 +03:00
Dmitry 17f91d833e Update config.yaml
Deploy ru-vps / deploy (push) Successful in 24s
2026-06-08 19:54:39 +03:00
Dmitry 32428d0156 Update config.yaml
Deploy ru-vps / deploy (push) Successful in 24s
2026-06-08 19:49:37 +03:00
Dmitry e49cf515e9 Убрать squid
Deploy ru-vps / deploy (push) Successful in 34s
2026-06-08 19:46:52 +03:00
Dmitry eb9c8767b0 Update config.yaml
Deploy ru-vps / deploy (push) Successful in 22s
2026-06-08 19:41:25 +03:00
Dmitry f61ad5763e Update config.yaml
Deploy ru-vps / deploy (push) Failing after 33s
2026-06-08 19:40:16 +03:00
Dmitry 3cca8246db Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 33s
2026-06-08 19:33:57 +03:00
Dmitry 573590ef45 Опять прокси новый
Deploy ru-vps / deploy (push) Successful in 24s
2026-06-08 19:31:18 +03:00
Dmitry 4a80762cbc Переосмысление сервисов
Deploy ru-vps / deploy (push) Successful in 29s
2026-06-08 19:22:42 +03:00
Dmitry f98b83514f Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 29s
2026-06-08 19:21:12 +03:00
Dmitry 055bb45be4 Sss
Deploy ru-vps / deploy (push) Failing after 1s
2026-06-08 19:18:03 +03:00
Dmitry 40a499decf Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 30s
2026-06-08 19:13:07 +03:00
Dmitry 14bf5c9e63 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 43s
2026-06-08 19:08:15 +03:00
Dmitry ab9d07c379 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 29s
2026-06-08 19:06:50 +03:00
Dmitry 39bdb810a2 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 27s
2026-06-08 18:58:46 +03:00
Dmitry b3a17ab5e4 Update entrypoint.sh
Deploy ru-vps / deploy (push) Failing after 16s
2026-06-08 18:57:08 +03:00
Dmitry 322bfa9513 Добавить 3proxy к squid
Deploy ru-vps / deploy (push) Failing after 20s
2026-06-08 18:55:05 +03:00
Dmitry c0cd22a73a Update squid.conf
Deploy ru-vps / deploy (push) Successful in 43s
2026-06-08 18:50:54 +03:00
Dmitry 744aa4f14a Добавитьь ui в mihomo
Deploy ru-vps / deploy (push) Successful in 44s
2026-06-08 18:48:48 +03:00
Dmitry 2a9ac8cbfd Переименования
Deploy ru-vps / deploy (push) Failing after 43s
2026-06-08 18:46:36 +03:00
Dmitry 70f6d6c51f Create config.yml
Deploy ru-vps / deploy (push) Successful in 44s
2026-06-08 18:45:53 +03:00
Dmitry e9d39360b3 Перенос конфига для mihomo
Deploy ru-vps / deploy (push) Successful in 43s
2026-06-08 18:44:57 +03:00
Dmitry 87557bee15 Добавить mihomo
Deploy ru-vps / deploy (push) Successful in 44s
2026-06-08 18:41:37 +03:00
Dmitry abd88f5744 Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 36s
2026-06-07 10:51:49 +03:00
Dmitry aea97a1d2a Запинить версию v2raya до 2.1.3
Deploy ru-vps / deploy (push) Successful in 38s
2026-06-07 10:48:06 +03:00
Dmitry d1ae1afe34 Включить v2raya
Deploy ru-vps / deploy (push) Successful in 37s
2026-06-07 10:46:11 +03:00
Dmitry f369bf2eaf Вернуть v2raya
Deploy ru-vps / deploy (push) Successful in 29s
2026-06-07 10:44:57 +03:00
Dmitry 05a95ae3c0 Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 48s
2026-06-07 10:43:24 +03:00
Dmitry e37db8dd7a Убрать v2raya, добавить mihomo
Deploy ru-vps / deploy (push) Failing after 10s
2026-06-07 10:40:48 +03:00
Dmitry 1bd13b2c1b Update squid.conf
Deploy ru-vps / deploy (push) Failing after 21s
2026-06-07 09:50:39 +03:00
Dmitry 35fce0220c Исправление конфигов squid
Deploy ru-vps / deploy (push) Failing after 20s
2026-06-07 09:40:33 +03:00
Dmitry d5b589abf8 Update docker-compose.yml
Deploy ru-vps / deploy (push) Successful in 36s
2026-06-07 09:36:42 +03:00
Dmitry 1548c1f524 Исправить путь в конфиге squid
Deploy ru-vps / deploy (push) Successful in 43s
2026-06-07 09:30:43 +03:00
Dmitry 05c38bb63f Добавить squid.proxy
Deploy ru-vps / deploy (push) Successful in 46s
2026-06-07 09:25:05 +03:00
Dmitry 7b1c637a76 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 30s
2026-06-07 09:12:40 +03:00
Dmitry fe2fe590e4 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 17s
2026-06-07 09:11:05 +03:00
Dmitry 860f09e8f8 Update docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 30s
2026-06-07 09:10:11 +03:00
Dmitry 0a38cf4237 Create docker-compose.yml
Deploy ru-vps / deploy (push) Failing after 36s
2026-06-07 09:06:02 +03:00
Dmitry ce7654908c Убрать syncthing
Deploy cloud-pc / deploy (push) Successful in 34s
2026-06-03 16:56:52 +03:00
Dmitry 185f7a52a1 Remove telegram bot api service
Deploy mini-pc / deploy (push) Successful in 33s
NixOS switch mini-pc / switch (push) Successful in 9s
2026-05-31 15:15:56 +03:00
Dmitry 8dbca5368d Use host networking for telegram bot API
Deploy mini-pc / deploy (push) Successful in 40s
2026-05-31 15:10:07 +03:00
Dmitry df9f965f38 Update docker-compose.yml
Deploy mini-pc / deploy (push) Successful in 39s
2026-05-31 14:56:43 +03:00
Dmitry cb401219e8 Update docker-compose.yml
Deploy mini-pc / deploy (push) Successful in 39s
2026-05-31 14:52:30 +03:00
Dmitry 16aa567053 Update Telegram Bot API proxy port
Deploy mini-pc / deploy (push) Successful in 39s
2026-05-31 14:41:27 +03:00
Dmitry 6c95ad59a7 Add Telegram Bot API service to mini-pc
Deploy mini-pc / deploy (push) Failing after 14s
NixOS switch mini-pc / switch (push) Successful in 12s
2026-05-31 14:20:24 +03:00
Dmitry c21e2b175c Remove Maddy relay from mini-pc
Deploy mini-pc / deploy (push) Successful in 40s
NixOS switch mini-pc / switch (push) Successful in 10s
2026-05-31 12:39:04 +03:00
Dmitry 649dd5a076 Update maddy.conf
Deploy mini-pc / deploy (push) Failing after 39s
2026-05-31 12:27:27 +03:00
Dmitry 5168b22eba Refactor Maddy relay delivery queue
Deploy mini-pc / deploy (push) Failing after 40s
2026-05-31 12:20:53 +03:00
Dmitry 5ef1d4830e Configure Maddy for Yandex SMTP relay
Deploy mini-pc / deploy (push) Successful in 39s
2026-05-31 12:17:28 +03:00
Dmitry 9b47d7a216 Update maddy.conf
Deploy mini-pc / deploy (push) Successful in 39s
2026-05-31 12:11:09 +03:00
Dmitry 340802ec1a Delay Caddy Until Certs Are Ready
Deploy mini-pc / deploy (push) Successful in 49s
2026-05-31 11:38:53 +03:00
Dmitry 4cfc9597b4 Update mail sender defaults
Deploy cloud-pc / deploy (push) Successful in 44s
Deploy mini-pc / deploy (push) Failing after 1m17s
2026-05-31 11:24:45 +03:00
Dmitry bf6aa03fcb Restrict Maddy Relay Sender
Deploy mini-pc / deploy (push) Failing after 40s
2026-05-30 20:38:15 +03:00
Dmitry ae9ca5d07f Enable Vaultwarden SMTP on mini-pc
Deploy mini-pc / deploy (push) Failing after 45s
NixOS switch mini-pc / switch (push) Successful in 12s
Expose Maddy on port 2525 and connect
Vaultwarden and Uptime Kuma to the mail
network for outbound mail delivery.
2026-05-30 20:28:08 +03:00
Dmitry 2b1d98292b Update maddy.conf
Deploy mini-pc / deploy (push) Failing after 39s
2026-05-30 20:23:56 +03:00
Dmitry fb5003ef0c Add Maddy relay on mini-pc
Deploy mini-pc / deploy (push) Failing after 14s
Deploy cloud-pc / deploy (push) Failing after 34s
2026-05-30 20:20:54 +03:00
Dmitry 6742f89420 Update docker-compose.yml
Deploy cloud-pc / deploy (push) Failing after 27s
2026-05-30 17:36:08 +03:00
Dmitry 54b4a23446 Extract Shared NixOS Settings
NixOS switch mini-pc / switch (push) Successful in 14s
Deploy cloud-pc / deploy (push) Failing after 29s
NixOS switch cloud-pc / switch (push) Successful in 11s
2026-05-30 17:25:18 +03:00
Dmitry 2e0b494000 Update base.nix
NixOS switch mini-pc / switch (push) Successful in 31s
NixOS switch cloud-pc / switch (push) Successful in 33s
2026-05-30 15:24:09 +03:00
Dmitry 4057d5d7f1 Update Caddyfile
Deploy ru-vps / deploy (push) Successful in 24s
2026-05-30 15:16:14 +03:00
Dmitry 037372d500 Set Nix daemon proxy environment
NixOS switch cloud-pc / switch (push) Successful in 14s
NixOS switch mini-pc / switch (push) Successful in 13s
2026-05-30 15:07:08 +03:00
Dmitry 6852d740b5 Update base.nix
NixOS switch cloud-pc / switch (push) Failing after 2s
NixOS switch mini-pc / switch (push) Failing after 8s
2026-05-30 15:05:58 +03:00
Dmitry 058044074a Configure Nix HTTP proxy on hosts
NixOS switch cloud-pc / switch (push) Failing after 1m28s
NixOS switch mini-pc / switch (push) Failing after 1m26s
2026-05-30 15:05:01 +03:00
Dmitry 8ed947e48b Enable fallback DNS on mini-pc
Deploy mini-pc / deploy (push) Failing after 33s
NixOS switch mini-pc / switch (push) Failing after 1m27s
2026-05-30 14:58:42 +03:00
Dmitry 7d66b0e296 Use Numeric Ownership in Deploy Workflows
Deploy ru-vps / deploy (push) Successful in 23s
Deploy mini-pc / deploy (push) Failing after 34s
NixOS switch mini-pc / switch (push) Failing after 1m32s
Deploy cloud-pc / deploy (push) Failing after 4m1s
NixOS switch cloud-pc / switch (push) Failing after 1m50s
2026-05-30 14:48:17 +03:00
Dmitry 18b0242fdf Ensure /opt/services ownership on deploy
Deploy cloud-pc / deploy (push) Failing after 1s
Deploy mini-pc / deploy (push) Failing after 2s
Deploy ru-vps / deploy (push) Failing after 2s
NixOS switch cloud-pc / switch (push) Failing after 2s
NixOS switch mini-pc / switch (push) Failing after 2s
2026-05-30 14:46:47 +03:00
Dmitry ec79b653dc Add OpenSSH client to Gitea runners
Deploy cloud-pc / deploy (push) Failing after 1s
Deploy mini-pc / deploy (push) Has been cancelled
2026-05-30 14:40:31 +03:00
Dmitry b18ecaeef6 Update base.nix
NixOS switch cloud-pc / switch (push) Failing after 1s
NixOS switch mini-pc / switch (push) Failing after 1s
2026-05-30 14:39:23 +03:00
Dmitry c65c5cc54d Update docker-compose.yml
Deploy mini-pc / deploy (push) Has been cancelled
2026-05-30 14:35:10 +03:00
Dmitry 60a5778194 Move NixOS switches to SSH runners
NixOS switch cloud-pc / switch (push) Failing after 10m52s
Deploy cloud-pc / deploy (push) Failing after 11m46s
Deploy mini-pc / deploy (push) Failing after 10m14s
NixOS switch mini-pc / switch (push) Has been cancelled
2026-05-30 13:11:29 +03:00
Dmitry ef1503aa3a Fix firewall port formatting
NixOS switch mini-pc / switch (push) Has been cancelled
NixOS switch cloud-pc / switch (push) Failing after 10m4s
2026-05-30 12:58:11 +03:00
Dmitry e9ca74b236 Update firewall.nix
NixOS switch mini-pc / switch (push) Has been cancelled
2026-05-30 12:48:31 +03:00
Dmitry de9ed1e9ee Set Runner PATH in NixOS services
NixOS switch mini-pc / switch (push) Has been cancelled
NixOS switch cloud-pc / switch (push) Has been cancelled
2026-05-30 12:37:40 +03:00
Dmitry 9148838e12 Consolidate Gitea runners on NixOS
Deploy cloud-pc / deploy (push) Failing after 1s
Deploy mini-pc / deploy (push) Failing after 0s
NixOS switch mini-pc / switch (push) Has been cancelled
NixOS switch cloud-pc / switch (push) Has been cancelled
Add the docker group to the runner services and allow passwordless
nixos-rebuild for the gitea-runner user.
2026-05-30 12:35:48 +03:00
Dmitry 27bdb48466 Enable NixOS Gitea runners
NixOS switch mini-pc / switch (push) Failing after 14m0s
NixOS switch cloud-pc / switch (push) Has been cancelled
2026-05-30 12:09:42 +03:00
Dmitry 04256f9f37 Add proxy shell helpers to mini-pc
NixOS switch mini-pc / switch (push) Failing after 14m54s
2026-05-30 12:03:30 +03:00
Dmitry 9c9ad292b2 Expose v2raya LAN proxy ports
Deploy mini-pc / deploy (push) Failing after 1s
NixOS switch mini-pc / switch (push) Failing after 10m48s
2026-05-30 11:54:48 +03:00
Dmitry 8f3dcc415a Enable wait-online for any interface
NixOS switch mini-pc / switch (push) Failing after 11m42s
NixOS switch cloud-pc / switch (push) Has been cancelled
2026-05-30 11:38:23 +03:00
Dmitry 780707123d Update Gitea Runner URLs
NixOS switch mini-pc / switch (push) Failing after 12m36s
NixOS switch cloud-pc / switch (push) Has been cancelled
2026-05-30 11:26:41 +03:00
Dmitry 9b66b72729 Move host firewall configs into host dirs
NixOS switch mini-pc / switch (push) Failing after 13m30s
NixOS switch cloud-pc / switch (push) Has been cancelled
2026-05-30 11:24:56 +03:00
Dmitry 8dada68767 Create docker-compose.yml
Deploy mini-pc / deploy (push) Failing after 2s
NixOS switch mini-pc / switch (push) Failing after 14m25s
NixOS switch cloud-pc / switch (push) Has been cancelled
2026-05-29 22:21:42 +03:00
Dmitry e715d577b1 Add NixOS host modules and runners 2026-05-29 22:11:41 +03:00
214 changed files with 2555 additions and 285 deletions
-27
View File
@@ -1,27 +0,0 @@
name: Deploy ru-vps
on:
workflow_dispatch:
inputs:
stack:
description: "Stack to deploy (leave empty to reconcile all)"
required: false
push:
paths:
- ".gitea/workflows/deploy-ru-vps.yml"
- "ru-vps/**"
schedule:
- cron: "0 4 * * *"
jobs:
deploy:
runs-on: ru-vps
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull --ff-only
- name: Deploy stacks
run: /opt/services/scripts/deploy.sh ru-vps "${{ inputs.stack }}"
@@ -1,20 +0,0 @@
name: NixOS switch cloud-pc
on:
workflow_dispatch:
push:
paths:
- ".gitea/workflows/nixos-switch-cloud-pc.yml"
- "nixos/**"
jobs:
switch:
runs-on: cloud-pc-nixos
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull --ff-only
- name: Switch NixOS generation
run: sudo /run/current-system/sw/bin/nixos-rebuild switch --flake /opt/services/nixos#cloud-pc
-20
View File
@@ -1,20 +0,0 @@
name: NixOS switch mini-pc
on:
workflow_dispatch:
push:
paths:
- ".gitea/workflows/nixos-switch-mini-pc.yml"
- "nixos/**"
jobs:
switch:
runs-on: mini-pc-nixos
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull --ff-only
- name: Switch NixOS generation
run: sudo /run/current-system/sw/bin/nixos-rebuild switch --flake /opt/services/nixos#mini-pc
+5
View File
@@ -1,3 +1,8 @@
.env .env
*.env *.env
!*.env.example !*.env.example
passwd
.venv/
ansible/.venv/
ansible/collections/
ansible/generated/
+26
View File
@@ -0,0 +1,26 @@
# HomeLab Infrastructure
Active HomeLab infrastructure is managed through Ansible.
## Active Files
- `ansible/` — current control plane.
- `ansible/inventory/hosts.yml` — inventory and host facts.
- `ansible/playbooks/check.yml` — safe connectivity/facts check.
## Archive
Historical pre-Proxmox material is kept under:
```text
archive/2026-07-proxmox-migration/
```
It contains old NixOS configs, Docker Compose service definitions, Gitea workflows, deploy scripts and old Ansible bootstrap playbooks.
## Basic Check
```bash
cd ansible
ansible-playbook playbooks/check.yml
```
+8
View File
@@ -0,0 +1,8 @@
export PROXMOX_HOST=192.168.1.10
export PROXMOX_USER='ansible@pve'
export PROXMOX_TOKEN_ID='homelab'
export PROXMOX_TOKEN_SECRET='replace-me'
export PROXMOX_VALIDATE_CERTS=false
# Override if the downloaded template name differs.
export PVE_LXC_OSTEMPLATE='local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst'
+82
View File
@@ -0,0 +1,82 @@
# HomeLab Ansible
Ansible is the control plane for HomeLab infrastructure changes.
## Contract
- The operator keeps passwords, SSH access and network reachability working.
- The agent changes infrastructure only through inventory, roles and playbooks in this directory.
- Manual server changes are allowed only for break-glass recovery; afterwards they must be captured in Ansible.
- Secrets stay outside git. Use `.env`, vault files or local prompt input, not committed variables.
## Layout
- `inventory/hosts.yml` — canonical host list and host-specific facts.
- `playbooks/` — entry points for tasks.
- `roles/` — reusable configuration units.
## Current Groups
- `ru-vps` — public VPS, JumpHost, qdevice, ZeroTier member.
- `pve_nodes` — Proxmox hosts: `cloud-pc`, `mini-pc`.
- `lxc_infra` — infrastructure LXC containers: `pbs`, `zt-cloud`, `zt-mini`.
- `vpn_openvpn` — OpenVPN transport hosts: `ru-vps`, `wg-mini`.
- `shell_hosts` — hosts with unified bash config: `ru-vps`, `cloud-pc`, `mini-pc`.
- `servers` — all managed hosts.
## First Checks
Install control-node dependencies locally:
```bash
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
ansible-galaxy collection install -r requirements.yml -p collections
```
Run from `ansible/`:
```bash
ansible-playbook playbooks/check.yml
```
For Proxmox API playbooks, create ignored `.env` from `.env.example` and load it:
```bash
cp .env.example .env
. ./.env
.venv/bin/ansible-playbook playbooks/pve-wg-mini.yml
```
Or bootstrap the token from `mini-pc` with sudo:
```bash
.venv/bin/ansible-playbook playbooks/bootstrap-pve-api-token.yml -K
```
OpenVPN transport:
```bash
.venv/bin/ansible-playbook playbooks/openvpn-vps-mini.yml -K
.venv/bin/ansible-playbook playbooks/openvpn-check.yml
```
Bootstrap the Ansible service account on shell hosts:
```bash
.venv/bin/ansible-playbook -i inventory/hosts.yml playbooks/bootstrap-ansible-user.yml -K
```
When a task needs privilege escalation:
```bash
ansible-playbook playbooks/<name>.yml -K
```
## Workflow
1. Describe the desired infrastructure change.
2. Add or update a role/playbook.
3. Run the smallest safe check or playbook.
4. Document non-obvious decisions in the project notes.
+4 -1
View File
@@ -1,6 +1,9 @@
[defaults] [defaults]
inventory = inventory/hosts.yml inventory = inventory/hosts.yml
roles_path = roles
collections_path = collections
retry_files_enabled = false
[ssh_connection] [ssh_connection]
pipelining = true pipelining = true
ssh_args = -o ControlMaster=auto -o ControlPersist=60s ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=accept-new
+82 -10
View File
@@ -1,18 +1,30 @@
all: all:
vars: vars:
ansible_python_interpreter: /usr/bin/python3 ansible_python_interpreter: /usr/bin/python3
ansible_user: ada ansible_user: ansible
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab_ansible
ansible_become: true
homelab_lan_cidr: 192.168.1.0/24
homelab_service_range: 192.168.1.5-192.168.1.40
homelab_zerotier_network_id: 743993800fa5a34c
openvpn_network_cidr: 10.78.0.0/30
openvpn_listen_port: 8443
children: children:
servers: homelab:
hosts: hosts:
ru-vps: ru-vps:
ansible_host: vps ansible_host: vps
openvpn_local_ip: 10.78.0.1
openvpn_peer_ip: 10.78.0.2
openvpn_role: server
pve_nodes:
hosts:
cloud-pc: cloud-pc:
ansible_host: 10.122.62.51 ansible_host: 192.168.1.5
# ansible_host: 192.168.1.5 ansible_ssh_common_args: >-
ansible_ssh_common_args: '-J vps' -o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.5 expected_lan_ip: 192.168.1.5
storage_mounts: storage_mounts:
- src: UUID=ae278333-4116-4225-9b95-595496fadd26 - src: UUID=ae278333-4116-4225-9b95-595496fadd26
@@ -21,11 +33,71 @@ all:
- src: UUID=940a9a4e-10cf-4380-9d22-a71549066561 - src: UUID=940a9a4e-10cf-4380-9d22-a71549066561
path: /mnt/backup path: /mnt/backup
fstype: ext4 fstype: ext4
- src: UUID=89024d30-1ab6-4b2e-b23b-62837455b24a
path: /opt/samba
fstype: ext4
mini-pc: mini-pc:
ansible_host: 10.122.62.95 ansible_host: 192.168.1.10
ansible_ssh_common_args: '-J vps' ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.10 expected_lan_ip: 192.168.1.10
lxc_infra:
hosts:
pbs:
ansible_host: 192.168.1.20
expected_lan_ip: 192.168.1.20
zt-cloud:
ansible_host: 192.168.1.21
expected_lan_ip: 192.168.1.21
zerotier_ip: 10.122.62.206
zerotier_node_id: c606e6d181
zt-mini:
ansible_host: 192.168.1.22
expected_lan_ip: 192.168.1.22
zerotier_ip: 10.122.62.213
zerotier_node_id: 022ac284e1
wg-mini:
ansible_host: 192.168.1.23
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab
expected_lan_ip: 192.168.1.23
openvpn_local_ip: 10.78.0.2
openvpn_peer_ip: 10.78.0.1
openvpn_role: gateway
vaultwarden:
ansible_host: 192.168.1.24
ansible_user: root
ansible_become: false
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab
ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.24
gitea:
ansible_host: 192.168.1.25
ansible_user: root
ansible_become: false
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab
ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.25
vpn_openvpn:
hosts:
ru-vps:
wg-mini:
shell_hosts:
hosts:
ru-vps:
cloud-pc:
mini-pc:
servers:
children:
homelab:
pve_nodes:
lxc_infra:
+6
View File
@@ -0,0 +1,6 @@
- name: Configure unified bash profile
hosts: shell_hosts
gather_facts: true
become: true
roles:
- bash_config
@@ -0,0 +1,38 @@
- name: Bootstrap Ansible service account
hosts: shell_hosts
gather_facts: true
become: true
vars:
ansible_service_user: ansible
ansible_service_public_key_file: ~/.ssh/id_ed25519_homelab_ansible.pub
ansible_service_sudoers_file: /etc/sudoers.d/99-ansible-service-user
tasks:
- name: Read local Ansible service public key
ansible.builtin.set_fact:
ansible_service_public_key: "{{ lookup('file', ansible_service_public_key_file) }}"
delegate_to: localhost
become: false
- name: Ensure Ansible service user exists
ansible.builtin.user:
name: "{{ ansible_service_user }}"
shell: /bin/bash
create_home: true
state: present
- name: Install Ansible service SSH key
ansible.posix.authorized_key:
user: "{{ ansible_service_user }}"
key: "{{ ansible_service_public_key }}"
state: present
exclusive: true
- name: Allow passwordless sudo for Ansible service user
ansible.builtin.copy:
dest: "{{ ansible_service_sudoers_file }}"
owner: root
group: root
mode: "0440"
content: "{{ ansible_service_user }} ALL=(ALL) NOPASSWD: ALL\n"
validate: /usr/sbin/visudo -cf %s
@@ -0,0 +1,53 @@
- name: Bootstrap Proxmox API token for Ansible
hosts: mini-pc
gather_facts: false
become: true
vars:
pve_api_user: ansible@pve
pve_api_token_id: homelab
pve_api_role: Administrator
tasks:
- name: Ensure Proxmox API user exists
ansible.builtin.command: "pveum user add {{ pve_api_user }} --comment 'Ansible automation user'"
register: pve_user_add
changed_when: pve_user_add.rc == 0
failed_when:
- pve_user_add.rc != 0
- "'already exists' not in (pve_user_add.stderr | lower)"
- name: Grant Proxmox API user permissions
ansible.builtin.command: "pveum aclmod / -user {{ pve_api_user }} -role {{ pve_api_role }}"
changed_when: true
- name: Remove old Ansible token if present
ansible.builtin.command: "pveum user token remove {{ pve_api_user }} {{ pve_api_token_id }}"
register: pve_token_remove
changed_when: pve_token_remove.rc == 0
failed_when: false
no_log: true
- name: Create Proxmox API token
ansible.builtin.command: "pveum user token add {{ pve_api_user }} {{ pve_api_token_id }} --privsep 0 --comment 'HomeLab Ansible token' --output-format json"
register: pve_token_add
changed_when: true
no_log: true
- name: Store token data
ansible.builtin.set_fact:
pve_token_data: "{{ pve_token_add.stdout | from_json }}"
no_log: true
- name: Write local ansible/.env
ansible.builtin.copy:
dest: "{{ playbook_dir }}/../.env"
mode: "0600"
content: |
export PROXMOX_HOST={{ ansible_host }}
export PROXMOX_USER='{{ pve_api_user }}'
export PROXMOX_TOKEN_ID='{{ pve_api_token_id }}'
export PROXMOX_TOKEN_SECRET='{{ pve_token_data.value }}'
export PROXMOX_VALIDATE_CERTS=false
export PVE_LXC_OSTEMPLATE='local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst'
delegate_to: localhost
become: false
no_log: true
+15
View File
@@ -0,0 +1,15 @@
- name: Check HomeLab Ansible connectivity
hosts: servers
gather_facts: true
become: false
tasks:
- name: Ping host
ansible.builtin.ping:
- name: Check expected LAN IP when defined
ansible.builtin.assert:
that:
- expected_lan_ip in ansible_facts.all_ipv4_addresses
fail_msg: "{{ inventory_hostname }} must have {{ expected_lan_ip }}; found: {{ ansible_facts.all_ipv4_addresses | join(', ') }}"
success_msg: "{{ inventory_hostname }} has {{ expected_lan_ip }}"
when: expected_lan_ip is defined
+19
View File
@@ -0,0 +1,19 @@
- name: Check OpenVPN tunnel from ru-vps
hosts: ru-vps
gather_facts: false
tasks:
- name: Ping VPN LXC OpenVPN address
ansible.builtin.command: ping -c 3 -W 2 10.78.0.2
changed_when: false
- name: Check mini-pc PVE port through OpenVPN gateway
ansible.builtin.command: nc -vz -w 5 192.168.1.10 8006
changed_when: false
- name: Check cloud-pc PVE port through OpenVPN gateway
ansible.builtin.command: nc -vz -w 5 192.168.1.5 8006
changed_when: false
- name: Check PBS port through OpenVPN gateway
ansible.builtin.command: nc -vz -w 5 192.168.1.20 8007
changed_when: false
+197
View File
@@ -0,0 +1,197 @@
- name: Configure OpenVPN laptop access through ru-vps
hosts: ru-vps
gather_facts: true
become: true
vars:
laptop_profile_name: ada-x1
laptop_openvpn_dir: /etc/openvpn/homelab-laptop
laptop_openvpn_config: /etc/openvpn/homelab-laptop/server.conf
laptop_openvpn_key: /etc/openvpn/homelab-laptop/static.key
laptop_openvpn_service: homelab-openvpn-laptop
laptop_openvpn_port: 9443
laptop_openvpn_interface: tun1
laptop_openvpn_site_interface: tun0
laptop_openvpn_network_cidr: 10.79.0.0/30
laptop_openvpn_server_ip: 10.79.0.1
laptop_openvpn_client_ip: 10.79.0.2
laptop_openvpn_remote: 157.22.231.198
laptop_openvpn_client_config_local: "{{ playbook_dir }}/../generated/{{ laptop_profile_name }}.ovpn"
laptop_openvpn_home_routes:
- network: 192.168.1.5
netmask: 255.255.255.255
- network: 192.168.1.6
netmask: 255.255.255.254
- network: 192.168.1.8
netmask: 255.255.255.248
- network: 192.168.1.16
netmask: 255.255.255.240
- network: 192.168.1.32
netmask: 255.255.255.248
- network: 192.168.1.40
netmask: 255.255.255.255
tasks:
- name: Install OpenVPN packages
ansible.builtin.apt:
name:
- openvpn
- iptables
state: present
update_cache: true
- name: Ensure laptop OpenVPN config directory exists
ansible.builtin.file:
path: "{{ laptop_openvpn_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Generate laptop static key if missing
ansible.builtin.command: "openvpn --genkey secret {{ laptop_openvpn_key }}"
args:
creates: "{{ laptop_openvpn_key }}"
no_log: true
- name: Enable IPv4 forwarding on ru-vps
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: "1"
state: present
reload: true
- name: Allow laptop OpenVPN TCP in UFW
ansible.builtin.command: "ufw allow {{ laptop_openvpn_port }}/tcp"
register: ufw_allow_laptop_openvpn
changed_when: "'Rule added' in ufw_allow_laptop_openvpn.stdout or 'Rules updated' in ufw_allow_laptop_openvpn.stdout"
failed_when: false
- name: Render laptop OpenVPN server config
ansible.builtin.copy:
dest: "{{ laptop_openvpn_config }}"
owner: root
group: root
mode: "0600"
content: |
dev {{ laptop_openvpn_interface }}
ifconfig {{ laptop_openvpn_server_ip }} {{ laptop_openvpn_client_ip }}
secret {{ laptop_openvpn_key }}
cipher AES-256-CBC
auth SHA256
proto tcp-server
port {{ laptop_openvpn_port }}
persist-key
persist-tun
verb 3
keepalive 10 60
notify: restart laptop openvpn
- name: Render laptop OpenVPN up script
ansible.builtin.copy:
dest: "{{ laptop_openvpn_dir }}/up.sh"
owner: root
group: root
mode: "0700"
content: |
#!/bin/sh
iptables -t nat -C POSTROUTING -s {{ laptop_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ laptop_openvpn_site_interface }} -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s {{ laptop_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ laptop_openvpn_site_interface }} -j MASQUERADE
notify: restart laptop openvpn
- name: Render laptop OpenVPN down script
ansible.builtin.copy:
dest: "{{ laptop_openvpn_dir }}/down.sh"
owner: root
group: root
mode: "0700"
content: |
#!/bin/sh
iptables -t nat -D POSTROUTING -s {{ laptop_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ laptop_openvpn_site_interface }} -j MASQUERADE 2>/dev/null || true
notify: restart laptop openvpn
- name: Render laptop OpenVPN systemd service
ansible.builtin.copy:
dest: "/etc/systemd/system/{{ laptop_openvpn_service }}.service"
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=HomeLab OpenVPN laptop access
After=network-online.target homelab-openvpn.service
Wants=network-online.target
Requires=homelab-openvpn.service
[Service]
Type=simple
ExecStartPre={{ laptop_openvpn_dir }}/up.sh
ExecStart=/usr/sbin/openvpn --config {{ laptop_openvpn_config }}
ExecStopPost={{ laptop_openvpn_dir }}/down.sh
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
notify:
- reload systemd
- restart laptop openvpn
- name: Flush handlers before starting laptop OpenVPN
ansible.builtin.meta: flush_handlers
- name: Enable and start laptop OpenVPN
ansible.builtin.systemd:
name: "{{ laptop_openvpn_service }}"
state: started
enabled: true
- name: Read laptop static key for client profile
ansible.builtin.slurp:
src: "{{ laptop_openvpn_key }}"
register: laptop_openvpn_static_key_raw
no_log: true
- name: Ensure local generated directory exists
ansible.builtin.file:
path: "{{ playbook_dir }}/../generated"
state: directory
mode: "0700"
become: false
delegate_to: localhost
- name: Write local laptop OpenVPN profile
ansible.builtin.copy:
dest: "{{ laptop_openvpn_client_config_local }}"
mode: "0600"
content: |
client
dev tun
proto tcp-client
remote {{ laptop_openvpn_remote }} {{ laptop_openvpn_port }}
ifconfig {{ laptop_openvpn_client_ip }} {{ laptop_openvpn_server_ip }}
cipher AES-256-CBC
auth SHA256
persist-key
persist-tun
resolv-retry infinite
nobind
verb 3
{% for route in laptop_openvpn_home_routes %}
route {{ route.network }} {{ route.netmask }}
{% endfor %}
<secret>
{{ laptop_openvpn_static_key_raw.content | b64decode }}
</secret>
become: false
delegate_to: localhost
no_log: true
handlers:
- name: reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: restart laptop openvpn
ansible.builtin.systemd:
name: "{{ laptop_openvpn_service }}"
state: restarted
+6
View File
@@ -0,0 +1,6 @@
- name: Configure OpenVPN on ru-vps and VPN LXC
hosts: vpn_openvpn
gather_facts: true
become: true
roles:
- openvpn_gateway
+197
View File
@@ -0,0 +1,197 @@
- name: Create Gitea LXC on cloud-pc
hosts: cloud-pc
gather_facts: false
vars:
gitea_vmid: 141
gitea_hostname: gitea
gitea_ip: 192.168.1.25/24
gitea_gateway: 192.168.1.1
gitea_bridge: vmbr0
gitea_rootfs: data:32
gitea_host_data_dir: /opt/data/gitea
gitea_host_data_uid: 101000
gitea_host_data_gid: 101000
gitea_container_data_dir: /opt/gitea/data
gitea_ostemplate: local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst
gitea_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
handlers:
- name: restart gitea lxc
ansible.builtin.shell: "pct stop {{ gitea_vmid }} || true; pct start {{ gitea_vmid }}"
changed_when: true
tasks:
- name: Check if Gitea LXC exists
ansible.builtin.command: "pct config {{ gitea_vmid }}"
register: gitea_pct_config
changed_when: false
failed_when: false
- name: Install Gitea LXC SSH public key on PVE host
ansible.builtin.copy:
dest: /tmp/gitea-lxc.pub
owner: root
group: root
mode: "0600"
content: "{{ lookup('file', gitea_pubkey_file) }}\n"
when: gitea_pct_config.rc != 0
- name: Create Gitea LXC
ansible.builtin.command: >-
pct create {{ gitea_vmid }} {{ gitea_ostemplate }}
--hostname {{ gitea_hostname }}
--rootfs {{ gitea_rootfs }}
--cores 2
--memory 2048
--swap 1024
--net0 name=eth0,bridge={{ gitea_bridge }},gw={{ gitea_gateway }},ip={{ gitea_ip }},firewall=1
--nameserver 1.1.1.1
--unprivileged 1
--features nesting=1,keyctl=1
--onboot 1
--startup order=50
--cmode shell
--ssh-public-keys /tmp/gitea-lxc.pub
when: gitea_pct_config.rc != 0
- name: Start Gitea LXC
ansible.builtin.command: "pct start {{ gitea_vmid }}"
register: gitea_pct_start
changed_when: gitea_pct_start.rc == 0
failed_when: gitea_pct_start.rc not in [0, 255]
- name: Allow FUSE device in Gitea LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ gitea_vmid }}.conf"
line: "lxc.cgroup2.devices.allow: c 10:229 rwm"
state: present
notify: restart gitea lxc
- name: Bind mount FUSE device in Gitea LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ gitea_vmid }}.conf"
line: "lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file"
state: present
notify: restart gitea lxc
- name: Bind mount Gitea data directory in LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ gitea_vmid }}.conf"
line: "mp0: {{ gitea_host_data_dir }},mp={{ gitea_container_data_dir }}"
state: present
notify: restart gitea lxc
- name: Ensure Gitea host data directory ownership matches unprivileged LXC uid map
ansible.builtin.file:
path: "{{ gitea_host_data_dir }}"
state: directory
owner: "{{ gitea_host_data_uid }}"
group: "{{ gitea_host_data_gid }}"
recurse: true
- name: Apply pending LXC config changes
ansible.builtin.meta: flush_handlers
- name: Wait for Gitea SSH through ru-vps
ansible.builtin.wait_for_connection:
timeout: 120
delegate_to: gitea
vars:
ansible_become: false
- name: Configure Docker and Gitea inside LXC
hosts: gitea
gather_facts: true
vars:
ansible_become: false
gitea_data_dir: /opt/gitea/data
gitea_image: gitea/gitea:latest
gitea_container_name: gitea
gitea_http_port: 3000
gitea_ssh_port: 2222
tasks:
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker.io
- fuse-overlayfs
- sqlite3
- rsync
- ca-certificates
- curl
state: present
update_cache: true
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Docker storage driver for unprivileged LXC
ansible.builtin.copy:
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
content: |
{
"storage-driver": "fuse-overlayfs"
}
register: docker_daemon_config
- name: Ensure Docker service is enabled and running
ansible.builtin.systemd:
name: docker
state: "{{ 'restarted' if docker_daemon_config.changed else 'started' }}"
enabled: true
- name: Ensure Gitea data directory has container ownership
ansible.builtin.file:
path: "{{ gitea_data_dir }}"
state: directory
owner: "1000"
group: "1000"
mode: "0750"
- name: Install Gitea systemd unit
ansible.builtin.copy:
dest: /etc/systemd/system/gitea.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Gitea container
After=docker.service
Requires=docker.service
[Service]
Restart=always
RestartSec=10
ExecStartPre=-/usr/bin/docker rm -f {{ gitea_container_name }}
ExecStart=/usr/bin/docker run --rm \
--name {{ gitea_container_name }} \
--pull always \
-p {{ gitea_http_port }}:3000 \
-p {{ gitea_ssh_port }}:22 \
-v {{ gitea_data_dir }}:/data \
-e USER_UID=1000 \
-e USER_GID=1000 \
{{ gitea_image }}
ExecStop=/usr/bin/docker stop {{ gitea_container_name }}
[Install]
WantedBy=multi-user.target
register: gitea_unit
- name: Reload systemd when Gitea unit changes
ansible.builtin.systemd:
daemon_reload: true
when: gitea_unit.changed
- name: Enable and start Gitea
ansible.builtin.systemd:
name: gitea
state: started
enabled: true
+174
View File
@@ -0,0 +1,174 @@
- name: Create Vaultwarden LXC on mini-pc
hosts: mini-pc
gather_facts: false
vars:
vaultwarden_vmid: 140
vaultwarden_hostname: vaultwarden
vaultwarden_ip: 192.168.1.24/24
vaultwarden_gateway: 192.168.1.1
vaultwarden_bridge: vmbr0
vaultwarden_rootfs: local-lvm:16
vaultwarden_ostemplate: local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst
vaultwarden_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
handlers:
- name: restart vaultwarden lxc
ansible.builtin.shell: "pct stop {{ vaultwarden_vmid }} || true; pct start {{ vaultwarden_vmid }}"
changed_when: true
tasks:
- name: Check if Vaultwarden LXC exists
ansible.builtin.command: "pct config {{ vaultwarden_vmid }}"
register: vaultwarden_pct_config
changed_when: false
failed_when: false
- name: Install Vaultwarden LXC SSH public key on PVE host
ansible.builtin.copy:
dest: /tmp/vaultwarden-lxc.pub
owner: root
group: root
mode: "0600"
content: "{{ lookup('file', vaultwarden_pubkey_file) }}\n"
when: vaultwarden_pct_config.rc != 0
- name: Create Vaultwarden LXC
ansible.builtin.command: >-
pct create {{ vaultwarden_vmid }} {{ vaultwarden_ostemplate }}
--hostname {{ vaultwarden_hostname }}
--rootfs {{ vaultwarden_rootfs }}
--cores 2
--memory 1024
--swap 512
--net0 name=eth0,bridge={{ vaultwarden_bridge }},gw={{ vaultwarden_gateway }},ip={{ vaultwarden_ip }},firewall=1
--nameserver 1.1.1.1
--unprivileged 1
--features nesting=1,keyctl=1
--onboot 1
--startup order=40
--cmode shell
--ssh-public-keys /tmp/vaultwarden-lxc.pub
when: vaultwarden_pct_config.rc != 0
- name: Start Vaultwarden LXC
ansible.builtin.command: "pct start {{ vaultwarden_vmid }}"
register: vaultwarden_pct_start
changed_when: vaultwarden_pct_start.rc == 0
failed_when: vaultwarden_pct_start.rc not in [0, 255]
- name: Allow FUSE device in Vaultwarden LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ vaultwarden_vmid }}.conf"
line: "lxc.cgroup2.devices.allow: c 10:229 rwm"
state: present
notify: restart vaultwarden lxc
- name: Bind mount FUSE device in Vaultwarden LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ vaultwarden_vmid }}.conf"
line: "lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file"
state: present
notify: restart vaultwarden lxc
- name: Apply pending LXC config changes
ansible.builtin.meta: flush_handlers
- name: Wait for Vaultwarden SSH through ru-vps
ansible.builtin.wait_for_connection:
timeout: 120
delegate_to: vaultwarden
vars:
ansible_become: false
- name: Configure Docker and Vaultwarden inside LXC
hosts: vaultwarden
gather_facts: true
vars:
ansible_become: false
vaultwarden_data_dir: /opt/vaultwarden/data
vaultwarden_image: vaultwarden/server:latest
vaultwarden_container_name: vaultwarden
vaultwarden_http_port: 80
tasks:
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker.io
- fuse-overlayfs
- sqlite3
- ca-certificates
- curl
state: present
update_cache: true
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Docker storage driver for unprivileged LXC
ansible.builtin.copy:
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
content: |
{
"storage-driver": "fuse-overlayfs"
}
register: docker_daemon_config
- name: Ensure Docker service is enabled and running
ansible.builtin.systemd:
name: docker
state: "{{ 'restarted' if docker_daemon_config.changed else 'started' }}"
enabled: true
- name: Ensure Vaultwarden data directory exists
ansible.builtin.file:
path: "{{ vaultwarden_data_dir }}"
state: directory
owner: root
group: root
mode: "0750"
- name: Install Vaultwarden systemd unit
ansible.builtin.copy:
dest: /etc/systemd/system/vaultwarden.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Vaultwarden container
After=docker.service
Requires=docker.service
[Service]
Restart=always
RestartSec=10
ExecStartPre=-/usr/bin/docker rm -f {{ vaultwarden_container_name }}
ExecStart=/usr/bin/docker run --rm \
--name {{ vaultwarden_container_name }} \
--pull always \
-p {{ vaultwarden_http_port }}:80 \
-v {{ vaultwarden_data_dir }}:/data \
-e WEBSOCKET_ENABLED=true \
{{ vaultwarden_image }}
ExecStop=/usr/bin/docker stop {{ vaultwarden_container_name }}
[Install]
WantedBy=multi-user.target
register: vaultwarden_unit
- name: Reload systemd when Vaultwarden unit changes
ansible.builtin.systemd:
daemon_reload: true
when: vaultwarden_unit.changed
- name: Enable and start Vaultwarden
ansible.builtin.systemd:
name: vaultwarden
state: started
enabled: true
+38
View File
@@ -0,0 +1,38 @@
- name: Create wg-mini LXC on mini-pc
hosts: localhost
connection: local
gather_facts: false
roles:
- role: pve_lxc
vars:
pve_lxc_vmid: 132
pve_lxc_node: mini-pc
pve_lxc_hostname: wg-mini
pve_lxc_ip: 192.168.1.23/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_storage: local-lvm
pve_lxc_disk: local-lvm:8
pve_lxc_ostemplate: "{{ lookup('env', 'PVE_LXC_OSTEMPLATE') | default('local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst', true) }}"
- name: Allow TUN device in wg-mini LXC config
hosts: mini-pc
gather_facts: false
become: true
handlers:
- name: restart wg-mini lxc
ansible.builtin.shell: pct stop 132 || true; pct start 132
changed_when: true
tasks:
- name: Allow /dev/net/tun device
ansible.builtin.lineinfile:
path: /etc/pve/lxc/132.conf
line: "lxc.cgroup2.devices.allow: c 10:200 rwm"
state: present
notify: restart wg-mini lxc
- name: Bind mount /dev/net/tun
ansible.builtin.lineinfile:
path: /etc/pve/lxc/132.conf
line: "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file"
state: present
notify: restart wg-mini lxc
+65
View File
@@ -0,0 +1,65 @@
- name: Configure Gitea reverse proxy on ru-vps
hosts: ru-vps
gather_facts: false
vars:
caddy_dir: /opt/services/ru-vps/caddy
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
gitea_domain: git.ada-dev.ru
gitea_upstream: 192.168.1.25:3000
tasks:
- name: Ensure Caddy service directory exists
ansible.builtin.file:
path: "{{ caddy_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Gitea Caddy site
ansible.builtin.blockinfile:
path: "{{ caddyfile_path }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED GITEA SITE"
block: |
{{ gitea_domain }} {
reverse_proxy {{ gitea_upstream }}
}
register: gitea_caddy_site
- name: Remove legacy unmanaged Gitea Caddy site
ansible.builtin.replace:
path: "{{ caddyfile_path }}"
regexp: '(?ms)^git\.ada-dev\.ru \{\n\s*reverse_proxy 10\.122\.62\.51:3002\n\}\n+'
replace: ''
register: gitea_legacy_site
- name: Validate host Caddy config
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
changed_when: false
- name: Check Caddy container mounted config
ansible.builtin.command: docker exec caddy grep -F 'reverse_proxy {{ gitea_upstream }}' /etc/caddy/Caddyfile
register: gitea_container_caddyfile
changed_when: false
failed_when: false
- name: Restart Caddy when config changed or bind mount is stale
ansible.builtin.command: docker restart caddy
when: >-
gitea_caddy_site.changed or
gitea_legacy_site.changed or
gitea_container_caddyfile.rc != 0
- name: Validate Caddy container config after restart
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
changed_when: false
- name: Check Gitea upstream from ru-vps
ansible.builtin.command: curl -fsS -o /dev/null -w '%{http_code}' http://{{ gitea_upstream }}/
register: gitea_upstream_http
changed_when: false
failed_when: gitea_upstream_http.stdout != '200'
@@ -0,0 +1,65 @@
- name: Configure Vaultwarden reverse proxy on ru-vps
hosts: ru-vps
gather_facts: false
vars:
caddy_dir: /opt/services/ru-vps/caddy
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
vaultwarden_domain: pass.ada-dev.ru
vaultwarden_upstream: 192.168.1.24:80
tasks:
- name: Ensure Caddy service directory exists
ansible.builtin.file:
path: "{{ caddy_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Vaultwarden Caddy site
ansible.builtin.blockinfile:
path: "{{ caddyfile_path }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED VAULTWARDEN SITE"
block: |
{{ vaultwarden_domain }} {
reverse_proxy {{ vaultwarden_upstream }}
}
register: vaultwarden_caddy_site
- name: Remove legacy unmanaged Vaultwarden Caddy site
ansible.builtin.replace:
path: "{{ caddyfile_path }}"
regexp: '(?ms)^pass\.ada-dev\.ru \{\n\s*reverse_proxy 10\.122\.62\.95:10380\n\}\n+'
replace: ''
register: vaultwarden_legacy_site
- name: Validate host Caddy config
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
changed_when: false
- name: Check Caddy container mounted config
ansible.builtin.command: docker exec caddy grep -F 'reverse_proxy {{ vaultwarden_upstream }}' /etc/caddy/Caddyfile
register: vaultwarden_container_caddyfile
changed_when: false
failed_when: false
- name: Restart Caddy when config changed or bind mount is stale
ansible.builtin.command: docker restart caddy
when: >-
vaultwarden_caddy_site.changed or
vaultwarden_legacy_site.changed or
vaultwarden_container_caddyfile.rc != 0
- name: Validate Caddy container config after restart
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
changed_when: false
- name: Check Vaultwarden upstream from ru-vps
ansible.builtin.command: curl -fsS -o /dev/null -w '%{http_code}' http://{{ vaultwarden_upstream }}/
register: vaultwarden_upstream_http
changed_when: false
failed_when: vaultwarden_upstream_http.stdout != '200'
+3
View File
@@ -0,0 +1,3 @@
ansible-core>=2.19
proxmoxer>=2.3
requests>=2.31
+6
View File
@@ -0,0 +1,6 @@
---
collections:
- name: ansible.posix
version: ">=2.0.0"
- name: community.proxmox
version: ">=2.0.0"
View File
@@ -0,0 +1,7 @@
---
bash_config_file: /etc/profile.d/homelab.sh
bash_config_bashrc: /etc/bash.bashrc
bash_config_legacy_user: ada
bash_config_legacy_home: "/home/{{ bash_config_legacy_user }}"
bash_config_legacy_profile_files:
- /etc/profile.d/custom_bash.sh
+63
View File
@@ -0,0 +1,63 @@
- name: Install unified HomeLab bash config
ansible.builtin.template:
src: homelab.sh.j2
dest: "{{ bash_config_file }}"
owner: root
group: root
mode: "0644"
- name: Ensure system bashrc sources managed config
ansible.builtin.blockinfile:
path: "{{ bash_config_bashrc }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED HOMELAB BASH CONFIG"
block: |
if [ -f /etc/profile.d/homelab.sh ]; then
. /etc/profile.d/homelab.sh
fi
- name: List bash users with local home directories
ansible.builtin.shell: >-
getent passwd | awk -F: '$7 ~ /\/bash$/ && $6 ~ /^\// { print $1 ":" $6 }'
register: bash_config_bash_users_raw
changed_when: false
- name: Ensure bash users source managed system config
ansible.builtin.blockinfile:
path: "{{ item.split(':')[1] }}/.bashrc"
create: true
owner: "{{ item.split(':')[0] }}"
group: "{{ item.split(':')[0] }}"
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED HOMELAB SYSTEM BASH CONFIG"
block: |
if [ -f /etc/profile.d/homelab.sh ]; then
. /etc/profile.d/homelab.sh
fi
loop: "{{ bash_config_bash_users_raw.stdout_lines }}"
- name: Check legacy per-user bashrc
ansible.builtin.stat:
path: "{{ bash_config_legacy_home }}/.bashrc"
register: bash_config_legacy_bashrc
- name: Remove legacy per-user bashrc source block
ansible.builtin.blockinfile:
path: "{{ bash_config_legacy_home }}/.bashrc"
marker: "# {mark} ANSIBLE MANAGED HOMELAB BASH CONFIG"
state: absent
when: bash_config_legacy_bashrc.stat.exists
- name: Remove legacy per-user bash config file
ansible.builtin.file:
path: "{{ bash_config_legacy_home }}/.bashrc.d/homelab.sh"
state: absent
- name: Remove legacy profile scripts that conflict with unified config
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ bash_config_legacy_profile_files }}"
@@ -0,0 +1,44 @@
# Managed by Ansible: HomeLab unified bash config.
export EDITOR="${EDITOR:-vim}"
export PAGER="${PAGER:-less}"
export LESS="${LESS:--FRSX}"
export HISTCONTROL="ignoreboth:erasedups"
export HISTSIZE=50000
export HISTFILESIZE=100000
shopt -s histappend 2>/dev/null || true
shopt -s checkwinsize 2>/dev/null || true
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias grep='grep --color=auto'
alias ip='ip --color=auto'
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias ..='cd ..'
alias ...='cd ../..'
alias please='sudo $(history -p !!)'
alias gs='git status --short --branch'
alias gd='git diff'
alias gl='git log --oneline --decorate -10'
if command -v systemctl >/dev/null 2>&1; then
alias sctl='systemctl'
alias jctl='journalctl'
fi
if [ -n "$PS1" ]; then
if [ "$(id -u)" -eq 0 ]; then
PS1='\[\e[31m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]# '
else
PS1='\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]\$ '
fi
fi
@@ -0,0 +1,22 @@
---
openvpn_interface: tun0
openvpn_listen_port: 8443
openvpn_proto_server: tcp-server
openvpn_proto_client: tcp-client
openvpn_config_dir: /etc/openvpn/homelab
openvpn_config_path: /etc/openvpn/homelab/homelab.conf
openvpn_static_key_path: /etc/openvpn/homelab/static.key
openvpn_service_name: homelab-openvpn
openvpn_home_routes:
- network: 192.168.1.5
netmask: 255.255.255.255
- network: 192.168.1.6
netmask: 255.255.255.254
- network: 192.168.1.8
netmask: 255.255.255.248
- network: 192.168.1.16
netmask: 255.255.255.240
- network: 192.168.1.32
netmask: 255.255.255.248
- network: 192.168.1.40
netmask: 255.255.255.255
@@ -0,0 +1,6 @@
---
- name: restart openvpn
ansible.builtin.systemd:
name: "{{ openvpn_service_name }}"
state: restarted
enabled: true
@@ -0,0 +1,105 @@
---
- name: Install OpenVPN packages
ansible.builtin.apt:
name:
- openvpn
- iptables
- iproute2
- netcat-openbsd
state: present
update_cache: true
- name: Ensure OpenVPN config directory exists
ansible.builtin.file:
path: "{{ openvpn_config_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Generate static key on server if missing
ansible.builtin.command: "openvpn --genkey secret {{ openvpn_static_key_path }}"
args:
creates: "{{ openvpn_static_key_path }}"
when: openvpn_role == 'server'
no_log: true
- name: Read static key from server
ansible.builtin.slurp:
src: "{{ openvpn_static_key_path }}"
register: openvpn_static_key_raw
when: openvpn_role == 'server'
no_log: true
- name: Copy static key to all peers
ansible.builtin.copy:
dest: "{{ openvpn_static_key_path }}"
content: "{{ hostvars['ru-vps'].openvpn_static_key_raw.content | b64decode }}"
owner: root
group: root
mode: "0600"
no_log: true
notify: restart openvpn
- name: Enable IPv4 forwarding on gateway
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: "1"
state: present
reload: true
when: openvpn_role == 'gateway'
- name: Allow OpenVPN TCP in UFW on server
ansible.builtin.command: "ufw allow {{ openvpn_listen_port }}/tcp"
register: ufw_allow_openvpn
changed_when: "'Rule added' in ufw_allow_openvpn.stdout or 'Rules updated' in ufw_allow_openvpn.stdout"
failed_when: false
when: openvpn_role == 'server'
- name: Render OpenVPN config
ansible.builtin.template:
src: homelab.conf.j2
dest: "{{ openvpn_config_path }}"
owner: root
group: root
mode: "0600"
notify: restart openvpn
- name: Render OpenVPN gateway up script
ansible.builtin.template:
src: up.sh.j2
dest: "{{ openvpn_config_dir }}/up.sh"
owner: root
group: root
mode: "0700"
notify: restart openvpn
when: openvpn_role == 'gateway'
- name: Render OpenVPN gateway down script
ansible.builtin.template:
src: down.sh.j2
dest: "{{ openvpn_config_dir }}/down.sh"
owner: root
group: root
mode: "0700"
notify: restart openvpn
when: openvpn_role == 'gateway'
- name: Render OpenVPN systemd service
ansible.builtin.template:
src: homelab-openvpn.service.j2
dest: "/etc/systemd/system/{{ openvpn_service_name }}.service"
owner: root
group: root
mode: "0644"
notify: restart openvpn
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Enable and start OpenVPN
ansible.builtin.systemd:
name: "{{ openvpn_service_name }}"
state: started
enabled: true
@@ -0,0 +1,2 @@
#!/bin/sh
iptables -t nat -D POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE 2>/dev/null || true
@@ -0,0 +1,13 @@
[Unit]
Description=HomeLab OpenVPN {{ openvpn_role }}
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/openvpn --config {{ openvpn_config_path }}
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,26 @@
dev {{ openvpn_interface }}
ifconfig {{ openvpn_local_ip }} {{ openvpn_peer_ip }}
secret {{ openvpn_static_key_path }}
cipher AES-256-CBC
auth SHA256
persist-key
persist-tun
verb 3
keepalive 10 60
{% if openvpn_role == 'server' %}
proto {{ openvpn_proto_server }}
port {{ openvpn_listen_port }}
{% for route in openvpn_home_routes %}
route {{ route.network }} {{ route.netmask }}
{% endfor %}
{% endif %}
{% if openvpn_role == 'gateway' %}
proto {{ openvpn_proto_client }}
remote 157.22.231.198 {{ openvpn_listen_port }}
resolv-retry infinite
nobind
route-nopull
script-security 2
up /etc/openvpn/homelab/up.sh
down /etc/openvpn/homelab/down.sh
{% endif %}
@@ -0,0 +1,3 @@
#!/bin/sh
iptables -t nat -C POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE
+20
View File
@@ -0,0 +1,20 @@
---
pve_lxc_vmid: 132
pve_lxc_node: mini-pc
pve_lxc_hostname: wg-mini
pve_lxc_ip: 192.168.1.23/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_bridge: vmbr0
pve_lxc_storage: local-lvm
pve_lxc_disk: local-lvm:8
pve_lxc_cores: 1
pve_lxc_memory: 256
pve_lxc_swap: 128
pve_lxc_onboot: true
pve_lxc_startup: order=30
pve_lxc_unprivileged: true
pve_lxc_features:
- nesting=1
pve_lxc_nameserver: 1.1.1.1
pve_lxc_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
pve_lxc_cmode: shell
+46
View File
@@ -0,0 +1,46 @@
---
- name: Validate Proxmox API environment
ansible.builtin.assert:
that:
- lookup('env', 'PROXMOX_HOST') | length > 0
- lookup('env', 'PROXMOX_USER') | length > 0
- lookup('env', 'PROXMOX_TOKEN_ID') | length > 0
- lookup('env', 'PROXMOX_TOKEN_SECRET') | length > 0
fail_msg: Load ansible/.env with PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN_ID and PROXMOX_TOKEN_SECRET.
- name: Create or update LXC container
community.proxmox.proxmox:
api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: "{{ lookup('env', 'PROXMOX_VALIDATE_CERTS') | default('false', true) | bool }}"
vmid: "{{ pve_lxc_vmid }}"
node: "{{ pve_lxc_node }}"
hostname: "{{ pve_lxc_hostname }}"
ostemplate: "{{ pve_lxc_ostemplate }}"
disk: "{{ pve_lxc_disk }}"
cores: "{{ pve_lxc_cores }}"
memory: "{{ pve_lxc_memory }}"
swap: "{{ pve_lxc_swap }}"
nameserver: "{{ pve_lxc_nameserver }}"
cmode: "{{ pve_lxc_cmode }}"
netif:
net0: "name=eth0,gw={{ pve_lxc_gateway }},ip={{ pve_lxc_ip }},bridge={{ pve_lxc_bridge }},firewall=1"
pubkey: "{{ lookup('file', pve_lxc_pubkey_file) }}"
features: "{{ pve_lxc_features }}"
unprivileged: "{{ pve_lxc_unprivileged }}"
onboot: "{{ pve_lxc_onboot }}"
startup: "{{ pve_lxc_startup }}"
update: true
state: present
- name: Start LXC container
community.proxmox.proxmox:
api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: "{{ lookup('env', 'PROXMOX_VALIDATE_CERTS') | default('false', true) | bool }}"
vmid: "{{ pve_lxc_vmid }}"
state: started
@@ -21,7 +21,8 @@ jobs:
- name: Sync repo - name: Sync repo
run: | run: |
git config --global --replace-all safe.directory /opt/services git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull --ff-only git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Deploy stacks - name: Deploy stacks
run: /opt/services/scripts/deploy.sh cloud-pc "${{ inputs.stack }}" run: /opt/services/scripts/deploy.sh cloud-pc "${{ inputs.stack }}"
@@ -21,7 +21,8 @@ jobs:
- name: Sync repo - name: Sync repo
run: | run: |
git config --global --replace-all safe.directory /opt/services git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull --ff-only git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Deploy stacks - name: Deploy stacks
run: /opt/services/scripts/deploy.sh mini-pc "${{ inputs.stack }}" run: /opt/services/scripts/deploy.sh mini-pc "${{ inputs.stack }}"
@@ -0,0 +1,28 @@
name: Deploy ru-vps
on:
workflow_dispatch:
inputs:
stack:
description: "Stack to deploy (leave empty to reconcile all)"
required: false
push:
paths:
- ".gitea/workflows/deploy-ru-vps.yml"
- "ru-vps/**"
schedule:
- cron: "0 4 * * *"
jobs:
deploy:
runs-on: ru-vps
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Deploy stacks
run: /opt/services/scripts/deploy.sh ru-vps "${{ inputs.stack }}"
@@ -0,0 +1,33 @@
name: NixOS switch cloud-pc
on:
workflow_dispatch:
push:
paths:
- ".gitea/workflows/nixos-switch-cloud-pc.yml"
- "nixos/flake.nix"
- "nixos/flake.lock"
- "nixos/modules/**"
- "nixos/hosts/cloud-pc/**"
jobs:
switch:
runs-on: cloud-pc
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Switch NixOS generation
env:
NIXOS_DEPLOY_KEY: ${{ secrets.NIXOS_DEPLOY_KEY }}
run: |
KEY=$(mktemp)
echo "$NIXOS_DEPLOY_KEY" > "$KEY"
chmod 600 "$KEY"
trap "rm -f $KEY" EXIT
ssh -i "$KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
ada@192.168.1.5 \
"sudo /run/current-system/sw/bin/nixos-rebuild switch --flake /opt/services/nixos#cloud-pc"
@@ -0,0 +1,33 @@
name: NixOS switch mini-pc
on:
workflow_dispatch:
push:
paths:
- ".gitea/workflows/nixos-switch-mini-pc.yml"
- "nixos/flake.nix"
- "nixos/flake.lock"
- "nixos/modules/**"
- "nixos/hosts/mini-pc/**"
jobs:
switch:
runs-on: mini-pc
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Switch NixOS generation
env:
NIXOS_DEPLOY_KEY: ${{ secrets.NIXOS_DEPLOY_KEY }}
run: |
KEY=$(mktemp)
echo "$NIXOS_DEPLOY_KEY" > "$KEY"
chmod 600 "$KEY"
trap "rm -f $KEY" EXIT
ssh -i "$KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
ada@192.168.1.10 \
"sudo /run/current-system/sw/bin/nixos-rebuild switch --flake /opt/services/nixos#mini-pc"
@@ -0,0 +1,13 @@
# Archive: 2026-07 Proxmox Migration
Historical configuration from the pre-Proxmox HomeLab setup.
Moved here:
- `nixos/` — old NixOS host configs and modules.
- `cloud-pc/`, `mini-pc/`, `ru-vps/` — old Docker Compose service trees.
- `.gitea/` — old GitOps workflows.
- `scripts/` — old deploy scripts.
- `legacy-ansible/` — old bootstrap/base/dirs/restic/rclone playbooks and bootstrap role.
Use these files only as reference when implementing new Ansible roles/playbooks.
@@ -0,0 +1,4 @@
FROM gitea/act_runner:latest
USER root
RUN apk add --no-cache docker-cli docker-cli-compose openssh-client
@@ -1,18 +1,4 @@
services: services:
postgres:
image: postgres:${PG_VER}-alpine
container_name: postgres
hostname: postgres
restart: unless-stopped
volumes:
- /opt/data/postgres:/var/lib/postgresql/data
environment:
TZ: Europe/Moscow
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
ports:
- 5432:5432
redis: redis:
image: redis:alpine image: redis:alpine
container_name: redis container_name: redis
@@ -20,6 +6,7 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
TZ: Europe/Moscow TZ: Europe/Moscow
nextcloud: nextcloud:
image: nextcloud:${NC_VER} image: nextcloud:${NC_VER}
container_name: nextcloud container_name: nextcloud
@@ -32,7 +19,6 @@ services:
extra_hosts: extra_hosts:
- ${NC_DOMAIN}:host-gateway - ${NC_DOMAIN}:host-gateway
depends_on: depends_on:
- postgres
- redis - redis
volumes: volumes:
- /opt/configs/nextcloud/app:/var/www/html - /opt/configs/nextcloud/app:/var/www/html
@@ -43,14 +29,11 @@ services:
VIRTUAL_HOST: ${NC_DOMAIN} VIRTUAL_HOST: ${NC_DOMAIN}
NEXTCLOUD_TRUSTED_DOMAINS: ${NC_DOMAIN} 127.0.0.1 NEXTCLOUD_TRUSTED_DOMAINS: ${NC_DOMAIN} 127.0.0.1
NEXTCLOUD_OVERWRITEPROTOCOL: https NEXTCLOUD_OVERWRITEPROTOCOL: https
NEXTCLOUD_ADMIN_USER: ${NC_ADMIN_USER} NEXTCLOUD_OVERWRITEHOST: ${NC_DOMAIN}
NEXTCLOUD_ADMIN_PASSWORD: ${NC_ADMIN_PASSWORD} NEXTCLOUD_OVERWRITECLIURL: https://${NC_DOMAIN}
POSTGRES_HOST: postgres
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis REDIS_HOST: redis
TRUSTED_PROXIES: ${TRUSTED_PROXIES} TRUSTED_PROXIES: ${TRUSTED_PROXIES}
cron: cron:
image: nextcloud:${NC_VER} image: nextcloud:${NC_VER}
hostname: cron hostname: cron
@@ -64,9 +47,6 @@ services:
- nextcloud - nextcloud
environment: environment:
TZ: Europe/Moscow TZ: Europe/Moscow
POSTGRES_HOST: postgres
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis REDIS_HOST: redis
networks: {} networks: {}
@@ -1,6 +1,6 @@
services: services:
samba: samba:
image: ghcr.io/servercontainers/samba:sha256-e49f754b7c4a29d6b9338e33527d497c7167e5f0f3172c8a96f2daab9d0e5ec0.sig image: ghcr.io/servercontainers/samba:latest
container_name: samba container_name: samba
restart: unless-stopped restart: unless-stopped
network_mode: host network_mode: host
@@ -0,0 +1,19 @@
git.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.5:3002
}
cloud.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.5:8081
}
router.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.1:80
}
dns.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.10:8088
}
@@ -3,6 +3,9 @@ services:
image: caddy:2-alpine image: caddy:2-alpine
container_name: caddy container_name: caddy
restart: unless-stopped restart: unless-stopped
dns:
- 1.1.1.1
- 8.8.8.8
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
@@ -11,10 +14,21 @@ services:
- ./Caddyfile:/etc/caddy/Caddyfile:ro - ./Caddyfile:/etc/caddy/Caddyfile:ro
- /opt/data/caddy:/data - /opt/data/caddy:/data
- /opt/configs/caddy:/config - /opt/configs/caddy:/config
entrypoint: /bin/sh
command:
- -c
- |
while [ ! -f /config/lego/certificates/ada-dev.ru.crt ] || [ ! -f /config/lego/certificates/ada-dev.ru.key ]; do
sleep 10
done
exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
lego: lego:
image: git.ada-dev.ru/ada/lego:latest image: git.ada-dev.ru/ada/lego:latest
container_name: caddy-lego container_name: caddy-lego
dns:
- 1.1.1.1
- 8.8.8.8
env_file: env_file:
- .env - .env
restart: unless-stopped restart: unless-stopped
@@ -29,19 +43,26 @@ services:
/lego renew \ /lego renew \
--email "$$LEGO_EMAIL" \ --email "$$LEGO_EMAIL" \
--dns timewebcloud \ --dns timewebcloud \
--dns.resolvers 1.1.1.1:53 \
--dns.resolvers 8.8.8.8:53 \
--dns.propagation-timeout 5m \
--domains ada-dev.ru \ --domains ada-dev.ru \
--domains '*.ada-dev.ru' \ --domains '*.ada-dev.ru' \
--path /lego-data \ --path /lego-data \
--days 30 --days 30
sleep 24h
else else
/lego run \ /lego run \
--email "$$LEGO_EMAIL" \ --email "$$LEGO_EMAIL" \
--accept-tos \ --accept-tos \
--dns timewebcloud \ --dns timewebcloud \
--dns.resolvers 1.1.1.1:53 \
--dns.resolvers 8.8.8.8:53 \
--dns.propagation-timeout 5m \
--domains ada-dev.ru \ --domains ada-dev.ru \
--domains '*.ada-dev.ru' \ --domains '*.ada-dev.ru' \
--path /lego-data --path /lego-data
sleep 10m
fi fi
sleep 24h
done done
networks: {} networks: {}
@@ -0,0 +1,4 @@
FROM gitea/act_runner:latest
USER root
RUN apk add --no-cache docker-cli docker-cli-compose openssh-client
@@ -11,3 +11,6 @@ services:
GITEA_RUNNER_REGISTRATION_TOKEN: ${TOKEN} GITEA_RUNNER_REGISTRATION_TOKEN: ${TOKEN}
GITEA_RUNNER_NAME: mini-pc-runner GITEA_RUNNER_NAME: mini-pc-runner
GITEA_RUNNER_LABELS: mini-pc GITEA_RUNNER_LABELS: mini-pc
dns:
- 1.1.1.1
- 8.8.8.8
@@ -0,0 +1,13 @@
services:
memoir-bot:
image: git.ada-dev.ru/ada/memoir_bot:0.1
restart: unless-stopped
env_file:
- .env
environment:
OBSIDIAN_VAULT_PATH: /vault
TZ: ${TIMEZONE:-Europe/Moscow}
volumes:
- /opt/data/memoir_bot:/vault
- /home/ada/.ssh:/root/.ssh:ro
network_mode: host
@@ -0,0 +1,14 @@
services:
v2raya:
image: mzz2017/v2raya:latest
container_name: v2raya
restart: unless-stopped
network_mode: host
privileged: true
volumes:
- /opt/data/v2raya:/etc/v2raya
- /opt/data/v2raya-extra:/etc/v2raya-extra
environment:
- V2RAYA_LOG_FILE=/tmp/v2raya.log
- V2RAYA_V2RAY_BIN_PATH=/usr/local/bin/v2ray
- V2RAYA_V2RAY_CONFDIR=/etc/v2raya-extra
@@ -0,0 +1,8 @@
ADMIN_TOKEN=changeme
SMTP_HOST=smtp.yandex.ru
SMTP_PORT=465
SMTP_SECURITY=force_tls
SMTP_USERNAME=info.ada-dev@yandex.ru
SMTP_PASSWORD=changeme
SMTP_FROM=info.ada-dev@yandex.ru
SMTP_FROM_NAME=Vaultwarden
@@ -2,7 +2,6 @@ services:
vaultwarden: vaultwarden:
hostname: vaultwarden hostname: vaultwarden
image: vaultwarden/server:latest image: vaultwarden/server:latest
network_mode: bridge
ports: ports:
- target: 80 - target: 80
published: "10380" published: "10380"
@@ -9,14 +9,14 @@
nixosConfigurations.cloud-pc = nixpkgs.lib.nixosSystem { nixosConfigurations.cloud-pc = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; system = "x86_64-linux";
modules = [ modules = [
./hosts/cloud-pc.nix ./hosts/cloud-pc
]; ];
}; };
nixosConfigurations.mini-pc = nixpkgs.lib.nixosSystem { nixosConfigurations.mini-pc = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; system = "x86_64-linux";
modules = [ modules = [
./hosts/mini-pc.nix ./hosts/mini-pc
]; ];
}; };
}; };
@@ -1,11 +1,13 @@
{ pkgs, ... }: { ... }:
{ {
imports = [ imports = [
../modules/base.nix ../../modules/base.nix
../modules/docker.nix ../../modules/docker.nix
../modules/firewall.nix ../../modules/libvirtd.nix
./hardware-configuration.nix ../../modules/zerotier.nix
./firewall.nix
./hardware.nix
]; ];
networking = { networking = {
@@ -43,23 +45,6 @@
options = [ "nofail" ]; options = [ "nofail" ];
}; };
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
virtualisation.libvirtd = {
enable = true;
qemu = {
package = pkgs.qemu_kvm;
};
};
services.zerotierone = {
enable = true;
joinNetworks = [
"743993800fa5a34c"
];
};
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d /opt/data/postgres 0755 ada users - -" "d /opt/data/postgres 0755 ada users - -"
"d /opt/data/nextcloud 0755 ada users - -" "d /opt/data/nextcloud 0755 ada users - -"
@@ -0,0 +1,25 @@
{
networking.firewall = {
enable = true;
allowedTCPPorts = [
22 # SSH
139 # Samba (NetBIOS session)
445 # Samba (SMB)
2222 # Gitea SSH
3002
4567
5432 # PostgreSQL
7080
8081
8384 # Syncthing web UI
22000 # Syncthing data transfer
];
allowedUDPPorts = [
137 # Samba (NetBIOS name)
138 # Samba (NetBIOS datagram)
9993 # ZeroTier
21027 # Syncthing discovery
22000 # Syncthing data transfer
];
};
}
@@ -0,0 +1,31 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/f729e8cb-5c37-45fe-bd0a-e511d79bb49c";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/8BA8-38FE";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
@@ -0,0 +1,72 @@
{ ... }:
{
imports = [
../../modules/base.nix
../../modules/docker.nix
../../modules/libvirtd.nix
../../modules/zerotier.nix
./firewall.nix
./hardware.nix
];
networking = {
hostName = "mini-pc";
useDHCP = false;
};
systemd.network = {
enable = true;
networks."10-lan" = {
matchConfig.Name = "en*";
networkConfig = {
Address = "192.168.1.10/24";
Gateway = "192.168.1.1";
DNS = [
"192.168.1.1"
"1.1.1.1"
];
};
};
};
services.resolved = {
enable = true;
fallbackDns = [ "1.1.1.1" "8.8.8.8" ];
};
systemd.tmpfiles.rules = [
"d /opt/data/adguard 0755 ada users - -"
"d /opt/data/adguard/work 0755 ada users - -"
"d /opt/data/caddy 0755 ada users - -"
"d /opt/data/gitea-runner 0755 ada users - -"
"d /opt/data/uptime-kuma 0755 ada users - -"
"d /opt/data/vaultwarden 0755 ada users - -"
"d /opt/data/zerotier 0755 ada users - -"
"d /opt/configs/adguard 0755 ada users - -"
"d /opt/configs/caddy 0755 ada users - -"
"d /opt/configs/caddy/lego 0755 ada users - -"
];
programs.bash.interactiveShellInit = ''
proxy_on() {
export http_proxy=http://192.168.1.10:10172
export https_proxy=http://192.168.1.10:10172
export HTTP_PROXY=http://192.168.1.10:10172
export HTTPS_PROXY=http://192.168.1.10:10172
export all_proxy=socks5://192.168.1.10:10171
export ALL_PROXY=socks5://192.168.1.10:10171
export ftp_proxy=http://192.168.1.10:10172
export FTP_PROXY=http://192.168.1.10:10172
export no_proxy=localhost,127.0.0.0/8,::1,192.168.1.0/24
export NO_PROXY=localhost,127.0.0.0/8,::1,192.168.1.0/24
}
proxy_off() {
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
unset all_proxy ALL_PROXY ftp_proxy FTP_PROXY
unset no_proxy NO_PROXY
}
'';
system.stateVersion = "25.11";
}
@@ -0,0 +1,28 @@
{
networking.firewall = {
enable = true;
allowedTCPPorts = [
22 # SSH
53 # DNS (AdGuard)
80 # HTTP
443 # HTTPS
784 # DNS-over-QUIC
853 # DNS-over-TLS
3001 # Uptime Kuma
3015
4438
5443
8088
10171 # v2raya SOCKS5 LAN
10172 # v2raya HTTP proxy LAN
10380
];
allowedUDPPorts = [
53 # DNS
443 # QUIC
784 # DNS-over-QUIC
9993 # ZeroTier
5443
];
};
}
@@ -0,0 +1,31 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "sdhci_pci" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/a2fc1626-3301-44dd-9d00-fa7e8d216d25";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/73C2-3F45";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
@@ -1,10 +1,31 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
nix.settings.experimental-features = [ nix = {
"nix-command" settings = {
"flakes" experimental-features = [ "nix-command" "flakes" ];
]; auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
boot = {
tmp.cleanOnBoot = true;
loader.systemd-boot.enable = true;
loader.efi.canTouchEfiVariables = true;
};
systemd.network.wait-online.anyInterface = true;
systemd.services.nix-daemon.environment = {
HTTPS_PROXY = "http://192.168.1.10:10172";
HTTP_PROXY = "http://192.168.1.10:10172";
NO_PROXY = "localhost,127.0.0.0/8,::1,192.168.1.0/24";
};
time.timeZone = "Europe/Moscow"; time.timeZone = "Europe/Moscow";
@@ -20,7 +41,6 @@
extraGroups = [ extraGroups = [
"wheel" "wheel"
"docker" "docker"
"libvirtd"
]; ];
openssh.authorizedKeys.keys = [ openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWNsUQmppB6cQccXX1ZaBbFIcmM6RmghsTVbG9TgoZB ada@ada-x1" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWNsUQmppB6cQccXX1ZaBbFIcmM6RmghsTVbG9TgoZB ada@ada-x1"
@@ -53,10 +73,12 @@
git git
restic restic
resticprofile resticprofile
htop
]; ];
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d /opt/services 0755 ada users - -" "d /opt/services 0755 ada users - -"
"Z /opt/services - ada users - -"
"d /opt/data 0755 ada users - -" "d /opt/data 0755 ada users - -"
"d /opt/configs 0755 ada users - -" "d /opt/configs 0755 ada users - -"
]; ];
@@ -0,0 +1,10 @@
{ pkgs, ... }:
{
virtualisation.libvirtd = {
enable = true;
qemu.package = pkgs.qemu_kvm;
};
users.users.ada.extraGroups = [ "libvirtd" ];
}
@@ -0,0 +1,6 @@
{
services.zerotierone = {
enable = true;
joinNetworks = [ "743993800fa5a34c" ];
};
}
@@ -18,6 +18,9 @@ uptime.ada-dev.ru {
reverse_proxy 10.122.62.95:3001 reverse_proxy 10.122.62.95:3001
} }
# projects.ada-dev.ru {
# reverse_proxy 10.122.62.51:4567 projects.ada-dev.ru {
# } handle_path /neural* {
reverse_proxy 10.122.62.51:4567
}
}
@@ -0,0 +1,56 @@
mixed-port: 7890
http-port: 7892
socks-port: 7891
allow-lan: true # Меняем с false на true
bind-address: "0.0.0.0" # Добавляем - слушаем все интерфейсы
mode: rule
external-controller: 127.0.0.1:9090
external-ui: /root/.config/mihomo/ui
# secret: "changeme"
authentication:
- "ada:Holivarr588856"
proxy-providers:
main:
type: http
url: "https://491b5056.withblancvpn.online/s/c1b50e857b4e41a59ea80bcf6d1953d3"
interval: 3600
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 300
proxy-groups:
- name: "URL-Test"
type: url-test
use: [main]
url: "https://www.gstatic.com/generate_204"
interval: 300 # Тестировать каждые 5 минут
tolerance: 50
rules:
- DOMAIN-SUFFIX,local,DIRECT
- DOMAIN-SUFFIX,lan,DIRECT
- DOMAIN-SUFFIX,ru,DIRECT
- DOMAIN-SUFFIX,su,DIRECT
- DOMAIN-SUFFIX,xn--p1ai,DIRECT
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR,169.254.0.0/16,DIRECT,no-resolve
- IP-CIDR6,::1/128,DIRECT,no-resolve
- IP-CIDR6,fc00::/7,DIRECT,no-resolve
- IP-CIDR6,fe80::/10,DIRECT,no-resolve
- GEOIP,RU,DIRECT
- MATCH,URL-Test
# tun:
# enable: true
# stack: mixed
# auto-route: true
# auto-detect-interface: true
# dns-hijack:
# - "any:53"
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><script type="importmap">{"imports":{"#entry":"./_nuxt/Bmqvg1eR.js"}}</script><title>MetaCubeXD</title><script src="config.js" onerror="window.__METACUBEXD_CONFIG__={defaultBackendURL:''}"></script><script>window.__METACUBEXD_CONFIG__ = window.__METACUBEXD_CONFIG__ || { defaultBackendURL: '' }</script><link rel="stylesheet" href="./_nuxt/entry.B_k74qUh.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="./_nuxt/Bmqvg1eR.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/CaGtQlYh.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/QTnfLwEv.js"><script type="module" src="./_nuxt/Bmqvg1eR.js" crossorigin></script><meta name="theme-color" content="#000000"><link rel="icon" type="image/svg+xml" href="favicon.svg"><link rel="apple-touch-icon" href="pwa-192x192.png"><link rel="manifest" href="manifest.webmanifest"><script id="unhead:payload" type="application/json">{"title":"MetaCubeXD"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{appVersion:"1.251.3",mockMode:false,defaultBackendURL:"",i18n:{baseUrl:"",defaultLocale:"en",rootRedirect:"",redirectStatusCode:302,skipSettingLocaleOnNavigate:false,locales:[{code:"en",name:"English",language:""},{code:"zh",name:"简体中文",language:""},{code:"ru",name:"Русский",language:""}],detectBrowserLanguage:{alwaysRedirect:false,cookieCrossOrigin:false,cookieDomain:"",cookieKey:"metacubexd_lang",cookieSecure:false,fallbackLocale:"en",redirectOn:"root",useCookie:true},experimental:{localeDetector:"",typedPages:true,typedOptionsAndMessages:false,alternateLinkCanonicalQueries:true,devCache:false,cacheLifetime:"",stripMessagesPayload:false,preload:false,strictSeo:false,nitroContextDetection:true,httpCacheDuration:10,compactRoutes:false,prerenderMessages:false},domainLocales:{en:{domain:""},zh:{domain:""},ru:{domain:""}}}},app:{baseURL:"./",buildId:"cdc84ee9-d7c0-44a7-96d3-b0595bcf2138",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1780682638349,false]</script></body></html>
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><script type="importmap">{"imports":{"#entry":"./_nuxt/Bmqvg1eR.js"}}</script><title>MetaCubeXD</title><script src="config.js" onerror="window.__METACUBEXD_CONFIG__={defaultBackendURL:''}"></script><script>window.__METACUBEXD_CONFIG__ = window.__METACUBEXD_CONFIG__ || { defaultBackendURL: '' }</script><link rel="stylesheet" href="./_nuxt/entry.B_k74qUh.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="./_nuxt/Bmqvg1eR.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/CaGtQlYh.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/QTnfLwEv.js"><script type="module" src="./_nuxt/Bmqvg1eR.js" crossorigin></script><meta name="theme-color" content="#000000"><link rel="icon" type="image/svg+xml" href="favicon.svg"><link rel="apple-touch-icon" href="pwa-192x192.png"><link rel="manifest" href="manifest.webmanifest"><script id="unhead:payload" type="application/json">{"title":"MetaCubeXD"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{appVersion:"1.251.3",mockMode:false,defaultBackendURL:"",i18n:{baseUrl:"",defaultLocale:"en",rootRedirect:"",redirectStatusCode:302,skipSettingLocaleOnNavigate:false,locales:[{code:"en",name:"English",language:""},{code:"zh",name:"简体中文",language:""},{code:"ru",name:"Русский",language:""}],detectBrowserLanguage:{alwaysRedirect:false,cookieCrossOrigin:false,cookieDomain:"",cookieKey:"metacubexd_lang",cookieSecure:false,fallbackLocale:"en",redirectOn:"root",useCookie:true},experimental:{localeDetector:"",typedPages:true,typedOptionsAndMessages:false,alternateLinkCanonicalQueries:true,devCache:false,cacheLifetime:"",stripMessagesPayload:false,preload:false,strictSeo:false,nitroContextDetection:true,httpCacheDuration:10,compactRoutes:false,prerenderMessages:false},domainLocales:{en:{domain:""},zh:{domain:""},ru:{domain:""}}}},app:{baseURL:"./",buildId:"cdc84ee9-d7c0-44a7-96d3-b0595bcf2138",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1780682638350,false]</script></body></html>
@@ -0,0 +1 @@
d.metacubex.one

Some files were not shown because too many files have changed in this diff Show More