Archive Legacy Setup And Add Ansible Control Plane

This commit is contained in:
Dmitry
2026-07-08 12:47:37 +03:00
parent 841d56380b
commit 4bafa7d09e
203 changed files with 1663 additions and 11 deletions
@@ -0,0 +1,28 @@
name: Deploy cloud-pc
on:
workflow_dispatch:
inputs:
stack:
description: "Stack to deploy (leave empty to reconcile all)"
required: false
push:
paths:
- ".gitea/workflows/deploy-cloud-pc.yml"
- "cloud-pc/**"
- "scripts/deploy.sh"
schedule:
- cron: "0 4 * * *"
jobs:
deploy:
runs-on: cloud-pc
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Deploy stacks
run: /opt/services/scripts/deploy.sh cloud-pc "${{ inputs.stack }}"
@@ -0,0 +1,28 @@
name: Deploy mini-pc
on:
workflow_dispatch:
inputs:
stack:
description: "Stack to deploy (leave empty to reconcile all)"
required: false
push:
paths:
- ".gitea/workflows/deploy-mini-pc.yml"
- "mini-pc/**"
- "scripts/deploy.sh"
schedule:
- cron: "0 4 * * *"
jobs:
deploy:
runs-on: mini-pc
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Deploy stacks
run: /opt/services/scripts/deploy.sh mini-pc "${{ inputs.stack }}"
@@ -0,0 +1,28 @@
name: Deploy ru-vps
on:
workflow_dispatch:
inputs:
stack:
description: "Stack to deploy (leave empty to reconcile all)"
required: false
push:
paths:
- ".gitea/workflows/deploy-ru-vps.yml"
- "ru-vps/**"
schedule:
- cron: "0 4 * * *"
jobs:
deploy:
runs-on: ru-vps
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Deploy stacks
run: /opt/services/scripts/deploy.sh ru-vps "${{ inputs.stack }}"
@@ -0,0 +1,33 @@
name: NixOS switch cloud-pc
on:
workflow_dispatch:
push:
paths:
- ".gitea/workflows/nixos-switch-cloud-pc.yml"
- "nixos/flake.nix"
- "nixos/flake.lock"
- "nixos/modules/**"
- "nixos/hosts/cloud-pc/**"
jobs:
switch:
runs-on: cloud-pc
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Switch NixOS generation
env:
NIXOS_DEPLOY_KEY: ${{ secrets.NIXOS_DEPLOY_KEY }}
run: |
KEY=$(mktemp)
echo "$NIXOS_DEPLOY_KEY" > "$KEY"
chmod 600 "$KEY"
trap "rm -f $KEY" EXIT
ssh -i "$KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
ada@192.168.1.5 \
"sudo /run/current-system/sw/bin/nixos-rebuild switch --flake /opt/services/nixos#cloud-pc"
@@ -0,0 +1,33 @@
name: NixOS switch mini-pc
on:
workflow_dispatch:
push:
paths:
- ".gitea/workflows/nixos-switch-mini-pc.yml"
- "nixos/flake.nix"
- "nixos/flake.lock"
- "nixos/modules/**"
- "nixos/hosts/mini-pc/**"
jobs:
switch:
runs-on: mini-pc
steps:
- name: Sync repo
run: |
git config --global --replace-all safe.directory /opt/services
git -C /opt/services pull origin main --ff-only
chown -R 1000:100 /opt/services
- name: Switch NixOS generation
env:
NIXOS_DEPLOY_KEY: ${{ secrets.NIXOS_DEPLOY_KEY }}
run: |
KEY=$(mktemp)
echo "$NIXOS_DEPLOY_KEY" > "$KEY"
chmod 600 "$KEY"
trap "rm -f $KEY" EXIT
ssh -i "$KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
ada@192.168.1.10 \
"sudo /run/current-system/sw/bin/nixos-rebuild switch --flake /opt/services/nixos#mini-pc"
@@ -0,0 +1,13 @@
# Archive: 2026-07 Proxmox Migration
Historical configuration from the pre-Proxmox HomeLab setup.
Moved here:
- `nixos/` — old NixOS host configs and modules.
- `cloud-pc/`, `mini-pc/`, `ru-vps/` — old Docker Compose service trees.
- `.gitea/` — old GitOps workflows.
- `scripts/` — old deploy scripts.
- `legacy-ansible/` — old bootstrap/base/dirs/restic/rclone playbooks and bootstrap role.
Use these files only as reference when implementing new Ansible roles/playbooks.
@@ -0,0 +1,7 @@
services:
ada-dev:
image: git.ada-dev.ru/ada/ada-dev.ru:latest
container_name: ada-dev-ru
restart: unless-stopped
ports:
- "7080:80"
@@ -0,0 +1,4 @@
FROM gitea/act_runner:latest
USER root
RUN apk add --no-cache docker-cli docker-cli-compose openssh-client
@@ -0,0 +1,13 @@
services:
gitea-runner:
build: .
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/services:/opt/services
- /opt/data/gitea-runner:/data
environment:
GITEA_INSTANCE_URL: https://git.ada-dev.ru
GITEA_RUNNER_REGISTRATION_TOKEN: ${TOKEN}
GITEA_RUNNER_NAME: cloud-pc-runner
GITEA_RUNNER_LABELS: cloud-pc
@@ -0,0 +1,23 @@
services:
gitea:
environment:
USER_GID: "1000"
USER_UID: "1000"
hostname: gitea
image: gitea/gitea:latest
network_mode: bridge
ports:
- target: 3000
published: "3002"
protocol: tcp
- target: 22
published: "2222"
protocol: tcp
restart: unless-stopped
volumes:
- type: bind
source: /opt/data/gitea
target: /data
networks:
default:
name: gitea_default
@@ -0,0 +1,6 @@
services:
neural:
ports:
- 4567:4567
image: git.ada-dev.ru/ada/lr_miad_nn:latest
networks: {}
@@ -0,0 +1,52 @@
services:
redis:
image: redis:alpine
container_name: redis
hostname: redis
restart: unless-stopped
environment:
TZ: Europe/Moscow
nextcloud:
image: nextcloud:${NC_VER}
container_name: nextcloud
hostname: nextcloud
restart: unless-stopped
ports:
- 8081:80
dns:
- 8.8.8.8
extra_hosts:
- ${NC_DOMAIN}:host-gateway
depends_on:
- redis
volumes:
- /opt/configs/nextcloud/app:/var/www/html
- /opt/data/nextcloud:/var/www/html/data
- /mnt/yandex:/mnt/yandex:shared
environment:
TZ: Europe/Moscow
VIRTUAL_HOST: ${NC_DOMAIN}
NEXTCLOUD_TRUSTED_DOMAINS: ${NC_DOMAIN} 127.0.0.1
NEXTCLOUD_OVERWRITEPROTOCOL: https
NEXTCLOUD_OVERWRITEHOST: ${NC_DOMAIN}
NEXTCLOUD_OVERWRITECLIURL: https://${NC_DOMAIN}
REDIS_HOST: redis
TRUSTED_PROXIES: ${TRUSTED_PROXIES}
cron:
image: nextcloud:${NC_VER}
hostname: cron
container_name: cron
restart: unless-stopped
volumes:
- /opt/configs/nextcloud/app:/var/www/html
- /opt/data/nextcloud:/var/www/html/data
entrypoint: /cron.sh
depends_on:
- nextcloud
environment:
TZ: Europe/Moscow
REDIS_HOST: redis
networks: {}
@@ -0,0 +1,98 @@
version = "1"
[global]
scheduler = "systemd"
# ── Общие настройки ────────────────────────────────────────────────────────────
[default]
password-file = "/etc/restic/password"
initialize = true
[default.forget]
keep-daily = 7
keep-weekly = 4
keep-monthly = 6
schedule-permission = "system"
schedule-after-backup = true
prune = true
[default.check]
schedule = "Sun 04:00"
schedule-permission = "system"
# ── Данные и конфиги сервисов ─────────────────────────────────────────────────
[services]
inherit = "default"
repository = "/mnt/backup/restic/services"
[services.backup]
source = ["/opt/data", "/opt/configs"]
tag = ["cloud-pc", "services"]
exclude-caches = true
one-file-system = true
schedule = "02:00"
schedule-permission = "system"
schedule-log = "/var/log/resticprofile/services.log"
[services.forget]
schedule = "02:30"
# ── Samba-шара ────────────────────────────────────────────────────────────────
[samba]
inherit = "default"
repository = "/mnt/backup/restic/samba"
[samba.backup]
source = ["/opt/samba"]
tag = ["cloud-pc", "samba"]
exclude-caches = true
one-file-system = true
schedule = "03:00"
schedule-permission = "system"
schedule-log = "/var/log/resticprofile/samba.log"
[samba.forget]
schedule = "03:30"
# ── Yandex Disk: ночная удалённая копия ───────────────────────────────────────
[yadisk-default]
inherit = "default"
[yadisk-default.check]
schedule = "Sun 06:00"
schedule-permission = "system"
schedule-after-network-online = true
[yadisk-services]
inherit = "yadisk-default"
repository = "rclone:yadisk:System/Backups/cloud-pc/restic/services"
[yadisk-services.backup]
source = ["/opt/data", "/opt/configs"]
tag = ["cloud-pc", "services", "yadisk"]
exclude-caches = true
one-file-system = true
schedule = "04:00"
schedule-permission = "system"
schedule-after-network-online = true
schedule-log = "/var/log/resticprofile/yadisk-services.log"
[yadisk-services.forget]
schedule = "04:30"
[yadisk-samba]
inherit = "yadisk-default"
repository = "rclone:yadisk:System/Backups/cloud-pc/restic/samba"
[yadisk-samba.backup]
source = ["/opt/samba"]
tag = ["cloud-pc", "samba", "yadisk"]
exclude-caches = true
one-file-system = true
schedule = "05:00"
schedule-permission = "system"
schedule-after-network-online = true
schedule-log = "/var/log/resticprofile/yadisk-samba.log"
[yadisk-samba.forget]
schedule = "05:30"
@@ -0,0 +1 @@
SAMBA_PASSWORD=
@@ -0,0 +1,16 @@
services:
samba:
image: ghcr.io/servercontainers/samba:latest
container_name: samba
restart: unless-stopped
network_mode: host
volumes:
- /opt/samba/media:/shares/media
- /opt/samba/documents:/shares/documents
- /opt/samba/backups_win:/shares/backups_win
environment:
ACCOUNT_ada: ${SAMBA_PASSWORD}
UID_ada: "1000"
SAMBA_VOLUME_CONFIG_media: "[media]; path = /shares/media; valid users = ada; read only = no"
SAMBA_VOLUME_CONFIG_documents: "[documents]; path = /shares/documents; valid users = ada; read only = no"
SAMBA_VOLUME_CONFIG_backups_win: "[backups_win]; path = /shares/backups_win; valid users = ada; read only = no"
@@ -0,0 +1,14 @@
services:
syncthing:
image: syncthing/syncthing:latest
container_name: syncthing
hostname: cloud-pc
environment:
PUID: "1000"
PGID: "1000"
STGUIADDRESS: 0.0.0.0:8384
volumes:
- /opt/configs/syncthing:/var/syncthing/config
- /opt/data/syncthing:/var/syncthing/data
network_mode: host
restart: unless-stopped
@@ -0,0 +1,15 @@
services:
zerotier:
container_name: zerotier
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
- SYS_ADMIN
devices:
- /dev/net/tun
volumes:
- /opt/data/zerotier:/var/lib/zerotier-one
image: zerotier/zerotier:latest
command: ["743993800fa5a34c"]
networks: {}
@@ -0,0 +1,16 @@
- name: Base server setup
hosts: servers
gather_facts: true
become: false
tasks:
- name: Ping hosts
ansible.builtin.ping:
- name: Check expected LAN IP
ansible.builtin.assert:
that:
- expected_lan_ip in ansible_all_ipv4_addresses
fail_msg: "{{ inventory_hostname }} must have {{ expected_lan_ip }}; found: {{ ansible_all_ipv4_addresses | join(', ') }}"
success_msg: "{{ inventory_hostname }} has {{ expected_lan_ip }}"
when: expected_lan_ip is defined
@@ -0,0 +1,10 @@
# Запускать один раз на новом сервере от имени root или пользователя с полным sudo:
# ansible-playbook -i inventory/hosts.yml playbooks/bootstrap.yml -K
#
# После выполнения: Ansible-плейбуки всё равно требуют -K (become),
# но деплой через GitOps (docker compose) работает без пароля.
- name: Bootstrap servers
hosts: servers
become: true
roles:
- bootstrap
@@ -0,0 +1,119 @@
- name: Directory structure — cloud-pc
hosts: cloud-pc
become: true
vars:
data_user: ada
data_group: ada
tasks:
- name: Create base mount points
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ data_user }}"
group: "{{ data_group }}"
mode: "0755"
loop: "{{ storage_mounts | default([]) }}"
- name: Mount base storage disks
ansible.posix.mount:
path: "{{ item.path }}"
src: "{{ item.src }}"
fstype: "{{ item.fstype }}"
opts: "{{ item.opts | default('defaults,nofail') }}"
state: mounted
loop: "{{ storage_mounts | default([]) }}"
- name: Create service data and config directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ data_user }}"
group: "{{ data_group }}"
mode: "0755"
loop:
- /opt/data/postgres
- /opt/data/nextcloud
- /opt/data/gitea
- /opt/data/gitea-runner
- /opt/data/syncthing
- /opt/data/zerotier
- /opt/samba/media
- /opt/samba/documents
- /opt/samba/backups_win
- /opt/configs
- /opt/configs/nextcloud/app
- /opt/configs/syncthing
- name: Create /opt/services
ansible.builtin.file:
path: /opt/services
state: directory
owner: "{{ data_user }}"
group: "{{ data_group }}"
mode: "0755"
- name: Create backup directories (3TB)
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ data_user }}"
group: "{{ data_group }}"
mode: "0755"
loop:
- /mnt/backup/restic/services
- /mnt/backup/restic/samba
- /mnt/backup/devices/proxmox
- /mnt/backup/devices/ada-x1
- name: Directory structure — mini-pc
hosts: mini-pc
become: true
vars:
data_user: ada
data_group: ada
tasks:
- name: Create service directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ data_user }}"
group: "{{ data_group }}"
mode: "0755"
loop:
- /opt/services
- /opt/data
- /opt/configs
- /opt/data/vaultwarden
- /opt/data/adguard/work
- /opt/data/caddy
- /opt/data/gitea-runner
- /opt/data/npm
- /opt/data/uptime-kuma
- /opt/data/zerotier
- /opt/configs/adguard
- /opt/configs/caddy
- /opt/configs/caddy/lego
- /opt/configs/npm/letsencrypt
- name: Directory structure — ru-vps
hosts: ru-vps
become: true
vars:
data_user: ada
data_group: ada
tasks:
- name: Create service directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ data_user }}"
group: "{{ data_group }}"
mode: "0755"
loop:
- /opt/services
- /opt/data
- /opt/configs
- /opt/data/caddy
- /opt/data/gitea-runner
- /opt/data/zerotier
- /opt/configs/caddy
@@ -0,0 +1,9 @@
- name: Install rclone
hosts: servers
become: true
tasks:
- name: Install rclone package
ansible.builtin.apt:
name: rclone
state: present
update_cache: true
@@ -0,0 +1,36 @@
- name: Install resticprofile
hosts: servers
become: true
tasks:
- name: Install restic package
ansible.builtin.apt:
name: restic
state: present
update_cache: true
- name: Create restic config directory
ansible.builtin.file:
path: /etc/restic
state: directory
owner: root
group: root
mode: "0755"
- name: Create resticprofile log directory
ansible.builtin.file:
path: /var/log/resticprofile
state: directory
owner: root
group: root
mode: "0755"
- name: Download resticprofile install script
ansible.builtin.get_url:
url: https://raw.githubusercontent.com/creativeprojects/resticprofile/master/install.sh
dest: /tmp/resticprofile-install.sh
mode: "0755"
- name: Install resticprofile binary
ansible.builtin.command:
cmd: /tmp/resticprofile-install.sh -b /usr/local/bin
creates: /usr/local/bin/resticprofile
@@ -0,0 +1,5 @@
bootstrap_user: ada
bootstrap_ssh_public_keys: [ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWNsUQmppB6cQccXX1ZaBbFIcmM6RmghsTVbG9TgoZB ada@ada-x1]
docker_registry_mirrors:
- https://mirror.gcr.io
- https://dockerhub.timeweb.cloud
@@ -0,0 +1,4 @@
- name: restart sshd
ansible.builtin.service:
name: sshd
state: restarted
@@ -0,0 +1,85 @@
- name: Ensure user exists
ansible.builtin.user:
name: "{{ bootstrap_user }}"
shell: /bin/bash
groups: sudo
append: true
create_home: true
state: present
# docker group создаётся только после установки Docker.
# Добавление происходит в роли docker, не здесь.
- name: Add SSH authorized keys
ansible.posix.authorized_key:
user: "{{ bootstrap_user }}"
key: "{{ item }}"
state: present
loop: "{{ bootstrap_ssh_public_keys }}"
when: bootstrap_ssh_public_keys | length > 0
- name: Gather service facts
ansible.builtin.service_facts:
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
mode: "0755"
- name: Configure Docker registry mirrors
ansible.builtin.copy:
dest: /etc/docker/daemon.json
content: "{{ {'registry-mirrors': docker_registry_mirrors} | to_nice_json }}\n"
mode: "0644"
register: docker_daemon_config
when: docker_registry_mirrors | length > 0
- name: Restart Docker after daemon config change
ansible.builtin.service:
name: docker
state: restarted
when:
- docker_daemon_config is changed
- "'docker.service' in ansible_facts.services"
# Команды, которые реально нужны без пароля:
# apt / apt-get — обновление пакетов
# systemctl — управление сервисами
# ufw — фаервол
# resticprofile — установка systemd-таймеров бэкапа
#
# Ansible become НЕ входит в этот список намеренно:
# для плейбуков запускать с -K. Это сознательный компромисс
# между удобством и тем, чтобы не давать python3 NOPASSWD (= root).
- name: Configure sudoers — NOPASSWD for specific commands
ansible.builtin.copy:
dest: /etc/sudoers.d/{{ bootstrap_user }}
content: |
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/bin/ufw
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/local/bin/resticprofile
validate: /usr/sbin/visudo -cf %s
mode: "0440"
# Drop-in конфиг — не трогаем основной sshd_config.
# Требует Debian 12+ (OpenSSH 8.9+).
- name: Harden SSH
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
mode: "0644"
notify: restart sshd
- name: Ensure sshd_config.d is included
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
line: Include /etc/ssh/sshd_config.d/*.conf
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
notify: restart sshd
@@ -0,0 +1,46 @@
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
#network_mode: "host" # DHCP server - optional, only if AdGuard handles DHCP
restart: always
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 64M
healthcheck:
test:
- CMD-SHELL
- /opt/adguardhome/AdGuardHome --check-config -w /opt/adguardhome/work
-c /opt/adguardhome/conf/AdGuardHome.yaml
interval: 45s
timeout: 30s
retries: 3
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Moscow
volumes:
- /opt/data/adguard/work:/opt/adguardhome/work:rw
- /opt/configs/adguard:/opt/adguardhome/conf:rw
- /etc/hosts:/etc/hosts:ro
ports:
- 53:53/tcp # DNS - **mandatory**
- 53:53/udp # DNS - **mandatory**
#- "67:67/udp" # DHCP server - optional, only if AdGuard handles DHCP
#- "68:68/udp" # DHCP server - optional
- 8088:80/tcp # Dashboard HTTP - optional, only if not using HTTPS
- 4438:443/tcp # DNS-over-HTTPS / dashboard HTTPS - optional but recommended if using DoH
- 784:784/udp # DNS-over-QUIC (beta) - optional, experimental
- 853:853/tcp # DNS-over-TLS - optional
- 3015:3000/tcp # Dashboard initial config - optional, only for initial setup
- 5443:5443/tcp # DNSCrypt / alternative DoH - optional
- 5443:5443/udp # DNSCrypt depends on TCP - optional
#- "6060:6060/tcp" # Internal API - optional, do not expose
#- "8853:8853/udp" # DNS mTLS (beta) - optional, experimental
cap_add:
- NET_ADMIN
- SYS_MODULE
networks: {}
@@ -0,0 +1,2 @@
LEGO_EMAIL=you@example.com
TIMEWEBCLOUD_AUTH_TOKEN=changeme
@@ -0,0 +1,19 @@
git.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.5:3002
}
cloud.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.5:8081
}
router.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.1:80
}
dns.ada-dev.ru {
tls /config/lego/certificates/ada-dev.ru.crt /config/lego/certificates/ada-dev.ru.key
reverse_proxy 192.168.1.10:8088
}
@@ -0,0 +1,68 @@
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
dns:
- 1.1.1.1
- 8.8.8.8
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- /opt/data/caddy:/data
- /opt/configs/caddy:/config
entrypoint: /bin/sh
command:
- -c
- |
while [ ! -f /config/lego/certificates/ada-dev.ru.crt ] || [ ! -f /config/lego/certificates/ada-dev.ru.key ]; do
sleep 10
done
exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
lego:
image: git.ada-dev.ru/ada/lego:latest
container_name: caddy-lego
dns:
- 1.1.1.1
- 8.8.8.8
env_file:
- .env
restart: unless-stopped
volumes:
- /opt/configs/caddy/lego:/lego-data
entrypoint: /bin/sh
command:
- -c
- |
while true; do
if [ -f /lego-data/certificates/ada-dev.ru.crt ]; then
/lego renew \
--email "$$LEGO_EMAIL" \
--dns timewebcloud \
--dns.resolvers 1.1.1.1:53 \
--dns.resolvers 8.8.8.8:53 \
--dns.propagation-timeout 5m \
--domains ada-dev.ru \
--domains '*.ada-dev.ru' \
--path /lego-data \
--days 30
sleep 24h
else
/lego run \
--email "$$LEGO_EMAIL" \
--accept-tos \
--dns timewebcloud \
--dns.resolvers 1.1.1.1:53 \
--dns.resolvers 8.8.8.8:53 \
--dns.propagation-timeout 5m \
--domains ada-dev.ru \
--domains '*.ada-dev.ru' \
--path /lego-data
sleep 10m
fi
done
networks: {}
@@ -0,0 +1,4 @@
FROM gitea/act_runner:latest
USER root
RUN apk add --no-cache docker-cli docker-cli-compose openssh-client
@@ -0,0 +1,16 @@
services:
gitea-runner:
build: .
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/services:/opt/services
- /opt/data/gitea-runner:/data
environment:
GITEA_INSTANCE_URL: https://git.ada-dev.ru
GITEA_RUNNER_REGISTRATION_TOKEN: ${TOKEN}
GITEA_RUNNER_NAME: mini-pc-runner
GITEA_RUNNER_LABELS: mini-pc
dns:
- 1.1.1.1
- 8.8.8.8
@@ -0,0 +1,13 @@
services:
memoir-bot:
image: git.ada-dev.ru/ada/memoir_bot:0.1
restart: unless-stopped
env_file:
- .env
environment:
OBSIDIAN_VAULT_PATH: /vault
TZ: ${TIMEZONE:-Europe/Moscow}
volumes:
- /opt/data/memoir_bot:/vault
- /home/ada/.ssh:/root/.ssh:ro
network_mode: host
@@ -0,0 +1,38 @@
version = "1"
[global]
scheduler = "systemd"
[default]
password-file = "/etc/restic/password"
initialize = true
[default.forget]
keep-daily = 7
keep-weekly = 4
keep-monthly = 6
schedule-permission = "system"
schedule-after-backup = true
prune = true
[default.check]
schedule = "Sun 04:00"
schedule-permission = "system"
schedule-after-network-online = true
[services]
inherit = "default"
repository = "rclone:yadisk:System/Backups/mini-pc/restic/services"
[services.backup]
source = ["/opt/data", "/opt/configs"]
tag = ["mini-pc", "services"]
exclude-caches = true
one-file-system = true
schedule = "03:00"
schedule-permission = "system"
schedule-after-network-online = true
schedule-log = "/var/log/resticprofile/services.log"
[services.forget]
schedule = "03:30"
@@ -0,0 +1,13 @@
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
hostname: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
environment:
TZ: Europe/Moscow
volumes:
- /opt/data/uptime-kuma:/app/data
networks: {}
@@ -0,0 +1,14 @@
services:
v2raya:
image: mzz2017/v2raya:latest
container_name: v2raya
restart: unless-stopped
network_mode: host
privileged: true
volumes:
- /opt/data/v2raya:/etc/v2raya
- /opt/data/v2raya-extra:/etc/v2raya-extra
environment:
- V2RAYA_LOG_FILE=/tmp/v2raya.log
- V2RAYA_V2RAY_BIN_PATH=/usr/local/bin/v2ray
- V2RAYA_V2RAY_CONFDIR=/etc/v2raya-extra
@@ -0,0 +1,8 @@
ADMIN_TOKEN=changeme
SMTP_HOST=smtp.yandex.ru
SMTP_PORT=465
SMTP_SECURITY=force_tls
SMTP_USERNAME=info.ada-dev@yandex.ru
SMTP_PASSWORD=changeme
SMTP_FROM=info.ada-dev@yandex.ru
SMTP_FROM_NAME=Vaultwarden
@@ -0,0 +1,17 @@
services:
vaultwarden:
hostname: vaultwarden
image: vaultwarden/server:latest
ports:
- target: 80
published: "10380"
protocol: tcp
restart: unless-stopped
env_file: .env
volumes:
- type: bind
source: /opt/data/vaultwarden
target: /data
networks:
default:
name: vaultwarden_default
@@ -0,0 +1,15 @@
services:
zerotier:
container_name: zerotier
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
- SYS_ADMIN
devices:
- /dev/net/tun
volumes:
- /opt/data/zerotier:/var/lib/zerotier-one
image: zerotier/zerotier:latest
command: ["743993800fa5a34c"]
networks: {}
@@ -0,0 +1,23 @@
{
description = "Homelab NixOS hosts";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
};
outputs = { nixpkgs, ... }: {
nixosConfigurations.cloud-pc = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/cloud-pc
];
};
nixosConfigurations.mini-pc = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/mini-pc
];
};
};
}
@@ -0,0 +1,73 @@
{ ... }:
{
imports = [
../../modules/base.nix
../../modules/docker.nix
../../modules/libvirtd.nix
../../modules/zerotier.nix
./firewall.nix
./hardware.nix
];
networking = {
hostName = "cloud-pc";
useDHCP = false;
defaultGateway = "192.168.1.1";
nameservers = [
"192.168.1.1"
"1.1.1.1"
];
interfaces.enp2s0.ipv4.addresses = [
{
address = "192.168.1.5";
prefixLength = 24;
}
];
};
fileSystems."/opt/data" = {
device = "/dev/disk/by-uuid/ae278333-4116-4225-9b95-595496fadd26";
fsType = "ext4";
options = [ "nofail" ];
};
fileSystems."/opt/samba" = {
device = "/dev/disk/by-uuid/89024d30-1ab6-4b2e-b23b-62837455b24a";
fsType = "ext4";
options = [ "nofail" ];
};
fileSystems."/mnt/backup" = {
device = "/dev/disk/by-uuid/940a9a4e-10cf-4380-9d22-a71549066561";
fsType = "ext4";
options = [ "nofail" ];
};
systemd.tmpfiles.rules = [
"d /opt/data/postgres 0755 ada users - -"
"d /opt/data/nextcloud 0755 ada users - -"
"d /opt/data/gitea 0755 ada users - -"
"d /opt/data/gitea-runner 0755 ada users - -"
"d /opt/data/syncthing 0755 ada users - -"
"d /opt/data/zerotier 0755 ada users - -"
"d /opt/configs/nextcloud 0755 ada users - -"
"d /opt/configs/nextcloud/app 0755 ada users - -"
"d /opt/configs/syncthing 0755 ada users - -"
"d /opt/samba 0755 ada users - -"
"d /opt/samba/media 0755 ada users - -"
"d /opt/samba/documents 0755 ada users - -"
"d /opt/samba/backups_win 0755 ada users - -"
"d /mnt/backup 0755 ada users - -"
"d /mnt/backup/restic 0755 ada users - -"
"d /mnt/backup/restic/services 0755 ada users - -"
"d /mnt/backup/restic/samba 0755 ada users - -"
"d /mnt/backup/devices 0755 ada users - -"
"d /mnt/backup/devices/proxmox 0755 ada users - -"
"d /mnt/backup/devices/ada-x1 0755 ada users - -"
"d /mnt/yandex 0755 ada users - -"
];
system.stateVersion = "25.11";
}
@@ -0,0 +1,25 @@
{
networking.firewall = {
enable = true;
allowedTCPPorts = [
22 # SSH
139 # Samba (NetBIOS session)
445 # Samba (SMB)
2222 # Gitea SSH
3002
4567
5432 # PostgreSQL
7080
8081
8384 # Syncthing web UI
22000 # Syncthing data transfer
];
allowedUDPPorts = [
137 # Samba (NetBIOS name)
138 # Samba (NetBIOS datagram)
9993 # ZeroTier
21027 # Syncthing discovery
22000 # Syncthing data transfer
];
};
}
@@ -0,0 +1,31 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/f729e8cb-5c37-45fe-bd0a-e511d79bb49c";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/8BA8-38FE";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
@@ -0,0 +1,72 @@
{ ... }:
{
imports = [
../../modules/base.nix
../../modules/docker.nix
../../modules/libvirtd.nix
../../modules/zerotier.nix
./firewall.nix
./hardware.nix
];
networking = {
hostName = "mini-pc";
useDHCP = false;
};
systemd.network = {
enable = true;
networks."10-lan" = {
matchConfig.Name = "en*";
networkConfig = {
Address = "192.168.1.10/24";
Gateway = "192.168.1.1";
DNS = [
"192.168.1.1"
"1.1.1.1"
];
};
};
};
services.resolved = {
enable = true;
fallbackDns = [ "1.1.1.1" "8.8.8.8" ];
};
systemd.tmpfiles.rules = [
"d /opt/data/adguard 0755 ada users - -"
"d /opt/data/adguard/work 0755 ada users - -"
"d /opt/data/caddy 0755 ada users - -"
"d /opt/data/gitea-runner 0755 ada users - -"
"d /opt/data/uptime-kuma 0755 ada users - -"
"d /opt/data/vaultwarden 0755 ada users - -"
"d /opt/data/zerotier 0755 ada users - -"
"d /opt/configs/adguard 0755 ada users - -"
"d /opt/configs/caddy 0755 ada users - -"
"d /opt/configs/caddy/lego 0755 ada users - -"
];
programs.bash.interactiveShellInit = ''
proxy_on() {
export http_proxy=http://192.168.1.10:10172
export https_proxy=http://192.168.1.10:10172
export HTTP_PROXY=http://192.168.1.10:10172
export HTTPS_PROXY=http://192.168.1.10:10172
export all_proxy=socks5://192.168.1.10:10171
export ALL_PROXY=socks5://192.168.1.10:10171
export ftp_proxy=http://192.168.1.10:10172
export FTP_PROXY=http://192.168.1.10:10172
export no_proxy=localhost,127.0.0.0/8,::1,192.168.1.0/24
export NO_PROXY=localhost,127.0.0.0/8,::1,192.168.1.0/24
}
proxy_off() {
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
unset all_proxy ALL_PROXY ftp_proxy FTP_PROXY
unset no_proxy NO_PROXY
}
'';
system.stateVersion = "25.11";
}
@@ -0,0 +1,28 @@
{
networking.firewall = {
enable = true;
allowedTCPPorts = [
22 # SSH
53 # DNS (AdGuard)
80 # HTTP
443 # HTTPS
784 # DNS-over-QUIC
853 # DNS-over-TLS
3001 # Uptime Kuma
3015
4438
5443
8088
10171 # v2raya SOCKS5 LAN
10172 # v2raya HTTP proxy LAN
10380
];
allowedUDPPorts = [
53 # DNS
443 # QUIC
784 # DNS-over-QUIC
9993 # ZeroTier
5443
];
};
}
@@ -0,0 +1,31 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "sdhci_pci" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/a2fc1626-3301-44dd-9d00-fa7e8d216d25";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/73C2-3F45";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
@@ -0,0 +1,85 @@
{ pkgs, ... }:
{
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
boot = {
tmp.cleanOnBoot = true;
loader.systemd-boot.enable = true;
loader.efi.canTouchEfiVariables = true;
};
systemd.network.wait-online.anyInterface = true;
systemd.services.nix-daemon.environment = {
HTTPS_PROXY = "http://192.168.1.10:10172";
HTTP_PROXY = "http://192.168.1.10:10172";
NO_PROXY = "localhost,127.0.0.0/8,::1,192.168.1.0/24";
};
time.timeZone = "Europe/Moscow";
i18n.defaultLocale = "ru_RU.UTF-8";
i18n.supportedLocales = [
"ru_RU.UTF-8/UTF-8"
"en_US.UTF-8/UTF-8"
];
users.users.ada = {
isNormalUser = true;
shell = pkgs.bashInteractive;
extraGroups = [
"wheel"
"docker"
];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWNsUQmppB6cQccXX1ZaBbFIcmM6RmghsTVbG9TgoZB ada@ada-x1"
];
};
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "no";
PubkeyAuthentication = true;
};
};
security.sudo.extraRules = [
{
users = [ "ada" ];
commands = [
{
command = "/run/current-system/sw/bin/nixos-rebuild";
options = [ "NOPASSWD" ];
}
];
}
];
environment.systemPackages = with pkgs; [
git
restic
resticprofile
htop
];
systemd.tmpfiles.rules = [
"d /opt/services 0755 ada users - -"
"Z /opt/services - ada users - -"
"d /opt/data 0755 ada users - -"
"d /opt/configs 0755 ada users - -"
];
}
@@ -0,0 +1,17 @@
{ pkgs, ... }:
{
virtualisation.docker = {
enable = true;
daemon.settings = {
registry-mirrors = [
"https://mirror.gcr.io"
"https://dockerhub.timeweb.cloud"
];
};
};
environment.systemPackages = with pkgs; [
docker-compose
];
}
@@ -0,0 +1,10 @@
{ pkgs, ... }:
{
virtualisation.libvirtd = {
enable = true;
qemu.package = pkgs.qemu_kvm;
};
users.users.ada.extraGroups = [ "libvirtd" ];
}
@@ -0,0 +1,6 @@
{
services.zerotierone = {
enable = true;
joinNetworks = [ "743993800fa5a34c" ];
};
}
@@ -0,0 +1,26 @@
ada-dev.ru {
reverse_proxy 10.122.62.51:7080
}
cloud.ada-dev.ru {
reverse_proxy 10.122.62.51:8081
}
git.ada-dev.ru {
reverse_proxy 10.122.62.51:3002
}
pass.ada-dev.ru {
reverse_proxy 10.122.62.95:10380
}
uptime.ada-dev.ru {
reverse_proxy 10.122.62.95:3001
}
projects.ada-dev.ru {
handle_path /neural* {
reverse_proxy 10.122.62.51:4567
}
}
@@ -0,0 +1,14 @@
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- /opt/data/caddy:/data
- /opt/configs/caddy:/config
networks: {}
@@ -0,0 +1,4 @@
FROM gitea/act_runner:latest
USER root
RUN apk add --no-cache docker-cli docker-cli-compose
@@ -0,0 +1,13 @@
services:
gitea-runner:
build: .
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/services:/opt/services
- /opt/data/gitea-runner:/data
environment:
GITEA_INSTANCE_URL: https://git.ada-dev.ru
GITEA_RUNNER_REGISTRATION_TOKEN: ${TOKEN}
GITEA_RUNNER_NAME: ru-vps-runner
GITEA_RUNNER_LABELS: ru-vps
@@ -0,0 +1,56 @@
mixed-port: 7890
http-port: 7892
socks-port: 7891
allow-lan: true # Меняем с false на true
bind-address: "0.0.0.0" # Добавляем - слушаем все интерфейсы
mode: rule
external-controller: 127.0.0.1:9090
external-ui: /root/.config/mihomo/ui
# secret: "changeme"
authentication:
- "ada:Holivarr588856"
proxy-providers:
main:
type: http
url: "https://491b5056.withblancvpn.online/s/c1b50e857b4e41a59ea80bcf6d1953d3"
interval: 3600
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 300
proxy-groups:
- name: "URL-Test"
type: url-test
use: [main]
url: "https://www.gstatic.com/generate_204"
interval: 300 # Тестировать каждые 5 минут
tolerance: 50
rules:
- DOMAIN-SUFFIX,local,DIRECT
- DOMAIN-SUFFIX,lan,DIRECT
- DOMAIN-SUFFIX,ru,DIRECT
- DOMAIN-SUFFIX,su,DIRECT
- DOMAIN-SUFFIX,xn--p1ai,DIRECT
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR,169.254.0.0/16,DIRECT,no-resolve
- IP-CIDR6,::1/128,DIRECT,no-resolve
- IP-CIDR6,fc00::/7,DIRECT,no-resolve
- IP-CIDR6,fe80::/10,DIRECT,no-resolve
- GEOIP,RU,DIRECT
- MATCH,URL-Test
# tun:
# enable: true
# stack: mixed
# auto-route: true
# auto-detect-interface: true
# dns-hijack:
# - "any:53"
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><script type="importmap">{"imports":{"#entry":"./_nuxt/Bmqvg1eR.js"}}</script><title>MetaCubeXD</title><script src="config.js" onerror="window.__METACUBEXD_CONFIG__={defaultBackendURL:''}"></script><script>window.__METACUBEXD_CONFIG__ = window.__METACUBEXD_CONFIG__ || { defaultBackendURL: '' }</script><link rel="stylesheet" href="./_nuxt/entry.B_k74qUh.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="./_nuxt/Bmqvg1eR.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/CaGtQlYh.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/QTnfLwEv.js"><script type="module" src="./_nuxt/Bmqvg1eR.js" crossorigin></script><meta name="theme-color" content="#000000"><link rel="icon" type="image/svg+xml" href="favicon.svg"><link rel="apple-touch-icon" href="pwa-192x192.png"><link rel="manifest" href="manifest.webmanifest"><script id="unhead:payload" type="application/json">{"title":"MetaCubeXD"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{appVersion:"1.251.3",mockMode:false,defaultBackendURL:"",i18n:{baseUrl:"",defaultLocale:"en",rootRedirect:"",redirectStatusCode:302,skipSettingLocaleOnNavigate:false,locales:[{code:"en",name:"English",language:""},{code:"zh",name:"简体中文",language:""},{code:"ru",name:"Русский",language:""}],detectBrowserLanguage:{alwaysRedirect:false,cookieCrossOrigin:false,cookieDomain:"",cookieKey:"metacubexd_lang",cookieSecure:false,fallbackLocale:"en",redirectOn:"root",useCookie:true},experimental:{localeDetector:"",typedPages:true,typedOptionsAndMessages:false,alternateLinkCanonicalQueries:true,devCache:false,cacheLifetime:"",stripMessagesPayload:false,preload:false,strictSeo:false,nitroContextDetection:true,httpCacheDuration:10,compactRoutes:false,prerenderMessages:false},domainLocales:{en:{domain:""},zh:{domain:""},ru:{domain:""}}}},app:{baseURL:"./",buildId:"cdc84ee9-d7c0-44a7-96d3-b0595bcf2138",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1780682638349,false]</script></body></html>
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><script type="importmap">{"imports":{"#entry":"./_nuxt/Bmqvg1eR.js"}}</script><title>MetaCubeXD</title><script src="config.js" onerror="window.__METACUBEXD_CONFIG__={defaultBackendURL:''}"></script><script>window.__METACUBEXD_CONFIG__ = window.__METACUBEXD_CONFIG__ || { defaultBackendURL: '' }</script><link rel="stylesheet" href="./_nuxt/entry.B_k74qUh.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="./_nuxt/Bmqvg1eR.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/CaGtQlYh.js"><link rel="modulepreload" as="script" crossorigin href="./_nuxt/QTnfLwEv.js"><script type="module" src="./_nuxt/Bmqvg1eR.js" crossorigin></script><meta name="theme-color" content="#000000"><link rel="icon" type="image/svg+xml" href="favicon.svg"><link rel="apple-touch-icon" href="pwa-192x192.png"><link rel="manifest" href="manifest.webmanifest"><script id="unhead:payload" type="application/json">{"title":"MetaCubeXD"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{appVersion:"1.251.3",mockMode:false,defaultBackendURL:"",i18n:{baseUrl:"",defaultLocale:"en",rootRedirect:"",redirectStatusCode:302,skipSettingLocaleOnNavigate:false,locales:[{code:"en",name:"English",language:""},{code:"zh",name:"简体中文",language:""},{code:"ru",name:"Русский",language:""}],detectBrowserLanguage:{alwaysRedirect:false,cookieCrossOrigin:false,cookieDomain:"",cookieKey:"metacubexd_lang",cookieSecure:false,fallbackLocale:"en",redirectOn:"root",useCookie:true},experimental:{localeDetector:"",typedPages:true,typedOptionsAndMessages:false,alternateLinkCanonicalQueries:true,devCache:false,cacheLifetime:"",stripMessagesPayload:false,preload:false,strictSeo:false,nitroContextDetection:true,httpCacheDuration:10,compactRoutes:false,prerenderMessages:false},domainLocales:{en:{domain:""},zh:{domain:""},ru:{domain:""}}}},app:{baseURL:"./",buildId:"cdc84ee9-d7c0-44a7-96d3-b0595bcf2138",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1780682638350,false]</script></body></html>
@@ -0,0 +1 @@
d.metacubex.one
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{a as e,i as t,r as n}from"./DhTvfVif.js";import{t as r}from"./CuyTkb7v.js";var i=`[object Symbol]`;function a(e){return typeof e==`symbol`||n(e)&&t(e)==i}var o=/\s/;function s(e){for(var t=e.length;t--&&o.test(e.charAt(t)););return t}var c=/^\s+/;function l(e){return e&&e.slice(0,s(e)+1).replace(c,``)}var u=NaN,d=/^[-+]0x[0-9a-f]+$/i,f=/^0b[01]+$/i,p=/^0o[0-7]+$/i,m=parseInt;function h(e){if(typeof e==`number`)return e;if(a(e))return u;if(r(e)){var t=typeof e.valueOf==`function`?e.valueOf():e;e=r(t)?t+``:t}if(typeof e!=`string`)return e===0?e:+e;e=l(e);var n=f.test(e);return n||p.test(e)?m(e.slice(2),n?2:8):d.test(e)?u:+e}var g=function(){return e.Date.now()},_=`Expected a function`,v=Math.max,y=Math.min;function b(e,t,n){var i,a,o,s,c,l,u=0,d=!1,f=!1,p=!0;if(typeof e!=`function`)throw TypeError(_);t=h(t)||0,r(n)&&(d=!!n.leading,f=`maxWait`in n,o=f?v(h(n.maxWait)||0,t):o,p=`trailing`in n?!!n.trailing:p);function m(t){var n=i,r=a;return i=a=void 0,u=t,s=e.apply(r,n),s}function b(e){return u=e,c=setTimeout(C,t),d?m(e):s}function x(e){var n=e-l,r=e-u,i=t-n;return f?y(i,o-r):i}function S(e){var n=e-l,r=e-u;return l===void 0||n>=t||n<0||f&&r>=o}function C(){var e=g();if(S(e))return w(e);c=setTimeout(C,x(e))}function w(e){return c=void 0,p&&i?m(e):(i=a=void 0,s)}function T(){c!==void 0&&clearTimeout(c),u=0,i=l=a=c=void 0}function E(){return c===void 0?s:w(g())}function D(){var e=g(),n=S(e);if(i=arguments,a=this,l=e,n){if(c===void 0)return b(l);if(f)return clearTimeout(c),c=setTimeout(C,t),m(l)}return c===void 0&&(c=setTimeout(C,t)),s}return D.cancel=T,D.flush=E,D}var x=`Expected a function`;function S(e,t,n){var i=!0,a=!0;if(typeof e!=`function`)throw TypeError(x);return r(n)&&(i=`leading`in n?!!n.leading:i,a=`trailing`in n?!!n.trailing:a),b(e,t,{leading:i,maxWait:t,trailing:a})}export{S as t};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{D as e,G as t,H as n,T as r,b as i,d as a,v as o,vt as s,wt as c,x as l,xt as u}from"./CaGtQlYh.js";import{J as d,X as f,Y as p,q as m}from"#entry";var h={class:`flex shrink-0 items-center overflow-hidden rounded-lg border border-base-content/12`},g=[`disabled`],_=[`disabled`],v={key:0,class:`flex items-center border-r border-r-base-content/10 bg-base-200/80 px-1 text-xs text-base-content/40`},y=[`onClick`],b=[`disabled`],x=[`disabled`],S=Object.assign(e({__name:`ConnectionsPagination`,props:{currentPage:{},totalPages:{},visiblePages:{}},emits:[`goToPage`,`previous`,`next`],setup(e,{emit:S}){let C=S;return(S,w)=>(n(),l(`div`,h,[o(`button`,{class:`flex h-7 min-w-7 cursor-pointer items-center justify-center border-r border-none border-r-base-content/10 bg-base-200/80 px-1.5 text-xs text-base-content transition-all duration-150 ease-out hover:not-disabled:bg-base-content/10 disabled:cursor-not-allowed disabled:opacity-40`,disabled:e.currentPage===0,onClick:w[0]||=e=>C(`goToPage`,0)},[r(s(d),{size:14})],8,g),o(`button`,{class:`flex h-7 min-w-7 cursor-pointer items-center justify-center border-r border-none border-r-base-content/10 bg-base-200/80 px-1.5 text-xs text-base-content transition-all duration-150 ease-out hover:not-disabled:bg-base-content/10 disabled:cursor-not-allowed disabled:opacity-40`,disabled:e.currentPage===0,onClick:w[1]||=e=>C(`previous`)},[r(s(f),{size:14})],8,_),(n(!0),l(a,null,t(e.visiblePages,(t,r)=>(n(),l(a,{key:t},[r>0&&e.visiblePages[r-1]!==t-1?(n(),l(`span`,v,` ··· `)):i(``,!0),o(`button`,{class:u([`flex h-7 min-w-8 cursor-pointer items-center justify-center border-r border-none border-r-base-content/10 bg-base-200/80 px-1.5 text-xs text-base-content transition-all duration-150 ease-out last:border-r-0 hover:not-disabled:bg-base-content/10 disabled:cursor-not-allowed disabled:opacity-40`,{"bg-primary! text-primary-content! hover:bg-primary!":e.currentPage===t}]),onClick:e=>C(`goToPage`,t)},c(t+1),11,y)],64))),128)),o(`button`,{class:`flex h-7 min-w-7 cursor-pointer items-center justify-center border-r border-none border-r-base-content/10 bg-base-200/80 px-1.5 text-xs text-base-content transition-all duration-150 ease-out last:border-r-0 hover:not-disabled:bg-base-content/10 disabled:cursor-not-allowed disabled:opacity-40`,disabled:e.currentPage>=e.totalPages-1,onClick:w[2]||=e=>C(`next`)},[r(s(p),{size:14})],8,b),o(`button`,{class:`flex h-7 min-w-7 cursor-pointer items-center justify-center border-none bg-base-200/80 px-1.5 text-xs text-base-content transition-all duration-150 ease-out last:border-r-0 hover:not-disabled:bg-base-content/10 disabled:cursor-not-allowed disabled:opacity-40`,disabled:e.currentPage>=e.totalPages-1,onClick:w[3]||=t=>C(`goToPage`,e.totalPages-1)},[r(s(m),{size:14})],8,x)]))}}),{__name:`ConnectionsPagination`});export{S as t};
@@ -0,0 +1,2 @@
try{self[`workbox:window:7.4.0`]&&_()}catch{}function e(e,t){return new Promise((function(n){var r=new MessageChannel;r.port1.onmessage=function(e){n(e.data)},e.postMessage(t,[r.port2])}))}function t(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function n(e,t,n){return t&&function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,`value`in r&&(r.writable=!0),Object.defineProperty(e,a(r.key),r)}}(e.prototype,t),Object.defineProperty(e,"prototype",{writable:!1}),e}function r(e,n){var r=typeof Symbol<`u`&&e[Symbol.iterator]||e[`@@iterator`];if(r)return(r=r.call(e)).next.bind(r);if(Array.isArray(e)||(r=function(e,n){if(e){if(typeof e==`string`)return t(e,n);var r={}.toString.call(e).slice(8,-1);return r===`Object`&&e.constructor&&(r=e.constructor.name),r===`Map`||r===`Set`?Array.from(e):r===`Arguments`||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}(e))||n){r&&(e=r);var i=0;return function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}}}throw TypeError(`Invalid attempt to iterate non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function i(e,t){return i=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},i(e,t)}function a(e){var t=function(e,t){if(typeof e!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return String(e)}(e,`string`);return typeof t==`symbol`?t:t+``}try{self[`workbox:core:7.4.0`]&&_()}catch{}var o=function(){var e=this;this.promise=new Promise((function(t,n){e.resolve=t,e.reject=n}))};function s(e,t){var n=location.href;return new URL(e,n).href===new URL(t,n).href}var c=function(e,t){this.type=e,Object.assign(this,t)};function l(e,t,n){return e&&e.then||(e=Promise.resolve(e)),t?e.then(t):e}function u(){}var d={type:`SKIP_WAITING`};function f(e,t){return e&&e.then?e.then(u):Promise.resolve()}var p=function(t){function r(e,n){var r,i;return n===void 0&&(n={}),(r=t.call(this)||this).nn={},r.tn=0,r.rn=new o,r.en=new o,r.on=new o,r.un=0,r.an=new Set,r.cn=function(){var e=r.fn,t=e.installing;r.tn>0||!s(t.scriptURL,r.sn.toString())||performance.now()>r.un+6e4?(r.vn=t,e.removeEventListener(`updatefound`,r.cn)):(r.hn=t,r.an.add(t),r.rn.resolve(t)),++r.tn,t.addEventListener(`statechange`,r.ln)},r.ln=function(e){var t=r.fn,n=e.target,i=n.state,a=n===r.vn,o={sw:n,isExternal:a,originalEvent:e};!a&&r.mn&&(o.isUpdate=!0),r.dispatchEvent(new c(i,o)),i===`installed`?r.wn=self.setTimeout((function(){i===`installed`&&t.waiting===n&&r.dispatchEvent(new c(`waiting`,o))}),200):i===`activating`&&(clearTimeout(r.wn),a||r.en.resolve(n))},r.yn=function(e){var t=r.hn,n=t!==navigator.serviceWorker.controller;r.dispatchEvent(new c(`controlling`,{isExternal:n,originalEvent:e,sw:t,isUpdate:r.mn})),n||r.on.resolve(t)},r.gn=(i=function(e){var t=e.data,n=e.ports,i=e.source;return l(r.getSW(),(function(){r.an.has(i)&&r.dispatchEvent(new c(`message`,{data:t,originalEvent:e,ports:n,sw:i}))}))},function(){var e=[...arguments];try{return Promise.resolve(i.apply(this,e))}catch(e){return Promise.reject(e)}}),r.sn=e,r.nn=n,navigator.serviceWorker.addEventListener(`message`,r.gn),r}var a,u=t;(a=r).prototype=Object.create(u.prototype),a.prototype.constructor=a,i(a,u);var p=r.prototype;return p.register=function(e){var t=(e===void 0?{}:e).immediate,n=t!==void 0&&t;try{var r=this;return l(function(e,t){var n=e();return n&&n.then?n.then(t):t(n)}((function(){if(!n&&document.readyState!==`complete`)return f(new Promise((function(e){return window.addEventListener(`load`,e)})))}),(function(){return r.mn=!!navigator.serviceWorker.controller,r.dn=r.pn(),l(r.bn(),(function(e){r.fn=e,r.dn&&(r.hn=r.dn,r.en.resolve(r.dn),r.on.resolve(r.dn),r.dn.addEventListener(`statechange`,r.ln,{once:!0}));var t=r.fn.waiting;return t&&s(t.scriptURL,r.sn.toString())&&(r.hn=t,Promise.resolve().then((function(){r.dispatchEvent(new c(`waiting`,{sw:t,wasWaitingBeforeRegister:!0}))})).then((function(){}))),r.hn&&(r.rn.resolve(r.hn),r.an.add(r.hn)),r.fn.addEventListener(`updatefound`,r.cn),navigator.serviceWorker.addEventListener(`controllerchange`,r.yn),r.fn}))})))}catch(e){return Promise.reject(e)}},p.update=function(){try{return this.fn?l(f(this.fn.update())):l()}catch(e){return Promise.reject(e)}},p.getSW=function(){return this.hn===void 0?this.rn.promise:Promise.resolve(this.hn)},p.messageSW=function(t){try{return l(this.getSW(),(function(n){return e(n,t)}))}catch(e){return Promise.reject(e)}},p.messageSkipWaiting=function(){this.fn&&this.fn.waiting&&e(this.fn.waiting,d)},p.pn=function(){var e=navigator.serviceWorker.controller;return e&&s(e.scriptURL,this.sn.toString())?e:void 0},p.bn=function(){try{var e=this;return l(function(e,t){try{var n=e()}catch(e){return t(e)}return n&&n.then?n.then(void 0,t):n}((function(){return l(navigator.serviceWorker.register(e.sn,e.nn),(function(t){return e.un=performance.now(),t}))}),(function(e){throw e})))}catch(e){return Promise.reject(e)}},n(r,[{key:`active`,get:function(){return this.en.promise}},{key:`controlling`,get:function(){return this.on.promise}}])}(function(){function e(){this.Pn=new Map}var t=e.prototype;return t.addEventListener=function(e,t){this.jn(e).add(t)},t.removeEventListener=function(e,t){this.jn(e).delete(t)},t.dispatchEvent=function(e){e.target=this;for(var t,n=r(this.jn(e.type));!(t=n()).done;)(0,t.value)(e)},t.jn=function(e){return this.Pn.has(e)||this.Pn.set(e,new Set),this.Pn.get(e)},e}());export{p as Workbox};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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