mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-29 12:30:11 +00:00
Compare commits
8 Commits
9787fce275
...
7bd8fadf76
Author | SHA1 | Date | |
---|---|---|---|
7bd8fadf76 | |||
21490faa9e | |||
f685582e1a | |||
f792166523 | |||
7c0754a70c | |||
2f33580f32 | |||
eb8f2777ae | |||
92332206f0 |
36
.drone.yml
36
.drone.yml
@ -11,6 +11,8 @@ steps:
|
||||
- name: release
|
||||
image: golang:latest
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
commands:
|
||||
@ -18,7 +20,12 @@ steps:
|
||||
- apt install build-essential python3-pip curl software-properties-common sed upx -y
|
||||
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
|
||||
- apt install nodejs
|
||||
- curl -sL https://git.io/goreleaser | bash
|
||||
- curl -sL https://git.io/goreleaser > goreleaser
|
||||
- chmod +x goreleaser
|
||||
- ./scripts/version.sh ./goreleaser
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- pip3 install requests
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag internal=true'
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
@ -33,6 +40,9 @@ steps:
|
||||
volumes:
|
||||
- name: ssh_key
|
||||
path: /root/drone_rsa
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
settings:
|
||||
host:
|
||||
from_secret: ssh2_host
|
||||
@ -44,8 +54,14 @@ steps:
|
||||
- /root/.ssh/docker-build:/root/drone_rsa
|
||||
key_path: /root/drone_rsa
|
||||
command_timeout: 50m
|
||||
envs:
|
||||
- BUILDRONE_KEY
|
||||
script:
|
||||
- /mnt/buildx/jfa-go/build.sh stable
|
||||
- wget https://builds.hrfee.pw/upload.py -O /mnt/buildx/jfa-go/jfa-go/upload.py
|
||||
- pip3 install requests
|
||||
- bash -c 'cd /mnt/buildx/jfa-go/jfa-go && BUILDRONE_KEY=$(cat /mnt/buildx/jfa-go/key) python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-stable=true'
|
||||
- rm -f /mnt/buildx/jfa-go/jfa-go/upload.py
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
@ -66,12 +82,12 @@ steps:
|
||||
- apt install build-essential python3-pip curl software-properties-common sed upx -y
|
||||
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
|
||||
- apt install nodejs
|
||||
- curl -sL https://git.io/goreleaser > goreleaser.sh
|
||||
- chmod +x goreleaser.sh
|
||||
- ./goreleaser.sh --snapshot --skip-publish --rm-dist
|
||||
- curl -sL https://git.io/goreleaser > goreleaser
|
||||
- chmod +x goreleaser
|
||||
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --rm-dist
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- pip3 install requests
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go ./dist/*.tar.gz'
|
||||
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --upload ./dist/*.tar.gz --tag internal-git=true'
|
||||
environment:
|
||||
BUILDRONE_KEY:
|
||||
from_secret: BUILDRONE_KEY
|
||||
@ -108,6 +124,10 @@ steps:
|
||||
command_timeout: 50m
|
||||
script:
|
||||
- /mnt/buildx/jfa-go/build.sh
|
||||
- wget https://builds.hrfee.pw/upload.py -O /mnt/buildx/jfa-go/jfa-go/upload.py
|
||||
- pip3 install requests
|
||||
- bash -c 'cd /mnt/buildx/jfa-go/jfa-go && BUILDRONE_KEY=$(cat /mnt/buildx/jfa-go/key) python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-unstable=true'
|
||||
- rm -f /mnt/buildx/jfa-go/jfa-go/upload.py
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
@ -132,9 +152,9 @@ steps:
|
||||
- apt install build-essential python3-pip curl software-properties-common sed upx -y
|
||||
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
|
||||
- apt install nodejs
|
||||
- curl -sL https://git.io/goreleaser > goreleaser.sh
|
||||
- chmod +x goreleaser.sh
|
||||
- ./goreleaser.sh --snapshot --skip-publish --rm-dist
|
||||
- curl -sL https://git.io/goreleaser > goreleaser
|
||||
- chmod +x goreleaser
|
||||
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --rm-dist
|
||||
|
||||
trigger:
|
||||
event:
|
||||
|
@ -17,6 +17,7 @@ before:
|
||||
- cp node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 data/web/css/
|
||||
- cp -r html data/
|
||||
- cp -r lang data/
|
||||
- cp LICENSE data/
|
||||
- python3 scripts/enumerate_config.py -i config/config-base.json -o data/config-base.json
|
||||
- python3 scripts/generate_ini.py -i config/config-base.json -o data/config-default.ini
|
||||
- python3 scripts/compile_mjml.py -o data/
|
||||
@ -30,6 +31,8 @@ builds:
|
||||
- dir: ./
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
|
@ -16,7 +16,7 @@ ENV GOARCH=$TARGETARCH
|
||||
|
||||
COPY --from=support /opt/build /opt/build
|
||||
|
||||
RUN (cd /opt/build; make compile)
|
||||
RUN (cd /opt/build; make compile UPDATER=docker)
|
||||
|
||||
FROM golang:latest
|
||||
|
||||
|
12
Makefile
12
Makefile
@ -10,6 +10,14 @@ VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit)
|
||||
VERSION := $(shell echo $(VERSION) | sed 's/v//g')
|
||||
COMMIT ?= $(shell git rev-parse --short HEAD || echo unknown)
|
||||
|
||||
UPDATER ?= off
|
||||
BUILDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT)
|
||||
ifeq ($(UPDATER), on)
|
||||
BUILDFLAGS := $(BUILDFLAGS) -X main.updater=binary
|
||||
else ifneq ($(UPDATER), off)
|
||||
BUILDFLAGS := $(BUILDFLAGS) -X main.updater=$(UPDATER)
|
||||
endif
|
||||
|
||||
npm:
|
||||
$(info installing npm dependencies)
|
||||
npm install
|
||||
@ -56,14 +64,14 @@ compile:
|
||||
$(GOBINARY) mod download
|
||||
$(info Building)
|
||||
mkdir -p build
|
||||
cd build && CGO_ENABLED=0 $(GOBINARY) build -ldflags="-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT)" -o ./jfa-go ../*.go
|
||||
cd build && CGO_ENABLED=0 $(GOBINARY) build -ldflags="-s -w $(BUILDFLAGS)" -o ./jfa-go ../*.go
|
||||
|
||||
compile-debug:
|
||||
$(info Downloading deps)
|
||||
$(GOBINARY) mod download
|
||||
$(info Building)
|
||||
mkdir -p build
|
||||
cd build && CGO_ENABLED=0 $(GOBINARY) build -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT)" -o ./jfa-go ../*.go
|
||||
cd build && CGO_ENABLED=0 $(GOBINARY) build -ldflags "$(BUILDFLAGS)" -o ./jfa-go ../*.go
|
||||
|
||||
compress:
|
||||
upx --lzma build/jfa-go
|
||||
|
33
api.go
33
api.go
@ -1440,6 +1440,39 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns whether there's a new update, and extra info if there is.
|
||||
// @Produce json
|
||||
// @Success 200 {object} checkUpdateDTO
|
||||
// @Router /config/update [get]
|
||||
// @tags Configuration
|
||||
func (app *appContext) CheckUpdate(gc *gin.Context) {
|
||||
if !app.newUpdate {
|
||||
app.update = Update{}
|
||||
}
|
||||
gc.JSON(200, checkUpdateDTO{New: app.newUpdate, Update: app.update})
|
||||
}
|
||||
|
||||
// @Summary Apply an update.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} stringResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
// @Router /config/update [post]
|
||||
// @tags Configuration
|
||||
func (app *appContext) ApplyUpdate(gc *gin.Context) {
|
||||
if !app.update.CanUpdate {
|
||||
respond(400, "Update is manual", gc)
|
||||
return
|
||||
}
|
||||
err := app.update.update()
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to apply update: %s", err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns the custom email (generating it if not set) and list of used variables in it.
|
||||
// @Produce json
|
||||
// @Success 200 {object} customEmailDTO
|
||||
|
22
config.go
22
config.go
@ -84,6 +84,28 @@ func (app *appContext) loadConfig() error {
|
||||
emailEnabled = true
|
||||
}
|
||||
|
||||
app.MustSetValue("updates", "enabled", "true")
|
||||
releaseChannel := app.config.Section("updates").Key("channel").String()
|
||||
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
||||
v := version
|
||||
if releaseChannel == "stable" {
|
||||
if version == "git" {
|
||||
v = "0.0.0"
|
||||
}
|
||||
} else if releaseChannel == "unstable" {
|
||||
v = "git"
|
||||
}
|
||||
app.updater = newUpdater(baseURL, namespace, repo, v, commit, updater)
|
||||
}
|
||||
if releaseChannel == "" {
|
||||
if version == "git" {
|
||||
releaseChannel = "unstable"
|
||||
} else {
|
||||
releaseChannel = "stable"
|
||||
}
|
||||
app.MustSetValue("updates", "channel", releaseChannel)
|
||||
}
|
||||
|
||||
app.storage.customEmails_path = app.config.Section("files").Key("custom_emails").String()
|
||||
app.storage.loadCustomEmails()
|
||||
|
||||
|
@ -1,6 +1,35 @@
|
||||
{
|
||||
"order": [],
|
||||
"sections": {
|
||||
"updates": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
"name": "Updates",
|
||||
"description": "Settings for update notifications and release channel."
|
||||
},
|
||||
"settings": {
|
||||
"enabled": {
|
||||
"name": "Enabled",
|
||||
"required": true,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"value": true,
|
||||
"description": "Enable/disable updating notifications and downloading/applying updates."
|
||||
},
|
||||
"channel": {
|
||||
"name": "Release Channel",
|
||||
"required": true,
|
||||
"requires_restart": false,
|
||||
"type": "select",
|
||||
"options": [
|
||||
["stable", "Stable"],
|
||||
["unstable", "Unstable"]
|
||||
],
|
||||
"value": "",
|
||||
"description": "Release channel for updates."
|
||||
}
|
||||
}
|
||||
},
|
||||
"jellyfin": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
|
28
css/base.css
28
css/base.css
@ -417,3 +417,31 @@ pre {
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
|
||||
.circle {
|
||||
height: 0.5rem;
|
||||
width: 0.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.circle.\~urge {
|
||||
background-color: var(--color-urge-200);
|
||||
}
|
||||
|
||||
.markdown-box {
|
||||
max-height: 20rem;
|
||||
display: block;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: var(--color-urge-200);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--color-urge-100);
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
color: var(--color-urge-200);
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const binaryType = "external"
|
||||
|
||||
var localFS fs.FS
|
||||
var langFS fs.FS
|
||||
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
const binaryType = "internal"
|
||||
|
||||
//go:embed data data/html data/web data/web/css data/web/js
|
||||
var loFS embed.FS
|
||||
|
||||
|
5
go.mod
5
go.mod
@ -11,6 +11,7 @@ replace github.com/hrfee/jfa-go/common => ./common
|
||||
replace github.com/hrfee/jfa-go/ombi => ./ombi
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/evanw/esbuild v0.8.50 // indirect
|
||||
github.com/fatih/color v1.10.0
|
||||
@ -34,6 +35,7 @@ require (
|
||||
github.com/mailgun/mailgun-go/v4 v4.3.0
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
|
||||
github.com/swaggo/gin-swagger v1.3.0
|
||||
@ -41,7 +43,8 @@ require (
|
||||
github.com/ugorji/go v1.2.0 // indirect
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
|
10
go.sum
10
go.sum
@ -17,6 +17,8 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -188,6 +190,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCb
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
@ -266,6 +270,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -293,6 +299,10 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbq
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b h1:ggRgirZABFolTmi3sn6Ivd9SipZwLedQ5wR0aAKnFxU=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -254,6 +254,21 @@
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-update" class="modal">
|
||||
<div class="modal-content wide card">
|
||||
<span class="heading">{{ .strings.updates }} <span class="modal-close">×</span></span>
|
||||
<p class="content">
|
||||
<h2>
|
||||
<a id="update-version"></a> (<span class="monospace" id="update-commit"></span>)
|
||||
</h2>
|
||||
<p class="content" id="update-description"></p>
|
||||
<p class="support" id="update-date"></p>
|
||||
<div class="content markdown-box" id="update-changelog"></div>
|
||||
</p>
|
||||
<span class="button ~info !normal full-width center" id="update-download">{{ .strings.download }}</span>
|
||||
<span class="button ~urge !normal full-width center" id="update-update">{{ .strings.update }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="notification-box"></div>
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
|
@ -24,6 +24,9 @@
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"admin": "Admin",
|
||||
"updates": "Updates",
|
||||
"update": "Update",
|
||||
"download": "Download",
|
||||
"lastActiveTime": "Last Active",
|
||||
"from": "From",
|
||||
"user": "User",
|
||||
@ -93,6 +96,7 @@
|
||||
"saveEmail": "Email saved.",
|
||||
"sentAnnouncement": "Announcement sent.",
|
||||
"setOmbiDefaults": "Stored ombi defaults.",
|
||||
"updateApplied": "Update applied, please restart.",
|
||||
"errorConnection": "Couldn't connect to jfa-go.",
|
||||
"error401Unauthorized": "Unauthorized. Try refreshing the page.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
|
||||
@ -115,7 +119,11 @@
|
||||
"errorFailureCheckLogs": "Failed (check console/logs)",
|
||||
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)",
|
||||
"errorUserCreated": "Failed to create user {n}.",
|
||||
"errorSendWelcomeEmail": "Failed to send welcome email (check console/logs)"
|
||||
"errorSendWelcomeEmail": "Failed to send welcome email (check console/logs)",
|
||||
"errorApplyUpdate": "Failed to apply update, try manually.",
|
||||
"errorCheckUpdate": "Failed to check for update.",
|
||||
"updateAvailable": "A new update is available, check settings.",
|
||||
"noUpdatesAvailable": "No new updates available."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
|
22
main.go
22
main.go
@ -48,6 +48,14 @@ var (
|
||||
commit string
|
||||
)
|
||||
|
||||
var temp = func() string {
|
||||
temp := "/tmp"
|
||||
if PLATFORM == "windows" {
|
||||
temp = os.Getenv("TEMP")
|
||||
}
|
||||
return temp
|
||||
}()
|
||||
|
||||
var serverTypes = map[string]string{
|
||||
"jellyfin": "Jellyfin",
|
||||
"emby": "Emby (experimental)",
|
||||
@ -90,6 +98,10 @@ type appContext struct {
|
||||
version string
|
||||
quit chan os.Signal
|
||||
URLBase string
|
||||
updater *Updater
|
||||
newUpdate bool // Whether whatever's in update is new.
|
||||
tag Tag
|
||||
update Update
|
||||
}
|
||||
|
||||
func generateSecret(length int) (string, error) {
|
||||
@ -521,6 +533,10 @@ func start(asDaemon, firstCall bool) {
|
||||
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
|
||||
go app.StartPWR()
|
||||
}
|
||||
|
||||
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
||||
go app.checkForUpdates()
|
||||
}
|
||||
} else {
|
||||
debugMode = false
|
||||
address = "0.0.0.0:8056"
|
||||
@ -636,11 +652,7 @@ func printVersion() {
|
||||
|
||||
func main() {
|
||||
printVersion()
|
||||
folder := "/tmp"
|
||||
if PLATFORM == "windows" {
|
||||
folder = os.Getenv("TEMP")
|
||||
}
|
||||
SOCK = filepath.Join(folder, SOCK)
|
||||
SOCK = filepath.Join(temp, SOCK)
|
||||
fmt.Println("Socket:", SOCK)
|
||||
if flagPassed("test") {
|
||||
TEST = true
|
||||
|
@ -214,3 +214,8 @@ type extendExpiryDTO struct {
|
||||
Hours int `json:"hours" example:"2"` // Number of hours to add.
|
||||
Minutes int `json:"minutes" example:"3"` // Number of minutes to add.
|
||||
}
|
||||
|
||||
type checkUpdateDTO struct {
|
||||
New bool `json:"new"` // Whether or not there's a new update.
|
||||
Update Update `json:"update"`
|
||||
}
|
||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -236,9 +236,9 @@
|
||||
"integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY="
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.8.53",
|
||||
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.53.tgz",
|
||||
"integrity": "sha1-tAi7DKGynasT2Lv31Z9Zr+Z3boY="
|
||||
"version": "0.8.56",
|
||||
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.56.tgz",
|
||||
"integrity": "sha1-nHw9bmFNtzZ6+jSK2wqyh8KWc14="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
|
@ -19,7 +19,7 @@
|
||||
"dependencies": {
|
||||
"@ts-stack/markdown": "^1.3.0",
|
||||
"a17t": "^0.4.0",
|
||||
"esbuild": "^0.8.53",
|
||||
"esbuild": "^0.8.56",
|
||||
"lodash": "^4.17.19",
|
||||
"mjml": "^4.8.0",
|
||||
"remixicon": "^2.5.0",
|
||||
|
@ -140,6 +140,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
// api.POST(p + "/setDefaults", app.SetDefaults)
|
||||
api.POST(p+"/users/settings", app.ApplySettings)
|
||||
api.POST(p+"/users/announce", app.Announce)
|
||||
api.GET(p+"/config/update", app.CheckUpdate)
|
||||
api.POST(p+"/config/update", app.ApplyUpdate)
|
||||
api.GET(p+"/config/emails", app.GetEmails)
|
||||
api.GET(p+"/config/emails/:id", app.GetEmail)
|
||||
api.POST(p+"/config/emails/:id", app.SetEmail)
|
||||
|
@ -1,27 +0,0 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
version = sys.argv[1].replace('v', '')
|
||||
except IndexError:
|
||||
version = "git"
|
||||
|
||||
if version == "auto":
|
||||
try:
|
||||
version = subprocess.check_output("git describe --exact-match HEAD".split()).decode("utf-8").rstrip().replace('v', '')
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode == 128:
|
||||
version = "git"
|
||||
|
||||
commit = subprocess.check_output("git rev-parse --short HEAD".split()).decode("utf-8").rstrip()
|
||||
|
||||
file = f'package main; const VERSION = "{version}"; const COMMIT = "{commit}";'
|
||||
|
||||
try:
|
||||
writeto = sys.argv[2]
|
||||
except IndexError:
|
||||
writeto = "version.go"
|
||||
|
||||
with open(writeto, 'w') as f:
|
||||
f.write(file)
|
||||
|
3
scripts/version.sh
Executable file
3
scripts/version.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
JFA_GO_VERSION=$(git describe --exact-match HEAD 2> /dev/null || echo 'vgit')
|
||||
JFA_GO_VERSION="$(echo $JFA_GO_VERSION | sed 's/v//g')" $@
|
@ -7,6 +7,7 @@ import { accountsList } from "./modules/accounts.js";
|
||||
import { settingsList } from "./modules/settings.js";
|
||||
import { ProfileEditor } from "./modules/profiles.js";
|
||||
import { _get, _post, notificationBox, whichAnimationEvent, toggleLoader } from "./modules/common.js";
|
||||
import { Updater } from "./modules/update.js";
|
||||
|
||||
loadTheme();
|
||||
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
||||
@ -59,9 +60,12 @@ window.availableProfiles = window.availableProfiles || [];
|
||||
window.modals.customizeEmails = new Modal(document.getElementById("modal-customize"));
|
||||
|
||||
window.modals.extendExpiry = new Modal(document.getElementById("modal-extend-expiry"));
|
||||
|
||||
window.modals.updateInfo = new Modal(document.getElementById("modal-update"));
|
||||
})();
|
||||
|
||||
var inviteCreator = new createInvite();
|
||||
|
||||
var accounts = new accountsList();
|
||||
|
||||
window.invites = new inviteList();
|
||||
@ -154,6 +158,7 @@ function login(username: string, password: string, run?: (state?: number) => voi
|
||||
} else {
|
||||
const data = this.response;
|
||||
window.token = data["token"];
|
||||
window.updater = new Updater(); // mmm, a race condition
|
||||
window.modals.login.close();
|
||||
setInterval(() => { window.invites.reload(); accounts.reload(); }, 30*1000);
|
||||
const currentTab = window.tabs.current;
|
||||
@ -198,4 +203,3 @@ login("", "");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -598,6 +598,14 @@ export class settingsList {
|
||||
`;
|
||||
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = this._emailEditor.showList;
|
||||
this.addSection(name, settings.sections[name], editButton);
|
||||
} else if (name == "updates") {
|
||||
const icon = document.createElement("span") as HTMLSpanElement;
|
||||
if (window.updater.updateAvailable) {
|
||||
icon.classList.add("button", "~urge");
|
||||
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
|
||||
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
|
||||
}
|
||||
this.addSection(name, settings.sections[name], icon);
|
||||
} else {
|
||||
this.addSection(name, settings.sections[name]);
|
||||
}
|
||||
|
124
ts/modules/update.ts
Normal file
124
ts/modules/update.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { _get, _post, toggleLoader } from "../modules/common.js";
|
||||
import { Marked, Renderer } from "@ts-stack/markdown";
|
||||
|
||||
interface updateDTO {
|
||||
new: boolean;
|
||||
update: Update;
|
||||
}
|
||||
|
||||
export class Updater implements updater {
|
||||
private _update: Update;
|
||||
private _date: Date;
|
||||
updateAvailable = false;
|
||||
|
||||
checkForUpdates = (run?: (req: XMLHttpRequest) => void) => _get("/config/update", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("errorCheckUpdate", window.lang.notif("errorCheckUpdate"));
|
||||
return
|
||||
}
|
||||
let resp = req.response as updateDTO;
|
||||
if (resp.new) {
|
||||
this.update = resp.update;
|
||||
if (run) { run(req); }
|
||||
// } else {
|
||||
// window.notifications.customPositive("noUpdatesAvailable", "", window.lang.notif("noUpdatesAvailable"));
|
||||
}
|
||||
}
|
||||
});
|
||||
get date(): number { return Math.floor(this._date.getTime() / 1000); }
|
||||
set date(unix: number) {
|
||||
this._date = new Date(unix * 1000);
|
||||
document.getElementById("update-date").textContent = this._date.toDateString() + " " + this._date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
get description(): string { return this._update.description; }
|
||||
set description(description: string) {
|
||||
this._update.description = description;
|
||||
const el = document.getElementById("update-description") as HTMLParagraphElement;
|
||||
el.textContent = description;
|
||||
if (this.version == "git") {
|
||||
el.classList.add("monospace");
|
||||
} else {
|
||||
el.classList.remove("monospace");
|
||||
}
|
||||
}
|
||||
|
||||
get changelog(): string { return this._update.changelog; }
|
||||
set changelog(changelog: string) {
|
||||
this._update.changelog = changelog;
|
||||
|
||||
document.getElementById("update-changelog").innerHTML = Marked.parse(changelog);
|
||||
}
|
||||
|
||||
get version(): string { return this._update.version; }
|
||||
set version(version: string) {
|
||||
this._update.version = version;
|
||||
document.getElementById("update-version").textContent = version;
|
||||
}
|
||||
|
||||
get commit(): string { return this._update.commit; }
|
||||
set commit(commit: string) {
|
||||
this._update.commit = commit;
|
||||
document.getElementById("update-commit").textContent = commit.slice(0, 7);
|
||||
}
|
||||
|
||||
get link(): string { return this._update.link; }
|
||||
set link(link: string) {
|
||||
this._update.link = link;
|
||||
(document.getElementById("update-version") as HTMLAnchorElement).href = link;
|
||||
}
|
||||
|
||||
get download_link(): string { return this._update.download_link; }
|
||||
set download_link(link: string) { this._update.download_link = link; }
|
||||
|
||||
get can_update(): boolean { return this._update.can_update; }
|
||||
set can_update(can: boolean) {
|
||||
this._update.can_update = can;
|
||||
const download = document.getElementById("update-download") as HTMLSpanElement;
|
||||
const update = document.getElementById("update-update") as HTMLSpanElement;
|
||||
if (can) {
|
||||
download.classList.add("unfocused");
|
||||
update.classList.remove("unfocused");
|
||||
} else {
|
||||
download.onclick = () => window.open(this._update.download_link || this._update.link);
|
||||
download.classList.remove("unfocused");
|
||||
update.classList.add("unfocused");
|
||||
}
|
||||
}
|
||||
|
||||
get update(): Update { return this._update; }
|
||||
set update(update: Update) {
|
||||
this._update = update;
|
||||
this.version = update.version;
|
||||
this.commit = update.commit;
|
||||
this.date = update.date;
|
||||
this.description = update.description;
|
||||
this.changelog = update.changelog;
|
||||
this.link = update.link;
|
||||
this.download_link = update.download_link;
|
||||
this.can_update = update.can_update;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const update = document.getElementById("update-update") as HTMLSpanElement;
|
||||
update.onclick = () => {
|
||||
toggleLoader(update);
|
||||
_post("/config/update", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(update);
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("applyUpdateError", window.lang.notif("errorApplyUpdate"));
|
||||
} else {
|
||||
window.notifications.customSuccess("applyUpdate", window.lang.notif("updateApplied"));
|
||||
}
|
||||
window.modals.updateInfo.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
this.checkForUpdates(() => {
|
||||
this.updateAvailable = true;
|
||||
window.notifications.customPositive("updateAvailable", "", window.lang.notif("updateAvailable"));
|
||||
});
|
||||
}
|
||||
}
|
@ -30,6 +30,24 @@ declare interface Window {
|
||||
language: string;
|
||||
lang: Lang;
|
||||
langFile: {};
|
||||
updater: updater;
|
||||
}
|
||||
|
||||
declare interface Update {
|
||||
version: string;
|
||||
commit: string;
|
||||
date: number;
|
||||
description: string;
|
||||
changelog: string;
|
||||
link: string;
|
||||
download_link?: string;
|
||||
can_update: boolean;
|
||||
}
|
||||
|
||||
declare interface updater extends Update {
|
||||
checkForUpdates: (run?: (req: XMLHttpRequest) => void) => void;
|
||||
updateAvailable: boolean;
|
||||
update: Update;
|
||||
}
|
||||
|
||||
declare interface Lang {
|
||||
@ -78,6 +96,7 @@ declare interface Modals {
|
||||
editor: Modal;
|
||||
customizeEmails: Modal;
|
||||
extendExpiry: Modal;
|
||||
updateInfo: Modal;
|
||||
}
|
||||
|
||||
interface Invite {
|
||||
|
486
updater.go
Normal file
486
updater.go
Normal file
@ -0,0 +1,486 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://builds.hrfee.pw"
|
||||
namespace = "hrfee"
|
||||
repo = "jfa-go"
|
||||
)
|
||||
|
||||
type GHRelease struct {
|
||||
HTMLURL string `json:"html_url"`
|
||||
ID int `json:"id"`
|
||||
TagName string `json:"tag_name"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
Assets []GHAsset `json:"assets"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type GHAsset struct {
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
type UnixTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t *UnixTime) UnmarshalJSON(b []byte) (err error) {
|
||||
unix, err := strconv.ParseInt(strings.TrimPrefix(strings.TrimSuffix(string(b), "\""), "\""), 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Time = time.Unix(unix, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func (t UnixTime) MarshalJSON() ([]byte, error) {
|
||||
if t.Time == (time.Time{}) {
|
||||
return []byte("\"\""), nil
|
||||
}
|
||||
return []byte("\"" + strconv.FormatInt(t.Time.Unix(), 10) + "\""), nil
|
||||
}
|
||||
|
||||
var updater string
|
||||
|
||||
type BuildType int
|
||||
|
||||
const (
|
||||
off BuildType = iota
|
||||
internal // Internal assets through go:embed, no data/.
|
||||
external // External assets in data/, accesses through app.localFS.
|
||||
docker // Only notify of new updates, no self-updating.
|
||||
)
|
||||
|
||||
type ApplyUpdate func() error
|
||||
|
||||
type Update struct {
|
||||
Version string `json:"version"` // vX.X.X or git
|
||||
Commit string `json:"commit"`
|
||||
ReleaseDate int64 `json:"date"` // unix time
|
||||
Description string `json:"description"` // Commit Name/Release title.
|
||||
Changelog string `json:"changelog"` // Changelog, if applicable
|
||||
Link string `json:"link"` // Link to commit/release page,
|
||||
DownloadLink string `json:"download_link"` // Optional link to download page.
|
||||
CanUpdate bool `json:"can_update"` // Whether or not update can be done automatically.
|
||||
update ApplyUpdate `json:"-"` // Function to apply update if possible.
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Ready bool `json:"ready"` // Whether or not build on this tag has completed.
|
||||
Version string `json:"version,omitempty"` // Version/Commit
|
||||
ReleaseDate UnixTime `json:"date"`
|
||||
}
|
||||
|
||||
var goos = map[string]string{
|
||||
"darwin": "macOS",
|
||||
"linux": "Linux",
|
||||
"windows": "Windows",
|
||||
}
|
||||
|
||||
var goarch = map[string]string{
|
||||
"amd64": "x86_64",
|
||||
"arm64": "arm64",
|
||||
"arm": "armv6",
|
||||
}
|
||||
|
||||
// func newDockerBuild() Update {
|
||||
// var tag string
|
||||
// if version == "git" {
|
||||
// tag = "docker-unstable"
|
||||
// } else {
|
||||
// tag = "docker-latest"
|
||||
// }
|
||||
// }
|
||||
type Updater struct {
|
||||
version, commit, tag, url, namespace, name string
|
||||
stable bool
|
||||
buildType BuildType
|
||||
httpClient *http.Client
|
||||
timeoutHandler common.TimeoutHandler
|
||||
binary string
|
||||
}
|
||||
|
||||
func newUpdater(buildroneURL, namespace, repo, version, commit, buildType string) *Updater {
|
||||
bType := off
|
||||
tag := ""
|
||||
switch buildType {
|
||||
case "binary":
|
||||
if binaryType == "internal" {
|
||||
bType = internal
|
||||
tag = "internal"
|
||||
} else {
|
||||
bType = external
|
||||
tag = "external"
|
||||
}
|
||||
case "docker":
|
||||
bType = docker
|
||||
if version == "git" {
|
||||
tag = "docker-unstable"
|
||||
} else {
|
||||
tag = "docker-latest"
|
||||
}
|
||||
default:
|
||||
bType = off
|
||||
}
|
||||
if commit == "unknown" {
|
||||
bType = off
|
||||
}
|
||||
if version == "git" && bType != docker {
|
||||
tag += "-git"
|
||||
}
|
||||
binary := "jfa-go"
|
||||
if runtime.GOOS == "windows" {
|
||||
binary += ".exe"
|
||||
}
|
||||
return &Updater{
|
||||
httpClient: &http.Client{Timeout: 10 * time.Second},
|
||||
timeoutHandler: common.NewTimeoutHandler("updater", buildroneURL, true),
|
||||
version: version,
|
||||
commit: commit,
|
||||
buildType: bType,
|
||||
tag: tag,
|
||||
url: buildroneURL,
|
||||
namespace: namespace,
|
||||
name: repo,
|
||||
binary: binary,
|
||||
}
|
||||
}
|
||||
|
||||
type BuildDTO struct {
|
||||
ID int64 // `json:"id"`
|
||||
Name string // `json:"name"`
|
||||
Date time.Time // `json:"date"`
|
||||
Link string // `json:"link"`
|
||||
Message string
|
||||
Branch string // `json:"branch"`
|
||||
Tags map[string]Tag
|
||||
}
|
||||
|
||||
func (ud *Updater) GetTag() (Tag, int, error) {
|
||||
if ud.buildType == off {
|
||||
return Tag{}, -1, nil
|
||||
}
|
||||
url := fmt.Sprintf("%s/repo/%s/%s/tag/latest/%s", ud.url, ud.namespace, ud.name, ud.tag)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
resp, err := ud.httpClient.Do(req)
|
||||
defer ud.timeoutHandler()
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
return Tag{}, resp.StatusCode, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return Tag{}, -1, err
|
||||
}
|
||||
|
||||
var tag Tag
|
||||
err = json.Unmarshal(body, &tag)
|
||||
return tag, resp.StatusCode, err
|
||||
}
|
||||
|
||||
func (t *Tag) IsNew() bool {
|
||||
return t.Version != commit
|
||||
}
|
||||
|
||||
func (ud *Updater) getRelease() (release GHRelease, status int, err error) {
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", ud.namespace, ud.name)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
resp, err := ud.httpClient.Do(req)
|
||||
status = resp.StatusCode
|
||||
defer ud.timeoutHandler()
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, &release)
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *Updater) GetUpdate(tag Tag) (update Update, status int, err error) {
|
||||
switch ud.buildType {
|
||||
case internal:
|
||||
if ud.tag == "internal-git" {
|
||||
update, status, err = ud.getUpdateInternalGit(tag)
|
||||
} else if ud.tag == "internal" {
|
||||
update, status, err = ud.getUpdateInternal(tag)
|
||||
}
|
||||
case external, docker:
|
||||
if strings.Contains(ud.tag, "git") || ud.tag == "docker-unstable" {
|
||||
update, status, err = ud.getCommitGit(tag)
|
||||
} else {
|
||||
var release GHRelease
|
||||
release, status, err = ud.getRelease()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
update = Update{
|
||||
Changelog: release.Body,
|
||||
Description: release.Name,
|
||||
Version: release.TagName,
|
||||
Commit: tag.Version,
|
||||
Link: release.HTMLURL,
|
||||
ReleaseDate: release.PublishedAt.Unix(),
|
||||
}
|
||||
}
|
||||
if ud.buildType == docker {
|
||||
update.DownloadLink = fmt.Sprintf("https://hub.docker.com/r/%s/%s/tags", ud.namespace, ud.name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *Updater) getUpdateInternal(tag Tag) (update Update, status int, err error) {
|
||||
release, status, err := ud.getRelease()
|
||||
update = Update{
|
||||
Changelog: release.Body,
|
||||
Description: release.Name,
|
||||
Version: release.TagName,
|
||||
Commit: tag.Version,
|
||||
Link: release.HTMLURL,
|
||||
ReleaseDate: release.PublishedAt.Unix(),
|
||||
}
|
||||
if err != nil || status != 200 {
|
||||
return
|
||||
}
|
||||
updateFunc, status, err := ud.downloadInternal(&release.Assets, tag)
|
||||
if err == nil && status == 200 {
|
||||
update.CanUpdate = true
|
||||
update.update = updateFunc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *Updater) getCommitGit(tag Tag) (update Update, status int, err error) {
|
||||
url := fmt.Sprintf("%s/repo/%s/%s/build/%s", ud.url, ud.namespace, ud.name, tag.Version)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
resp, err := ud.httpClient.Do(req)
|
||||
status = resp.StatusCode
|
||||
defer ud.timeoutHandler()
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var build BuildDTO
|
||||
err = json.Unmarshal(body, &build)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
update = Update{
|
||||
Description: build.Name,
|
||||
Version: "git",
|
||||
Commit: tag.Version,
|
||||
Link: build.Link,
|
||||
ReleaseDate: tag.ReleaseDate.Unix(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *Updater) getUpdateInternalGit(tag Tag) (update Update, status int, err error) {
|
||||
update, status, err = ud.getCommitGit(tag)
|
||||
if err != nil || status != 200 {
|
||||
return
|
||||
}
|
||||
updateFunc, status, err := ud.downloadInternalGit()
|
||||
if err == nil && status == 200 {
|
||||
update.CanUpdate = true
|
||||
update.update = updateFunc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getBuildName() string {
|
||||
operatingSystem, ok := goos[runtime.GOOS]
|
||||
if !ok {
|
||||
for _, v := range goos {
|
||||
if strings.Contains(v, runtime.GOOS) {
|
||||
operatingSystem = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if operatingSystem == "" {
|
||||
return ""
|
||||
}
|
||||
arch, ok := goarch[runtime.GOARCH]
|
||||
if !ok {
|
||||
for _, v := range goarch {
|
||||
if strings.Contains(v, runtime.GOARCH) {
|
||||
arch = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if arch == "" {
|
||||
return ""
|
||||
}
|
||||
return operatingSystem + "_" + arch
|
||||
}
|
||||
|
||||
func (ud *Updater) downloadInternal(assets *[]GHAsset, tag Tag) (applyUpdate ApplyUpdate, status int, err error) {
|
||||
return ud.pullInternal(ud.getInternalURL(assets, tag))
|
||||
}
|
||||
|
||||
func (ud *Updater) downloadInternalGit() (applyUpdate ApplyUpdate, status int, err error) {
|
||||
return ud.pullInternal(ud.getInternalGitURL())
|
||||
}
|
||||
|
||||
func (ud *Updater) getInternalURL(assets *[]GHAsset, tag Tag) string {
|
||||
buildName := getBuildName()
|
||||
if buildName == "" {
|
||||
return ""
|
||||
}
|
||||
url := ""
|
||||
for _, asset := range *assets {
|
||||
if strings.Contains(asset.Name, buildName) {
|
||||
url = asset.BrowserDownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
func (ud *Updater) getInternalGitURL() string {
|
||||
buildName := getBuildName()
|
||||
if buildName == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s/repo/%s/%s/latest/file/%s", ud.url, ud.namespace, ud.name, buildName)
|
||||
}
|
||||
|
||||
func (ud *Updater) pullInternal(url string) (applyUpdate ApplyUpdate, status int, err error) {
|
||||
if url == "" {
|
||||
return
|
||||
}
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
resp, err := ud.httpClient.Do(req)
|
||||
status = resp.StatusCode
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
return
|
||||
}
|
||||
gz, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
status = -1
|
||||
return
|
||||
}
|
||||
tarReader := tar.NewReader(gz)
|
||||
var header *tar.Header
|
||||
for {
|
||||
header, err = tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
status = -1
|
||||
return
|
||||
}
|
||||
switch header.Typeflag {
|
||||
case tar.TypeReg:
|
||||
// Search only for file named ud.binary
|
||||
if header.Name == ud.binary {
|
||||
applyUpdate = func() error {
|
||||
defer gz.Close()
|
||||
defer resp.Body.Close()
|
||||
file, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path, err := filepath.EvalSymlinks(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mode := info.Mode()
|
||||
f, err := os.OpenFile(path+"_", os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, tarReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(path+"_", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = errors.New("Couldn't find file: " + ud.binary)
|
||||
return
|
||||
}
|
||||
|
||||
// func newInternalBuild() Update {
|
||||
// tag := "internal"
|
||||
|
||||
// func update(path string) err {
|
||||
// if
|
||||
// fp, err := os.Executable()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// fullPath, err := filepath.EvalSymlinks(fp)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// newBinary,
|
||||
// }
|
||||
func (app *appContext) checkForUpdates() {
|
||||
for {
|
||||
go func() {
|
||||
tag, status, err := app.updater.GetTag()
|
||||
if status != 200 || err != nil {
|
||||
if strings.Contains(err.Error(), "strconv.ParseInt") {
|
||||
app.err.Println("No new updates available.")
|
||||
} else {
|
||||
app.err.Printf("Failed to get latest tag (%d): %v", status, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if tag != app.tag && tag.IsNew() {
|
||||
app.info.Println("Update found")
|
||||
update, status, err := app.updater.GetUpdate(tag)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get update (%d): %v", status, err)
|
||||
return
|
||||
}
|
||||
app.tag = tag
|
||||
app.update = update
|
||||
app.newUpdate = true
|
||||
}
|
||||
}()
|
||||
time.Sleep(30 * time.Minute)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user