1 /** 2 Copyright: © 2013 rejectedsoftware e.K. 3 License: Subject to the terms of the GNU GPLv3 license, as written in the included LICENSE.txt file. 4 Authors: Sönke Ludwig 5 */ 6 module app; 7 8 import dubregistry.dbcontroller; 9 import dubregistry.mirror; 10 import dubregistry.repositories.bitbucket; 11 import dubregistry.repositories.github; 12 import dubregistry.repositories.gitlab; 13 import dubregistry.registry; 14 import dubregistry.web; 15 import dubregistry.api; 16 17 import std.algorithm : sort; 18 import std.process : environment; 19 import std.file; 20 import std.path; 21 import std.traits : isIntegral; 22 import userman.db.controller : UserManController, createUserManController; 23 import userman.userman : UserManSettings; 24 import userman.web; 25 import vibe.d; 26 27 28 Task s_checkTask; 29 DubRegistry s_registry; 30 DubRegistryWebFrontend s_web; 31 string s_mirror; 32 33 void checkForNewVersions() 34 { 35 if (s_mirror.length) s_registry.mirrorRegistry(s_mirror); 36 else s_registry.updatePackages(); 37 } 38 39 void startMonitoring() 40 { 41 void monitorPackages() 42 { 43 sleep(1.seconds()); // give the cache a chance to warm up first 44 while(true){ 45 checkForNewVersions; 46 sleep(30.minutes()); 47 } 48 } 49 s_checkTask = runTask(&monitorPackages); 50 } 51 52 version (linux) private immutable string certPath; 53 54 shared static this() 55 { 56 enum debianCA = "/etc/ssl/certs/ca-certificates.crt"; 57 enum redhatCA = "/etc/pki/tls/certs/ca-bundle.crt"; 58 certPath = redhatCA.exists ? redhatCA : debianCA; 59 } 60 61 // generate dummy data for e.g. Heroku's preview apps 62 void defaultInit(UserManController userMan, DubRegistry registry) 63 { 64 if (environment.get("GENERATE_DEFAULT_DATA", "0") == "1" && 65 registry.getPackageDump().empty && userMan.getUserCount() == 0) 66 { 67 logInfo("'GENERATE_DEFAULT_DATA' is set and an empty database has been detected. Inserting dummy data."); 68 auto userId = userMan.registerUser("dummy@dummy.org", "dummyuser", 69 "Dummy User", "test1234"); 70 auto packages = [ 71 "https://github.com/libmir/mir-algorithm", 72 "https://github.com/libmir/mir-runtime", 73 "https://github.com/libmir/mir-random", 74 "https://github.com/libmir/mir-core", 75 "https://gitlab.com/WebFreak001/bancho-irc", 76 ]; 77 foreach (url; packages) 78 { 79 DbRepository repo; 80 repo.parseURL(URL(url)); 81 registry.addPackage(repo, userId); 82 } 83 } 84 } 85 86 struct AppConfig 87 { 88 string ghuser, ghpassword, 89 glurl, glauth, 90 bbuser, bbpassword; 91 92 bool enforceCertificateTrust = false; 93 94 string serviceName = "DUB - The D package registry"; 95 string serviceURL = "https://code.dlang.org/"; 96 string serviceEmail = "noreply@rejectedsoftware.com"; 97 98 string mailServer; 99 ushort mailServerPort; 100 SMTPConnectionType mailConnectionType; 101 string mailClientName; 102 string mailUser; 103 string mailPassword; 104 105 106 void init() 107 { 108 import dub.internal.utils : jsonFromFile; 109 auto regsettingsjson = jsonFromFile(NativePath("settings.json"), true); 110 // TODO: use UDAs instead 111 static immutable variables = [ 112 ["ghuser", "github-user"], 113 ["ghpassword", "github-password"], 114 ["glurl", "gitlab-url"], 115 ["glauth", "gitlab-auth"], 116 ["bbuser", "bitbucket-user"], 117 ["bbpassword", "bitbucket-password"], 118 ["enforceCertificateTrust", "enforce-certificate-trust"], 119 ["serviceName", "service-name"], 120 ["serviceURL", "service-url"], 121 ["serviceEmail", "service-email"], 122 ["mailServer", "mail-server"], 123 ["mailServerPort", "mail-server-port"], 124 ["mailConnectionType", "mail-connection-type"], 125 ["mailClientName", "mail-client-name"], 126 ["mailUser", "mail-user"], 127 ["mailPassword", "mail-password"], 128 ]; 129 static foreach (var; variables) {{ 130 alias T = typeof(__traits(getMember, this, var[0])); 131 132 T val = __traits(getMember, this, var[0]); 133 134 if (var[1] in regsettingsjson) { 135 static if (is(T == bool)) val = regsettingsjson[var[1]].get!bool; 136 else static if (isIntegral!T && !is(T == enum)) val = regsettingsjson[var[1]].get!int.to!T; 137 else val = regsettingsjson[var[1]].get!string.to!T; 138 } else { 139 // fallback to environment variables 140 auto ev = environment.get(var[1].replace("-", "_").toUpper); 141 if (ev.length) val = ev.to!T; 142 } 143 144 __traits(getMember, this, var[0]) = val; 145 }} 146 } 147 } 148 149 void main() 150 { 151 bool noMonitoring; 152 setLogFile("log.txt", LogLevel.diagnostic); 153 154 version (linux) version (DMD) 155 { 156 // register memory error handler on heroku 157 if ("DYNO" in environment) 158 { 159 import etc.linux.memoryerror : registerMemoryErrorHandler; 160 registerMemoryErrorHandler(); 161 } 162 } 163 164 string hostname = "code.dlang.org"; 165 166 readOption("mirror", &s_mirror, "URL of a package registry that this instance should mirror (WARNING: will overwrite local database!)"); 167 readOption("hostname", &hostname, "Domain name of this instance (default: code.dlang.org)"); 168 readOption("no-monitoring", &noMonitoring, "Don't periodically monitor for updates (for local development)"); 169 170 // validate provided mirror URL 171 if (s_mirror.length) 172 validateMirrorURL(s_mirror); 173 174 AppConfig appConfig; 175 appConfig.init(); 176 177 version (linux) { 178 if (appConfig.enforceCertificateTrust) { 179 logInfo("Enforcing certificate trust."); 180 HTTPClient.setTLSSetupCallback((ctx) { 181 ctx.useTrustedCertificateFile(certPath); 182 ctx.peerValidationMode = TLSPeerValidationMode.trustedCert; 183 }); 184 } 185 } 186 187 GithubRepository.register(appConfig.ghuser, appConfig.ghpassword); 188 BitbucketRepository.register(appConfig.bbuser, appConfig.bbpassword); 189 if (appConfig.glurl.length) GitLabRepository.register(appConfig.glauth, appConfig.glurl); 190 191 auto router = new URLRouter; 192 if (s_mirror.length) router.any("*", (req, res) { req.params["mirror"] = s_mirror; }); 193 if (!noMonitoring) 194 router.get("*", (req, res) @trusted { if (!s_checkTask.running) startMonitoring(); }); 195 196 // init mongo 197 import dubregistry.mongodb : databaseName, mongoSettings; 198 mongoSettings(); 199 200 // VPM registry 201 auto regsettings = new DubRegistrySettings; 202 regsettings.databaseName = databaseName; 203 s_registry = new DubRegistry(regsettings); 204 205 UserManController userdb; 206 207 if (!s_mirror.length) { 208 // user management 209 auto udbsettings = new UserManSettings; 210 udbsettings.serviceName = appConfig.serviceName; 211 udbsettings.serviceURL = URL(appConfig.serviceURL); 212 udbsettings.serviceEmail = appConfig.serviceEmail; 213 udbsettings.databaseURL = environment.get("MONGODB_URI", environment.get("MONGO_URI", "mongodb://127.0.0.1:27017/vpmreg")); 214 udbsettings.requireActivation = false; 215 216 udbsettings.mailSettings.host = appConfig.mailServer; 217 udbsettings.mailSettings.port = appConfig.mailServerPort; 218 udbsettings.mailSettings.connectionType = appConfig.mailConnectionType; 219 udbsettings.mailSettings.localname = appConfig.mailClientName; 220 udbsettings.mailSettings.username = appConfig.mailUser; 221 udbsettings.mailSettings.password = appConfig.mailPassword; 222 if (appConfig.mailUser.length || appConfig.mailPassword.length) 223 udbsettings.mailSettings.authType = SMTPAuthType.plain; 224 udbsettings.mailSettings.tlsValidationMode = TLSPeerValidationMode.validCert; 225 version (linux) { 226 if (appConfig.enforceCertificateTrust) { 227 udbsettings.mailSettings.tlsValidationMode = TLSPeerValidationMode.trustedCert; 228 udbsettings.mailSettings.tlsContextSetup = (ctx) { 229 ctx.useTrustedCertificateFile(certPath); 230 }; 231 } 232 } 233 234 userdb = createUserManController(udbsettings); 235 } 236 237 // web front end 238 s_web = router.registerDubRegistryWebFrontend(s_registry, userdb); 239 router.registerDubRegistryAPI(s_registry); 240 241 // check whether dummy data should be loaded 242 defaultInit(userdb, s_registry); 243 244 // start the web server 245 auto settings = new HTTPServerSettings; 246 settings.hostName = hostname; 247 settings.bindAddresses = ["127.0.0.1"]; 248 settings.port = 8005; 249 settings.sessionStore = new MemorySessionStore; 250 settings.useCompressionIfPossible = true; 251 readOption("bind", &settings.bindAddresses[0], "Sets the address used for serving."); 252 readOption("port|p", &settings.port, "Sets the port used for serving."); 253 254 listenHTTP(settings, router); 255 256 // poll github for new project versions 257 if (!noMonitoring) 258 startMonitoring(); 259 runApplication(); 260 }