nullforge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. nullforge/__init__.py +1 -0
  2. nullforge/foundry/README.md +0 -0
  3. nullforge/foundry/__init__.py +7 -0
  4. nullforge/foundry/full_cast.py +22 -0
  5. nullforge/inventories/README.md +0 -0
  6. nullforge/inventories/example.py +87 -0
  7. nullforge/models/__init__.py +1 -0
  8. nullforge/models/containers.py +60 -0
  9. nullforge/models/dns.py +222 -0
  10. nullforge/models/monitoring/__init__.py +29 -0
  11. nullforge/models/monitoring/base.py +19 -0
  12. nullforge/models/monitoring/nezha.py +13 -0
  13. nullforge/models/netsec.py +43 -0
  14. nullforge/models/profiles.py +33 -0
  15. nullforge/models/system.py +15 -0
  16. nullforge/models/users.py +9 -0
  17. nullforge/models/warp.py +65 -0
  18. nullforge/models/zerotrust.py +9 -0
  19. nullforge/molds/__init__.py +38 -0
  20. nullforge/molds/base_mold.py +59 -0
  21. nullforge/molds/containers.py +26 -0
  22. nullforge/molds/defaults.py +10 -0
  23. nullforge/molds/dns.py +70 -0
  24. nullforge/molds/features.py +75 -0
  25. nullforge/molds/haproxy.py +23 -0
  26. nullforge/molds/monitoring/__init__.py +45 -0
  27. nullforge/molds/monitoring/nezha.py +118 -0
  28. nullforge/molds/netsec.py +237 -0
  29. nullforge/molds/profiles.py +30 -0
  30. nullforge/molds/system.py +140 -0
  31. nullforge/molds/telemt.py +140 -0
  32. nullforge/molds/tor.py +28 -0
  33. nullforge/molds/user.py +77 -0
  34. nullforge/molds/utils.py +115 -0
  35. nullforge/molds/warp.py +54 -0
  36. nullforge/molds/xray.py +16 -0
  37. nullforge/molds/zerotrust.py +59 -0
  38. nullforge/runes/__init__.py +46 -0
  39. nullforge/runes/base.py +327 -0
  40. nullforge/runes/containers.py +212 -0
  41. nullforge/runes/dns.py +256 -0
  42. nullforge/runes/haproxy.py +135 -0
  43. nullforge/runes/monitoring.py +21 -0
  44. nullforge/runes/netsec.py +547 -0
  45. nullforge/runes/prepare.py +30 -0
  46. nullforge/runes/profiles.py +583 -0
  47. nullforge/runes/telemt.py +252 -0
  48. nullforge/runes/tor.py +57 -0
  49. nullforge/runes/users.py +159 -0
  50. nullforge/runes/warp.py +273 -0
  51. nullforge/runes/xray.py +81 -0
  52. nullforge/runes/zerotrust.py +91 -0
  53. nullforge/smithy/__init__.py +1 -0
  54. nullforge/smithy/admin.py +40 -0
  55. nullforge/smithy/arch.py +36 -0
  56. nullforge/smithy/blocky.py +37 -0
  57. nullforge/smithy/cloudflare.py +32 -0
  58. nullforge/smithy/github.py +256 -0
  59. nullforge/smithy/http.py +85 -0
  60. nullforge/smithy/install.py +149 -0
  61. nullforge/smithy/monitoring/__init__.py +1 -0
  62. nullforge/smithy/monitoring/nezha/__init__.py +0 -0
  63. nullforge/smithy/monitoring/nezha/agent.py +54 -0
  64. nullforge/smithy/monitoring/nezha/dashboard.py +137 -0
  65. nullforge/smithy/monitoring/nezha/deploy.py +81 -0
  66. nullforge/smithy/network.py +95 -0
  67. nullforge/smithy/packages.py +176 -0
  68. nullforge/smithy/service.py +37 -0
  69. nullforge/smithy/sni.py +358 -0
  70. nullforge/smithy/swap.py +132 -0
  71. nullforge/smithy/system.py +86 -0
  72. nullforge/smithy/versions.py +282 -0
  73. nullforge/templates/__init__.py +32 -0
  74. nullforge/templates/cloudflared/tunnel.yml.j2 +6 -0
  75. nullforge/templates/dns/blocky.yaml.j2 +11 -0
  76. nullforge/templates/dns/dns.yaml.j2 +6 -0
  77. nullforge/templates/dns/resolv.conf.j2 +1 -0
  78. nullforge/templates/dns/resolved.conf.j2 +13 -0
  79. nullforge/templates/etc/default/zramswap.j2 +13 -0
  80. nullforge/templates/nvim/nvim_patch.lua.j2 +5 -0
  81. nullforge/templates/profiles/direnv.toml +2 -0
  82. nullforge/templates/profiles/starship.toml +186 -0
  83. nullforge/templates/profiles/tmux.conf +32 -0
  84. nullforge/templates/profiles/zshrc.j2 +46 -0
  85. nullforge/templates/scripts/telemt-synfix.sh +89 -0
  86. nullforge/templates/scripts/teleproxy-warp.sh +127 -0
  87. nullforge/templates/scripts/warp-v6-policy.sh +118 -0
  88. nullforge/templates/scripts/zt-tunnel-warp.sh +28 -0
  89. nullforge/templates/systemd/blocky.service.j2 +19 -0
  90. nullforge/templates/systemd/cloudflare-tunnel.service.j2 +27 -0
  91. nullforge/templates/systemd/cloudflare-warp.service.j2 +21 -0
  92. nullforge/templates/systemd/dns-internal.service.j2 +15 -0
  93. nullforge/templates/systemd/telemt.service.j2 +36 -0
  94. nullforge/templates/telemt/telemt.toml.j2 +49 -0
  95. nullforge/templates/tor/torrc.j2 +5 -0
  96. nullforge-0.1.0.dist-info/METADATA +39 -0
  97. nullforge-0.1.0.dist-info/RECORD +99 -0
  98. nullforge-0.1.0.dist-info/WHEEL +4 -0
  99. nullforge-0.1.0.dist-info/licenses/LICENSE +7 -0
nullforge/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0" # x-release-please-version
File without changes
@@ -0,0 +1,7 @@
1
+ """Foundry deploy entry points for Pyinfra."""
2
+
3
+ from pathlib import Path
4
+ from typing import Final
5
+
6
+
7
+ FOUNDRY_DIR: Final[Path] = Path(__file__).parent
@@ -0,0 +1,22 @@
1
+ from pyinfra import local
2
+ from pyinfra.context import host
3
+
4
+ from nullforge.molds.features import iter_runes
5
+ from nullforge.molds.utils import ensure_features, ensure_system
6
+ from nullforge.runes import rune_path
7
+
8
+
9
+ def cast_full() -> None:
10
+ host.data.features = ensure_features(getattr(host.data, "features", None))
11
+ host.data.system = ensure_system(getattr(host.data, "system", None))
12
+
13
+ local.include(str(rune_path("prepare")))
14
+
15
+ local.include(str(rune_path("base")))
16
+
17
+ for name, active in host.loop(iter_runes(host.data.features)):
18
+ if active:
19
+ local.include(str(rune_path(name)))
20
+
21
+
22
+ cast_full()
File without changes
@@ -0,0 +1,87 @@
1
+ from nullforge.models.dns import DnsMode
2
+ from nullforge.models.users import Shell
3
+ from nullforge.molds import DnsMold, MonitoringMold, TelemtMold, UserMold, WarpMold
4
+ from nullforge.molds.defaults import BASE_FEATURES, BASE_SYSTEM
5
+ from nullforge.molds.monitoring import NezhaBackend
6
+ from nullforge.molds.utils import merge_features, merge_system
7
+
8
+
9
+ users = UserMold(
10
+ manage=True,
11
+ name="example",
12
+ shell=Shell.ZSH,
13
+ )
14
+ """User configuration preset
15
+ with user management enabled and the user "example".
16
+ with shell set to ZSH (default behavior).
17
+ """
18
+
19
+ warp = WarpMold(
20
+ install=True,
21
+ iface="warp-example",
22
+ )
23
+ """WARP configuration preset
24
+ setup Cloudflare WARP
25
+ with default MASQUE engine and interface "warp-example".
26
+ """
27
+
28
+ dns = DnsMold(
29
+ mode=DnsMode.BLOCKY,
30
+ )
31
+ """DNS configuration preset
32
+ with DNS over HTTPS Blocky mode.
33
+ """
34
+
35
+ monitoring = MonitoringMold(
36
+ install=True,
37
+ backend=NezhaBackend(
38
+ server="agent.status.example.com:443",
39
+ client_secret="REPLACE_WITH_NZ_CLIENT_SECRET", # noqa: S106 - example placeholder
40
+ dashboard_url="https://dash.example.com",
41
+ api_token="REPLACE_WITH_DASHBOARD_PAT", # noqa: S106 - example placeholder
42
+ ),
43
+ )
44
+ """Monitoring configuration preset
45
+ installing Nezha agent and renaming dashboard entry to host hostname.
46
+ The dashboard_url/api_token are used on control node for rename API call.
47
+ """
48
+
49
+ telemt = TelemtMold(
50
+ install=True,
51
+ tls_domain="example.com",
52
+ users={
53
+ "example": "bf777cca8384a074a671460d51e4e31f",
54
+ },
55
+ route_via_warp=True,
56
+ synfix=True,
57
+ )
58
+ """Telemt MTProto proxy preset with Fake-TLS masking.
59
+ Telegram-bound egress is routed through WARP (enabled above) via per-uid policy routing,
60
+ and the MEKO SYN rate-limiting fix is applied.
61
+ """
62
+
63
+ overrides = (
64
+ users,
65
+ warp,
66
+ dns,
67
+ monitoring,
68
+ telemt,
69
+ )
70
+ """Wrappers for the features to be merged with the base features."""
71
+
72
+ hosts = [
73
+ (
74
+ "203.0.113.10",
75
+ {
76
+ "system": merge_system(BASE_SYSTEM, {"hostname": "example-node1.local"}),
77
+ "features": merge_features(BASE_FEATURES, *overrides),
78
+ },
79
+ ),
80
+ (
81
+ "203.0.113.20",
82
+ {
83
+ "system": merge_system(BASE_SYSTEM, {"hostname": "example-node2.local"}),
84
+ "features": merge_features(BASE_FEATURES, *overrides),
85
+ },
86
+ ),
87
+ ]
@@ -0,0 +1 @@
1
+ """Internal domain types for NullForge - pure value types."""
@@ -0,0 +1,60 @@
1
+ """Containers configuration models."""
2
+
3
+ from enum import StrEnum
4
+ from typing import Annotated, Literal
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class ContainersBackendType(StrEnum):
10
+ DOCKER = "docker"
11
+ PODMAN = "podman"
12
+ CRIO = "crio"
13
+
14
+
15
+ class ContainersRuntimeType(StrEnum):
16
+ DEFAULT = "default"
17
+ CRUN = "crun"
18
+ GVISOR = "gvisor"
19
+
20
+
21
+ class _ContainersBackendBase(BaseModel):
22
+ """Base for a containers backend."""
23
+
24
+ type: ContainersBackendType = Field(description="The type of containers backend")
25
+ runtime: ContainersRuntimeType = Field(description="The type of containers runtime")
26
+
27
+
28
+ class DockerContainersBackend(_ContainersBackendBase):
29
+ type: Literal[ContainersBackendType.DOCKER] = ContainersBackendType.DOCKER
30
+ runtime: Literal[ContainersRuntimeType.GVISOR] = ContainersRuntimeType.GVISOR
31
+
32
+
33
+ class PodmanContainersBackend(_ContainersBackendBase):
34
+ type: Literal[ContainersBackendType.PODMAN] = ContainersBackendType.PODMAN
35
+ runtime: Literal[ContainersRuntimeType.CRUN] = ContainersRuntimeType.CRUN
36
+
37
+
38
+ class CrioContainersBackend(_ContainersBackendBase):
39
+ type: Literal[ContainersBackendType.CRIO] = ContainersBackendType.CRIO
40
+ runtime: Literal[ContainersRuntimeType.DEFAULT] = ContainersRuntimeType.DEFAULT
41
+
42
+
43
+ ContainersBackend = Annotated[
44
+ DockerContainersBackend | PodmanContainersBackend | CrioContainersBackend,
45
+ Field(discriminator="type"),
46
+ ]
47
+
48
+
49
+ def containers_backend_factory(type: ContainersBackendType) -> ContainersBackend:
50
+ """Factory function for containers backends."""
51
+
52
+ match type:
53
+ case ContainersBackendType.DOCKER:
54
+ return DockerContainersBackend()
55
+ case ContainersBackendType.PODMAN:
56
+ return PodmanContainersBackend()
57
+ case ContainersBackendType.CRIO:
58
+ return CrioContainersBackend()
59
+ case _:
60
+ raise ValueError(f"Unknown ContainersBackendType: {type}")
@@ -0,0 +1,222 @@
1
+ """DNS configuration models."""
2
+
3
+ from enum import StrEnum
4
+ from typing import Annotated, Literal
5
+
6
+ from pydantic import BaseModel, Field, IPvAnyAddress, field_validator
7
+
8
+
9
+ class DnsProtocol(StrEnum):
10
+ DOU = "dou"
11
+ DOT = "dot"
12
+ DOH = "doh"
13
+
14
+
15
+ class DnsMode(StrEnum):
16
+ DOU = "dou"
17
+ DOT_RESOLVED = "dot_resolved"
18
+ BLOCKY = "blocky"
19
+ NONE = "none"
20
+
21
+
22
+ class DnsProvider(StrEnum):
23
+ CLOUDFLARE = "cloudflare"
24
+ GOOGLE = "google"
25
+ QUAD9 = "quad9"
26
+
27
+
28
+ class _DnsServerBase(BaseModel):
29
+ """Base for a DNS server."""
30
+
31
+ protocol: DnsProtocol
32
+
33
+
34
+ class DnsServerDoU(_DnsServerBase):
35
+ protocol: Literal[DnsProtocol.DOU] = DnsProtocol.DOU
36
+ host: IPvAnyAddress | str = Field(description="Resolver hostname or IP")
37
+ port: int = Field(default=53, ge=1, le=65535, description="UDP port")
38
+
39
+
40
+ class DnsServerDoH(_DnsServerBase):
41
+ protocol: Literal[DnsProtocol.DOH] = DnsProtocol.DOH
42
+ url: str = Field(description="HTTPS endpoint for DNS over HTTPS (RFC 8484).")
43
+
44
+ @field_validator("url")
45
+ @classmethod
46
+ def _require_https(cls, v: str) -> str:
47
+ """Enforce HTTPS."""
48
+
49
+ if not v.startswith("https://"):
50
+ raise ValueError("DoH endpoint must use HTTPS")
51
+ return v
52
+
53
+
54
+ class DnsServerDoT(_DnsServerBase):
55
+ protocol: Literal[DnsProtocol.DOT] = DnsProtocol.DOT
56
+ host: IPvAnyAddress | str = Field(description="Resolver hostname or IP")
57
+ port: int = Field(default=853, ge=1, le=65535, description="TLS port")
58
+ sni: str | None = Field(default=None, description="Optional SNI/hostname for TLS verification")
59
+
60
+
61
+ DnsServer = Annotated[
62
+ DnsServerDoH | DnsServerDoT | DnsServerDoU,
63
+ Field(discriminator="protocol"),
64
+ ]
65
+
66
+
67
+ class DnsProviders:
68
+ """DNS providers."""
69
+
70
+ @staticmethod
71
+ def _cloudflare_doh(ipv6: bool) -> list[DnsServer]:
72
+ ups: list[DnsServer] = [
73
+ DnsServerDoH(url="https://1.1.1.1/dns-query"),
74
+ DnsServerDoH(url="https://1.0.0.1/dns-query"),
75
+ ]
76
+ if ipv6:
77
+ ups.extend(
78
+ [
79
+ DnsServerDoH(url="https://[2606:4700:4700::1111]/dns-query"),
80
+ DnsServerDoH(url="https://[2606:4700:4700::1001]/dns-query"),
81
+ ]
82
+ )
83
+ return ups
84
+
85
+ @staticmethod
86
+ def _cloudflare_dot(ipv6: bool) -> list[DnsServer]:
87
+ ups: list[DnsServer] = [
88
+ DnsServerDoT(host="1.1.1.1", sni="cloudflare-dns.com"),
89
+ DnsServerDoT(host="1.0.0.1", sni="cloudflare-dns.com"),
90
+ ]
91
+ if ipv6:
92
+ ups.extend(
93
+ [
94
+ DnsServerDoT(host="2606:4700:4700::1111", sni="cloudflare-dns.com"),
95
+ DnsServerDoT(host="2606:4700:4700::1001", sni="cloudflare-dns.com"),
96
+ ]
97
+ )
98
+ return ups
99
+
100
+ @staticmethod
101
+ def _google_doh(ipv6: bool) -> list[DnsServer]:
102
+ ups: list[DnsServer] = [
103
+ DnsServerDoH(url="https://8.8.8.8/dns-query"),
104
+ DnsServerDoH(url="https://8.8.4.4/dns-query"),
105
+ ]
106
+ if ipv6:
107
+ ups.extend(
108
+ [
109
+ DnsServerDoH(url="https://[2001:4860:4860::8888]/dns-query"),
110
+ DnsServerDoH(url="https://[2001:4860:4860::8844]/dns-query"),
111
+ ]
112
+ )
113
+ return ups
114
+
115
+ @staticmethod
116
+ def _google_dot(ipv6: bool) -> list[DnsServer]:
117
+ ups: list[DnsServer] = [
118
+ DnsServerDoT(host="8.8.8.8", sni="dns.google"),
119
+ DnsServerDoT(host="8.8.4.4", sni="dns.google"),
120
+ ]
121
+ if ipv6:
122
+ ups.extend(
123
+ [
124
+ DnsServerDoT(host="2001:4860:4860::8888", sni="dns.google"),
125
+ DnsServerDoT(host="2001:4860:4860::8844", sni="dns.google"),
126
+ ]
127
+ )
128
+ return ups
129
+
130
+ @staticmethod
131
+ def _quad9_doh(ipv6: bool, ecs: bool) -> list[DnsServer]:
132
+ if ecs:
133
+ primary_ipv4 = "https://9.9.9.12/dns-query"
134
+ secondary_ipv4 = "https://149.112.112.12/dns-query"
135
+ primary_ipv6 = "https://[2620:fe::12]/dns-query"
136
+ secondary_ipv6 = "https://[2620:fe::fe:12]/dns-query"
137
+ else:
138
+ primary_ipv4 = "https://9.9.9.10/dns-query"
139
+ secondary_ipv4 = "https://149.112.112.10/dns-query"
140
+ primary_ipv6 = "https://[2620:fe::10]/dns-query"
141
+ secondary_ipv6 = "https://[2620:fe::fe:10]/dns-query"
142
+
143
+ ups: list[DnsServer] = [
144
+ DnsServerDoH(url=primary_ipv4),
145
+ DnsServerDoH(url=secondary_ipv4),
146
+ ]
147
+ if ipv6:
148
+ ups.extend(
149
+ [
150
+ DnsServerDoH(url=primary_ipv6),
151
+ DnsServerDoH(url=secondary_ipv6),
152
+ ]
153
+ )
154
+ return ups
155
+
156
+ @staticmethod
157
+ def _quad9_dot(ipv6: bool, ecs: bool) -> list[DnsServer]:
158
+ if ecs:
159
+ sni = "dns11.quad9.net"
160
+ primary_ipv4 = "9.9.9.12"
161
+ secondary_ipv4 = "149.112.112.12"
162
+ primary_ipv6 = "2620:fe::12"
163
+ secondary_ipv6 = "2620:fe::fe:12"
164
+ else:
165
+ sni = "dns10.quad9.net"
166
+ primary_ipv4 = "9.9.9.10"
167
+ secondary_ipv4 = "149.112.112.10"
168
+ primary_ipv6 = "2620:fe::10"
169
+ secondary_ipv6 = "2620:fe::fe:10"
170
+
171
+ ups: list[DnsServer] = [
172
+ DnsServerDoT(host=primary_ipv4, sni=sni),
173
+ DnsServerDoT(host=secondary_ipv4, sni=sni),
174
+ ]
175
+ if ipv6:
176
+ ups.extend(
177
+ [
178
+ DnsServerDoT(host=primary_ipv6, sni=sni),
179
+ DnsServerDoT(host=secondary_ipv6, sni=sni),
180
+ ]
181
+ )
182
+ return ups
183
+
184
+ @staticmethod
185
+ def get_upstreams(
186
+ provider: DnsProvider,
187
+ protocol: DnsProtocol,
188
+ ipv6: bool,
189
+ ecs: bool = False,
190
+ ) -> list[DnsServer]:
191
+ """Get upstream DNS servers for a provider and protocol."""
192
+
193
+ if ecs and provider != DnsProvider.QUAD9:
194
+ raise ValueError(f"ECS is supported only with the Quad9 provider, not {provider}")
195
+
196
+ match provider:
197
+ case DnsProvider.CLOUDFLARE:
198
+ if protocol == DnsProtocol.DOH:
199
+ return DnsProviders._cloudflare_doh(ipv6)
200
+ elif protocol == DnsProtocol.DOT:
201
+ return DnsProviders._cloudflare_dot(ipv6)
202
+ else:
203
+ raise ValueError(f"Unsupported protocol {protocol} for Cloudflare")
204
+ case DnsProvider.GOOGLE:
205
+ if protocol == DnsProtocol.DOH:
206
+ return DnsProviders._google_doh(ipv6)
207
+ elif protocol == DnsProtocol.DOT:
208
+ return DnsProviders._google_dot(ipv6)
209
+ else:
210
+ raise ValueError(f"Unsupported protocol {protocol} for Google")
211
+ case DnsProvider.QUAD9:
212
+ if protocol == DnsProtocol.DOH:
213
+ return DnsProviders._quad9_doh(ipv6, ecs)
214
+ elif protocol == DnsProtocol.DOT:
215
+ return DnsProviders._quad9_dot(ipv6, ecs)
216
+ else:
217
+ raise ValueError(f"Unsupported protocol {protocol} for Quad9")
218
+ case _:
219
+ raise ValueError(f"Unknown provider: {provider}")
220
+
221
+
222
+ dns_providers = DnsProviders()
@@ -0,0 +1,29 @@
1
+ """Monitoring configuration models."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import Field
6
+
7
+ from .base import MonitoringBackendType, _MonitoringLayoutBase
8
+ from .nezha import NezhaLayout
9
+
10
+
11
+ MonitoringLayout = Annotated[NezhaLayout, Field(discriminator="type")]
12
+ """Union-of-one for now. When second monitoring backend is added, extend union."""
13
+
14
+
15
+ def monitoring_layout_factory(type: MonitoringBackendType) -> MonitoringLayout:
16
+ match type:
17
+ case MonitoringBackendType.NEZHA:
18
+ return NezhaLayout()
19
+ case _:
20
+ raise ValueError(f"Unknown MonitoringBackendType: {type}")
21
+
22
+
23
+ __all__ = [
24
+ "MonitoringBackendType",
25
+ "MonitoringLayout",
26
+ "NezhaLayout",
27
+ "_MonitoringLayoutBase",
28
+ "monitoring_layout_factory",
29
+ ]
@@ -0,0 +1,19 @@
1
+ """Shared monitoring backend value types."""
2
+
3
+ from enum import StrEnum
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class MonitoringBackendType(StrEnum):
9
+ NEZHA = "nezha"
10
+
11
+
12
+ class _MonitoringLayoutBase(BaseModel):
13
+ """Static on-disk/service layout for monitoring agent backend."""
14
+
15
+ type: MonitoringBackendType = Field(description="The monitoring backend type")
16
+ base_dir: str | None = Field(..., description="The base/installation directory")
17
+ binary_path: str | None = Field(..., description="The path to the binary")
18
+ config_path: str | None = Field(..., description="The path to the configuration file")
19
+ systemd_service_name: str | None = Field(..., description="The systemd service name")
@@ -0,0 +1,13 @@
1
+ """Nezha monitoring backend layout."""
2
+
3
+ from typing import Literal
4
+
5
+ from .base import MonitoringBackendType, _MonitoringLayoutBase
6
+
7
+
8
+ class NezhaLayout(_MonitoringLayoutBase):
9
+ type: Literal[MonitoringBackendType.NEZHA] = MonitoringBackendType.NEZHA
10
+ base_dir: Literal["/opt/nezha/agent"] = "/opt/nezha/agent"
11
+ binary_path: Literal["/opt/nezha/agent/nezha-agent"] = "/opt/nezha/agent/nezha-agent"
12
+ config_path: Literal["/opt/nezha/agent/config.yml"] = "/opt/nezha/agent/config.yml"
13
+ systemd_service_name: Literal["nezha-agent"] = "nezha-agent"
@@ -0,0 +1,43 @@
1
+ """Network security domain types."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class SshHostKeyType(StrEnum):
7
+ """Host key type sshd offers."""
8
+
9
+ ED25519 = "ed25519"
10
+ ECDSA = "ecdsa"
11
+ RSA = "rsa"
12
+
13
+
14
+ PQ_KEX_ALGORITHMS = (
15
+ "mlkem768x25519-sha256",
16
+ "sntrup761x25519-sha512@openssh.com",
17
+ )
18
+ """Post-quantum hybrid key exchange algorithms, most preferred first."""
19
+
20
+ CLASSICAL_KEX_ALGORITHMS = (
21
+ "curve25519-sha256",
22
+ "curve25519-sha256@libssh.org",
23
+ "diffie-hellman-group-exchange-sha256",
24
+ "diffie-hellman-group16-sha512",
25
+ "diffie-hellman-group18-sha512",
26
+ )
27
+ """Strong classical key exchange in default-preference order, offered after the PQ hybrids."""
28
+
29
+ WEAK_KEX_PATTERNS = "ecdh-sha2-*,diffie-hellman-group14-*,diffie-hellman-group-exchange-sha1"
30
+ """Key exchanges removed from default set."""
31
+
32
+ WEAK_MAC_PATTERNS = "hmac-sha1*,umac-64*,hmac-sha2-256,hmac-sha2-512,umac-128@openssh.com"
33
+ """MACs removed from default set."""
34
+
35
+ MIN_DH_MODULUS_SIZE = 3071
36
+ """Minimum size kept in /etc/ssh/moduli; the file's size column is bits-1, so 3071 keeps >=3072-bit groups."""
37
+
38
+ HOST_KEY_ALGORITHMS: dict[SshHostKeyType, tuple[str, ...]] = {
39
+ SshHostKeyType.ED25519: ("ssh-ed25519",),
40
+ SshHostKeyType.ECDSA: ("ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"),
41
+ SshHostKeyType.RSA: ("rsa-sha2-512", "rsa-sha2-256"),
42
+ }
43
+ """Signature algorithms sshd may use per host key type."""
@@ -0,0 +1,33 @@
1
+ """Profiles domain models."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class NerdFont(StrEnum):
7
+ """Nerd Font family, valued as its base asset name in a `ryanoasis/nerd-fonts` release.
8
+
9
+ A curated subset of the ~70 published families; add a member to offer another one.
10
+ """
11
+
12
+ CASCADIA_CODE = "CascadiaCode"
13
+ COMMIT_MONO = "CommitMono"
14
+ DEJAVU_SANS_MONO = "DejaVuSansMono"
15
+ FIRA_CODE = "FiraCode"
16
+ FIRA_MONO = "FiraMono"
17
+ GEIST_MONO = "GeistMono"
18
+ HACK = "Hack"
19
+ IBM_PLEX_MONO = "IBMPlexMono"
20
+ INCONSOLATA = "Inconsolata"
21
+ INTEL_ONE_MONO = "IntelOneMono"
22
+ IOSEVKA = "Iosevka"
23
+ JETBRAINS_MONO = "JetBrainsMono"
24
+ MESLO = "Meslo"
25
+ MONASPACE = "Monaspace"
26
+ MONONOKI = "Mononoki"
27
+ ROBOTO_MONO = "RobotoMono"
28
+ SOURCE_CODE_PRO = "SourceCodePro"
29
+ SPACE_MONO = "SpaceMono"
30
+ SYMBOLS_ONLY = "NerdFontsSymbolsOnly"
31
+ UBUNTU_MONO = "UbuntuMono"
32
+ VICTOR_MONO = "VictorMono"
33
+ ZED_MONO = "ZedMono"
@@ -0,0 +1,15 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class SwapType(StrEnum):
5
+ """Type of swap configuration."""
6
+
7
+ BASIC = "basic"
8
+ ZRAM = "zram"
9
+
10
+
11
+ class SwapAlgo(StrEnum):
12
+ """Algorithm for ZRAM compression."""
13
+
14
+ ZSTD = "zstd"
15
+ LZO = "lzo"
@@ -0,0 +1,9 @@
1
+ """User configuration models."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class Shell(StrEnum):
7
+ BASH = "/bin/bash"
8
+ ZSH = "/bin/zsh"
9
+ ZSH_USER = "/usr/bin/zsh"
@@ -0,0 +1,65 @@
1
+ """WARP configuration models."""
2
+
3
+ from enum import StrEnum
4
+ from typing import Annotated, Literal
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class WarpEngineType(StrEnum):
10
+ WIREGUARD = "wireguard"
11
+ MASQUE = "masque"
12
+
13
+
14
+ class _WarpEngineBase(BaseModel):
15
+ """Base for a WARP engine."""
16
+
17
+ type: WarpEngineType = Field(description="The type of WARP engine")
18
+ binary_path: str = Field(description="The path to the WARP binary")
19
+ config_dir: str = Field(description="The path to the WARP configuration directory")
20
+ systemd_service_name: str = Field(description="The name of the systemd service")
21
+ policy_script: str = Field(default="", description="The path to the WARP policy script")
22
+
23
+ @property
24
+ def config_path(self) -> str:
25
+ return f"{self.config_dir}/config.json"
26
+
27
+ @property
28
+ def account_path(self) -> str:
29
+ return f"{self.config_dir}/wgcf-account.toml"
30
+
31
+ @property
32
+ def profile_path(self) -> str:
33
+ return f"{self.config_dir}/warp.conf"
34
+
35
+
36
+ class MasqueWarpEngine(_WarpEngineBase):
37
+ type: Literal[WarpEngineType.MASQUE] = WarpEngineType.MASQUE
38
+ binary_path: Literal["/usr/local/bin/usque"] = "/usr/local/bin/usque"
39
+ config_dir: Literal["/etc/usque"] = "/etc/usque"
40
+ systemd_service_name: Literal["cloudflare-warp"] = "cloudflare-warp"
41
+ policy_script: Literal["/etc/usque/warp-v6-policy.sh"] = "/etc/usque/warp-v6-policy.sh"
42
+
43
+
44
+ class WireguardWarpEngine(_WarpEngineBase):
45
+ type: Literal[WarpEngineType.WIREGUARD] = WarpEngineType.WIREGUARD
46
+ binary_path: Literal["/usr/local/bin/wgcf"] = "/usr/local/bin/wgcf"
47
+ config_dir: Literal["/etc/wgcf"] = "/etc/wgcf"
48
+ systemd_service_name: Literal["wg-quick@warp"] = "wg-quick@warp"
49
+ policy_script: Literal[""] = ""
50
+
51
+
52
+ WarpEngine = Annotated[
53
+ MasqueWarpEngine | WireguardWarpEngine,
54
+ Field(discriminator="type"),
55
+ ]
56
+
57
+
58
+ def warp_engine_factory(type: WarpEngineType) -> WarpEngine:
59
+ match type:
60
+ case WarpEngineType.MASQUE:
61
+ return MasqueWarpEngine()
62
+ case WarpEngineType.WIREGUARD:
63
+ return WireguardWarpEngine()
64
+ case _:
65
+ raise ValueError(f"Unknown WarpEngineType: {type}")
@@ -0,0 +1,9 @@
1
+ """Zero Trust Tunnel configuration models."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class ZeroTrustTunnelProtocol(StrEnum):
7
+ HTTP2 = "http2"
8
+ QUIC = "quic"
9
+ AUTO = "auto"