<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://tech.uvoo.io/index.php?action=history&amp;feed=atom&amp;title=Lab3</id>
	<title>Lab3 - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://tech.uvoo.io/index.php?action=history&amp;feed=atom&amp;title=Lab3"/>
	<link rel="alternate" type="text/html" href="https://tech.uvoo.io/index.php?title=Lab3&amp;action=history"/>
	<updated>2026-05-16T00:55:31Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.35.2</generator>
	<entry>
		<id>https://tech.uvoo.io/index.php?title=Lab3&amp;diff=5602&amp;oldid=prev</id>
		<title>Busk at 03:45, 22 July 2025</title>
		<link rel="alternate" type="text/html" href="https://tech.uvoo.io/index.php?title=Lab3&amp;diff=5602&amp;oldid=prev"/>
		<updated>2025-07-22T03:45:20Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table class=&quot;diff diff-contentalign-left diff-editfont-monospace&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 03:45, 22 July 2025&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot; &gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;```&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Config Setting	Default Value&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;ingester.max_streams_per_user	10,000&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;limits_config.max_global_streams_per_user&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;```&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;```&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;loki-query-scheduler-5dc6856dc6-rhtht/log?container=query-scheduler&amp;amp;follow=true&amp;amp;tailLines=1&amp;quot;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;level=warn ts=2025-07-22T03:31:06.996612349Z caller=grpc_logging.go:76 method=/logproto.Pusher/Push duration=163.425µs msg=gRPC err=&amp;quot;rpc error: code = Code(429) desc = Maximum active stream limit exceeded when trying to create stream {_collector=&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;```&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;https://dis.gg/xxybvxxyucCVHH&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;https://dis.gg/xxybvxxyucCVHH&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Busk</name></author>
	</entry>
	<entry>
		<id>https://tech.uvoo.io/index.php?title=Lab3&amp;diff=5597&amp;oldid=prev</id>
		<title>Busk at 17:24, 16 July 2025</title>
		<link rel="alternate" type="text/html" href="https://tech.uvoo.io/index.php?title=Lab3&amp;diff=5597&amp;oldid=prev"/>
		<updated>2025-07-16T17:24:11Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table class=&quot;diff diff-contentalign-left diff-editfont-monospace&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 17:24, 16 July 2025&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot; &gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;https://dis.gg/xxybvxxyucCVHH&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot;&gt; &lt;/td&gt;&lt;td class='diff-marker'&gt;+&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;```&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;```&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;package main&lt;/div&gt;&lt;/td&gt;&lt;td class='diff-marker'&gt; &lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;package main&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Busk</name></author>
	</entry>
	<entry>
		<id>https://tech.uvoo.io/index.php?title=Lab3&amp;diff=5596&amp;oldid=prev</id>
		<title>Busk: Created page with &quot;``` package main  import ( 	&quot;bytes&quot; 	&quot;context&quot; 	&quot;crypto/tls&quot; 	&quot;fmt&quot; 	&quot;io&quot; 	&quot;net/http&quot; 	&quot;net/url&quot; 	&quot;os&quot; 	&quot;os/signal&quot; 	&quot;path&quot; 	&quot;strings&quot; 	&quot;sync&quot; 	&quot;syscall&quot; 	&quot;time&quot;  	&quot;github.com...&quot;</title>
		<link rel="alternate" type="text/html" href="https://tech.uvoo.io/index.php?title=Lab3&amp;diff=5596&amp;oldid=prev"/>
		<updated>2025-07-16T14:28:03Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;``` package main  import ( 	&amp;quot;bytes&amp;quot; 	&amp;quot;context&amp;quot; 	&amp;quot;crypto/tls&amp;quot; 	&amp;quot;fmt&amp;quot; 	&amp;quot;io&amp;quot; 	&amp;quot;net/http&amp;quot; 	&amp;quot;net/url&amp;quot; 	&amp;quot;os&amp;quot; 	&amp;quot;os/signal&amp;quot; 	&amp;quot;path&amp;quot; 	&amp;quot;strings&amp;quot; 	&amp;quot;sync&amp;quot; 	&amp;quot;syscall&amp;quot; 	&amp;quot;time&amp;quot;  	&amp;quot;github.com...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;```&lt;br /&gt;
package main&lt;br /&gt;
&lt;br /&gt;
import (&lt;br /&gt;
	&amp;quot;bytes&amp;quot;&lt;br /&gt;
	&amp;quot;context&amp;quot;&lt;br /&gt;
	&amp;quot;crypto/tls&amp;quot;&lt;br /&gt;
	&amp;quot;fmt&amp;quot;&lt;br /&gt;
	&amp;quot;io&amp;quot;&lt;br /&gt;
	&amp;quot;net/http&amp;quot;&lt;br /&gt;
	&amp;quot;net/url&amp;quot;&lt;br /&gt;
	&amp;quot;os&amp;quot;&lt;br /&gt;
	&amp;quot;os/signal&amp;quot;&lt;br /&gt;
	&amp;quot;path&amp;quot;&lt;br /&gt;
	&amp;quot;strings&amp;quot;&lt;br /&gt;
	&amp;quot;sync&amp;quot;&lt;br /&gt;
	&amp;quot;syscall&amp;quot;&lt;br /&gt;
	&amp;quot;time&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	&amp;quot;github.com/labstack/echo/v4&amp;quot;&lt;br /&gt;
	&amp;quot;github.com/labstack/echo/v4/middleware&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	&amp;quot;github.com/prometheus/client_golang/prometheus&amp;quot;&lt;br /&gt;
	&amp;quot;github.com/prometheus/client_golang/prometheus/promhttp&amp;quot;&lt;br /&gt;
	&amp;quot;github.com/sirupsen/logrus&amp;quot;&lt;br /&gt;
	&amp;quot;golang.org/x/crypto/bcrypt&amp;quot;&lt;br /&gt;
	&amp;quot;gorm.io/driver/postgres&amp;quot;&lt;br /&gt;
	&amp;quot;gorm.io/gorm&amp;quot;&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
var (&lt;br /&gt;
	db         *gorm.DB&lt;br /&gt;
	mimirURL   *url.URL&lt;br /&gt;
	mimirUser  string&lt;br /&gt;
	mimirPass  string&lt;br /&gt;
	httpClient *http.Client&lt;br /&gt;
	log        = logrus.New()&lt;br /&gt;
&lt;br /&gt;
	userCache = make(map[string]User)&lt;br /&gt;
	cacheMux  = &amp;amp;sync.RWMutex{}&lt;br /&gt;
	cacheTTL  = 30 * time.Second&lt;br /&gt;
&lt;br /&gt;
	adminUser string&lt;br /&gt;
	adminPass string&lt;br /&gt;
&lt;br /&gt;
	requestCounter = prometheus.NewCounterVec(&lt;br /&gt;
		prometheus.CounterOpts{&lt;br /&gt;
			Name: &amp;quot;proxy_requests_total&amp;quot;,&lt;br /&gt;
			Help: &amp;quot;Total proxied requests&amp;quot;,&lt;br /&gt;
		},&lt;br /&gt;
		[]string{&amp;quot;path&amp;quot;, &amp;quot;method&amp;quot;, &amp;quot;status&amp;quot;},&lt;br /&gt;
	)&lt;br /&gt;
&lt;br /&gt;
	requestDuration = prometheus.NewHistogramVec(&lt;br /&gt;
		prometheus.HistogramOpts{&lt;br /&gt;
			Name:    &amp;quot;proxy_request_duration_seconds&amp;quot;,&lt;br /&gt;
			Help:    &amp;quot;Duration of proxied requests&amp;quot;,&lt;br /&gt;
			Buckets: prometheus.DefBuckets,&lt;br /&gt;
		},&lt;br /&gt;
		[]string{&amp;quot;path&amp;quot;},&lt;br /&gt;
	)&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
type User struct {&lt;br /&gt;
	Username      string    `gorm:&amp;quot;primaryKey&amp;quot;`&lt;br /&gt;
	Password1     string&lt;br /&gt;
	Password2     string&lt;br /&gt;
	OrgID         string&lt;br /&gt;
	LastLogin     time.Time&lt;br /&gt;
	LastLoginIP   string&lt;br /&gt;
	FailedLogins  int&lt;br /&gt;
	LastFailedIP  string&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func main() {&lt;br /&gt;
	initLogger()&lt;br /&gt;
	validateEnvVars(&amp;quot;MIMIR_URL&amp;quot;, &amp;quot;MIMIR_USERNAME&amp;quot;, &amp;quot;MIMIR_PASSWORD&amp;quot;)&lt;br /&gt;
	openDB()&lt;br /&gt;
&lt;br /&gt;
	adminUser = os.Getenv(&amp;quot;ADMIN_USERNAME&amp;quot;)&lt;br /&gt;
	adminPass = os.Getenv(&amp;quot;ADMIN_PASSWORD&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	var err error&lt;br /&gt;
	mimirURL, err = url.Parse(os.Getenv(&amp;quot;MIMIR_URL&amp;quot;))&lt;br /&gt;
	if err != nil {&lt;br /&gt;
		log.WithError(err).Fatal(&amp;quot;Invalid MIMIR_URL&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
	mimirUser = os.Getenv(&amp;quot;MIMIR_USERNAME&amp;quot;)&lt;br /&gt;
	mimirPass = os.Getenv(&amp;quot;MIMIR_PASSWORD&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	if os.Getenv(&amp;quot;BACKEND_SKIP_TLS_VERIFY&amp;quot;) == &amp;quot;true&amp;quot; {&lt;br /&gt;
		log.Warn(&amp;quot;BACKEND_SKIP_TLS_VERIFY is true — skipping TLS verification for backend requests!&amp;quot;)&lt;br /&gt;
		httpClient = &amp;amp;http.Client{&lt;br /&gt;
			Timeout: 30 * time.Second,&lt;br /&gt;
			Transport: &amp;amp;http.Transport{&lt;br /&gt;
				TLSClientConfig: &amp;amp;tls.Config{InsecureSkipVerify: true},&lt;br /&gt;
			},&lt;br /&gt;
		}&lt;br /&gt;
	} else {&lt;br /&gt;
		httpClient = &amp;amp;http.Client{Timeout: 30 * time.Second}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	loadUserCache()&lt;br /&gt;
	go cacheRefresher()&lt;br /&gt;
&lt;br /&gt;
	prometheus.MustRegister(requestCounter)&lt;br /&gt;
	prometheus.MustRegister(requestDuration)&lt;br /&gt;
&lt;br /&gt;
	e := echo.New()&lt;br /&gt;
	e.HideBanner = true&lt;br /&gt;
	e.Use(middleware.Recover())&lt;br /&gt;
	e.Use(middleware.Logger())&lt;br /&gt;
&lt;br /&gt;
	// health &amp;amp; metrics&lt;br /&gt;
	e.GET(&amp;quot;/healthz&amp;quot;, func(c echo.Context) error { return c.String(http.StatusOK, &amp;quot;ok&amp;quot;) })&lt;br /&gt;
	e.GET(&amp;quot;/metrics&amp;quot;, echo.WrapHandler(promhttp.Handler()))&lt;br /&gt;
	e.GET(&amp;quot;/version&amp;quot;, func(c echo.Context) error {&lt;br /&gt;
		version := os.Getenv(&amp;quot;VERSION&amp;quot;)&lt;br /&gt;
		if version == &amp;quot;&amp;quot; {&lt;br /&gt;
			version = &amp;quot;unknown&amp;quot;&lt;br /&gt;
		}&lt;br /&gt;
		return c.String(http.StatusOK, fmt.Sprintf(&amp;quot;mimirproxy version: %s\n&amp;quot;, version))&lt;br /&gt;
	})&lt;br /&gt;
&lt;br /&gt;
	// admin endpoints&lt;br /&gt;
	admin := e.Group(&amp;quot;/admin&amp;quot;, adminAuth)&lt;br /&gt;
	admin.GET(&amp;quot;/refresh&amp;quot;, adminRefresh)&lt;br /&gt;
	admin.GET(&amp;quot;/stats&amp;quot;, adminStats)&lt;br /&gt;
	admin.POST(&amp;quot;/users&amp;quot;, adminAddUser)&lt;br /&gt;
	admin.DELETE(&amp;quot;/users&amp;quot;, adminDeleteUser)&lt;br /&gt;
&lt;br /&gt;
	// proxy endpoints&lt;br /&gt;
	for _, p := range getAllowedPaths() {&lt;br /&gt;
		e.Any(p, handleProxy)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// graceful shutdown&lt;br /&gt;
	go func() {&lt;br /&gt;
		if err := e.Start(&amp;quot;:8080&amp;quot;); err != nil &amp;amp;&amp;amp; err != http.ErrServerClosed {&lt;br /&gt;
			log.WithError(err).Fatal(&amp;quot;server error&amp;quot;)&lt;br /&gt;
		}&lt;br /&gt;
	}()&lt;br /&gt;
&lt;br /&gt;
	quit := make(chan os.Signal, 1)&lt;br /&gt;
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)&lt;br /&gt;
	&amp;lt;-quit&lt;br /&gt;
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)&lt;br /&gt;
	defer cancel()&lt;br /&gt;
	if err := e.Shutdown(ctx); err != nil {&lt;br /&gt;
		log.WithError(err).Fatal(&amp;quot;forced shutdown&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
	log.Info(&amp;quot;Server stopped&amp;quot;)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func getAllowedPaths() []string {&lt;br /&gt;
	env := os.Getenv(&amp;quot;ALLOWED_PATHS&amp;quot;)&lt;br /&gt;
	if env != &amp;quot;&amp;quot; {&lt;br /&gt;
		parts := strings.Split(env, &amp;quot;,&amp;quot;)&lt;br /&gt;
		for i := range parts {&lt;br /&gt;
			parts[i] = strings.TrimSpace(parts[i])&lt;br /&gt;
		}&lt;br /&gt;
		return parts&lt;br /&gt;
	}&lt;br /&gt;
	return []string{&lt;br /&gt;
		&amp;quot;/prometheus/*&amp;quot;,&lt;br /&gt;
		&amp;quot;/api/v1/*&amp;quot;,&lt;br /&gt;
		&amp;quot;/otlp/v1/metrics&amp;quot;,&lt;br /&gt;
		&amp;quot;/distributor&amp;quot;,&lt;br /&gt;
		&amp;quot;/prometheus/api/v1/rules&amp;quot;,&lt;br /&gt;
		&amp;quot;/prometheus/api/v1/alerts&amp;quot;,&lt;br /&gt;
		&amp;quot;/prometheus/config/v1/rules&amp;quot;,&lt;br /&gt;
		&amp;quot;/api/v1/status/buildinfo&amp;quot;,&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func handleProxy(c echo.Context) error {&lt;br /&gt;
	start := time.Now()&lt;br /&gt;
	if !authenticate(c.Request()) {&lt;br /&gt;
		requestCounter.WithLabelValues(c.Path(), c.Request().Method, &amp;quot;401&amp;quot;).Inc()&lt;br /&gt;
		return echo.NewHTTPError(http.StatusUnauthorized)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	username, _, _ := c.Request().BasicAuth()&lt;br /&gt;
	orgID := userCache[username].OrgID&lt;br /&gt;
&lt;br /&gt;
	backendURL := *mimirURL&lt;br /&gt;
	backendURL.Path = path.Join(mimirURL.Path, c.Request().URL.Path)&lt;br /&gt;
	backendURL.RawQuery = c.Request().URL.RawQuery&lt;br /&gt;
&lt;br /&gt;
	var body io.Reader&lt;br /&gt;
	if c.Request().Body != nil {&lt;br /&gt;
		data, err := io.ReadAll(c.Request().Body)&lt;br /&gt;
		if err != nil {&lt;br /&gt;
			requestCounter.WithLabelValues(c.Path(), c.Request().Method, &amp;quot;500&amp;quot;).Inc()&lt;br /&gt;
			return echo.NewHTTPError(http.StatusInternalServerError)&lt;br /&gt;
		}&lt;br /&gt;
		body = bytes.NewReader(data)&lt;br /&gt;
		defer c.Request().Body.Close()&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	req, err := http.NewRequestWithContext(c.Request().Context(), c.Request().Method, backendURL.String(), body)&lt;br /&gt;
	if err != nil {&lt;br /&gt;
		requestCounter.WithLabelValues(c.Path(), c.Request().Method, &amp;quot;500&amp;quot;).Inc()&lt;br /&gt;
		return echo.NewHTTPError(http.StatusInternalServerError)&lt;br /&gt;
	}&lt;br /&gt;
	req.Header = c.Request().Header.Clone()&lt;br /&gt;
	req.SetBasicAuth(mimirUser, mimirPass)&lt;br /&gt;
	if orgID != &amp;quot;&amp;quot; {&lt;br /&gt;
		req.Header.Set(&amp;quot;X-Scope-OrgID&amp;quot;, orgID)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	resp, err := httpClient.Do(req)&lt;br /&gt;
	if err != nil {&lt;br /&gt;
		requestCounter.WithLabelValues(c.Path(), c.Request().Method, &amp;quot;502&amp;quot;).Inc()&lt;br /&gt;
		return echo.NewHTTPError(http.StatusBadGateway)&lt;br /&gt;
	}&lt;br /&gt;
	defer resp.Body.Close()&lt;br /&gt;
&lt;br /&gt;
	statusCode := resp.StatusCode&lt;br /&gt;
	requestCounter.WithLabelValues(c.Path(), c.Request().Method, fmt.Sprintf(&amp;quot;%d&amp;quot;, statusCode)).Inc()&lt;br /&gt;
	requestDuration.WithLabelValues(c.Path()).Observe(time.Since(start).Seconds())&lt;br /&gt;
&lt;br /&gt;
	c.Response().WriteHeader(resp.StatusCode)&lt;br /&gt;
	io.Copy(c.Response(), resp.Body)&lt;br /&gt;
&lt;br /&gt;
	log.WithFields(logrus.Fields{&lt;br /&gt;
		&amp;quot;path&amp;quot;:     c.Path(),&lt;br /&gt;
		&amp;quot;method&amp;quot;:   c.Request().Method,&lt;br /&gt;
		&amp;quot;status&amp;quot;:   resp.StatusCode,&lt;br /&gt;
		&amp;quot;duration&amp;quot;: time.Since(start),&lt;br /&gt;
	}).Info(&amp;quot;proxied request&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	return nil&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func adminRefresh(c echo.Context) error {&lt;br /&gt;
	loadUserCache()&lt;br /&gt;
	cacheMux.RLock()&lt;br /&gt;
	defer cacheMux.RUnlock()&lt;br /&gt;
&lt;br /&gt;
	var buf strings.Builder&lt;br /&gt;
	buf.WriteString(&amp;quot;Current cached users:\n&amp;quot;)&lt;br /&gt;
	for u, data := range userCache {&lt;br /&gt;
		buf.WriteString(fmt.Sprintf(&amp;quot;- %s (org_id: %s)\n&amp;quot;, u, data.OrgID))&lt;br /&gt;
	}&lt;br /&gt;
	return c.String(http.StatusOK, buf.String())&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func adminStats(c echo.Context) error {&lt;br /&gt;
	var users []User&lt;br /&gt;
	if err := db.Find(&amp;amp;users).Error; err != nil {&lt;br /&gt;
		return echo.NewHTTPError(http.StatusInternalServerError, &amp;quot;Failed to load users&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	var buf strings.Builder&lt;br /&gt;
	buf.WriteString(&amp;quot;=== Mimir Proxy Stats ===\n&amp;quot;)&lt;br /&gt;
	buf.WriteString(fmt.Sprintf(&amp;quot;Total Users: %d\n\n&amp;quot;, len(users)))&lt;br /&gt;
	buf.WriteString(fmt.Sprintf(&amp;quot;%-20s %-20s %-20s %-10s\n&amp;quot;, &amp;quot;Username&amp;quot;, &amp;quot;OrgID&amp;quot;, &amp;quot;LastLogin&amp;quot;, &amp;quot;FailedLogins&amp;quot;))&lt;br /&gt;
	for _, u := range users {&lt;br /&gt;
		buf.WriteString(fmt.Sprintf(&amp;quot;%-20s %-20s %-20s %-10d\n&amp;quot;,&lt;br /&gt;
			u.Username, u.OrgID, u.LastLogin.Format(time.RFC3339), u.FailedLogins))&lt;br /&gt;
	}&lt;br /&gt;
	return c.String(http.StatusOK, buf.String())&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func adminAddUser(c echo.Context) error {&lt;br /&gt;
	username := c.FormValue(&amp;quot;username&amp;quot;)&lt;br /&gt;
	password := c.FormValue(&amp;quot;password&amp;quot;)&lt;br /&gt;
	orgID := c.FormValue(&amp;quot;org_id&amp;quot;)&lt;br /&gt;
	which := c.FormValue(&amp;quot;which&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	if username == &amp;quot;&amp;quot; || password == &amp;quot;&amp;quot; {&lt;br /&gt;
		return echo.NewHTTPError(http.StatusBadRequest, &amp;quot;username &amp;amp; password required&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)&lt;br /&gt;
	if err != nil {&lt;br /&gt;
		return echo.NewHTTPError(http.StatusInternalServerError, &amp;quot;bcrypt error&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	var u User&lt;br /&gt;
	db.First(&amp;amp;u, &amp;quot;username = ?&amp;quot;, username)&lt;br /&gt;
&lt;br /&gt;
	if u.Username == &amp;quot;&amp;quot; {&lt;br /&gt;
		u.Username = username&lt;br /&gt;
		u.OrgID = orgID&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if which == &amp;quot;2&amp;quot; {&lt;br /&gt;
		u.Password2 = string(hash)&lt;br /&gt;
	} else {&lt;br /&gt;
		u.Password1 = string(hash)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if err := db.WithContext(c.Request().Context()).Save(&amp;amp;u).Error; err != nil {&lt;br /&gt;
		return echo.NewHTTPError(http.StatusInternalServerError, &amp;quot;Failed to save user&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	loadUserCache()&lt;br /&gt;
	return c.String(http.StatusOK, fmt.Sprintf(&amp;quot;Added/Updated user: %s\n&amp;quot;, username))&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func adminDeleteUser(c echo.Context) error {&lt;br /&gt;
	username := c.FormValue(&amp;quot;username&amp;quot;)&lt;br /&gt;
	if username == &amp;quot;&amp;quot; {&lt;br /&gt;
		return echo.NewHTTPError(http.StatusBadRequest, &amp;quot;username required&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if err := db.WithContext(c.Request().Context()).Delete(&amp;amp;User{}, &amp;quot;username = ?&amp;quot;, username).Error; err != nil {&lt;br /&gt;
		return echo.NewHTTPError(http.StatusInternalServerError, &amp;quot;Failed to delete user&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	loadUserCache()&lt;br /&gt;
	return c.String(http.StatusOK, fmt.Sprintf(&amp;quot;Deleted user: %s\n&amp;quot;, username))&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func adminAuth(next echo.HandlerFunc) echo.HandlerFunc {&lt;br /&gt;
	return func(c echo.Context) error {&lt;br /&gt;
		username, password, ok := c.Request().BasicAuth()&lt;br /&gt;
		if !ok || username != adminUser || password != adminPass {&lt;br /&gt;
			return echo.NewHTTPError(http.StatusUnauthorized)&lt;br /&gt;
		}&lt;br /&gt;
		return next(c)&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func authenticate(r *http.Request) bool {&lt;br /&gt;
	username, password, ok := r.BasicAuth()&lt;br /&gt;
	if !ok {&lt;br /&gt;
		return false&lt;br /&gt;
	}&lt;br /&gt;
	ip := getClientIP(r)&lt;br /&gt;
	return validateUser(username, password, ip)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func getClientIP(r *http.Request) string {&lt;br /&gt;
	xForwardedFor := r.Header.Get(&amp;quot;X-Forwarded-For&amp;quot;)&lt;br /&gt;
	if xForwardedFor != &amp;quot;&amp;quot; {&lt;br /&gt;
		parts := strings.Split(xForwardedFor, &amp;quot;,&amp;quot;)&lt;br /&gt;
		return strings.TrimSpace(parts[0])&lt;br /&gt;
	}&lt;br /&gt;
	ip := strings.Split(r.RemoteAddr, &amp;quot;:&amp;quot;)[0]&lt;br /&gt;
	return ip&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func validateUser(username, password, ip string) bool {&lt;br /&gt;
	cacheMux.RLock()&lt;br /&gt;
	u, ok := userCache[username]&lt;br /&gt;
	cacheMux.RUnlock()&lt;br /&gt;
&lt;br /&gt;
	if !ok {&lt;br /&gt;
		incrementFailed(username, ip)&lt;br /&gt;
		return false&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if bcrypt.CompareHashAndPassword([]byte(u.Password1), []byte(password)) == nil ||&lt;br /&gt;
		(u.Password2 != &amp;quot;&amp;quot; &amp;amp;&amp;amp; bcrypt.CompareHashAndPassword([]byte(u.Password2), []byte(password)) == nil) {&lt;br /&gt;
		updateLastLogin(username, ip)&lt;br /&gt;
		return true&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	incrementFailed(username, ip)&lt;br /&gt;
	return false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func incrementFailed(username, ip string) {&lt;br /&gt;
	db.Model(&amp;amp;User{}).Where(&amp;quot;username = ?&amp;quot;, username).Updates(map[string]interface{}{&lt;br /&gt;
		&amp;quot;failed_logins&amp;quot;:  gorm.Expr(&amp;quot;failed_logins + 1&amp;quot;),&lt;br /&gt;
		&amp;quot;last_failed_ip&amp;quot;: ip,&lt;br /&gt;
	})&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func updateLastLogin(username, ip string) {&lt;br /&gt;
	db.Model(&amp;amp;User{}).Where(&amp;quot;username = ?&amp;quot;, username).Updates(map[string]interface{}{&lt;br /&gt;
		&amp;quot;last_login&amp;quot;:    time.Now(),&lt;br /&gt;
		&amp;quot;failed_logins&amp;quot;: 0,&lt;br /&gt;
		&amp;quot;last_login_ip&amp;quot;: ip,&lt;br /&gt;
	})&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func loadUserCache() {&lt;br /&gt;
	var users []User&lt;br /&gt;
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)&lt;br /&gt;
	defer cancel()&lt;br /&gt;
&lt;br /&gt;
	if err := db.WithContext(ctx).Find(&amp;amp;users).Error; err != nil {&lt;br /&gt;
		log.WithError(err).Error(&amp;quot;Failed to load users&amp;quot;)&lt;br /&gt;
		return&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	tmp := make(map[string]User)&lt;br /&gt;
	for _, u := range users {&lt;br /&gt;
		tmp[u.Username] = u&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	cacheMux.Lock()&lt;br /&gt;
	userCache = tmp&lt;br /&gt;
	cacheMux.Unlock()&lt;br /&gt;
	log.WithField(&amp;quot;count&amp;quot;, len(userCache)).Info(&amp;quot;User cache refreshed&amp;quot;)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func initLogger() {&lt;br /&gt;
	levelStr := strings.ToLower(os.Getenv(&amp;quot;LOG_LEVEL&amp;quot;))&lt;br /&gt;
	if levelStr == &amp;quot;&amp;quot; {&lt;br /&gt;
		levelStr = &amp;quot;info&amp;quot;&lt;br /&gt;
	}&lt;br /&gt;
	level, err := logrus.ParseLevel(levelStr)&lt;br /&gt;
	if err != nil {&lt;br /&gt;
		level = logrus.InfoLevel&lt;br /&gt;
	}&lt;br /&gt;
	log.SetLevel(level)&lt;br /&gt;
	log.SetFormatter(&amp;amp;logrus.JSONFormatter{TimestampFormat: time.RFC3339})&lt;br /&gt;
	log.SetOutput(os.Stdout)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func validateEnvVars(vars ...string) {&lt;br /&gt;
	for _, v := range vars {&lt;br /&gt;
		if os.Getenv(v) == &amp;quot;&amp;quot; {&lt;br /&gt;
			log.WithField(&amp;quot;var&amp;quot;, v).Fatal(&amp;quot;Environment variable must be set&amp;quot;)&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func openDB() {&lt;br /&gt;
	dsn := fmt.Sprintf(&lt;br /&gt;
		&amp;quot;host=%s port=%s dbname=%s user=%s password=%s sslmode=disable&amp;quot;,&lt;br /&gt;
		os.Getenv(&amp;quot;POSTGRES_HOST&amp;quot;), os.Getenv(&amp;quot;POSTGRES_PORT&amp;quot;),&lt;br /&gt;
		os.Getenv(&amp;quot;POSTGRES_DB&amp;quot;), os.Getenv(&amp;quot;POSTGRES_USER&amp;quot;), os.Getenv(&amp;quot;POSTGRES_PASSWORD&amp;quot;),&lt;br /&gt;
	)&lt;br /&gt;
	var err error&lt;br /&gt;
	db, err = gorm.Open(postgres.Open(dsn), &amp;amp;gorm.Config{})&lt;br /&gt;
	if err != nil {&lt;br /&gt;
		log.WithError(err).Fatal(&amp;quot;Failed to connect to Postgres&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
	if err := db.AutoMigrate(&amp;amp;User{}); err != nil {&lt;br /&gt;
		log.WithError(err).Fatal(&amp;quot;Failed to migrate schema&amp;quot;)&lt;br /&gt;
	}&lt;br /&gt;
	log.Info(&amp;quot;Connected to Postgres and ensured schema&amp;quot;)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Busk</name></author>
	</entry>
</feed>