Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bff7af6bc |
@@ -33,8 +33,11 @@ jobs:
|
||||
run: |
|
||||
cd VRWT_Tool/VRC_OSC
|
||||
mkdir -p dist
|
||||
GO111MODULE=on GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o dist/vrc_osc.exe ./cmd/vrc_osc
|
||||
GO111MODULE=on GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o dist/vrwt_tool.exe ./cmd/vrwt_tool
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
GO111MODULE=on GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X vrc_osc_go/internal/buildinfo.Version=${VERSION} -X vrc_osc_go/internal/buildinfo.BuildTime=${BUILD_TIME}" -o dist/vrc_osc.exe ./cmd/vrc_osc
|
||||
GO111MODULE=on GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X vrc_osc_go/internal/buildinfo.Version=${VERSION} -X vrc_osc_go/internal/buildinfo.BuildTime=${BUILD_TIME}" -o dist/vrc_osc_launcher.exe ./cmd/vrc_osc_launcher
|
||||
GO111MODULE=on GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X vrc_osc_go/internal/buildinfo.Version=${VERSION} -X vrc_osc_go/internal/buildinfo.BuildTime=${BUILD_TIME}" -o dist/vrwt_tool.exe ./cmd/vrwt_tool
|
||||
|
||||
- name: Package executables
|
||||
shell: bash
|
||||
@@ -43,7 +46,7 @@ jobs:
|
||||
cp config/config.example.toml dist/config.example.toml
|
||||
cp config/secrets.example.toml dist/secrets.example.toml
|
||||
cp config/guests.example.txt dist/guests.example.txt
|
||||
(cd dist && zip -r vrc_osc-windows.zip vrc_osc.exe vrwt_tool.exe config.example.toml secrets.example.toml guests.example.txt)
|
||||
(cd dist && zip -r vrc_osc-windows.zip vrc_osc.exe vrc_osc_launcher.exe vrwt_tool.exe config.example.toml secrets.example.toml guests.example.txt)
|
||||
|
||||
- name: Create Release
|
||||
env:
|
||||
|
||||
40
VRWT_Tool/VRC_OSC/cmd/vrc_osc_launcher/main.go
Normal file
40
VRWT_Tool/VRC_OSC/cmd/vrc_osc_launcher/main.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"vrc_osc_go/internal/buildinfo"
|
||||
"vrc_osc_go/internal/common"
|
||||
"vrc_osc_go/internal/update"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var baseURL string
|
||||
var ownerRepo string
|
||||
var assetName string
|
||||
flag.StringVar(&baseURL, "base-url", "https://git.vrcworldtour.com", "Gitea base URL")
|
||||
flag.StringVar(&ownerRepo, "repo", "every_holiday/VRCWT-OSC", "owner/repo")
|
||||
flag.StringVar(&assetName, "asset", "vrc_osc-windows.zip", "release asset name")
|
||||
flag.Parse()
|
||||
|
||||
client := &update.Client{BaseURL: baseURL}
|
||||
mgr := &update.Manager{
|
||||
Client: client,
|
||||
OwnerRepo: ownerRepo,
|
||||
AssetName: assetName,
|
||||
CurrentLabel: buildinfo.Version,
|
||||
}
|
||||
if _, err := mgr.CheckAndUpdate(); err != nil {
|
||||
log.Printf("auto update skipped: %v", err)
|
||||
}
|
||||
exe := filepath.Join(common.RootDir(), "vrc_osc.exe")
|
||||
if _, err := os.Stat(exe); err != nil {
|
||||
log.Fatalf("missing exe: %v", err)
|
||||
}
|
||||
if err := update.Launch(exe, os.Args[1:]); err != nil {
|
||||
log.Fatalf("launch failed: %v", err)
|
||||
}
|
||||
}
|
||||
6
VRWT_Tool/VRC_OSC/internal/buildinfo/buildinfo.go
Normal file
6
VRWT_Tool/VRC_OSC/internal/buildinfo/buildinfo.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package buildinfo
|
||||
|
||||
var (
|
||||
Version = "dev"
|
||||
BuildTime = "unknown"
|
||||
)
|
||||
106
VRWT_Tool/VRC_OSC/internal/update/manager.go
Normal file
106
VRWT_Tool/VRC_OSC/internal/update/manager.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"vrc_osc_go/internal/buildinfo"
|
||||
"vrc_osc_go/internal/common"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
Client *Client
|
||||
OwnerRepo string
|
||||
AssetName string
|
||||
CurrentLabel string
|
||||
}
|
||||
|
||||
func (m *Manager) CheckAndUpdate() (bool, error) {
|
||||
if m.Client == nil || m.OwnerRepo == "" || m.AssetName == "" {
|
||||
return false, nil
|
||||
}
|
||||
latest, err := m.Client.LatestRelease(m.OwnerRepo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if latest.TagName == "" || latest.TagName == m.CurrentLabel {
|
||||
return false, nil
|
||||
}
|
||||
assetURL := ""
|
||||
for _, a := range latest.Assets {
|
||||
if a.Name == m.AssetName {
|
||||
assetURL = a.URL
|
||||
break
|
||||
}
|
||||
}
|
||||
if assetURL == "" {
|
||||
return false, fmt.Errorf("asset %q not found in release %s", m.AssetName, latest.TagName)
|
||||
}
|
||||
base := common.RootDir()
|
||||
tmpDir := filepath.Join(base, "update", latest.TagName)
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
zipPath := filepath.Join(tmpDir, m.AssetName)
|
||||
log.Printf("update available: %s -> %s", m.CurrentLabel, latest.TagName)
|
||||
if err := m.Client.DownloadAsset(assetURL, zipPath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := ExtractZip(zipPath, tmpDir); err != nil {
|
||||
return false, err
|
||||
}
|
||||
exePath := filepath.Join(base, CurrentExeName())
|
||||
newExe := filepath.Join(tmpDir, CurrentExeName())
|
||||
if _, err := os.Stat(newExe); err != nil {
|
||||
return false, err
|
||||
}
|
||||
backup := exePath + ".bak"
|
||||
_ = os.Remove(backup)
|
||||
if err := os.Rename(exePath, backup); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := CopyFile(newExe, exePath); err != nil {
|
||||
_ = os.Rename(backup, exePath)
|
||||
return false, err
|
||||
}
|
||||
_ = os.Remove(backup)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
_, err = io.Copy(out, in)
|
||||
return err
|
||||
}
|
||||
|
||||
func Launch(exe string, args []string) error {
|
||||
cmd := exec.Command(exe, args...)
|
||||
cmd.Dir = filepath.Dir(exe)
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func Version() string { return buildinfo.Version }
|
||||
|
||||
func CurrentExeName() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "vrc_osc.exe"
|
||||
}
|
||||
return "vrc_osc"
|
||||
}
|
||||
125
VRWT_Tool/VRC_OSC/internal/update/release.go
Normal file
125
VRWT_Tool/VRC_OSC/internal/update/release.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReleaseInfo struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
HTTP *http.Client
|
||||
}
|
||||
|
||||
func (c *Client) LatestRelease(ownerRepo string) (*ReleaseInfo, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, c.apiURL(ownerRepo, "/releases/latest"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info ReleaseInfo
|
||||
if err := c.doJSON(req, &info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (c *Client) DownloadAsset(url, dst string) error {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := c.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("download failed: %s", resp.Status)
|
||||
}
|
||||
f, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func ExtractZip(zipPath, dstDir string) error {
|
||||
zr, err := zip.OpenReader(zipPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zr.Close()
|
||||
root := filepath.Clean(dstDir) + string(os.PathSeparator)
|
||||
for _, f := range zr.File {
|
||||
target := filepath.Clean(filepath.Join(dstDir, f.Name))
|
||||
if target != filepath.Clean(dstDir) && !strings.HasPrefix(target, root) {
|
||||
return fmt.Errorf("zip path escapes destination: %s", f.Name)
|
||||
}
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(target, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := os.Create(target)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
_, copyErr := io.Copy(out, rc)
|
||||
out.Close()
|
||||
rc.Close()
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) apiURL(ownerRepo, suffix string) string {
|
||||
base := strings.TrimRight(c.BaseURL, "/")
|
||||
return base + "/api/v1/repos/" + strings.Trim(ownerRepo, "/") + suffix
|
||||
}
|
||||
|
||||
func (c *Client) doJSON(req *http.Request, out any) error {
|
||||
resp, err := c.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return fmt.Errorf("request failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return json.NewDecoder(resp.Body).Decode(out)
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTP != nil {
|
||||
return c.HTTP
|
||||
}
|
||||
return &http.Client{Timeout: 30 * time.Second}
|
||||
}
|
||||
Reference in New Issue
Block a user