feat: name change + bugfixes
This commit is contained in:
+3
-3
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"kooltab-updateserver/internal/db"
|
||||
"kooltab-updateserver/internal/routes"
|
||||
"kooltap-updateserver/internal/db"
|
||||
"kooltap-updateserver/internal/routes"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -28,7 +28,7 @@ func main() {
|
||||
})
|
||||
|
||||
r.Use(func(c *gin.Context) {
|
||||
c.Header("Server", "KoolTab Updater")
|
||||
c.Header("Server", "KoolTap Updater")
|
||||
c.Next()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
services:
|
||||
kooltab-updater:
|
||||
kooltap-updater:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
@@ -8,16 +8,24 @@ services:
|
||||
|
||||
environment:
|
||||
GIN_MODE: release
|
||||
# Generated by Coolify (see Environment Variables); needed in the X-Admin-Token header to upload releases
|
||||
ADMIN_TOKEN: ${SERVICE_PASSWORD_64_ADMINTOKEN}
|
||||
|
||||
volumes:
|
||||
- kooltab_uploads:/app/uploads
|
||||
- kooltab_tmp:/app/tmp
|
||||
- kooltab_data:/app/data
|
||||
- kooltap_uploads:/app/uploads
|
||||
- kooltap_tmp:/app/tmp
|
||||
- kooltap_data:/app/data
|
||||
|
||||
expose:
|
||||
- "3000"
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
kooltab_uploads:
|
||||
kooltab_tmp:
|
||||
kooltab_data:
|
||||
kooltap_uploads:
|
||||
kooltap_tmp:
|
||||
kooltap_data:
|
||||
|
||||
+4
-3
@@ -1,9 +1,9 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
kooltab-updater:
|
||||
image: kooltab-updater:latest
|
||||
container_name: kooltab-updater
|
||||
kooltap-updater:
|
||||
image: kooltap-updater:latest
|
||||
container_name: kooltap-updater
|
||||
restart: unless-stopped
|
||||
|
||||
#ports:
|
||||
@@ -11,6 +11,7 @@ services:
|
||||
|
||||
environment:
|
||||
GIN_MODE: release
|
||||
ADMIN_TOKEN: ${ADMIN_TOKEN}
|
||||
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module kooltab-updateserver
|
||||
module kooltap-updateserver
|
||||
|
||||
go 1.26
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
@@ -15,7 +16,9 @@ func AdminAuth() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
|
||||
if c.GetHeader("X-Admin-Token") != token {
|
||||
// Without a configured token every admin request is refused, never allowed
|
||||
provided := c.GetHeader("X-Admin-Token")
|
||||
if token == "" || subtle.ConstantTimeCompare([]byte(provided), []byte(token)) != 1 {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Unauthorized",
|
||||
})
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func status(t *testing.T, configured, provided string) int {
|
||||
t.Setenv("ADMIN_TOKEN", configured)
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
r := gin.New()
|
||||
r.POST("/admin", AdminAuth(), func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/admin", nil)
|
||||
if provided != "" {
|
||||
req.Header.Set("X-Admin-Token", provided)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
return w.Code
|
||||
}
|
||||
|
||||
func TestAdminAuth(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, configured, provided string
|
||||
want int
|
||||
}{
|
||||
{"no token configured refuses requests without a header", "", "", http.StatusUnauthorized},
|
||||
{"no token configured refuses any header", "", "anything", http.StatusUnauthorized},
|
||||
{"wrong token", "secret", "nope", http.StatusUnauthorized},
|
||||
{"missing header", "secret", "", http.StatusUnauthorized},
|
||||
{"correct token", "secret", "secret", http.StatusOK},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := status(t, tc.configured, tc.provided); got != tc.want {
|
||||
t.Fatalf("got %d, want %d", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"kooltab-updateserver/internal/models"
|
||||
"kooltap-updateserver/internal/models"
|
||||
"log"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
|
||||
@@ -3,11 +3,12 @@ package routes
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"kooltab-updateserver/internal/apk_util"
|
||||
"kooltab-updateserver/internal/auth"
|
||||
"kooltab-updateserver/internal/db"
|
||||
"kooltab-updateserver/internal/models"
|
||||
"kooltap-updateserver/internal/apk_util"
|
||||
"kooltap-updateserver/internal/auth"
|
||||
"kooltap-updateserver/internal/db"
|
||||
"kooltap-updateserver/internal/models"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -24,6 +25,8 @@ func Register(r *gin.Engine) {
|
||||
api.GET("/releases", ListReleases)
|
||||
}
|
||||
|
||||
r.GET("/download/latest", DownloadLatest)
|
||||
|
||||
admin := r.Group("/admin")
|
||||
admin.Use(auth.AdminAuth())
|
||||
{
|
||||
@@ -38,9 +41,8 @@ func CheckUpdate(c *gin.Context) {
|
||||
|
||||
var latest models.Release
|
||||
|
||||
if err := db.DB.
|
||||
Order("created_at desc").
|
||||
First(&latest).Error; err != nil {
|
||||
// The highest Android version code is the latest build, regardless of upload order
|
||||
if err := latestRelease(&latest); err != nil {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"update": false,
|
||||
@@ -79,6 +81,27 @@ func CheckUpdate(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// DownloadLatest redirects to the newest APK, for installing on a new tablet from a browser
|
||||
func DownloadLatest(c *gin.Context) {
|
||||
|
||||
var latest models.Release
|
||||
|
||||
if err := latestRelease(&latest); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "No releases yet",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/downloads/"+latest.APKFile)
|
||||
}
|
||||
|
||||
func latestRelease(release *models.Release) error {
|
||||
return db.DB.
|
||||
Order("version_code desc").
|
||||
First(release).Error
|
||||
}
|
||||
|
||||
func ListReleases(c *gin.Context) {
|
||||
|
||||
var releases []models.Release
|
||||
@@ -124,6 +147,16 @@ func UploadRelease(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Tablets only see the release with the highest version code, so an older or equal one would never be offered
|
||||
var latest models.Release
|
||||
if err := latestRelease(&latest); err == nil && int64(info.VersionCode) <= latest.VersionCode {
|
||||
c.JSON(http.StatusConflict, gin.H{
|
||||
"error": fmt.Sprintf("version code %d must be higher than the latest release (%s, code %d)",
|
||||
info.VersionCode, latest.Version, latest.VersionCode),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
filename := "app-" + info.Version + ".apk"
|
||||
path := filepath.Join("uploads", filename)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user