main.go 49 KB

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