Add cs.money worker stack with per-worker IPRoyal residential proxy
Brings up the pull-model scraper: the .NET C2 hands skin+wear jobs to Python nodriver workers that scrape cs.money and post results back, plus the supporting Core/EFCore data model, migrations, and docker-compose orchestration. IPRoyal proxying lets workers scale horizontally with a distinct residential exit IP each: every worker process mints its own sticky session at startup, and an in-process forwarding proxy injects the gateway auth so Chromium talks only to an auth-free localhost endpoint (zero CDP). On a Cloudflare challenge a worker rotates to a fresh session/IP and re-warms. Verified end-to-end against live IPRoyal: distinct US residential exits per worker and IP rotation on demand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
72
BlueLaminate/BlueLaminate.Cli/Commands/ProbeProxyCommand.cs
Normal file
72
BlueLaminate/BlueLaminate.Cli/Commands/ProbeProxyCommand.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using BlueLaminate.Scraper.Proxies;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.CommandLine;
|
||||
|
||||
namespace BlueLaminate.Cli.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// <c>probe-proxy</c>: launch a non-headless Edge browser through the IPRoyal
|
||||
/// residential proxy and print the exit IP, to confirm authentication works and
|
||||
/// the IP is genuinely residential. Reads IPROYAL_USERNAME / IPROYAL_PASSWORD.
|
||||
/// Costs a few KB, so it's the right first check against a metered plan.
|
||||
/// </summary>
|
||||
internal static class ProbeProxyCommand
|
||||
{
|
||||
public static Command Build(IHost host)
|
||||
{
|
||||
var countryOption = new Option<string?>("--country")
|
||||
{
|
||||
Description = "Optional ISO country code(s) for the exit IP, e.g. \"us\" or \"us,gb\". "
|
||||
+ "Default: random.",
|
||||
};
|
||||
var rotatingOption = new Option<bool>("--rotating")
|
||||
{
|
||||
Description = "Use a rotating exit IP instead of a pinned (sticky) session.",
|
||||
};
|
||||
|
||||
var command = new Command(
|
||||
"probe-proxy",
|
||||
"Launch non-headless Edge through the IPRoyal residential proxy and print the exit IP "
|
||||
+ "to confirm auth works and the IP is residential. Reads IPROYAL_USERNAME / IPROYAL_PASSWORD.")
|
||||
{
|
||||
countryOption,
|
||||
rotatingOption,
|
||||
};
|
||||
|
||||
command.SetAction((parseResult, ct) => RunAsync(
|
||||
host,
|
||||
parseResult.GetValue(countryOption),
|
||||
parseResult.GetValue(rotatingOption),
|
||||
ct));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static async Task<int> RunAsync(
|
||||
IHost host, string? country, bool rotating, CancellationToken ct)
|
||||
{
|
||||
using var scope = host.Services.CreateScope();
|
||||
|
||||
try
|
||||
{
|
||||
var probe = scope.ServiceProvider.GetRequiredService<ProxyProbe>();
|
||||
var info = await probe.RunAsync(new ProxyRequest(Country: country, Sticky: !rotating));
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($" Exit IP : {info.Ip}");
|
||||
Console.WriteLine($" Location: {info.City}, {info.Region}, {info.Country}");
|
||||
Console.WriteLine($" Org/ASN : {info.Org}");
|
||||
Console.WriteLine($" Hostname: {info.Hostname ?? "—"}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(
|
||||
"Check Org/ASN: a consumer ISP = residential; a hosting provider = datacenter.");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Proxy probe failed: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user