ae9cc3ecb55e392503f56d8f7b01c3a4f424e2d6
- Author
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Committer
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/go.mod b/go.mod
2index ea287b8a72ecb35b94c9858d32a5504ade2c8c1e..a21aed2bca8fd2d0af6aee82874a61d37b86f15a 100644
3--- a/go.mod
4+++ b/go.mod
5@@ -4,7 +4,6 @@ go 1.17
6
7 require (
8 github.com/alecthomas/chroma v0.10.0
9- github.com/caarlos0/env/v6 v6.10.1
10 github.com/charmbracelet/bubbles v0.15.0
11 github.com/charmbracelet/bubbletea v0.23.2
12 github.com/charmbracelet/glamour v0.6.0
13@@ -20,6 +19,7 @@ require (
14
15 require (
16 github.com/aymanbagabas/go-osc52 v1.2.2
17+ github.com/caarlos0/env/v7 v7.1.0
18 github.com/charmbracelet/keygen v0.3.0
19 github.com/charmbracelet/log v0.2.1
20 github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103
21@@ -28,6 +28,8 @@ require (
22 github.com/lrstanley/bubblezone v0.0.0-20220716194435-3cb8c52f6a8f
23 github.com/muesli/mango-cobra v1.2.0
24 github.com/muesli/roff v0.1.0
25+ github.com/prometheus/client_golang v1.14.0
26+ github.com/robfig/cron/v3 v3.0.1
27 github.com/spf13/cobra v1.6.1
28 goji.io v2.0.2+incompatible
29 golang.org/x/crypto v0.7.0
30@@ -40,16 +42,20 @@ require (
31 github.com/atotto/clipboard v0.1.4 // indirect
32 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
33 github.com/aymerick/douceur v0.2.0 // indirect
34+ github.com/beorn7/perks v1.0.1 // indirect
35 github.com/caarlos0/sshmarshal v0.1.0 // indirect
36+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
37 github.com/containerd/console v1.0.3 // indirect
38 github.com/dlclark/regexp2 v1.4.0 // indirect
39 github.com/go-logfmt/logfmt v0.6.0 // indirect
40+ github.com/golang/protobuf v1.5.2 // indirect
41 github.com/gorilla/css v1.0.0 // indirect
42 github.com/inconshreveable/mousetrap v1.0.1 // indirect
43 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
44 github.com/mattn/go-isatty v0.0.18 // indirect
45 github.com/mattn/go-localereader v0.0.1 // indirect
46 github.com/mattn/go-runewidth v0.0.14 // indirect
47+ github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
48 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 // indirect
49 github.com/microcosm-cc/bluemonday v1.0.21 // indirect
50 github.com/mitchellh/go-homedir v1.1.0 // indirect
51@@ -58,6 +64,9 @@ require (
52 github.com/muesli/mango v0.1.0 // indirect
53 github.com/muesli/mango-pflag v0.1.0 // indirect
54 github.com/olekukonko/tablewriter v0.0.5 // indirect
55+ github.com/prometheus/client_model v0.3.0 // indirect
56+ github.com/prometheus/common v0.37.0 // indirect
57+ github.com/prometheus/procfs v0.8.0 // indirect
58 github.com/rivo/uniseg v0.2.0 // indirect
59 github.com/sahilm/fuzzy v0.1.0 // indirect
60 github.com/spf13/pflag v1.0.5 // indirect
61@@ -67,4 +76,5 @@ require (
62 golang.org/x/sys v0.6.0 // indirect
63 golang.org/x/term v0.6.0 // indirect
64 golang.org/x/text v0.8.0 // indirect
65+ google.golang.org/protobuf v1.28.1 // indirect
66 )
67diff --git a/go.sum b/go.sum
68index 04e8a16f6787ffec10cbf2d570ba0392ee91e189..8587ada7d55d021c2bf620476510d3c4d68d2f0e 100644
69--- a/go.sum
70+++ b/go.sum
71@@ -1,8 +1,48 @@
72+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
73+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
74+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
75+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
76+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
77+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
78+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
79+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
80+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
81+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
82+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
83+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
84+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
85+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
86+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
87+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
88+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
89+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
90+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
91+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
92+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
93+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
94+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
95+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
96+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
97+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
98+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
99+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
100+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
101+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
102+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
103+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
104+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
105+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
106+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
107 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
108 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
109 github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
110 github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
111 github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
112+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
113+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
114+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
115+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
116+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
117 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
118 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
119 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
120@@ -16,12 +56,20 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
121 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
122 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
123 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
124+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
125+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
126+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
127+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
128 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
129-github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
130-github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
131+github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
132+github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
133 github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA=
134 github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I=
135 github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA=
136+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
137+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
138+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
139+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
140 github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI=
141 github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74=
142 github.com/charmbracelet/bubbletea v0.22.0/go.mod h1:aoVIwlNlr5wbCB26KhxfrqAn0bMp4YpJcoOelbxApjs=
143@@ -44,7 +92,12 @@ github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103 h1:wpHMERIN0pQZE
144 github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103/go.mod h1:0Vm2/8yBljiLDnGJHU8ehswfawrEybGk33j5ssqKQVM=
145 github.com/charmbracelet/wish v1.1.0 h1:0ArX9SOG70saqd23NYjoS56oLPVNgqcQegkz1Lw+4zY=
146 github.com/charmbracelet/wish v1.1.0/go.mod h1:yHbm0hs/qX4lFE7nrhAcXjFYc8bxMIfSqJOfOYfwyYo=
147+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
148+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
149+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
150+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
151 github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
152+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
153 github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
154 github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
155 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
156@@ -57,6 +110,10 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
157 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
158 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
159 github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
160+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
161+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
162+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
163+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
164 github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
165 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
166 github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
167@@ -64,23 +121,106 @@ github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw4
168 github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
169 github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
170 github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
171diff --git a/server/config/config.go b/server/config/config.go
172index 6a3d5610cdb2182c93d02607ea9f67d98a81cc10..9c8954de383fb36c1d13b77979a13256b69c510f 100644
173--- a/server/config/config.go
174+++ b/server/config/config.go
175@@ -4,7 +4,7 @@ import (
176 "os"
177 "path/filepath"
178
179- "github.com/caarlos0/env/v6"
180+ "github.com/caarlos0/env/v7"
181 "github.com/charmbracelet/log"
182 "github.com/charmbracelet/soft-serve/server/backend"
183 "gopkg.in/yaml.v3"
184@@ -61,6 +61,12 @@ type HTTPConfig struct {
185 PublicURL string `env:"PUBLIC_URL" yaml:"public_url"`
186 }
187
188+// StatsConfig is the configuration for the stats server.
189+type StatsConfig struct {
190+ // ListenAddr is the address on which the stats server will listen.
191+ ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
192+}
193+
194 // Config is the configuration for Soft Serve.
195 type Config struct {
196 // Name is the name of the server.
197@@ -75,6 +81,9 @@ type Config struct {
198 // HTTP is the configuration for the HTTP server.
199 HTTP HTTPConfig `envPrefix:"HTTP_" yaml:"http"`
200
201+ // Stats is the configuration for the stats server.
202+ Stats StatsConfig `envPrefix:"STATS_" yaml:"stats"`
203+
204 // InitialAdminKeys is a list of public keys that will be added to the list of admins.
205 InitialAdminKeys []string `env:"INITIAL_ADMIN_KEY" envSeparator:"\n" yaml:"initial_admin_keys"`
206
207@@ -92,7 +101,7 @@ func ParseConfig(path string) (*Config, error) {
208 if err != nil {
209 return nil, err
210 }
211- defer f.Close()
212+ defer f.Close() // nolint: errcheck
213 if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
214 return nil, err
215 }
216@@ -129,11 +138,14 @@ func DefaultConfig() *Config {
217 ListenAddr: ":8080",
218 PublicURL: "http://localhost:8080",
219 },
220+ Stats: StatsConfig{
221+ ListenAddr: ":8081",
222+ },
223 }
224 cp := filepath.Join(cfg.DataPath, "config.yaml")
225 f, err := os.Open(cp)
226 if err == nil {
227- defer f.Close()
228+ defer f.Close() // nolint: errcheck
229 if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
230 log.Error("failed to decode config", "err", err)
231 }
232diff --git a/server/config/file.go b/server/config/file.go
233index 932b8063700bb17ceb19649583530f02c596ae4c..8f295cf6abfddcf9d1b403a7ecdebe1cd9583d3b 100644
234--- a/server/config/file.go
235+++ b/server/config/file.go
236@@ -64,6 +64,11 @@ http:
237 # This is the address will be used to clone repositories.
238 public_url: "{{ .HTTP.PublicURL }}"
239
240+# The stats server configuration.
241+stats:
242+ # The address on which the stats server will listen.
243+ listen_addr: "{{ .Stats.ListenAddr }}"
244+
245 `))
246 )
247
248diff --git a/server/daemon.go b/server/daemon.go
249index 6424988d75d361ab9b715526942face540ce00d5..047bf4b6cd8bf4b5d69836893e469e0deb9e909e 100644
250--- a/server/daemon.go
251+++ b/server/daemon.go
252@@ -13,6 +13,24 @@ import (
253 "github.com/charmbracelet/soft-serve/server/config"
254 "github.com/charmbracelet/soft-serve/server/utils"
255 "github.com/go-git/go-git/v5/plumbing/format/pktline"
256+ "github.com/prometheus/client_golang/prometheus"
257+ "github.com/prometheus/client_golang/prometheus/promauto"
258+)
259+
260+var (
261+ uploadPackGitCounter = promauto.NewCounterVec(prometheus.CounterOpts{
262+ Namespace: "soft_serve",
263+ Subsystem: "git",
264+ Name: "git_upload_pack_total",
265+ Help: "The total number of git-upload-pack requests",
266+ }, []string{"repo"})
267+
268+ uploadArchiveGitCounter = promauto.NewCounterVec(prometheus.CounterOpts{
269+ Namespace: "soft_serve",
270+ Subsystem: "git",
271+ Name: "git_upload_archive_total",
272+ Help: "The total number of git-upload-archive requests",
273+ }, []string{"repo"})
274 )
275
276 // ErrServerClosed indicates that the server has been closed.
277@@ -184,12 +202,14 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
278 }
279
280 gitPack := uploadPack
281+ counter := uploadPackGitCounter
282 cmd := string(split[0])
283 switch cmd {
284 case uploadPackBin:
285 gitPack = uploadPack
286 case uploadArchiveBin:
287 gitPack = uploadArchive
288+ counter = uploadArchiveGitCounter
289 default:
290 fatal(c, ErrInvalidRequest)
291 return
292@@ -223,6 +243,8 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
293 fatal(c, err)
294 return
295 }
296+
297+ counter.WithLabelValues(name)
298 }
299 }
300
301diff --git a/server/http.go b/server/http.go
302index 2ac99b5e497db0ceb1d954a0a23801caa7d5f733..30f4547d9a3c44ff68d275e40020a2c3e12aeb23 100644
303--- a/server/http.go
304+++ b/server/http.go
305@@ -15,11 +15,29 @@ import (
306 "github.com/charmbracelet/soft-serve/server/config"
307 "github.com/charmbracelet/soft-serve/server/utils"
308 "github.com/dustin/go-humanize"
309+ "github.com/prometheus/client_golang/prometheus"
310+ "github.com/prometheus/client_golang/prometheus/promauto"
311 "goji.io"
312 "goji.io/pat"
313 "goji.io/pattern"
314 )
315
316+var (
317+ gitHttpCounter = promauto.NewCounterVec(prometheus.CounterOpts{
318+ Namespace: "soft_serve",
319+ Subsystem: "http",
320+ Name: "git_fetch_pull_total",
321+ Help: "The total number of git fetch/pull requests",
322+ }, []string{"repo", "file"})
323+
324+ goGetCounter = promauto.NewCounterVec(prometheus.CounterOpts{
325+ Namespace: "soft_serve",
326+ Subsystem: "http",
327+ Name: "go_get_total",
328+ Help: "The total number of go get requests",
329+ }, []string{"repo"})
330+)
331+
332 // logWriter is a wrapper around http.ResponseWriter that allows us to capture
333 // the HTTP status code and bytes written to the response.
334 type logWriter struct {
335@@ -179,12 +197,12 @@ var (
336 return findStringSubmatch(u.Path, getLooseObjectRegexp)
337 }
338
339- getPackFileRegexp = regexp.MustCompile(".*?(/objects/pack/pack-[0-9a-f]{40}\\.pack)$")
340+ getPackFileRegexp = regexp.MustCompile(`.*?(/objects/pack/pack-[0-9a-f]{40}\.pack)$`)
341 getPackFile = func(u *url.URL) *Match {
342 return findStringSubmatch(u.Path, getPackFileRegexp)
343 }
344
345- getIdxFileRegexp = regexp.MustCompile(".*?(/objects/pack/pack-[0-9a-f]{40}\\.idx)$")
346+ getIdxFileRegexp = regexp.MustCompile(`.*?(/objects/pack/pack-[0-9a-f]{40}\.idx)$`)
347 getIdxFile = func(u *url.URL) *Match {
348 return findStringSubmatch(u.Path, getIdxFileRegexp)
349 }
350@@ -264,6 +282,8 @@ func (s *HTTPServer) handleIndex(w http.ResponseWriter, r *http.Request) {
351 http.Error(w, err.Error(), http.StatusInternalServerError)
352 return
353 }
354+
355+ goGetCounter.WithLabelValues(repo).Inc()
356 }
357
358 func (s *HTTPServer) handleGit(w http.ResponseWriter, r *http.Request) {
359@@ -287,6 +307,7 @@ func (s *HTTPServer) handleGit(w http.ResponseWriter, r *http.Request) {
360 }
361
362 file := pat.Param(r, "file")
363+ gitHttpCounter.WithLabelValues(repo, file).Inc()
364 r.URL.Path = fmt.Sprintf("/%s/%s", repo, file)
365 s.dirHandler.ServeHTTP(w, r)
366 }
367diff --git a/server/server.go b/server/server.go
368index eb62692f1f77d465987f613a75cd9422e668d6d3..976b1b9fa7423976936ccf5fc03cd4c48ee6ab79 100644
369--- a/server/server.go
370+++ b/server/server.go
371@@ -21,11 +21,12 @@ var (
372
373 // Server is the Soft Serve server.
374 type Server struct {
375- SSHServer *SSHServer
376- GitDaemon *GitDaemon
377- HTTPServer *HTTPServer
378- Config *config.Config
379- Backend backend.Backend
380+ SSHServer *SSHServer
381+ GitDaemon *GitDaemon
382+ HTTPServer *HTTPServer
383+ StatsServer *StatsServer
384+ Config *config.Config
385+ Backend backend.Backend
386 }
387
388 // NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
389@@ -74,6 +75,11 @@ func NewServer(cfg *config.Config) (*Server, error) {
390 return nil, err
391 }
392
393+ srv.StatsServer, err = NewStatsServer(cfg)
394+ if err != nil {
395+ return nil, err
396+ }
397+
398 return srv, nil
399 }
400
401@@ -101,6 +107,13 @@ func (s *Server) Start() error {
402 }
403 return nil
404 })
405+ errg.Go(func() error {
406+ log.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
407+ if err := s.StatsServer.ListenAndServe(); err != http.ErrServerClosed {
408+ return err
409+ }
410+ return nil
411+ })
412 return errg.Wait()
413 }
414
415@@ -116,6 +129,9 @@ func (s *Server) Shutdown(ctx context.Context) error {
416 errg.Go(func() error {
417 return s.SSHServer.Shutdown(ctx)
418 })
419+ errg.Go(func() error {
420+ return s.StatsServer.Shutdown(ctx)
421+ })
422 return errg.Wait()
423 }
424
425@@ -125,5 +141,6 @@ func (s *Server) Close() error {
426 errg.Go(s.GitDaemon.Close)
427 errg.Go(s.HTTPServer.Close)
428 errg.Go(s.SSHServer.Close)
429+ errg.Go(s.StatsServer.Close)
430 return errg.Wait()
431 }
432diff --git a/server/session.go b/server/session.go
433index 03df433ddb530e753e083af6c55f4c4f96d9c044..6c1a655c5bd6a6588d2308affd659e0332a968cd 100644
434--- a/server/session.go
435+++ b/server/session.go
436@@ -13,15 +13,28 @@ import (
437 "github.com/charmbracelet/ssh"
438 "github.com/charmbracelet/wish"
439 bm "github.com/charmbracelet/wish/bubbletea"
440+ "github.com/prometheus/client_golang/prometheus"
441+ "github.com/prometheus/client_golang/prometheus/promauto"
442+)
443+
444+var (
445+ tuiSessionCounter = promauto.NewCounterVec(prometheus.CounterOpts{
446+ Namespace: "soft_serve",
447+ Subsystem: "ssh",
448+ Name: "tui_session_total",
449+ Help: "The total number of TUI sessions",
450+ }, []string{"key", "user", "repo", "term"})
451 )
452
453 // SessionHandler is the soft-serve bubbletea ssh session handler.
454 func SessionHandler(cfg *config.Config) bm.ProgramHandler {
455 return func(s ssh.Session) *tea.Program {
456+ ak := backend.MarshalAuthorizedKey(s.PublicKey())
457 pty, _, active := s.Pty()
458 if !active {
459 return nil
460 }
461+
462 cmd := s.Command()
463 initialRepo := ""
464 if len(cmd) == 1 {
465@@ -32,6 +45,7 @@ func SessionHandler(cfg *config.Config) bm.ProgramHandler {
466 return nil
467 }
468 }
469+
470 envs := s.Environ()
471 envs = append(envs, fmt.Sprintf("TERM=%s", pty.Term))
472 output := osc52.NewOutput(s, envs)
473@@ -45,6 +59,9 @@ func SessionHandler(cfg *config.Config) bm.ProgramHandler {
474 tea.WithoutCatchPanics(),
475 tea.WithMouseCellMotion(),
476 )
477+
478+ tuiSessionCounter.WithLabelValues(ak, s.User(), initialRepo, pty.Term).Inc()
479+
480 return p
481 }
482 }
483diff --git a/server/ssh.go b/server/ssh.go
484index 6a73e935ac43b0a25872cfb9831e61c311e98112..64f48a00f12144fb49704b2f38ca79e33bfdcd03 100644
485--- a/server/ssh.go
486+++ b/server/ssh.go
487@@ -5,6 +5,7 @@ import (
488 "errors"
489 "net"
490 "path/filepath"
491+ "strconv"
492 "strings"
493 "time"
494
495@@ -20,9 +21,55 @@ import (
496 lm "github.com/charmbracelet/wish/logging"
497 rm "github.com/charmbracelet/wish/recover"
498 "github.com/muesli/termenv"
499+ "github.com/prometheus/client_golang/prometheus"
500+ "github.com/prometheus/client_golang/prometheus/promauto"
501 gossh "golang.org/x/crypto/ssh"
502 )
503
504+var (
505+ publicKeyCounter = promauto.NewCounterVec(prometheus.CounterOpts{
506+ Namespace: "soft_serve",
507+ Subsystem: "ssh",
508+ Name: "public_key_auth_total",
509+ Help: "The total number of public key auth requests",
510+ }, []string{"key", "user", "access", "allowed"})
511+
512+ keyboardInteractiveCounter = promauto.NewCounterVec(prometheus.CounterOpts{
513+ Namespace: "soft_serve",
514+ Subsystem: "ssh",
515+ Name: "keyboard_interactive_auth_total",
516+ Help: "The total number of keyboard interactive auth requests",
517+ }, []string{"user", "allowed"})
518+
519+ uploadPackCounter = promauto.NewCounterVec(prometheus.CounterOpts{
520+ Namespace: "soft_serve",
521+ Subsystem: "ssh",
522+ Name: "git_upload_pack_total",
523+ Help: "The total number of git-upload-pack requests",
524+ }, []string{"key", "user", "repo"})
525+
526+ receivePackCounter = promauto.NewCounterVec(prometheus.CounterOpts{
527+ Namespace: "soft_serve",
528+ Subsystem: "ssh",
529+ Name: "git_receive_pack_total",
530+ Help: "The total number of git-receive-pack requests",
531+ }, []string{"key", "user", "repo"})
532+
533+ uploadArchiveCounter = promauto.NewCounterVec(prometheus.CounterOpts{
534+ Namespace: "soft_serve",
535+ Subsystem: "ssh",
536+ Name: "git_upload_archive_total",
537+ Help: "The total number of git-upload-archive requests",
538+ }, []string{"key", "user", "repo"})
539+
540+ createRepoCounter = promauto.NewCounterVec(prometheus.CounterOpts{
541+ Namespace: "soft_serve",
542+ Subsystem: "ssh",
543+ Name: "create_repo_total",
544+ Help: "The total number of create repo requests",
545+ }, []string{"key", "user", "repo"})
546+)
547+
548 // SSHServer is a SSH server that implements the git protocol.
549 type SSHServer struct {
550 srv *ssh.Server
551@@ -90,12 +137,18 @@ func (s *SSHServer) Shutdown(ctx context.Context) error {
552
553 // PublicKeyAuthHandler handles public key authentication.
554 func (s *SSHServer) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
555- return s.cfg.Backend.AccessLevel("", pk) >= backend.ReadOnlyAccess
556+ ac := s.cfg.Backend.AccessLevel("", pk)
557+ allowed := ac >= backend.ReadOnlyAccess
558+ ak := backend.MarshalAuthorizedKey(pk)
559+ publicKeyCounter.WithLabelValues(ak, ctx.User(), ac.String(), strconv.FormatBool(allowed)).Inc()
560+ return allowed
561 }
562
563 // KeyboardInteractiveHandler handles keyboard interactive authentication.
564 func (s *SSHServer) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.KeyboardInteractiveChallenge) bool {
565- return s.cfg.Backend.AllowKeyless() && s.PublicKeyHandler(ctx, nil)
566+ ac := s.cfg.Backend.AllowKeyless() && s.PublicKeyHandler(ctx, nil)
567+ keyboardInteractiveCounter.WithLabelValues(ctx.User(), strconv.FormatBool(ac)).Inc()
568+ return ac
569 }
570
571 // Middleware adds Git server functionality to the ssh.Server. Repos are stored
572@@ -113,6 +166,7 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
573 // repo should be in the form of "repo.git"
574 name := utils.SanitizeRepo(cmd[1])
575 pk := s.PublicKey()
576+ ak := backend.MarshalAuthorizedKey(pk)
577 access := cfg.Backend.AccessLevel(name, pk)
578 // git bare repositories should end in ".git"
579 // https://git-scm.com/docs/gitrepository-layout
580@@ -136,26 +190,34 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
581 sshFatal(s, err)
582 return
583 }
584+ createRepoCounter.WithLabelValues(ak, s.User(), name).Inc()
585 }
586 if err := receivePack(s, s, s.Stderr(), repoDir); err != nil {
587diff --git a/server/stats.go b/server/stats.go
588new file mode 100644
589index 0000000000000000000000000000000000000000..30b8cd0bef9ba125c293b7bd7a6d5b72e882bee0
590--- /dev/null
591+++ b/server/stats.go
592@@ -0,0 +1,48 @@
593+package server
594+
595+import (
596+ "context"
597+ "net/http"
598+ "time"
599+
600+ "github.com/charmbracelet/soft-serve/server/config"
601+ "github.com/prometheus/client_golang/prometheus/promhttp"
602+)
603+
604+// StatsServer is a server for collecting and reporting statistics.
605+type StatsServer struct {
606+ cfg *config.Config
607+ server *http.Server
608+}
609+
610+// NewStatsServer returns a new StatsServer.
611+func NewStatsServer(cfg *config.Config) (*StatsServer, error) {
612+ mux := http.NewServeMux()
613+ mux.Handle("/metrics", promhttp.Handler())
614+ return &StatsServer{
615+ cfg: cfg,
616+ server: &http.Server{
617+ Addr: cfg.Stats.ListenAddr,
618+ Handler: mux,
619+ ReadHeaderTimeout: time.Second * 10,
620+ ReadTimeout: time.Second * 10,
621+ WriteTimeout: time.Second * 10,
622+ MaxHeaderBytes: http.DefaultMaxHeaderBytes,
623+ },
624+ }, nil
625+}
626+
627+// ListenAndServe starts the StatsServer.
628+func (s *StatsServer) ListenAndServe() error {
629+ return s.server.ListenAndServe()
630+}
631+
632+// Shutdown gracefully shuts down the StatsServer.
633+func (s *StatsServer) Shutdown(ctx context.Context) error {
634+ return s.server.Shutdown(ctx)
635+}
636+
637+// Close closes the StatsServer.
638+func (s *StatsServer) Close() error {
639+ return s.server.Close()
640+}