package ru.kirillius.pf.sdn; import lombok.Getter; import lombok.SneakyThrows; import ru.kirillius.pf.sdn.External.API.Components.FRR; import ru.kirillius.pf.sdn.External.API.Components.OVPN; import ru.kirillius.pf.sdn.External.API.Components.TDNS; import ru.kirillius.pf.sdn.External.API.HEInfoProvider; import ru.kirillius.pf.sdn.core.Auth.AuthManager; import ru.kirillius.pf.sdn.core.Auth.TokenStorage; import ru.kirillius.pf.sdn.core.*; import ru.kirillius.pf.sdn.core.Component; import ru.kirillius.pf.sdn.core.Networking.ASInfoService; import ru.kirillius.pf.sdn.core.Networking.NetworkManager; import ru.kirillius.pf.sdn.core.Subscription.SubscriptionManager; import ru.kirillius.pf.sdn.core.Util.Wait; import ru.kirillius.pf.sdn.web.HTTPServer; import ru.kirillius.utils.logging.SystemLogger; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; public class App implements Context, Closeable { private final static File configFile = new File("config.json"); protected final static String CTX = App.class.getSimpleName(); static { SystemLogger.initializeLogging(Level.INFO, List.of(InMemoryLogHandler.class)); SystemLogger.setExceptionDumping(true); } private final AtomicBoolean shouldRestart = new AtomicBoolean(false); private final AtomicBoolean running = new AtomicBoolean(true); @Getter private final NetworkManager networkManager; @Getter private Config config; @Getter private final AuthManager authManager; @Getter private final HTTPServer server; @Getter private final ASInfoService ASInfoService; @Getter private final SubscriptionManager subscriptionManager; @Getter private final UpdateManager updateManager; @Getter private final TokenStorage tokenStorage; @Getter private final ContextEventsHandler EventsHandler = new ContextEventsHandler(); private final List> loadedComponents = new ArrayList<>(); @SneakyThrows public App(File configFile) { try { config = Config.load(configFile); } catch (IOException e) { config = new Config(); try { Config.store(config, configFile); } catch (IOException ex) { throw new RuntimeException(ex); } } authManager = new AuthManager(this); ASInfoService = new ASInfoService(); ASInfoService.setProvider(new HEInfoProvider(this)); networkManager = new NetworkManager(this); networkManager.getInputResources().add(config.getCustomResources()); subscriptionManager = new SubscriptionManager(this); updateManager = new UpdateManager(this); tokenStorage = new TokenStorage(this); subscribe(); updateManager.start(); initComponents(); server = new HTTPServer(this); if (config.getPasswordHash() == null || config.getPasswordHash().isEmpty()) { SystemLogger.error("There is no password for admin. Setting default password: admin", CTX); getAuthManager().updatePassword("admin"); } getSubscriptionManager().triggerUpdate(); } public static void main(String[] args) { var restart = false; do { try (var app = new App(configFile)) { Wait.when(app.running::get); restart = app.shouldRestart.get(); } catch (Exception e) { SystemLogger.error("Unhandled error", CTX, e); } } while (restart); } public void triggerRestart() { SystemLogger.message("Restarting app", CTX); running.set(false); shouldRestart.set(true); } public void triggerShutdown() { SystemLogger.message("Shutting down app", CTX); running.set(false); } public Collection>> getComponentClasses() { return List.of(FRR.class, OVPN.class, TDNS.class); } public void initComponents() { var enabledPlugins = config.getEnabledComponents(); (List.copyOf(loadedComponents)).forEach(plugin -> { if (!enabledPlugins.contains(plugin.getClass())) { SystemLogger.message("Unloading plugin: " + plugin.getClass().getSimpleName(), CTX); try { plugin.close(); } catch (IOException e) { SystemLogger.error("Error on plugin unload", CTX, e); } finally { loadedComponents.remove(plugin); } } }); var loadedClasses = loadedComponents.stream().map(plugin -> plugin.getClass()).toList(); enabledPlugins.forEach(pluginClass -> { if (loadedClasses.contains(pluginClass)) { return; } SystemLogger.message("Loading plugin: " + pluginClass.getSimpleName(), CTX); var plugin = Component.loadPlugin(pluginClass, this); loadedComponents.add(plugin); }); } private void subscribe() { var eventsHandler = getEventsHandler(); eventsHandler.getSubscriptionsUpdateEvent().add(bundle -> { var manager = getNetworkManager(); var inputResources = getNetworkManager().getInputResources(); inputResources.clear(); inputResources.add(config.getCustomResources()); inputResources.add(bundle); manager.triggerUpdate(false); }); } @Override public Component getPluginInstance(Class> pluginClass) { return loadedComponents.stream().filter(plugin -> plugin.getClass().equals(pluginClass)).findFirst().orElse(null); } @Override public void close() throws IOException { loadedComponents.forEach(plugin -> { try { plugin.close(); } catch (IOException e) { SystemLogger.error("Error closing plugin", CTX, e); } }); ASInfoService.close(); networkManager.close(); try { server.stop(); } catch (Exception e) { SystemLogger.error("Error stopping server", CTX, e); } } }