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
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # MEKO-style SYN rate-limiting fix for the telemt MTProto port.
4
+ # https://github.com/Mekotofeuka/MTPROTO_FIX_By_MEKO
5
+ #
6
+ # Non-iOS clients are throttled per source IP (default 54 SYN/minute, burst 1);
7
+ # excess connection attempts get a TCP reset so dead sockets are reaped in
8
+ # minutes. Small low-TTL SYNs (the iOS fingerprint) bypass limit for fast reconnects.
9
+ # Under-limit and iOS traffic RETURN to the normal INPUT path, so this coexists
10
+ # with UFW/firewalld (the port must still be allowed there).
11
+ # Best-effort: failures are logged, never fatal to startup.
12
+ #
13
+
14
+ set -uo pipefail
15
+
16
+ cmd=${1:?up|down}
17
+ PORT=${2:-443}
18
+ RATE=${3:-54/minute}
19
+ BURST=${4:-1}
20
+ IOS_TTL=${5:-65}
21
+
22
+ CHAIN="MTPR_SYNFIX"
23
+ HL_NAME="mtpr${PORT}"
24
+
25
+ log() { logger -t telemt-synfix -- "$*"; }
26
+
27
+ up_family() {
28
+ # $1 = iptables|ip6tables ; remaining args = iOS-bypass match (may be empty)
29
+ local ipt=$1
30
+ shift
31
+
32
+ "$ipt" -N "$CHAIN" 2>/dev/null || "$ipt" -F "$CHAIN" || return 1
33
+
34
+ if [ "$#" -gt 0 ]; then
35
+ "$ipt" -A "$CHAIN" -p tcp --dport "$PORT" --syn "$@" -j RETURN || return 1
36
+ fi
37
+
38
+ "$ipt" -A "$CHAIN" -p tcp --dport "$PORT" --syn \
39
+ -m hashlimit \
40
+ --hashlimit-name "$HL_NAME" \
41
+ --hashlimit-mode srcip \
42
+ --hashlimit-upto "$RATE" \
43
+ --hashlimit-burst "$BURST" \
44
+ --hashlimit-htable-expire 60000 \
45
+ --hashlimit-htable-size 32768 \
46
+ -j RETURN || return 1
47
+
48
+ "$ipt" -A "$CHAIN" -p tcp --dport "$PORT" --syn -j REJECT --reject-with tcp-reset || return 1
49
+
50
+ # Ensure exactly one jump from INPUT, evaluated before other allow rules.
51
+ while "$ipt" -D INPUT -p tcp --dport "$PORT" --syn -j "$CHAIN" 2>/dev/null; do :; done
52
+ "$ipt" -I INPUT 1 -p tcp --dport "$PORT" --syn -j "$CHAIN" || return 1
53
+ }
54
+
55
+ down_family() {
56
+ local ipt=$1
57
+ while "$ipt" -D INPUT -p tcp --dport "$PORT" --syn -j "$CHAIN" 2>/dev/null; do :; done
58
+ "$ipt" -F "$CHAIN" 2>/dev/null || true
59
+ "$ipt" -X "$CHAIN" 2>/dev/null || true
60
+ }
61
+
62
+ case "$cmd" in
63
+ up)
64
+ if up_family iptables -m length --length 64 -m ttl --ttl-lt "$IOS_TTL"; then
65
+ log "up: ipv4 chain=$CHAIN port=$PORT rate=$RATE burst=$BURST ios_ttl<$IOS_TTL"
66
+ else
67
+ log "up: ipv4 synfix not applied (missing iptables modules?)"
68
+ fi
69
+
70
+ if command -v ip6tables >/dev/null 2>&1; then
71
+ if up_family ip6tables -m hl --hl-lt "$IOS_TTL"; then
72
+ log "up: ipv6 chain=$CHAIN port=$PORT rate=$RATE burst=$BURST"
73
+ else
74
+ log "up: ipv6 synfix not applied"
75
+ fi
76
+ fi
77
+ ;;
78
+ down)
79
+ down_family iptables
80
+ command -v ip6tables >/dev/null 2>&1 && down_family ip6tables
81
+ log "down: chain=$CHAIN port=$PORT cleaned"
82
+ ;;
83
+ *)
84
+ echo "usage: $0 {up|down} [port] [rate] [burst] [ios_ttl]" >&2
85
+ exit 1
86
+ ;;
87
+ esac
88
+
89
+ exit 0
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Per-uid policy routing that steers a service user's Telegram-bound traffic
4
+ # through the Cloudflare WARP interface, leaving all other egress untouched.
5
+ #
6
+ # Usage: teleproxy-warp.sh {up|down} [svc_user] [iface] [table] [tid] [prio]
7
+ #
8
+ # Routing is scoped to the service uid and to Telegram's published CIDRs, so the
9
+ # proxy reaches Telegram over WARP while clients still connect to it directly.
10
+ # IPv4 fails closed for the service user when WARP has no v4 address; IPv6 is
11
+ # optional and skipped entirely when absent.
12
+ #
13
+
14
+ set -euo pipefail
15
+
16
+ cmd=${1:?up|down}
17
+ SVC_USER=${2:-telemt} # daemon's systemd User=
18
+ IFACE=${3:-warp} # WARP interface (usque -n <iface>)
19
+ TABLE=${4:-tpwarp} # dedicated routing table, separate from WARP's own
20
+ TID=${5:-124}
21
+ PRIO=${6:-11900} # rule priority (below the WARP policy band, above main)
22
+ WAIT_SECS=15
23
+
24
+ CIDR_URLS=(
25
+ "https://core.telegram.org/resources/cidr.txt"
26
+ )
27
+
28
+ # Embedded fallback (core.telegram.org/resources/cidr.txt, verified 2026-05)
29
+ FB_V4="91.108.56.0/22 91.108.4.0/22 91.108.8.0/22 91.108.16.0/22 91.108.12.0/22 149.154.160.0/20 91.105.192.0/23 91.108.20.0/22 185.76.151.0/24"
30
+ FB_V6="2001:b28:f23d::/48 2001:b28:f23f::/48 2001:67c:4e8::/48 2001:b28:f23c::/48 2a0a:f280::/32"
31
+
32
+ V4=""
33
+ V6=""
34
+
35
+ log() { logger -t telemt-warp -- "$*"; }
36
+
37
+ ensure_table() {
38
+ grep -qE "^[[:space:]]*$TID[[:space:]]+$TABLE$" /etc/iproute2/rt_tables \
39
+ || echo "$TID $TABLE" >> /etc/iproute2/rt_tables
40
+ }
41
+
42
+ get_warp6() { ip -6 -o addr show dev "$IFACE" scope global 2>/dev/null | awk '{print $4}' | head -n1 | cut -d/ -f1; }
43
+ get_warp4() { ip -4 -o addr show dev "$IFACE" scope global 2>/dev/null | awk '{print $4}' | head -n1 | cut -d/ -f1; }
44
+
45
+ wait_for_warp6() {
46
+ local i=0 a
47
+ while [ $i -lt "$WAIT_SECS" ]; do
48
+ a=$(get_warp6 || true); [ -n "$a" ] && { echo "$a"; return 0; }
49
+ sleep 1; i=$((i+1))
50
+ done
51
+ return 1
52
+ }
53
+
54
+ fetch_cidrs() { # tries all CIDR_URLS; merges + deduplicates; falls back to embedded list
55
+ local raw url new_v4 new_v6
56
+ for url in "${CIDR_URLS[@]}"; do
57
+ raw=$(curl --interface "$IFACE" --connect-timeout 3 --max-time 8 -fsSL "$url" 2>/dev/null || true)
58
+ if [ -n "$raw" ]; then
59
+ new_v4=$(echo "$raw" | tr ' ' '\n' | grep -E '^[0-9]+\.' | tr '\n' ' ' || true)
60
+ new_v6=$(echo "$raw" | tr ' ' '\n' | grep -E ':' | tr '\n' ' ' || true)
61
+ V4=$(printf '%s %s' "$V4" "$new_v4" | tr ' ' '\n' | sort -u | awk 'NF' | tr '\n' ' ')
62
+ V6=$(printf '%s %s' "$V6" "$new_v6" | tr ' ' '\n' | sort -u | awk 'NF' | tr '\n' ' ')
63
+ log "fetched CIDRs from $url"
64
+ else
65
+ log "cidr fetch failed from $url (expected on a filtered uplink); skipping"
66
+ fi
67
+ done
68
+ [ -n "${V4:-}" ] || V4="$FB_V4"
69
+ [ -n "${V6:-}" ] || V6="$FB_V6"
70
+ }
71
+
72
+ del_rules() {
73
+ while ip rule del pref "$PRIO" 2>/dev/null; do :; done
74
+ while ip -6 rule del pref "$PRIO" 2>/dev/null; do :; done
75
+ }
76
+
77
+ case "$cmd" in
78
+ up)
79
+ UID_N=$(id -u "$SVC_USER" 2>/dev/null) && [ -n "$UID_N" ] || { log "user $SVC_USER not found"; exit 1; }
80
+ ensure_table
81
+
82
+ # IPv6 is optional: non-fatal if absent; routes/rules skipped entirely when missing.
83
+ WARP6=$(wait_for_warp6 || true)
84
+ [ -n "$WARP6" ] || log "no IPv6 on $IFACE after ${WAIT_SECS}s; IPv6 routing skipped"
85
+
86
+ del_rules
87
+ ip -6 route flush table "$TABLE" 2>/dev/null || true
88
+ ip -4 route flush table "$TABLE" 2>/dev/null || true
89
+
90
+ if [ -n "$WARP6" ]; then
91
+ ip -6 route replace default dev "$IFACE" src "$WARP6" metric 100 table "$TABLE"
92
+ ip -6 route replace unreachable default metric 1024 table "$TABLE"
93
+ fi
94
+
95
+ WARP4=$(get_warp4 || true)
96
+ if [ -n "$WARP4" ]; then
97
+ ip -4 route replace default dev "$IFACE" src "$WARP4" metric 100 table "$TABLE"
98
+ log "IPv4 egress via WARP src=$WARP4"
99
+ else
100
+ log "no IPv4 on $IFACE; IPv4-to-Telegram from $SVC_USER fails closed"
101
+ fi
102
+ ip -4 route replace unreachable default metric 1024 table "$TABLE"
103
+
104
+ fetch_cidrs
105
+ for p in $V4; do
106
+ ip rule add pref "$PRIO" to "$p" uidrange "${UID_N}-${UID_N}" lookup "$TABLE" || log "v4 rule add failed for $p"
107
+ done
108
+ if [ -n "$WARP6" ]; then
109
+ for p in $V6; do
110
+ ip -6 rule add pref "$PRIO" to "$p" uidrange "${UID_N}-${UID_N}" lookup "$TABLE" || log "v6 rule add failed for $p"
111
+ done
112
+ fi
113
+
114
+ v6_count=0; [ -n "$WARP6" ] && v6_count=$(echo $V6 | wc -w) || true
115
+ log "up: uid=$UID_N v6src=${WARP6:-none} table=$TABLE pref=$PRIO ($(echo $V4 | wc -w)v4 ${v6_count}v6)"
116
+ ;;
117
+ down)
118
+ del_rules
119
+ ip -6 route flush table "$TABLE" 2>/dev/null || true
120
+ ip -4 route flush table "$TABLE" 2>/dev/null || true
121
+ log "down: table=$TABLE pref=$PRIO cleaned"
122
+ ;;
123
+ *)
124
+ echo "usage: $0 {up|down} [svc_user] [iface] [table] [tid] [prio]" >&2
125
+ exit 1
126
+ ;;
127
+ esac
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # IPv6 policy routing for Cloudflare WARP (usque nativetun).
4
+ #
5
+
6
+ set -euo pipefail
7
+
8
+ cmd=${1:?up|down}
9
+ IFACE=${2:-warp}
10
+ CFG=${3:-/etc/usque/config.json}
11
+
12
+ # If omitted, values are derived from IFACE.
13
+ EXPLICIT_TID=${4:-}
14
+ EXPLICIT_PRIO=${5:-}
15
+
16
+ # This lets multiple independent warp interfacescoexist
17
+ _slot() {
18
+ printf '%s' "$1" | cksum | awk '{print $1 % 100}'
19
+ }
20
+
21
+ SLOT=$(_slot "$IFACE")
22
+
23
+ if [ -n "$EXPLICIT_TID" ]; then
24
+ TID=$EXPLICIT_TID
25
+ else
26
+ TID=$((200 + SLOT)) # 200-299: safe custom table ID range
27
+ fi
28
+
29
+ # Unique table name per interface
30
+ TABLE="warp_$(printf '%s' "$IFACE" | tr -c '[:alnum:]_.-' '_')"
31
+
32
+ if [ -n "$EXPLICIT_PRIO" ]; then
33
+ PRIO=$EXPLICIT_PRIO
34
+ else
35
+ PRIO=$((20000 + SLOT * 10)) # 20000-20990
36
+ fi
37
+
38
+ PRIO_OIF=$((PRIO + 5))
39
+ WAIT_SECS=8
40
+
41
+ log(){ logger -t warp-policy -- "$*"; }
42
+
43
+ get_warp6(){
44
+ ip -6 -o addr show dev "$IFACE" scope global 2>/dev/null \
45
+ | awk '{print $4}' | head -n1 | cut -d/ -f1
46
+ }
47
+
48
+ get_ep6(){
49
+ jq -r '.endpoint_v6 // empty' "$CFG" 2>/dev/null || true
50
+ }
51
+
52
+ ensure_table(){
53
+ grep -qE "^[[:space:]]*$TID[[:space:]]+$TABLE$" /etc/iproute2/rt_tables \
54
+ || echo "$TID $TABLE" >> /etc/iproute2/rt_tables
55
+ }
56
+
57
+ wait_for_warp6(){
58
+ local i=0
59
+ while [ $i -lt "$WAIT_SECS" ]; do
60
+ local a
61
+ a=$(get_warp6 || true)
62
+ if [ -n "$a" ]; then echo "$a"; return 0; fi
63
+ sleep 1; i=$((i+1))
64
+ done
65
+ return 1
66
+ }
67
+
68
+ case "$cmd" in
69
+ up)
70
+ ensure_table
71
+
72
+ if ! WARP6=$(wait_for_warp6); then
73
+ log "No IPv6 on $IFACE after ${WAIT_SECS}s; leaving without rules"
74
+ exit 0
75
+ fi
76
+
77
+ EP6=$(get_ep6)
78
+ if [ -n "$EP6" ]; then
79
+ # Ensure the WARP endpoint remains reachable outside the tunnel while
80
+ # our default-via-warp is installed. Only possible if host has IPv6.
81
+ route_info=$(ip -6 route get "${EP6}" 2>/dev/null || true)
82
+ GW6=$(echo "$route_info" | awk '{for(i=1;i<NF;i++) if($i=="via"){print $(i+1); exit}}')
83
+ DEV=$(echo "$route_info" | awk '{for(i=1;i<NF;i++) if($i=="dev"){print $(i+1); exit}}')
84
+ if [ -n "${GW6}" ] && [ -n "${DEV}" ]; then
85
+ ip -6 route replace "${EP6}/128" via "$GW6" dev "$DEV" onlink
86
+ fi
87
+ fi
88
+
89
+ ip -6 route replace default dev "$IFACE" table "$TABLE"
90
+
91
+ ip -6 rule del pref "$PRIO" 2>/dev/null || true
92
+ ip -6 rule add pref "$PRIO" from "${WARP6}/128" lookup "$TABLE"
93
+ ip -6 rule del pref "$PRIO_OIF" 2>/dev/null || true
94
+ ip -6 rule add pref "$PRIO_OIF" oif "$IFACE" lookup "$TABLE"
95
+
96
+ log "IPv6 policy up: src=${WARP6}/128 pref=$PRIO, oif=$IFACE pref=$PRIO_OIF table=$TABLE"
97
+ ;;
98
+
99
+ down)
100
+ ip -6 rule del pref "$PRIO" 2>/dev/null || true
101
+ ip -6 rule del pref "$PRIO_OIF" 2>/dev/null || true
102
+
103
+ EP6=$(get_ep6 || true)
104
+ if [ -n "${EP6:-}" ]; then
105
+ ip -6 route del "${EP6}/128" 2>/dev/null || true
106
+ fi
107
+
108
+ ip -6 route flush table "$TABLE" 2>/dev/null || true
109
+
110
+ log "IPv6 policy down: table=$TABLE prefs=$PRIO,$PRIO_OIF cleaned"
111
+ ;;
112
+
113
+ *)
114
+ echo "usage: $0 {up|down} [iface] [config] [tid] [prio]" >&2
115
+ echo " tid/prio: optional explicit table id and rule priority base (advanced)" >&2
116
+ exit 1
117
+ ;;
118
+ esac
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ cmd=${1:?up|down}
6
+ IFACE=${2:-warp}
7
+
8
+ log(){ logger -t zt-tunnel-warp -- "$*"; }
9
+
10
+ CIDRS="198.41.200.0/24 104.16.0.0/12"
11
+
12
+ case "$cmd" in
13
+ up)
14
+ for cidr in $CIDRS; do
15
+ ip route replace "$cidr" dev "$IFACE"
16
+ log "Added route for $cidr via $IFACE"
17
+ done
18
+ ;;
19
+ down)
20
+ for cidr in $CIDRS; do
21
+ ip route del "$cidr" dev "$IFACE" 2>/dev/null || true
22
+ log "Removed route for $cidr via $IFACE"
23
+ done
24
+ ;;
25
+ *)
26
+ log "Invalid command: $cmd"
27
+ exit 1
28
+ esac
@@ -0,0 +1,19 @@
1
+ [Unit]
2
+ Description=Blocky DNS proxy
3
+ Wants=network-online.target nss-lookup.target
4
+ After=dns-internal.service
5
+ Before=nss-lookup.target
6
+
7
+ [Service]
8
+ User=blocky
9
+ Group=blocky
10
+ Type=simple
11
+ ExecStart=/usr/bin/blocky --config {{ CONFIG_PATH }}
12
+ AmbientCapabilities=CAP_NET_BIND_SERVICE
13
+ StandardOutput=journal
14
+ StandardError=journal
15
+ Restart=on-failure
16
+ RestartSec=5s
17
+
18
+ [Install]
19
+ WantedBy=multi-user.target
@@ -0,0 +1,27 @@
1
+ [Unit]
2
+ Description=Cloudflare Zero Trust Tunnel
3
+ Wants=network-online.target
4
+ After=network-online.target{% if ROUTE_THROUGH_WARP %} {{ WARP_SERVICE }}.service{% endif %}
5
+
6
+ {% if ROUTE_THROUGH_WARP %}
7
+ Requires={{ WARP_SERVICE }}.service
8
+ {% endif %}
9
+
10
+ [Service]
11
+ User=cloudflare
12
+ Group=cloudflare
13
+ Type=simple
14
+
15
+ ExecStart=/usr/bin/cloudflared --config {{ CONFIG_PATH }} tunnel run
16
+ {% if ROUTE_THROUGH_WARP %}
17
+ ExecStartPost={{ WORKDIR }}/zt-tunnel-warp.sh up {{ WARP_IFACE }}
18
+ ExecStopPost={{ WORKDIR }}/zt-tunnel-warp.sh down {{ WARP_IFACE }}
19
+ {% endif %}
20
+ AmbientCapabilities=CAP_NET_ADMIN
21
+ StandardOutput=journal
22
+ StandardError=journal
23
+ Restart=on-failure
24
+ RestartSec=5s
25
+
26
+ [Install]
27
+ WantedBy=multi-user.target
@@ -0,0 +1,21 @@
1
+ [Unit]
2
+ Description=Cloudflare Warp Tunnel (Masque)
3
+ Wants=network-online.target
4
+ After=network-online.target
5
+
6
+ [Service]
7
+ User=cloudflare
8
+ Group=cloudflare
9
+ Type=simple
10
+
11
+ ExecStart=/usr/local/bin/usque nativetun -m {{ MTU }} -c {{ CONFIG_PATH }} -n {{ INET_NAME }} {% if USE_OUTBOUND_IPV6 %}--ipv6{% endif %}
12
+ ExecStartPost={{ WORKDIR }}/warp-v6-policy.sh up {{ INET_NAME }} {{ CONFIG_PATH }}
13
+ ExecStopPost={{ WORKDIR }}/warp-v6-policy.sh down {{ INET_NAME }} {{ CONFIG_PATH }}
14
+ AmbientCapabilities=CAP_NET_ADMIN
15
+ StandardOutput=journal
16
+ StandardError=journal
17
+ Restart=on-failure
18
+ RestartSec=5s
19
+
20
+ [Install]
21
+ WantedBy=multi-user.target
@@ -0,0 +1,15 @@
1
+ [Unit]
2
+ Description=Setup dns-internal dummy interface for blocky
3
+ Before=blocky.service
4
+ After=network.target
5
+
6
+ [Service]
7
+ Type=oneshot
8
+ RemainAfterExit=yes
9
+ ExecStart=/bin/sh -c 'ip link show dns-internal >/dev/null 2>&1 || ip link add dns-internal type dummy'
10
+ ExecStart=/bin/sh -c 'ip addr show dev dns-internal | grep -q "{{ LISTEN_ADDRESS }}" || ip addr add {{ LISTEN_CIDR }} dev dns-internal'
11
+ ExecStart=/sbin/ip link set dns-internal up
12
+ ExecStop=/sbin/ip link del dns-internal
13
+
14
+ [Install]
15
+ WantedBy=multi-user.target
@@ -0,0 +1,36 @@
1
+ [Unit]
2
+ Description=Telemt MTProto proxy
3
+ Wants=network-online.target
4
+ After=network-online.target{% if WARP_ENABLED %} {{ WARP_SERVICE }}.service{% endif %}
5
+
6
+ {% if WARP_ENABLED %}
7
+ Requires={{ WARP_SERVICE }}.service
8
+ BindsTo={{ WARP_SERVICE }}.service
9
+ {% endif %}
10
+
11
+ [Service]
12
+ Type=simple
13
+ User={{ USER }}
14
+ Group={{ GROUP }}
15
+ ExecStart={{ BINARY_PATH }} {{ CONFIG_PATH }}
16
+ {% if WARP_ENABLED %}
17
+ ExecStartPre=+{{ TELEPROXY_SCRIPT }} up {{ USER }} {{ WARP_IFACE }}
18
+ {% endif %}
19
+ {% if SYNFIX_ENABLED %}
20
+ ExecStartPre=+{{ SYNFIX_SCRIPT }} up {{ PORT }}
21
+ ExecStopPost=+{{ SYNFIX_SCRIPT }} down {{ PORT }}
22
+ {% endif %}
23
+ {% if WARP_ENABLED %}
24
+ ExecStopPost=+{{ TELEPROXY_SCRIPT }} down {{ USER }} {{ WARP_IFACE }}
25
+ {% endif %}
26
+ Restart=on-failure
27
+ RestartSec=5s
28
+ LimitNOFILE=65536
29
+ AmbientCapabilities=CAP_NET_BIND_SERVICE
30
+ CapabilityBoundingSet=CAP_NET_BIND_SERVICE
31
+ NoNewPrivileges=true
32
+ StandardOutput=journal
33
+ StandardError=journal
34
+
35
+ [Install]
36
+ WantedBy=multi-user.target
@@ -0,0 +1,49 @@
1
+ # Managed by NullForge. Local edits are overwritten on redeploy.
2
+
3
+ [general]
4
+ fast_mode = {{ FAST_MODE | lower }}
5
+ use_middle_proxy = {{ USE_MIDDLE_PROXY | lower }}
6
+ log_level = "silent"
7
+
8
+ [general.modes]
9
+ classic = {{ MODE_CLASSIC | lower }}
10
+ secure = {{ MODE_SECURE | lower }}
11
+ tls = {{ MODE_TLS | lower }}
12
+
13
+ [server]
14
+ max_connections = {{ MAX_CONNECTIONS }}
15
+ port = {{ PORT }}
16
+
17
+ [server.api]
18
+ enabled = {{ API_ENABLED | lower }}
19
+
20
+ [network]
21
+ ipv6 = {{ IPV6 | lower }}
22
+ prefer = {{ PREFER }}
23
+
24
+ [timeouts]
25
+ client_handshake = {{ CLIENT_HANDSHAKE }}
26
+
27
+ [censorship]
28
+ tls_domain = "{{ TLS_DOMAIN }}"
29
+ {% if UNKNOWN_SNI_ACTION %}
30
+ unknown_sni_action = "{{ UNKNOWN_SNI_ACTION }}"
31
+ {% else %}
32
+ #unknown_sni_action = "mask"
33
+ {% endif %}
34
+
35
+ [access.users]
36
+ {% for name, secret in USERS.items() %}
37
+ {{ name }} = "{{ secret }}"
38
+ {% endfor %}
39
+
40
+ [[upstreams]]
41
+ type = "direct"
42
+ {% if ROUTE_VIA_WARP %}
43
+ # Egress is steered through WARP by teleproxy-warp.sh (per-uid policy routing),
44
+ # so the upstream stays "direct" instead of binding to the "{{ WARP_IFACE }}" interface.
45
+ {% else %}
46
+ #interface = "{{ WARP_IFACE }}"
47
+ {% endif %}
48
+ weight = 10
49
+ enabled = true
@@ -0,0 +1,5 @@
1
+ SocksPort {{ SOCKS_PORT }}
2
+ AutomapHostsOnResolve 1
3
+ DnsPort {{ DNS_PORT }}
4
+ SocksPolicy accept 127.0.0.1
5
+ SocksPolicy reject *
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: nullforge
3
+ Version: 0.1.0
4
+ Summary: Forge the server's baseline from null
5
+ Author-email: wlix13 <github@wlix13.dev>
6
+ Maintainer-email: wlix13 <github@wlix13.dev>
7
+ License-File: LICENSE
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Requires-Python: <3.14,>=3.13
11
+ Requires-Dist: click
12
+ Requires-Dist: pydantic
13
+ Requires-Dist: pyinfra
14
+ Requires-Dist: rich-click
15
+ Description-Content-Type: text/markdown
16
+
17
+ # NullForge
18
+
19
+ Forge the server's baseline from null — an infrastructure-as-code framework built on [pyinfra](https://pyinfra.com), themed around a blacksmith's forge: **inventories** define hosts, **molds** shape the configuration, **runes** are idempotent operation sets, the **foundry** casts them onto targets, and the **smithy** holds cross-distro helpers.
20
+
21
+ > [!WARNING]
22
+ > **NullForge is in active development.** Until the `v1.0.0` release, the mold schemas, runes and deploy behaviour may change at any time — breaking changes can land in **any** release, including patch versions. Pin an exact version (e.g. `nullforge==0.1.0`) and check the [release notes](https://github.com/wlix13/NullForge/releases) before upgrading.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ uv sync
28
+ ```
29
+
30
+ ## Deploy
31
+
32
+ ```bash
33
+ # Cast the full baseline (all enabled features from the inventory)
34
+ uv run pyinfra nullforge/inventories/example.py nullforge/foundry/full_cast.py
35
+ ```
36
+
37
+ ## Contributing
38
+
39
+ See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the full setup, commit conventions and pull request flow.