main.go 47 KB

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