Skip to content

Commit d29bd21

Browse files
committed
Add DigitalOcean configuration for IRC network migration.
- Create DigitalOcean-specific server configs for magnet-9rl-do and magnet-1eu-do - Use Tailscale networking with stable domain names for server linking - Remove ephemeral IP dependencies from Fly.io approach - Configure proper autoconn for magnet-1eu to connect to magnet-9rl hub
1 parent c5120d7 commit d29bd21

File tree

592 files changed

+36357
-36
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

592 files changed

+36357
-36
lines changed

.github/workflows/ci.yml

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ name: CI
55

66
on:
77
push:
8-
branches: [main]
98
pull_request:
10-
branches: [main]
119

1210
env:
1311
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
@@ -17,25 +15,25 @@ jobs:
1715
name: Run Test Suite
1816
runs-on: ubuntu-latest
1917
container: perl:stable-slim
20-
18+
2119
steps:
2220
- name: Checkout code
2321
uses: actions/checkout@v4
24-
22+
2523
- name: Setup flyctl
2624
uses: superfly/flyctl-actions/setup-flyctl@master
27-
25+
2826
- name: Run Test Suite
2927
run: prove -v t/
30-
28+
3129
lint:
3230
name: Lint and Validation
3331
runs-on: ubuntu-latest
34-
32+
3533
steps:
3634
- name: Checkout code
3735
uses: actions/checkout@v4
38-
36+
3937
- name: Validate Dockerfile syntax
4038
run: |
4139
# Check basic Dockerfile syntax
@@ -58,7 +56,7 @@ jobs:
5856
echo "$dockerfile syntax OK"
5957
fi
6058
done
61-
59+
6260
- name: Validate fly.toml files
6361
run: |
6462
# Check for required fly.toml files and basic structure
@@ -76,7 +74,7 @@ jobs:
7674
echo "$config structure OK"
7775
fi
7876
done
79-
77+
8078
- name: Validate configuration templates
8179
run: |
8280
# Check that templates use environment variable substitution
@@ -90,7 +88,7 @@ jobs:
9088
echo "$template template syntax OK"
9189
fi
9290
done
93-
91+
9492
- name: Validate startup scripts
9593
run: |
9694
# Check startup scripts are executable and have proper shebang
@@ -116,38 +114,38 @@ jobs:
116114
security:
117115
name: Security Checks
118116
runs-on: ubuntu-latest
119-
117+
120118
steps:
121119
- name: Checkout code
122120
uses: actions/checkout@v4
123-
121+
124122
- name: Check for hardcoded secrets
125123
run: |
126124
echo "Checking for hardcoded secrets..."
127-
125+
128126
# Check for hardcoded Tailscale auth keys (actual keys, not placeholders)
129127
if grep -r "tskey-auth-[a-zA-Z0-9]\{20,\}" . --exclude-dir=.git --exclude="*.md" --exclude="t/*.t"; then
130128
echo "ERROR: Found hardcoded Tailscale auth key"
131129
exit 1
132130
fi
133-
131+
134132
# Check for hardcoded passwords
135133
if grep -ri "password.*=" . --exclude-dir=.git --exclude="*.md" | grep -v '\${' | grep -v 'PASSWORD'; then
136134
echo "WARNING: Found potential hardcoded password"
137135
fi
138-
136+
139137
# Check that sensitive files aren't tracked
140138
if [ -f "passwords.conf" ] || [ -f "*.key" ]; then
141139
echo "ERROR: Sensitive files found in repository"
142140
exit 1
143141
fi
144-
142+
145143
echo "Security checks passed"
146-
144+
147145
- name: Validate USER directives in Dockerfiles
148146
run: |
149147
echo "Checking for non-root users in Dockerfiles..."
150-
148+
151149
for dockerfile in solanum/Dockerfile atheme/Dockerfile; do
152150
if [ -f "$dockerfile" ]; then
153151
if ! grep -q "^USER " "$dockerfile"; then
@@ -156,4 +154,4 @@ jobs:
156154
fi
157155
echo "$dockerfile has proper USER directive"
158156
fi
159-
done
157+
done

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ Thumbs.db
2020

2121
# Log files
2222
*.log
23+
.envrc

FUTURE_IMPROVEMENTS.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ABOUTME: Future improvements and architectural changes for Magnet IRC Network
2+
# ABOUTME: Notes on potential optimizations and consolidation strategies
3+
4+
## Single Multi-Region App Architecture
5+
6+
### Dynamic SID Generation
7+
Instead of hardcoded server IDs like `9RL` and `1EU`, we could use a dynamic scheme:
8+
9+
```bash
10+
# Generate SID from region + machine count
11+
REGION_PREFIX=$(echo "$FLY_REGION" | cut -c1-2 | tr '[:lower:]' '[:upper:]')
12+
13+
# Get machine count via DNS SRV query
14+
MACHINE_COUNT=$(dig +short SRV ${FLY_APP_NAME}.internal | grep "\.${FLY_REGION}\." | wc -l)
15+
SERVER_NUMBER=$((MACHINE_COUNT + 1))
16+
export SERVER_SID="${SERVER_NUMBER}${REGION_PREFIX}"
17+
export SERVER_NAME="magnet-${FLY_REGION}"
18+
export SERVER_DESCRIPTION="Magnet IRC Network - ${FLY_REGION} Server"
19+
```
20+
21+
This would generate:
22+
- First machine in `ord`: `1OR`
23+
- Second machine in `ord`: `2OR`
24+
- First machine in `ams`: `1AM`
25+
- Third machine in `ord`: `3OR`
26+
27+
### Benefits
28+
- **Auto-scaling**: Machines get sequential SIDs automatically
29+
- **No region-specific configs**: Single template works everywhere
30+
- **Simpler deployment**: One app instead of separate regional apps
31+
- **Automatic geo-routing**: Fly.io routes users to nearest region
32+
- **DNS-based discovery**: No API calls needed, just standard DNS queries
33+
34+
### Implementation
35+
- Consolidate into single app with multi-region deployment
36+
- Remove hardcoded server configs
37+
- Use `FLY_REGION` environment variable for runtime decisions
38+
- Query DNS SRV records to determine machine sequence number
39+
40+
### Migration Path
41+
1. Create new single multi-region app
42+
2. Deploy to both `ord` and `ams` regions
43+
3. Test dynamic SID generation and linking
44+
4. Migrate users and DNS
45+
5. Decommission separate regional apps

atheme/atheme.conf.template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ serverinfo {
3535
/* Uplink configuration - primary IRC server */
3636
uplink "${ATHEME_HUB_HOSTNAME}" {
3737
host = "${ATHEME_HUB_HOSTNAME}";
38-
vhost = "${ATHEME_TAILSCALE_IP}";
38+
vhost = "magnet-atheme.${TAILSCALE_DOMAIN}";
3939
send_password = "${PASSWORD_9RL}";
4040
receive_password = "${SERVICES_PASSWORD}";
4141
port = 6667;
4242
};
4343

4444
uplink "${ATHEME_FALLBACK_HOSTNAME}" {
4545
host = "${ATHEME_FALLBACK_HOSTNAME}";
46-
vhost = "${ATHEME_TAILSCALE_IP}";
46+
vhost = "magnet-atheme.${TAILSCALE_DOMAIN}";
4747
send_password = "${PASSWORD_1EU}";
4848
receive_password = "${SERVICES_PASSWORD}";
4949
port = 6667;

bin/generate-connects.sh

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/bin/bash
2+
# ABOUTME: Generate Solanum connect blocks from Fly.io DNS _instances.internal lookup
3+
# ABOUTME: Creates dynamic server linking configuration based on running instances
4+
5+
set -e
6+
7+
# Function to show usage
8+
usage() {
9+
echo "Usage: $0 [options]"
10+
echo ""
11+
echo "Options:"
12+
echo " -h, --help Show this help message"
13+
echo " -a, --app <name> Fly.io app name (default: from FLY_APP_NAME env)"
14+
echo " -r, --region <region> Current region (default: from FLY_REGION env)"
15+
echo " -o, --output <file> Output file (default: stdout)"
16+
echo ""
17+
echo "Example:"
18+
echo " $0 --app magnet-irc --region ams"
19+
echo " $0 > /opt/solanum/etc/connects.conf"
20+
exit 0
21+
}
22+
23+
# Parse arguments
24+
OUTPUT=""
25+
APP_NAME="${FLY_APP_NAME}"
26+
CURRENT_REGION="${FLY_REGION}"
27+
28+
while [[ $# -gt 0 ]]; do
29+
case $1 in
30+
-h|--help)
31+
usage
32+
;;
33+
-a|--app)
34+
APP_NAME="$2"
35+
shift 2
36+
;;
37+
-r|--region)
38+
CURRENT_REGION="$2"
39+
shift 2
40+
;;
41+
-o|--output)
42+
OUTPUT="$2"
43+
shift 2
44+
;;
45+
--simulate)
46+
SIMULATE=true
47+
shift
48+
;;
49+
*)
50+
echo "Unknown option: $1"
51+
usage
52+
;;
53+
esac
54+
done
55+
56+
if [ -z "$APP_NAME" ]; then
57+
echo "Error: App name not specified and FLY_APP_NAME not set" >&2
58+
exit 1
59+
fi
60+
61+
# Function to generate SID from region and instance number
62+
generate_sid() {
63+
local region="$1"
64+
local instance_num="$2"
65+
local region_prefix=$(echo "$region" | cut -c1-2 | tr '[:lower:]' '[:upper:]')
66+
echo "${instance_num}${region_prefix}"
67+
}
68+
69+
# Function to generate connect block
70+
generate_connect_block() {
71+
local instance_id="$1"
72+
local region="$2"
73+
local ipv6="$3"
74+
local instance_num="$4"
75+
76+
# Skip self
77+
if [ "$region" = "$CURRENT_REGION" ] && [ "$instance_num" = "1" ]; then
78+
return
79+
fi
80+
81+
local sid=$(generate_sid "$region" "$instance_num")
82+
local server_name="magnet-${region}"
83+
84+
cat <<EOF
85+
/* Connection to ${server_name} (${instance_id}) */
86+
connect "${server_name}.internal" {
87+
host = "[${ipv6}]";
88+
send_password = "\${LINK_PASSWORD_OUT}";
89+
accept_password = "\${LINK_PASSWORD_IN}";
90+
port = 6667;
91+
class = "server";
92+
flags = topicburst;
93+
};
94+
95+
EOF
96+
}
97+
98+
# Function to parse DNS TXT record response
99+
parse_instances() {
100+
local dns_response="$1"
101+
local instance_num=0
102+
local current_region=""
103+
104+
echo "/* Generated connect blocks from _instances.internal */"
105+
echo "/* Generated at: $(date -u '+%Y-%m-%d %H:%M:%S UTC') */"
106+
echo "/* Current region: ${CURRENT_REGION} */"
107+
echo ""
108+
109+
# Parse each line of the DNS response
110+
# Format expected: "instance_id region ipv6_address"
111+
echo "$dns_response" | while IFS=' ' read -r instance region ipv6 rest; do
112+
# Skip empty lines or comments
113+
[ -z "$instance" ] && continue
114+
[[ "$instance" =~ ^#.*$ ]] && continue
115+
116+
# Reset counter if region changes
117+
if [ "$region" != "$current_region" ]; then
118+
instance_num=1
119+
current_region="$region"
120+
else
121+
instance_num=$((instance_num + 1))
122+
fi
123+
124+
generate_connect_block "$instance" "$region" "$ipv6" "$instance_num"
125+
done
126+
}
127+
128+
# Main execution
129+
echo "Querying _instances.${APP_NAME}.internal for running instances..." >&2
130+
131+
# For testing/development: simulate DNS response if needed
132+
if [ "$SIMULATE" = "true" ]; then
133+
echo "Using simulated DNS response..." >&2
134+
DNS_RESULT="3287e444b64708 ord fdaa:27:74d0:a7b:569:4950:e79e:2
135+
56837dddad4268 ams fdaa:27:74d0:a7b:569:1c37:481e:2
136+
78945bccef5512 sin fdaa:27:74d0:a7b:569:8821:912a:2"
137+
else
138+
# Query DNS for instances
139+
# Using dig for TXT records which Fly.io uses for instance discovery
140+
DNS_RESULT=$(dig +short TXT "_instances.${APP_NAME}.internal" 2>/dev/null || true)
141+
142+
if [ -z "$DNS_RESULT" ]; then
143+
echo "Warning: No instances found via DNS query" >&2
144+
echo "Trying alternative query method..." >&2
145+
146+
# Alternative: Try using nslookup
147+
DNS_RESULT=$(nslookup -type=TXT "_instances.${APP_NAME}.internal" 2>/dev/null | grep "text =" | sed 's/.*text = "\(.*\)"/\1/' || true)
148+
fi
149+
150+
if [ -z "$DNS_RESULT" ]; then
151+
echo "Warning: Could not retrieve instance list" >&2
152+
echo "/* No instances found - manual configuration required */"
153+
exit 0
154+
fi
155+
fi
156+
157+
# Generate the configuration
158+
if [ -n "$OUTPUT" ]; then
159+
parse_instances "$DNS_RESULT" > "$OUTPUT"
160+
echo "Connect blocks written to: $OUTPUT" >&2
161+
else
162+
parse_instances "$DNS_RESULT"
163+
fi

0 commit comments

Comments
 (0)