init: atlas cluster v1

This commit is contained in:
v 2026-10-04 13:03:50 +03:00
commit 620231f5da
92 changed files with 8831 additions and 0 deletions

20
.gitignore vendored Normal file
View file

@ -0,0 +1,20 @@
*.kdbx
*.key
*.priv
*.env
.terraform/
*.tfstate*
*.tfstate.backup
*.tfvars
.terraform.lock.hcl
crash.log
*.retry
talos/clusterconfig/
talos/kubeconfig
# Repomix
repomix-output.xml
talos/secrets.yaml
# Local Dynamic State
docs/state/

18
ansible/ansible.cfg Normal file
View file

@ -0,0 +1,18 @@
[defaults]
inventory = inventory.yml
remote_user = vyos
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silent
deprecation_warnings = False
[privilege_escalation]
become = False
[network_cli]
ssh_type = paramiko
[paramiko_connection]
host_key_auto_add = True
record_host_keys = False
look_for_keys = False

View file

@ -0,0 +1,9 @@
{
"forwarders": [
"9.9.9.9",
"1.1.1.1"
],
"forwarderProtocol": "Tls",
"dnssecValidation": true,
"enableQueryLog": true
}

View file

@ -0,0 +1,706 @@
{
"log": {
"disabled": false,
"level": "warn",
"timestamp": false
},
"dns": {
"servers": [
{
"type": "udp",
"tag": "atlas-dns",
"server": "10.0.10.11",
"server_port": 53
},
{
"type": "fakeip",
"tag": "fakeip-server",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
],
"rules": [
{
"action": "reject",
"domain_suffix": "use-application-dns.net"
},
{
"action": "route",
"server": "atlas-dns",
"domain_suffix": [
"svo88.space"
]
},
{
"action": "route",
"server": "fakeip-server",
"rewrite_ttl": 60,
"domain": [
"fakeip.podkop.fyi",
"ip.podkop.fyi"
]
},
{
"domain_suffix": [
"aistudio.google.com",
"chatgpt.com",
"claude.com",
"claude.ai",
"rutracker.cc",
"udemy.com",
"rutracker.org",
"dub.co",
"vyos.io",
"vyos.net",
"opentofu.org",
"fontshare.com",
"patreon.com",
"getoutline.com",
"appflowy.io",
"notebooklm.google",
"patreonusercontent.com",
"anthropic.com",
"media-amazon.com",
"notion.com",
"oxygenforensics.com",
"forensicfocus.com",
"apple.com",
"cdn-apple.com",
"reo.dev",
"mintlify.com",
"omarchy.org",
"omacom.io",
"omakub.org",
"omamix.org",
"dnsv1.com",
"nll.coach",
"office.net",
"static.microsoft",
"office.com",
"msftauth.net",
"pythonhosted.org",
"vscode-unpkg.net",
"notion.site",
"imdb.com",
"supersliv.biz",
"kiraa.ai",
"modrinth.com",
"chunkbase.com",
"minecraft.wiki",
"fabricmc.net",
"vimedocdn.com",
"spotify.com",
"mega.io",
"mega.co.nz",
"mega.nz",
"unlocklocks.com",
"ntc.party",
"martview-forum.com",
"gsmturkey.net",
"makerworld.com",
"audiomack.com",
"beszel.dev",
"ghfast.top",
"virtualizationhowto.com",
"ubuntu.com",
"gnome.org",
"archlinug.org",
"usememos.com",
"rackspace.com",
"pkgbuild.com",
"duckduckgo.com",
"affine.pro",
"docker.io",
"docker.com",
"cloudflarestorage.com",
"production.cloudflare.docker.com",
"zen-browser.app",
"grapheneos.org",
"androidauthority.com",
"elhacker.info",
"neetcode.io",
"speedtest.net",
"trendshift.io",
"kali.org",
"kali.download",
"cdn-perfprod.com",
"commandmasters.com",
"netacad.com",
"microsoft.com",
"apphb.com",
"vsassets.io",
"visualstudio.com",
"opencollective.com",
"simpleicons.org",
"vscode-cdn.net",
"homarr.dev",
"semrush.com",
"cake.com",
"clockify.me",
"amazonaws.com",
"nvidia.com",
"githubusercontent.com",
"trafficmanager.net",
"cookielaw.org",
"adobedtm.com",
"amd.com",
"custhelp.com",
"geforce.com",
"steamcommunity.com",
"steamstatic.com",
"continue.dev",
"pinimg.com",
"xda-developers.com",
"xdaimages.com",
"binance.com",
"itsfoss.com",
"tecmint.com",
"medium.com",
"gravatar.com",
"contextly.com",
"jsdelivr.net",
"stripe.com",
"stripe.network",
"fastcomments.com",
"akamai.net",
"fastly.net",
"sixthtone.com",
"github.dev",
"githubassets.com",
"gitlab.io",
"github.io",
"zimaspace.com",
"polymaker.com",
"qidi3d.com",
"videowise.com",
"kingroon.com",
"qiditech.com",
"mutecmutes.com",
"flow-content.google",
"googleusercontent.com",
"warburton-usa.com",
"ebay.com",
"bambulab.com",
"openqidi.com",
"tinkercad.com",
"yeggi.com",
"ttvnw.net",
"jtvnw.net",
"twitch.tv"
],
"action": "route",
"server": "fakeip-server",
"rewrite_ttl": 60
},
{
"rule_set": [
"Main-telegram-community-ruleset",
"Main-cloudfront-community-ruleset",
"Main-digitalocean-community-ruleset",
"Main-ovh-community-ruleset",
"Main-hetzner-community-ruleset",
"Main-github-community-ruleset",
"Main-hodca-community-ruleset",
"Main-google_play-community-ruleset",
"Main-google_ai-community-ruleset",
"Main-cloudflare-community-ruleset",
"Main-hdrezka-community-ruleset",
"Main-twitter-community-ruleset",
"Main-meta-community-ruleset",
"Main-news-community-ruleset"
],
"action": "route",
"server": "fakeip-server",
"rewrite_ttl": 60
},
{
"domain_suffix": [
"reddit.com",
"ozon.ru",
"vtb.ru",
"vtb.com",
"vtb.tech",
"sbpvtb.ru",
"multicarta.ru",
"nspk.ru",
"xn--90ad4b.xn--p1ai",
"gosuslugi.ru"
],
"action": "route",
"server": "atlas-dns",
"rewrite_ttl": 60
},
{
"rule_set": "direct-russia_inside-community-ruleset",
"action": "route",
"server": "atlas-dns",
"rewrite_ttl": 60
}
],
"final": "atlas-dns",
"strategy": "prefer_ipv4",
"independent_cache": true
},
"inbounds": [
{
"type": "tun",
"tag": "tproxy-in",
"address": [
"172.19.0.1/30"
],
"auto_route": true,
"strict_route": false,
"route_address": [
"198.18.0.0/15",
"91.108.0.0/16",
"149.154.160.0/20"
]
},
{
"type": "direct",
"tag": "dns-in",
"listen": "127.0.0.42",
"listen_port": 53
},
{
"type": "mixed",
"tag": "service-mixed-in",
"listen": "127.0.0.1",
"listen_port": 4534
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct-out"
},
{
"type": "direct",
"tag": "bypass-out"
},
{
"type": "vless",
"tag": "Main-1-out",
"server": "153.76.194.24",
"server_port": 443,
"uuid": "3ce63e78-ada7-4855-add1-15ad5b9ac3bf",
"tls": {
"enabled": true,
"server_name": "ng.w.svo88.space",
"utls": {
"enabled": true,
"fingerprint": "edge"
},
"reality": {
"enabled": true,
"public_key": "csLZ_3t_NswOEGV64PP_tTA9ne_4_R6YD95uprPAfis",
"short_id": "c395"
}
}
},
{
"type": "urltest",
"tag": "Main-urltest-cfg052898-out",
"outbounds": [
"Main-1-out"
],
"url": "https://captive.apple.com",
"interval": "1m",
"tolerance": 50
},
{
"type": "selector",
"tag": "Main-out",
"outbounds": [
"Main-1-out",
"Main-urltest-cfg052898-out"
],
"default": "Main-urltest-cfg052898-out"
}
],
"route": {
"rules": [
{
"action": "sniff",
"inbound": [
"tproxy-in",
"dns-in"
]
},
{
"action": "hijack-dns",
"port": 53
},
{
"action": "hijack-dns",
"protocol": "dns"
},
{
"action": "reject",
"inbound": "tproxy-in",
"protocol": "quic"
},
{
"action": "route",
"inbound": [
"tproxy-in"
],
"outbound": "Main-out",
"domain": "ip.podkop.fyi"
},
{
"action": "route",
"inbound": [
"tproxy-in"
],
"outbound": "Main-out",
"domain_suffix": [
"aistudio.google.com",
"chatgpt.com",
"claude.com",
"claude.ai",
"rutracker.cc",
"udemy.com",
"rutracker.org",
"dub.co",
"vyos.io",
"vyos.net",
"opentofu.org",
"fontshare.com",
"patreon.com",
"getoutline.com",
"appflowy.io",
"notebooklm.google",
"patreonusercontent.com",
"anthropic.com",
"media-amazon.com",
"notion.com",
"oxygenforensics.com",
"forensicfocus.com",
"apple.com",
"cdn-apple.com",
"reo.dev",
"mintlify.com",
"omarchy.org",
"omacom.io",
"omakub.org",
"omamix.org",
"dnsv1.com",
"nll.coach",
"office.net",
"static.microsoft",
"office.com",
"msftauth.net",
"pythonhosted.org",
"vscode-unpkg.net",
"notion.site",
"imdb.com",
"supersliv.biz",
"kiraa.ai",
"modrinth.com",
"chunkbase.com",
"minecraft.wiki",
"fabricmc.net",
"vimedocdn.com",
"spotify.com",
"mega.io",
"mega.co.nz",
"mega.nz",
"unlocklocks.com",
"ntc.party",
"martview-forum.com",
"gsmturkey.net",
"makerworld.com",
"audiomack.com",
"beszel.dev",
"ghfast.top",
"virtualizationhowto.com",
"ubuntu.com",
"gnome.org",
"archlinug.org",
"usememos.com",
"rackspace.com",
"pkgbuild.com",
"duckduckgo.com",
"affine.pro",
"docker.io",
"docker.com",
"cloudflarestorage.com",
"production.cloudflare.docker.com",
"zen-browser.app",
"grapheneos.org",
"androidauthority.com",
"elhacker.info",
"neetcode.io",
"speedtest.net",
"trendshift.io",
"kali.org",
"kali.download",
"cdn-perfprod.com",
"commandmasters.com",
"netacad.com",
"microsoft.com",
"apphb.com",
"vsassets.io",
"visualstudio.com",
"opencollective.com",
"simpleicons.org",
"vscode-cdn.net",
"homarr.dev",
"semrush.com",
"cake.com",
"clockify.me",
"amazonaws.com",
"nvidia.com",
"githubusercontent.com",
"trafficmanager.net",
"cookielaw.org",
"adobedtm.com",
"amd.com",
"custhelp.com",
"geforce.com",
"steamcommunity.com",
"steamstatic.com",
"continue.dev",
"pinimg.com",
"xda-developers.com",
"xdaimages.com",
"binance.com",
"itsfoss.com",
"tecmint.com",
"medium.com",
"gravatar.com",
"contextly.com",
"jsdelivr.net",
"stripe.com",
"stripe.network",
"fastcomments.com",
"akamai.net",
"fastly.net",
"sixthtone.com",
"github.dev",
"githubassets.com",
"gitlab.io",
"github.io",
"zimaspace.com",
"polymaker.com",
"qidi3d.com",
"videowise.com",
"kingroon.com",
"qiditech.com",
"mutecmutes.com",
"flow-content.google",
"googleusercontent.com",
"warburton-usa.com",
"ebay.com",
"bambulab.com",
"openqidi.com",
"tinkercad.com",
"yeggi.com",
"ttvnw.net",
"jtvnw.net",
"twitch.tv"
],
"ip_cidr": [
"194.87.55.26",
"91.108.0.0/16",
"149.154.160.0/20"
],
"rule_set": [
"Main-telegram-community-ruleset",
"Main-cloudfront-community-ruleset",
"Main-digitalocean-community-ruleset",
"Main-ovh-community-ruleset",
"Main-hetzner-community-ruleset",
"Main-github-community-ruleset",
"Main-hodca-community-ruleset",
"Main-google_play-community-ruleset",
"Main-google_ai-community-ruleset",
"Main-cloudflare-community-ruleset",
"Main-hdrezka-community-ruleset",
"Main-twitter-community-ruleset",
"Main-meta-community-ruleset",
"Main-news-community-ruleset"
]
},
{
"action": "route",
"inbound": [
"tproxy-in"
],
"outbound": "bypass-out",
"domain_suffix": [
"reddit.com",
"ozon.ru",
"vtb.ru",
"vtb.com",
"vtb.tech",
"sbpvtb.ru",
"multicarta.ru",
"nspk.ru",
"xn--90ad4b.xn--p1ai",
"gosuslugi.ru"
],
"ip_cidr": [
"195.242.82.0/23",
"185.179.144.0/22",
"212.11.144.0/21"
],
"rule_set": [
"direct-russia_inside-community-ruleset",
"inline-custom-c73fec97249a-ruleset"
]
},
{
"action": "route",
"inbound": "service-mixed-in",
"outbound": "Main-out"
}
],
"rule_set": [
{
"type": "remote",
"tag": "Main-telegram-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/telegram.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-cloudfront-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/cloudfront.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-digitalocean-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/digitalocean.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-ovh-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/ovh.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-hetzner-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/hetzner.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-github-community-ruleset",
"format": "binary",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/github.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-hodca-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/hodca.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-google_play-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/google_play.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-google_ai-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/google_ai.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-cloudflare-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/cloudflare.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-hdrezka-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/hdrezka.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-twitter-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/twitter.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-meta-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/meta.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "Main-news-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/news.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "direct-russia_inside-community-ruleset",
"format": "binary",
"url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/russia_inside.srs",
"update_interval": "1d",
"download_detour": "Main-out"
},
{
"type": "remote",
"tag": "inline-custom-c73fec97249a-ruleset",
"format": "binary",
"url": "https://github.com/lyc8503/sing-box-rules/raw/rule-set-geoip/geoip-ru.srs",
"download_detour": "Main-out",
"update_interval": "1d"
}
],
"final": "direct-out",
"auto_detect_interface": true,
"default_domain_resolver": "atlas-dns",
"default_mark": 134217728
},
"experimental": {
"cache_file": {
"enabled": true,
"path": "/etc/sing-box/cache.db",
"store_fakeip": true
},
"clash_api": {
"external_controller": "0.0.0.0:9090",
"external_ui": "ui"
}
}
}

150
ansible/files/vyos/zapret/config Executable file
View file

@ -0,0 +1,150 @@
# this file is included from init scripts
# change values here
# can help in case /tmp has not enough space
#TMPDIR=/opt/zapret/tmp
# redefine user for zapret daemons. required on Keenetic
WS_USER="daemon"
# override firewall type : iptables,nftables,ipfw
FWTYPE=nftables
# nftables only : set this to 0 to use pre-nat mode. default is post-nat.
# pre-nat mode disables some bypass techniques for forwarded traffic but allows to see client IP addresses in debug log
POSTNAT=1
# options for ipsets
# maximum number of elements in sets. also used for nft sets
SET_MAXELEM=522288
# too low hashsize can cause memory allocation errors on low RAM systems , even if RAM is enough
# too large hashsize will waste lots of RAM
IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM"
# dynamically generate additional ip. $1 = ipset/nfset/table name
#IPSET_HOOK="/etc/zapret.ipset.hook"
# options for ip2net. "-4" or "-6" auto added by ipset create script
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
# options for auto hostlist
# NOTE : in order for these adjustment to work it's required to redirect enough starting packets
# NOTE : set PKT_IN, PKT_OUT variables appropriately
AUTOHOSTLIST_INCOMING_MAXSEQ=4096
AUTOHOSTLIST_RETRANS_MAXSEQ=32768
AUTOHOSTLIST_RETRANS_RESET=1
AUTOHOSTLIST_RETRANS_THRESHOLD=3
AUTOHOSTLIST_FAIL_THRESHOLD=3
AUTOHOSTLIST_FAIL_TIME=60
AUTOHOSTLIST_UDP_IN=1
AUTOHOSTLIST_UDP_OUT=4
# 1 = debug autohostlist positives to ipset/zapret-hosts-auto-debug.log
AUTOHOSTLIST_DEBUGLOG=0
# number of parallel threads for domain list resolves
MDIG_THREADS=30
# EAI_AGAIN retries
MDIG_EAGAIN=10
# delay between EAI_AGAIN retries (ms)
MDIG_EAGAIN_DELAY=500
# ipset/*.sh can compress large lists
GZIP_LISTS=0
# command to reload ip/host lists after update
# comment or leave empty for auto backend selection : ipset or ipfw if present
# on BSD systems with PF no auto reloading happens. you must provide your own command
# set to "-" to disable reload
#LISTS_RELOAD="pfctl -f /etc/pf.conf"
# none,ipset,hostlist,autohostlist
MODE_FILTER=hostlist
# mark bit used by nfqws to prevent loop
DESYNC_MARK=0x40000000
DESYNC_MARK_POSTNAT=0x20000000
# do not pass outgoing traffic to tpws/nfqws not marked with this bit
# this setting allows to write your own rules to limit traffic that should be fooled
# for example based on source IP or incoming interface name
# no filter if not defined
FILTER_MARK=""
NFQWS2_ENABLE=1
# redirect outgoing traffic with connbytes limiter applied in both directions.
NFQWS2_PORTS_TCP="443,2053,2083,2087,2096,8443"
NFQWS2_PORTS_UDP="443,19294-19344,50000-65535"
# PKT_OUT means connbytes dir original
# PKT_IN means connbytes dir reply
NFQWS2_TCP_PKT_OUT="20"
NFQWS2_TCP_PKT_IN="10"
NFQWS2_UDP_PKT_OUT="5"
NFQWS2_UDP_PKT_IN="3"
# redirect outgoing traffic without connbytes limiter and incoming with connbytes limiter
# normally it's needed only for stateless DPI that matches every packet in a single TCP session
# typical example are plain HTTP keep alives
# this mode can be very CPU consuming. enable with care !
NFQWS2_PORTS_TCP_KEEPALIVE=""
NFQWS2_PORTS_UDP_KEEPALIVE=""
# use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir
# hostlist markers are replaced to empty string if MODE_FILTER does not satisfy
# <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list
NFQWS2_OPT="--blob=quic_google:@/opt/zapret2/files/fake/quic_initial_www_google_com.bin --blob=tls_google:@/opt/zapret2/files/fake/tls_clienthello_www_google_com.bin --name=Quic --filter-udp=443 --filter-l7=quic --hostlist=/opt/zapret2/ipset/zapret-hosts-user.txt --payload=quic_initial --lua-desync=fake:blob=quic_google:repeats=6 --new --name=Google --filter-tcp=443 --filter-l7=tls --hostlist=/opt/zapret2/ipset/zapret-hosts-user.txt --payload=tls_client_hello --lua-desync=hostfakesplit:repeats=4:tcp_ts=-600000:host=www.google.com:ip_id=zero"
DISABLE_CUSTOM=1
# FlowOffload mode : donttouch,none,software,hardware
FLOWOFFLOAD=none
# openwrt: specify networks to be treated as LAN. default is "lan"
#OPENWRT_LAN="lan lan2 lan3"
# openwrt: specify networks to be treated as WAN. default wans are interfaces with default route
#OPENWRT_WAN4="wan vpn"
#OPENWRT_WAN6="wan6 vpn6"
# for routers based on desktop linux and macos. has no effect in openwrt.
# CHOOSE LAN and optinally WAN/WAN6 NETWORK INTERFACES
# or leave them commented if its not router
# it's possible to specify multiple interfaces like this : IFACE_WAN="eth0 eth1 eth2"
# if IFACE_WAN6 is not defined it take the value of IFACE_WAN
#IFACE_WAN=eth1
#IFACE_WAN6="ipsec0 wireguard0 he_net"
# should start/stop command of init scripts apply firewall rules ?
# not applicable to openwrt with firewall3+iptables
INIT_APPLY_FW=1
# firewall apply hooks
#INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret2.hook.pre_up"
#INIT_FW_POST_UP_HOOK="/etc/firewall.zapret2.hook.post_up"
#INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret2.hook.pre_down"
#INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret2.hook.post_down"
# do not work with ipv4
DISABLE_IPV4=0
# do not work with ipv6
DISABLE_IPV6=1
# drop icmp time exceeded messages for nfqws tampered connections
# in POSTNAT mode this can interfere with default mtr/traceroute in tcp or udp mode. use source port not redirected to nfqws
# set to 0 if you are not expecting connection breakage due to icmp in response to TCP SYN or UDP
FILTER_TTL_EXPIRED_ICMP=1
# select which init script will be used to get ip or host list
# possible values : get_user.sh get_antizapret.sh get_combined.sh get_reestr.sh get_hostlist.sh
# comment if not required
#GETLIST=get_antizapret_domains.sh
DAEMON_LOG_ENABLE=0
DAEMON_LOG_SIZE_MAX=2000
DAEMON_LOG_FILE="/tmp/zapret2+<DAEMON_NAME>+<DAEMON_IDNUM>+<DAEMON_CFGNAME>.log"

View file

@ -0,0 +1,198 @@
cdn.youtube.com
fonts.googleapis.com
fonts.gstatic.com
ggpht.com
googleapis.com
googleusercontent.com
googlevideo.com
i.ytimg.com
i9.ytimg.com
jnn-pa.googleapis.com
kids.youtube.com
m.youtube.com
manifest.googlevideo.com
music.youtube.com
nhacmp3youtube.com
returnyoutubedislikeapi.com
s.ytimg.com
signaler-pa.youtube.com
studio.youtube.com
tv.youtube.com
wide-youtube.l.google.com
withyoutube.com
youtu.be
youtube.com
youtube.googleapis.com
youtubeeducation.com
youtubeembeddedplayer.googleapis.com
youtubefanfest.com
youtubegaming.com
youtubei.googleapis.com
youtubekids.com
youtubemobilesupport.com
youtube-nocookie.com
youtube-ui.l.google.com
yt.be
yt3.ggpht.com
yt3.googleusercontent.com
yt4.ggpht.com
ytimg.com
ytimg.l.google.com
yting.com
yt-video-upload.l.google.com
youtube.ae
youtube.al
youtube.am
youtube.at
youtube.az
youtube.ba
youtube.be
youtube.bg
youtube.bh
youtube.bo
youtube.by
youtube.ca
youtube.cat
youtube.ch
youtube.cl
youtube.co
youtube.co.ae
youtube.co.at
youtube.co.cr
youtube.co.hu
youtube.co.id
youtube.co.il
youtube.co.in
youtube.co.jp
youtube.co.ke
youtube.co.kr
youtube.co.ma
youtube.co.nz
youtube.co.th
youtube.co.tz
youtube.co.ug
youtube.co.uk
youtube.co.ve
youtube.co.za
youtube.co.zw
youtube.com.ar
youtube.com.au
youtube.com.az
youtube.com.bd
youtube.com.bh
youtube.com.bo
youtube.com.br
youtube.com.by
youtube.com.co
youtube.com.do
youtube.com.ec
youtube.com.ee
youtube.com.eg
youtube.com.es
youtube.com.gh
youtube.com.gr
youtube.com.gt
youtube.com.hk
youtube.com.hn
youtube.com.hr
youtube.com.jm
youtube.com.jo
youtube.com.kw
youtube.com.lb
youtube.com.lv
youtube.com.ly
youtube.com.mk
youtube.com.mt
youtube.com.mx
youtube.com.my
youtube.com.ng
youtube.com.ni
youtube.com.om
youtube.com.pa
youtube.com.pe
youtube.com.ph
youtube.com.pk
youtube.com.pt
youtube.com.py
youtube.com.qa
youtube.com.ro
youtube.com.sa
youtube.com.sg
youtube.com.sv
youtube.com.tn
youtube.com.tr
youtube.com.tw
youtube.com.ua
youtube.com.uy
youtube.com.ve
youtube.cr
youtube.cz
youtube.de
youtube.dk
youtube.ee
youtube.es
youtube.fi
youtube.fr
youtube.ge
youtube.gr
youtube.gt
youtube.hk
youtube.hr
youtube.hu
youtube.ie
youtube.in
youtube.iq
youtube.is
youtube.it
youtube.jo
youtube.jp
youtube.kr
youtube.kz
youtube.la
youtube.lk
youtube.lt
youtube.lu
youtube.lv
youtube.ly
youtube.ma
youtube.md
youtube.me
youtube.mk
youtube.mn
youtube.mx
youtube.my
youtube.ng
youtube.ni
youtube.nl
youtube.no
youtube.pa
youtube.pe
youtube.ph
youtube.pk
youtube.pl
youtube.pr
youtube.pt
youtube.qa
youtube.ro
youtube.rs
youtube.ru
youtube.sa
youtube.se
youtube.sg
youtube.si
youtube.sk
youtube.sn
youtube.soy
youtube.sv
youtube.tn
youtube.tv
youtube.ua
youtube.ug
youtube.uy
youtube.vn
youtubego.co.id
youtubego.co.in
youtubego.com
youtubego.com.br
youtubego.id
youtubego.in

View file

@ -0,0 +1,198 @@
cdn.youtube.com
fonts.googleapis.com
fonts.gstatic.com
ggpht.com
googleapis.com
googleusercontent.com
googlevideo.com
i.ytimg.com
i9.ytimg.com
jnn-pa.googleapis.com
kids.youtube.com
m.youtube.com
manifest.googlevideo.com
music.youtube.com
nhacmp3youtube.com
returnyoutubedislikeapi.com
s.ytimg.com
signaler-pa.youtube.com
studio.youtube.com
tv.youtube.com
wide-youtube.l.google.com
withyoutube.com
youtu.be
youtube.com
youtube.googleapis.com
youtubeeducation.com
youtubeembeddedplayer.googleapis.com
youtubefanfest.com
youtubegaming.com
youtubei.googleapis.com
youtubekids.com
youtubemobilesupport.com
youtube-nocookie.com
youtube-ui.l.google.com
yt.be
yt3.ggpht.com
yt3.googleusercontent.com
yt4.ggpht.com
ytimg.com
ytimg.l.google.com
yting.com
yt-video-upload.l.google.com
youtube.ae
youtube.al
youtube.am
youtube.at
youtube.az
youtube.ba
youtube.be
youtube.bg
youtube.bh
youtube.bo
youtube.by
youtube.ca
youtube.cat
youtube.ch
youtube.cl
youtube.co
youtube.co.ae
youtube.co.at
youtube.co.cr
youtube.co.hu
youtube.co.id
youtube.co.il
youtube.co.in
youtube.co.jp
youtube.co.ke
youtube.co.kr
youtube.co.ma
youtube.co.nz
youtube.co.th
youtube.co.tz
youtube.co.ug
youtube.co.uk
youtube.co.ve
youtube.co.za
youtube.co.zw
youtube.com.ar
youtube.com.au
youtube.com.az
youtube.com.bd
youtube.com.bh
youtube.com.bo
youtube.com.br
youtube.com.by
youtube.com.co
youtube.com.do
youtube.com.ec
youtube.com.ee
youtube.com.eg
youtube.com.es
youtube.com.gh
youtube.com.gr
youtube.com.gt
youtube.com.hk
youtube.com.hn
youtube.com.hr
youtube.com.jm
youtube.com.jo
youtube.com.kw
youtube.com.lb
youtube.com.lv
youtube.com.ly
youtube.com.mk
youtube.com.mt
youtube.com.mx
youtube.com.my
youtube.com.ng
youtube.com.ni
youtube.com.om
youtube.com.pa
youtube.com.pe
youtube.com.ph
youtube.com.pk
youtube.com.pt
youtube.com.py
youtube.com.qa
youtube.com.ro
youtube.com.sa
youtube.com.sg
youtube.com.sv
youtube.com.tn
youtube.com.tr
youtube.com.tw
youtube.com.ua
youtube.com.uy
youtube.com.ve
youtube.cr
youtube.cz
youtube.de
youtube.dk
youtube.ee
youtube.es
youtube.fi
youtube.fr
youtube.ge
youtube.gr
youtube.gt
youtube.hk
youtube.hr
youtube.hu
youtube.ie
youtube.in
youtube.iq
youtube.is
youtube.it
youtube.jo
youtube.jp
youtube.kr
youtube.kz
youtube.la
youtube.lk
youtube.lt
youtube.lu
youtube.lv
youtube.ly
youtube.ma
youtube.md
youtube.me
youtube.mk
youtube.mn
youtube.mx
youtube.my
youtube.ng
youtube.ni
youtube.nl
youtube.no
youtube.pa
youtube.pe
youtube.ph
youtube.pk
youtube.pl
youtube.pr
youtube.pt
youtube.qa
youtube.ro
youtube.rs
youtube.ru
youtube.sa
youtube.se
youtube.sg
youtube.si
youtube.sk
youtube.sn
youtube.soy
youtube.sv
youtube.tn
youtube.tv
youtube.ua
youtube.ug
youtube.uy
youtube.vn
youtubego.co.id
youtubego.co.in
youtubego.com
youtubego.com.br
youtubego.id
youtubego.in

View file

@ -0,0 +1,10 @@
127.0.0.0/8
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
169.254.0.0/16
100.64.0.0/10
::1
fc00::/7
fe80::/10
153.76.194.24

View file

@ -0,0 +1,25 @@
#!/bin/sh
set -e
# 1. Раскладываем дефолты (скрипты и блобы)
[ -d /opt/zapret2/lua ] || cp -r /opt/zapret2/lua.dist /opt/zapret2/lua
[ -d /opt/zapret2/files/fake ] || cp -r /opt/zapret2/files/fake.dist /opt/zapret2/files/fake
# 2. Подкладываем конфиг и списки хостов
cp -f /etc/zapret/config /opt/zapret2/config
mkdir -p /opt/zapret2/ipset
cp -rf /etc/zapret/ipset/* /opt/zapret2/ipset/ 2>/dev/null || true
# 3. Стартуем родной zapret2 (он сам настроит nftables и поднимет nfqws2)
/opt/zapret2/init.d/sysv/zapret2 start
# 4. Обработка остановки контейнера
cleanup() {
/opt/zapret2/init.d/sysv/zapret2 stop || true
exit 0
}
trap cleanup TERM INT EXIT
# Держим контейнер в фоне
sleep infinity &
wait $!

34
ansible/inventory.yml Normal file
View file

@ -0,0 +1,34 @@
all:
children:
hypervisors:
hosts:
pve0:
ansible_host: 192.168.1.177
ansible_user: root
ansible_python_interpreter: /usr/bin/python3
routers:
hosts:
vyos_router:
ansible_host: 192.168.1.2
ansible_user: vyos
ansible_network_os: vyos.vyos.vyos
ansible_connection: network_cli
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
ansible_paramiko_host_key_auto_add: true
tier0_lxc:
hosts:
dns0:
ansible_host: 10.0.10.11
vault0:
ansible_host: 10.0.10.12
git0:
ansible_host: 10.0.10.13
storage0:
ansible_host: 10.0.10.14
registry0:
ansible_host: 10.0.10.15
vars:
ansible_user: root
ansible_ssh_common_args: '-J vyos@192.168.1.2'

View file

@ -0,0 +1,143 @@
---
- name: Configure Proxmox Host Day-0 Integration
hosts: pve0
gather_facts: false
tasks:
- name: Ensure nfsd kernel module is loaded on PVE host
ansible.builtin.command: modprobe nfsd
changed_when: false
- name: Persist nfsd module in /etc/modules
ansible.builtin.lineinfile:
path: /etc/modules
line: "nfsd"
state: present
- name: Ensure host ZFS directories exist with 0777 permissions
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "0"
group: "0"
mode: "0777"
loop:
- /terra/media
- /terra/torrents
- /terra/vault
- /terra/registry
- /terra/models
# ================= LXC 104 (storage0 - NAS) =================
- name: Check existing mount points for LXC 104
ansible.builtin.command: >
grep -q "^{{ item.id }}: {{ item.src }},mp={{ item.dest }}" /etc/pve/lxc/104.conf
register: check_104
changed_when: false
failed_when: false
loop:
- { id: 'mp0', src: '/terra/media', dest: '/media' }
- { id: 'mp1', src: '/terra/torrents', dest: '/torrents' }
- { id: 'mp2', src: '/terra/vault', dest: '/backups' }
- { id: 'mp3', src: '/terra/models', dest: '/models' }
- name: Apply mount points for LXC 104
ansible.builtin.command: pct set 104 -{{ item.item.id }} {{ item.item.src }},mp={{ item.item.dest }}
when: item.rc != 0
register: apply_104
loop: "{{ check_104.results }}"
loop_control:
label: "{{ item.item.id }}"
- name: Configure AppArmor unconfined for LXC 104 kernel NFS
ansible.builtin.lineinfile:
path: /etc/pve/lxc/104.conf
line: "lxc.apparmor.profile: unconfined"
state: present
register: apparmor_104
- name: Reboot LXC 104 to apply mounts and AppArmor
ansible.builtin.command: pct reboot 104
when: apply_104.changed or apparmor_104.changed
# ================= LXC 105 (registry0 - Zot OCI) =================
- name: Check existing mount points for LXC 105
ansible.builtin.command: >
grep -q "^{{ item.id }}: {{ item.src }},mp={{ item.dest }}" /etc/pve/lxc/105.conf
register: check_105
changed_when: false
failed_when: false
loop:
- { id: 'mp0', src: '/terra/registry', dest: '/mnt/registry' }
- name: Apply mount points for LXC 105
ansible.builtin.command: pct set 105 -{{ item.item.id }} {{ item.item.src }},mp={{ item.item.dest }}
when: item.rc != 0
register: apply_105
loop: "{{ check_105.results }}"
loop_control:
label: "{{ item.item.id }}"
- name: Reboot LXC 105 to apply pending mount points
ansible.builtin.command: pct reboot 105
when: apply_105.changed
# ================= DAY-0 OS IMAGES & TEMPLATES (SSOT) =================
- name: Update pveam repository catalog
ansible.builtin.command: pveam update
changed_when: false
- name: Query latest Debian 12 standard template name
ansible.builtin.shell: pveam available --section system | awk '/debian-12-standard/ {print $2}' | tail -n 1
register: pve_debian_tmpl
changed_when: false
- name: Ensure Debian 12 template is downloaded and symlinked
ansible.builtin.shell: |
set -e
CACHE_DIR="/var/lib/vz/template/cache"
TARGET_LINK="${CACHE_DIR}/debian-12-standard_amd64.tar.zst"
if [ ! -f "${TARGET_LINK}" ]; then
ACTUAL_FILE=$(ls -1 ${CACHE_DIR}/debian-12-standard_*_amd64.tar.zst 2>/dev/null | tail -n 1 || true)
if [ -z "${ACTUAL_FILE}" ]; then
pveam download local {{ pve_debian_tmpl.stdout }}
ACTUAL_FILE="${CACHE_DIR}/{{ pve_debian_tmpl.stdout }}"
fi
ln -sf "${ACTUAL_FILE}" "${TARGET_LINK}"
echo "DOWNLOADED"
fi
register: debian_download_status
changed_when: "'DOWNLOADED' in debian_download_status.stdout"
- name: Query latest Alpine template name
ansible.builtin.shell: pveam available --section system | awk '/alpine-3\.[0-9]+-default/ {print $2}' | tail -n 1
register: pve_alpine_tmpl
changed_when: false
- name: Ensure Alpine 3.24 template is downloaded and symlinked
ansible.builtin.shell: |
set -e
CACHE_DIR="/var/lib/vz/template/cache"
TARGET_LINK="${CACHE_DIR}/alpine-3.24-default_20260714_amd64.tar.xz"
if [ ! -f "${TARGET_LINK}" ]; then
ACTUAL_FILE=$(ls -1 ${CACHE_DIR}/alpine-*-default_*_amd64.tar.xz 2>/dev/null | tail -n 1 || true)
if [ -z "${ACTUAL_FILE}" ]; then
pveam download local {{ pve_alpine_tmpl.stdout }}
ACTUAL_FILE="${CACHE_DIR}/{{ pve_alpine_tmpl.stdout }}"
fi
ln -sf "${ACTUAL_FILE}" "${TARGET_LINK}"
echo "DOWNLOADED"
fi
register: alpine_download_status
changed_when: "'DOWNLOADED' in alpine_download_status.stdout"
- name: Check if Talos Linux ISO exists
ansible.builtin.stat:
path: /var/lib/vz/template/iso/talos-amd64.iso
register: talos_iso
- name: Ensure Talos Linux ISO (v1.14.0) is present
ansible.builtin.get_url:
url: https://github.com/siderolabs/talos/releases/download/v1.14.0/metal-amd64.iso
dest: /var/lib/vz/template/iso/talos-amd64.iso
mode: "0644"
when: not talos_iso.stat.exists

View file

@ -0,0 +1,173 @@
---
- name: Configure VyOS Network and Sing-Box
hosts: vyos_router
gather_facts: false
tasks:
# --- 1. ДОСТАВКА КОНФИГА ЧЕРЕЗ NATIVE SSH ---
- name: Ensure sing-box directory exists
ansible.builtin.file:
path: /config/sing-box
state: directory
owner: vyos
group: vyattacfg
mode: "0755"
vars:
ansible_connection: ssh
become: true
- name: Deploy sing-box config via native copy
ansible.builtin.copy:
src: "{{ playbook_dir }}/../files/vyos/sing-box/config.json"
dest: "/config/sing-box/config.json"
owner: vyos
group: vyattacfg
mode: "0644"
vars:
ansible_connection: ssh
become: true
- name: Persist route_localnet for DNS DNAT
ansible.posix.sysctl:
name: "{{ item }}"
value: "1"
state: present
sysctl_file: /etc/sysctl.d/99-singbox.conf
reload: true
loop:
- net.ipv4.conf.all.route_localnet
- net.ipv4.conf.eth0.route_localnet
- net.ipv4.conf.eth3.route_localnet
vars:
ansible_connection: ssh
become: true
- name: Ensure zapret directory exists
ansible.builtin.file:
path: /config/zapret
state: directory
owner: vyos
group: vyattacfg
mode: "0755"
vars:
ansible_connection: ssh
become: true
- name: Deploy zapret directory via native copy
ansible.builtin.copy:
src: "{{ playbook_dir }}/../files/vyos/zapret/"
dest: "/config/zapret/"
owner: vyos
group: vyattacfg
mode: "0755"
vars:
ansible_connection: ssh
become: true
# --- 2. PULL ОБРАЗА (OP-MODE) ---
- name: Check existing container images
vyos.vyos.vyos_command:
commands:
- show container image
register: vyos_images
- name: Pull sing-box image if not present (Op Mode)
vyos.vyos.vyos_command:
commands:
- add container image ghcr.io/sagernet/sing-box:latest
when: "'sing-box' not in vyos_images.stdout[0]"
# --- 3. СЕТЬ, DNAT И КОНТЕЙНЕР (CONFIG MODE) ---
- name: Apply base network, DNS DNAT, and sing-box container
vyos.vyos.vyos_config:
lines:
- set interfaces ethernet eth0 address '192.168.1.2/24'
- set interfaces ethernet eth0 description 'WAN_TRUNK'
- set interfaces ethernet eth2 address '10.0.10.1/24'
- set interfaces ethernet eth2 description 'TIER0'
- set interfaces ethernet eth1 address '10.0.20.1/24'
- set interfaces ethernet eth1 description 'K8S'
- set interfaces ethernet eth3 address '10.0.30.1/24'
- set interfaces ethernet eth3 description 'LAN_CLIENTS'
- set interfaces wireguard wg0 address '10.8.0.2/24'
- set interfaces wireguard wg0 description 'TUNNEL_TO_VPS_FIN21'
- set interfaces wireguard wg0 private-key 'mP869Eu59vE0hlvFHoGjgKT+j6UZgxIYuojZCbGWjVo='
- set interfaces wireguard wg0 peer fin21 address '153.76.194.24'
- set interfaces wireguard wg0 peer fin21 port '51820'
- set interfaces wireguard wg0 peer fin21 public-key 'tS0m/x/dVCs8+N+mtSH3PENWpEUymJDiuPNWrCySMWY='
- set interfaces wireguard wg0 peer fin21 allowed-ips '10.8.0.0/24'
- set interfaces wireguard wg0 peer fin21 persistent-keepalive '25'
- set protocols static route 0.0.0.0/0 next-hop 192.168.1.1
- set nat source rule 100 outbound-interface name 'eth0'
- set nat source rule 100 source address '0.0.0.0/0'
- set nat source rule 100 translation address 'masquerade'
- set nat source rule 110 description 'NAT_WG0_TO_LAN'
- set nat source rule 110 outbound-interface name 'eth3'
- set nat source rule 110 source address '10.8.0.0/24'
- set nat source rule 110 translation address 'masquerade'
# DNAT перехват DNS на Sing-Box
- set firewall group interface-group LAN interface 'eth0'
- set firewall group interface-group LAN interface 'eth3'
- set nat destination rule 53 description 'REDIRECT_DNS_TO_SINGBOX'
- set nat destination rule 53 destination port '53'
- set nat destination rule 53 inbound-interface group 'LAN'
- set nat destination rule 53 protocol 'tcp_udp'
- set nat destination rule 53 translation address '127.0.0.42'
- set nat destination rule 53 translation port '53'
# Железный фаервол изоляции wg0 (периметр VPS)
# 1. Транзитный трафик (Forward): разрешаем ТОЛЬКО 10.0.30.200:80,443, остальное дропаем
- set firewall ipv4 forward filter rule 10 action 'accept'
- set firewall ipv4 forward filter rule 10 state established
- set firewall ipv4 forward filter rule 10 state related
- set firewall ipv4 forward filter rule 20 action 'accept'
- set firewall ipv4 forward filter rule 20 inbound-interface name 'wg0'
- set firewall ipv4 forward filter rule 20 destination address '10.0.30.200'
- set firewall ipv4 forward filter rule 20 destination port '80,443'
- set firewall ipv4 forward filter rule 20 protocol 'tcp'
- set firewall ipv4 forward filter rule 30 action 'drop'
- set firewall ipv4 forward filter rule 30 inbound-interface name 'wg0'
# 2. Трафик к самому роутеру (Input): запрещаем дедику доступ к сокетам VyOS (SSH и т.д.)
- set firewall ipv4 input filter rule 10 action 'accept'
- set firewall ipv4 input filter rule 10 state established
- set firewall ipv4 input filter rule 10 state related
- set firewall ipv4 input filter rule 20 action 'drop'
- set firewall ipv4 input filter rule 20 inbound-interface name 'wg0'
# DHCP
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 subnet-id '20'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 option default-router '10.0.20.1'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 option name-server '10.0.10.11'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 range 0 start '10.0.20.100'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 range 0 stop '10.0.20.150'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 static-mapping talos-master-1 ip-address '10.0.20.10'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 static-mapping talos-master-1 mac 'bc:24:11:71:b6:82'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 static-mapping talos-worker-1 ip-address '10.0.20.11'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 static-mapping talos-worker-1 mac 'bc:24:11:12:c5:1b'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 static-mapping talos-worker-2 ip-address '10.0.20.12'
- set service dhcp-server shared-network-name K8S subnet 10.0.20.0/24 static-mapping talos-worker-2 mac 'bc:24:11:ef:1a:d4'
- set service dhcp-server shared-network-name LAN_CLIENTS subnet 10.0.30.0/24 subnet-id '30'
- set service dhcp-server shared-network-name LAN_CLIENTS subnet 10.0.30.0/24 option default-router '10.0.30.1'
- set service dhcp-server shared-network-name LAN_CLIENTS subnet 10.0.30.0/24 option name-server '10.0.10.11'
- set service dhcp-server shared-network-name LAN_CLIENTS subnet 10.0.30.0/24 range 0 start '10.0.30.100'
- set service dhcp-server shared-network-name LAN_CLIENTS subnet 10.0.30.0/24 range 0 stop '10.0.30.150'
# Контейнер Sing-Box
- set container name sing-box image 'ghcr.io/sagernet/sing-box:latest'
- set container name sing-box allow-host-networks
- set container name sing-box capability 'net-admin'
- set container name sing-box capability 'net-raw'
- set container name sing-box restart 'always'
- set container name sing-box memory '256'
- set container name sing-box command 'run'
- set container name sing-box arguments '-c /etc/sing-box/config.json'
- set container name sing-box volume singbox-cfg destination '/etc/sing-box'
- set container name sing-box volume singbox-cfg source '/config/sing-box'
# Контейнер Zapret2
- set container name zapret2 image 'docker.io/vernette/ss-zapret2:latest'
- set container name zapret2 allow-host-networks
- set container name zapret2 capability 'net-admin'
- set container name zapret2 capability 'net-raw'
- set container name zapret2 restart 'always'
- set container name zapret2 memory '256'
- set container name zapret2 entrypoint '/bin/sh'
- set container name zapret2 command '/etc/zapret/run.sh'
- set container name zapret2 volume zapret-cfg destination '/etc/zapret'
- set container name zapret2 volume zapret-cfg source '/config/zapret'
save: true

View file

@ -0,0 +1,132 @@
---
- name: Deploy Zot OCI Registry
hosts: registry0
gather_facts: false
tasks:
- name: Create zot group
ansible.builtin.group:
name: zot
state: present
- name: Create zot user
ansible.builtin.user:
name: zot
group: zot
system: true
create_home: false
shell: /usr/sbin/nologin
state: present
- name: Ensure Zot configuration directory exists
ansible.builtin.file:
path: /etc/zot
state: directory
owner: zot
group: zot
mode: "0755"
- name: Download Zot binary
ansible.builtin.get_url:
url: https://github.com/project-zot/zot/releases/download/v2.1.1/zot-linux-amd64
dest: /usr/local/bin/zot
owner: root
group: root
mode: "0755"
notify: Restart Zot
- name: Generate Zot configuration
ansible.builtin.copy:
dest: /etc/zot/config.json
owner: zot
group: zot
mode: "0644"
content: |
{
"distSpecVersion": "1.1.0-dev",
"storage": {
"rootDirectory": "/mnt/registry",
"gc": true
},
"http": {
"address": "0.0.0.0",
"port": "5000"
},
"extensions": {
"search": {
"enable": true
},
"ui": {
"enable": true
},
"sync": {
"enable": true,
"registries": [
{
"urls": ["https://registry-1.docker.io"],
"onDemand": true,
"tlsVerify": true,
"content": [
{
"prefix": "**",
"destination": "/docker.io"
}
]
},
{
"urls": ["https://ghcr.io"],
"onDemand": true,
"tlsVerify": true,
"content": [
{
"prefix": "**",
"destination": "/ghcr.io"
}
]
},
{
"urls": ["https://registry.k8s.io"],
"onDemand": true,
"tlsVerify": true,
"content": [
{
"prefix": "**",
"destination": "/k8s.io"
}
]
},
{
"urls": ["https://quay.io"],
"onDemand": true,
"tlsVerify": true,
"content": [{"prefix": "**", "destination": "/quay.io"}]
}
]
}
}
}
notify: Restart Zot
- name: Create Zot systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/zot.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Zot OCI Registry
After=network.target
[Service]
User=zot
Group=zot
ExecStart=/usr/local/bin/zot serve /etc/zot/config.json
Restart=always
[Install]
WantedBy=multi-user.target
notify: Restart Zot
- name: Enable and start Zot service
ansible.builtin.systemd:
name: zot
enabled: true
state: started
daemon_reload: true
handlers:
- name: Restart Zot
ansible.builtin.systemd:
name: zot
state: restarted

View file

@ -0,0 +1,132 @@
---
- name: Deploy and Configure Technitium DNS on Debian 12
hosts: dns0
gather_facts: false
tasks:
- name: Update APT cache and install dependencies
ansible.builtin.apt:
name:
- curl
- tar
- libicu72
state: present
update_cache: true
- name: Download dotnet-install script
ansible.builtin.get_url:
url: https://dot.net/v1/dotnet-install.sh
dest: /tmp/dotnet-install.sh
mode: "0755"
- name: Install .NET 10 Runtime
ansible.builtin.command:
cmd: /tmp/dotnet-install.sh --runtime aspnetcore --channel 10.0 --install-dir /usr/share/dotnet
creates: /usr/share/dotnet/shared/Microsoft.NETCore.App/10.0.*
- name: Create dotnet symlink
ansible.builtin.file:
src: /usr/share/dotnet/dotnet
dest: /usr/bin/dotnet
state: link
- name: Ensure Technitium DNS directory exists
ansible.builtin.file:
path: /opt/technitium/dns
state: directory
mode: "0755"
- name: Download and extract Technitium DNS
ansible.builtin.unarchive:
src: https://download.technitium.com/dns/DnsServerPortable.tar.gz
dest: /opt/technitium/dns
remote_src: true
notify: Restart Technitium
- name: Create systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/technitium-dns.service
mode: "0644"
content: |
[Unit]
Description=Technitium DNS Server
After=network.target
[Service]
ExecStart=/usr/bin/dotnet /opt/technitium/dns/DnsServerApp.dll
WorkingDirectory=/opt/technitium/dns
Restart=always
SyslogIdentifier=technitium-dns
[Install]
WantedBy=multi-user.target
notify: Restart Technitium
- name: Enable and start Technitium DNS service
ansible.builtin.systemd:
name: technitium-dns
enabled: true
state: started
daemon_reload: true
- name: Wait for Technitium HTTP API port 5380 to become available
ansible.builtin.wait_for:
port: 5380
host: 127.0.0.1
state: started
timeout: 20
# ================= ДЕКЛАРАТИВНЫЕ ЗОНЫ И ЗАПИСИ (SSOT) =================
- name: Authenticate with Technitium API to get session token
ansible.builtin.uri:
url: "http://127.0.0.1:5380/api/user/login?user=admin&pass={{ lookup('env', 'TECHNITIUM_PASSWORD') | default('admin', true) }}"
method: GET
status_code: [200]
register: dns_auth
failed_when: dns_auth.json.status != 'ok'
- name: Apply Technitium server settings from JSON manifest
ansible.builtin.uri:
url: "http://127.0.0.1:5380/api/settings/set?token={{ dns_auth.json.token }}"
method: POST
body_format: json
body: "{{ lookup('file', playbook_dir + '/../files/technitium/settings.json') }}"
status_code: [200]
register: settings_res
failed_when: settings_res.json.status != 'ok'
- name: Check installed DNS apps
ansible.builtin.uri:
url: "http://127.0.0.1:5380/api/apps/list?token={{ dns_auth.json.token }}"
method: GET
status_code: [200]
register: installed_apps
- name: Download and install Query Logs (Sqlite) from store
ansible.builtin.uri:
url: "http://127.0.0.1:5380/api/apps/downloadAndInstall?token={{ dns_auth.json.token }}&name=Query%20Logs%20(Sqlite)&url=https://download.technitium.com/dns/apps/QueryLogsSqliteApp-v9.1.zip"
timeout: 120
method: GET
status_code: [200]
when: "'Query Logs (Sqlite)' not in (installed_apps.json.response.apps | default([]) | map(attribute='name') | list)"
- name: Ensure authoritative zone svo88.space exists
ansible.builtin.uri:
url: "http://127.0.0.1:5380/api/zones/create?token={{ dns_auth.json.token }}&zone=svo88.space&type=Primary"
method: GET
status_code: [200]
register: zone_create
failed_when: zone_create.json.status != 'ok' and 'already exists' not in zone_create.json.errorMessage | default('')
- name: Ensure wildcard A-record *.svo88.space points to Traefik VIP (10.0.30.200)
ansible.builtin.uri:
url: "http://127.0.0.1:5380/api/zones/records/add?token={{ dns_auth.json.token }}&zone=svo88.space&domain=*.svo88.space&type=A&ipAddress=10.0.30.200&overwrite=true"
method: GET
status_code: [200]
register: record_create
failed_when: record_create.json.status != 'ok'
handlers:
- name: Restart Technitium
ansible.builtin.systemd:
name: technitium-dns
state: restarted

View file

@ -0,0 +1,102 @@
---
- name: Deploy OpenBao Vault
hosts: vault0
gather_facts: false
tasks:
- name: Update APT cache and install dependencies
ansible.builtin.apt:
name:
- curl
- gpg
- apt-transport-https
- unzip
state: present
update_cache: true
- name: Create bao group
ansible.builtin.group:
name: bao
state: present
- name: Create bao user
ansible.builtin.user:
name: bao
group: bao
system: true
create_home: false
shell: /usr/sbin/nologin
state: present
- name: Ensure bao configuration directory exists
ansible.builtin.file:
path: /etc/bao
state: directory
owner: bao
group: bao
mode: "0755"
- name: Ensure bao data directory exists
ansible.builtin.file:
path: /var/lib/bao/data
state: directory
owner: bao
group: bao
mode: "0750"
- name: Download and extract OpenBao binary
ansible.builtin.unarchive:
src: https://github.com/openbao/openbao/releases/download/v2.0.1/bao_2.0.1_Linux_x86_64.tar.gz
dest: /usr/local/bin
remote_src: true
creates: /usr/local/bin/bao
owner: root
group: root
mode: "0755"
notify: Restart OpenBao
- name: Generate OpenBao configuration
ansible.builtin.copy:
dest: /etc/bao/vault.hcl
owner: bao
group: bao
mode: "0644"
content: |
disable_mlock = true
ui = true
storage "file" {
path = "/var/lib/bao/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
notify: Restart OpenBao
- name: Create OpenBao systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/openbao.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=OpenBao Secrets Management
After=network.target
[Service]
User=bao
Group=bao
ExecStart=/usr/local/bin/bao server -config=/etc/bao/vault.hcl
Restart=on-failure
[Install]
WantedBy=multi-user.target
notify: Restart OpenBao
- name: Enable and start OpenBao service
ansible.builtin.systemd:
name: openbao
enabled: true
state: started
daemon_reload: true
- name: Wait for OpenBao port 8200 to become available
ansible.builtin.wait_for:
port: 8200
host: 127.0.0.1
state: started
timeout: 30
handlers:
- name: Restart OpenBao
ansible.builtin.systemd:
name: openbao
state: restarted

View file

@ -0,0 +1,131 @@
---
- name: Deploy Forgejo GitOps Server
hosts: git0
gather_facts: false
tasks:
- name: Update APT cache and install dependencies
ansible.builtin.apt:
name:
- git
- curl
state: present
update_cache: true
- name: Create git group
ansible.builtin.group:
name: git
state: present
- name: Create git user
ansible.builtin.user:
name: git
group: git
system: true
home: /var/lib/forgejo
shell: /bin/bash
state: present
- name: Ensure Forgejo directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: git
group: git
mode: "0750"
loop:
- /etc/forgejo
- /var/lib/forgejo
- /var/lib/forgejo/data
- /var/lib/forgejo/custom
- /var/lib/forgejo/log
- name: Check if Forgejo binary exists
ansible.builtin.stat:
path: /usr/local/bin/forgejo
register: forgejo_bin
- name: Download Forgejo binary
ansible.builtin.get_url:
url: https://codeberg.org/forgejo/forgejo/releases/download/v7.0.4/forgejo-7.0.4-linux-amd64
dest: /usr/local/bin/forgejo
owner: root
group: root
mode: "0755"
timeout: 30
when: not forgejo_bin.stat.exists or forgejo_bin.stat.size == 0
notify: Restart Forgejo
- name: Generate Forgejo configuration
ansible.builtin.copy:
dest: /etc/forgejo/app.ini
owner: git
group: git
mode: "0640"
content: |
APP_NAME = Sovereign C2 GitOps
RUN_USER = git
WORK_PATH = /var/lib/forgejo
[database]
DB_TYPE = sqlite3
PATH = /var/lib/forgejo/data/forgejo.db
[server]
DOMAIN = git.svo88.space
ROOT_URL = https://git.svo88.space/
HTTP_PORT = 3000
SSH_PORT = 2222
DISABLE_SSH = false
START_SSH_SERVER = true
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = false'
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
[repository]
ENABLE_PUSH_CREATE_USER = true
ENABLE_PUSH_CREATE_ORG = true
DEFAULT_PUSH_CREATE_PRIVATE = false
[security]
INSTALL_LOCK = true
notify: Restart Forgejo
- name: Create Forgejo systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/forgejo.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Forgejo Git Service
After=network.target
[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/forgejo
ExecStart=/usr/local/bin/forgejo web -c /etc/forgejo/app.ini
Restart=always
Environment=USER=git HOME=/var/lib/forgejo GITEA_WORK_DIR=/var/lib/forgejo
[Install]
WantedBy=multi-user.target
notify: Restart Forgejo
- name: Enable and start Forgejo service
ansible.builtin.systemd:
name: forgejo
enabled: true
state: started
daemon_reload: true
- name: Wait for Forgejo port 3000 to become available
ansible.builtin.wait_for:
port: 3000
host: 127.0.0.1
state: started
timeout: 30
- name: Initialize Forgejo database schema
ansible.builtin.command: >
su - git -c "/usr/local/bin/forgejo --work-path /var/lib/forgejo --config /etc/forgejo/app.ini migrate"
changed_when: false
- name: Create initial admin user
ansible.builtin.command: >
su - git -c "/usr/local/bin/forgejo --work-path /var/lib/forgejo --config /etc/forgejo/app.ini admin user create --admin --username atlas --password 'AtlasAdmin2026!' --email atlas@c2 --must-change-password=false"
register: admin_user_res
failed_when: admin_user_res.rc != 0 and 'already exists' not in admin_user_res.stderr and 'already exists' not in admin_user_res.stdout
changed_when: admin_user_res.rc == 0
handlers:
- name: Restart Forgejo
ansible.builtin.systemd:
name: forgejo
state: restarted

View file

@ -0,0 +1,71 @@
---
- name: Deploy NAS Storage on Alpine Linux (storage0)
hosts: storage0
gather_facts: false
tasks:
- name: Bootstrap Python 3 on Alpine via raw command
ansible.builtin.raw: apk update && apk add --no-cache python3 py3-pip
changed_when: false
- name: Install nfs-utils and rpcbind
ansible.builtin.package:
name:
- nfs-utils
- rpcbind
state: present
- name: Ensure exported root directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0777"
loop:
- /media
- /torrents
- /torrents/downloads
- /torrents/config
- /backups
- /models
- /mnt
- name: Create backward compatibility symlinks in /mnt
ansible.builtin.file:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
state: link
force: true
loop:
- { src: '/media', dest: '/mnt/media' }
- { src: '/torrents', dest: '/mnt/torrents' }
- { src: '/backups', dest: '/mnt/backups' }
- { src: '/models', dest: '/mnt/models' }
- name: Configure /etc/exports for Kubernetes PV clients
ansible.builtin.copy:
dest: /etc/exports
owner: root
group: root
mode: "0644"
content: |
/media 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) 127.0.0.1(rw,sync,no_subtree_check,no_root_squash)
/torrents 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) 127.0.0.1(rw,sync,no_subtree_check,no_root_squash)
/backups 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) 127.0.0.1(rw,sync,no_subtree_check,no_root_squash)
/models 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) 127.0.0.1(rw,sync,no_subtree_check,no_root_squash)
notify: Reload NFS exports
- name: Enable and start rpcbind service
ansible.builtin.service:
name: rpcbind
enabled: true
state: started
- name: Enable and start nfs service
ansible.builtin.service:
name: nfs
enabled: true
state: started
handlers:
- name: Reload NFS exports
ansible.builtin.command: exportfs -ra
changed_when: false

83
deploy.sh Executable file
View file

@ -0,0 +1,83 @@
#!/bin/bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${REPO_ROOT}/scripts/lib/common.sh"
COMMAND="${1:-help}"
case "${COMMAND}" in
up)
shift
target="${1:-all}"
case "${target}" in
host) "${REPO_ROOT}/scripts/layers/00_host.sh" ;;
net) "${REPO_ROOT}/scripts/layers/01_net.sh" ;;
tier0) "${REPO_ROOT}/scripts/layers/02_tier0.sh" ;;
talos) "${REPO_ROOT}/scripts/layers/03_talos.sh" ;;
apps) "${REPO_ROOT}/scripts/layers/04_apps.sh" ;;
all)
"${REPO_ROOT}/scripts/layers/00_host.sh"
"${REPO_ROOT}/scripts/layers/01_net.sh"
"${REPO_ROOT}/scripts/layers/02_tier0.sh"
"${REPO_ROOT}/scripts/layers/03_talos.sh"
"${REPO_ROOT}/scripts/layers/04_apps.sh"
log_ok "Вся инфраструктура полностью развернута!"
;;
*)
log_err "Неизвестный слой: ${target}. Доступны: host, net, tier0, talos, apps, all"
exit 1
;;
esac
;;
unseal)
ensure_prerequisites
unseal_vault
;;
clean)
rm -f "${RAM_CACHE}"
log_ok "Секреты безопасно удалены из RAM."
;;
gc)
"${REPO_ROOT}/scripts/layers/gc.sh"
;;
reset|nuke)
"${REPO_ROOT}/scripts/layers/reset.sh"
;;
state)
shift || true
"${REPO_ROOT}/scripts/collect_state.sh" "$@"
;;
plan)
ensure_prerequisites
load_secrets_to_ram
cd "${REPO_ROOT}/tofu" && tofu plan
;;
apply|a)
ensure_prerequisites
load_secrets_to_ram
unseal_vault
shift || true
cd "${REPO_ROOT}/tofu" && tofu apply "$@"
;;
help|--help|-h)
cat <<EOF
Sovereign C2 Orchestrator (dep)
Команды:
dep up [layer] Послойный деплой: host, net, tier0, talos, apps, all
dep unseal Ручной ансил OpenBao + авто-синк токена в K8s
dep state [module] Диагностика: workstation, pve, vps, vyos, tier0, k8s, all
dep plan Tofu plan
dep a / dep apply Tofu apply с авто-ансилом и синхронизацией токена
dep clean Очистка кэша в /dev/shm
dep reset Полный сброс всех VM/LXC до голого PVE
dep gc Сборка мусора: удаление мертвых нод, зависших подов и сброс бэкоффа
EOF
;;
*)
ensure_prerequisites
load_secrets_to_ram
cd "${REPO_ROOT}/tofu" && tofu "$@"
;;
esac

650
docs/hardware_snapshot.json Normal file
View file

@ -0,0 +1,650 @@
{
"cpu": {
"lscpu": [
{
"field": "Architecture:",
"data": "x86_64"
},
{
"field": "CPU op-mode(s):",
"data": "32-bit, 64-bit"
},
{
"field": "Address sizes:",
"data": "43 bits physical, 48 bits virtual"
},
{
"field": "Byte Order:",
"data": "Little Endian"
},
{
"field": "CPU(s):",
"data": "8"
},
{
"field": "On-line CPU(s) list:",
"data": "0-7"
},
{
"field": "Vendor ID:",
"data": "AuthenticAMD"
},
{
"field": "Model name:",
"data": "AMD Ryzen 5 3350GE with Radeon Vega Graphics"
},
{
"field": "CPU family:",
"data": "23"
},
{
"field": "Model:",
"data": "24"
},
{
"field": "Thread(s) per core:",
"data": "2"
},
{
"field": "Core(s) per socket:",
"data": "4"
},
{
"field": "Socket(s):",
"data": "1"
},
{
"field": "Stepping:",
"data": "1"
},
{
"field": "Frequency boost:",
"data": "enabled"
},
{
"field": "CPU(s) scaling MHz:",
"data": "94%"
},
{
"field": "CPU max MHz:",
"data": "3300.0000"
},
{
"field": "CPU min MHz:",
"data": "1400.0000"
},
{
"field": "BogoMIPS:",
"data": "6600.28"
},
{
"field": "Flags:",
"data": "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 clzero xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca sev sev_es"
},
{
"field": "Virtualization:",
"data": "AMD-V"
},
{
"field": "L1d cache:",
"data": "128 KiB (4 instances)"
},
{
"field": "L1i cache:",
"data": "256 KiB (4 instances)"
},
{
"field": "L2 cache:",
"data": "2 MiB (4 instances)"
},
{
"field": "L3 cache:",
"data": "4 MiB (1 instance)"
},
{
"field": "NUMA node(s):",
"data": "1"
},
{
"field": "NUMA node0 CPU(s):",
"data": "0-7"
},
{
"field": "Vulnerability Gather data sampling:",
"data": "Not affected"
},
{
"field": "Vulnerability Ghostwrite:",
"data": "Not affected"
},
{
"field": "Vulnerability Indirect target selection:",
"data": "Not affected"
},
{
"field": "Vulnerability Itlb multihit:",
"data": "Not affected"
},
{
"field": "Vulnerability L1tf:",
"data": "Not affected"
},
{
"field": "Vulnerability Mds:",
"data": "Not affected"
},
{
"field": "Vulnerability Meltdown:",
"data": "Not affected"
},
{
"field": "Vulnerability Mmio stale data:",
"data": "Not affected"
},
{
"field": "Vulnerability Old microcode:",
"data": "Not affected"
},
{
"field": "Vulnerability Reg file data sampling:",
"data": "Not affected"
},
{
"field": "Vulnerability Retbleed:",
"data": "Mitigation; untrained return thunk; SMT vulnerable"
},
{
"field": "Vulnerability Spec rstack overflow:",
"data": "Mitigation; Safe RET"
},
{
"field": "Vulnerability Spec store bypass:",
"data": "Mitigation; Speculative Store Bypass disabled via prctl"
},
{
"field": "Vulnerability Spectre v1:",
"data": "Mitigation; usercopy/swapgs barriers and __user pointer sanitization"
},
{
"field": "Vulnerability Spectre v2:",
"data": "Mitigation; Retpolines; IBPB conditional; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected"
},
{
"field": "Vulnerability Srbds:",
"data": "Not affected"
},
{
"field": "Vulnerability Tsa:",
"data": "Not affected"
},
{
"field": "Vulnerability Tsx async abort:",
"data": "Not affected"
},
{
"field": "Vulnerability Vmscape:",
"data": "Mitigation; IBPB before exit to userspace"
}
]
},
"ram_bytes": 39863885824,
"disks": {
"blockdevices": [
{
"name": "loop0",
"size": "8G",
"fstype": "ext4",
"uuid": "1cda1590-5d27-4cab-858f-2666c19f45bd",
"model": null,
"serial": null,
"path": "/dev/loop0",
"rota": false,
"type": "loop"
},
{
"name": "loop1",
"size": "8G",
"fstype": "ext4",
"uuid": "b2e798db-809f-48d5-a97a-58a16d19de8c",
"model": null,
"serial": null,
"path": "/dev/loop1",
"rota": false,
"type": "loop"
},
{
"name": "loop2",
"size": "4G",
"fstype": "ext4",
"uuid": "a0dad42d-f88a-4983-88f9-6e8f9b8e5c74",
"model": null,
"serial": null,
"path": "/dev/loop2",
"rota": false,
"type": "loop"
},
{
"name": "loop3",
"size": "4G",
"fstype": "ext4",
"uuid": "5e9edd0b-1e43-4f26-96aa-aec3bd3b6d98",
"model": null,
"serial": null,
"path": "/dev/loop3",
"rota": false,
"type": "loop"
},
{
"name": "loop4",
"size": "2G",
"fstype": "ext4",
"uuid": "37dd032b-8381-4eec-b69f-a9748bc33777",
"model": null,
"serial": null,
"path": "/dev/loop4",
"rota": false,
"type": "loop"
},
{
"name": "sda",
"size": "1.8T",
"fstype": null,
"uuid": null,
"model": "TOSHIBA HDWA120",
"serial": "27MWR2SAS",
"path": "/dev/sda",
"rota": true,
"type": "disk",
"children": [
{
"name": "sda1",
"size": "1.8T",
"fstype": "zfs_member",
"uuid": "7540128891022881476",
"model": null,
"serial": null,
"path": "/dev/sda1",
"rota": true,
"type": "part"
},
{
"name": "sda9",
"size": "8M",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/sda9",
"rota": true,
"type": "part"
}
]
},
{
"name": "sdb",
"size": "953.9G",
"fstype": null,
"uuid": null,
"model": "DEXP SSD C100 1Tb",
"serial": "HGB446242911",
"path": "/dev/sdb",
"rota": false,
"type": "disk",
"children": [
{
"name": "sdb1",
"size": "953.9G",
"fstype": "zfs_member",
"uuid": "1766286880976563783",
"model": null,
"serial": null,
"path": "/dev/sdb1",
"rota": false,
"type": "part"
},
{
"name": "sdb9",
"size": "8M",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/sdb9",
"rota": false,
"type": "part"
}
]
},
{
"name": "sdc",
"size": "476.9G",
"fstype": null,
"uuid": null,
"model": "Patriot P210 512GB",
"serial": "P210EDCB22092102722",
"path": "/dev/sdc",
"rota": false,
"type": "disk",
"children": [
{
"name": "sdc1",
"size": "976M",
"fstype": "vfat",
"uuid": "9DAA-752C",
"model": null,
"serial": null,
"path": "/dev/sdc1",
"rota": false,
"type": "part"
},
{
"name": "sdc2",
"size": "977M",
"fstype": "linux_raid_member",
"uuid": "e895a645-731a-c3f0-4f42-5bf822ece61e",
"model": null,
"serial": null,
"path": "/dev/sdc2",
"rota": false,
"type": "part",
"children": [
{
"name": "md0",
"size": "976M",
"fstype": "ext4",
"uuid": "4326ce97-013c-48e0-9b89-08722f8b13d3",
"model": null,
"serial": null,
"path": "/dev/md0",
"rota": false,
"type": "raid1"
}
]
},
{
"name": "sdc3",
"size": "445.3G",
"fstype": "linux_raid_member",
"uuid": "b383b1c8-144c-322c-58ab-66a03745768a",
"model": null,
"serial": null,
"path": "/dev/sdc3",
"rota": false,
"type": "part",
"children": [
{
"name": "md1",
"size": "445.1G",
"fstype": "crypto_LUKS",
"uuid": "d5ac7445-7491-4299-9cc5-22c316245793",
"model": null,
"serial": null,
"path": "/dev/md1",
"rota": false,
"type": "raid1",
"children": [
{
"name": "md1_crypt",
"size": "445.1G",
"fstype": "ext4",
"uuid": "aa4d959e-ebdb-4bd7-b8e9-5631a250da60",
"model": null,
"serial": null,
"path": "/dev/mapper/md1_crypt",
"rota": false,
"type": "crypt"
}
]
}
]
}
]
},
{
"name": "sdd",
"size": "953.9G",
"fstype": null,
"uuid": null,
"model": "DEXP SSD C100 1Tb",
"serial": "HGB443029148",
"path": "/dev/sdd",
"rota": false,
"type": "disk",
"children": [
{
"name": "sdd1",
"size": "953.9G",
"fstype": "zfs_member",
"uuid": "1766286880976563783",
"model": null,
"serial": null,
"path": "/dev/sdd1",
"rota": false,
"type": "part"
},
{
"name": "sdd9",
"size": "8M",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/sdd9",
"rota": false,
"type": "part"
}
]
},
{
"name": "sde",
"size": "447.1G",
"fstype": null,
"uuid": null,
"model": "HS-SSD-C100 480G",
"serial": "30103741962",
"path": "/dev/sde",
"rota": false,
"type": "disk",
"children": [
{
"name": "sde1",
"size": "976M",
"fstype": "vfat",
"uuid": "9DAB-4792",
"model": null,
"serial": null,
"path": "/dev/sde1",
"rota": false,
"type": "part"
},
{
"name": "sde2",
"size": "977M",
"fstype": "linux_raid_member",
"uuid": "e895a645-731a-c3f0-4f42-5bf822ece61e",
"model": null,
"serial": null,
"path": "/dev/sde2",
"rota": false,
"type": "part",
"children": [
{
"name": "md0",
"size": "976M",
"fstype": "ext4",
"uuid": "4326ce97-013c-48e0-9b89-08722f8b13d3",
"model": null,
"serial": null,
"path": "/dev/md0",
"rota": false,
"type": "raid1"
}
]
},
{
"name": "sde3",
"size": "445.2G",
"fstype": "linux_raid_member",
"uuid": "b383b1c8-144c-322c-58ab-66a03745768a",
"model": null,
"serial": null,
"path": "/dev/sde3",
"rota": false,
"type": "part",
"children": [
{
"name": "md1",
"size": "445.1G",
"fstype": "crypto_LUKS",
"uuid": "d5ac7445-7491-4299-9cc5-22c316245793",
"model": null,
"serial": null,
"path": "/dev/md1",
"rota": false,
"type": "raid1",
"children": [
{
"name": "md1_crypt",
"size": "445.1G",
"fstype": "ext4",
"uuid": "aa4d959e-ebdb-4bd7-b8e9-5631a250da60",
"model": null,
"serial": null,
"path": "/dev/mapper/md1_crypt",
"rota": false,
"type": "crypt"
}
]
}
]
}
]
},
{
"name": "sdf",
"size": "953.9G",
"fstype": null,
"uuid": null,
"model": "DEXP SSD C100 1024Gb",
"serial": "MBM343145343",
"path": "/dev/sdf",
"rota": false,
"type": "disk",
"children": [
{
"name": "sdf1",
"size": "953.9G",
"fstype": "zfs_member",
"uuid": "1766286880976563783",
"model": null,
"serial": null,
"path": "/dev/sdf1",
"rota": false,
"type": "part"
},
{
"name": "sdf9",
"size": "8M",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/sdf9",
"rota": false,
"type": "part"
}
]
},
{
"name": "zd0",
"size": "4M",
"fstype": "iso9660",
"uuid": "2026-08-26-12-40-52-00",
"model": null,
"serial": null,
"path": "/dev/zd0",
"rota": false,
"type": "disk"
},
{
"name": "zd16",
"size": "100G",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/zd16",
"rota": false,
"type": "disk"
},
{
"name": "zd32",
"size": "100G",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/zd32",
"rota": false,
"type": "disk"
},
{
"name": "zd48",
"size": "10G",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/zd48",
"rota": false,
"type": "disk",
"children": [
{
"name": "zd48p1",
"size": "1M",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/zd48p1",
"rota": false,
"type": "part"
},
{
"name": "zd48p2",
"size": "256M",
"fstype": "vfat",
"uuid": "C283-D401",
"model": null,
"serial": null,
"path": "/dev/zd48p2",
"rota": false,
"type": "part"
},
{
"name": "zd48p3",
"size": "9G",
"fstype": "ext4",
"uuid": "c33ec994-5719-497b-a670-f3941f9a2792",
"model": null,
"serial": null,
"path": "/dev/zd48p3",
"rota": false,
"type": "part"
}
]
},
{
"name": "zd64",
"size": "20G",
"fstype": null,
"uuid": null,
"model": null,
"serial": null,
"path": "/dev/zd64",
"rota": false,
"type": "disk"
}
]
}
}

51
docs/invariants.md Normal file
View file

@ -0,0 +1,51 @@
# Каноническая спецификация инвариантов Sovereign C2
**Статус документа:** Строгий стандарт
**Назначение:** Определение фундаментальных правил, которые не могут быть нарушены ни при каких изменениях программного или аппаратного обеспечения. Любое техническое решение (выбор ОС, файловой системы, сетевого стека) должно доказывать соответствие данным инвариантам.
**Формат кода:** `[ДОМЕН]-[НОМЕР]`. Код постоянен, эволюция требований отслеживается историей Git.
---
## Категория A: Хранение и данные (DATA)
* **DATA-01 (Изоляция и профилирование ввода-вывода):** Хранилище строго разделяется по паттернам доступа. Физические носители и параметры файловых систем обязаны математически соответствовать профилю нагрузки: мелкоблочный случайный ввод-вывод с критическими требованиями к задержкам физически изолирован от емкостных последовательных нагрузок, а геометрия хранилища выровнена по размеру транзакции потребителя для исключения эффекта усиления записи.
* **DATA-02 (Абсолютная криптография покоя):** Все без исключения энергонезависимые носители информации криптографически защищены. Извлечение физического накопителя из вычислительного комплекса не должно раскрывать структуру, метаданные или содержимое данных.
* **DATA-03 (Иммутабельность точек восстановления):** Резервные копии формируются как атомарные, криптографически запечатанные слепки состояния, не зависящие от работоспособности исходной файловой системы и доступные для валидации в изолированной среде.
---
## Категория B: Сеть и изоляция (NET)
* **NET-01 (Мультиплексирование физической среды):** Логические домены безопасности (внешний периметр, маршрутизация, управление, внутренние вычисления) полностью изолированы друг от друга на уровне канального протокола, независимо от количества доступных физических сетевых интерфейсов.
* **NET-02 (L2 Fast-Path и скрытая плоскость данных):** Вычислительные узлы скрыты от внешних широковещательных доменов. При этом обмен данными между доверенными локальными клиентами и внутренними прикладными сервисами замыкается напрямую в общем L2-сегменте на скорости коммутатора, полностью минуя межсетевой экран и исключая нагрузку на процессор маршрутизатора.
* **NET-03 (Запрет прямой эксфильтрации):** Ни один прикладной сервис не имеет права на самостоятельное установление исходящих соединений во внешние сети. Доставка любых внешних бинарных компонентов, зависимостей и образов ПО разрешена исключительно через контролируемые локальные кэширующие шлюзы.
* **NET-04 (Обфускация транзитного трафика):** Любой исходящий трафик за пределы доверенного периметра должен быть инкапсулирован и криптографически замаскирован для предотвращения глубокого анализа пакетов (DPI), цензуры или активного зондирования промежуточными операторами связи.
* **NET-05 (Топология наложенного управления):** Административный доступ к инфраструктуре осуществляется строго через криптографически аутентифицированную одноранговую наложенную сеть (Overlay Network), исключающую зависимость от таблиц маршрутизации физических пограничных маршрутизаторов.
---
## Категория C: Физическая безопасность и доступ (SEC)
* **SEC-01 (Устойчивость к физическому захвату):** Физический захват включенного или выключенного оборудования третьими лицами на месте установки не должен приводить к автоматической компрометации критических данных или ключей управления.
* **SEC-02 (Стойкость при досмотре клиентов):** Компрометация или принудительный досмотр мобильных и клиентских устройств за пределами доверенного периметра не должны раскрывать факт наличия, архитектуру или содержимое приватных сервисов.
* **SEC-03 (Энергозависимость секретов):** Любые криптографические ключи, пароли и токены доступа существуют исключительно в энергозависимой памяти (RAM). Запись секретов в открытом виде на постоянные носители строго запрещена на всех этапах жизненного цикла инфраструктуры.
* **SEC-04 (Автономность холодной загрузки):** Базовый слой инфраструктуры обязан корректно инициализироваться в условиях полного отсутствия доступа к глобальной сети, опираясь исключительно на доверенную локальную среду и энергозависимую инъекцию мастер-ключей авторизованным оператором.
* **SEC-05 (Автономность клиентских терминалов):** Отказ, обесточивание или недоступность основного вычислительного комплекса не должны блокировать доступ пользователя к локально закэшированным критическим учетным данным и ключам на клиентских терминалах (ноутбук, смартфон).
---
## Категория D: Вычисления и отказоустойчивость (COMP)
* **COMP-01 (Детерминизм планировщика):** Компоненты управления (Control Plane) и маршрутизации получают строгие гарантии процессорного времени. Пользовательские и прикладные нагрузки утилизируют только остаточные вычислительные такты и не могут вызвать голодание системных процессов.
* **COMP-02 (Строгое бюджетирование памяти):** Оперативная память распределяется квотами с обязательным аппаратным резервом. Каждому домену (маршрутизация, кэш файловой системы, управление, вычисления) установлен жесткий лимит для предотвращения каскадных отказов по исчерпанию памяти (OOM-шторм).
* **COMP-03 (Гарантии восстановления из пепла):** При полной физической гибели основного оборудования допустима потеря данных не более чем за 7 дней (RPO ≤ 7 дней). Восстановление рабочего состояния на новом оборудовании должно выполняться оффлайн, используя исключительно холодные резервные копии и мастер-ключ.
* **COMP-04 (N-Node Elasticity):** Архитектура индифферентна к количеству и вычислительной мощности узлов. Система обязана полноценно функционировать на одном маломощном устройстве, сохраняя способность прозрачно подключать разнородные вычислительные узлы без изменения сетевой модели и логики работы сервисов.
---
## Категория E: Управление и источник правды (SSOT)
* **SSOT-01 (Идемпотентная декларативность):** Весь жизненный цикл инфраструктуры (от создания дисков до конфигурации приложений) описан в виде исполняемого кода. Процесс применения конфигурации обязан быть конвергентным и идемпотентным (безопасным для повторного запуска).
* **SSOT-02 (Локализация источника правды):** Механизмы непрерывной доставки и хранилища конфигураций (Source of Truth) размещаются внутри управляемого периметра. Инфраструктура развертывает и обновляет саму себя без привлечения внешних публичных репозиториев или облачных CI/CD конвейеров.
* **SSOT-03 (Иммутабельность вычислительных узлов):** Любые ручные изменения состояния внутри операционных систем вычислительных узлов запрещены. Обновление или исправление конфигурации достигается исключительно пересозданием вычислительного ресурса на основе эталонного образа.
* **SSOT-04 (Автономная телеметрия):** Непрерывный аудит, мониторинг и сбор телеметрии замкнуты строго внутри доверенного периметра. Любая эксфильтрация метрик, логов или событий безопасности во внешние облачные платформы категорически запрещена.

382
docs/topology.yaml Normal file
View file

@ -0,0 +1,382 @@
hardware:
cpu:
architecture: "x86_64"
model: "AMD Ryzen 5 3350GE with Radeon Vega Graphics"
threads: 8
cores: 4
ram:
total_bytes: 39863885824
budgeting_gb:
reserved_apu: 0.064
arc_cache_limit: 4.0
vyos_podman: 1.0
lxc_tier0: 2.5
talos_master: 2.5
talos_workers: 20.0
oom_headroom: 9.936
storage:
pools:
astra:
type: "zfs_raidz1"
media: "ssd"
devices:
- "/dev/disk/by-id/ata-DEXP_SSD_C100_1Tb_HGB446242911"
- "/dev/disk/by-id/ata-DEXP_SSD_C100_1Tb_HGB443029148"
- "/dev/disk/by-id/ata-DEXP_SSD_C100_1024Gb_MBM343145343"
terra:
type: "zfs_single"
media: "hdd"
devices:
- "/dev/disk/by-id/ata-TOSHIBA_HDWA120_27MWR2SAS"
local:
type: "mdadm_luks_ext4"
media: "ssd"
devices:
- "/dev/disk/by-id/ata-Patriot_P210_512GB_P210EDCB22092102722"
- "/dev/disk/by-id/ata-HS-SSD-C100_480G_30103741962"
network:
vlans:
vlan_10:
id: 10
name: "TIER0"
cidr: "10.0.10.0/24"
gateway: "10.0.10.1"
policy: "strict_internal"
vlan_20:
id: 20
name: "K8S"
cidr: "10.0.20.0/24"
gateway: "10.0.20.1"
policy: "k8s_overlay"
vlan_30:
id: 30
name: "LAN_CLIENTS"
cidr: "10.0.30.0/24"
gateway: "10.0.30.1"
policy: "l2_fast_path_vip"
vlan_40:
id: 40
name: "IOT"
cidr: "10.0.40.0/24"
gateway: "10.0.40.1"
policy: "isolated_no_internet"
vlan_99:
id: 99
name: "WAN_TRUNK"
cidr: "192.168.1.0/24"
gateway: "192.168.1.1"
policy: "external_egress"
tier0_ips:
vyos: "10.0.10.1"
dns0: "10.0.10.11"
vault0: "10.0.10.12"
git0: "10.0.10.13"
storage0: "10.0.10.14"
registry0: "10.0.10.15"
infrastructure_nodes:
- id: 100
name: "vyos-router"
type: "vm"
role: "edge_router"
vcpu: 2
ram_mb: 1024
disk_gb: 10
pool: "astra"
- id: 101
name: "dns0"
type: "lxc"
role: "dns_server"
vcpu: 1
ram_mb: 512
disk_gb: 2
pool: "local"
- id: 102
name: "vault0"
type: "lxc"
role: "secrets_manager"
vcpu: 1
ram_mb: 512
disk_gb: 4
pool: "local"
- id: 103
name: "git0"
type: "lxc"
role: "gitops_repo"
vcpu: 2
ram_mb: 1024
disk_gb: 8
pool: "local"
- id: 104
name: "storage0"
type: "lxc"
role: "nas_storage"
vcpu: 2
ram_mb: 1024
disk_gb: 4
pool: "local"
mounts:
- path: "/mnt/media"
volume: "/terra/media"
- path: "/mnt/torrents"
volume: "/terra/torrents"
- path: "/mnt/backups"
volume: "/terra/vault"
- id: 105
name: "registry0"
type: "lxc"
role: "oci_registry"
vcpu: 2
ram_mb: 1024
disk_gb: 8
pool: "local"
mounts:
- path: "/mnt/registry"
pool: "terra"
volume: "/terra/registry"
- id: 201
name: "talos-master-1"
type: "vm"
role: "k8s_control_plane"
vcpu: 2
ram_mb: 2560
disk_gb: 20
pool: "astra"
- id: 202
name: "talos-worker-1"
type: "vm"
role: "k8s_worker"
vcpu: 4
ram_mb: 10240
disk_gb: 100
pool: "astra"
- id: 203
name: "talos-worker-2"
type: "vm"
role: "k8s_worker"
vcpu: 4
ram_mb: 10240
disk_gb: 100
pool: "astra"
workloads:
- name: "cilium_cni"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "traefik_v3"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "csi_drivers"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "IO-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "fluxcd"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "Idle"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "kyverno"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "tetragon"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "vector_agent"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "IO-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "crowdsec"
runtime: "k8s"
state_type: "Micro-Stateful"
compute_profile: "CPU-Bound"
storage_medium: "SSD"
storage_fs: "PVC"
db_dependency: "SQLite"
- name: "postgresql"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "IO-Bound"
storage_medium: "SSD"
storage_fs: "ZVOL"
db_dependency: "None"
- name: "redis_cache"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "RAM-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "authentik_sso"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "Postgres, Redis"
- name: "vaultwarden"
runtime: "k8s"
state_type: "Micro-Stateful"
compute_profile: "Idle"
storage_medium: "SSD"
storage_fs: "ZVOL"
db_dependency: "SQLite"
- name: "immich_system"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "CPU-Bound"
storage_medium: "SSD"
storage_fs: "ZVOL"
media_storage_medium: "SSD"
media_storage_fs: "PVC"
db_dependency: "Postgres, Redis"
- name: "paperless_ngx"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "CPU-Bound"
storage_medium: "SSD"
storage_fs: "PVC"
db_dependency: "Postgres, Redis"
- name: "netbox_dcim"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "Idle"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "Postgres, Redis"
- name: "opencloud"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "IO-Bound"
storage_medium: "SSD"
storage_fs: "PVC"
db_dependency: "Postgres, Redis"
- name: "homebox"
runtime: "k8s"
state_type: "Micro-Stateful"
compute_profile: "Idle"
storage_medium: "SSD"
storage_fs: "ZVOL"
db_dependency: "SQLite"
- name: "spoolman"
runtime: "k8s"
state_type: "Micro-Stateful"
compute_profile: "Idle"
storage_medium: "SSD"
storage_fs: "ZVOL"
db_dependency: "SQLite"
- name: "victoriametrics"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "IO-Bound"
storage_medium: "SSD"
storage_fs: "PVC"
db_dependency: "None"
- name: "grafana_loki"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "IO-Bound"
storage_medium: "HDD"
storage_fs: "Dataset"
db_dependency: "None"
- name: "grafana_ui"
runtime: "k8s"
state_type: "Micro-Stateful"
compute_profile: "Idle"
storage_medium: "SSD"
storage_fs: "PVC"
db_dependency: "SQLite"
- name: "pixie"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "RAM-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "qbittorrent"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "IO-Bound"
storage_medium: "HDD"
storage_fs: "Dataset"
db_dependency: "None"
- name: "jellyfin"
runtime: "bare-metal"
state_type: "Stateful"
compute_profile: "IO-Bound"
storage_medium: "HDD"
storage_fs: "Dataset"
db_dependency: "None"
- name: "kiwix_reader"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "IO-Bound"
storage_medium: "HDD"
storage_fs: "Dataset"
db_dependency: "None"
- name: "sure_finance"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "Idle"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "Postgres"
- name: "searxng"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "act_runner_ci"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "karakeep"
runtime: "k8s"
state_type: "Micro-Stateful"
compute_profile: "Idle"
storage_medium: "SSD"
storage_fs: "PVC"
db_dependency: "SQLite"
- name: "transmute"
runtime: "k8s"
state_type: "Stateless"
compute_profile: "CPU-Bound"
storage_medium: "RAM"
storage_fs: "tmpfs"
db_dependency: "None"
- name: "printstash"
runtime: "k8s"
state_type: "Stateful"
compute_profile: "Idle"
storage_medium: "HDD"
storage_fs: "Dataset"
db_dependency: "Postgres"

View file

@ -0,0 +1,21 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: authentik-credentials
namespace: authentik
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: authentik-credentials
data:
- secretKey: secret_key
remoteRef:
key: secret/authentik
property: secret_key
- secretKey: postgresql_password
remoteRef:
key: secret/authentik
property: postgresql_password

View file

@ -0,0 +1,132 @@
apiVersion: v1
kind: Namespace
metadata:
name: authentik
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: authentik-server
namespace: authentik
spec:
serviceName: authentik-server
replicas: 1
selector:
matchLabels:
app: authentik-server
template:
metadata:
labels:
app: authentik-server
spec:
containers:
- name: server
image: ghcr.io/goauthentik/server:2026.8.3
args: ["server"]
ports:
- name: http
containerPort: 9000
- name: https
containerPort: 9443
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-credentials
key: secret_key
- name: AUTHENTIK_POSTGRESQL__HOST
value: postgres-core.databases.svc.cluster.local
- name: AUTHENTIK_POSTGRESQL__NAME
value: authentik
- name: AUTHENTIK_POSTGRESQL__USER
value: authentik
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-credentials
key: postgresql_password
- name: AUTHENTIK_REDIS__HOST
value: redis-core.databases.svc.cluster.local
---
apiVersion: v1
kind: Service
metadata:
name: authentik-server
namespace: authentik
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 9000
- name: https
port: 443
targetPort: 9443
selector:
app: authentik-server
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: authentik-worker
namespace: authentik
spec:
serviceName: authentik-server
replicas: 1
selector:
matchLabels:
app: authentik-worker
template:
metadata:
labels:
app: authentik-worker
spec:
containers:
- name: worker
image: ghcr.io/goauthentik/server:2026.8.3
args: ["worker"]
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-credentials
key: secret_key
- name: AUTHENTIK_POSTGRESQL__HOST
value: postgres-core.databases.svc.cluster.local
- name: AUTHENTIK_POSTGRESQL__NAME
value: authentik
- name: AUTHENTIK_POSTGRESQL__USER
value: authentik
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-credentials
key: postgresql_password
- name: AUTHENTIK_REDIS__HOST
value: redis-core.databases.svc.cluster.local
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: authentik
namespace: authentik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- auth.svo88.space
secretName: auth-svo88-tls
rules:
- host: auth.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: authentik-server
port:
number: 80

View file

@ -0,0 +1,251 @@
apiVersion: v1
kind: Namespace
metadata:
name: databases
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: postgres-credentials
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: postgres-credentials
data:
- secretKey: POSTGRES_PASSWORD
remoteRef:
key: secret/postgres
property: POSTGRES_PASSWORD
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-core
namespace: databases
spec:
serviceName: postgres-core
replicas: 1
selector:
matchLabels:
app: postgres-core
template:
metadata:
labels:
app: postgres-core
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: POSTGRES_PASSWORD
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2048Mi
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-db
resources:
requests:
storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres-core
namespace: databases
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
selector:
app: postgres-core
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-core
serviceName: redis-core
namespace: databases
spec:
replicas: 1
selector:
matchLabels:
app: redis-core
template:
metadata:
labels:
app: redis-core
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
limits:
cpu: 500m
memory: 400Mi
volumeMounts:
- name: redis-data
mountPath: /data
volumes:
- name: redis-data
emptyDir:
medium: Memory
---
apiVersion: v1
kind: Service
metadata:
name: redis-core
serviceName: redis-core
namespace: databases
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
selector:
app: redis-core
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: all-db-passwords
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: all-db-passwords
data:
- secretKey: AUTHENTIK_PASS
remoteRef:
key: secret/authentik
property: postgresql_password
- secretKey: IMMICH_PASS
remoteRef:
key: secret/immich
property: DB_PASSWORD
- secretKey: PAPERLESS_PASS
remoteRef:
key: secret/paperless
property: PAPERLESS_DBPASS
- secretKey: NETBOX_PASS
remoteRef:
key: secret/netbox
property: DB_PASSWORD
- secretKey: SURE_PASS
remoteRef:
key: secret/sure
property: DB_PASSWORD
- secretKey: PRINTSTASH_PASS
remoteRef:
key: secret/printstash
property: DB_PASSWORD
- secretKey: INVIDIOUS_PASS
remoteRef:
key: secret/invidious
property: DB_PASSWORD
---
apiVersion: batch/v1
kind: Job
metadata:
name: postgres-init-databases
namespace: databases
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: db-init
image: postgres:16-alpine
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: POSTGRES_PASSWORD
- name: AUTHENTIK_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: AUTHENTIK_PASS
- name: IMMICH_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: IMMICH_PASS
- name: PAPERLESS_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: PAPERLESS_PASS
- name: NETBOX_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: NETBOX_PASS
- name: SURE_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: SURE_PASS
- name: PRINTSTASH_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: PRINTSTASH_PASS
- name: INVIDIOUS_PASS
valueFrom:
secretKeyRef:
name: all-db-passwords
key: INVIDIOUS_PASS
command:
- /bin/sh
- -c
- |
until pg_isready -h postgres-core -p 5432 -U postgres; do sleep 2; done
create_db() {
local user=$1
local pass=$2
local db=$3
psql -h postgres-core -U postgres -tc "SELECT 1 FROM pg_user WHERE usename = '$user'" | grep -q 1 || \
psql -h postgres-core -U postgres -c "CREATE USER $user WITH PASSWORD '$pass';"
psql -h postgres-core -U postgres -c "ALTER USER $user WITH PASSWORD '$pass';"
psql -h postgres-core -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '$db'" | grep -q 1 || \
psql -h postgres-core -U postgres -c "CREATE DATABASE $db OWNER $user;"
}
create_db "authentik" "$AUTHENTIK_PASS" "authentik"
create_db "immich" "$IMMICH_PASS" "immich"
create_db "paperless" "$PAPERLESS_PASS" "paperless"
create_db "netbox" "$NETBOX_PASS" "netbox"
create_db "sure" "$SURE_PASS" "sure_finance"
create_db "printstash" "$PRINTSTASH_PASS" "printstash"
create_db "invidious" "$INVIDIOUS_PASS" "invidious"
echo "Все базы данных успешно инициализированы!"

View file

@ -0,0 +1,311 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: dashboard-html
namespace: tools
data:
index.html: |
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATLAS</title>
<style>
:root {
--bg: #090a0f;
--card-bg: #10121a;
--border: #1a1d2b;
--text: #cdd6f4;
--text-dim: #6c7086;
--accent: #89b4fa;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background-color: var(--bg);
color: var(--text);
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
padding: 2.5rem 2rem;
min-height: 100vh;
}
.container { max-width: 1400px; margin: 0 auto; }
header { margin-bottom: 2rem; }
.title {
font-size: 1.1rem;
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text);
}
.search-box {
margin: 1.5rem 0 2.5rem 0;
position: relative;
}
input[type="text"] {
width: 100%;
background: var(--card-bg);
border: 1px solid var(--border);
padding: 0.85rem 1.25rem;
color: var(--text);
font-family: inherit;
font-size: 0.95rem;
outline: none;
transition: border-color 0.15s;
}
input[type="text"]:focus { border-color: var(--accent); }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 2rem;
}
.section-title {
font-size: 0.8rem;
color: var(--accent);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 1rem;
padding-bottom: 0.35rem;
border-bottom: 1px dashed var(--border);
}
.links { display: flex; flex-direction: column; gap: 0.5rem; }
.link-card {
background: var(--card-bg);
border: 1px solid var(--border);
padding: 0.75rem 1rem;
text-decoration: none;
color: var(--text);
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.15s;
}
.link-card:hover, .link-card.active {
border-color: var(--accent);
background: #141724;
transform: translateX(4px);
}
.link-name { font-size: 0.9rem; font-weight: 500; }
.link-desc { font-size: 0.75rem; color: var(--text-dim); }
.hidden { display: none !important; }
</style>
</head>
<body>
<div class="container">
<header>
<div class="title">ATLAS</div>
</header>
<div class="search-box">
<input type="text" id="search" placeholder="..." autofocus autocomplete="off">
</div>
<div class="grid" id="mainGrid">
<div class="section" data-section>
<div class="section-title">01 // CORE & INFRA</div>
<div class="links">
<a href="https://pve.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Proxmox VE</div><div class="link-desc">Hypervisor Node (pve0)</div></div>
</a>
<a href="https://dns.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Technitium DNS</div><div class="link-desc">Authoritative / DoT DNS</div></div>
</a>
<a href="https://vault.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">OpenBao Vault</div><div class="link-desc">RAM Secrets Engine</div></div>
</a>
<a href="https://git.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Forgejo GitOps</div><div class="link-desc">Source of Truth Repo</div></div>
</a>
<a href="https://zot.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Zot Registry</div><div class="link-desc">OCI Container Cache</div></div>
</a>
<a href="https://netbox.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">NetBox DCIM</div><div class="link-desc">IPAM & Network Assets</div></div>
</a>
</div>
</div>
<div class="section" data-section>
<div class="section-title">02 // SERVICES & DATA</div>
<div class="links">
<a href="https://paperless.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Paperless-ngx</div><div class="link-desc">OCR Document Archive</div></div>
</a>
<a href="https://opencloud.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">OpenCloud</div><div class="link-desc">Encrypted Cloud Storage</div></div>
</a>
<a href="https://vw.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Vaultwarden</div><div class="link-desc">Bitwarden Password Vault</div></div>
</a>
<a href="https://auth.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Authentik SSO</div><div class="link-desc">Identity & ForwardAuth</div></div>
</a>
<a href="https://finance.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Sure Finance</div><div class="link-desc">Personal Accounting</div></div>
</a>
<a href="https://search.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">SearXNG</div><div class="link-desc">Private Meta-Search</div></div>
</a>
<a href="https://homebox.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Homebox</div><div class="link-desc">Inventory & Asset Tracking</div></div>
</a>
<a href="https://spoolman.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Spoolman</div><div class="link-desc">3D Filament Manager</div></div>
</a>
<a href="https://printstash.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Printstash</div><div class="link-desc">3D Model Stash</div></div>
</a>
</div>
</div>
<div class="section" data-section>
<div class="section-title">03 // MEDIA & ENTERTAINMENT</div>
<div class="links">
<a href="https://immich.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Immich Photos</div><div class="link-desc">Photo & Video Archive</div></div>
</a>
<a href="https://jellyfin.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">Jellyfin</div><div class="link-desc">Courses & Media Stream</div></div>
</a>
<a href="https://mtube.svo88.space" class="link-card" target="_blank">
<div><div class="link-name">MeTube</div><div class="link-desc">Video Course Downloader</div></div>
</a>
<a href="http://10.0.30.200:8080" class="link-card" target="_blank">
<div><div class="link-name">qBittorrent</div><div class="link-desc">NFS Bulk Downloader</div></div>
</a>
</div>
</div>
<div class="section" data-section>
<div class="section-title">04 // NETWORK & EXTERNAL</div>
<div class="links">
<a href="https://153.76.194.24:8443" class="link-card" target="_blank">
<div><div class="link-name">Fin21 Perimeter</div><div class="link-desc">Edge VPS Fallback</div></div>
</a>
<a href="https://github.com" class="link-card" target="_blank">
<div><div class="link-name">GitHub</div><div class="link-desc">External Repositories</div></div>
</a>
<a href="https://ntc.party" class="link-card" target="_blank">
<div><div class="link-name">NTC Party</div><div class="link-desc">DPI & Censorship Research</div></div>
</a>
</div>
</div>
</div>
</div>
<script>
const search = document.getElementById('search');
const cards = Array.from(document.querySelectorAll('.link-card'));
const sections = Array.from(document.querySelectorAll('[data-section]'));
search.addEventListener('input', (e) => {
const val = e.target.value.toLowerCase().trim();
cards.forEach(c => {
const text = (c.querySelector('.link-name').textContent + ' ' + c.querySelector('.link-desc').textContent).toLowerCase();
if (text.includes(val)) {
c.classList.remove('hidden');
} else {
c.classList.add('hidden');
}
});
sections.forEach(s => {
const hasVisible = s.querySelectorAll('.link-card:not(.hidden)').length > 0;
s.style.display = hasVisible ? 'block' : 'none';
});
});
document.addEventListener('keydown', (e) => {
if (e.key === '/' && document.activeElement !== search) {
e.preventDefault();
search.focus();
search.select();
} else if (e.key === 'Escape') {
search.value = '';
search.dispatchEvent(new Event('input'));
search.blur();
} else if (e.key === 'Enter') {
const first = document.querySelector('.link-card:not(.hidden)');
if (first) window.open(first.href, '_blank');
}
});
</script>
</body>
</html>
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: dashboard
namespace: tools
labels:
app: dashboard
spec:
serviceName: dashboard
replicas: 1
selector:
matchLabels:
app: dashboard
template:
metadata:
labels:
app: dashboard
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 50m
memory: 32Mi
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: dashboard-html
---
apiVersion: v1
kind: Service
metadata:
name: dashboard
namespace: tools
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: dashboard
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dashboard
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- dash.svo88.space
secretName: dash-svo88-tls
rules:
- host: dash.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dashboard
port:
number: 80

View file

@ -0,0 +1,109 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: homebox-data-pvc
namespace: tools
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 5Gi
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: homebox-credentials
namespace: tools
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: homebox-credentials
data:
- secretKey: HBOX_AUTH_API_KEY_PEPPER
remoteRef:
key: secret/homebox
property: HBOX_AUTH_API_KEY_PEPPER
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: homebox
namespace: tools
spec:
serviceName: homebox
replicas: 1
selector:
matchLabels:
app: homebox
template:
metadata:
labels:
app: homebox
spec:
securityContext:
fsGroup: 1000
containers:
- name: homebox
image: ghcr.io/sysadminsmedia/homebox:latest
ports:
- containerPort: 7745
env:
- name: HBOX_MODE
value: "production"
- name: HBOX_AUTH_API_KEY_PEPPER
valueFrom:
secretKeyRef:
name: homebox-credentials
key: HBOX_AUTH_API_KEY_PEPPER
volumeMounts:
- name: homebox-data
mountPath: /data
volumes:
- name: homebox-data
persistentVolumeClaim:
claimName: homebox-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: homebox
namespace: tools
spec:
type: ClusterIP
ports:
- port: 7745
targetPort: 7745
selector:
app: homebox
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: homebox
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- homebox.svo88.space
secretName: homebox-svo88-tls
rules:
- host: homebox.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: homebox
port:
number: 7745

View file

@ -0,0 +1,17 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: immich-credentials
namespace: media
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: immich-credentials
data:
- secretKey: DB_PASSWORD
remoteRef:
key: secret/immich
property: DB_PASSWORD

View file

@ -0,0 +1,229 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-db-pvc
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-db
resources:
requests:
storage: 30Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-library-pvc
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 400Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: immich-postgres
namespace: media
spec:
serviceName: immich-postgres
replicas: 1
selector:
matchLabels:
app: immich-postgres
template:
metadata:
labels:
app: immich-postgres
spec:
containers:
- name: postgres
image: ghcr.io/immich-app/postgres:16-vectorchord1.1.1-pgvector0.8.5
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: immich
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: immich-credentials
key: DB_PASSWORD
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2048Mi
volumeMounts:
- name: db-data
mountPath: /var/lib/postgresql/data
volumes:
- name: db-data
persistentVolumeClaim:
claimName: immich-db-pvc
---
apiVersion: v1
kind: Service
metadata:
name: immich-postgres
namespace: media
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
selector:
app: immich-postgres
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: immich-machine-learning
namespace: media
spec:
replicas: 1
selector:
matchLabels:
app: immich-machine-learning
template:
metadata:
labels:
app: immich-machine-learning
spec:
containers:
- name: ml
image: ghcr.io/immich-app/immich-machine-learning:v3.1.0
ports:
- containerPort: 3003
resources:
requests:
cpu: 500m
memory: 1024Mi
limits:
cpu: 3000m
memory: 3072Mi
---
apiVersion: v1
kind: Service
metadata:
name: immich-machine-learning
namespace: media
spec:
type: ClusterIP
ports:
- port: 3003
targetPort: 3003
selector:
app: immich-machine-learning
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: immich-server
namespace: media
spec:
serviceName: immich-server
replicas: 1
selector:
matchLabels:
app: immich-server
template:
metadata:
labels:
app: immich-server
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: feature.node.kubernetes.io/intel-qsv
operator: In
values: ["true"]
containers:
- name: server
image: ghcr.io/immich-app/immich-server:v3.1.0
ports:
- containerPort: 2283
env:
- name: DB_HOSTNAME
value: immich-postgres
- name: DB_USERNAME
value: postgres
- name: DB_DATABASE_NAME
value: immich
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: immich-credentials
key: DB_PASSWORD
- name: REDIS_HOSTNAME
value: redis-core.databases.svc.cluster.local
- name: IMMICH_MACHINE_LEARNING_URL
value: http://immich-machine-learning:3003
- name: IMMICH_MEDIA_LOCATION
value: /usr/src/app/upload
resources:
requests:
cpu: 500m
memory: 1024Mi
limits:
cpu: 3000m
memory: 3072Mi
volumeMounts:
- name: library
mountPath: /usr/src/app/upload
volumes:
- name: library
persistentVolumeClaim:
claimName: immich-library-pvc
---
apiVersion: v1
kind: Service
metadata:
name: immich-server
namespace: media
spec:
type: ClusterIP
ports:
- port: 2283
targetPort: 2283
selector:
app: immich-server
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: immich
namespace: media
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- immich.svo88.space
secretName: immich-svo88-tls
rules:
- host: immich.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: immich-server
port:
number: 2283

View file

@ -0,0 +1,157 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: invidious-credentials
namespace: media
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: invidious-credentials
data:
- secretKey: DB_PASSWORD
remoteRef:
key: secret/invidious
property: DB_PASSWORD
- secretKey: HMAC_KEY
remoteRef:
key: secret/invidious
property: HMAC_KEY
- secretKey: COMPANION_KEY
remoteRef:
key: secret/invidious
property: COMPANION_KEY
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: invidious
namespace: media
labels:
app: invidious
spec:
serviceName: invidious
replicas: 1
selector:
matchLabels:
app: invidious
template:
metadata:
labels:
app: invidious
spec:
enableServiceLinks: false
containers:
- name: invidious
image: quay.io/invidious/invidious:latest
ports:
- containerPort: 3000
name: http
env:
- name: INVIDIOUS_PORT
value: "3000"
- name: PORT
value: "3000"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: invidious-credentials
key: DB_PASSWORD
- name: HMAC_KEY
valueFrom:
secretKeyRef:
name: invidious-credentials
key: HMAC_KEY
- name: COMPANION_KEY
valueFrom:
secretKeyRef:
name: invidious-credentials
key: COMPANION_KEY
- name: INVIDIOUS_CONFIG
value: |
db:
dbname: invidious
user: invidious
password: $(DB_PASSWORD)
host: postgres-core.databases.svc.cluster.local
port: 5432
check_tables: true
domain: yt.svo88.space
external_port: 443
https_only: true
port: 3000
hmac_key: $(HMAC_KEY)
invidious_companion:
- private_url: "http://127.0.0.1:8282/companion"
invidious_companion_key: $(COMPANION_KEY)
default_user_preferences:
dark_mode: true
feed_menu: ["Subscriptions", "Playlists"]
default_home: Subscriptions
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 2000m
memory: 1024Mi
- name: companion
image: quay.io/invidious/invidious-companion:latest
ports:
- containerPort: 8282
env:
- name: SERVER_SECRET_KEY
valueFrom:
secretKeyRef:
name: invidious-credentials
key: COMPANION_KEY
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: invidious
namespace: media
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
name: http
selector:
app: invidious
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: invidious
namespace: media
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- yt.svo88.space
secretName: yt-svo88-tls
rules:
- host: yt.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: invidious
port:
number: 3000

View file

@ -0,0 +1,138 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-media-pv
spec:
capacity:
storage: 1Ti
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- nfsvers=4.2
nfs:
server: 10.0.10.14
path: /media
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-media-pvc
namespace: media
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Ti
volumeName: nfs-media-pv
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-config-pvc
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jellyfin
namespace: media
labels:
app: jellyfin
spec:
serviceName: jellyfin
replicas: 1
selector:
matchLabels:
app: jellyfin
template:
metadata:
labels:
app: jellyfin
spec:
containers:
- name: jellyfin
image: jellyfin/jellyfin:latest
ports:
- containerPort: 8096
name: http
env:
- name: JELLYFIN_PublishedServerUrl
value: "https://jellyfin.svo88.space"
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 4000m
memory: 2048Mi
volumeMounts:
- name: config-storage
mountPath: /config
- name: config-storage
mountPath: /cache
subPath: cache
- name: media-storage
mountPath: /media
- name: torrents-storage
mountPath: /torrents
readOnly: true
volumes:
- name: config-storage
persistentVolumeClaim:
claimName: jellyfin-config-pvc
- name: media-storage
persistentVolumeClaim:
claimName: nfs-media-pvc
- name: torrents-storage
persistentVolumeClaim:
claimName: nfs-torrents-pvc
---
apiVersion: v1
kind: Service
metadata:
name: jellyfin
namespace: media
spec:
type: ClusterIP
ports:
- port: 8096
targetPort: 8096
name: http
selector:
app: jellyfin
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jellyfin
namespace: media
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- jellyfin.svo88.space
secretName: jellyfin-svo88-tls
rules:
- host: jellyfin.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jellyfin
port:
number: 8096

View file

@ -0,0 +1,188 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: karakeep-data-pvc
namespace: tools
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 15Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: karakeep-meili-pvc
namespace: tools
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 5Gi
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: karakeep-credentials
namespace: tools
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: karakeep-credentials
data:
- secretKey: NEXTAUTH_SECRET
remoteRef:
key: secret/karakeep
property: NEXTAUTH_SECRET
- secretKey: MEILI_MASTER_KEY
remoteRef:
key: secret/karakeep
property: MEILI_MASTER_KEY
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: karakeep
namespace: tools
labels:
app: karakeep
spec:
serviceName: karakeep
replicas: 1
selector:
matchLabels:
app: karakeep
template:
metadata:
labels:
app: karakeep
spec:
containers:
- name: web
image: ghcr.io/karakeep-app/karakeep:latest
ports:
- containerPort: 3000
name: http
env:
- name: DATA_DIR
value: "/data"
- name: NEXTAUTH_URL
value: "https://keep.svo88.space"
- name: MEILI_ADDR
value: "http://127.0.0.1:7700"
- name: BROWSER_WEB_URL
value: "http://127.0.0.1:9222"
- name: DISABLE_SIGNUPS
value: "false"
- name: CRAWLER_DOWNLOAD_BANNER_IMAGE
value: "true"
- name: NEXTAUTH_SECRET
valueFrom:
secretKeyRef:
name: karakeep-credentials
key: NEXTAUTH_SECRET
- name: MEILI_MASTER_KEY
valueFrom:
secretKeyRef:
name: karakeep-credentials
key: MEILI_MASTER_KEY
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 2000m
memory: 1024Mi
volumeMounts:
- name: data
mountPath: /data
- name: meilisearch
image: getmeili/meilisearch:v1.11
ports:
- containerPort: 7700
name: meili
env:
- name: MEILI_NO_ANALYTICS
value: "true"
- name: MEILI_MASTER_KEY
valueFrom:
secretKeyRef:
name: karakeep-credentials
key: MEILI_MASTER_KEY
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
volumeMounts:
- name: meili-data
mountPath: /meili_data
- name: chrome
image: ghcr.io/karakeep-app/karakeep-chrome:release
ports:
- containerPort: 9222
name: cdp
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
cpu: 1500m
memory: 1024Mi
volumes:
- name: data
persistentVolumeClaim:
claimName: karakeep-data-pvc
- name: meili-data
persistentVolumeClaim:
claimName: karakeep-meili-pvc
---
apiVersion: v1
kind: Service
metadata:
name: karakeep
namespace: tools
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
name: http
selector:
app: karakeep
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: karakeep
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- keep.svo88.space
secretName: keep-svo88-tls
rules:
- host: keep.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: karakeep
port:
number: 3000

View file

@ -0,0 +1,81 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-kiwix-pv
spec:
capacity:
storage: 200Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- nfsvers=4.2
nfs:
server: 10.0.10.14
path: /media
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-kiwix-pvc
namespace: media
spec:
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 200Gi
volumeName: nfs-kiwix-pv
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kiwix-reader
namespace: media
spec:
serviceName: kiwix-reader
replicas: 1
selector:
matchLabels:
app: kiwix-reader
template:
metadata:
labels:
app: kiwix-reader
spec:
containers:
- name: kiwix-reader
image: ghcr.io/kiwix/kiwix-serve:latest
command:
- /bin/sh
- -c
- |
if ls /data/*.zim 1> /dev/null 2>&1; then
exec kiwix-serve --port 8080 /data/*.zim
else
echo "No .zim archives found in /data. Waiting for files..."
sleep infinity
fi
ports:
- containerPort: 8080
volumeMounts:
- name: kiwix-storage
mountPath: /data
readOnly: true
volumes:
- name: kiwix-storage
persistentVolumeClaim:
claimName: nfs-kiwix-pvc
---
apiVersion: v1
kind: Service
metadata:
name: kiwix-reader
namespace: media
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
selector:
app: kiwix-reader

View file

@ -0,0 +1,34 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- authentik/credentials.yaml
- authentik/deployment.yaml
- core-db/core-db.yaml
- dashboard/deployment.yaml
- homebox/deployment.yaml
- immich/credentials.yaml
- immich/deployment.yaml
- invidious/deployment.yaml
- jellyfin/deployment.yaml
- karakeep/deployment.yaml
- kiwix/deployment.yaml
- metube/deployment.yaml
- netbox/credentials.yaml
- netbox/deployment.yaml
- opencloud/deployment.yaml
- paperless/credentials.yaml
- paperless/deployment.yaml
- printstash/credentials.yaml
- printstash/deployment.yaml
- qbittorrent/deployment.yaml
- searxng/deployment.yaml
- spoolman/deployment.yaml
- sure-finance/deployment.yaml
- tier0/dns.yaml
- tier0/forgejo.yaml
- tier0/pve.yaml
- tier0/vault.yaml
- tier0/zot.yaml
- trilium/deployment.yaml
- vaultwarden/credentials.yaml
- vaultwarden/deployment.yaml

View file

@ -0,0 +1,86 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: metube
namespace: media
labels:
app: metube
spec:
serviceName: metube
replicas: 1
selector:
matchLabels:
app: metube
template:
metadata:
labels:
app: metube
spec:
containers:
- name: metube
image: ghcr.io/alexta69/metube:latest
ports:
- containerPort: 8081
name: http
env:
- name: OUTPUT_TEMPLATE
value: "%(playlist_title)s/%(title)s.%(ext)s"
- name: DOWNLOAD_DIR
value: "/downloads"
- name: STATE_DIR
value: "/downloads/.metube"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 2000m
memory: 512Mi
volumeMounts:
- name: media-storage
mountPath: /downloads
volumes:
- name: media-storage
persistentVolumeClaim:
claimName: nfs-media-pvc
---
apiVersion: v1
kind: Service
metadata:
name: metube
namespace: media
spec:
type: ClusterIP
ports:
- port: 8081
targetPort: 8081
name: http
selector:
app: metube
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: metube
namespace: media
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- metube.svo88.space
secretName: metube-svo88-tls
rules:
- host: metube.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: metube
port:
number: 8081

View file

@ -0,0 +1,21 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: netbox-credentials
namespace: infra
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: netbox-credentials
data:
- secretKey: DB_PASSWORD
remoteRef:
key: secret/netbox
property: DB_PASSWORD
- secretKey: SECRET_KEY
remoteRef:
key: secret/netbox
property: SECRET_KEY

View file

@ -0,0 +1,107 @@
apiVersion: v1
kind: Namespace
metadata:
name: infra
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: netbox-media-pvc
namespace: infra
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: netbox
namespace: infra
spec:
serviceName: netbox
replicas: 1
selector:
matchLabels:
app: netbox
template:
metadata:
labels:
app: netbox
spec:
containers:
- name: netbox
image: netboxcommunity/netbox:latest
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: postgres-core.databases.svc.cluster.local
- name: DB_NAME
value: netbox
- name: DB_USER
value: netbox
- name: REDIS_HOST
value: redis-core.databases.svc.cluster.local
- name: REDIS_CACHE_HOST
value: redis-core.databases.svc.cluster.local
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: netbox-credentials
key: DB_PASSWORD
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: netbox-credentials
key: SECRET_KEY
volumeMounts:
- name: netbox-media
mountPath: /opt/netbox/netbox/media
volumes:
- name: netbox-media
persistentVolumeClaim:
claimName: netbox-media-pvc
---
apiVersion: v1
kind: Service
metadata:
name: netbox
namespace: infra
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
selector:
app: netbox
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: netbox
namespace: infra
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- netbox.svo88.space
secretName: netbox-svo88-tls
rules:
- host: netbox.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: netbox
port:
number: 8080

View file

@ -0,0 +1,158 @@
apiVersion: v1
kind: Namespace
metadata:
name: storage
---
apiVersion: v1
kind: ConfigMap
metadata:
name: opencloud-csp
namespace: storage
data:
csp.yaml: |
directives:
connect-src:
- "'self'"
- "blob:"
- "https://auth.svo88.space"
- "https://raw.githubusercontent.com/opencloud-eu/awesome-apps/"
- "https://update.opencloud.eu/"
frame-src:
- "'self'"
- "https://auth.svo88.space"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: opencloud-data-pvc
namespace: storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 800Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: opencloud
namespace: storage
spec:
serviceName: opencloud
replicas: 1
selector:
matchLabels:
app: opencloud
template:
metadata:
labels:
app: opencloud
spec:
securityContext:
fsGroup: 1000
containers:
- name: opencloud
image: opencloudeu/opencloud-rolling:8.0.1
command: ["/bin/sh", "-c", "opencloud init || true; exec opencloud server"]
ports:
- containerPort: 9200
env:
- name: OC_URL
value: "https://opencloud.svo88.space"
- name: OC_INSECURE
value: "false"
- name: PROXY_TLS
value: "false"
- name: PROXY_HTTP_ADDR
value: "0.0.0.0:9200"
- name: PROXY_CSP_CONFIG_FILE_LOCATION
value: "/etc/opencloud-config/csp.yaml"
- name: OC_OIDC_ISSUER
value: "https://auth.svo88.space/application/o/opencloud/"
- name: WEB_OIDC_CLIENT_ID
value: "web"
- name: WEBFINGER_ANDROID_OIDC_CLIENT_ID
value: "web"
- name: WEBFINGER_ANDROID_OIDC_CLIENT_SCOPES
value: "openid profile email offline_access"
- name: PROXY_OIDC_REWRITE_WELLKNOWN
value: "true"
- name: PROXY_AUTOPROVISION_ACCOUNTS
value: "true"
- name: PROXY_USER_OIDC_CLAIM
value: "preferred_username"
- name: OC_EXCLUDE_RUN_SERVICES
value: "idp"
- name: PROXY_TRANSPORT_READ_TIMEOUT
value: "3600s"
- name: PROXY_TRANSPORT_WRITE_TIMEOUT
value: "3600s"
- name: STORAGE_USERS_DATA_SERVER_READ_TIMEOUT
value: "3600s"
- name: STORAGE_USERS_DATA_SERVER_WRITE_TIMEOUT
value: "3600s"
resources:
requests:
cpu: 500m
memory: 1024Mi
limits:
cpu: 2000m
memory: 2048Mi
volumeMounts:
- name: opencloud-data
mountPath: /var/lib/opencloud
subPath: data
- name: opencloud-data
mountPath: /etc/opencloud
subPath: config
- name: opencloud-csp-config
mountPath: /etc/opencloud-config/csp.yaml
subPath: csp.yaml
volumes:
- name: opencloud-data
persistentVolumeClaim:
claimName: opencloud-data-pvc
- name: opencloud-csp-config
configMap:
name: opencloud-csp
---
apiVersion: v1
kind: Service
metadata:
name: opencloud
namespace: storage
spec:
type: ClusterIP
ports:
- port: 9200
targetPort: 9200
selector:
app: opencloud
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: opencloud
namespace: storage
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- opencloud.svo88.space
secretName: opencloud-svo88-tls
rules:
- host: opencloud.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: opencloud
port:
number: 9200

View file

@ -0,0 +1,21 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: paperless-credentials
namespace: documents
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: paperless-credentials
data:
- secretKey: PAPERLESS_DBPASS
remoteRef:
key: secret/paperless
property: PAPERLESS_DBPASS
- secretKey: PAPERLESS_SECRET_KEY
remoteRef:
key: secret/paperless
property: PAPERLESS_SECRET_KEY

View file

@ -0,0 +1,135 @@
apiVersion: v1
kind: Namespace
metadata:
name: documents
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: paperless-data-pvc
namespace: documents
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 30Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: paperless
namespace: documents
spec:
serviceName: paperless
replicas: 1
selector:
matchLabels:
app: paperless
template:
metadata:
labels:
app: paperless
spec:
enableServiceLinks: false
initContainers:
- name: clear-migration-lock
image: busybox:latest
command: ["sh", "-c", "rm -f /data/migration_lock"]
volumeMounts:
- name: paperless-data
mountPath: /data
subPath: data
containers:
- name: paperless-ngx
image: ghcr.io/paperless-ngx/paperless-ngx:latest
ports:
- containerPort: 8000
env:
- name: PAPERLESS_PORT
value: "8000"
- name: PAPERLESS_URL
value: "https://docs.svo88.space"
- name: PAPERLESS_CSRF_TRUSTED_ORIGINS
value: "https://docs.svo88.space"
- name: PAPERLESS_TRUSTED_PROXIES
value: "10.0.0.0/8,10.244.0.0/16"
- name: PAPERLESS_REDIS
value: redis://redis-core.databases.svc.cluster.local:6379
- name: PAPERLESS_DBENGINE
value: postgresql
- name: PAPERLESS_DBHOST
value: postgres-core.databases.svc.cluster.local
- name: PAPERLESS_DBNAME
value: paperless
- name: PAPERLESS_DBUSER
value: paperless
- name: PAPERLESS_OCR_LANGUAGES
value: "rus"
- name: PAPERLESS_OCR_LANGUAGE
value: rus+eng
- name: PAPERLESS_DBPASS
valueFrom:
secretKeyRef:
name: paperless-credentials
key: PAPERLESS_DBPASS
- name: PAPERLESS_SECRET_KEY
valueFrom:
secretKeyRef:
name: paperless-credentials
key: PAPERLESS_SECRET_KEY
volumeMounts:
- name: paperless-data
mountPath: /usr/src/paperless/data
subPath: data
- name: paperless-data
mountPath: /usr/src/paperless/media
subPath: media
- name: paperless-data
mountPath: /usr/src/paperless/export
subPath: export
volumes:
- name: paperless-data
persistentVolumeClaim:
claimName: paperless-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: paperless
namespace: documents
spec:
type: ClusterIP
ports:
- port: 8000
targetPort: 8000
selector:
app: paperless
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: paperless
namespace: documents
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- paperless.svo88.space
secretName: paperless-svo88-tls
rules:
- host: paperless.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: paperless
port:
number: 8000

View file

@ -0,0 +1,17 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: printstash-credentials
namespace: tools
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: printstash-credentials
data:
- secretKey: DB_PASSWORD
remoteRef:
key: secret/printstash
property: DB_PASSWORD

View file

@ -0,0 +1,124 @@
apiVersion: v1
kind: Namespace
metadata:
name: tools
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: printstash-models-pv
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- nfsvers=4.2
nfs:
server: 10.0.10.14
path: /models
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: printstash-models-pvc
namespace: tools
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
volumeName: printstash-models-pv
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: printstash
namespace: tools
spec:
serviceName: printstash
replicas: 1
selector:
matchLabels:
app: printstash
template:
metadata:
labels:
app: printstash
spec:
containers:
- name: printstash
image: ghcr.io/xiao-villamor/printstash:latest
ports:
- containerPort: 8080
env:
- name: VAULT_SETUP_MODE
value: "trusted_network"
- name: VAULT_SETUP_ALLOWED_HOSTS
value: "printstash.svo88.space"
- name: VAULT_SESSION_COOKIE_SECURE
value: "true"
- name: FORWARDED_ALLOW_IPS
value: "*"
- name: DB_HOST
value: postgres-core.databases.svc.cluster.local
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: printstash
- name: DB_USER
value: printstash
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: printstash-credentials
key: DB_PASSWORD
volumeMounts:
- name: models-storage
mountPath: /data
volumes:
- name: models-storage
persistentVolumeClaim:
claimName: printstash-models-pvc
---
apiVersion: v1
kind: Service
metadata:
name: printstash
namespace: tools
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 3000
selector:
app: printstash
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: printstash
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- printstash.svo88.space
secretName: printstash-svo88-tls
rules:
- host: printstash.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: printstash
port:
number: 8080

View file

@ -0,0 +1,102 @@
apiVersion: v1
kind: Namespace
metadata:
name: media
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-torrents-pv
spec:
capacity:
storage: 500Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- nfsvers=4.2
nfs:
server: 10.0.10.14
path: /torrents
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-torrents-pvc
namespace: media
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Gi
volumeName: nfs-torrents-pv
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: qbittorrent
namespace: media
spec:
serviceName: qbittorrent
replicas: 1
selector:
matchLabels:
app: qbittorrent
template:
metadata:
labels:
app: qbittorrent
spec:
containers:
- name: qbittorrent
image: lscr.io/linuxserver/qbittorrent:latest
ports:
- containerPort: 8080
name: webui
- containerPort: 6881
protocol: TCP
name: torrent-tcp
- containerPort: 6881
protocol: UDP
name: torrent-udp
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: WEBUI_PORT
value: "8080"
volumeMounts:
- name: torrents-storage
mountPath: /downloads
subPath: downloads
- name: torrents-storage
mountPath: /config
subPath: config
volumes:
- name: torrents-storage
persistentVolumeClaim:
claimName: nfs-torrents-pvc
---
apiVersion: v1
kind: Service
metadata:
name: qbittorrent
namespace: media
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
name: webui
- port: 6881
targetPort: 6881
protocol: TCP
name: torrent-tcp
- port: 6881
targetPort: 6881
protocol: UDP
name: torrent-udp
selector:
app: qbittorrent

View file

@ -0,0 +1,89 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: searxng-credentials
namespace: tools
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: searxng-credentials
data:
- secretKey: SEARXNG_SECRET
remoteRef:
key: secret/searxng
property: SEARXNG_SECRET
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: searxng
namespace: tools
spec:
serviceName: searxng
replicas: 1
selector:
matchLabels:
app: searxng
template:
metadata:
labels:
app: searxng
spec:
containers:
- name: searxng
image: searxng/searxng:latest
ports:
- containerPort: 8080
env:
- name: INSTANCE_NAME
value: "Sovereign Search"
- name: BASE_URL
value: "https://search.svo88.space"
- name: SEARXNG_SECRET
valueFrom:
secretKeyRef:
name: searxng-credentials
key: SEARXNG_SECRET
---
apiVersion: v1
kind: Service
metadata:
name: searxng
namespace: tools
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
selector:
app: searxng
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: searxng
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- search.svo88.space
secretName: search-svo88-tls
rules:
- host: search.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: searxng
port:
number: 8080

View file

@ -0,0 +1,83 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: spoolman-data-pvc
namespace: tools
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: spoolman
namespace: tools
spec:
serviceName: spoolman
replicas: 1
selector:
matchLabels:
app: spoolman
template:
metadata:
labels:
app: spoolman
spec:
securityContext:
fsGroup: 1000
containers:
- name: spoolman
image: ghcr.io/donkie/spoolman:latest
ports:
- containerPort: 7912
volumeMounts:
- name: spoolman-data
mountPath: /home/app/.local/share/spoolman
volumes:
- name: spoolman-data
persistentVolumeClaim:
claimName: spoolman-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: spoolman
namespace: tools
spec:
type: ClusterIP
ports:
- port: 7912
targetPort: 8000
selector:
app: spoolman
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spoolman
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- spoolman.svo88.space
secretName: spoolman-svo88-tls
rules:
- host: spoolman.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spoolman
port:
number: 7912

View file

@ -0,0 +1,108 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sure-finance
namespace: tools
spec:
serviceName: sure-finance
replicas: 1
selector:
matchLabels:
app: sure-finance
template:
metadata:
labels:
app: sure-finance
spec:
containers:
- name: sure-finance
image: ghcr.io/we-promise/sure:latest
ports:
- containerPort: 3000
env:
- name: SELF_HOSTED
value: "true"
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: sure-credentials
key: SECRET_KEY_BASE
- name: DB_HOST
value: postgres-core.databases.svc.cluster.local
- name: POSTGRES_HOST
value: postgres-core.databases.svc.cluster.local
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_DB
value: sure_finance
- name: POSTGRES_USER
value: sure
- name: REDIS_URL
value: redis://redis-core.databases.svc.cluster.local:6379/1
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: sure-credentials
key: DB_PASSWORD
---
apiVersion: v1
kind: Service
metadata:
name: sure-finance
namespace: tools
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
selector:
app: sure-finance
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: sure-credentials
namespace: tools
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: sure-credentials
data:
- secretKey: DB_PASSWORD
remoteRef:
key: secret/sure
property: DB_PASSWORD
- secretKey: SECRET_KEY_BASE
remoteRef:
key: secret/sure
property: SECRET_KEY_BASE
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sure-finance
namespace: tools
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- finance.svo88.space
secretName: finance-svo88-tls
rules:
- host: finance.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sure-finance
port:
number: 3000

View file

@ -0,0 +1,49 @@
apiVersion: v1
kind: Service
metadata:
name: technitium-dns
namespace: infra
spec:
ports:
- name: http
port: 5380
targetPort: 5380
---
apiVersion: v1
kind: Endpoints
metadata:
name: technitium-dns
namespace: infra
subsets:
- addresses:
- ip: 10.0.10.11
ports:
- name: http
port: 5380
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: technitium-dns
namespace: infra
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-local-only@kubernetescrd,traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- dns.svo88.space
secretName: dns-svo88-tls
rules:
- host: dns.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: technitium-dns
port:
number: 5380

View file

@ -0,0 +1,48 @@
apiVersion: v1
kind: Service
metadata:
name: forgejo-git
namespace: infra
spec:
ports:
- name: http
port: 3000
targetPort: 3000
---
apiVersion: v1
kind: Endpoints
metadata:
name: forgejo-git
namespace: infra
subsets:
- addresses:
- ip: 10.0.10.13
ports:
- name: http
port: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: forgejo-git
namespace: infra
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- git.svo88.space
secretName: git-svo88-tls
rules:
- host: git.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: forgejo-git
port:
number: 3000

View file

@ -0,0 +1,52 @@
apiVersion: v1
kind: Service
metadata:
name: proxmox-ve
namespace: infra
annotations:
traefik.ingress.kubernetes.io/service.serversscheme: https
traefik.ingress.kubernetes.io/service.serverstransport: traefik-pve-transport@kubernetescrd
spec:
ports:
- name: https
port: 8006
targetPort: 8006
---
apiVersion: v1
kind: Endpoints
metadata:
name: proxmox-ve
namespace: infra
subsets:
- addresses:
- ip: 192.168.1.177
ports:
- name: https
port: 8006
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: proxmox-ve
namespace: infra
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-local-only@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- pve.svo88.space
secretName: pve-svo88-tls
rules:
- host: pve.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: proxmox-ve
port:
number: 8006

View file

@ -0,0 +1,49 @@
apiVersion: v1
kind: Service
metadata:
name: openbao-vault
namespace: infra
spec:
ports:
- name: http
port: 8200
targetPort: 8200
---
apiVersion: v1
kind: Endpoints
metadata:
name: openbao-vault
namespace: infra
subsets:
- addresses:
- ip: 10.0.10.12
ports:
- name: http
port: 8200
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openbao-vault
namespace: infra
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-local-only@kubernetescrd,traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- vault.svo88.space
secretName: vault-svo88-tls
rules:
- host: vault.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openbao-vault
port:
number: 8200

View file

@ -0,0 +1,49 @@
apiVersion: v1
kind: Service
metadata:
name: zot-registry
namespace: infra
spec:
ports:
- name: http
port: 5000
targetPort: 5000
---
apiVersion: v1
kind: Endpoints
metadata:
name: zot-registry
namespace: infra
subsets:
- addresses:
- ip: 10.0.10.15
ports:
- name: http
port: 5000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: zot-registry
namespace: infra
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-local-only@kubernetescrd,traefik-authentik@kubernetescrd
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- zot.svo88.space
secretName: zot-svo88-tls
rules:
- host: zot.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: zot-registry
port:
number: 5000

View file

@ -0,0 +1,99 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: trilium-data-pvc
namespace: documents
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: trilium
namespace: documents
labels:
app: trilium
spec:
serviceName: trilium
replicas: 1
selector:
matchLabels:
app: trilium
template:
metadata:
labels:
app: trilium
spec:
enableServiceLinks: false
securityContext:
fsGroup: 1000
containers:
- name: trilium
image: ghcr.io/triliumnext/trilium:latest
ports:
- containerPort: 8080
name: http
env:
- name: TRILIUM_DATA_DIR
value: "/home/node/trilium-data"
- name: TRILIUM_PORT
value: "8080"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1500m
memory: 512Mi
volumeMounts:
- name: data
mountPath: /home/node/trilium-data
volumes:
- name: data
persistentVolumeClaim:
claimName: trilium-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: trilium
namespace: documents
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
name: http
selector:
app: trilium
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: trilium
namespace: documents
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- trilium.svo88.space
secretName: trilium-svo88-tls
rules:
- host: trilium.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: trilium
port:
number: 8080

View file

@ -0,0 +1,17 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: vaultwarden-credentials
namespace: security
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: vaultwarden-credentials
data:
- secretKey: ADMIN_TOKEN
remoteRef:
key: secret/vaultwarden
property: ADMIN_TOKEN

View file

@ -0,0 +1,103 @@
apiVersion: v1
kind: Namespace
metadata:
name: security
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vaultwarden-data-pvc
namespace: security
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vaultwarden
namespace: security
spec:
serviceName: vaultwarden
replicas: 1
selector:
matchLabels:
app: vaultwarden
template:
metadata:
labels:
app: vaultwarden
spec:
containers:
- name: vaultwarden
image: vaultwarden/server:latest
ports:
- containerPort: 80
env:
- name: SIGNUPS_ALLOWED
value: "false"
- name: INVITATIONS_ALLOWED
value: "false"
- name: SHOW_PASSWORD_HINT
value: "false"
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: vaultwarden-credentials
key: ADMIN_TOKEN
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: vaultwarden-data
mountPath: /data
volumes:
- name: vaultwarden-data
persistentVolumeClaim:
claimName: vaultwarden-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: vaultwarden
namespace: security
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: vaultwarden
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vaultwarden
namespace: security
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- vw.svo88.space
secretName: vw-svo88-tls
rules:
- host: vw.svo88.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vaultwarden
port:
number: 80

View file

@ -0,0 +1,42 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: atlas-repo
namespace: flux-system
spec:
interval: 1m
url: http://10.0.10.13:3000/atlas/atlas.git
ref:
branch: master
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infra-sync
namespace: flux-system
spec:
interval: 10m
path: ./kubernetes/infrastructure
prune: true
wait: true
timeout: 5m
sourceRef:
kind: GitRepository
name: atlas-repo
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps-sync
namespace: flux-system
spec:
interval: 10m
path: ./kubernetes/apps
prune: true
wait: false
timeout: 10m
dependsOn:
- name: infra-sync
sourceRef:
kind: GitRepository
name: atlas-repo

View file

@ -0,0 +1,20 @@
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: atlas@svo88.space
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
ingressClassName: traefik
ingressTemplate:
metadata:
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
---

View file

@ -0,0 +1,60 @@
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: cert-manager
namespace: cert-manager
spec:
interval: 24h
url: https://charts.jetstack.io
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cert-manager
namespace: cert-manager
spec:
interval: 1h
chart:
spec:
chart: cert-manager
sourceRef:
kind: HelmRepository
name: cert-manager
install:
createNamespace: true
crds: CreateReplace
upgrade:
crds: CreateReplace
values:
installCRDs: true
extraArgs:
- --dns01-recursive-nameservers-only
- --dns01-recursive-nameservers=10.0.10.11:53
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
webhook:
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
cainjector:
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi

View file

@ -0,0 +1,70 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: cilium
namespace: kube-system
spec:
interval: 24h
url: https://helm.cilium.io/
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cilium
namespace: kube-system
spec:
interval: 1h
chart:
spec:
chart: cilium
version: "1.15.5"
sourceRef:
kind: HelmRepository
name: cilium
install:
crds: CreateReplace
upgrade:
crds: CreateReplace
values:
k8sServiceHost: 127.0.0.1
k8sServicePort: 7445
kubeProxyReplacement: true
l2announcements:
enabled: true
devices: "ens+"
ipam:
mode: kubernetes
cgroup:
autoMount:
enabled: false
hostRoot: /sys/fs/cgroup
securityContext:
capabilities:
ciliumAgent:
- CHOWN
- KILL
- NET_ADMIN
- NET_RAW
- IPC_LOCK
- SYS_ADMIN
- SYS_RESOURCE
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
cleanCiliumState:
- NET_ADMIN
- SYS_ADMIN
- SYS_RESOURCE
image:
repository: quay.io/cilium/cilium
useDigest: false
operator:
image:
repository: quay.io/cilium/operator
useDigest: false
envoy:
image:
repository: quay.io/cilium/cilium-envoy
useDigest: false
---

View file

@ -0,0 +1,20 @@
apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
name: lan-ingress-pool
spec:
blocks:
- cidr: 10.0.30.200/32
---
apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
name: lan-fastpath-announcement
spec:
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
interfaces:
- ens19
loadBalancerIPs: true

View file

@ -0,0 +1,40 @@
k8sServiceHost: 127.0.0.1
k8sServicePort: 7445
kubeProxyReplacement: true
l2announcements:
enabled: true
ipam:
mode: kubernetes
cgroup:
autoMount:
enabled: false
hostRoot: /sys/fs/cgroup
securityContext:
capabilities:
ciliumAgent:
- CHOWN
- KILL
- NET_ADMIN
- NET_RAW
- IPC_LOCK
- SYS_ADMIN
- SYS_RESOURCE
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
cleanCiliumState:
- NET_ADMIN
- SYS_ADMIN
- SYS_RESOURCE
image:
repository: quay.io/cilium/cilium
useDigest: false
operator:
image:
repository: quay.io/cilium/operator
useDigest: false
envoy:
image:
repository: quay.io/cilium/cilium-envoy
useDigest: false

View file

@ -0,0 +1,31 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: external-secrets
namespace: external-secrets
spec:
interval: 24h
url: https://charts.external-secrets.io
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: external-secrets
namespace: external-secrets
spec:
interval: 1h
chart:
spec:
chart: external-secrets
sourceRef:
kind: HelmRepository
name: external-secrets
install:
crds: CreateReplace
upgrade:
crds: CreateReplace
values:
installCRDs: true
metrics:
service:
enabled: true

View file

@ -0,0 +1,45 @@
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: openbao-backend
spec:
provider:
vault:
server: http://10.0.10.12:8200
version: v2
auth:
tokenSecretRef:
name: openbao-token
key: token
namespace: external-secrets
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: proxmox-csi-config
namespace: kube-system
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: ClusterSecretStore
target:
name: proxmox-csi-config
template:
data:
config.yaml: |
clusters:
- url: https://192.168.1.177:8006/api2/json
insecure: true
token_id: "{{ .token_id }}"
token_secret: "{{ .token_secret }}"
region: atlas
data:
- secretKey: token_id
remoteRef:
key: secret/proxmox-csi
property: token_id
- secretKey: token_secret
remoteRef:
key: secret/proxmox-csi
property: token_secret

View file

@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cilium/helm-release.yaml
- cilium/l2-policy.yaml
- proxmox-csi/helm-release.yaml
- external-secrets/helm-release.yaml
- external-secrets/proxmox-csi-secret.yaml
- traefik/deployment.yaml
- cert-manager/helm-release.yaml
- cert-manager/cluster-issuer.yaml

View file

@ -0,0 +1,185 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: loki-data-pvc
namespace: monitoring
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 30Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
namespace: monitoring
data:
loki.yaml: |
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
securityContext:
fsGroup: 10001
containers:
- name: loki
image: grafana/loki:latest
args:
- -config.file=/etc/loki/loki.yaml
ports:
- containerPort: 3100
volumeMounts:
- name: config
mountPath: /etc/loki
- name: storage
mountPath: /loki
volumes:
- name: config
configMap:
name: loki-config
- name: storage
persistentVolumeClaim:
claimName: loki-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: loki
namespace: monitoring
spec:
type: ClusterIP
ports:
- port: 3100
targetPort: 3100
selector:
app: loki
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vector-agent
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: vector-agent
rules:
- apiGroups: [""]
resources: ["namespaces", "nodes", "pods"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vector-agent
subjects:
- kind: ServiceAccount
name: vector-agent
namespace: monitoring
roleRef:
kind: ClusterRole
name: vector-agent
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vector-config
namespace: monitoring
data:
vector.yaml: |
data_dir: "/var/lib/vector"
sources:
kubernetes_logs:
type: "kubernetes_logs"
sinks:
loki:
type: "loki"
inputs:
- "kubernetes_logs"
endpoint: "http://loki.monitoring.svc.cluster.local:3100"
encoding:
codec: "json"
labels:
pod_namespace: "{{ kubernetes.pod_namespace }}"
pod_name: "{{ kubernetes.pod_name }}"
container_name: "{{ kubernetes.container_name }}"
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: vector-agent
namespace: monitoring
spec:
selector:
matchLabels:
app: vector-agent
template:
metadata:
labels:
app: vector-agent
spec:
serviceAccountName: vector-agent
containers:
- name: vector
image: timberio/vector:latest-alpine
env:
- name: VECTOR_CONFIG
value: /etc/vector/vector.yaml
volumeMounts:
- name: vector-config
mountPath: /etc/vector
- name: vector-data
mountPath: /var/lib/vector
- name: var-log-pods
mountPath: /var/log/pods
readOnly: true
volumes:
- name: vector-config
configMap:
name: vector-config
- name: vector-data
emptyDir:
medium: Memory
- name: var-log-pods
hostPath:
path: /var/log/pods

View file

@ -0,0 +1,125 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: victoria-metrics-pvc
namespace: monitoring
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-data-pvc
namespace: monitoring
spec:
accessModes:
- ReadWriteOnce
storageClassName: proxmox-csi-general
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: victoriametrics
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: victoriametrics
template:
metadata:
labels:
app: victoriametrics
spec:
containers:
- name: victoriametrics
image: victoriametrics/victoria-metrics:latest
args:
- -storageDataPath=/vmagent-data
- -retentionPeriod=1y
ports:
- containerPort: 8428
volumeMounts:
- name: victoria-metrics-data
mountPath: /vmagent-data
volumes:
- name: victoria-metrics-data
persistentVolumeClaim:
claimName: victoria-metrics-pvc
---
apiVersion: v1
kind: Service
metadata:
name: victoriametrics
namespace: monitoring
spec:
type: ClusterIP
ports:
- port: 8428
targetPort: 8428
selector:
app: victoriametrics
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
securityContext:
fsGroup: 472
containers:
- name: grafana
image: grafana/grafana:latest
ports:
- containerPort: 3000
env:
- name: GF_SECURITY_ADMIN_USER
value: "admin"
- name: GF_SECURITY_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: grafana-credentials
key: GF_SECURITY_ADMIN_PASSWORD
volumeMounts:
- name: grafana-data
mountPath: /var/lib/grafana
volumes:
- name: grafana-data
persistentVolumeClaim:
claimName: grafana-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: monitoring
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
selector:
app: grafana

View file

@ -0,0 +1,43 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: proxmox-csi-plugin
namespace: kube-system
spec:
interval: 24h
type: oci
url: oci://ghcr.io/sergelogvinov/charts
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: proxmox-csi-plugin
namespace: kube-system
spec:
interval: 1h
chart:
spec:
chart: proxmox-csi-plugin
version: "0.5.12"
sourceRef:
kind: HelmRepository
name: proxmox-csi-plugin
install:
crds: CreateReplace
upgrade:
crds: CreateReplace
values:
existingConfigSecret: proxmox-csi-config
storageClass:
- name: proxmox-csi-db
storage: astra
cache: none
ssd: true
storageFormat: raw
blockSize: "16384"
- name: proxmox-csi-general
storage: astra
cache: none
ssd: true
storageFormat: raw
blockSize: "65536"

View file

@ -0,0 +1,169 @@
apiVersion: v1
kind: Namespace
metadata:
name: traefik
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik
namespace: traefik
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: traefik-ingress-controller
rules:
- apiGroups: [""]
resources: ["services", "endpoints", "secrets", "nodes", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
- apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses", "ingressclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses/status"]
verbs: ["update"]
- apiGroups: ["traefik.io", "traefik.containo.us"]
resources: ["*"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: traefik-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
name: traefik
namespace: traefik
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: traefik.io/ingress-controller
---
apiVersion: v1
kind: Service
metadata:
name: traefik
namespace: traefik
spec:
type: LoadBalancer
externalTrafficPolicy: Local
loadBalancerIP: 10.0.30.200
ports:
- name: web
port: 80
targetPort: 8000
- name: websecure
port: 443
targetPort: 8443
selector:
app: traefik
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: traefik
namespace: traefik
spec:
serviceName: traefik
replicas: 2
selector:
matchLabels:
app: traefik
template:
metadata:
labels:
app: traefik
spec:
serviceAccountName: traefik
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: traefik
topologyKey: "kubernetes.io/hostname"
containers:
- name: traefik
image: docker.io/traefik:v3.7.13
args:
- "--entryPoints.web.address=:8000/tcp"
- "--entryPoints.web.allowACMEByPass=true"
- "--entryPoints.websecure.address=:8443/tcp"
- "--entryPoints.websecure.http.tls=true"
- "--entryPoints.web.http.redirections.entryPoint.to=:443"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https"
- "--entryPoints.web.http.redirections.entryPoint.permanent=true"
- "--providers.kubernetesingress.allowEmptyServices=true"
- "--providers.kubernetesingress.ingressendpoint.publishedservice=traefik/traefik"
- "--entryPoints.websecure.transport.respondingTimeouts.readTimeout=3600s"
- "--entryPoints.websecure.transport.respondingTimeouts.writeTimeout=3600s"
- "--entryPoints.websecure.transport.respondingTimeouts.idleTimeout=3600s"
- "--providers.kubernetesingress=true"
- "--providers.kubernetesingress.ingressclass=traefik"
- "--providers.kubernetescrd=true"
- "--api.dashboard=true"
- "--ping=true"
- "--log.level=INFO"
ports:
- name: web
containerPort: 8000
- name: websecure
containerPort: 8443
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 1000m
memory: 400Mi
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authentik
namespace: traefik
spec:
forwardAuth:
address: http://authentik-server.authentik.svc.cluster.local:80/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: local-only
namespace: traefik
spec:
ipAllowList:
sourceRange:
- 127.0.0.1/32
- 192.168.1.0/24
- 10.0.10.0/24
- 10.0.30.64/26
- 10.0.30.128/26
---
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
name: pve-transport
namespace: traefik
spec:
insecureSkipVerify: true

31
repomix.config.json Normal file
View file

@ -0,0 +1,31 @@
{
"output": {
"filePath": "repomix-output.xml",
"style": "xml",
"removeComments": false,
"removeEmptyLines": true,
"showLineNumbers": false,
"copyToClipboard": true
},
"include": [
"**/*",
"docs/state/**"
],
"ignore": {
"useGitignore": true,
"useDefaultPatterns": true,
"customPatterns": [
"!docs/state/**",
"**/.terraform/**",
"**/.terraform.lock.hcl",
"**/*.tfstate*",
"**/terraform.tfvars",
"vpn/*.priv",
"**/*.priv",
"**/*.key"
]
},
"security": {
"enableSecurityCheck": true
}
}

89
scripts/collect_state.sh Executable file
View file

@ -0,0 +1,89 @@
#!/bin/bash
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
STATE_DIR="${REPO_ROOT}/scripts/state"
STATE_OUT_DIR="${REPO_ROOT}/docs/state"
TARGET="${1:-all}"
MODULES=(
"00_workstation.sh"
"01_pve.sh"
"02_vps.sh"
"03_vyos.sh"
"04_tier0.sh"
"05_k8s.sh"
)
mkdir -p "${STATE_OUT_DIR}"
generate_header() {
local mod_name="$1"
local timestamp
local branch
local short_hash
local commit_msg
timestamp=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
branch=$(git -C "${REPO_ROOT}" branch --show-current 2>/dev/null || echo "unknown")
short_hash=$(git -C "${REPO_ROOT}" rev-parse --short HEAD 2>/dev/null || echo "none")
commit_msg=$(git -C "${REPO_ROOT}" log -1 --format='%s' 2>/dev/null || echo "none")
cat <<EOF
# Sovereign C2 Runtime: ${mod_name}
**Срез:** ${timestamp} | **Git:** ${branch}@${short_hash} ("${commit_msg}")
EOF
}
run_single_module() {
local mod="$1"
local mod_name="${mod%.sh}"
local script="${STATE_DIR}/${mod}"
local out_file="${STATE_OUT_DIR}/${mod_name}.md"
local tmp_file="${out_file}.tmp"
echo "==> Сбор среза: ${mod_name} -> docs/state/${mod_name}.md..."
generate_header "${mod_name}" > "${tmp_file}"
if [[ -x "${script}" ]]; then
"${script}" >> "${tmp_file}"
else
bash "${script}" >> "${tmp_file}"
fi
mv "${tmp_file}" "${out_file}"
echo "✔ Срез ${mod_name} сохранен в docs/state/${mod_name}.md"
}
if [[ "${TARGET}" == "all" ]]; then
echo "==> Сбор полного снимка Sovereign C2 (все модули)..."
for mod in "${MODULES[@]}"; do
run_single_module "${mod}"
done
echo "✔ Все модули среза успешно обновлены в docs/state/"
else
case "${TARGET}" in
00_workstation|00_workstation.sh|workstation|host)
run_single_module "00_workstation.sh"
;;
01_pve|01_pve.sh|pve)
run_single_module "01_pve.sh"
;;
02_vps|02_vps.sh|vps)
run_single_module "02_vps.sh"
;;
03_vyos|03_vyos.sh|vyos|net)
run_single_module "03_vyos.sh"
;;
04_tier0|04_tier0.sh|tier0)
run_single_module "04_tier0.sh"
;;
05_k8s|05_k8s.sh|k8s|talos)
run_single_module "05_k8s.sh"
;;
*)
echo "Неизвестный модуль: ${TARGET}"
echo "Доступны: all, workstation (host), pve, vps, vyos (net), tier0, k8s (talos)"
exit 1
;;
esac
fi

9
scripts/layers/00_host.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
ensure_prerequisites
load_secrets_to_ram
log_info "[Layer 0: PVE Host] Накатка директорий и прав ZFS..."
ansible-playbook -i "${REPO_ROOT}/ansible/inventory.yml" "${REPO_ROOT}/ansible/playbooks/01_pve_host_day0.yml"
log_ok "Layer 0 готов."

9
scripts/layers/01_net.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
ensure_prerequisites
load_secrets_to_ram
log_info "[Layer 1: VyOS & Perimeter] Накатка конфигурации сети..."
ansible-playbook -i "${REPO_ROOT}/ansible/inventory.yml" "${REPO_ROOT}/ansible/playbooks/02_vyos_network_day0.yml"
log_ok "Layer 1 готов."

26
scripts/layers/02_tier0.sh Executable file
View file

@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
ensure_prerequisites
load_secrets_to_ram
log_info "[Layer 2: Tier-0 Air-Gap] Подъем базовых контейнеров через Tofu..."
cd "${REPO_ROOT}/tofu" && tofu apply -target=proxmox_virtual_environment_container.dns0 \
-target=proxmox_virtual_environment_container.vault0 \
-target=proxmox_virtual_environment_container.git0 \
-target=proxmox_virtual_environment_container.storage0 \
-target=proxmox_virtual_environment_container.registry0 -auto-approve
log_info "[Layer 2: Tier-0 Air-Gap] Применение ZFS-маунтов и прав на хосте Proxmox..."
ansible-playbook -i "${REPO_ROOT}/ansible/inventory.yml" "${REPO_ROOT}/ansible/playbooks/01_pve_host_day0.yml"
log_info "[Layer 2: Tier-0 Air-Gap] Конфигурация базовых сервисов..."
for pb in 03_zot_registry.yml 04_technitium_dns.yml 05_openbao_vault.yml 06_forgejo_git.yml 07_nas_storage.yml; do
ansible-playbook -i "${REPO_ROOT}/ansible/inventory.yml" "${REPO_ROOT}/ansible/playbooks/${pb}"
done
unseal_vault
log_info "[Layer 2: Tier-0 Air-Gap] Доведение полного стейта Tofu..."
cd "${REPO_ROOT}/tofu" && tofu apply -auto-approve
log_ok "Layer 2 готов."

17
scripts/layers/03_talos.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
ensure_prerequisites
load_secrets_to_ram
unseal_vault
log_info "[Layer 3: Talos & Kubernetes] Проверка конфигураций машинных манифестов..."
if [[ ! -f "${REPO_ROOT}/talos/clusterconfig/controlplane.yaml" ]]; then
log_info "Генерация машинных конфигураций Talos..."
cd "${REPO_ROOT}" && talos/generate_config.sh
fi
log_info "[Layer 3: Talos & Kubernetes] Бутстрап кластера..."
cd "${REPO_ROOT}/talos" && ./bootstrap.sh
sync_k8s_vault_token
log_ok "Layer 3 готов."

8
scripts/layers/04_apps.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
ensure_prerequisites
log_info "[Layer 4: Apps] Применение манифестов FluxCD..."
kubectl apply -f "${REPO_ROOT}/kubernetes/bootstrap/sync.yaml"
log_ok "Layer 4 готов."

37
scripts/layers/gc.sh Executable file
View file

@ -0,0 +1,37 @@
#!/bin/bash
set -uo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
ensure_prerequisites
log_info "🧹 Запуск генеральной очистки кластера (dep gc)..."
# 1. Удаление зомби-нод в статусе NotReady
NOT_READY_NODES=$(kubectl get nodes --no-headers 2>/dev/null | grep 'NotReady' | awk '{print $1}' || true)
if [[ -n "${NOT_READY_NODES}" ]]; then
for node in ${NOT_READY_NODES}; do
log_warn "Удаляем мертвую ноду: ${node}"
kubectl delete node "${node}" --timeout=5s 2>/dev/null || true
done
fi
# 2. Мгновенная зачистка зависших в Terminating
kubectl get pods -A --no-headers 2>/dev/null | awk '/Terminating/ {print $1, $2}' | while read -r ns pod; do
log_warn "Принудительно удаляем: ${ns}/${pod}"
kubectl -n "${ns}" delete pod "${pod}" --force --grace-period=0 2>/dev/null || true
done
# 3. Сброс бэкоффа для упавших подов (CrashLoopBackOff, Error, OOMKilled, CreateContainerConfigError)
kubectl get pods -A --no-headers 2>/dev/null | grep -E 'CrashLoopBackOff|Error|OOMKilled|CreateContainerConfigError|ImagePullBackOff' | awk '{print $1, $2}' | while read -r ns pod; do
log_info "Сбрасываем упавший под: ${ns}/${pod}"
kubectl -n "${ns}" delete pod "${pod}" --force --grace-period=0 2>/dev/null || true
done
# 4. Очистка завершенных одноразовых Job
COMPLETED_JOBS=$(kubectl get jobs -A --field-selector status.successful=1 --no-headers 2>/dev/null | awk '{print $1, $2}' || true)
if [[ -n "${COMPLETED_JOBS}" ]]; then
echo "${COMPLETED_JOBS}" | while read -r ns job; do
kubectl -n "${ns}" delete job "${job}" >/dev/null 2>&1 || true
done
fi
log_ok "Кластер очищен. Все сервисы пересоздаются против живых БД и NFS."

27
scripts/layers/reset.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
log_warn "ВНИМАНИЕ! Полная зачистка всех ВМ и LXC на Proxmox VE (host: proxmox)."
read -r -p "Введите 'NUKE' для уничтожения: " confirm
if [[ "${confirm}" != "NUKE" ]]; then
echo "Отмена операции."
exit 0
fi
log_info "Уничтожение виртуальных машин и контейнеров..."
ssh proxmox bash << 'SSHEOF'
for id in 100 201 202 203; do
qm stop $id --skiplock 1 2>/dev/null || true
qm destroy $id --purge 1 --destroy-unreferenced-disks 1 2>/dev/null || true
done
for id in 101 102 103 104 105; do
pct stop $id --skiplock 1 2>/dev/null || true
pct destroy $id --purge 1 --force 1 2>/dev/null || true
done
SSHEOF
log_info "Очистка локального стейта OpenTofu..."
rm -f "${REPO_ROOT}/tofu/terraform.tfstate"* "${REPO_ROOT}/tofu/.terraform.tfstate.lock.info"
rm -f "${RAM_CACHE}"
log_ok "Сервер зачищен до голого железа."

96
scripts/lib/common.sh Executable file
View file

@ -0,0 +1,96 @@
# scripts/lib/common.sh
#!/bin/bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
KDBX_PATH="${HOME}/Projects/atlas-secrets.kdbx"
RAM_CACHE="/dev/shm/.atlas_secrets_cache"
# Цвета вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}==>${NC} $*"; }
log_ok() { echo -e "${GREEN}✔${NC} $*"; }
log_warn() { echo -e "${YELLOW}⚠${NC} $*"; }
log_err() { echo -e "${RED}✖${NC} $*"; }
ensure_prerequisites() {
if ! ssh-add -l >/dev/null 2>&1; then
ssh-add ~/.ssh/id_ed25519 2>/dev/null || true
fi
if ! ip route get 10.0.10.12 2>/dev/null | grep -q "192.168.1.2"; then
log_info "Восстанавливаем маршрут к 10.0.0.0/16 через VyOS (192.168.1.2)..."
sudo ip route replace 10.0.0.0/16 via 192.168.1.2 2>/dev/null || true
fi
}
load_secrets_to_ram() {
if [[ -f "${RAM_CACHE}" ]]; then
source "${RAM_CACHE}"
return 0
fi
log_info "🔐 Вход в защищенный контур (KeePassXC)..."
read -s -p "Master Password: " KP_PASS
echo ""
log_info "Загрузка секретов в оперативную память (/dev/shm)..."
PVE_TOKEN=$(echo "${KP_PASS}" | keepassxc-cli show -q -a Password "${KDBX_PATH}" "ProxmoxAPI" | tr -d '\r\n ')
TF_PASS=$(echo "${KP_PASS}" | keepassxc-cli show -q -a Password "${KDBX_PATH}" "TofuState" | tr -d '\r\n ')
BAO_TOKEN=$(echo "${KP_PASS}" | keepassxc-cli show -q -a Password "${KDBX_PATH}" "OpenBaoToken" | tr -d '\r\n ')
BAO_UNSEAL=$(echo "${KP_PASS}" | keepassxc-cli show -q -a Password "${KDBX_PATH}" "OpenBaoUnseal" | tr -d '\r\n ')
TECH_PASS=$(echo "${KP_PASS}" | keepassxc-cli show -q -a Password "${KDBX_PATH}" "Technitium" 2>/dev/null | tr -d '\r\n ' || echo "admin")
VW_ADMIN=$(echo "${KP_PASS}" | keepassxc-cli show -q -a Password "${KDBX_PATH}" "VaultwardenAdmin" | tr -d '\r\n ')
unset KP_PASS
touch "${RAM_CACHE}"
chmod 600 "${RAM_CACHE}"
{
printf 'export %s=%q\n' "PROXMOX_VE_API_TOKEN" "${PVE_TOKEN}"
printf 'export %s=%q\n' "TF_VAR_proxmox_api_token" "${PVE_TOKEN}"
printf 'export %s=%q\n' "TF_VAR_state_passphrase" "${TF_PASS}"
printf 'export %s=%q\n' "PROXMOX_VE_ENDPOINT" "https://192.168.1.177:8006/"
printf 'export %s=%q\n' "TF_VAR_ssh_public_key" "$(cat ~/.ssh/id_ed25519.pub)"
printf 'export %s=%q\n' "VAULT_ADDR" "http://10.0.10.12:8200"
printf 'export %s=%q\n' "VAULT_TOKEN" "${BAO_TOKEN}"
printf 'export %s=%q\n' "VAULT_UNSEAL_KEY" "${BAO_UNSEAL}"
printf 'export %s=%q\n' "TECHNITIUM_PASSWORD" "${TECH_PASS}"
printf 'export %s=%q\n' "TF_VAR_vaultwarden_admin_token" "${VW_ADMIN}"
} > "${RAM_CACHE}"
source "${RAM_CACHE}"
unset PVE_TOKEN TF_PASS BAO_TOKEN BAO_UNSEAL TECH_PASS VW_ADMIN
log_ok "Секреты безопасно загружены в RAM"
}
# АВТО-СИНХРОНИЗАЦИЯ: передает токен в K8s без ручных команд
sync_k8s_vault_token() {
if kubectl --request-timeout=2s cluster-info >/dev/null 2>&1; then
log_info "Синхронизация токена OpenBao в Kubernetes (namespace: external-secrets)..."
kubectl create namespace external-secrets --dry-run=client -o yaml | kubectl apply -f - >/dev/null 2>&1 || true
kubectl -n external-secrets create secret generic openbao-token \
--from-literal=token="${VAULT_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f - >/dev/null 2>&1
# Сбрасываем кэш сессий в контроллере ExternalSecrets
kubectl -n external-secrets rollout restart deployment external-secrets >/dev/null 2>&1 || true
log_ok "Токен OpenBao в Kubernetes актуализирован, контроллер перезапущен."
fi
}
unseal_vault() {
load_secrets_to_ram
local seal_status
seal_status=$(curl -s --connect-timeout 2 "${VAULT_ADDR}/v1/sys/seal-status" 2>/dev/null || echo "")
if [[ -z "${seal_status}" ]]; then
log_warn "OpenBao (${VAULT_ADDR}) недоступен по сети. Пропуск ансила."
return 0
fi
if echo "${seal_status}" | grep -q '"sealed":true'; then
log_info "OpenBao запечатан! Выполняем ансил из памяти..."
local resp
resp=$(printf '{"key":"%s"}' "${VAULT_UNSEAL_KEY}" | curl -s -X POST -H "Content-Type: application/json" --data-binary @- "${VAULT_ADDR}/v1/sys/unseal")
if echo "${resp}" | grep -q '"sealed":false'; then
log_ok "OpenBao успешно распечатан."
sync_k8s_vault_token
else
log_err "Ошибка ансила OpenBao: ${resp}"
return 1
fi
else
log_ok "OpenBao распечатан."
sync_k8s_vault_token
fi
}

43
scripts/state/00_workstation.sh Executable file
View file

@ -0,0 +1,43 @@
#!/bin/bash
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
echo "## 0. Рабочая станция администратора (Operator Environment)"
echo "### Инфраструктурные SSH-хосты (~/.ssh/config):"
echo '```text'
if [[ -f ~/.ssh/config ]]; then
awk '/^Host (router|proxmox|vyos|10\.|fin21|ded|incserv)/, /^$/' ~/.ssh/config
else
echo "Файл ~/.ssh/config не найден"
fi
echo '```'
echo "### Рабочие алиасы и функции оператора (Active Workflow Aliases):"
echo '```bash'
if [[ -f ~/.bashrc ]] && grep -q '# >>> ATLAS C2 WORKFLOW >>>' ~/.bashrc; then
sed -n '/# >>> ATLAS C2 WORKFLOW >>>/,/# <<< ATLAS C2 WORKFLOW <<</p' ~/.bashrc | grep -v '>>>'
else
echo "# Маркер # >>> ATLAS C2 WORKFLOW >>> не найден в ~/.bashrc"
fi
echo '```'
echo "### Маршрутизация на хосте (Routing to C2):"
echo '```text'
ip route show | grep -E "10.0.|192.168." || echo "Маршруты к ЦОД не обнаружены"
echo '```'
echo "### Версии локальных инструментов:"
echo '```text'
echo -n "OpenTofu: "; tofu version 2>/dev/null | head -n1 || echo "not installed"
echo -n "Kubectl: "; kubectl version --client -o yaml 2>/dev/null | grep gitVersion | head -n1 || echo "not installed"
echo -n "Talosctl: "; talosctl version --client --short 2>/dev/null || echo "not installed"
echo '```'
echo "### Состояние локального Git-репозитория (SSOT Source):"
echo '```text'
git -C "${REPO_ROOT}" status -sb
echo "Последний коммит: $(git -C "${REPO_ROOT}" log -1 --format='%h - %s (%cr)')"
echo "Remote URL: $(git -C "${REPO_ROOT}" remote -v 2>/dev/null | head -n1 | awk '{print $1, $2}' || echo 'no remote')"
echo '```'
echo "---"

19
scripts/state/01_pve.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
set -uo pipefail
SSH_CMD="ssh -n -o BatchMode=yes -o ConnectTimeout=3 proxmox"
echo "## 1. Гипервизор Proxmox VE (192.168.1.177)"
echo "### Память хоста и Uptime:"
echo '```text'
timeout 4s ${SSH_CMD} "uptime; echo ''; free -h" 2>&1 || echo "PVE недоступен"
echo '```'
echo "### Здоровье и емкость ZFS-пулов:"
echo '```text'
timeout 4s ${SSH_CMD} "zpool list -o name,size,alloc,free,frag,cap,health; echo ''; zfs list -o name,used,avail,refer,mountpoint -r astra terra" 2>&1 || echo "Ошибка сбора ZFS"
echo '```'
echo "### Виртуальные машины (QEMU) и Контейнеры (LXC):"
echo '```text'
timeout 4s ${SSH_CMD} "qm list; echo ''; pct list" 2>&1 || echo "Ошибка получения списка ВМ/LXC"
echo '```'
echo "---"

27
scripts/state/02_vps.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
set -uo pipefail
SSH_CMD="ssh -n -o BatchMode=yes -o ConnectTimeout=4 fin21"
echo "## 2. Периметр: Белый VPS (Host: fin21, IP: 153.76.194.24)"
echo "### WireGuard wg0 на VPS:"
echo '```text'
timeout 4s ${SSH_CMD} "sudo wg show wg0" 2>&1 || echo "VPS или WireGuard недоступен"
echo '```'
echo "### Статус системных сервисов (Edge Stack):"
echo '```text'
timeout 4s ${SSH_CMD} "sudo systemctl is-active wg-quick@wg0 haproxy xray nginx | paste -d' ' -s - | awk '{print \"WireGuard:\", \$1, \"| HAProxy:\", \$2, \"| Xray:\", \$3, \"| Nginx:\", \$4}'" 2>&1 || echo "Ошибка проверки systemd"
echo '```'
echo "### Прослушиваемые сокеты (Ports 80, 443, 4443, 8443, 51820):"
echo '```text'
timeout 4s ${SSH_CMD} "sudo ss -tulpn | grep -E '80|443|4443|8443|51820'" 2>&1 || echo "Ошибка ss"
echo '```'
echo "### Тест аварийной заглушки Nginx (127.0.0.1:8443):"
echo '```text'
timeout 3s ${SSH_CMD} "curl -k -s https://127.0.0.1:8443" 2>&1 || echo "Nginx fallback не отвечает"
echo '```'
echo "### Конфигурация SSH-демона (sshd runtime):"
echo '```text'
timeout 4s ${SSH_CMD} "sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin'" 2>&1 || echo "Ошибка проверки sshd"
echo '```'
echo "---"

15
scripts/state/03_vyos.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
set -uo pipefail
SSH_CMD="ssh -n -o BatchMode=yes -o ConnectTimeout=3 vyos"
echo "## 3. Маршрутизатор VyOS (192.168.1.2)"
echo "### Статус WireGuard туннеля и пинг до VPS:"
echo '```text'
timeout 4s ${SSH_CMD} "sudo wg show wg0; echo ''; /opt/vyatta/bin/vyatta-op-cmd-wrapper ping 10.8.0.1 count 2" 2>&1 || echo "VyOS или туннель недоступен"
echo '```'
echo "### Таблица маршрутизации (show ip route):"
echo '```text'
timeout 4s ${SSH_CMD} "/opt/vyatta/bin/vyatta-op-cmd-wrapper show ip route" 2>&1 || echo "Ошибка получения маршрутов"
echo '```'
echo "---"

25
scripts/state/04_tier0.sh Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
set -uo pipefail
SSH_VYOS="ssh -n -o BatchMode=yes -o ConnectTimeout=3 vyos"
SSH_STORAGE="ssh -n -o BatchMode=yes -o ConnectTimeout=3 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -J vyos root@10.0.10.14"
echo "## 4. Сервисы Tier-0 (Air-Gap фундамент)"
echo "### Technitium DNS (10.0.10.11:53):"
echo "* **Внутренний домен (*.svo88.space -> 10.0.30.200):** $(timeout 3s ${SSH_VYOS} "dig @10.0.10.11 vw.svo88.space +short" 2>&1 | tr '\n' ' ')"
echo "* **Внешний резолв (google.com):** $(timeout 3s ${SSH_VYOS} "dig @10.0.10.11 google.com +short" 2>&1 | head -n1)"
echo "### OpenBao Vault (10.0.10.12:8200):"
echo '```json'
timeout 3s ${SSH_VYOS} "curl -s http://10.0.10.12:8200/v1/sys/seal-status" 2>&1 || echo '{"error": "Vault недоступен"}'
echo '```'
echo "### Zot OCI Registry (10.0.10.15:5000):"
echo '```json'
timeout 3s ${SSH_VYOS} "curl -s http://10.0.10.15:5000/v2/_catalog" 2>&1 || echo '{"error": "Zot недоступен"}'
echo '```'
echo "### Ядерный NFS nfsd (storage0 - 10.0.10.14):"
echo '```text'
timeout 4s ${SSH_STORAGE} "echo '== Активные экспорты ядра (exportfs):'; exportfs -v; echo ''; echo '== Емкость смонтированных ZFS-датасетов:'; df -h /media /torrents /backups" 2>/dev/null || \
timeout 4s ssh -n -o BatchMode=yes -o ConnectTimeout=3 root@10.0.10.14 "echo '== Активные экспорты ядра (exportfs):'; exportfs -v; echo ''; echo '== Емкость смонтированных ZFS-датасетов:'; df -h /media /torrents /backups" 2>&1 || \
echo "NFS (storage0) недоступен"
echo '```'
echo "---"

42
scripts/state/05_k8s.sh Executable file
View file

@ -0,0 +1,42 @@
#!/bin/bash
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TALOSCONFIG="${REPO_ROOT}/talos/clusterconfig/talosconfig"
KUBECONFIG_PATH="${HOME}/.kube/config"
echo "## 5. Kubernetes Платформа & Нагрузки"
echo "### Участники кластера Talos (OS-level):"
echo '```text'
timeout 5s talosctl --talosconfig="${TALOSCONFIG}" -n 10.0.20.10 -e 10.0.20.10 get members 2>&1 || echo "Talos API недоступен"
echo '```'
echo "### Ноды кластера K8s:"
echo '```text'
timeout 5s kubectl --kubeconfig="${KUBECONFIG_PATH}" get nodes -o wide 2>&1 || echo "K8s API недоступен"
echo '```'
echo "### Статус ExternalSecrets (Синхронизация из Vault):"
echo '```text'
timeout 5s kubectl --kubeconfig="${KUBECONFIG_PATH}" get clustersecretstores,externalsecrets -A 2>&1 || echo "Не удалось получить ExternalSecrets"
echo '```'
echo "### Диски и тома (PVC / PV):"
echo '```text'
timeout 5s kubectl --kubeconfig="${KUBECONFIG_PATH}" get pvc,pv -A 2>&1 || echo "Не удалось получить PVC"
echo '```'
echo "### Логи ошибок Proxmox CSI Plugin (последние строки):"
echo '```text'
timeout 5s kubectl --kubeconfig="${KUBECONFIG_PATH}" -n kube-system logs -l app=proxmox-csi-plugin --tail=15 --prefix=true 2>&1 || echo "Нет логов CSI"
echo '```'
echo "### Синхронизация GitOps (FluxCD):"
echo '```text'
timeout 5s kubectl --kubeconfig="${KUBECONFIG_PATH}" get gitrepositories,kustomizations,helmreleases -A 2>&1 || echo "Flux не установлен или недоступен"
echo '```'
echo "### Проблемные поды (Non-Running):"
echo '```text'
timeout 5s kubectl --kubeconfig="${KUBECONFIG_PATH}" get pods -A --field-selector=status.phase!=Running 2>&1 || echo "Все поды в статусе Running"
echo '```'
echo "### Реальный объем данных OpenCloud (/var/lib/opencloud/data):"
echo '```text'
timeout 10s kubectl --kubeconfig="${KUBECONFIG_PATH}" -n storage exec deploy/opencloud -- du -h -d 1 /var/lib/opencloud/data 2>/dev/null || \
timeout 10s kubectl --kubeconfig="${KUBECONFIG_PATH}" -n storage exec statefulset/opencloud -- du -h -d 1 /var/lib/opencloud/data 2>&1 || \
echo "Не удалось получить размер данных OpenCloud"
echo '```'

76
talos/bootstrap.sh Executable file
View file

@ -0,0 +1,76 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
export TALOSCONFIG="${SCRIPT_DIR}/clusterconfig/talosconfig"
KUBECONFIG_PATH="${HOME}/.kube/config"
NODES=("10.0.20.10" "10.0.20.11" "10.0.20.12")
CONFIGS=(
"${SCRIPT_DIR}/clusterconfig/controlplane.yaml"
"${SCRIPT_DIR}/clusterconfig/worker.yaml"
"${SCRIPT_DIR}/clusterconfig/worker.yaml"
)
# 1. Ждем доступности порта API (50000)
echo "==> [1/6] Checking Talos nodes reachability..."
for node in "${NODES[@]}"; do
echo -n "Checking ${node}:50000 "
while ! nc -z -w 2 "${node}" 50000 2>/dev/null; do
sleep 2
echo -n "."
done
echo " UP!"
done
# 2. Применение конфигураций нод
echo "==> [2/6] Applying machine configurations..."
for i in "${!NODES[@]}"; do
node="${NODES[$i]}"
cfg="${CONFIGS[$i]}"
if ! talosctl apply-config --insecure --nodes "${node}" --file "${cfg}" 2>/dev/null; then
talosctl apply-config --nodes "${node}" --endpoints "${node}" --file "${cfg}" 2>/dev/null || true
fi
done
# 3. Бутстрап кластера etcd (проверяем, не поднят ли уже Kubernetes API)
echo "==> [3/6] Checking control plane bootstrap..."
if nc -z -w 2 10.0.20.10 6443 2>/dev/null; then
echo "==> Kubernetes API (port 6443) is ALREADY active and healthy! Skipping bootstrap."
else
echo "==> Initiating etcd bootstrap on 10.0.20.10..."
talosctl bootstrap --nodes 10.0.20.10 --endpoints 10.0.20.10 2>/dev/null || true
sleep 5
fi
# 4. Выкачивание kubeconfig в системный путь
echo "==> [4/6] Fetching kubeconfig..."
mkdir -p "${HOME}/.kube"
until talosctl kubeconfig "${KUBECONFIG_PATH}" --nodes 10.0.20.10 --endpoints 10.0.20.10 --force 2>/dev/null; do
sleep 2
echo -n "."
done
cp -f "${KUBECONFIG_PATH}" "${SCRIPT_DIR}/kubeconfig"
echo " OK!"
# 5. Установка Cilium CNI через Helm
echo "==> [5/6] Deploying Cilium CNI (Day-0 eBPF Network)..."
helm repo add cilium https://helm.cilium.io/ --force-update >/dev/null 2>&1 || true
helm upgrade --install cilium cilium/cilium \
--version 1.15.5 \
--namespace kube-system \
--kubeconfig "${KUBECONFIG_PATH}" \
-f "${REPO_ROOT}/kubernetes/infrastructure/network/cilium/values.yaml"
echo "==> Applying L2 Announcement Policy..."
kubectl --kubeconfig "${KUBECONFIG_PATH}" apply -f "${REPO_ROOT}/kubernetes/infrastructure/network/cilium/l2-policy.yaml"
# 6. Ожидание перехода всех нод в статус Ready
echo "==> [6/6] Waiting for all nodes to become Ready..."
kubectl --kubeconfig "${KUBECONFIG_PATH}" wait --for=condition=Ready nodes --all --timeout=180s
echo ""
echo "🎉 Cluster is FULLY READY and OPERATIONAL!"
kubectl --kubeconfig "${KUBECONFIG_PATH}" get nodes -o wide

21
talos/generate_config.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/bash
set -euo pipefail
mkdir -p talos/clusterconfig
if [ ! -f "talos/secrets.yaml" ]; then
talosctl gen secrets -o talos/secrets.yaml
fi
chmod 0600 talos/secrets.yaml
talosctl gen config atlas https://10.0.20.10:6443 \
--with-secrets talos/secrets.yaml \
--config-patch @talos/patches/common.yaml \
--config-patch-worker @talos/patches/worker.yaml \
--install-disk /dev/vda \
--output-dir talos/clusterconfig \
--with-docs=false \
--with-examples=false \
--force
chmod 0600 talos/clusterconfig/*

29
talos/patches/common.yaml Normal file
View file

@ -0,0 +1,29 @@
machine:
registries:
mirrors:
docker.io:
overridePath: true
endpoints:
- http://10.0.10.15:5000/v2/docker.io
ghcr.io:
overridePath: true
endpoints:
- http://10.0.10.15:5000/v2/ghcr.io
registry.k8s.io:
overridePath: true
endpoints:
- http://10.0.10.15:5000/v2/k8s.io
quay.io:
overridePath: true
endpoints:
- http://10.0.10.15:5000/v2/quay.io
---
apiVersion: v1alpha1
kind: ResolverConfig
nameservers:
- address: 10.0.10.11
- address: 10.0.20.1
---
apiVersion: v1alpha1
kind: KubeFlannelCNIConfig
$patch: delete

12
talos/patches/worker.yaml Normal file
View file

@ -0,0 +1,12 @@
---
machine:
nodeLabels:
topology.kubernetes.io/region: atlas
topology.kubernetes.io/zone: pve0
network:
interfaces:
- interface: ens18
dhcp: true
- interface: ens19
dhcp: true
---

453
tofu/compute.tf Normal file
View file

@ -0,0 +1,453 @@
variable "talos_iso_file_id" {
type = string
description = "Proxmox file ID for the Talos Linux ISO"
default = "local:iso/talos-amd64.iso"
}
# ==============================================================================
# ROUTING & EDGE (VyOS)
# ==============================================================================
resource "proxmox_virtual_environment_vm" "vyos_router" {
vm_id = 100
name = "vyos-router"
node_name = "pve0"
started = true
cpu {
cores = 2
type = "host"
}
memory {
dedicated = 2048
}
disk {
datastore_id = "astra"
file_id = "local:iso/vyos-1.5-cloudinit.img"
interface = "virtio0"
size = 10
}
boot_order = ["virtio0"]
# VLAN 99: WAN_TRUNK
network_device {
bridge = "vmbr0"
model = "virtio"
}
# VLAN 10: TIER0 (MGMT)
network_device {
bridge = "vmbr0"
vlan_id = 10
model = "virtio"
}
# VLAN 20: K8S
network_device {
bridge = "vmbr0"
vlan_id = 20
model = "virtio"
}
# VLAN 30: LAN_CLIENTS
network_device {
bridge = "vmbr0"
vlan_id = 30
model = "virtio"
}
operating_system {
type = "l26"
}
initialization {
datastore_id = "astra"
ip_config {
ipv4 {
address = "192.168.1.2/24"
gateway = "192.168.1.1"
}
}
user_account {
username = "vyos"
keys = [var.ssh_public_key]
}
}
agent {
enabled = false
}
}
# ==============================================================================
# TIER-0 LXC CONTAINERS (AIR-GAP FOUNDATION)
# ==============================================================================
resource "proxmox_virtual_environment_container" "dns0" {
vm_id = 101
node_name = "pve0"
unprivileged = true
started = true
cpu { cores = 1 }
memory { dedicated = 512 }
disk {
datastore_id = "local"
size = 2
}
network_interface {
name = "eth0"
bridge = "vmbr0"
vlan_id = 10
}
initialization {
hostname = "dns0"
ip_config {
ipv4 {
address = "10.0.10.11/24"
gateway = "10.0.10.1"
}
}
user_account { keys = [var.ssh_public_key] }
}
operating_system {
template_file_id = "local:vztmpl/debian-12-standard_amd64.tar.zst"
type = "debian"
}
features {
nesting = true
}
}
resource "proxmox_virtual_environment_container" "vault0" {
vm_id = 102
node_name = "pve0"
unprivileged = true
started = true
cpu { cores = 1 }
memory { dedicated = 512 }
disk {
datastore_id = "local"
size = 4
}
network_interface {
name = "eth0"
bridge = "vmbr0"
vlan_id = 10
}
initialization {
hostname = "vault0"
ip_config {
ipv4 {
address = "10.0.10.12/24"
gateway = "10.0.10.1"
}
}
user_account { keys = [var.ssh_public_key] }
}
operating_system {
template_file_id = "local:vztmpl/debian-12-standard_amd64.tar.zst"
type = "debian"
}
features {
nesting = true
}
}
resource "proxmox_virtual_environment_container" "git0" {
vm_id = 103
node_name = "pve0"
unprivileged = true
started = true
cpu { cores = 2 }
memory { dedicated = 1024 }
disk {
datastore_id = "local"
size = 8
}
network_interface {
name = "eth0"
bridge = "vmbr0"
vlan_id = 10
}
initialization {
hostname = "git0"
ip_config {
ipv4 {
address = "10.0.10.13/24"
gateway = "10.0.10.1"
}
}
user_account { keys = [var.ssh_public_key] }
}
operating_system {
template_file_id = "local:vztmpl/debian-12-standard_amd64.tar.zst"
type = "debian"
}
features {
nesting = true
}
}
resource "proxmox_virtual_environment_container" "storage0" {
vm_id = 104
node_name = "pve0"
unprivileged = false
started = true
cpu { cores = 2 }
memory { dedicated = 1024 }
disk {
datastore_id = "local"
size = 4
}
network_interface {
name = "eth0"
bridge = "vmbr0"
vlan_id = 10
}
initialization {
hostname = "storage0"
ip_config {
ipv4 {
address = "10.0.10.14/24"
gateway = "10.0.10.1"
}
}
user_account { keys = [var.ssh_public_key] }
}
operating_system {
template_file_id = "local:vztmpl/alpine-3.24-default_20260714_amd64.tar.xz"
type = "alpine"
}
lifecycle {
ignore_changes = [mount_point]
}
}
resource "proxmox_virtual_environment_container" "registry0" {
vm_id = 105
node_name = "pve0"
unprivileged = true
started = true
cpu { cores = 2 }
memory { dedicated = 1024 }
disk {
datastore_id = "local"
size = 8
}
network_interface {
name = "eth0"
bridge = "vmbr0"
vlan_id = 10
}
initialization {
hostname = "registry0"
ip_config {
ipv4 {
address = "10.0.10.15/24"
gateway = "10.0.10.1"
}
}
user_account { keys = [var.ssh_public_key] }
}
operating_system {
template_file_id = "local:vztmpl/debian-12-standard_amd64.tar.zst"
type = "debian"
}
features {
nesting = true
}
lifecycle {
ignore_changes = [mount_point]
}
}
# ==============================================================================
# TALOS KUBERNETES CLUSTER
# ==============================================================================
resource "proxmox_virtual_environment_vm" "talos_master_1" {
vm_id = 201
name = "talos-master-1"
node_name = "pve0"
started = true
cpu {
cores = 2
type = "host"
units = 2048
}
memory {
dedicated = 2560
}
disk {
datastore_id = "astra"
file_format = "raw"
interface = "virtio0"
size = 20
}
cdrom {
file_id = var.talos_iso_file_id
interface = "ide2"
}
boot_order = ["virtio0", "ide2"]
network_device {
bridge = "vmbr0"
vlan_id = 20
model = "virtio"
mac_address = "BC:24:11:71:B6:82"
}
operating_system {
type = "l26"
}
agent {
enabled = false
}
}
resource "proxmox_virtual_environment_vm" "talos_worker_1" {
vm_id = 202
name = "talos-worker-1"
node_name = "pve0"
started = true
cpu {
cores = 4
type = "host"
units = 1024
}
memory {
dedicated = 10240
}
disk {
datastore_id = "astra"
file_format = "raw"
interface = "virtio0"
size = 100
}
cdrom {
file_id = var.talos_iso_file_id
interface = "ide2"
}
boot_order = ["virtio0", "ide2"]
# eth0: K8S Overlay
network_device {
bridge = "vmbr0"
vlan_id = 20
model = "virtio"
mac_address = "BC:24:11:12:C5:1B"
}
# eth1: LAN_CLIENTS (L2 Fast-Path)
network_device {
bridge = "vmbr0"
vlan_id = 30
model = "virtio"
}
operating_system {
type = "l26"
}
agent {
enabled = false
}
}
resource "proxmox_virtual_environment_vm" "talos_worker_2" {
vm_id = 203
name = "talos-worker-2"
node_name = "pve0"
started = true
cpu {
cores = 4
type = "host"
units = 1024
}
memory {
dedicated = 10240
}
disk {
datastore_id = "astra"
file_format = "raw"
interface = "virtio0"
size = 100
}
cdrom {
file_id = var.talos_iso_file_id
interface = "ide2"
}
boot_order = ["virtio0", "ide2"]
# eth0: K8S Overlay
network_device {
bridge = "vmbr0"
vlan_id = 20
model = "virtio"
mac_address = "BC:24:11:EF:1A:D4"
}
# eth1: LAN_CLIENTS (L2 Fast-Path)
network_device {
bridge = "vmbr0"
vlan_id = 30
model = "virtio"
}
operating_system {
type = "l26"
}
agent {
enabled = false
}
}

29
tofu/providers.tf Normal file
View file

@ -0,0 +1,29 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.73.0"
}
vault = {
source = "hashicorp/vault"
version = "~> 4.2"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "proxmox" {
insecure = true
ssh {
agent = true
username = "root"
}
}
provider "vault" {
address = "http://10.0.10.12:8200"
skip_tls_verify = true
}

211
tofu/secrets.tf Normal file
View file

@ -0,0 +1,211 @@
resource "vault_mount" "secret" {
path = "secret"
type = "kv-v2"
description = "Primary KV v2 secrets store"
}
locals {
pve_token_parts = split("=", var.proxmox_api_token)
}
resource "vault_kv_secret_v2" "proxmox_csi" {
mount = vault_mount.secret.path
name = "proxmox-csi"
data_json = jsonencode({
token_id = local.pve_token_parts[0]
token_secret = local.pve_token_parts[1]
})
}
resource "random_password" "postgres_password" {
length = 32
special = false
}
resource "vault_kv_secret_v2" "postgres" {
mount = vault_mount.secret.path
name = "postgres"
data_json = jsonencode({
POSTGRES_PASSWORD = random_password.postgres_password.result
})
}
resource "random_password" "sure_db_password" {
length = 32
special = false
}
resource "random_password" "sure_secret_key_base" {
length = 64
special = false
}
resource "vault_kv_secret_v2" "sure" {
mount = vault_mount.secret.path
name = "sure"
data_json = jsonencode({
DB_PASSWORD = random_password.sure_db_password.result
SECRET_KEY_BASE = random_password.sure_secret_key_base.result
})
}
resource "random_password" "authentik_secret_key" {
length = 50
special = false
}
resource "random_password" "authentik_db_password" {
length = 32
special = false
}
resource "vault_kv_secret_v2" "authentik" {
mount = vault_mount.secret.path
name = "authentik"
data_json = jsonencode({
secret_key = random_password.authentik_secret_key.result
postgresql_password = random_password.authentik_db_password.result
})
}
resource "random_password" "immich_db_password" {
length = 32
special = false
}
resource "vault_kv_secret_v2" "immich" {
mount = vault_mount.secret.path
name = "immich"
data_json = jsonencode({
DB_PASSWORD = random_password.immich_db_password.result
})
}
resource "random_password" "paperless_db_password" {
length = 32
special = false
}
resource "random_password" "paperless_secret_key" {
length = 64
special = false
}
resource "vault_kv_secret_v2" "paperless" {
mount = vault_mount.secret.path
name = "paperless"
data_json = jsonencode({
PAPERLESS_DBPASS = random_password.paperless_db_password.result
PAPERLESS_SECRET_KEY = random_password.paperless_secret_key.result
})
}
resource "random_password" "netbox_db_password" {
length = 32
special = false
}
resource "random_password" "netbox_secret_key" {
length = 50
special = false
}
resource "vault_kv_secret_v2" "netbox" {
mount = vault_mount.secret.path
name = "netbox"
data_json = jsonencode({
DB_PASSWORD = random_password.netbox_db_password.result
SECRET_KEY = random_password.netbox_secret_key.result
})
}
resource "random_password" "printstash_db_password" {
length = 32
special = false
}
resource "vault_kv_secret_v2" "printstash" {
mount = vault_mount.secret.path
name = "printstash"
data_json = jsonencode({
DB_PASSWORD = random_password.printstash_db_password.result
})
}
resource "vault_kv_secret_v2" "vaultwarden" {
mount = vault_mount.secret.path
name = "vaultwarden"
data_json = jsonencode({
ADMIN_TOKEN = var.vaultwarden_admin_token
})
}
resource "random_password" "searxng_secret" {
length = 32
special = false
}
resource "vault_kv_secret_v2" "searxng" {
mount = vault_mount.secret.path
name = "searxng"
data_json = jsonencode({
SEARXNG_SECRET = random_password.searxng_secret.result
})
}
resource "random_password" "homebox_pepper" {
length = 48
special = false
}
resource "vault_kv_secret_v2" "homebox" {
mount = vault_mount.secret.path
name = "homebox"
data_json = jsonencode({
HBOX_AUTH_API_KEY_PEPPER = random_password.homebox_pepper.result
})
}
resource "random_password" "invidious_db_password" {
length = 32
special = false
}
resource "random_password" "invidious_hmac_key" {
length = 32
special = false
}
resource "random_password" "invidious_companion_key" {
length = 16
special = false
}
resource "vault_kv_secret_v2" "invidious" {
mount = vault_mount.secret.path
name = "invidious"
data_json = jsonencode({
DB_PASSWORD = random_password.invidious_db_password.result
HMAC_KEY = random_password.invidious_hmac_key.result
COMPANION_KEY = random_password.invidious_companion_key.result
})
}
resource "random_password" "karakeep_secret_key" {
length = 32
special = false
}
resource "random_password" "karakeep_meili_key" {
length = 32
special = false
}
resource "vault_kv_secret_v2" "karakeep" {
mount = vault_mount.secret.path
name = "karakeep"
data_json = jsonencode({
NEXTAUTH_SECRET = random_password.karakeep_secret_key.result
MEILI_MASTER_KEY = random_password.karakeep_meili_key.result
})
}

16
tofu/variables.tf Normal file
View file

@ -0,0 +1,16 @@
variable "ssh_public_key" {
type = string
description = "Публичный SSH-ключ для доступа к ВМ и LXC"
}
variable "proxmox_api_token" {
type = string
description = "Proxmox API Token формата USER@REALM!TOKENID=UUID"
sensitive = true
}
variable "vaultwarden_admin_token" {
type = string
description = "Vaultwarden Admin Argon2 hash from KeePassXC"
sensitive = true
}