feat: init hectic-lab merge

This commit is contained in:
2026-02-27 19:57:44 +00:00
parent 4e2264dc03
commit cfcd6647ae
13 changed files with 948 additions and 10 deletions

View File

@@ -0,0 +1,108 @@
{
inputs ? null,
flake ? null,
self ? null,
...
}:
{
config ? null,
pkgs ? null,
lib ? null,
modulesPath ? null,
...
}:
with builtins;
with lib;
# with inputs.dream.lib;
let
in {
# networking.nat = {
# enable = true;
# internalInterfaces = [ "ve-+" ];
# externalInterface = "lo";
# # Lazy IPv6 connectivity for the container
# enableIPv6 = true;
# };
# containers.webserver = {
# autoStart = true;
# privateNetwork = true;
# hostAddress = "192.168.115.10";
# localAddress = "192.168.115.11";
# hostAddress6 = "fc00::1";
# localAddress6 = "fc00::2";
# config = import "${inputs.quteproxy}/nixos/system/quteproxy-staging/quteproxy-staging.nix" {
# self = inputs.quteproxy;
# inputs = inputs.quteproxy.inputs;
# flake = inputs.quteproxy;
# };
# };
# environment.etc.nixos.source = self;
# boot.kernelModules = [ "kvm" ];
# microvm.autostart = [
# "myvm1"
# ];
# microvm.vms = {
# myvm1 = {
# flake = self;
# updateFlake = "git+file:///etc/nixos";
# };
# };
# microvm = {
# mem = 1024*3;
# vcpu = 4;
# storeOnDisk = false;
# shares = [
# {
# proto = "9p";
# # securityModel = "mapped";
# tag = "ro-store";
# source = "/nix/store";
# mountPoint = "/nix/.ro-store";
# }
# {
# proto = "9p";
# securityModel = "mapped";
# tag = "fsRoot";
# source = "/media/pool/mythos/vm/work/vproxy/pr";
# mountPoint = "/home/devbox-user/pr";
# }
# ];
# interfaces = [
# {
# type = "user";
#
# # interface name on the host
# id = "vm-seht";
#
# # Ethernet address of the MicroVM's interface, not the host's
# # Locally administered have one of 2/6/A/E in the second nibble.
# mac = "02:00:00:00:00:01";
# }
# ];
# forwardPorts = [
# { from = "host"; host.port = 40500; guest.port = 22; }
# ];
#
# writableStoreOverlay = "/nix/.rw-store";
# volumes = [
# {
# autoCreate = true;
# size = 1024*32;
#
# image = "/media/pool/mythos/vm/work/vproxy/nix-store-overlay.img";
# mountPoint = config.microvm.writableStoreOverlay;
# }
# {
# autoCreate = true;
# size = 1024*32;
#
# image = "/media/pool/mythos/vm/work/vproxy/root.img";
# mountPoint = "/";
# }
# ];
# };
}

View File

@@ -0,0 +1,20 @@
{
flake,
self,
inputs,
system ? "x86_64-linux",
...
}: let
# Use folder name as name of this system
name = builtins.baseNameOf ./.;
in self.lib.nixpkgs-lib.nixosSystem {
pkgs = import inputs.nixpkgs {
inherit system;
overlays = [ self.overlays.default ];
};
modules = [
{ networking.hostName = name; }
(import ./${name}.nix { inherit flake self inputs; })
];
}

View File

@@ -0,0 +1,238 @@
{
inputs,
flake,
self,
...
}:
{
config,
pkgs,
lib,
modulesPath,
...
}:
with builtins;
with lib;
let
domain = "hectic-lab.com";
sslOpts = {
sslCertificate = config.sops.secrets."ssl/porkbun/${domain}/domain.cert.pem".path;
sslCertificateKey = config.sops.secrets."ssl/porkbun/${domain}/private.key.pem".path;
};
in {
imports = [
self.nixosModules.hectic
inputs.sops-nix.nixosModules.sops
self.nixosModules."shadowsocks-rust" # NOTE(nrv): impl
self.nixosModules."shadowsocks" # NOTE(nrv): usage/instance
(import ./containers.nix { inherit flake self inputs; })
(import (./. + "/sentinèlla.nix") { inherit flake self inputs domain sslOpts; })
];
hectic = {
archetype.dev.enable = true;
hardware.hetzner-cloud = {
enable = true;
networkMatchConfigName = "enp1s0";
ipv4 = "188.245.181.123";
ipv6 = "2a01:4f8:c2c:d54a";
};
};
programs.zsh.enable = true;
programs.zsh.interactiveShellInit = ''
setopt vi
'';
environment.systemPackages = with pkgs; [
git
rsync
python311
kitty
];
# Secrets config
sops = {
gnupg.sshKeyPaths = [ ];
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
defaultSopsFile = "${flake}/sus/hectic-lab.yaml";
};
users.users.root.openssh.authorizedKeys.keys = [
# yukkop
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMuP5NSfEQmO6m77xBWZvZ3hk7cw1q2k2vbsFd37rybU u0_a327@localhost"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBLxMo5icX2Xyng7mcWGnIi+c4ZbVygjPhuU8noCkfZ"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxgLlX/15Fk7PgIc9FSrA7oRtA8qK4GXfOhj7ZlNUaJ nix-on-droid@localhost"
# snuff
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFouceNUxI3bGC24/hfA8J3VuBpvTcZh3KhixgrMiLte"
# nrv
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/EhBI6sJb2yHbTkqhZiCzUrsLE6t+CZe7RhS22z7w5 nrv@adamantia"
# github workflow
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKPEUArBxu7NUULT7Pi8ArtVxY1uVbIBSaeRKtqz1sz1"
];
users.users.ds4d = { # NOTE(nrv): artishoque
isNormalUser = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINcjBc57N6MxtMYAHEB/nwZ+OGsG3P1KWO1ZXvzQyhKn ds4d@ds4d"
];
};
users.users.sshuttle = {
isNormalUser = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKd4iU2E5fiwPwBbeo1ZPo0YBFEj9qBPew/KitaO+OHU"
];
};
sops.secrets."mailserver/security/hashedPassword" = {};
sops.secrets."mailserver/yukkop/hashedPassword" = {};
sops.secrets."mailserver/snuff/hashedPassword" = {};
sops.secrets."mailserver/antoshka/hashedPassword" = {};
# services.mailserver = {
# enable = false;
# domain = domain;
# loginAccounts = {
# "security" = {
# hashedPasswordFile = config.sops.secrets."mailserver/security/hashedPassword".path;
# };
# "yukkop" = {
# hashedPasswordFile = config.sops.secrets."mailserver/yukkop/hashedPassword".path;
# };
# "snuff" = {
# hashedPasswordFile = config.sops.secrets."mailserver/snuff/hashedPassword".path;
# };
# "antoshka" = {
# hashedPasswordFile = config.sops.secrets."mailserver/antoshka/hashedPassword".path;
# };
# };
# };
services.redis.servers."vproxy-bot-test-state" = {
enable = true;
port = 6379;
};
services.mysql = {
enable = true;
package = pkgs.mariadb;
};
networking.firewall = {
allowedTCPPorts = [
443
3306 # mysql
25565
55228 # ss-bfs
];
allowedUDPPorts = [
51820 # wg-bfs
55228 # ss-bfs
];
};
virtualisation.docker.enable = true;
systemd.tmpfiles.rules = [
"d /var/www/store 0755 nginx nginx -"
];
sops.secrets."ssl/porkbun/${domain}/domain.cert.pem" = { group = "nginx"; mode = "0440"; };
sops.secrets."ssl/porkbun/${domain}/private.key.pem" = { group = "nginx"; mode = "0440"; };
sops.secrets."ssl/porkbun/${domain}/public.key.pem" = { group = "nginx"; mode = "0440"; };
services.nginx = {
enable = true;
virtualHosts.${domain} = sslOpts // {
forceSSL = true;
locations."/" = {
extraConfig = ''
root ${"${flake}/nixos/system/hectic-lab/static"};
try_files $uri $uri/ /index.html;
'';
};
};
virtualHosts."umbriel.${domain}" = sslOpts // {
forceSSL = true;
locations."/" = {
extraConfig = ''
root ${"${flake}/nixos/system/hectic-lab/static"};
try_files $uri $uri/ /index.html;
'';
};
};
virtualHosts."store.${domain}" = sslOpts // {
forceSSL = true;
root = "/var/www/store";
locations."/" = {
extraConfig = ''
autoindex on;
'';
};
};
virtualHosts."snuff.${domain}" = sslOpts // {
forceSSL = true;
locations."/" = {
extraConfig = ''
proxy_pass http://188.32.215.29:3993/;
proxy_redirect off;
'';
};
};
virtualHosts."nrv.${domain}" = sslOpts // {
forceSSL = true;
locations."/" = {
extraConfig = ''
proxy_pass http://127.0.0.1:22842/;
proxy_redirect off;
'';
};
};
virtualHosts."yukkop.${domain}" = sslOpts // {
forceSSL = true;
locations."/" = {
extraConfig = ''
proxy_pass http://127.0.0.1:9855/;
proxy_redirect off;
'';
};
};
};
# === WireGuard (disabled) ===
sops.secrets."wg-bfs/private-key" = {};
# networking.wireguard.interfaces = let
# subnet = "10.13.37";
# externalInterface = "eth0";
# in {
# wg-bfs = {
# ips = [ "${subnet}.1/24" ];
# listenPort = 51820;
# postSetup = ''
# ${pkgs.iptables}/bin/iptables -t 'nat' -A 'POSTROUTING' -s '${subnet}.0/24' -o '${externalInterface}' -j 'MASQUERADE'
# '';
# postShutdown = ''
# ${pkgs.iptables}/bin/iptables -t 'nat' -D 'POSTROUTING' -s '${subnet}.0/24' -o '${externalInterface}' -j 'MASQUERADE'
# '';
# privateKeyFile = config.sops.secrets."wg-bfs/private-key".path;
# generatePrivateKeyFile = false;
# peers = with lib; with builtins; let
# pubkeys = [
# "3dVzf1jxnVVTkLAyxedW+kRQBexZDzYDwpaLIcTrLjc=" # nrv (host: 2)
# "Kk2d0ncj24rO0qbuKh4V4t1OLnmVYbeaYvuEnL2OPFM=" # lysmi (host: 3)
# "BkM/NEDbR/XQ6WYQ0Yt+nJrc2HFCVsoW4QxBmkqxHn8=" # yukkop (host: 4)
# ];
# hosts = lists.range 2 254;
# zipped = zipLists pubkeys hosts;
# in flip map zipped ({ fst, snd }: {
# publicKey = "${fst}";
# allowedIPs = [ "${subnet}.${toString snd}/32" ];
# });
# };
# };
}

View File

@@ -0,0 +1,26 @@
{
inputs,
flake,
self,
domain,
sslOpts,
...
}: let
port = 5869;
in {
hectic = {
services."sentinèlla".probe = {
enable = true;
inherit port;
};
};
services.nginx = {
virtualHosts."probe.${domain}" = sslOpts // {
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${builtins.toString port}";
};
};
};
}

View File

@@ -0,0 +1,204 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Counter App</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.tab-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-button {
padding: 10px 20px;
border: none;
background: #e5e7eb;
cursor: pointer;
border-radius: 4px;
}
.tab-button.active {
background: #3b82f6;
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.counter-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
margin-bottom: 20px;
}
.counter-button {
padding: 15px;
border: 1px solid #e5e7eb;
background: white;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
}
.counter-button:hover {
background: #f3f4f6;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.control-button {
padding: 10px 20px;
background: #3b82f6;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.control-button:hover {
background: #2563eb;
}
</style>
</head>
<body>
<div class="controls">
<button class="control-button" onclick="resetCounters()">Reset</button>
<button class="control-button" onclick="saveCounters()">Save</button>
<input type="file" id="loadFile" style="display: none" onchange="loadCounters(event)">
<button class="control-button" onclick="document.getElementById('loadFile').click()">Load</button>
</div>
<div class="tab-buttons">
<button class="tab-button active" onclick="showTab('counters')">Counters</button>
<button class="tab-button" onclick="showTab('chart')">Chart</button>
</div>
<div id="counters" class="tab-content active">
<div class="counter-grid" id="counterGrid"></div>
</div>
<div id="chart" class="tab-content">
<canvas id="counterChart"></canvas>
</div>
<script>
let counters = Array(20).fill(0);
let chart = null;
function initializeCounters() {
const grid = document.getElementById('counterGrid');
grid.innerHTML = '';
for (let i = 0; i < 20; i++) {
const button = document.createElement('button');
button.className = 'counter-button';
button.innerHTML = `Button ${i + 1}<br>Count: ${counters[i]}`;
button.onclick = () => incrementCounter(i);
grid.appendChild(button);
}
updateChart();
}
function incrementCounter(index) {
counters[index]++;
updateCounterDisplay();
updateChart();
}
function updateCounterDisplay() {
const buttons = document.querySelectorAll('.counter-button');
buttons.forEach((button, i) => {
button.innerHTML = `Button ${i + 1}<br>Count: ${counters[i]}`;
});
}
function updateChart() {
if (chart) {
chart.destroy();
}
const ctx = document.getElementById('counterChart').getContext('2d');
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: Array.from({length: 20}, (_, i) => `Button ${i + 1}`),
datasets: [{
label: 'Click Count',
data: counters,
backgroundColor: '#3b82f6'
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
}
function showTab(tabId) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.remove('active');
});
document.getElementById(tabId).classList.add('active');
document.querySelector(`[onclick="showTab('${tabId}')"]`).classList.add('active');
if (tabId === 'chart') {
updateChart();
}
}
function resetCounters() {
counters = Array(20).fill(0);
updateCounterDisplay();
updateChart();
}
function saveCounters() {
const data = JSON.stringify(counters);
const blob = new Blob([data], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'counters.json';
a.click();
URL.revokeObjectURL(url);
}
function loadCounters(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
try {
counters = JSON.parse(e.target.result);
updateCounterDisplay();
updateChart();
} catch (error) {
alert('Invalid file format');
}
};
reader.readAsText(file);
}
}
initializeCounters();
</script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>tg test</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://telegram.org/js/telegram-web-app.js?56"></script>
<script src="test.js"></script>
</head>
<body>
<div class="container">
<div class="content">
<p>TEST (again)</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,41 @@
function webappInit() {
console.log("Init start");
window.Telegram.WebApp.BackButton.isVisible = true;
window.Telegram.WebApp.backgroundColor = "#E60C0C";
let initData = window.Telegram.WebApp.initData;
if (initData) {
console.log("InitData", initData);
validate(initData);
}
console.log("Init end");
}
function validate(initData) {
const urlencodedData = initData;
const decodedData = decodeURIComponent(urlencodedData);
fetch(
"http://localhost:52022/rpc/webapp_auth",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Profile": "qutegate",
},
body: JSON.stringify({ raw_init_data: btoa(decodedData) }),
}
)
}
function waitForWebApp() {
if (window.Telegram && window.Telegram.WebApp) {
console.log("Telegram WebApp is available");
webappInit();
} else {
console.log("Telegram WebApp is not available yet");
setTimeout(waitForWebApp, 100);
}
}
waitForWebApp();