567 lines
21 KiB
C#
567 lines
21 KiB
C#
using Lampac.Engine;
|
|
using Lampac.Engine.CRON;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyModel;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using Shared;
|
|
using Shared.Engine;
|
|
using Shared.Models.Base;
|
|
using Shared.Models.SISI.Base;
|
|
using Shared.Models.SQL;
|
|
using Shared.PlaywrightCore;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
using System.Runtime.Loader;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
|
|
namespace Lampac
|
|
{
|
|
public class Program
|
|
{
|
|
#region static
|
|
public static string Runtime { get; private set; }
|
|
|
|
public static bool RuntimeCve2025_55315 { get; private set; }
|
|
|
|
public static AppReload appReload { get; private set; }
|
|
|
|
public static List<PortableExecutableReference> assemblieReferences { get; private set; }
|
|
|
|
static IHost _host;
|
|
public static bool _reload { get; private set; } = true;
|
|
|
|
public static List<(IPAddress prefix, int prefixLength)> cloudflare_ips = new List<(IPAddress prefix, int prefixLength)>();
|
|
|
|
static Timer _usersTimer, _kitTimer;
|
|
#endregion
|
|
|
|
#region Main
|
|
public static void Main(string[] args)
|
|
{
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
|
|
bool IsAssemblyLoaded(AssemblyName assemblyName)
|
|
{
|
|
foreach (var assembly in assemblies)
|
|
{
|
|
if (assembly.GetName().Name == assemblyName.Name)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
foreach (string dllPath in Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "runtimes", "references"), "*.dll"))
|
|
{
|
|
try
|
|
{
|
|
AssemblyName assemblyName = AssemblyName.GetAssemblyName(dllPath);
|
|
if (!IsAssemblyLoaded(assemblyName))
|
|
Assembly.LoadFrom(dllPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Failed to load {dllPath}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
AssemblyLoadContext.Default.Resolving += (context, assemblyName) =>
|
|
{
|
|
foreach (string name in new string[] { $"ru/{assemblyName.Name}", assemblyName.Name })
|
|
{
|
|
string assemblyPath = Path.Combine(AppContext.BaseDirectory, "runtimes", "references", name);
|
|
if (File.Exists(assemblyPath))
|
|
return context.LoadFromAssemblyPath(assemblyPath);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
Run(args);
|
|
}
|
|
#endregion
|
|
|
|
#region Run
|
|
static void Run(string[] args)
|
|
{
|
|
#region assemblieReferences
|
|
var dependencyContext = DependencyContext.Default;
|
|
var assemblies = dependencyContext.RuntimeLibraries
|
|
.SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext))
|
|
.Select(Assembly.Load)
|
|
.ToList();
|
|
|
|
assemblieReferences = assemblies.Select(assembly => MetadataReference.CreateFromFile(assembly.Location)).ToList();
|
|
#endregion
|
|
|
|
#region dotnet runtime
|
|
var asm = typeof(WebApplication).Assembly;
|
|
string info = asm.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
|
if (!string.IsNullOrWhiteSpace(info))
|
|
{
|
|
Runtime = info.Split('+', '-', ' ')[0];
|
|
RuntimeCve2025_55315 = Runtime is "9.0.1" or "9.0.2" or "9.0.3" or "9.0.4" or "9.0.5" or "9.0.6" or "9.0.7" or "9.0.8" or "9.0.9";
|
|
}
|
|
#endregion
|
|
|
|
CultureInfo.CurrentCulture = new CultureInfo("ru-RU");
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
|
|
var init = AppInit.conf;
|
|
var mods = init.BaseModule;
|
|
|
|
#region GC
|
|
var gc = init.GC;
|
|
if (gc != null && gc.enable)
|
|
{
|
|
if (gc.Concurrent.HasValue)
|
|
AppContext.SetSwitch("System.GC.Concurrent", gc.Concurrent.Value);
|
|
|
|
if (gc.ConserveMemory.HasValue)
|
|
AppContext.SetData("System.GC.ConserveMemory", gc.ConserveMemory.Value);
|
|
|
|
if (gc.HighMemoryPercent.HasValue)
|
|
AppContext.SetData("System.GC.HighMemoryPercent", gc.HighMemoryPercent.Value);
|
|
|
|
if (gc.RetainVM.HasValue)
|
|
AppContext.SetSwitch("System.GC.RetainVM", gc.RetainVM.Value);
|
|
}
|
|
#endregion
|
|
|
|
#region Http.onlog
|
|
Http.onlog += (e, log) =>
|
|
{
|
|
if (mods.nws)
|
|
NativeWebSocket.SendLog(log, "http");
|
|
|
|
if (mods.ws)
|
|
soks.SendLog(log, "http");
|
|
};
|
|
#endregion
|
|
|
|
#region RchClient
|
|
RchClient.hub += (e, req) =>
|
|
{
|
|
if (mods.nws)
|
|
_ = NativeWebSocket.SendRchRequestAsync(req.connectionId, req.rchId, req.url, req.data, req.headers, req.returnHeaders).ConfigureAwait(false);
|
|
|
|
if (mods.ws)
|
|
_ = soks.hubClients?.Client(req.connectionId)?.SendAsync("RchClient", req.rchId, req.url, req.data, req.headers, req.returnHeaders)?.ConfigureAwait(false);
|
|
};
|
|
#endregion
|
|
|
|
#region current.conf
|
|
if (!AppInit.conf.multiaccess)
|
|
{
|
|
Console.WriteLine(JsonConvert.SerializeObject(AppInit.conf, Formatting.Indented, new JsonSerializerSettings()
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
}));
|
|
}
|
|
|
|
File.WriteAllText("current.conf", JsonConvert.SerializeObject(AppInit.conf, Formatting.Indented));
|
|
#endregion
|
|
|
|
ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
|
|
ThreadPool.SetMinThreads(Math.Max(4096, workerThreads), Math.Max(1024, completionPortThreads));
|
|
|
|
#region passwd
|
|
if (!File.Exists("passwd"))
|
|
{
|
|
AppInit.rootPasswd = Guid.NewGuid().ToString();
|
|
File.WriteAllText("passwd", AppInit.rootPasswd);
|
|
}
|
|
else
|
|
{
|
|
AppInit.rootPasswd = File.ReadAllText("passwd");
|
|
AppInit.rootPasswd = Regex.Replace(AppInit.rootPasswd, "[\n\r\t ]+", "").Trim();
|
|
}
|
|
#endregion
|
|
|
|
#region vers.txt
|
|
if (!File.Exists("data/vers.txt"))
|
|
File.WriteAllText("data/vers.txt", BaseController.appversion);
|
|
|
|
if (!File.Exists("data/vers-minor.txt"))
|
|
File.WriteAllText("data/vers-minor.txt", "1");
|
|
#endregion
|
|
|
|
#region SQL
|
|
HybridCacheContext.Initialization();
|
|
ProxyLinkContext.Initialization();
|
|
|
|
if (init.chromium.enable || init.firefox.enable)
|
|
PlaywrightContext.Initialization();
|
|
|
|
if (mods.Sql.externalids)
|
|
ExternalidsContext.Initialization();
|
|
|
|
if (mods.Sql.sisi)
|
|
SisiContext.Initialization();
|
|
|
|
if (mods.Sql.syncUser)
|
|
SyncUserContext.Initialization();
|
|
#endregion
|
|
|
|
#region migration
|
|
if (Directory.Exists("cache/storage") || Directory.Exists("cache/bookmarks/sisi"))
|
|
{
|
|
Console.WriteLine("run migration");
|
|
|
|
#region cache/storage
|
|
if (mods.Sql.syncUser && Directory.Exists("cache/storage"))
|
|
{
|
|
string sourceDir = "cache/storage";
|
|
string targetDir = "database/storage";
|
|
|
|
void CopyAll(string source, string target)
|
|
{
|
|
Directory.CreateDirectory(target);
|
|
|
|
foreach (string file in Directory.GetFiles(source))
|
|
{
|
|
string destFile = Path.Combine(target, Path.GetFileName(file));
|
|
File.Copy(file, destFile, true);
|
|
}
|
|
|
|
foreach (string dir in Directory.GetDirectories(source))
|
|
{
|
|
string destDir = Path.Combine(target, Path.GetFileName(dir));
|
|
CopyAll(dir, destDir);
|
|
}
|
|
}
|
|
|
|
CopyAll(sourceDir, targetDir);
|
|
|
|
Directory.Move("cache/storage", "cache/storage.bak");
|
|
}
|
|
#endregion
|
|
|
|
#region cache/bookmarks/sisi
|
|
if (mods.Sql.sisi && Directory.Exists("cache/bookmarks/sisi"))
|
|
{
|
|
using (var sqlDb = new SisiContext())
|
|
{
|
|
var existing = new HashSet<string>(
|
|
sqlDb.bookmarks
|
|
.AsNoTracking()
|
|
.Select(i => $"{i.user}:{i.uid}")
|
|
);
|
|
|
|
foreach (string folder in Directory.GetDirectories("cache/bookmarks/sisi"))
|
|
{
|
|
string folderName = Path.GetFileName(folder);
|
|
|
|
foreach (string file in Directory.GetFiles(folder))
|
|
{
|
|
try
|
|
{
|
|
string md5user = folderName + Path.GetFileName(file);
|
|
var bookmarks = JsonConvert.DeserializeObject<List<PlaylistItem>>(File.ReadAllText(file));
|
|
|
|
if (bookmarks == null || bookmarks.Count == 0)
|
|
continue;
|
|
|
|
DateTime now = DateTime.UtcNow;
|
|
|
|
for (int i = 0; i < bookmarks.Count; i++)
|
|
{
|
|
var pl = bookmarks[i];
|
|
|
|
if (pl?.bookmark == null || string.IsNullOrEmpty(pl.bookmark.uid))
|
|
continue;
|
|
|
|
if (!existing.Add($"{md5user}:{pl.bookmark.uid}"))
|
|
continue;
|
|
|
|
sqlDb.bookmarks.Add(new SisiBookmarkSqlModel
|
|
{
|
|
user = md5user,
|
|
uid = pl.bookmark.uid,
|
|
created = now.AddSeconds(-i),
|
|
json = JsonConvert.SerializeObject(pl),
|
|
name = pl.name,
|
|
model = pl.model?.name
|
|
});
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
sqlDb.SaveChanges();
|
|
}
|
|
|
|
Directory.Move("cache/bookmarks/sisi", "cache/bookmarks/sisi.bak");
|
|
}
|
|
#endregion
|
|
}
|
|
#endregion
|
|
|
|
#region Playwright
|
|
if (init.chromium.enable || init.firefox.enable)
|
|
{
|
|
if (!init.multiaccess)
|
|
Environment.SetEnvironmentVariable("NODE_OPTIONS", "--max-old-space-size=256");
|
|
|
|
ThreadPool.QueueUserWorkItem(async _ =>
|
|
{
|
|
if (await PlaywrightBase.InitializationAsync())
|
|
{
|
|
if (init.chromium.enable)
|
|
_ = Chromium.CreateAsync().ConfigureAwait(false);
|
|
|
|
if (init.firefox.enable)
|
|
_ = Firefox.CreateAsync().ConfigureAwait(false);
|
|
}
|
|
});
|
|
|
|
Chromium.CronStart();
|
|
Firefox.CronStart();
|
|
}
|
|
#endregion
|
|
|
|
#region cloudflare_ips
|
|
ThreadPool.QueueUserWorkItem(async _ =>
|
|
{
|
|
string ips = await Http.Get("https://www.cloudflare.com/ips-v4");
|
|
if (ips == null || !ips.Contains("173.245."))
|
|
ips = File.Exists("data/cloudflare/ips-v4.txt") ? File.ReadAllText("data/cloudflare/ips-v4.txt") : null;
|
|
|
|
if (ips != null)
|
|
{
|
|
string ips_v6 = await Http.Get("https://www.cloudflare.com/ips-v6");
|
|
if (ips_v6 == null || !ips_v6.Contains("2400:cb00"))
|
|
ips_v6 = File.Exists("data/cloudflare/ips-v6.txt") ? File.ReadAllText("data/cloudflare/ips-v6.txt") : null;
|
|
|
|
if (ips_v6 != null)
|
|
{
|
|
foreach (string ip in (ips + "\n" + ips_v6).Split('\n'))
|
|
{
|
|
if (string.IsNullOrEmpty(ip) || !ip.Contains("/"))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
string[] ln = ip.Split('/');
|
|
cloudflare_ips.Add((IPAddress.Parse(ln[0].Trim()), int.Parse(ln[1].Trim())));
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"cloudflare_ips: {cloudflare_ips.Count}");
|
|
});
|
|
#endregion
|
|
|
|
#region fix update.sh
|
|
if (File.Exists("update.sh"))
|
|
{
|
|
var olds = new string[]
|
|
{
|
|
"02a7e97392e63b7e9e35a39ce475d6f8",
|
|
"6354eab8b101af90cb247fc8c977dd6b",
|
|
"b94b42ff158682661761a0b50a808a3b",
|
|
"97b0d657786b14e6a2faf7186de0556c",
|
|
"6b60a4d2173e99b11ecf4e792a24f598",
|
|
"cae6f0e79bbb2e6832922f25614d83a1",
|
|
"97b0d657786b14e6a2faf7186de0556c",
|
|
"cae6f0e79bbb2e6832922f25614d83a1",
|
|
"587794ca93c8d0318332858cf0e71e98",
|
|
"174ac2b94c5aa0e5ac086f843fd086a6",
|
|
"9c258d50e9eb06316efdf33de8b66dc3",
|
|
"bb4d6f2ba74b6a25dc3e4638c7f5282a",
|
|
"9607ae5805eaf5d06220298581a99beb",
|
|
"30078b973188c696273e10d6ef0ebbb2",
|
|
"92f5e2e03d2cc2697f2ee00becdb4696",
|
|
"b565c7e163485b8f8cc258b95f2891b6",
|
|
"ec6659f1f91f1f6ec0c734ff2111c7d7",
|
|
"43c8f2a9fe75a3ce9c109e67bc552ace",
|
|
"1ba06699c494190bce44f6786a24b96a"
|
|
};
|
|
|
|
try
|
|
{
|
|
if (olds.Contains(CrypTo.md5(File.ReadAllText("update.sh"))))
|
|
{
|
|
ThreadPool.QueueUserWorkItem(async _ =>
|
|
{
|
|
string new_update = await Http.Get("https://raw.githubusercontent.com/lampac-talks/lampac/refs/heads/main/update.sh");
|
|
if (new_update != null && new_update.Contains("DEST=\"/home/lampac\""))
|
|
File.WriteAllText("update.sh", new_update);
|
|
});
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
#endregion
|
|
|
|
CacheCron.Run();
|
|
|
|
if (mods.kurwaCron)
|
|
KurwaCron.Run();
|
|
|
|
PluginsCron.Run();
|
|
SyncCron.Run();
|
|
|
|
if (AppInit.modules?.FirstOrDefault(i => i.dll == "DLNA.dll" && i.enable) != null)
|
|
TrackersCron.Run();
|
|
|
|
LampaCron.Run();
|
|
|
|
appReload = new AppReload();
|
|
appReload.InkvReload = () =>
|
|
{
|
|
_host.StopAsync();
|
|
AppInit.LoadModules();
|
|
};
|
|
|
|
_usersTimer = new Timer(UpdateUsersDb, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
|
|
_kitTimer = new Timer(UpdateKitDb, null, TimeSpan.Zero, TimeSpan.FromSeconds(Math.Max(5, AppInit.conf.kit.cacheToSeconds)));
|
|
|
|
while (_reload)
|
|
{
|
|
_host = CreateHostBuilder(args).Build();
|
|
_reload = false;
|
|
_host.Run();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region CreateHostBuilder
|
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
Host.CreateDefaultBuilder(args)
|
|
.ConfigureWebHostDefaults(webBuilder =>
|
|
{
|
|
webBuilder.UseKestrel(op =>
|
|
{
|
|
op.AddServerHeader = false;
|
|
|
|
if (AppInit.conf.listen.keepalive.HasValue && AppInit.conf.listen.keepalive.Value > 0)
|
|
op.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(AppInit.conf.listen.keepalive.Value);
|
|
|
|
op.ConfigureEndpointDefaults(endpointOptions =>
|
|
{
|
|
if (AppInit.conf.listen.endpointDefaultsProtocols.HasValue)
|
|
endpointOptions.Protocols = AppInit.conf.listen.endpointDefaultsProtocols.Value;
|
|
});
|
|
|
|
if (string.IsNullOrEmpty(AppInit.conf.listen.sock) && string.IsNullOrEmpty(AppInit.conf.listen.ip))
|
|
{
|
|
op.Listen(IPAddress.Parse("127.0.0.1"), 9118);
|
|
}
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(AppInit.conf.listen.sock))
|
|
{
|
|
if (File.Exists($"/var/run/{AppInit.conf.listen.sock}.sock"))
|
|
File.Delete($"/var/run/{AppInit.conf.listen.sock}.sock");
|
|
|
|
op.ListenUnixSocket($"/var/run/{AppInit.conf.listen.sock}.sock");
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(AppInit.conf.listen.ip))
|
|
op.Listen(AppInit.conf.listen.ip == "any" ? IPAddress.Any : AppInit.conf.listen.ip == "broadcast" ? IPAddress.Broadcast : IPAddress.Parse(AppInit.conf.listen.ip), AppInit.conf.listen.port);
|
|
}
|
|
})
|
|
.UseStartup<Startup>();
|
|
});
|
|
#endregion
|
|
|
|
|
|
#region UpdateUsersDb
|
|
static int _updateUsersDb = 0;
|
|
static string _usersKeyUpdate = string.Empty;
|
|
|
|
static void UpdateUsersDb(object state)
|
|
{
|
|
if (Interlocked.Exchange(ref _updateUsersDb, 1) == 1)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (File.Exists("users.json"))
|
|
{
|
|
var lastWriteTime = File.GetLastWriteTime("users.json");
|
|
|
|
string keyUpdate = $"{AppInit.conf?.guid}:{AppInit.conf?.accsdb?.users?.Count ?? 0}:{lastWriteTime}";
|
|
if (keyUpdate == _usersKeyUpdate)
|
|
return;
|
|
|
|
foreach (var user in JsonConvert.DeserializeObject<List<AccsUser>>(File.ReadAllText("users.json")))
|
|
{
|
|
try
|
|
{
|
|
var find = AppInit.conf.accsdb.findUser(user.id ?? user.ids?.First());
|
|
if (find != null)
|
|
{
|
|
find.id = user.id;
|
|
find.ids = user.ids;
|
|
find.group = user.group;
|
|
find.IsPasswd = user.IsPasswd;
|
|
find.expires = user.expires;
|
|
find.ban = user.ban;
|
|
find.ban_msg = user.ban_msg;
|
|
find.comment = user.comment;
|
|
find.@params = user.@params;
|
|
}
|
|
else
|
|
{
|
|
AppInit.conf.accsdb.users.Add(user);
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
_usersKeyUpdate = keyUpdate;
|
|
}
|
|
}
|
|
catch { }
|
|
finally
|
|
{
|
|
Volatile.Write(ref _updateUsersDb, 0);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region UpdateKitDb
|
|
static int _updateKitDb = 0;
|
|
|
|
async static void UpdateKitDb(object state)
|
|
{
|
|
if (Interlocked.Exchange(ref _updateKitDb, 1) == 1)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (AppInit.conf.kit.enable && AppInit.conf.kit.IsAllUsersPath && !string.IsNullOrEmpty(AppInit.conf.kit.path))
|
|
{
|
|
var users = await Http.Get<Dictionary<string, JObject>>(AppInit.conf.kit.path);
|
|
if (users != null)
|
|
AppInit.conf.kit.allUsers = users;
|
|
}
|
|
}
|
|
catch { }
|
|
finally
|
|
{
|
|
Volatile.Write(ref _updateKitDb, 0);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|