diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b91c378..3a43b33c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,12 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 diff --git a/.goreleaser.yml b/.goreleaser.yml index c2340beb..2b80e6a0 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -5,8 +5,8 @@ env: before: hooks: - - make frontend - - make gen + - task ui + - task gen builds: - env: @@ -86,6 +86,8 @@ changelog: exclude: - '^README' - '^Update' + - '^Version' + - '^ci:' - Merge pull request - Merge branch diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4ccc583a..00000000 --- a/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ - -FROM golang:alpine AS builder - -RUN apk add --no-cache git unzip curl make bash - -WORKDIR /app - -COPY go.mod go.sum ./ - -RUN go mod download - -COPY . . - -RUN make build - -FROM scratch - -WORKDIR / - -COPY --from=builder /app/bin/teldrive /teldrive - -EXPOSE 8080 - -ENTRYPOINT ["/teldrive","run","--tg-session-file","/session.db"] \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 8cc870c9..00000000 --- a/Makefile +++ /dev/null @@ -1,137 +0,0 @@ -ifdef ComSpec -SHELL := powershell.exe -IS_WINDOWS := true -else -SHELL := /bin/bash -IS_WINDOWS := false -endif - -APP_NAME := teldrive -BUILD_DIR := bin -FRONTEND_DIR := ui/dist -FRONTEND_ASSET := https://github.com/tgdrive/teldrive-ui/releases/download/latest/teldrive-ui.zip -GIT_COMMIT := $(shell git rev-parse --short HEAD) -GIT_LINK := $(shell git remote get-url origin) -MODULE_PATH := $(shell go list -m) -GOOS ?= $(shell go env GOOS) -GOARCH ?= $(shell go env GOARCH) -GIT_COMMIT := $(shell git rev-parse --short HEAD) -VERSION_PACKAGE := $(MODULE_PATH)/internal/version -BINARY_EXTENSION := - -ifeq ($(IS_WINDOWS),true) - TAG_FILTER:=Sort-Object -Descending | Select-Object -First 1 -else - TAG_FILTER:=head -n 1 -endif - -GIT_TAG := $(shell git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | $(TAG_FILTER)) - -ifeq ($(IS_WINDOWS),true) - BINARY_EXTENSION := .exe - RM := powershell -Command "Remove-Item" - RMDIR := powershell -Command "Remove-Item -Recurse -Force" - MKDIR := powershell -Command "New-Item -ItemType Directory -Force" - DOWNLOAD := powershell -Command "Invoke-WebRequest -Uri" - UNZIP := powershell -Command "Expand-Archive" -else - RM := rm -f - RMDIR := rm -rf - MKDIR := mkdir -p - DOWNLOAD := curl -sLO - UNZIP := unzip -q -d -endif - -.PHONY: all build run clean frontend backend run sync-ui retag patch-version minor-version major-version gen check-semver install-semver - -all: build - -check-semver: -ifeq ($(IS_WINDOWS),true) - @powershell -Command "if (-not (Get-Command semver -ErrorAction SilentlyContinue)) { Write-Host 'Installing semver...'; npm install -g semver }" -else - @which semver > /dev/null || (echo "Installing semver..." && npm install -g semver) -endif - -frontend: - @echo "Extract UI" - $(RMDIR) $(FRONTEND_DIR) -ifeq ($(IS_WINDOWS),true) - $(DOWNLOAD) $(FRONTEND_ASSET) -OutFile teldrive-ui.zip - $(MKDIR) $(subst /,\\,$(FRONTEND_DIR)) - $(UNZIP) -Path teldrive-ui.zip -DestinationPath $(FRONTEND_DIR) -Force - $(RM) teldrive-ui.zip -else - $(DOWNLOAD) $(FRONTEND_ASSET) - $(MKDIR) $(FRONTEND_DIR) - $(UNZIP) $(FRONTEND_DIR) teldrive-ui.zip - $(RM) teldrive-ui.zip -endif - -gen: - go generate ./... - -backend: gen - @echo "Building backend for $(GOOS)/$(GOARCH)..." - go build -trimpath -ldflags "-s -w -X '$(VERSION_PACKAGE).Version=$(GIT_TAG)' -X '$(VERSION_PACKAGE).CommitSHA=$(GIT_COMMIT)' -extldflags=-static" -o $(BUILD_DIR)/$(APP_NAME)$(BINARY_EXTENSION) - - -build: frontend backend - @echo "Building complete." - -run: - @echo "Running $(APP_NAME)..." - $(BUILD_DIR)/$(APP_NAME) run - -clean: - @echo "Cleaning up..." - $(RMDIR) $(BUILD_DIR) -ifeq ($(IS_WINDOWS),true) - if exist "$(FRONTEND_DIR)" $(RMDIR) "$(FRONTEND_DIR)" -else - $(RMDIR) $(FRONTEND_DIR) -endif - -deps: - @echo "Installing Go dependencies..." - go mod download - -retag: - @echo "Retagging $(GIT_TAG)..." - -git tag -d $(GIT_TAG) - -git push --delete origin $(GIT_TAG) - git tag -a $(GIT_TAG) -m "Recreated tag $(GIT_TAG)" - git push origin $(GIT_TAG) - -patch-version: check-semver - @echo "Current version: $(GIT_TAG)" -ifeq ($(GIT_TAG),) - $(eval NEW_VERSION := 1.0.0) -else - $(eval NEW_VERSION := $(shell semver -i patch $(GIT_TAG))) -endif - @echo "Creating new patch version: $(NEW_VERSION)" - git tag -a $(NEW_VERSION) -m "Release $(NEW_VERSION)" - git push origin $(NEW_VERSION) - -minor-version: check-semver - @echo "Current version: $(GIT_TAG)" -ifeq ($(GIT_TAG),) - $(eval NEW_VERSION := 1.0.0) -else - $(eval NEW_VERSION := $(shell semver -i minor $(GIT_TAG))) -endif - @echo "Creating new minor version: $(NEW_VERSION)" - git tag -a $(NEW_VERSION) -m "Release $(NEW_VERSION)" - git push origin $(NEW_VERSION) - -major-version: check-semver - @echo "Current version: $(GIT_TAG)" -ifeq ($(GIT_TAG),) - $(eval NEW_VERSION := 1.0.0) -else - $(eval NEW_VERSION := $(shell semver -i major $(GIT_TAG))) -endif - @echo "Creating new major version: $(NEW_VERSION)" - git tag -a $(NEW_VERSION) -m "Release $(NEW_VERSION)" - git push origin $(NEW_VERSION) \ No newline at end of file diff --git a/go.mod b/go.mod index 283605a6..d232efb7 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/tgdrive/teldrive go 1.23.3 require ( + github.com/Masterminds/semver/v3 v3.3.1 github.com/WinterYukky/gorm-extra-clause-plugin v0.3.0 github.com/coocood/freecache v1.2.4 github.com/go-chi/chi/v5 v5.2.0 diff --git a/go.sum b/go.sum index d0e5e48a..51e34a56 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/WinterYukky/gorm-extra-clause-plugin v0.3.0 h1:fQfTkxoRso6mlm7eOfBIk94aqamJeqxCEfU+MyLWhgo= github.com/WinterYukky/gorm-extra-clause-plugin v0.3.0/go.mod h1:GFT8TzxeeGKYXNU/65PsiN2+zNHigm9HjybnbL1T7eg= github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho= diff --git a/scripts/extract.go b/scripts/extract.go new file mode 100644 index 00000000..5bd2e502 --- /dev/null +++ b/scripts/extract.go @@ -0,0 +1,104 @@ +//go:build ignore + +package main + +import ( + "archive/zip" + "flag" + "fmt" + "io" + "net/http" + "os" + "path/filepath" +) + +func main() { + urlFlag := flag.String("url", "", "URL to download from") + outputFlag := flag.String("output", "", "Output directory") + flag.Parse() + + if *urlFlag == "" || *outputFlag == "" { + flag.Usage() + os.Exit(1) + } + + if err := os.RemoveAll(*outputFlag); err != nil { + fmt.Printf("Error removing directory: %v\n", err) + os.Exit(1) + } + + if err := os.MkdirAll(*outputFlag, 0755); err != nil { + fmt.Printf("Error creating directory: %v\n", err) + os.Exit(1) + } + + resp, err := http.Get(*urlFlag) + if err != nil { + fmt.Printf("Error downloading: %v\n", err) + os.Exit(1) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + fmt.Printf("Bad status: %s\n", resp.Status) + os.Exit(1) + } + + tmpFile, err := os.CreateTemp("", "download-*.zip") + if err != nil { + fmt.Printf("Error creating temp file: %v\n", err) + os.Exit(1) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + _, err = io.Copy(tmpFile, resp.Body) + if err != nil { + fmt.Printf("Error saving download: %v\n", err) + os.Exit(1) + } + + reader, err := zip.OpenReader(tmpFile.Name()) + if err != nil { + fmt.Printf("Error opening zip: %v\n", err) + os.Exit(1) + } + defer reader.Close() + + for _, file := range reader.File { + path := filepath.Join(*outputFlag, file.Name) + + if file.FileInfo().IsDir() { + os.MkdirAll(path, os.ModePerm) + continue + } + + if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { + fmt.Printf("Error creating directory for file: %v\n", err) + os.Exit(1) + } + + dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + fmt.Printf("Error creating file: %v\n", err) + os.Exit(1) + } + + srcFile, err := file.Open() + if err != nil { + dstFile.Close() + fmt.Printf("Error opening zip entry: %v\n", err) + os.Exit(1) + } + + _, err = io.Copy(dstFile, srcFile) + dstFile.Close() + srcFile.Close() + if err != nil { + fmt.Printf("Error extracting file: %v\n", err) + os.Exit(1) + } + } + + fmt.Println("UI Extracted successfully!") +} diff --git a/scripts/release.go b/scripts/release.go new file mode 100644 index 00000000..74569f6a --- /dev/null +++ b/scripts/release.go @@ -0,0 +1,90 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/spf13/pflag" +) + +var ( + versionFile = "VERSION" + versionFlag bool + commitFlag bool +) + +func init() { + pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number") + pflag.Parse() +} + +func main() { + if err := release(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func release() error { + if len(pflag.Args()) != 1 { + return errors.New("error: expected version number") + } + + version, err := getVersion() + if err != nil { + return err + } + + if err := bumpVersion(version, pflag.Arg(0)); err != nil { + return err + } + + if versionFlag { + fmt.Println(version) + return nil + } + + if err := writeVersionFile(version); err != nil { + return fmt.Errorf("failed to write version file: %w", err) + } + + return nil +} + +func getVersion() (*semver.Version, error) { + cmd := exec.Command("git", "tag", "-l", "[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname") + b, err := cmd.Output() + if err != nil { + return nil, err + } + tags := strings.Split(strings.TrimSpace(string(b)), "\n") + if len(tags) == 0 || tags[0] == "" { + return nil, errors.New("error: no tags found") + } + + return semver.NewVersion(tags[0]) +} + +func bumpVersion(version *semver.Version, verb string) error { + switch verb { + case "major": + *version = version.IncMajor() + case "minor": + *version = version.IncMinor() + case "patch": + *version = version.IncPatch() + case "current": + // do nothing + default: + *version = *semver.MustParse(verb) + } + return nil +} + +func writeVersionFile(version *semver.Version) error { + return os.WriteFile(versionFile, []byte(version.String()), 0644) +} diff --git a/taskfile.yml b/taskfile.yml new file mode 100644 index 00000000..6815cac8 --- /dev/null +++ b/taskfile.yml @@ -0,0 +1,110 @@ +version: '3' + +vars: + APP_NAME: teldrive + BUILD_DIR: bin + FRONTEND_DIR: ui/dist + FRONTEND_ASSET: https://github.com/tgdrive/teldrive-ui/releases/download/latest/teldrive-ui.zip + GIT_COMMIT: + sh: git rev-parse --short HEAD + MODULE_PATH: + sh: go list -m + GOOS: + sh: go env GOOS + GOARCH: + sh: go env GOARCH + BINARY_EXTENSION: '{{if eq OS "windows"}}.exe{{end}}' + +tasks: + default: + cmds: + - task: ui + - task: server + + ui: + desc: Download UI assets + cmds: + - go run scripts/extract.go -url {{.FRONTEND_ASSET}} -output {{.FRONTEND_DIR}} + + gen: + desc: Generate API code + cmds: + - go generate ./... + + server: + desc: Build Server + deps: [gen] + vars: + VERSION: + sh: go run scripts/release.go --version current + cmds: + - echo "Building backend for {{.GOOS}}/{{.GOARCH}}..." + - > + go build -trimpath -ldflags "-s -w + -X '{{.MODULE_PATH}}/internal/version.Version={{.VERSION}}' + -X '{{.MODULE_PATH}}/internal/version.CommitSHA={{.GIT_COMMIT}}' + -extldflags=-static + " -o {{.BUILD_DIR}}/{{.APP_NAME}}{{.BINARY_EXTENSION}} + + run: + desc: Run Server + cmds: + - ./{{.BUILD_DIR}}/{{.APP_NAME}}{{.BINARY_EXTENSION}} run + + deps: + desc: Install dependencies + cmds: + - go mod download + - go mod tidy + + retag: + desc: Retag a version + vars: + VERSION: + sh: go run scripts/release.go --version current + preconditions: + - sh: test $(git rev-parse --abbrev-ref HEAD) = "main" + msg: "You must be on the main branch to retag" + - sh: "[[ -z $(git diff --shortstat main) ]]" + msg: "You must have a clean working tree to retag" + prompt: "This will recreate tag {{.VERSION}}. Are you sure?" + cmds: + - echo "Retagging {{.VERSION}}..." + - cmd: git rev-list -n 1 {{.VERSION}} || true + silent: true + vars: + OLD_COMMIT: out + - git tag -d {{.VERSION}} || true + - git push --delete origin {{.VERSION}} || true + - cmd: | + if [ ! -z "{{.OLD_COMMIT}}" ]; then + if ! git cherry-pick {{.OLD_COMMIT}}; then + git cherry-pick --abort + echo "Failed to cherry-pick version commit" + exit 1 + fi + fi + - git tag -a {{.VERSION}} -m "Release {{.VERSION}}" + - git push origin {{.VERSION}} + - git push origin main + + release:*: + desc: Prepare for a new release + vars: + VERSION: + sh: "go run scripts/release.go --version {{index .MATCH 0}}" + preconditions: + - sh: test $(git rev-parse --abbrev-ref HEAD) = "main" + msg: "You must be on the main branch to release" + - sh: "[[ -z $(git diff --shortstat main) ]]" + msg: "You must have a clean working tree to release" + prompt: "Are you sure you want to release version {{.VERSION}}?" + cmds: + - cmd: echo "Releasing {{.VERSION}}" + silent: true + - "go run scripts/release.go {{.VERSION}}" + - "git add --all" + - 'git commit -m "Version {{.VERSION}}"' + - "git push" + - "git tag {{.VERSION}}" + - "git push origin tag {{.VERSION}}" \ No newline at end of file