Merge branch 'merge-hectic-lab'
This commit is contained in:
15
.sops.yaml
15
.sops.yaml
@@ -1,11 +1,15 @@
|
|||||||
keys:
|
keys:
|
||||||
- &snuff age1w4hw2ntxrtfqhht63s9lf7nhjxjmdcc927hndn5ygcqqj532qssq4m2m6p
|
- &snuff age1w4hw2ntxrtfqhht63s9lf7nhjxjmdcc927hndn5ygcqqj532qssq4m2m6p
|
||||||
- &yukkop age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h
|
- &yukkop age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h
|
||||||
|
- &yukkop-alt age1vv46vn4hsn2lg6jy834cpu40c3mvqklldcm3hjtynrhwtpmlpc8szruz4v
|
||||||
|
- &nrv age1x04u7ftjgx8de2gq596e7frauze764cmn7jjwqnx8szthvfft5qq0tezx6
|
||||||
- &bfs-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
- &bfs-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
- &bfs-pol-server age1fpytf05sg9n6ywpwkmn09lhpfvgtud9h75h76jhxha475zpnasqq952rpu
|
- &bfs-pol-server age1fpytf05sg9n6ywpwkmn09lhpfvgtud9h75h76jhxha475zpnasqq952rpu
|
||||||
- &bfs-new-server age17yx98qk9gzgcf2q6zhhp05p6mmtrkgz66dvyk9gqclypvlr8rersxjy5v7
|
- &bfs-new-server age17yx98qk9gzgcf2q6zhhp05p6mmtrkgz66dvyk9gqclypvlr8rersxjy5v7
|
||||||
- &neuro-server age1ak7heljpr0pjr4m0rcwxgn3sp0jjxw03lxyf33r8lcemqh2u2sgqx0aplq
|
- &neuro-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
- &games-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
- &games-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
|
- &hectic-lab-server age13h8twnwvgxn04l5ywtru89a6psw5d0uckr2eghxsjp88a5augvsstq5ard
|
||||||
|
- &umbriel-bfs age1jxntjca8q2vxvf2jaal4xyvm2ae6sh62fhv897694kuzawfrk5asj00zdt
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: sus/home.xray.yaml$
|
- path_regex: sus/home.xray.yaml$
|
||||||
@@ -33,3 +37,12 @@ creation_rules:
|
|||||||
- age:
|
- age:
|
||||||
- *yukkop
|
- *yukkop
|
||||||
- *games-server
|
- *games-server
|
||||||
|
|
||||||
|
- path_regex: sus/hectic-lab.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *nrv
|
||||||
|
- *yukkop
|
||||||
|
- *yukkop-alt
|
||||||
|
- *hectic-lab-server
|
||||||
|
- *umbriel-bfs
|
||||||
|
|||||||
89
flake.lock
generated
89
flake.lock
generated
@@ -33,6 +33,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"blobs": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1604995301,
|
||||||
|
"narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
|
||||||
|
"owner": "simple-nixos-mailserver",
|
||||||
|
"repo": "blobs",
|
||||||
|
"rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
|
||||||
|
"type": "gitlab"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "simple-nixos-mailserver",
|
||||||
|
"repo": "blobs",
|
||||||
|
"type": "gitlab"
|
||||||
|
}
|
||||||
|
},
|
||||||
"deploy-rs": {
|
"deploy-rs": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
@@ -239,6 +255,32 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"git-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": [
|
||||||
|
"nixos-mailserver",
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
|
"gitignore": "gitignore_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixos-mailserver",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775585728,
|
||||||
|
"narHash": "sha256-8Psjt+TWvE4thRKktJsXfR6PA/fWWsZ04DVaY6PUhr4=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "580633fa3fe5fc0379905986543fd7495481913d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gitignore": {
|
"gitignore": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -261,6 +303,28 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gitignore_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixos-mailserver",
|
||||||
|
"git-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1709087332,
|
||||||
|
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -693,6 +757,30 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixos-mailserver": {
|
||||||
|
"inputs": {
|
||||||
|
"blobs": "blobs",
|
||||||
|
"flake-compat": "flake-compat_4",
|
||||||
|
"git-hooks": "git-hooks",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1764075412,
|
||||||
|
"narHash": "sha256-d1+H7z21NsXgk9vL/9LAPUuiKrq9iqFxxqAWNNk1gY0=",
|
||||||
|
"owner": "simple-nixos-mailserver",
|
||||||
|
"repo": "nixos-mailserver",
|
||||||
|
"rev": "8d35f004eeb47cfcfa5c4e1c8765f5c1bf64b9a0",
|
||||||
|
"type": "gitlab"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "simple-nixos-mailserver",
|
||||||
|
"ref": "snm-25.11",
|
||||||
|
"repo": "nixos-mailserver",
|
||||||
|
"type": "gitlab"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixos-stable": {
|
"nixos-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749086602,
|
"lastModified": 1749086602,
|
||||||
@@ -859,6 +947,7 @@
|
|||||||
"nixos-anywhere": "nixos-anywhere",
|
"nixos-anywhere": "nixos-anywhere",
|
||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixos-wsl": "nixos-wsl",
|
"nixos-wsl": "nixos-wsl",
|
||||||
|
"nixos-mailserver": "nixos-mailserver",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"nixpkgs-fixed": "nixpkgs-fixed",
|
"nixpkgs-fixed": "nixpkgs-fixed",
|
||||||
"nixvim": "nixvim",
|
"nixvim": "nixvim",
|
||||||
|
|||||||
@@ -47,6 +47,10 @@
|
|||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
nixos-mailserver = {
|
||||||
|
url = "gitlab:simple-nixos-mailserver/nixos-mailserver/snm-25.11";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
nix-minecraft.url = "github:Infinidoge/nix-minecraft";
|
nix-minecraft.url = "github:Infinidoge/nix-minecraft";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,6 +97,7 @@
|
|||||||
"neuro|x86_64-linux" = import ./nixos/system/neuro { inherit flake self inputs; system = "x86_64-linux"; };
|
"neuro|x86_64-linux" = import ./nixos/system/neuro { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
"games|x86_64-linux" = import ./nixos/system/games { inherit flake self inputs; system = "x86_64-linux"; };
|
"games|x86_64-linux" = import ./nixos/system/games { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
"wsl|x86_64-linux" = import ./nixos/system/wsl { inherit flake self inputs; system = "x86_64-linux"; };
|
"wsl|x86_64-linux" = import ./nixos/system/wsl { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
|
"hectic-lab|x86_64-linux" = import ./nixos/system/hectic-lab { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ let
|
|||||||
hectic.imports = attrValues (
|
hectic.imports = attrValues (
|
||||||
readModulesRecursive' ./hectic { inherit flake self inputs; }
|
readModulesRecursive' ./hectic { inherit flake self inputs; }
|
||||||
);
|
);
|
||||||
# Read generic modules seperately
|
# Read generic modules separately
|
||||||
generic = readModulesRecursive'
|
generic = readModulesRecursive'
|
||||||
./generic
|
./generic
|
||||||
{ inherit flake self inputs; };
|
{ inherit flake self inputs; };
|
||||||
|
|||||||
169
nixos/module/generic/shadowsocks-rust.nix
Normal file
169
nixos/module/generic/shadowsocks-rust.nix
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# INFO(nrv): This is standalone shadowsocks module. Instance-specific is at ./shadowsocks.nix
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.shadowsocks-rust;
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
server = cfg.localAddress;
|
||||||
|
server_port = cfg.port;
|
||||||
|
method = cfg.encryptionMethod;
|
||||||
|
mode = cfg.mode;
|
||||||
|
user = "nobody";
|
||||||
|
fast_open = cfg.fastOpen;
|
||||||
|
} // optionalAttrs (cfg.plugin != null) {
|
||||||
|
plugin = cfg.plugin;
|
||||||
|
plugin_opts = cfg.pluginOpts;
|
||||||
|
} // optionalAttrs (cfg.password != null) {
|
||||||
|
password = cfg.password;
|
||||||
|
} // cfg.extraConfig;
|
||||||
|
|
||||||
|
configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
###### interface
|
||||||
|
|
||||||
|
options = {
|
||||||
|
|
||||||
|
services.shadowsocks-rust = {
|
||||||
|
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Whether to run shadowsocks-rust shadowsocks server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
localAddress = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0.0.0.0";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Local addresses to which the server binds.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8388;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Port which the server uses.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
password = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Password for connecting clients.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Password file with a password for connecting clients.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
|
||||||
|
default = "tcp_and_udp";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Relay protocols.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
fastOpen = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
use TCP fast-open
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
encryptionMethod = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "chacha20-ietf-poly1305";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Encryption method. See <https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = literalExpression ''"''${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin"'';
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
SIP003 plugin for shadowsocks
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
pluginOpts = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
example = "server;host=example.com";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Options to pass to the plugin if one was specified
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
example = {
|
||||||
|
nameserver = "8.8.8.8";
|
||||||
|
};
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Additional configuration for shadowsocks that is not covered by the
|
||||||
|
provided options. The provided attrset will be serialized to JSON and
|
||||||
|
has to contain valid shadowsocks options. Unfortunately most
|
||||||
|
additional options are undocumented but it's easy to find out what is
|
||||||
|
available by looking into the source code of
|
||||||
|
<https://github.com/shadowsocks/shadowsocks-rust/blob/master/src/jconf.c>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
###### implementation
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
assertions = singleton
|
||||||
|
{ assertion = cfg.password == null || cfg.passwordFile == null;
|
||||||
|
message = "Cannot use both password and passwordFile for shadowsocks-rust";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.shadowsocks-rust = {
|
||||||
|
description = "shadowsocks-rust Daemon";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pkgs.shadowsocks-rust ]
|
||||||
|
++ optional (cfg.plugin != null) cfg.plugin
|
||||||
|
++ optional (cfg.passwordFile != null) pkgs.jq;
|
||||||
|
serviceConfig.PrivateTmp = true;
|
||||||
|
script = ''
|
||||||
|
${optionalString (cfg.passwordFile != null) ''
|
||||||
|
cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /run/shadowsocks.json
|
||||||
|
''}
|
||||||
|
exec ssserver --config ${if cfg.passwordFile != null then "/run/shadowsocks.json" else configFile}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
28
nixos/module/generic/shadowsocks.nix
Normal file
28
nixos/module/generic/shadowsocks.nix
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
sops.secrets."ss-bfs/password" = {};
|
||||||
|
services.shadowsocks-rust = {
|
||||||
|
enable = true;
|
||||||
|
plugin = "${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
|
||||||
|
# TODO: setup dnscrypt or a private DNS server for this
|
||||||
|
# extraConfig = {
|
||||||
|
# nameserver = "185.12.64.1"; # FIXME: this can vary across instances.
|
||||||
|
# };
|
||||||
|
port = 55228;
|
||||||
|
pluginOpts = "server";
|
||||||
|
# TODO: setup a TLS certs for this (look: (README.md) https://github.com/shadowsocks/v2ray-plugin/)
|
||||||
|
#pluginOpts = "server;tls;host=ss.bfs.band";
|
||||||
|
passwordFile = config.sops.secrets."ss-bfs/password".path;
|
||||||
|
mode = "tcp_and_udp"; # default
|
||||||
|
localAddress = "0.0.0.0";
|
||||||
|
fastOpen = true; # default
|
||||||
|
encryptionMethod = "chacha20-ietf-poly1305"; # default
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
inputs.disko.nixosModules.default
|
inputs.disko.nixosModules.default
|
||||||
|
inputs.nixos-mailserver.nixosModules.mailserver
|
||||||
];
|
];
|
||||||
|
|
||||||
options.hectic.archetype.base.enable = lib.mkEnableOption "Enable archetupe.dev";
|
options.hectic.archetype.base.enable = lib.mkEnableOption "Enable archetupe.dev";
|
||||||
|
|||||||
65
nixos/module/hectic/service/mailserver.nix
Normal file
65
nixos/module/hectic/service/mailserver.nix
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.services.mailserver;
|
||||||
|
transformLoginAccounts = domain: input:
|
||||||
|
builtins.listToAttrs (map (key: {
|
||||||
|
name = key + "@" + domain;
|
||||||
|
value = input.${key};
|
||||||
|
}) (builtins.attrNames input));
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
services.mailserver.enable = lib.mkEnableOption "Mail server";
|
||||||
|
|
||||||
|
services.mailserver.domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "The domain name of the mail server";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.mailserver.loginAccounts = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
hashedPassword = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
hashedPasswordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Full path to a file containing the hashed password suitable
|
||||||
|
for use with `chpasswd -e`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Login accounts for the mail server";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
mailserver = {
|
||||||
|
enable = true;
|
||||||
|
fqdn = "mail." + cfg.domain;
|
||||||
|
domains = [ cfg.domain ];
|
||||||
|
|
||||||
|
loginAccounts = transformLoginAccounts cfg.domain cfg.loginAccounts;
|
||||||
|
|
||||||
|
certificateScheme = "acme-nginx";
|
||||||
|
};
|
||||||
|
|
||||||
|
# NOTE(yukkop): avoid Gmail rejection due to missing IPv6 PTR records
|
||||||
|
services.postfix.settings.main.inet_protocols = lib.mkDefault "ipv4";
|
||||||
|
|
||||||
|
security.acme.acceptTerms = true;
|
||||||
|
security.acme.defaults.email = "security@" + cfg.domain;
|
||||||
|
};
|
||||||
|
}
|
||||||
108
nixos/system/hectic-lab/containers.nix
Normal file
108
nixos/system/hectic-lab/containers.nix
Normal 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 = "/";
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
}
|
||||||
20
nixos/system/hectic-lab/default.nix
Normal file
20
nixos/system/hectic-lab/default.nix
Normal 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; })
|
||||||
|
];
|
||||||
|
}
|
||||||
257
nixos/system/hectic-lab/hectic-lab.nix
Normal file
257
nixos/system/hectic-lab/hectic-lab.nix
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
{
|
||||||
|
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 = "128.140.75.58";
|
||||||
|
ipv6 = "2a01:4f8:c2c:d54a";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# NOTE(yukkop): disk was provisioned by Hetzner rescue image, disko was never
|
||||||
|
# run, so partition labels don't exist. Override fileSystems with actual UUIDs.
|
||||||
|
fileSystems."/" = lib.mkForce {
|
||||||
|
device = "/dev/disk/by-uuid/48ba7286-d019-4cdc-9784-459767979b07";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" = lib.mkForce {
|
||||||
|
device = "/dev/disk/by-uuid/71F2-4E98";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [ "umask=0077" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
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" = {};
|
||||||
|
sops.secrets."mailserver/founders/hashedPassword" = {};
|
||||||
|
|
||||||
|
services.mailserver = {
|
||||||
|
enable = true;
|
||||||
|
domain = domain;
|
||||||
|
loginAccounts = {
|
||||||
|
"security" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/security/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"founders" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/founders/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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mailserver.stateVersion = 3;
|
||||||
|
|
||||||
|
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" ];
|
||||||
|
# });
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
}
|
||||||
26
nixos/system/hectic-lab/sentinèlla.nix
Normal file
26
nixos/system/hectic-lab/sentinèlla.nix
Normal 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}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
204
nixos/system/hectic-lab/static/dice.html
Normal file
204
nixos/system/hectic-lab/static/dice.html
Normal 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>
|
||||||
16
nixos/system/hectic-lab/static/index.html
Normal file
16
nixos/system/hectic-lab/static/index.html
Normal 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>
|
||||||
41
nixos/system/hectic-lab/static/test.js
Normal file
41
nixos/system/hectic-lab/static/test.js
Normal 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();
|
||||||
76
sus/hectic-lab.yaml
Normal file
76
sus/hectic-lab.yaml
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user