Skip to content

Commit 4a37076

Browse files
committed
Add .NET 10 and remove legacy .NET versions
1 parent ec365c3 commit 4a37076

11 files changed

Lines changed: 274 additions & 11 deletions

File tree

.claude/hooks/install-dotnet.sh

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/bash
2+
# Install .NET 10 SDK and essential build tools for development
3+
# This script is intended to be run as a Claude Code Web startup hook
4+
set -euo pipefail
5+
6+
DOTNET_VERSION="10.0"
7+
INSTALL_DIR="$HOME/.dotnet"
8+
9+
# Only run in remote Claude Code environments
10+
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
11+
exit 0
12+
fi
13+
14+
# Install essential build tools if missing
15+
if ! command -v tail &> /dev/null || ! command -v head &> /dev/null; then
16+
echo "Installing essential build tools (coreutils)..."
17+
apt-get update -qq && apt-get install -y -qq coreutils > /dev/null 2>&1 || true
18+
fi
19+
20+
# Download and run the official Microsoft install script
21+
curl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
22+
chmod +x /tmp/dotnet-install.sh
23+
24+
/tmp/dotnet-install.sh --channel $DOTNET_VERSION --install-dir "$INSTALL_DIR"
25+
26+
echo "Installing additional .NET runtime frameworks..."
27+
/tmp/dotnet-install.sh --channel 8.0 --runtime dotnet --install-dir "$INSTALL_DIR"
28+
/tmp/dotnet-install.sh --channel 9.0 --runtime dotnet --install-dir "$INSTALL_DIR"
29+
30+
# Clean up
31+
rm -f /tmp/dotnet-install.sh
32+
33+
# Add to PATH for current session
34+
export DOTNET_ROOT="$INSTALL_DIR"
35+
export PATH="$PATH:$INSTALL_DIR:$INSTALL_DIR/tools"
36+
37+
# Add to PATH in the CLAUDE_ENV_FILE
38+
if [ -n "$CLAUDE_ENV_FILE" ]; then
39+
echo "export DOTNET_ROOT=\"$INSTALL_DIR\"" >> "$CLAUDE_ENV_FILE"
40+
echo "export PATH=\"\$PATH:$INSTALL_DIR:$INSTALL_DIR/tools\"" >> "$CLAUDE_ENV_FILE"
41+
fi
42+
43+
# Verify installation
44+
echo ".NET SDK version $(dotnet --version) installed successfully at $INSTALL_DIR/dotnet"
45+
46+
# Configure NuGet to work with Claude Code proxy
47+
# .NET's HttpClient doesn't properly handle proxy credentials from environment variables,
48+
# so we configure NuGet to use localhost:8888 which will be handled by a proxy forwarder
49+
echo "Configuring NuGet proxy settings..."
50+
NUGET_CONFIG_DIR="$HOME/.nuget/NuGet"
51+
NUGET_CONFIG_FILE="$NUGET_CONFIG_DIR/NuGet.Config"
52+
53+
mkdir -p "$NUGET_CONFIG_DIR"
54+
55+
cat > "$NUGET_CONFIG_FILE" << 'EOF'
56+
<?xml version="1.0" encoding="utf-8"?>
57+
<configuration>
58+
<packageSources>
59+
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
60+
</packageSources>
61+
<config>
62+
<add key="http_proxy" value="http://127.0.0.1:8888" />
63+
<add key="https_proxy" value="http://127.0.0.1:8888" />
64+
</config>
65+
</configuration>
66+
EOF
67+
68+
echo "NuGet configuration updated at $NUGET_CONFIG_FILE"
69+
70+
# Start NuGet proxy forwarder in background
71+
# This handles the proxy authentication that .NET's HttpClient doesn't support
72+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
73+
PROXY_FORWARDER="$SCRIPT_DIR/nuget-proxy-forwarder.py"
74+
75+
if [ -f "$PROXY_FORWARDER" ]; then
76+
# Check if forwarder is already running
77+
if ! pgrep -f "nuget-proxy-forwarder.py" > /dev/null; then
78+
echo "Starting NuGet proxy forwarder on localhost:8888..."
79+
nohup python3 "$PROXY_FORWARDER" > /dev/null 2>&1 &
80+
sleep 1
81+
if pgrep -f "nuget-proxy-forwarder.py" > /dev/null; then
82+
echo "Proxy forwarder started successfully"
83+
else
84+
echo "Warning: Proxy forwarder may not have started correctly"
85+
fi
86+
else
87+
echo "NuGet proxy forwarder is already running"
88+
fi
89+
else
90+
echo "Warning: Proxy forwarder script not found at $PROXY_FORWARDER"
91+
fi
92+
93+
exit 0
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env python3
2+
"""
3+
NuGet Proxy Forwarder for Claude Code
4+
5+
.NET's HttpClient doesn't properly handle proxy credentials from environment variables.
6+
This forwarder accepts unauthenticated connections on localhost:8888 and forwards them
7+
to the authenticated Claude Code proxy with proper authentication headers.
8+
9+
Started automatically by install-dotnet.sh as a background daemon.
10+
"""
11+
import socket
12+
import threading
13+
import os
14+
import base64
15+
import sys
16+
from urllib.parse import urlparse
17+
18+
def forward_data(source, destination):
19+
"""Forward data from source socket to destination socket"""
20+
try:
21+
while True:
22+
data = source.recv(4096)
23+
if not data:
24+
break
25+
destination.sendall(data)
26+
except:
27+
pass
28+
29+
def handle_client(client_socket, client_addr, proxy_host, proxy_port, proxy_auth_header):
30+
"""Handle a client connection"""
31+
try:
32+
# Read the CONNECT request
33+
request = b""
34+
while b"\r\n\r\n" not in request:
35+
chunk = client_socket.recv(1)
36+
if not chunk:
37+
return
38+
request += chunk
39+
40+
request_str = request.decode('utf-8', errors='ignore')
41+
lines = request_str.split('\r\n')
42+
43+
if not lines[0].startswith('CONNECT'):
44+
client_socket.close()
45+
return
46+
47+
# Parse CONNECT target
48+
target = lines[0].split(' ')[1]
49+
50+
# Connect to real proxy
51+
proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
52+
proxy_socket.connect((proxy_host, proxy_port))
53+
54+
# Forward CONNECT request with authentication
55+
connect_request = f"CONNECT {target} HTTP/1.1\r\n"
56+
connect_request += f"Host: {target}\r\n"
57+
if proxy_auth_header:
58+
connect_request += proxy_auth_header
59+
connect_request += "\r\n"
60+
61+
proxy_socket.sendall(connect_request.encode())
62+
63+
# Read proxy response
64+
response = b""
65+
while b"\r\n\r\n" not in response:
66+
chunk = proxy_socket.recv(1)
67+
if not chunk:
68+
client_socket.close()
69+
proxy_socket.close()
70+
return
71+
response += chunk
72+
73+
# Forward response to client
74+
client_socket.sendall(response)
75+
76+
response_str = response.decode('utf-8', errors='ignore')
77+
status_line = response_str.split('\r\n')[0]
78+
79+
# If successful, start bidirectional forwarding
80+
if "200" in status_line:
81+
# Start forwarding threads
82+
client_to_proxy = threading.Thread(target=forward_data, args=(client_socket, proxy_socket))
83+
proxy_to_client = threading.Thread(target=forward_data, args=(proxy_socket, client_socket))
84+
85+
client_to_proxy.daemon = True
86+
proxy_to_client.daemon = True
87+
88+
client_to_proxy.start()
89+
proxy_to_client.start()
90+
91+
# Wait for either thread to finish
92+
client_to_proxy.join()
93+
proxy_to_client.join()
94+
95+
except Exception as e:
96+
pass # Silently handle errors to avoid log spam
97+
finally:
98+
try:
99+
client_socket.close()
100+
except:
101+
pass
102+
try:
103+
proxy_socket.close()
104+
except:
105+
pass
106+
107+
def main():
108+
# Parse the real proxy from environment
109+
proxy_url = os.environ.get('HTTPS_PROXY') or os.environ.get('HTTP_PROXY')
110+
if not proxy_url:
111+
print("Error: No HTTPS_PROXY or HTTP_PROXY environment variable set", file=sys.stderr)
112+
sys.exit(1)
113+
114+
proxy_uri = urlparse(proxy_url)
115+
proxy_host = proxy_uri.hostname
116+
proxy_port = proxy_uri.port or 80
117+
proxy_auth = proxy_uri.username and proxy_uri.password
118+
119+
if proxy_auth:
120+
auth_string = f"{proxy_uri.username}:{proxy_uri.password}"
121+
proxy_auth_header = "Proxy-Authorization: Basic " + base64.b64encode(auth_string.encode()).decode() + "\r\n"
122+
else:
123+
proxy_auth_header = ""
124+
125+
# Create server socket
126+
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
127+
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
128+
129+
try:
130+
server.bind(('127.0.0.1', 8888))
131+
server.listen(5)
132+
except OSError as e:
133+
# Port already in use - another forwarder is running
134+
sys.exit(0)
135+
136+
# Daemonize - close stdout/stderr to run silently in background
137+
sys.stdout.close()
138+
sys.stderr.close()
139+
140+
try:
141+
while True:
142+
client_sock, client_addr = server.accept()
143+
client_thread = threading.Thread(
144+
target=handle_client,
145+
args=(client_sock, client_addr, proxy_host, proxy_port, proxy_auth_header)
146+
)
147+
client_thread.daemon = True
148+
client_thread.start()
149+
except KeyboardInterrupt:
150+
server.close()
151+
152+
if __name__ == '__main__':
153+
main()

.claude/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"matcher": "startup",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/install-dotnet.sh"
10+
}
11+
]
12+
}
13+
]
14+
}
15+
}

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Set default behavior to automatically normalize line endings.
33
###############################################################################
44
* text=auto
5+
*.sh eol=lf
56

67
###############################################################################
78
# Set default behavior for command prompt diff.

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Setup .NET
2020
uses: actions/setup-dotnet@v3
2121
with:
22-
dotnet-version: 9.0.x
22+
dotnet-version: 10.0.x
2323
- name: Restore dependencies
2424
run: dotnet restore
2525
- name: Build

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*.userosscache
88
*.sln.docstates
99

10+
.claude/*.local.json
11+
1012
# User-specific files (MonoDevelop/Xamarin Studio)
1113
*.userprefs
1214

Directory.Packages.props

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6-
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
7-
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" />
8-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
6+
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
7+
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" />
8+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
99
<PackageVersion Include="System.Data.SqlClient" Version="4.8.5" />
10-
<PackageVersion Include="xunit" Version="2.9.2" />
11-
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
10+
<PackageVersion Include="xunit" Version="2.9.3" />
11+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
1212
</ItemGroup>
1313
</Project>

benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
5+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
66
</PropertyGroup>
77

88
<ItemGroup>

benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ public DataStructure(
2323

2424
}
2525

26-
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net60)]
27-
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net70)]
2826
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net80)]
2927
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net90)]
28+
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net10_0)]
3029
[RPlotExporter, RankColumn]
3130
[MemoryDiagnoser]
3231
public class EnumerableDataReaderBenchmark
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
4+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
55
</PropertyGroup>
66

77
</Project>

0 commit comments

Comments
 (0)