main.go 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634
  1. // Copyright (C) 2018 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "crypto/tls"
  10. "database/sql"
  11. "database/sql/driver"
  12. "encoding/json"
  13. "errors"
  14. "fmt"
  15. "html/template"
  16. "io"
  17. "io/ioutil"
  18. "log"
  19. "net"
  20. "net/http"
  21. "os"
  22. "regexp"
  23. "sort"
  24. "strings"
  25. "sync"
  26. "time"
  27. "unicode"
  28. "github.com/lib/pq"
  29. "github.com/oschwald/geoip2-golang"
  30. )
  31. var (
  32. useHTTP = os.Getenv("UR_USE_HTTP") != ""
  33. debug = os.Getenv("UR_DEBUG") != ""
  34. keyFile = getEnvDefault("UR_KEY_FILE", "key.pem")
  35. certFile = getEnvDefault("UR_CRT_FILE", "crt.pem")
  36. dbConn = getEnvDefault("UR_DB_URL", "postgres://user:password@localhost/ur?sslmode=disable")
  37. listenAddr = getEnvDefault("UR_LISTEN", "0.0.0.0:8443")
  38. geoIPPath = getEnvDefault("UR_GEOIP", "GeoLite2-City.mmdb")
  39. tpl *template.Template
  40. compilerRe = regexp.MustCompile(`\(([A-Za-z0-9()., -]+) \w+-\w+(?:| android| default)\) ([\[email protected]]+)`)
  41. progressBarClass = []string{"", "progress-bar-success", "progress-bar-info", "progress-bar-warning", "progress-bar-danger"}
  42. featureOrder = []string{"Various", "Folder", "Device", "Connection", "GUI"}
  43. knownVersions = []string{"v2", "v3"}
  44. )
  45. var funcs = map[string]interface{}{
  46. "commatize": commatize,
  47. "number": number,
  48. "proportion": proportion,
  49. "counter": func() *counter {
  50. return &counter{}
  51. },
  52. "progressBarClassByIndex": func(a int) string {
  53. return progressBarClass[a%len(progressBarClass)]
  54. },
  55. "slice": func(numParts, whichPart int, input []feature) []feature {
  56. var part []feature
  57. perPart := (len(input) / numParts) + len(input)%2
  58. parts := make([][]feature, 0, numParts)
  59. for len(input) >= perPart {
  60. part, input = input[:perPart], input[perPart:]
  61. parts = append(parts, part)
  62. }
  63. if len(input) > 0 {
  64. parts = append(parts, input[:])
  65. }
  66. return parts[whichPart-1]
  67. },
  68. }
  69. func getEnvDefault(key, def string) string {
  70. if val := os.Getenv(key); val != "" {
  71. return val
  72. }
  73. return def
  74. }
  75. type IntMap map[string]int
  76. func (p IntMap) Value() (driver.Value, error) {
  77. return json.Marshal(p)
  78. }
  79. func (p *IntMap) Scan(src interface{}) error {
  80. source, ok := src.([]byte)
  81. if !ok {
  82. return errors.New("Type assertion .([]byte) failed.")
  83. }
  84. var i map[string]int
  85. err := json.Unmarshal(source, &i)
  86. if err != nil {
  87. return err
  88. }
  89. *p = i
  90. return nil
  91. }
  92. type report struct {
  93. Received time.Time // Only from DB
  94. UniqueID string
  95. Version string
  96. LongVersion string
  97. Platform string
  98. NumFolders int
  99. NumDevices int
  100. TotFiles int
  101. FolderMaxFiles int
  102. TotMiB int
  103. FolderMaxMiB int
  104. MemoryUsageMiB int
  105. SHA256Perf float64
  106. MemorySize int
  107. // v2 fields
  108. URVersion int
  109. NumCPU int
  110. FolderUses struct {
  111. SendOnly int
  112. ReceiveOnly int
  113. IgnorePerms int
  114. IgnoreDelete int
  115. AutoNormalize int
  116. SimpleVersioning int
  117. ExternalVersioning int
  118. StaggeredVersioning int
  119. TrashcanVersioning int
  120. }
  121. DeviceUses struct {
  122. Introducer int
  123. CustomCertName int
  124. CompressAlways int
  125. CompressMetadata int
  126. CompressNever int
  127. DynamicAddr int
  128. StaticAddr int
  129. }
  130. Announce struct {
  131. GlobalEnabled bool
  132. LocalEnabled bool
  133. DefaultServersDNS int
  134. DefaultServersIP int
  135. OtherServers int
  136. }
  137. Relays struct {
  138. Enabled bool
  139. DefaultServers int
  140. OtherServers int
  141. }
  142. UsesRateLimit bool
  143. UpgradeAllowedManual bool
  144. UpgradeAllowedAuto bool
  145. // V2.5 fields (fields that were in v2 but never added to the database
  146. UpgradeAllowedPre bool
  147. RescanIntvs pq.Int64Array
  148. // v3 fields
  149. Uptime int
  150. NATType string
  151. AlwaysLocalNets bool
  152. CacheIgnoredFiles bool
  153. OverwriteRemoteDeviceNames bool
  154. ProgressEmitterEnabled bool
  155. CustomDefaultFolderPath bool
  156. WeakHashSelection string
  157. CustomTrafficClass bool
  158. CustomTempIndexMinBlocks bool
  159. TemporariesDisabled bool
  160. TemporariesCustom bool
  161. LimitBandwidthInLan bool
  162. CustomReleaseURL bool
  163. RestartOnWakeup bool
  164. CustomStunServers bool
  165. FolderUsesV3 struct {
  166. ScanProgressDisabled int
  167. ConflictsDisabled int
  168. ConflictsUnlimited int
  169. ConflictsOther int
  170. DisableSparseFiles int
  171. DisableTempIndexes int
  172. AlwaysWeakHash int
  173. CustomWeakHashThreshold int
  174. FsWatcherEnabled int
  175. PullOrder IntMap
  176. FilesystemType IntMap
  177. FsWatcherDelays pq.Int64Array
  178. }
  179. GUIStats struct {
  180. Enabled int
  181. UseTLS int
  182. UseAuth int
  183. InsecureAdminAccess int
  184. Debugging int
  185. InsecureSkipHostCheck int
  186. InsecureAllowFrameLoading int
  187. ListenLocal int
  188. ListenUnspecified int
  189. Theme IntMap
  190. }
  191. BlockStats struct {
  192. Total int
  193. Renamed int
  194. Reused int
  195. Pulled int
  196. CopyOrigin int
  197. CopyOriginShifted int
  198. CopyElsewhere int
  199. }
  200. TransportStats IntMap
  201. IgnoreStats struct {
  202. Lines int
  203. Inverts int
  204. Folded int
  205. Deletable int
  206. Rooted int
  207. Includes int
  208. EscapedIncludes int
  209. DoubleStars int
  210. Stars int
  211. }
  212. // V3 fields added late in the RC
  213. WeakHashEnabled bool
  214. // Generated
  215. Date string
  216. Address string
  217. }
  218. func (r *report) Validate() error {
  219. if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
  220. return fmt.Errorf("missing required field")
  221. }
  222. if len(r.Date) != 8 {
  223. return fmt.Errorf("date not initialized")
  224. }
  225. // Some fields may not be null.
  226. if r.RescanIntvs == nil {
  227. r.RescanIntvs = []int64{}
  228. }
  229. if r.FolderUsesV3.FsWatcherDelays == nil {
  230. r.FolderUsesV3.FsWatcherDelays = []int64{}
  231. }
  232. return nil
  233. }
  234. func (r *report) FieldPointers() []interface{} {
  235. // All the fields of the report, in the same order as the database fields.
  236. return []interface{}{
  237. &r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform,
  238. &r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles,
  239. &r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf,
  240. &r.MemorySize, &r.Date,
  241. // V2
  242. &r.URVersion, &r.NumCPU, &r.FolderUses.SendOnly, &r.FolderUses.IgnorePerms,
  243. &r.FolderUses.IgnoreDelete, &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer,
  244. &r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways,
  245. &r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever,
  246. &r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr,
  247. &r.Announce.GlobalEnabled, &r.Announce.LocalEnabled,
  248. &r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP,
  249. &r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers,
  250. &r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual,
  251. &r.UpgradeAllowedAuto, &r.FolderUses.SimpleVersioning,
  252. &r.FolderUses.ExternalVersioning, &r.FolderUses.StaggeredVersioning,
  253. &r.FolderUses.TrashcanVersioning,
  254. // V2.5
  255. &r.UpgradeAllowedPre, &r.RescanIntvs,
  256. // V3
  257. &r.Uptime, &r.NATType, &r.AlwaysLocalNets, &r.CacheIgnoredFiles,
  258. &r.OverwriteRemoteDeviceNames, &r.ProgressEmitterEnabled, &r.CustomDefaultFolderPath,
  259. &r.WeakHashSelection, &r.CustomTrafficClass, &r.CustomTempIndexMinBlocks,
  260. &r.TemporariesDisabled, &r.TemporariesCustom, &r.LimitBandwidthInLan,
  261. &r.CustomReleaseURL, &r.RestartOnWakeup, &r.CustomStunServers,
  262. &r.FolderUsesV3.ScanProgressDisabled, &r.FolderUsesV3.ConflictsDisabled,
  263. &r.FolderUsesV3.ConflictsUnlimited, &r.FolderUsesV3.ConflictsOther,
  264. &r.FolderUsesV3.DisableSparseFiles, &r.FolderUsesV3.DisableTempIndexes,
  265. &r.FolderUsesV3.AlwaysWeakHash, &r.FolderUsesV3.CustomWeakHashThreshold,
  266. &r.FolderUsesV3.FsWatcherEnabled,
  267. &r.FolderUsesV3.PullOrder, &r.FolderUsesV3.FilesystemType,
  268. &r.FolderUsesV3.FsWatcherDelays,
  269. &r.GUIStats.Enabled, &r.GUIStats.UseTLS, &r.GUIStats.UseAuth,
  270. &r.GUIStats.InsecureAdminAccess,
  271. &r.GUIStats.Debugging, &r.GUIStats.InsecureSkipHostCheck,
  272. &r.GUIStats.InsecureAllowFrameLoading, &r.GUIStats.ListenLocal,
  273. &r.GUIStats.ListenUnspecified, &r.GUIStats.Theme,
  274. &r.BlockStats.Total, &r.BlockStats.Renamed,
  275. &r.BlockStats.Reused, &r.BlockStats.Pulled, &r.BlockStats.CopyOrigin,
  276. &r.BlockStats.CopyOriginShifted, &r.BlockStats.CopyElsewhere,
  277. &r.TransportStats,
  278. &r.IgnoreStats.Lines, &r.IgnoreStats.Inverts, &r.IgnoreStats.Folded,
  279. &r.IgnoreStats.Deletable, &r.IgnoreStats.Rooted, &r.IgnoreStats.Includes,
  280. &r.IgnoreStats.EscapedIncludes, &r.IgnoreStats.DoubleStars, &r.IgnoreStats.Stars,
  281. // V3 added late in the RC
  282. &r.WeakHashEnabled,
  283. &r.Address,
  284. // Receive only folders
  285. &r.FolderUses.ReceiveOnly,
  286. }
  287. }
  288. func (r *report) FieldNames() []string {
  289. // The database fields that back this struct in PostgreSQL
  290. return []string{
  291. // V1
  292. "Received",
  293. "UniqueID",
  294. "Version",
  295. "LongVersion",
  296. "Platform",
  297. "NumFolders",
  298. "NumDevices",
  299. "TotFiles",
  300. "FolderMaxFiles",
  301. "TotMiB",
  302. "FolderMaxMiB",
  303. "MemoryUsageMiB",
  304. "SHA256Perf",
  305. "MemorySize",
  306. "Date",
  307. // V2
  308. "ReportVersion",
  309. "NumCPU",
  310. "FolderRO",
  311. "FolderIgnorePerms",
  312. "FolderIgnoreDelete",
  313. "FolderAutoNormalize",
  314. "DeviceIntroducer",
  315. "DeviceCustomCertName",
  316. "DeviceCompressAlways",
  317. "DeviceCompressMetadata",
  318. "DeviceCompressNever",
  319. "DeviceDynamicAddr",
  320. "DeviceStaticAddr",
  321. "AnnounceGlobalEnabled",
  322. "AnnounceLocalEnabled",
  323. "AnnounceDefaultServersDNS",
  324. "AnnounceDefaultServersIP",
  325. "AnnounceOtherServers",
  326. "RelayEnabled",
  327. "RelayDefaultServers",
  328. "RelayOtherServers",
  329. "RateLimitEnabled",
  330. "UpgradeAllowedManual",
  331. "UpgradeAllowedAuto",
  332. // v0.12.19+
  333. "FolderSimpleVersioning",
  334. "FolderExternalVersioning",
  335. "FolderStaggeredVersioning",
  336. "FolderTrashcanVersioning",
  337. // V2.5
  338. "UpgradeAllowedPre",
  339. "RescanIntvs",
  340. // V3
  341. "Uptime",
  342. "NATType",
  343. "AlwaysLocalNets",
  344. "CacheIgnoredFiles",
  345. "OverwriteRemoteDeviceNames",
  346. "ProgressEmitterEnabled",
  347. "CustomDefaultFolderPath",
  348. "WeakHashSelection",
  349. "CustomTrafficClass",
  350. "CustomTempIndexMinBlocks",
  351. "TemporariesDisabled",
  352. "TemporariesCustom",
  353. "LimitBandwidthInLan",
  354. "CustomReleaseURL",
  355. "RestartOnWakeup",
  356. "CustomStunServers",
  357. "FolderScanProgressDisabled",
  358. "FolderConflictsDisabled",
  359. "FolderConflictsUnlimited",
  360. "FolderConflictsOther",
  361. "FolderDisableSparseFiles",
  362. "FolderDisableTempIndexes",
  363. "FolderAlwaysWeakHash",
  364. "FolderCustomWeakHashThreshold",
  365. "FolderFsWatcherEnabled",
  366. "FolderPullOrder",
  367. "FolderFilesystemType",
  368. "FolderFsWatcherDelays",
  369. "GUIEnabled",
  370. "GUIUseTLS",
  371. "GUIUseAuth",
  372. "GUIInsecureAdminAccess",
  373. "GUIDebugging",
  374. "GUIInsecureSkipHostCheck",
  375. "GUIInsecureAllowFrameLoading",
  376. "GUIListenLocal",
  377. "GUIListenUnspecified",
  378. "GUITheme",
  379. "BlocksTotal",
  380. "BlocksRenamed",
  381. "BlocksReused",
  382. "BlocksPulled",
  383. "BlocksCopyOrigin",
  384. "BlocksCopyOriginShifted",
  385. "BlocksCopyElsewhere",
  386. "Transport",
  387. "IgnoreLines",
  388. "IgnoreInverts",
  389. "IgnoreFolded",
  390. "IgnoreDeletable",
  391. "IgnoreRooted",
  392. "IgnoreIncludes",
  393. "IgnoreEscapedIncludes",
  394. "IgnoreDoubleStars",
  395. "IgnoreStars",
  396. // V3 added late in the RC
  397. "WeakHashEnabled",
  398. "Address",
  399. // Receive only folders
  400. "FolderRecvOnly",
  401. }
  402. }
  403. func setupDB(db *sql.DB) error {
  404. _, err := db.Exec(`CREATE TABLE IF NOT EXISTS Reports (
  405. Received TIMESTAMP NOT NULL,
  406. UniqueID VARCHAR(32) NOT NULL,
  407. Version VARCHAR(32) NOT NULL,
  408. LongVersion VARCHAR(256) NOT NULL,
  409. Platform VARCHAR(32) NOT NULL,
  410. NumFolders INTEGER NOT NULL,
  411. NumDevices INTEGER NOT NULL,
  412. TotFiles INTEGER NOT NULL,
  413. FolderMaxFiles INTEGER NOT NULL,
  414. TotMiB INTEGER NOT NULL,
  415. FolderMaxMiB INTEGER NOT NULL,
  416. MemoryUsageMiB INTEGER NOT NULL,
  417. SHA256Perf DOUBLE PRECISION NOT NULL,
  418. MemorySize INTEGER NOT NULL,
  419. Date VARCHAR(8) NOT NULL
  420. )`)
  421. if err != nil {
  422. return err
  423. }
  424. var t string
  425. row := db.QueryRow(`SELECT 'UniqueIDIndex'::regclass`)
  426. if err := row.Scan(&t); err != nil {
  427. if _, err = db.Exec(`CREATE UNIQUE INDEX UniqueIDIndex ON Reports (Date, UniqueID)`); err != nil {
  428. return err
  429. }
  430. }
  431. row = db.QueryRow(`SELECT 'ReceivedIndex'::regclass`)
  432. if err := row.Scan(&t); err != nil {
  433. if _, err = db.Exec(`CREATE INDEX ReceivedIndex ON Reports (Received)`); err != nil {
  434. return err
  435. }
  436. }
  437. // V2
  438. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'reportversion'`)
  439. if err := row.Scan(&t); err != nil {
  440. // The ReportVersion column doesn't exist; add the new columns.
  441. _, err = db.Exec(`ALTER TABLE Reports
  442. ADD COLUMN ReportVersion INTEGER NOT NULL DEFAULT 0,
  443. ADD COLUMN NumCPU INTEGER NOT NULL DEFAULT 0,
  444. ADD COLUMN FolderRO INTEGER NOT NULL DEFAULT 0,
  445. ADD COLUMN FolderIgnorePerms INTEGER NOT NULL DEFAULT 0,
  446. ADD COLUMN FolderIgnoreDelete INTEGER NOT NULL DEFAULT 0,
  447. ADD COLUMN FolderAutoNormalize INTEGER NOT NULL DEFAULT 0,
  448. ADD COLUMN DeviceIntroducer INTEGER NOT NULL DEFAULT 0,
  449. ADD COLUMN DeviceCustomCertName INTEGER NOT NULL DEFAULT 0,
  450. ADD COLUMN DeviceCompressAlways INTEGER NOT NULL DEFAULT 0,
  451. ADD COLUMN DeviceCompressMetadata INTEGER NOT NULL DEFAULT 0,
  452. ADD COLUMN DeviceCompressNever INTEGER NOT NULL DEFAULT 0,
  453. ADD COLUMN DeviceDynamicAddr INTEGER NOT NULL DEFAULT 0,
  454. ADD COLUMN DeviceStaticAddr INTEGER NOT NULL DEFAULT 0,
  455. ADD COLUMN AnnounceGlobalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  456. ADD COLUMN AnnounceLocalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  457. ADD COLUMN AnnounceDefaultServersDNS INTEGER NOT NULL DEFAULT 0,
  458. ADD COLUMN AnnounceDefaultServersIP INTEGER NOT NULL DEFAULT 0,
  459. ADD COLUMN AnnounceOtherServers INTEGER NOT NULL DEFAULT 0,
  460. ADD COLUMN RelayEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  461. ADD COLUMN RelayDefaultServers INTEGER NOT NULL DEFAULT 0,
  462. ADD COLUMN RelayOtherServers INTEGER NOT NULL DEFAULT 0,
  463. ADD COLUMN RateLimitEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  464. ADD COLUMN UpgradeAllowedManual BOOLEAN NOT NULL DEFAULT FALSE,
  465. ADD COLUMN UpgradeAllowedAuto BOOLEAN NOT NULL DEFAULT FALSE,
  466. ADD COLUMN FolderSimpleVersioning INTEGER NOT NULL DEFAULT 0,
  467. ADD COLUMN FolderExternalVersioning INTEGER NOT NULL DEFAULT 0,
  468. ADD COLUMN FolderStaggeredVersioning INTEGER NOT NULL DEFAULT 0,
  469. ADD COLUMN FolderTrashcanVersioning INTEGER NOT NULL DEFAULT 0
  470. `)
  471. if err != nil {
  472. return err
  473. }
  474. }
  475. row = db.QueryRow(`SELECT 'ReportVersionIndex'::regclass`)
  476. if err := row.Scan(&t); err != nil {
  477. if _, err = db.Exec(`CREATE INDEX ReportVersionIndex ON Reports (ReportVersion)`); err != nil {
  478. return err
  479. }
  480. }
  481. // V2.5
  482. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'upgradeallowedpre'`)
  483. if err := row.Scan(&t); err != nil {
  484. // The ReportVersion column doesn't exist; add the new columns.
  485. _, err = db.Exec(`ALTER TABLE Reports
  486. ADD COLUMN UpgradeAllowedPre BOOLEAN NOT NULL DEFAULT FALSE,
  487. ADD COLUMN RescanIntvs INT[] NOT NULL DEFAULT '{}'
  488. `)
  489. if err != nil {
  490. return err
  491. }
  492. }
  493. // V3
  494. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'uptime'`)
  495. if err := row.Scan(&t); err != nil {
  496. // The Uptime column doesn't exist; add the new columns.
  497. _, err = db.Exec(`ALTER TABLE Reports
  498. ADD COLUMN Uptime INTEGER NOT NULL DEFAULT 0,
  499. ADD COLUMN NATType VARCHAR(32) NOT NULL DEFAULT '',
  500. ADD COLUMN AlwaysLocalNets BOOLEAN NOT NULL DEFAULT FALSE,
  501. ADD COLUMN CacheIgnoredFiles BOOLEAN NOT NULL DEFAULT FALSE,
  502. ADD COLUMN OverwriteRemoteDeviceNames BOOLEAN NOT NULL DEFAULT FALSE,
  503. ADD COLUMN ProgressEmitterEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  504. ADD COLUMN CustomDefaultFolderPath BOOLEAN NOT NULL DEFAULT FALSE,
  505. ADD COLUMN WeakHashSelection VARCHAR(32) NOT NULL DEFAULT '',
  506. ADD COLUMN CustomTrafficClass BOOLEAN NOT NULL DEFAULT FALSE,
  507. ADD COLUMN CustomTempIndexMinBlocks BOOLEAN NOT NULL DEFAULT FALSE,
  508. ADD COLUMN TemporariesDisabled BOOLEAN NOT NULL DEFAULT FALSE,
  509. ADD COLUMN TemporariesCustom BOOLEAN NOT NULL DEFAULT FALSE,
  510. ADD COLUMN LimitBandwidthInLan BOOLEAN NOT NULL DEFAULT FALSE,
  511. ADD COLUMN CustomReleaseURL BOOLEAN NOT NULL DEFAULT FALSE,
  512. ADD COLUMN RestartOnWakeup BOOLEAN NOT NULL DEFAULT FALSE,
  513. ADD COLUMN CustomStunServers BOOLEAN NOT NULL DEFAULT FALSE,
  514. ADD COLUMN FolderScanProgressDisabled INTEGER NOT NULL DEFAULT 0,
  515. ADD COLUMN FolderConflictsDisabled INTEGER NOT NULL DEFAULT 0,
  516. ADD COLUMN FolderConflictsUnlimited INTEGER NOT NULL DEFAULT 0,
  517. ADD COLUMN FolderConflictsOther INTEGER NOT NULL DEFAULT 0,
  518. ADD COLUMN FolderDisableSparseFiles INTEGER NOT NULL DEFAULT 0,
  519. ADD COLUMN FolderDisableTempIndexes INTEGER NOT NULL DEFAULT 0,
  520. ADD COLUMN FolderAlwaysWeakHash INTEGER NOT NULL DEFAULT 0,
  521. ADD COLUMN FolderCustomWeakHashThreshold INTEGER NOT NULL DEFAULT 0,
  522. ADD COLUMN FolderFsWatcherEnabled INTEGER NOT NULL DEFAULT 0,
  523. ADD COLUMN FolderPullOrder JSONB NOT NULL DEFAULT '{}',
  524. ADD COLUMN FolderFilesystemType JSONB NOT NULL DEFAULT '{}',
  525. ADD COLUMN FolderFsWatcherDelays INT[] NOT NULL DEFAULT '{}',
  526. ADD COLUMN GUIEnabled INTEGER NOT NULL DEFAULT 0,
  527. ADD COLUMN GUIUseTLS INTEGER NOT NULL DEFAULT 0,
  528. ADD COLUMN GUIUseAuth INTEGER NOT NULL DEFAULT 0,
  529. ADD COLUMN GUIInsecureAdminAccess INTEGER NOT NULL DEFAULT 0,
  530. ADD COLUMN GUIDebugging INTEGER NOT NULL DEFAULT 0,
  531. ADD COLUMN GUIInsecureSkipHostCheck INTEGER NOT NULL DEFAULT 0,
  532. ADD COLUMN GUIInsecureAllowFrameLoading INTEGER NOT NULL DEFAULT 0,
  533. ADD COLUMN GUIListenLocal INTEGER NOT NULL DEFAULT 0,
  534. ADD COLUMN GUIListenUnspecified INTEGER NOT NULL DEFAULT 0,
  535. ADD COLUMN GUITheme JSONB NOT NULL DEFAULT '{}',
  536. ADD COLUMN BlocksTotal INTEGER NOT NULL DEFAULT 0,
  537. ADD COLUMN BlocksRenamed INTEGER NOT NULL DEFAULT 0,
  538. ADD COLUMN BlocksReused INTEGER NOT NULL DEFAULT 0,
  539. ADD COLUMN BlocksPulled INTEGER NOT NULL DEFAULT 0,
  540. ADD COLUMN BlocksCopyOrigin INTEGER NOT NULL DEFAULT 0,
  541. ADD COLUMN BlocksCopyOriginShifted INTEGER NOT NULL DEFAULT 0,
  542. ADD COLUMN BlocksCopyElsewhere INTEGER NOT NULL DEFAULT 0,
  543. ADD COLUMN Transport JSONB NOT NULL DEFAULT '{}',
  544. ADD COLUMN IgnoreLines INTEGER NOT NULL DEFAULT 0,
  545. ADD COLUMN IgnoreInverts INTEGER NOT NULL DEFAULT 0,
  546. ADD COLUMN IgnoreFolded INTEGER NOT NULL DEFAULT 0,
  547. ADD COLUMN IgnoreDeletable INTEGER NOT NULL DEFAULT 0,
  548. ADD COLUMN IgnoreRooted INTEGER NOT NULL DEFAULT 0,
  549. ADD COLUMN IgnoreIncludes INTEGER NOT NULL DEFAULT 0,
  550. ADD COLUMN IgnoreEscapedIncludes INTEGER NOT NULL DEFAULT 0,
  551. ADD COLUMN IgnoreDoubleStars INTEGER NOT NULL DEFAULT 0,
  552. ADD COLUMN IgnoreStars INTEGER NOT NULL DEFAULT 0
  553. `)
  554. if err != nil {
  555. return err
  556. }
  557. }
  558. // V3 added late in the RC
  559. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'weakhashenabled'`)
  560. if err := row.Scan(&t); err != nil {
  561. // The WeakHashEnabled column doesn't exist; add the new columns.
  562. _, err = db.Exec(`ALTER TABLE Reports
  563. ADD COLUMN WeakHashEnabled BOOLEAN NOT NULL DEFAULT FALSE
  564. ADD COLUMN Address VARCHAR(45) NOT NULL DEFAULT ''
  565. `)
  566. if err != nil {
  567. return err
  568. }
  569. }
  570. // Receive only added ad-hoc
  571. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'folderrecvonly'`)
  572. if err := row.Scan(&t); err != nil {
  573. // The RecvOnly column doesn't exist; add it.
  574. _, err = db.Exec(`ALTER TABLE Reports
  575. ADD COLUMN FolderRecvOnly INTEGER NOT NULL DEFAULT 0
  576. `)
  577. if err != nil {
  578. return err
  579. }
  580. }
  581. return nil
  582. }
  583. func insertReport(db *sql.DB, r report) error {
  584. r.Received = time.Now().UTC()
  585. fields := r.FieldPointers()
  586. params := make([]string, len(fields))
  587. for i := range params {
  588. params[i] = fmt.Sprintf("$%d", i+1)
  589. }
  590. query := "INSERT INTO Reports (" + strings.Join(r.FieldNames(), ", ") + ") VALUES (" + strings.Join(params, ", ") + ")"
  591. _, err := db.Exec(query, fields...)
  592. return err
  593. }
  594. type withDBFunc func(*sql.DB, http.ResponseWriter, *http.Request)
  595. func withDB(db *sql.DB, f withDBFunc) http.HandlerFunc {
  596. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  597. f(db, w, r)
  598. })
  599. }
  600. func main() {
  601. log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile)
  602. log.SetOutput(os.Stdout)
  603. // Template
  604. fd, err := os.Open("static/index.html")
  605. if err != nil {
  606. log.Fatalln("template:", err)
  607. }
  608. bs, err := ioutil.ReadAll(fd)
  609. if err != nil {
  610. log.Fatalln("template:", err)
  611. }
  612. fd.Close()
  613. tpl = template.Must(template.New("index.html").Funcs(funcs).Parse(string(bs)))
  614. // DB
  615. db, err := sql.Open("postgres", dbConn)
  616. if err != nil {
  617. log.Fatalln("database:", err)
  618. }
  619. err = setupDB(db)
  620. if err != nil {
  621. log.Fatalln("database:", err)
  622. }
  623. // TLS & Listening
  624. var listener net.Listener
  625. if useHTTP {
  626. listener, err = net.Listen("tcp", listenAddr)
  627. } else {
  628. cert, err := tls.LoadX509KeyPair(certFile, keyFile)
  629. if err != nil {
  630. log.Fatalln("tls:", err)
  631. }
  632. cfg := &tls.Config{
  633. Certificates: []tls.Certificate{cert},
  634. SessionTicketsDisabled: true,
  635. }
  636. listener, err = tls.Listen("tcp", listenAddr, cfg)
  637. }
  638. if err != nil {
  639. log.Fatalln("listen:", err)
  640. }
  641. srv := http.Server{
  642. ReadTimeout: 5 * time.Second,
  643. WriteTimeout: 15 * time.Second,
  644. }
  645. http.HandleFunc("/", withDB(db, rootHandler))
  646. http.HandleFunc("/newdata", withDB(db, newDataHandler))
  647. http.HandleFunc("/summary.json", withDB(db, summaryHandler))
  648. http.HandleFunc("/movement.json", withDB(db, movementHandler))
  649. http.HandleFunc("/performance.json", withDB(db, performanceHandler))
  650. http.HandleFunc("/blockstats.json", withDB(db, blockStatsHandler))
  651. http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
  652. err = srv.Serve(listener)
  653. if err != nil {
  654. log.Fatalln("https:", err)
  655. }
  656. }
  657. var (
  658. cacheData []byte
  659. cacheTime time.Time
  660. cacheMut sync.Mutex
  661. )
  662. const maxCacheTime = 5 * 60 * time.Second
  663. func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  664. if r.URL.Path == "/" || r.URL.Path == "/index.html" {
  665. cacheMut.Lock()
  666. defer cacheMut.Unlock()
  667. if time.Since(cacheTime) > maxCacheTime {
  668. rep := getReport(db)
  669. buf := new(bytes.Buffer)
  670. err := tpl.Execute(buf, rep)
  671. if err != nil {
  672. log.Println(err)
  673. http.Error(w, "Template Error", http.StatusInternalServerError)
  674. return
  675. }
  676. cacheData = buf.Bytes()
  677. cacheTime = time.Now()
  678. }
  679. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  680. w.Write(cacheData)
  681. } else {
  682. http.Error(w, "Not found", 404)
  683. return
  684. }
  685. }
  686. func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  687. defer r.Body.Close()
  688. addr := r.Header.Get("X-Forwarded-For")
  689. if addr != "" {
  690. addr = strings.Split(addr, ", ")[0]
  691. } else {
  692. addr = r.RemoteAddr
  693. }
  694. if host, _, err := net.SplitHostPort(addr); err == nil {
  695. addr = host
  696. }
  697. if net.ParseIP(addr) == nil {
  698. addr = ""
  699. }
  700. var rep report
  701. rep.Date = time.Now().UTC().Format("20060102")
  702. rep.Address = addr
  703. lr := &io.LimitedReader{R: r.Body, N: 40 * 1024}
  704. bs, _ := ioutil.ReadAll(lr)
  705. if err := json.Unmarshal(bs, &rep); err != nil {
  706. log.Println("decode:", err)
  707. if debug {
  708. log.Printf("%s", bs)
  709. }
  710. http.Error(w, "JSON Decode Error", http.StatusInternalServerError)
  711. return
  712. }
  713. if err := rep.Validate(); err != nil {
  714. log.Println("validate:", err)
  715. if debug {
  716. log.Printf("%#v", rep)
  717. }
  718. http.Error(w, "Validation Error", http.StatusInternalServerError)
  719. return
  720. }
  721. if err := insertReport(db, rep); err != nil {
  722. log.Println("insert:", err)
  723. if debug {
  724. log.Printf("%#v", rep)
  725. }
  726. http.Error(w, "Database Error", http.StatusInternalServerError)
  727. return
  728. }
  729. }
  730. func summaryHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  731. s, err := getSummary(db)
  732. if err != nil {
  733. log.Println("summaryHandler:", err)
  734. http.Error(w, "Database Error", http.StatusInternalServerError)
  735. return
  736. }
  737. bs, err := s.MarshalJSON()
  738. if err != nil {
  739. log.Println("summaryHandler:", err)
  740. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  741. return
  742. }
  743. w.Header().Set("Content-Type", "application/json")
  744. w.Write(bs)
  745. }
  746. func movementHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  747. s, err := getMovement(db)
  748. if err != nil {
  749. log.Println("movementHandler:", err)
  750. http.Error(w, "Database Error", http.StatusInternalServerError)
  751. return
  752. }
  753. bs, err := json.Marshal(s)
  754. if err != nil {
  755. log.Println("movementHandler:", err)
  756. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  757. return
  758. }
  759. w.Header().Set("Content-Type", "application/json")
  760. w.Write(bs)
  761. }
  762. func performanceHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  763. s, err := getPerformance(db)
  764. if err != nil {
  765. log.Println("performanceHandler:", err)
  766. http.Error(w, "Database Error", http.StatusInternalServerError)
  767. return
  768. }
  769. bs, err := json.Marshal(s)
  770. if err != nil {
  771. log.Println("performanceHandler:", err)
  772. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  773. return
  774. }
  775. w.Header().Set("Content-Type", "application/json")
  776. w.Write(bs)
  777. }
  778. func blockStatsHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  779. s, err := getBlockStats(db)
  780. if err != nil {
  781. log.Println("blockStatsHandler:", err)
  782. http.Error(w, "Database Error", http.StatusInternalServerError)
  783. return
  784. }
  785. bs, err := json.Marshal(s)
  786. if err != nil {
  787. log.Println("blockStatsHandler:", err)
  788. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  789. return
  790. }
  791. w.Header().Set("Content-Type", "application/json")
  792. w.Write(bs)
  793. }
  794. type category struct {
  795. Values [4]float64
  796. Key string
  797. Descr string
  798. Unit string
  799. Type NumberType
  800. }
  801. type feature struct {
  802. Key string
  803. Version string
  804. Count int
  805. Pct float64
  806. }
  807. type featureGroup struct {
  808. Key string
  809. Version string
  810. Counts map[string]int
  811. }
  812. // Used in the templates
  813. type counter struct {
  814. n int
  815. }
  816. func (c *counter) Current() int {
  817. return c.n
  818. }
  819. func (c *counter) Increment() string {
  820. c.n++
  821. return ""
  822. }
  823. func (c *counter) DrawTwoDivider() bool {
  824. return c.n != 0 && c.n%2 == 0
  825. }
  826. // add sets a key in a nested map, initializing things if needed as we go.
  827. func add(storage map[string]map[string]int, parent, child string, value int) {
  828. n, ok := storage[parent]
  829. if !ok {
  830. n = make(map[string]int)
  831. storage[parent] = n
  832. }
  833. n[child] += value
  834. }
  835. // inc makes sure that even for unused features, we initialize them in the
  836. // feature map. Furthermore, this acts as a helper that accepts booleans
  837. // to increment by one, or integers to increment by that integer.
  838. func inc(storage map[string]int, key string, i interface{}) {
  839. cv := storage[key]
  840. switch v := i.(type) {
  841. case bool:
  842. if v {
  843. cv++
  844. }
  845. case int:
  846. cv += v
  847. }
  848. storage[key] = cv
  849. }
  850. type location struct {
  851. Latitude float64
  852. Longitude float64
  853. }
  854. func getReport(db *sql.DB) map[string]interface{} {
  855. geoip, err := geoip2.Open(geoIPPath)
  856. if err != nil {
  857. log.Println("opening geoip db", err)
  858. geoip = nil
  859. } else {
  860. defer geoip.Close()
  861. }
  862. nodes := 0
  863. countriesTotal := 0
  864. var versions []string
  865. var platforms []string
  866. var numFolders []int
  867. var numDevices []int
  868. var totFiles []int
  869. var maxFiles []int
  870. var totMiB []int
  871. var maxMiB []int
  872. var memoryUsage []int
  873. var sha256Perf []float64
  874. var memorySize []int
  875. var uptime []int
  876. var compilers []string
  877. var builders []string
  878. locations := make(map[location]int)
  879. countries := make(map[string]int)
  880. reports := make(map[string]int)
  881. totals := make(map[string]int)
  882. // category -> version -> feature -> count
  883. features := make(map[string]map[string]map[string]int)
  884. // category -> version -> feature -> group -> count
  885. featureGroups := make(map[string]map[string]map[string]map[string]int)
  886. for _, category := range featureOrder {
  887. features[category] = make(map[string]map[string]int)
  888. featureGroups[category] = make(map[string]map[string]map[string]int)
  889. for _, version := range knownVersions {
  890. features[category][version] = make(map[string]int)
  891. featureGroups[category][version] = make(map[string]map[string]int)
  892. }
  893. }
  894. // Initialize some features that hide behind if conditions, and might not
  895. // be initialized.
  896. add(featureGroups["Various"]["v2"], "Upgrades", "Pre-release", 0)
  897. add(featureGroups["Various"]["v2"], "Upgrades", "Automatic", 0)
  898. add(featureGroups["Various"]["v2"], "Upgrades", "Manual", 0)
  899. add(featureGroups["Various"]["v2"], "Upgrades", "Disabled", 0)
  900. add(featureGroups["Various"]["v3"], "Temporary Retention", "Disabled", 0)
  901. add(featureGroups["Various"]["v3"], "Temporary Retention", "Custom", 0)
  902. add(featureGroups["Various"]["v3"], "Temporary Retention", "Default", 0)
  903. add(featureGroups["Connection"]["v3"], "IP version", "IPv4", 0)
  904. add(featureGroups["Connection"]["v3"], "IP version", "IPv6", 0)
  905. add(featureGroups["Connection"]["v3"], "IP version", "Unknown", 0)
  906. var numCPU []int
  907. var rep report
  908. rows, err := db.Query(`SELECT ` + strings.Join(rep.FieldNames(), ",") + ` FROM Reports WHERE Received > now() - '1 day'::INTERVAL`)
  909. if err != nil {
  910. log.Println("sql:", err)
  911. return nil
  912. }
  913. defer rows.Close()
  914. for rows.Next() {
  915. err := rows.Scan(rep.FieldPointers()...)
  916. if err != nil {
  917. log.Println("sql:", err)
  918. return nil
  919. }
  920. if geoip != nil && rep.Address != "" {
  921. if addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(rep.Address, "0")); err == nil {
  922. city, err := geoip.City(addr.IP)
  923. if err == nil {
  924. loc := location{
  925. Latitude: city.Location.Latitude,
  926. Longitude: city.Location.Longitude,
  927. }
  928. locations[loc]++
  929. countries[city.Country.Names["en"]]++
  930. countriesTotal++
  931. }
  932. }
  933. }
  934. nodes++
  935. versions = append(versions, transformVersion(rep.Version))
  936. platforms = append(platforms, rep.Platform)
  937. if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 {
  938. compilers = append(compilers, m[1])
  939. builders = append(builders, m[2])
  940. }
  941. if rep.NumFolders > 0 {
  942. numFolders = append(numFolders, rep.NumFolders)
  943. }
  944. if rep.NumDevices > 0 {
  945. numDevices = append(numDevices, rep.NumDevices)
  946. }
  947. if rep.TotFiles > 0 {
  948. totFiles = append(totFiles, rep.TotFiles)
  949. }
  950. if rep.FolderMaxFiles > 0 {
  951. maxFiles = append(maxFiles, rep.FolderMaxFiles)
  952. }
  953. if rep.TotMiB > 0 {
  954. totMiB = append(totMiB, rep.TotMiB*(1<<20))
  955. }
  956. if rep.FolderMaxMiB > 0 {
  957. maxMiB = append(maxMiB, rep.FolderMaxMiB*(1<<20))
  958. }
  959. if rep.MemoryUsageMiB > 0 {
  960. memoryUsage = append(memoryUsage, rep.MemoryUsageMiB*(1<<20))
  961. }
  962. if rep.SHA256Perf > 0 {
  963. sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20))
  964. }
  965. if rep.MemorySize > 0 {
  966. memorySize = append(memorySize, rep.MemorySize*(1<<20))
  967. }
  968. if rep.Uptime > 0 {
  969. uptime = append(uptime, rep.Uptime)
  970. }
  971. totals["Device"] += rep.NumDevices
  972. totals["Folder"] += rep.NumFolders
  973. if rep.URVersion >= 2 {
  974. reports["v2"]++
  975. numCPU = append(numCPU, rep.NumCPU)
  976. // Various
  977. inc(features["Various"]["v2"], "Rate limiting", rep.UsesRateLimit)
  978. if rep.UpgradeAllowedPre {
  979. add(featureGroups["Various"]["v2"], "Upgrades", "Pre-release", 1)
  980. } else if rep.UpgradeAllowedAuto {
  981. add(featureGroups["Various"]["v2"], "Upgrades", "Automatic", 1)
  982. } else if rep.UpgradeAllowedManual {
  983. add(featureGroups["Various"]["v2"], "Upgrades", "Manual", 1)
  984. } else {
  985. add(featureGroups["Various"]["v2"], "Upgrades", "Disabled", 1)
  986. }
  987. // Folders
  988. inc(features["Folder"]["v2"], "Automatic normalization", rep.FolderUses.AutoNormalize)
  989. inc(features["Folder"]["v2"], "Ignore deletes", rep.FolderUses.IgnoreDelete)
  990. inc(features["Folder"]["v2"], "Ignore permissions", rep.FolderUses.IgnorePerms)
  991. inc(features["Folder"]["v2"], "Mode, send only", rep.FolderUses.SendOnly)
  992. inc(features["Folder"]["v2"], "Mode, receive only", rep.FolderUses.ReceiveOnly)
  993. add(featureGroups["Folder"]["v2"], "Versioning", "Simple", rep.FolderUses.SimpleVersioning)
  994. add(featureGroups["Folder"]["v2"], "Versioning", "External", rep.FolderUses.ExternalVersioning)
  995. add(featureGroups["Folder"]["v2"], "Versioning", "Staggered", rep.FolderUses.StaggeredVersioning)
  996. add(featureGroups["Folder"]["v2"], "Versioning", "Trashcan", rep.FolderUses.TrashcanVersioning)
  997. add(featureGroups["Folder"]["v2"], "Versioning", "Disabled", rep.NumFolders-rep.FolderUses.SimpleVersioning-rep.FolderUses.ExternalVersioning-rep.FolderUses.StaggeredVersioning-rep.FolderUses.TrashcanVersioning)
  998. // Device
  999. inc(features["Device"]["v2"], "Custom certificate", rep.DeviceUses.CustomCertName)
  1000. inc(features["Device"]["v2"], "Introducer", rep.DeviceUses.Introducer)
  1001. add(featureGroups["Device"]["v2"], "Compress", "Always", rep.DeviceUses.CompressAlways)
  1002. add(featureGroups["Device"]["v2"], "Compress", "Metadata", rep.DeviceUses.CompressMetadata)
  1003. add(featureGroups["Device"]["v2"], "Compress", "Nothing", rep.DeviceUses.CompressNever)
  1004. add(featureGroups["Device"]["v2"], "Addresses", "Dynamic", rep.DeviceUses.DynamicAddr)
  1005. add(featureGroups["Device"]["v2"], "Addresses", "Static", rep.DeviceUses.StaticAddr)
  1006. // Connections
  1007. inc(features["Connection"]["v2"], "Relaying, enabled", rep.Relays.Enabled)
  1008. inc(features["Connection"]["v2"], "Discovery, global enabled", rep.Announce.GlobalEnabled)
  1009. inc(features["Connection"]["v2"], "Discovery, local enabled", rep.Announce.LocalEnabled)
  1010. add(featureGroups["Connection"]["v2"], "Discovery", "Default servers (using DNS)", rep.Announce.DefaultServersDNS)
  1011. add(featureGroups["Connection"]["v2"], "Discovery", "Default servers (using IP)", rep.Announce.DefaultServersIP)
  1012. add(featureGroups["Connection"]["v2"], "Discovery", "Other servers", rep.Announce.DefaultServersIP)
  1013. add(featureGroups["Connection"]["v2"], "Relaying", "Default relays", rep.Relays.DefaultServers)
  1014. add(featureGroups["Connection"]["v2"], "Relaying", "Other relays", rep.Relays.OtherServers)
  1015. }
  1016. if rep.URVersion >= 3 {
  1017. reports["v3"]++
  1018. inc(features["Various"]["v3"], "Custom LAN classification", rep.AlwaysLocalNets)
  1019. inc(features["Various"]["v3"], "Ignore caching", rep.CacheIgnoredFiles)
  1020. inc(features["Various"]["v3"], "Overwrite device names", rep.OverwriteRemoteDeviceNames)
  1021. inc(features["Various"]["v3"], "Download progress disabled", !rep.ProgressEmitterEnabled)
  1022. inc(features["Various"]["v3"], "Custom default path", rep.CustomDefaultFolderPath)
  1023. inc(features["Various"]["v3"], "Custom traffic class", rep.CustomTrafficClass)
  1024. inc(features["Various"]["v3"], "Custom temporary index threshold", rep.CustomTempIndexMinBlocks)
  1025. inc(features["Various"]["v3"], "Weak hash enabled", rep.WeakHashEnabled)
  1026. inc(features["Various"]["v3"], "LAN rate limiting", rep.LimitBandwidthInLan)
  1027. inc(features["Various"]["v3"], "Custom release server", rep.CustomReleaseURL)
  1028. inc(features["Various"]["v3"], "Restart after suspend", rep.RestartOnWakeup)
  1029. inc(features["Various"]["v3"], "Custom stun servers", rep.CustomStunServers)
  1030. inc(features["Various"]["v3"], "Ignore patterns", rep.IgnoreStats.Lines > 0)
  1031. if rep.NATType != "" {
  1032. natType := rep.NATType
  1033. natType = strings.Replace(natType, "unknown", "Unknown", -1)
  1034. natType = strings.Replace(natType, "Symetric", "Symmetric", -1)
  1035. add(featureGroups["Various"]["v3"], "NAT Type", natType, 1)
  1036. }
  1037. if rep.TemporariesDisabled {
  1038. add(featureGroups["Various"]["v3"], "Temporary Retention", "Disabled", 1)
  1039. } else if rep.TemporariesCustom {
  1040. add(featureGroups["Various"]["v3"], "Temporary Retention", "Custom", 1)
  1041. } else {
  1042. add(featureGroups["Various"]["v3"], "Temporary Retention", "Default", 1)
  1043. }
  1044. inc(features["Folder"]["v3"], "Scan progress disabled", rep.FolderUsesV3.ScanProgressDisabled)
  1045. inc(features["Folder"]["v3"], "Disable sharing of partial files", rep.FolderUsesV3.DisableTempIndexes)
  1046. inc(features["Folder"]["v3"], "Disable sparse files", rep.FolderUsesV3.DisableSparseFiles)
  1047. inc(features["Folder"]["v3"], "Weak hash, always", rep.FolderUsesV3.AlwaysWeakHash)
  1048. inc(features["Folder"]["v3"], "Weak hash, custom threshold", rep.FolderUsesV3.CustomWeakHashThreshold)
  1049. inc(features["Folder"]["v3"], "Filesystem watcher", rep.FolderUsesV3.FsWatcherEnabled)
  1050. add(featureGroups["Folder"]["v3"], "Conflicts", "Disabled", rep.FolderUsesV3.ConflictsDisabled)
  1051. add(featureGroups["Folder"]["v3"], "Conflicts", "Unlimited", rep.FolderUsesV3.ConflictsUnlimited)
  1052. add(featureGroups["Folder"]["v3"], "Conflicts", "Limited", rep.FolderUsesV3.ConflictsOther)
  1053. for key, value := range rep.FolderUsesV3.PullOrder {
  1054. add(featureGroups["Folder"]["v3"], "Pull Order", prettyCase(key), value)
  1055. }
  1056. totals["GUI"] += rep.GUIStats.Enabled
  1057. inc(features["GUI"]["v3"], "Auth Enabled", rep.GUIStats.UseAuth)
  1058. inc(features["GUI"]["v3"], "TLS Enabled", rep.GUIStats.UseTLS)
  1059. inc(features["GUI"]["v3"], "Insecure Admin Access", rep.GUIStats.InsecureAdminAccess)
  1060. inc(features["GUI"]["v3"], "Skip Host check", rep.GUIStats.InsecureSkipHostCheck)
  1061. inc(features["GUI"]["v3"], "Allow Frame loading", rep.GUIStats.InsecureAllowFrameLoading)
  1062. add(featureGroups["GUI"]["v3"], "Listen address", "Local", rep.GUIStats.ListenLocal)
  1063. add(featureGroups["GUI"]["v3"], "Listen address", "Unspecified", rep.GUIStats.ListenUnspecified)
  1064. add(featureGroups["GUI"]["v3"], "Listen address", "Other", rep.GUIStats.Enabled-rep.GUIStats.ListenLocal-rep.GUIStats.ListenUnspecified)
  1065. for theme, count := range rep.GUIStats.Theme {
  1066. add(featureGroups["GUI"]["v3"], "Theme", prettyCase(theme), count)
  1067. }
  1068. for transport, count := range rep.TransportStats {
  1069. add(featureGroups["Connection"]["v3"], "Transport", strings.Title(transport), count)
  1070. if strings.HasSuffix(transport, "4") {
  1071. add(featureGroups["Connection"]["v3"], "IP version", "IPv4", count)
  1072. } else if strings.HasSuffix(transport, "6") {
  1073. add(featureGroups["Connection"]["v3"], "IP version", "IPv6", count)
  1074. } else {
  1075. add(featureGroups["Connection"]["v3"], "IP version", "Unknown", count)
  1076. }
  1077. }
  1078. }
  1079. }
  1080. var categories []category
  1081. categories = append(categories, category{
  1082. Values: statsForInts(totFiles),
  1083. Descr: "Files Managed per Device",
  1084. })
  1085. categories = append(categories, category{
  1086. Values: statsForInts(maxFiles),
  1087. Descr: "Files in Largest Folder",
  1088. })
  1089. categories = append(categories, category{
  1090. Values: statsForInts(totMiB),
  1091. Descr: "Data Managed per Device",
  1092. Unit: "B",
  1093. Type: NumberBinary,
  1094. })
  1095. categories = append(categories, category{
  1096. Values: statsForInts(maxMiB),
  1097. Descr: "Data in Largest Folder",
  1098. Unit: "B",
  1099. Type: NumberBinary,
  1100. })
  1101. categories = append(categories, category{
  1102. Values: statsForInts(numDevices),
  1103. Descr: "Number of Devices in Cluster",
  1104. })
  1105. categories = append(categories, category{
  1106. Values: statsForInts(numFolders),
  1107. Descr: "Number of Folders Configured",
  1108. })
  1109. categories = append(categories, category{
  1110. Values: statsForInts(memoryUsage),
  1111. Descr: "Memory Usage",
  1112. Unit: "B",
  1113. Type: NumberBinary,
  1114. })
  1115. categories = append(categories, category{
  1116. Values: statsForInts(memorySize),
  1117. Descr: "System Memory",
  1118. Unit: "B",
  1119. Type: NumberBinary,
  1120. })
  1121. categories = append(categories, category{
  1122. Values: statsForFloats(sha256Perf),
  1123. Descr: "SHA-256 Hashing Performance",
  1124. Unit: "B/s",
  1125. Type: NumberBinary,
  1126. })
  1127. categories = append(categories, category{
  1128. Values: statsForInts(numCPU),
  1129. Descr: "Number of CPU cores",
  1130. })
  1131. categories = append(categories, category{
  1132. Values: statsForInts(uptime),
  1133. Descr: "Uptime (v3)",
  1134. Type: NumberDuration,
  1135. })
  1136. reportFeatures := make(map[string][]feature)
  1137. for featureType, versions := range features {
  1138. var featureList []feature
  1139. for version, featureMap := range versions {
  1140. // We count totals of the given feature type, for example number of
  1141. // folders or devices, if that doesn't exist, we work out percentage
  1142. // against the total of the version reports. Things like "Various"
  1143. // never have counts.
  1144. total, ok := totals[featureType]
  1145. if !ok {
  1146. total = reports[version]
  1147. }
  1148. for key, count := range featureMap {
  1149. featureList = append(featureList, feature{
  1150. Key: key,
  1151. Version: version,
  1152. Count: count,
  1153. Pct: (100 * float64(count)) / float64(total),
  1154. })
  1155. }
  1156. }
  1157. sort.Sort(sort.Reverse(sortableFeatureList(featureList)))
  1158. reportFeatures[featureType] = featureList
  1159. }
  1160. reportFeatureGroups := make(map[string][]featureGroup)
  1161. for featureType, versions := range featureGroups {
  1162. var featureList []featureGroup
  1163. for version, featureMap := range versions {
  1164. for key, counts := range featureMap {
  1165. featureList = append(featureList, featureGroup{
  1166. Key: key,
  1167. Version: version,
  1168. Counts: counts,
  1169. })
  1170. }
  1171. }
  1172. reportFeatureGroups[featureType] = featureList
  1173. }
  1174. var countryList []feature
  1175. for country, count := range countries {
  1176. countryList = append(countryList, feature{
  1177. Key: country,
  1178. Count: count,
  1179. Pct: (100 * float64(count)) / float64(countriesTotal),
  1180. })
  1181. sort.Sort(sort.Reverse(sortableFeatureList(countryList)))
  1182. }
  1183. r := make(map[string]interface{})
  1184. r["features"] = reportFeatures
  1185. r["featureGroups"] = reportFeatureGroups
  1186. r["nodes"] = nodes
  1187. r["versionNodes"] = reports
  1188. r["categories"] = categories
  1189. r["versions"] = group(byVersion, analyticsFor(versions, 2000), 10)
  1190. r["versionPenetrations"] = penetrationLevels(analyticsFor(versions, 2000), []float64{50, 75, 90, 95})
  1191. r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 5)
  1192. r["compilers"] = group(byCompiler, analyticsFor(compilers, 2000), 5)
  1193. r["builders"] = analyticsFor(builders, 12)
  1194. r["featureOrder"] = featureOrder
  1195. r["locations"] = locations
  1196. r["contries"] = countryList
  1197. return r
  1198. }
  1199. func ensureDir(dir string, mode int) {
  1200. fi, err := os.Stat(dir)
  1201. if os.IsNotExist(err) {
  1202. os.MkdirAll(dir, 0700)
  1203. } else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
  1204. os.Chmod(dir, os.FileMode(mode))
  1205. }
  1206. }
  1207. var (
  1208. plusRe = regexp.MustCompile(`\+.*$`)
  1209. plusStr = "(+dev)"
  1210. )
  1211. // transformVersion returns a version number formatted correctly, with all
  1212. // development versions aggregated into one.
  1213. func transformVersion(v string) string {
  1214. if v == "unknown-dev" {
  1215. return v
  1216. }
  1217. if !strings.HasPrefix(v, "v") {
  1218. v = "v" + v
  1219. }
  1220. v = plusRe.ReplaceAllString(v, " "+plusStr)
  1221. return v
  1222. }
  1223. type summary struct {
  1224. versions map[string]int // version string to count index
  1225. max map[string]int // version string to max users per day
  1226. rows map[string][]int // date to list of counts
  1227. }
  1228. func newSummary() summary {
  1229. return summary{
  1230. versions: make(map[string]int),
  1231. max: make(map[string]int),
  1232. rows: make(map[string][]int),
  1233. }
  1234. }
  1235. func (s *summary) setCount(date, version string, count int) {
  1236. idx, ok := s.versions[version]
  1237. if !ok {
  1238. idx = len(s.versions)
  1239. s.versions[version] = idx
  1240. }
  1241. if s.max[version] < count {
  1242. s.max[version] = count
  1243. }
  1244. row := s.rows[date]
  1245. if len(row) <= idx {
  1246. old := row
  1247. row = make([]int, idx+1)
  1248. copy(row, old)
  1249. s.rows[date] = row
  1250. }
  1251. row[idx] = count
  1252. }
  1253. func (s *summary) MarshalJSON() ([]byte, error) {
  1254. var versions []string
  1255. for v := range s.versions {
  1256. versions = append(versions, v)
  1257. }
  1258. sort.Strings(versions)
  1259. var filtered []string
  1260. for _, v := range versions {
  1261. if s.max[v] > 50 {
  1262. filtered = append(filtered, v)
  1263. }
  1264. }
  1265. versions = filtered
  1266. headerRow := []interface{}{"Day"}
  1267. for _, v := range versions {
  1268. headerRow = append(headerRow, v)
  1269. }
  1270. var table [][]interface{}
  1271. table = append(table, headerRow)
  1272. var dates []string
  1273. for k := range s.rows {
  1274. dates = append(dates, k)
  1275. }
  1276. sort.Strings(dates)
  1277. for _, date := range dates {
  1278. row := []interface{}{date}
  1279. for _, ver := range versions {
  1280. idx := s.versions[ver]
  1281. if len(s.rows[date]) > idx && s.rows[date][idx] > 0 {
  1282. row = append(row, s.rows[date][idx])
  1283. } else {
  1284. row = append(row, nil)
  1285. }
  1286. }
  1287. table = append(table, row)
  1288. }
  1289. return json.Marshal(table)
  1290. }
  1291. func getSummary(db *sql.DB) (summary, error) {
  1292. s := newSummary()
  1293. rows, err := db.Query(`SELECT Day, Version, Count FROM VersionSummary WHERE Day > now() - '2 year'::INTERVAL;`)
  1294. if err != nil {
  1295. return summary{}, err
  1296. }
  1297. defer rows.Close()
  1298. for rows.Next() {
  1299. var day time.Time
  1300. var ver string
  1301. var num int
  1302. err := rows.Scan(&day, &ver, &num)
  1303. if err != nil {
  1304. return summary{}, err
  1305. }
  1306. if ver == "v0.0" {
  1307. // ?
  1308. continue
  1309. }
  1310. // SUPER UGLY HACK to avoid having to do sorting properly
  1311. if len(ver) == 4 { // v0.x
  1312. ver = ver[:3] + "0" + ver[3:] // now v0.0x
  1313. }
  1314. s.setCount(day.Format("2006-01-02"), ver, num)
  1315. }
  1316. return s, nil
  1317. }
  1318. func getMovement(db *sql.DB) ([][]interface{}, error) {
  1319. rows, err := db.Query(`SELECT Day, Added, Removed, Bounced FROM UserMovement WHERE Day > now() - '2 year'::INTERVAL ORDER BY Day`)
  1320. if err != nil {
  1321. return nil, err
  1322. }
  1323. defer rows.Close()
  1324. res := [][]interface{}{
  1325. {"Day", "Joined", "Left", "Bounced"},
  1326. }
  1327. for rows.Next() {
  1328. var day time.Time
  1329. var added, removed, bounced int
  1330. err := rows.Scan(&day, &added, &removed, &bounced)
  1331. if err != nil {
  1332. return nil, err
  1333. }
  1334. row := []interface{}{day.Format("2006-01-02"), added, -removed, bounced}
  1335. if removed == 0 {
  1336. row[2] = nil
  1337. }
  1338. if bounced == 0 {
  1339. row[3] = nil
  1340. }
  1341. res = append(res, row)
  1342. }
  1343. return res, nil
  1344. }
  1345. func getPerformance(db *sql.DB) ([][]interface{}, error) {
  1346. rows, err := db.Query(`SELECT Day, TotFiles, TotMiB, SHA256Perf, MemorySize, MemoryUsageMiB FROM Performance WHERE Day > '2014-06-20'::TIMESTAMP ORDER BY Day`)
  1347. if err != nil {
  1348. return nil, err
  1349. }
  1350. defer rows.Close()
  1351. res := [][]interface{}{
  1352. {"Day", "TotFiles", "TotMiB", "SHA256Perf", "MemorySize", "MemoryUsageMiB"},
  1353. }
  1354. for rows.Next() {
  1355. var day time.Time
  1356. var sha256Perf float64
  1357. var totFiles, totMiB, memorySize, memoryUsage int
  1358. err := rows.Scan(&day, &totFiles, &totMiB, &sha256Perf, &memorySize, &memoryUsage)
  1359. if err != nil {
  1360. return nil, err
  1361. }
  1362. row := []interface{}{day.Format("2006-01-02"), totFiles, totMiB, float64(int(sha256Perf*10)) / 10, memorySize, memoryUsage}
  1363. res = append(res, row)
  1364. }
  1365. return res, nil
  1366. }
  1367. func getBlockStats(db *sql.DB) ([][]interface{}, error) {
  1368. rows, err := db.Query(`SELECT Day, Reports, Pulled, Renamed, Reused, CopyOrigin, CopyOriginShifted, CopyElsewhere FROM BlockStats WHERE Day > '2017-10-23'::TIMESTAMP ORDER BY Day`)
  1369. if err != nil {
  1370. return nil, err
  1371. }
  1372. defer rows.Close()
  1373. res := [][]interface{}{
  1374. {"Day", "Number of Reports", "Transferred (GiB)", "Saved by renaming files (GiB)", "Saved by resuming transfer (GiB)", "Saved by reusing data from old file (GiB)", "Saved by reusing shifted data from old file (GiB)", "Saved by reusing data from other files (GiB)"},
  1375. }
  1376. blocksToGb := float64(8 * 1024)
  1377. for rows.Next() {
  1378. var day time.Time
  1379. var reports, pulled, renamed, reused, copyOrigin, copyOriginShifted, copyElsewhere float64
  1380. err := rows.Scan(&day, &reports, &pulled, &renamed, &reused, &copyOrigin, &copyOriginShifted, &copyElsewhere)
  1381. if err != nil {
  1382. return nil, err
  1383. }
  1384. row := []interface{}{
  1385. day.Format("2006-01-02"),
  1386. reports,
  1387. pulled / blocksToGb,
  1388. renamed / blocksToGb,
  1389. reused / blocksToGb,
  1390. copyOrigin / blocksToGb,
  1391. copyOriginShifted / blocksToGb,
  1392. copyElsewhere / blocksToGb,
  1393. }
  1394. res = append(res, row)
  1395. }
  1396. return res, nil
  1397. }
  1398. type sortableFeatureList []feature
  1399. func (l sortableFeatureList) Len() int {
  1400. return len(l)
  1401. }
  1402. func (l sortableFeatureList) Swap(a, b int) {
  1403. l[a], l[b] = l[b], l[a]
  1404. }
  1405. func (l sortableFeatureList) Less(a, b int) bool {
  1406. if l[a].Pct != l[b].Pct {
  1407. return l[a].Pct < l[b].Pct
  1408. }
  1409. return l[a].Key > l[b].Key
  1410. }
  1411. func prettyCase(input string) string {
  1412. output := ""
  1413. for i, runeValue := range input {
  1414. if i == 0 {
  1415. runeValue = unicode.ToUpper(runeValue)
  1416. } else if unicode.IsUpper(runeValue) {
  1417. output += " "
  1418. }
  1419. output += string(runeValue)
  1420. }
  1421. return output
  1422. }