From eafbb2abcb1a083fbf2ded4b842f8bc2b845308b Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Wed, 29 Apr 2026 20:43:14 +1200 Subject: [PATCH 1/3] Complete rewrite --- .github/dependabot.yml | 6 +- .github/workflows/lint.yml | 23 ++++ .github/workflows/pr.yml | 19 +++ .github/workflows/push_and_pull.yml | 54 -------- .github/workflows/release.yml | 35 +++++ .github/workflows/tag.yml | 20 +++ .github/workflows/tag_and_release.yml | 76 ----------- .github/workflows/test.yml | 19 +++ .gitignore | 25 +++- .goreleaser.yml | 35 +++++ CHANGELOG.md | 7 + Dockerfile | 40 +++--- Makefile | 60 +++++---- README.md | 67 +++++----- cmd/{barcomic/main.go => root.go} | 106 +++++++++------ cmd/version.go | 19 +++ go.mod | 35 +---- go.sum | 86 +++---------- internal/barcomic/cert_test.go | 23 ---- internal/barcomic/restapi.go | 161 ----------------------- internal/barcomic/server.go | 45 ------- internal/{barcomic => }/cert.go | 8 +- internal/cert_test.go | 34 +++++ internal/keystroke.go | 133 +++++++++++++++++++ internal/keystroke_test.go | 148 +++++++++++++++++++++ internal/restapi.go | 163 ++++++++++++++++++++++++ internal/{barcomic => }/restapi_test.go | 26 +++- internal/server.go | 61 +++++++++ main.go | 7 + scripts/build_clean.sh | 10 -- scripts/build_darwin.sh | 18 --- scripts/build_linux.sh | 18 --- scripts/build_windows.sh | 21 --- scripts/export.sh | 13 -- scripts/git_tag_and_release.sh | 18 --- scripts/install_linux_deps.sh | 22 ---- 36 files changed, 950 insertions(+), 711 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/pr.yml delete mode 100644 .github/workflows/push_and_pull.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tag.yml delete mode 100644 .github/workflows/tag_and_release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .goreleaser.yml create mode 100644 CHANGELOG.md rename cmd/{barcomic/main.go => root.go} (50%) create mode 100644 cmd/version.go delete mode 100644 internal/barcomic/cert_test.go delete mode 100644 internal/barcomic/restapi.go delete mode 100644 internal/barcomic/server.go rename internal/{barcomic => }/cert.go (90%) create mode 100644 internal/cert_test.go create mode 100644 internal/keystroke.go create mode 100644 internal/keystroke_test.go create mode 100644 internal/restapi.go rename internal/{barcomic => }/restapi_test.go (81%) create mode 100644 internal/server.go create mode 100644 main.go delete mode 100755 scripts/build_clean.sh delete mode 100755 scripts/build_darwin.sh delete mode 100755 scripts/build_linux.sh delete mode 100755 scripts/build_windows.sh delete mode 100755 scripts/export.sh delete mode 100755 scripts/git_tag_and_release.sh delete mode 100755 scripts/install_linux_deps.sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 333544b..0f4ed3d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,17 +2,17 @@ version: 2 updates: - - package-ecosystem: gomod + - package-ecosystem: github-actions directory: / schedule: interval: weekly - open-pull-requests-limit: 0 # only security updates assignees: - "thomaslaurenson" - - package-ecosystem: github-actions + - package-ecosystem: gomod directory: / schedule: interval: weekly + open-pull-requests-limit: 0 # only security updates assignees: - "thomaslaurenson" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..098412c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint + +on: workflow_call + +permissions: + contents: read + +jobs: + go_lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - run: make fmt_check + + - run: make mod_check + + - run: make vet diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..6f9215f --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,19 @@ +name: PR + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +concurrency: + group: pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + lint: + uses: ./.github/workflows/lint.yml + + test: + uses: ./.github/workflows/test.yml diff --git a/.github/workflows/push_and_pull.yml b/.github/workflows/push_and_pull.yml deleted file mode 100644 index e8ce195..0000000 --- a/.github/workflows/push_and_pull.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Push and Pull - -on: - push: - branches: - - "main" - -jobs: - build_and_test_linux: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v6 - - - name: Setup Go environment - uses: actions/setup-go@v6 - with: - go-version: '1.24' - check-latest: true - cache: true - - - name: Check Go version - run: go version - - - name: Install golang dependencies - run: make install_golang_deps - - - name: Install robotgo dependencies - run: make install_linux_deps - - - name: Run unit tests - run: make test - - build_and_test_darwin: - runs-on: macos-14 - steps: - - name: Check out repository code - uses: actions/checkout@v6 - - - name: Setup Go environment - uses: actions/setup-go@v6 - with: - go-version: '1.24' - check-latest: true - cache: true - - - name: Check Go version - run: go version - - - name: Install golang dependencies - run: make install_golang_deps - - - name: Run unit tests - run: make test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8d10b60 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +name: Release + +on: workflow_call + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # required by GoReleaser for changelog/versioning + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: Extract release notes from CHANGELOG.md + run: | + TAG="${GITHUB_REF_NAME}" + awk "/^## ${TAG} /{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md \ + > /tmp/release-notes.md + if [ ! -s /tmp/release-notes.md ]; then + echo "No CHANGELOG entry found for ${TAG}" >&2 + exit 1 + fi + + - uses: goreleaser/goreleaser-action@v7 + with: + args: release --clean --release-notes /tmp/release-notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..0a6a00a --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,20 @@ +name: Tag + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + lint: + uses: ./.github/workflows/lint.yml + + test: + uses: ./.github/workflows/test.yml + + release: + needs: [lint, test] + uses: ./.github/workflows/release.yml + secrets: inherit diff --git a/.github/workflows/tag_and_release.yml b/.github/workflows/tag_and_release.yml deleted file mode 100644 index 7b8212d..0000000 --- a/.github/workflows/tag_and_release.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Tag and Release - -on: - push: - tags: - - "v*.*.*" - -jobs: - build_and_release_linux: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v6 - - - name: Setup Go environment - uses: actions/setup-go@v6 - with: - go-version: '1.24' - check-latest: true - cache: true - - - name: Check Go version - run: go version - - - name: Install golang dependencies - run: make install_golang_deps - - - name: Install robotgo dependencies - run: make install_linux_deps - - - name: Build Linux binary - run: make build_linux - - - name: Build Windows binary - run: make build_windows - - - name: Release package - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - bin/barcomic-linux - bin/barcomic-windows.exe - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - build_and_release_darwin: - runs-on: macos-14 - steps: - - name: Check out repository code - uses: actions/checkout@v6 - - - name: Setup Go environment - uses: actions/setup-go@v6 - with: - go-version: '1.24' - check-latest: true - cache: true - - - name: Check Go version - run: go version - - - name: Install golang dependencies - run: make install_golang_deps - - - name: Build Darwin binary - run: make build_darwin - - - name: Release package - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - bin/barcomic-darwin - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e94f4ae --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,19 @@ +name: Test + +on: workflow_call + +permissions: + contents: read + +jobs: + go_test: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - run: make test diff --git a/.gitignore b/.gitignore index 1f1370a..6c1819f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +# CUSTOM +bin/ +dist/ + +# GO +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# # Binaries for programs and plugins *.exe *.exe~ @@ -8,11 +16,22 @@ # Test binary, built with `go test -c` *.test -# Output of the go coverage tool, specifically when used with LiteIDE +# Code coverage profiles and other test artifacts *.out +coverage.* +*.coverprofile +profile.cov # Dependency directories (remove the comment below to include it) # vendor/ -# Custom additions -/bin/* +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Editor/IDE +# .idea/ +# .vscode/ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..18e5ed8 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# GoReleaser v2 +version: 2 + +project_name: barcomic + +dist: dist + +builds: + - env: + - CGO_ENABLED=0 + mod_timestamp: "{{ .CommitTimestamp }}" + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + ignore: + - goos: windows + goarch: arm64 + ldflags: + - -s -w -X github.com/thegraydot/barcomic/cmd.Version={{.Version}} -X github.com/thegraydot/barcomic/cmd.Hash={{.Commit}} + +archives: + - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + formats: [tar.gz] + format_overrides: + - goos: windows + formats: [zip] + +checksum: + name_template: "checksums.txt" + algorithm: sha256 + +changelog: + sort: desc + filters: + exclude: ["^docs:", "^test:", "^chore:", "^Merge "] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5d8fe95 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# CHANGELOG + +## 1.1.0 - 2026-04-29 + +### Changed + +- Complete rewrite diff --git a/Dockerfile b/Dockerfile index f3db7d7..e0dbdbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,30 @@ -FROM golang:1.24-bookworm +ARG TARGETOS=linux +ARG TARGETARCH=amd64 -WORKDIR /barcomic -COPY ./cmd /barcomic/cmd -COPY ./internal /barcomic/internal -COPY ./scripts /barcomic/scripts +# Stage 1: Build +FROM golang:1.24-bookworm AS builder -# Remove instances of robotgo from source -RUN sed -i '/robotgo/d' ./internal/barcomic/restapi.go +ARG TARGETOS +ARG TARGETARCH -# Create new Go module without robotgo -RUN go mod init github.com/TheGrayDot/barcomic -RUN go get -v github.com/mdp/qrterminal@v1.0.1 +WORKDIR /build -# Compile application -RUN bash ./scripts/build_linux.sh +# Cache dependency download separately from source build +COPY go.mod go.sum ./ +RUN go mod download -# Run application -CMD ./bin/barcomic-linux -v -a 0.0.0.0 -p 80 -s true -i false +COPY . . -# Hack to keep the container up and not complain -# CMD tail -f /dev/null +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build \ + -ldflags="-s -w" \ + -o /barcomic \ + ./cmd/barcomic/main.go + +# Stage 2: Minimal runtime image +FROM alpine:3.21 + +COPY --from=builder /barcomic /barcomic + +ENTRYPOINT ["/barcomic"] +CMD ["-v", "-a", "0.0.0.0", "-p", "80", "-s", "-i=false"] diff --git a/Makefile b/Makefile index 0d8dcd2..f4a8c0e 100644 --- a/Makefile +++ b/Makefile @@ -1,44 +1,42 @@ -run: - @go run cmd/barcomic/main.go -v +MODULE := github.com/thegraydot/barcomic +BINARY := barcomic -clean: - ./scripts/build_clean.sh +.PHONY: fmt fmt_check mod_check vet test test_verbose test_coverage ci build install clean snapshot release_check -docker_run: - docker image build -t barcomic .; \ - docker run --name barcomic -p 8080:80 barcomic +fmt: + gofmt -w . -docker_clean: - docker container stop barcomic; \ - docker container rm barcomic; \ - docker image rm barcomic +fmt_check: + @test -z "$$(gofmt -l .)" -install_golang_deps: - @go get ./internal/barcomic +mod_check: + go mod tidy && git diff --exit-code go.mod go.sum -install_linux_deps: - ./scripts/install_linux_deps.sh +vet: + go vet ./... -update_golang_packages: - @go get -u ./...; go mod tidy +test: + go test -race -count=1 ./... -format: - @gofmt -l . +test_verbose: + go test -race -count=1 -v ./... -test: - @go test -v ./internal/barcomic +test_coverage: + go test -race -count=1 -coverpkg=./internal/... -coverprofile=coverage.out ./... -coverage: - @go test -cover ./internal/barcomic +ci: fmt_check mod_check vet test -build_linux: - ./scripts/build_linux.sh +build: + go build -ldflags="-s -w -X $(MODULE)/cmd.Version=dev" -o bin/$(BINARY) . -build_windows: - ./scripts/build_windows.sh +install: + go install -ldflags="-s -w -X $(MODULE)/cmd.Version=dev" . + +clean: + rm -rf bin/ dist/ -build_darwin: - ./scripts/build_darwin.sh +snapshot: + goreleaser release --snapshot --clean -release: - ./scripts/git_tag_and_release.sh +release_check: + goreleaser check diff --git a/README.md b/README.md index 6a5c1ff..4d277f7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,23 @@ # barcomic -![Go](https://img.shields.io/github/go-mod/go-version/TheGrayDot/barcomic_server?filename=go.mod&style=plastic) ![Build Status](https://img.shields.io/github/actions/workflow/status/TheGrayDot/barcomic_server/push_and_pull.yml?branch=main&style=plastic) +![Build Status](https://img.shields.io/github/actions/workflow/status/thegraydot/barcomic_server/tag.yml?style=flat) +![Test Status](https://img.shields.io/github/actions/workflow/status/thegraydot/barcomic_server/tag.yml?style=flat&label=test) -![Release](https://img.shields.io/github/v/release/TheGrayDot/barcomic_server?style=plastic) ![Downloads](https://img.shields.io/github/downloads/TheGrayDot/barcomic_server/total?style=plastic) +![Release Version](https://img.shields.io/github/v/release/thegraydot/barcomic_server?style=flat) +![Release downloads](https://img.shields.io/github/downloads/thegraydot/barcomic_server/total?label=downloads) + +![Go Version](https://img.shields.io/github/go-mod/go-version/thegraydot/barcomic_server) +![Code Coverage](https://img.shields.io/badge/coverage-XX%25-blue) An HTTP API for receiving comic book barcodes from the Barcomic Android application ## Barcomic App -The Barcomic application for Android and iOS (which leverages this HTTP API) is in active development and not currently publicly available. Unsure of the release date, but aiming to release before end of 2023. - -## Latest Releases - -- [Linux 64bit (`barcomic-linux`)](https://github.com/TheGrayDot/barcomic_server/releases/latest/download/barcomic-linux) -- [Windows 64bit (`barcomic-windows.exe`)](https://github.com/TheGrayDot/barcomic_server/releases/latest/download/barcomic-windows.exe) -- [OS X 64bit (`barcomic-darwin`)](https://github.com/TheGrayDot/barcomic_server/releases/latest/download/barcomic-darwin) +The Barcomic application for Android and iOS (which leverages this HTTP API) is in active development and not currently publicly available. ## Quick Start -- Download [latest release](https://github.com/TheGrayDot/barcomic_server/releases/latest/) from GitHub releases page +- Download [latest release](https://github.com/thegraydot/barcomic_server/releases/latest/) from GitHub releases page - Double click to run the program - This should automatically open and start the server in interactive mode, if it doesn't, open a terminal and run (e.g., `./barcomic-linux` on Linux) - Pick an IP address from the list, usually you Ethernet or Wi-Fi adapter, so the Barcomic Android app can connect to the server @@ -26,27 +25,23 @@ The Barcomic application for Android and iOS (which leverages this HTTP API) is ## Command Arguments -``` -./barcomic-linux -h -[*] barcomic v1.0.0-6b9b9a5a750be60bc8c8f33a0c3acdbd783406a3 -Usage of ./barcomic-linux: - -a string - IP address to listen on (default "0.0.0.0") - -i Run interactive configuration (default true) - -k Disable keystrokes - -p string - Port to listen on (default "9999") - -v Prints verbose information -``` +| Flag | Description | +|------|-------------| +| `-a` | IP address or hostname to listen on (default `0.0.0.0`) | +| `-p` | Port to listen on (default `9999`) | +| `-k` | Enable HTTPS with a self-signed certificate | +| `-s` | Disable keystroke injection (useful with `-v` for logging only) | +| `-i` | Run interactive network interface selection (default `true`) | +| `-v` | Print verbose request logging | -> NOTE: For any of the examples provided below, change `barcomic-linux` to the correct release name you have downloaded. For example, `barcomic-windows.exe` or `barcomic-darwin`. +> NOTE: For any of the examples provided below, change `barcomic-linux-amd64` to the correct release name you have downloaded. For example, `barcomic-windows-amd64.exe` or `barcomic-darwin-arm64`. ### Start server with IP and port specified Use this if you have already configured your Barcomic Android app and want to start the server using known network configuration. ``` -./barcomic-linux -a 192.168.1.100 -p 9876 +./barcomic-linux-amd64 -a 192.168.1.100 -p 9876 ``` ### Start server without keystrokes enabled @@ -54,23 +49,31 @@ Use this if you have already configured your Barcomic Android app and want to st Use this if you don't want to have the server "type" the barcode out. Good when used in verbose or logging mode. ``` -./barcomic-linux -k -v +./barcomic-linux-amd64 -s -v ``` ## Build Project Compiled binaries are provided in GitHub releases for this project. However, the following instructions provide some general guidance on building the project. The barcomic server has the following requirements: -- Go (>= 1.19) +- Go (>= 1.24) - Make -Additionally, the [`robotgo` Golang package](https://github.com/go-vgo/robotgo) is required to send barcodes as keystrokes. This package has a variety of requirements depending on the operating system. Please see the [`robotgo` requirement documentation](https://github.com/go-vgo/robotgo#requirements) for platform-specific information. - -If you are compiling on Linux, perform the following steps: +To build a snapshot for all platforms: ``` -make install_linux_deps -make build_linux +make build ``` -The compiled binaries will be created in the `bin` folder. For more detailed information on how the project is compiled, refer to the `build_*` scripts in the `scripts` folder - where there is a script for each supported platform. +Built binaries are placed in the `bin/` folder. Releases are published automatically by goreleaser when a `v*` tag is pushed. + +## Platform Prerequisites + +Barcomic injects keystrokes into the currently focused window, the same model as a USB barcode scanner. Focus your target application (e.g. a spreadsheet or web form) before scanning. + +| Platform | Requirement | +|----------|-------------| +| Windows | None, PowerShell is built in | +| macOS | None, `osascript` is built in. Grant Accessibility permission on first run via **System Settings → Privacy & Security → Accessibility** | +| Linux X11 | Install `xdotool`: `apt install xdotool` / `dnf install xdotool` | +| Linux Wayland | Install `ydotool` and start the daemon: `systemctl --user start ydotoold`. Add your user to the `input` group: `sudo usermod -aG input $USER` | diff --git a/cmd/barcomic/main.go b/cmd/root.go similarity index 50% rename from cmd/barcomic/main.go rename to cmd/root.go index 2616a4b..deb803c 100644 --- a/cmd/barcomic/main.go +++ b/cmd/root.go @@ -1,56 +1,79 @@ -package main +package cmd import ( - "flag" + "context" "fmt" "net" "os" - "regexp" "strconv" "strings" + "time" - "github.com/TheGrayDot/barcomic/internal/barcomic" + "github.com/thegraydot/barcomic/internal" + "github.com/spf13/cobra" ) var Version = "dev" -var Hash = "mode" +var Hash = "none" + +var ( + addr string + port string + enableHttps bool + disableKeystrokes bool + interactive bool + verbose bool +) -func main() { - fmt.Printf("[*] barcomic %s-%s\n", Version, Hash) +var rootCmd = &cobra.Command{ + Use: "barcomic", + Short: "An HTTP API for receiving comic book barcodes", + RunE: runServer, +} - // Configure command line arguments - addr := flag.String("a", "", "Address to listen on") - port := flag.String("p", "9999", "Port to listen on") - enableHttps := flag.Bool("k", false, "Enable HTTPS (self-signed)") - disableKeystrokes := flag.Bool("s", false, "Disable keystrokes") - interactive := flag.Bool("i", true, "Run interactive configuration") - verbose := flag.Bool("v", false, "Prints verbose information") - flag.Parse() +func init() { + rootCmd.Flags().StringVarP(&addr, "addr", "a", "", "Address to listen on") + rootCmd.Flags().StringVarP(&port, "port", "p", "9999", "Port to listen on") + rootCmd.Flags().BoolVarP(&enableHttps, "https", "k", false, "Enable HTTPS (self-signed)") + rootCmd.Flags().BoolVarP(&disableKeystrokes, "no-keystrokes", "s", false, "Disable keystroke injection") + rootCmd.Flags().BoolVarP(&interactive, "interactive", "i", true, "Run interactive configuration") + rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Print verbose information") +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func runServer(cmd *cobra.Command, args []string) error { + fmt.Printf("[*] barcomic %s-%s\n", Version, Hash) // If address is provided, set interactive to false - if *addr != "" { - *interactive = false + if addr != "" { + interactive = false } // If requested, run the interactive server config - if *interactive { - *addr = interactiveNetworkConfiguration(*addr) + if interactive { + addr = interactiveNetworkConfiguration() } // Validate IP address and port before starting server - if !validateAddr(*addr) { - fmt.Printf("[*] Error: Invalid IP address %s\n", *addr) + if !validateAddr(addr) { + fmt.Printf("[*] Error: Invalid IP address %s\n", addr) os.Exit(1) } - if !validatePort(*port) { - fmt.Printf("[*] Error: Invalid port %s\n", *port) + if !validatePort(port) { + fmt.Printf("[*] Error: Invalid port %s\n", port) os.Exit(1) } - barcomic.Start(*addr, *port, *enableHttps, *disableKeystrokes, *verbose) + barcomic.Start(addr, port, enableHttps, disableKeystrokes, verbose) + return nil } -func interactiveNetworkConfiguration(addr string) string { +func interactiveNetworkConfiguration() string { // Get all network interfaces interfaces, err := net.Interfaces() if err != nil { @@ -70,6 +93,9 @@ func interactiveNetworkConfiguration(addr string) string { // Get addresses on interface and loop addresses, err := byNameInterface.Addrs() + if err != nil { + fmt.Println(err) + } for _, address := range addresses { // Check address is IP4 and not loopback (127.0.0.1) if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { @@ -100,8 +126,7 @@ func interactiveNetworkConfiguration(addr string) string { } // Check for valid input address - validAddress := validateAddr(addrInput) - if validAddress { + if validateAddr(addrInput) { return addrInput } @@ -114,25 +139,28 @@ func interactiveNetworkConfiguration(addr string) string { fmt.Println("\n[*] Error: Network interface selection error.") os.Exit(1) } - addrInput = availableInterfaces[addrInputInt][0] - return addrInput + return availableInterfaces[addrInputInt][0] } -func validateAddr(addr string) bool { - re, _ := regexp.Compile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`) - if re.MatchString(addr) { +func validateAddr(a string) bool { + if a == "" { + return false + } + // Fast path: valid IP address (IPv4 or IPv6) + if net.ParseIP(a) != nil { return true } - return false + // Fallback: attempt DNS resolution for hostnames + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + addrs, err := net.DefaultResolver.LookupHost(ctx, a) + return err == nil && len(addrs) > 0 } -func validatePort(port string) bool { - portInt, err := strconv.ParseInt(port, 10, 0) +func validatePort(p string) bool { + portInt, err := strconv.ParseInt(p, 10, 0) if err != nil { return false } - if portInt >= 0 && portInt <= 65535 { - return true - } - return false + return portInt >= 0 && portInt <= 65535 } diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..cde20a7 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("barcomic %s-%s\n", Version, Hash) + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) +} diff --git a/go.mod b/go.mod index 69e4cb8..0d1eeb7 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,16 @@ -module github.com/TheGrayDot/barcomic +module github.com/thegraydot/barcomic go 1.24.0 toolchain go1.24.4 -require ( - github.com/go-vgo/robotgo v1.0.0-rc2.1 - github.com/mdp/qrterminal v1.0.1 -) +require github.com/mdp/qrterminal/v3 v3.2.1 require ( - github.com/ebitengine/purego v0.9.0 // indirect - github.com/gen2brain/shm v0.1.1 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/jezek/xgb v1.1.1 // indirect - github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 // indirect - github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect - github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect - github.com/otiai10/gosseract v2.2.1+incompatible // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/robotn/xgb v0.10.0 // indirect - github.com/robotn/xgbutil v0.10.0 // indirect - github.com/shirou/gopsutil/v4 v4.25.9 // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect - github.com/vcaesar/gops v0.41.0 // indirect - github.com/vcaesar/imgo v0.41.0 // indirect - github.com/vcaesar/keycode v0.10.1 // indirect - github.com/vcaesar/tt v0.20.1 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/image v0.32.0 // indirect - golang.org/x/net v0.16.0 // indirect - golang.org/x/sys v0.37.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.9 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.13.0 // indirect rsc.io/qr v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 6550a2c..abc4c14 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,18 @@ -github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= -github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= -github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/gen2brain/shm v0.1.1 h1:1cTVA5qcsUFixnDHl14TmRoxgfWEEZlTezpUj1vm5uQ= -github.com/gen2brain/shm v0.1.1/go.mod h1:UgIcVtvmOu+aCJpqJX7GOtiN7X2ct+TKLg4RTxwPIUA= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-vgo/robotgo v1.0.0-rc2.1 h1:C7AZTxQmRc0GozMMbS5m/ZgfqjnSpvUvDZtzaXUdFWk= -github.com/go-vgo/robotgo v1.0.0-rc2.1/go.mod h1:DdJUdi6mEU8ttHMbow6hKD1TjgsfgJC/H+4dusok8Uw= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= -github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= -github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4= -github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ= -github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= -github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= -github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= -github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= -github.com/otiai10/gosseract v2.2.1+incompatible h1:Ry5ltVdpdp4LAa2bMjsSJH34XHVOV7XMi41HtzL8X2I= -github.com/otiai10/gosseract v2.2.1+incompatible/go.mod h1:XrzWItCzCpFRZ35n3YtVTgq5bLAhFIkascoRo8G32QE= -github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/robotn/xgb v0.0.0-20190912153532-2cb92d044934/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ= -github.com/robotn/xgb v0.10.0 h1:O3kFbIwtwZ3pgLbp1h5slCQ4OpY8BdwugJLrUe6GPIM= -github.com/robotn/xgb v0.10.0/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ= -github.com/robotn/xgbutil v0.10.0 h1:gvf7mGQqCWQ68aHRtCxgdewRk+/KAJui6l3MJQQRCKw= -github.com/robotn/xgbutil v0.10.0/go.mod h1:svkDXUDQjUiWzLrA0OZgHc4lbOts3C+uRfP6/yjwYnU= -github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= -github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= -github.com/vcaesar/gops v0.41.0 h1:FG748Jyw3FOuZnbzSgB+CQSx2e5LbLCPWV2JU1brFdc= -github.com/vcaesar/gops v0.41.0/go.mod h1:/3048L7Rj7QjQKTSB+kKc7hDm63YhTWy5QJ10TCP37A= -github.com/vcaesar/imgo v0.41.0 h1:kNLYGrThXhB9Dd6IwFmfPnxq9P6yat2g7dpPjr7OWO8= -github.com/vcaesar/imgo v0.41.0/go.mod h1:/LGOge8etlzaVu/7l+UfhJxR6QqaoX5yeuzGIMfWb4I= -github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw= -github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= -github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= -github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= -golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= +github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/internal/barcomic/cert_test.go b/internal/barcomic/cert_test.go deleted file mode 100644 index 6253d47..0000000 --- a/internal/barcomic/cert_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package barcomic - -import ( - "crypto/x509" - "testing" -) - -func TestGenerateTLSCertificate(t *testing.T) { - // Use cert.go to generate a TLS certificate - tlsCert := GenerateTLSCertificate() - - // Parse the generated TLS certificate - cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) - if err != nil { - panic(err) - } - - // Check cert common name - serverHost := config.addr + ":" + config.port - if cert.Subject.CommonName != serverHost { - t.Fatalf("Expected host: %s, but got: %d", serverHost, cert.IPAddresses) - } -} diff --git a/internal/barcomic/restapi.go b/internal/barcomic/restapi.go deleted file mode 100644 index de62dd7..0000000 --- a/internal/barcomic/restapi.go +++ /dev/null @@ -1,161 +0,0 @@ -package barcomic - -import ( - "crypto/tls" - "flag" - "fmt" - "io" - "log" - "net/http" - "regexp" - "strconv" - - "github.com/go-vgo/robotgo" -) - -var success []byte = []byte("OK") -var error []byte = []byte("ERROR") - -func startRestApi() { - fmt.Println("[*] Generating TLS certificate...") - if config.enableHttps { - tlsCert := GenerateTLSCertificate() - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{tlsCert}, - } - - server := http.Server{ - Addr: config.addr + ":" + config.port, - TLSConfig: tlsConfig, - } - - http.HandleFunc("/health", healthHandler) - http.HandleFunc("/barcode", barcodeHandler) - http.HandleFunc("/", otherHandler) - - fmt.Printf("[*] Starting HTTPS server...\n\n") - if err := server.ListenAndServeTLS("", ""); err != nil { - log.Fatalf("ERROR: %v", err) - } - } else { - server := http.Server{ - Addr: config.addr + ":" + config.port, - } - - http.HandleFunc("/health", healthHandler) - http.HandleFunc("/barcode", barcodeHandler) - http.HandleFunc("/", otherHandler) - - fmt.Printf("[*] Starting HTTP server...\n\n") - if err := server.ListenAndServe(); err != nil { - log.Fatalf("ERROR: %v", err) - } - } -} - -func verboseLoggingHandler(req *http.Request) { - // Only log if not unit testing - if flag.Lookup("test.v") == nil && config.verbose { - fmt.Printf("INFO: %s %s %s\n", req.Method, req.RemoteAddr, req.RequestURI) - } -} - -func healthHandler(w http.ResponseWriter, req *http.Request) { - verboseLoggingHandler(req) - if req.Method == "GET" { - w.WriteHeader(http.StatusOK) - w.Write(success) - return - } else { - w.WriteHeader(http.StatusBadRequest) - w.Write(error) - return - } -} - -func barcodeHandler(w http.ResponseWriter, req *http.Request) { - verboseLoggingHandler(req) - if req.Method == "POST" { - // UPC + EAN5 is longest barcode with 17 chars - // So set max byte length to 20 - req.Body = http.MaxBytesReader(w, req.Body, 20) - buffer, err := io.ReadAll(req.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write(error) - return - } - - bufferString := string(buffer) - - // Check request body is digits - barcodeRegexp := regexp.MustCompile(`\d{12,17}`) - match := barcodeRegexp.MatchString(bufferString) - if !match { - w.WriteHeader(http.StatusBadRequest) - w.Write(error) - return - } - - // Check request body is a valid UPC - isValidUpc := validateUpc(bufferString) - if !isValidUpc { - w.WriteHeader(http.StatusBadRequest) - w.Write(error) - return - } - - // Only type keystrokes if not unit testing - // or if keystrokes is disabled - if flag.Lookup("test.v") == nil { - if !config.disableKeystrokes { - robotgo.TypeStr(bufferString) - robotgo.KeyTap("enter") - } - } - - // Print barcode if verbose - if config.verbose { - fmt.Printf("INFO: %s\n", bufferString) - } - - w.WriteHeader(http.StatusOK) - w.Write(buffer) - return - } else { - w.WriteHeader(http.StatusBadRequest) - w.Write(error) - return - } -} - -func otherHandler(w http.ResponseWriter, req *http.Request) { - verboseLoggingHandler(req) - if req.URL.Path != "/" { - http.NotFound(w, req) - return - } -} - -func validateUpc(barcode string) bool { - // Extract first 11 digits of barcode - barcodePrefix := barcode[0:11] - // Extract last (check) digit from barcode - checkDigit := barcode[11:12] - - // Sum all digits - // Even digits are multiplied by 3 - sum := 0 - for i, v := range barcodePrefix { - value, _ := strconv.Atoi(string(v)) - if i%2 == 0 { - sum += 3 * value - } else { - sum += value - } - } - - result := (10 - sum%10) % 10 - checkDigitInt, _ := strconv.Atoi(string(checkDigit)) - return result == checkDigitInt -} diff --git a/internal/barcomic/server.go b/internal/barcomic/server.go deleted file mode 100644 index 54f28a7..0000000 --- a/internal/barcomic/server.go +++ /dev/null @@ -1,45 +0,0 @@ -package barcomic - -import ( - "fmt" - "os" - - "github.com/mdp/qrterminal" -) - -type Config struct { - addr string - port string - enableHttps bool - disableKeystrokes bool - verbose bool -} - -var config Config - -func Start(addr, port string, enableHttps, disableKeystrokes, verbose bool) { - config.addr = addr - config.port = port - config.enableHttps = enableHttps - config.disableKeystrokes = disableKeystrokes - config.verbose = verbose - - // Print QR code for user to scan - printQRCode(config.addr, config.port) - - // Start the HTTP API - startRestApi() -} - -func printQRCode(addr, port string) { - qrconfig := qrterminal.Config{ - Level: qrterminal.M, - Writer: os.Stdout, - BlackChar: qrterminal.WHITE, - WhiteChar: qrterminal.BLACK, - QuietZone: 1, - } - host := addr + ":" + port - qrterminal.GenerateWithConfig(host, qrconfig) - fmt.Printf("[*] Starting server using %s:%s\n", config.addr, config.port) -} diff --git a/internal/barcomic/cert.go b/internal/cert.go similarity index 90% rename from internal/barcomic/cert.go rename to internal/cert.go index 4f5a346..0c99f82 100644 --- a/internal/barcomic/cert.go +++ b/internal/cert.go @@ -12,7 +12,7 @@ import ( "time" ) -func GenerateTLSCertificate() tls.Certificate { +func GenerateTLSCertificate(addr, port string) tls.Certificate { // Generate an RSA key with 4096 bit size key, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { @@ -35,7 +35,7 @@ func GenerateTLSCertificate() tls.Certificate { log.Fatalf("Failed to generate serial number: %v", err) } notBefore := time.Now() - notAfter := notBefore.AddDate(0, 0, 1) // 1 hour + notAfter := notBefore.AddDate(1, 0, 0) // 1 year keyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment | x509.KeyUsageDataEncipherment extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} @@ -43,9 +43,9 @@ func GenerateTLSCertificate() tls.Certificate { template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ - CommonName: config.addr + ":" + config.port, + CommonName: addr + ":" + port, Country: []string{"NZ"}, - Organization: []string{"thegraydot.io"}, + Organization: []string{"thegraydot.com"}, OrganizationalUnit: []string{"barcomic"}, }, SignatureAlgorithm: x509.SHA256WithRSA, diff --git a/internal/cert_test.go b/internal/cert_test.go new file mode 100644 index 0000000..897e51a --- /dev/null +++ b/internal/cert_test.go @@ -0,0 +1,34 @@ +package barcomic + +import ( + "crypto/x509" + "testing" + "time" +) + +func TestGenerateTLSCertificate(t *testing.T) { + const testAddr = "testhost" + const testPort = "9999" + + // Use cert.go to generate a TLS certificate + tlsCert := GenerateTLSCertificate(testAddr, testPort) + + // Parse the generated TLS certificate + cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + panic(err) + } + + // Check cert common name + serverHost := testAddr + ":" + testPort + if cert.Subject.CommonName != serverHost { + t.Fatalf("Expected CommonName: %s, but got: %s", serverHost, cert.Subject.CommonName) + } + + // Assert 1-year validity window + expected := time.Now().AddDate(1, 0, 0) + if cert.NotAfter.Before(expected.Add(-time.Minute)) || + cert.NotAfter.After(expected.Add(time.Minute)) { + t.Fatalf("Expected NotAfter ~1 year from now, got %v", cert.NotAfter) + } +} diff --git a/internal/keystroke.go b/internal/keystroke.go new file mode 100644 index 0000000..ca0ee1b --- /dev/null +++ b/internal/keystroke.go @@ -0,0 +1,133 @@ +package barcomic + +import ( + "errors" + "fmt" + "os" + "os/exec" + "runtime" +) + +var ErrNoTool = errors.New( + "keystroke: no injection tool found; install xdotool (X11) or ydotool (Wayland)", +) +var ErrUnsupportedOS = fmt.Errorf("keystroke: unsupported OS %q", runtime.GOOS) +var ErrInvalidInput = errors.New("keystroke: input contains non-digit characters") + +// validateInput hard-gates before any exec call +// Barcodes must contain only ASCII digits 0-9 +func validateInput(s string) error { + if len(s) == 0 { + return ErrInvalidInput + } + for _, r := range s { + if r < '0' || r > '9' { + return ErrInvalidInput + } + } + return nil +} + +// TypeBarcode types the barcode string into the currently focused window +// and presses Enter. Returns an error if the platform tool is unavailable +// or if input validation fails. +func TypeBarcode(barcode string) error { + if err := validateInput(barcode); err != nil { + return err + } + switch runtime.GOOS { + case "windows": + args := buildWindowsArgs(barcode) + return exec.Command(args[0], args[1:]...).Run() + case "darwin": + args := buildDarwinArgs(barcode) + return exec.Command(args[0], args[1:]...).Run() + case "linux": + return typeLinux(barcode) + default: + return ErrUnsupportedOS + } +} + +func buildWindowsArgs(barcode string) []string { + script := fmt.Sprintf( + `Add-Type -AssemblyName System.Windows.Forms; `+ + `[System.Windows.Forms.SendKeys]::SendWait('%s'); `+ + `[System.Windows.Forms.SendKeys]::SendWait('{ENTER}')`, + barcode, + ) + return []string{"powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script} +} + +func buildDarwinArgs(barcode string) []string { + script := fmt.Sprintf( + `tell application "System Events"`+"\n"+ + ` keystroke "%s"`+"\n"+ + ` key code 36`+"\n"+ + `end tell`, + barcode, + ) + return []string{"osascript", "-e", script} +} + +func buildLinuxX11TypeArgs(barcode string) []string { + // --clearmodifiers: ensures Shift/Ctrl/etc are not accidentally held + // --: prevents barcodes starting with '-' being treated as flags + return []string{"xdotool", "type", "--clearmodifiers", "--", barcode} +} + +func buildLinuxX11ReturnArgs() []string { + return []string{"xdotool", "key", "Return"} +} + +func buildLinuxWaylandTypeArgs(barcode string) []string { + return []string{"ydotool", "type", "--", barcode} +} + +func buildLinuxWaylandReturnArgs() []string { + // evdev keycode 28 = Enter; 28:1 = keydown, 28:0 = keyup + return []string{"ydotool", "key", "28:1", "28:0"} +} + +func runX11(barcode string) error { + typeArgs := buildLinuxX11TypeArgs(barcode) + if err := exec.Command(typeArgs[0], typeArgs[1:]...).Run(); err != nil { + return err + } + retArgs := buildLinuxX11ReturnArgs() + return exec.Command(retArgs[0], retArgs[1:]...).Run() +} + +func runWayland(barcode string) error { + typeArgs := buildLinuxWaylandTypeArgs(barcode) + if err := exec.Command(typeArgs[0], typeArgs[1:]...).Run(); err != nil { + return err + } + retArgs := buildLinuxWaylandReturnArgs() + return exec.Command(retArgs[0], retArgs[1:]...).Run() +} + +func typeLinux(barcode string) error { + display := os.Getenv("DISPLAY") + waylandDisplay := os.Getenv("WAYLAND_DISPLAY") + + xdotoolPath, _ := exec.LookPath("xdotool") + ydotoolPath, _ := exec.LookPath("ydotool") + + // X11: DISPLAY set and xdotool in PATH + if display != "" && xdotoolPath != "" { + return runX11(barcode) + } + + // Wayland: WAYLAND_DISPLAY set and ydotool in PATH + if waylandDisplay != "" && ydotoolPath != "" { + return runWayland(barcode) + } + + // XWayland fallback: DISPLAY set but xdotool not in PATH, attempt anyway + if display != "" { + return runX11(barcode) + } + + return ErrNoTool +} diff --git a/internal/keystroke_test.go b/internal/keystroke_test.go new file mode 100644 index 0000000..0e7d731 --- /dev/null +++ b/internal/keystroke_test.go @@ -0,0 +1,148 @@ +package barcomic + +import ( + "strings" + "testing" +) + +func TestValidateInput_ValidDigits(t *testing.T) { + cases := []string{ + "0", + "12", + "759606096015", + "12345678901234567", + "01234567890123456789", // 20 digits + } + for _, s := range cases { + t.Run(s, func(t *testing.T) { + if err := validateInput(s); err != nil { + t.Fatalf("expected nil error for %q, got %v", s, err) + } + }) + } +} + +func TestValidateInput_Empty(t *testing.T) { + if err := validateInput(""); err != ErrInvalidInput { + t.Fatalf("expected ErrInvalidInput for empty string, got %v", err) + } +} + +func TestValidateInput_NonDigit(t *testing.T) { + cases := []string{"abc", "123abc", "abc123", "1a2"} + for _, s := range cases { + t.Run(s, func(t *testing.T) { + if err := validateInput(s); err != ErrInvalidInput { + t.Fatalf("expected ErrInvalidInput for %q, got %v", s, err) + } + }) + } +} + +func TestValidateInput_Symbols(t *testing.T) { + cases := []string{"+^%~()", "123+456", "^789"} + for _, s := range cases { + t.Run(s, func(t *testing.T) { + if err := validateInput(s); err != ErrInvalidInput { + t.Fatalf("expected ErrInvalidInput for %q, got %v", s, err) + } + }) + } +} + +func TestValidateInput_NonASCIIDigits(t *testing.T) { + // Arabic-Indic digits U+0660..U+0669 look like digits but are not ASCII + cases := []string{"٠١٢٣", "١٢٣٤٥٦٧٨٩٠"} + for _, s := range cases { + t.Run(s, func(t *testing.T) { + if err := validateInput(s); err != ErrInvalidInput { + t.Fatalf("expected ErrInvalidInput for non-ASCII digits %q, got %v", s, err) + } + }) + } +} + +func TestValidateInput_Whitespace(t *testing.T) { + cases := []string{" ", "123 456", "789\n", "\t123"} + for _, s := range cases { + t.Run(s, func(t *testing.T) { + if err := validateInput(s); err != ErrInvalidInput { + t.Fatalf("expected ErrInvalidInput for %q, got %v", s, err) + } + }) + } +} + +func TestBuildWindowsArgs(t *testing.T) { + args := buildWindowsArgs("759606096015") + script := strings.Join(args, " ") + if !strings.Contains(script, "759606096015") { + t.Fatal("expected barcode in Windows args") + } + if !strings.Contains(script, "{ENTER}") { + t.Fatal("expected {ENTER} in Windows args") + } +} + +func TestBuildWindowsArgs_NoProfile(t *testing.T) { + args := buildWindowsArgs("123456789012") + joined := strings.Join(args, " ") + if !strings.Contains(joined, "-NoProfile") { + t.Fatal("expected -NoProfile in Windows args") + } + if !strings.Contains(joined, "-NonInteractive") { + t.Fatal("expected -NonInteractive in Windows args") + } +} + +func TestBuildDarwinArgs(t *testing.T) { + args := buildDarwinArgs("759606096015") + script := strings.Join(args, " ") + if !strings.Contains(script, "759606096015") { + t.Fatal("expected barcode in Darwin args") + } + if !strings.Contains(script, "key code 36") { + t.Fatal("expected key code 36 (Return) in Darwin args") + } +} + +func TestBuildLinuxX11TypeArgs(t *testing.T) { + args := buildLinuxX11TypeArgs("759606096015") + joined := strings.Join(args, " ") + if !strings.Contains(joined, "--clearmodifiers") { + t.Fatal("expected --clearmodifiers in X11 type args") + } + if !strings.Contains(joined, "-- 759606096015") { + t.Fatal("expected -- separator before barcode in X11 type args") + } +} + +func TestBuildLinuxX11ReturnArgs(t *testing.T) { + args := buildLinuxX11ReturnArgs() + joined := strings.Join(args, " ") + if !strings.Contains(joined, "Return") { + t.Fatal("expected Return in X11 return args") + } + if args[0] != "xdotool" { + t.Fatalf("expected xdotool as command, got %q", args[0]) + } +} + +func TestBuildLinuxWaylandTypeArgs(t *testing.T) { + args := buildLinuxWaylandTypeArgs("759606096015") + joined := strings.Join(args, " ") + if !strings.Contains(joined, "-- 759606096015") { + t.Fatal("expected -- separator before barcode in Wayland type args") + } + if args[0] != "ydotool" { + t.Fatalf("expected ydotool as command, got %q", args[0]) + } +} + +func TestBuildLinuxWaylandReturnArgs(t *testing.T) { + args := buildLinuxWaylandReturnArgs() + joined := strings.Join(args, " ") + if !strings.Contains(joined, "28:1") || !strings.Contains(joined, "28:0") { + t.Fatal("expected evdev keydown 28:1 and keyup 28:0 in Wayland return args") + } +} diff --git a/internal/restapi.go b/internal/restapi.go new file mode 100644 index 0000000..86b8c37 --- /dev/null +++ b/internal/restapi.go @@ -0,0 +1,163 @@ +package barcomic + +import ( + "crypto/tls" + "fmt" + "io" + "log" + "net/http" + "regexp" + "strconv" + "time" +) + +var responseOK = []byte("OK") +var responseError = []byte("ERROR") + +// barcodeRegexp matches strings that are entirely 12-17 digits +// Compiled once at package init. Anchors prevent partial matches +var barcodeRegexp = regexp.MustCompile(`^\d{12,17}$`) + +func (s *Server) startRestAPI() { + fmt.Println("[*] Generating TLS certificate...") + if s.config.enableHttps { + tlsCert := GenerateTLSCertificate(s.config.addr, s.config.port) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + } + + server := http.Server{ + Addr: s.config.addr + ":" + s.config.port, + Handler: s.mux, + TLSConfig: tlsConfig, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 15 * time.Minute, + } + + fmt.Printf("[*] Starting HTTPS server...\n\n") + if err := server.ListenAndServeTLS("", ""); err != nil { + log.Fatalf("ERROR: %v", err) + } + } else { + server := http.Server{ + Addr: s.config.addr + ":" + s.config.port, + Handler: s.mux, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 15 * time.Minute, + } + + fmt.Printf("[*] Starting HTTP server...\n\n") + if err := server.ListenAndServe(); err != nil { + log.Fatalf("ERROR: %v", err) + } + } +} + +func (s *Server) verboseLoggingHandler(req *http.Request) { + if s.config.verbose { + fmt.Printf("INFO: %s %s %s\n", req.Method, req.RemoteAddr, req.RequestURI) + } +} + +func (s *Server) healthHandler(w http.ResponseWriter, req *http.Request) { + s.verboseLoggingHandler(req) + if req.Method == "GET" { + w.WriteHeader(http.StatusOK) + w.Write(responseOK) + return + } else { + w.WriteHeader(http.StatusBadRequest) + w.Write(responseError) + return + } +} + +func (s *Server) barcodeHandler(w http.ResponseWriter, req *http.Request) { + s.verboseLoggingHandler(req) + if req.Method == "POST" { + // UPC + EAN5 is longest barcode with 17 chars + // So set max byte length to 20 + req.Body = http.MaxBytesReader(w, req.Body, 20) + buffer, err := io.ReadAll(req.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(responseError) + return + } + + bufferString := string(buffer) + + // Check request body is digits with anchored match + if !barcodeRegexp.MatchString(bufferString) { + w.WriteHeader(http.StatusBadRequest) + w.Write(responseError) + return + } + + // Check request body is a valid UPC + isValidUpc, err := validateUpc(bufferString) + if err != nil || !isValidUpc { + w.WriteHeader(http.StatusBadRequest) + w.Write(responseError) + return + } + + if !s.config.disableKeystrokes { + if err := s.typeBarcode(bufferString); err != nil { + fmt.Printf("WARN: keystroke injection failed: %v\n", err) + } + } + + // Print barcode if verbose + if s.config.verbose { + fmt.Printf("INFO: %s\n", bufferString) + } + + w.WriteHeader(http.StatusOK) + w.Write(buffer) + return + } else { + w.WriteHeader(http.StatusBadRequest) + w.Write(responseError) + return + } +} + +func (s *Server) otherHandler(w http.ResponseWriter, req *http.Request) { + s.verboseLoggingHandler(req) + if req.URL.Path != "/" { + http.NotFound(w, req) + return + } +} + +func validateUpc(barcode string) (bool, error) { + // Extract first 11 digits of barcode + barcodePrefix := barcode[0:11] + // Extract last (check) digit from barcode + checkDigit := barcode[11:12] + + // Sum all digits + // Even digits are multiplied by 3 + sum := 0 + for i, v := range barcodePrefix { + value, err := strconv.Atoi(string(v)) + if err != nil { + return false, fmt.Errorf("validateUpc: non-digit at position %d: %w", i, err) + } + if i%2 == 0 { + sum += 3 * value + } else { + sum += value + } + } + + result := (10 - sum%10) % 10 + checkDigitInt, err := strconv.Atoi(string(checkDigit)) + if err != nil { + return false, fmt.Errorf("validateUpc: non-digit check character: %w", err) + } + return result == checkDigitInt, nil +} diff --git a/internal/barcomic/restapi_test.go b/internal/restapi_test.go similarity index 81% rename from internal/barcomic/restapi_test.go rename to internal/restapi_test.go index fe94c35..0086843 100644 --- a/internal/barcomic/restapi_test.go +++ b/internal/restapi_test.go @@ -7,6 +7,12 @@ import ( "testing" ) +func newTestServer() *Server { + s := NewServer(Config{}) + s.typeBarcode = func(string) error { return nil } + return s +} + func TestHealthHandler(t *testing.T) { table := []struct { body string @@ -23,7 +29,8 @@ func TestHealthHandler(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(v.method, "/heath", nil) - healthHandler(w, r) + s := newTestServer() + s.healthHandler(w, r) if w.Code != v.statusCode { t.Fatalf("Expected status code: %d, but got: %d", v.statusCode, w.Code) @@ -54,6 +61,9 @@ func TestBarcodeHandler(t *testing.T) { {`ERROR`, `7596060960150121101211`, http.MethodPost, 400}, {`ERROR`, `75960609601`, http.MethodPost, 400}, {`ERROR`, `759606096010`, http.MethodPost, 400}, + // Invalid requests: partial matches (anchors check) + {`ERROR`, `abc759606096015xyz`, http.MethodPost, 400}, + {`ERROR`, `759606096015 `, http.MethodPost, 400}, // trailing space // Invalid requests: HTTP methods not allowed {`ERROR`, `75960609601501211`, http.MethodGet, 400}, {`ERROR`, `75960609601501211`, http.MethodDelete, 400}, @@ -64,7 +74,8 @@ func TestBarcodeHandler(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(v.method, "/barcode", strings.NewReader(v.requestBody)) - barcodeHandler(w, r) + s := newTestServer() + s.barcodeHandler(w, r) if w.Code != v.statusCode { t.Fatalf("Expected status code: %d, but got: %d", v.statusCode, w.Code) @@ -94,7 +105,8 @@ func TestOtherHandler(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(v.method, v.url, nil) - otherHandler(w, r) + s := newTestServer() + s.otherHandler(w, r) if w.Code != v.statusCode { t.Fatalf("Expected status code: %d, but got: %d", v.statusCode, w.Code) @@ -115,10 +127,12 @@ func TestValidateUpc(t *testing.T) { for _, v := range table { t.Run(v.barcode, func(t *testing.T) { - testResult := validateUpc(v.barcode) - + testResult, err := validateUpc(v.barcode) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } if testResult != v.result { - t.Fatalf("Expected status code: %t, but got: %t", v.result, testResult) + t.Fatalf("Expected result: %t, but got: %t", v.result, testResult) } }) } diff --git a/internal/server.go b/internal/server.go new file mode 100644 index 0000000..19ad80f --- /dev/null +++ b/internal/server.go @@ -0,0 +1,61 @@ +package barcomic + +import ( + "fmt" + "net/http" + "os" + + "github.com/mdp/qrterminal/v3" +) + +type Config struct { + addr string + port string + enableHttps bool + disableKeystrokes bool + verbose bool +} + +type Server struct { + config Config + mux *http.ServeMux + typeBarcode func(string) error +} + +func NewServer(cfg Config) *Server { + s := &Server{ + config: cfg, + mux: http.NewServeMux(), + typeBarcode: TypeBarcode, + } + s.mux.HandleFunc("/health", s.healthHandler) + s.mux.HandleFunc("/barcode", s.barcodeHandler) + s.mux.HandleFunc("/", s.otherHandler) + return s +} + +func Start(addr, port string, enableHttps, disableKeystrokes, verbose bool) { + cfg := Config{ + addr: addr, + port: port, + enableHttps: enableHttps, + disableKeystrokes: disableKeystrokes, + verbose: verbose, + } + srv := NewServer(cfg) + printQRCode(srv.config.addr, srv.config.port) + srv.startRestAPI() +} + +func printQRCode(addr, port string) { + qrconfig := qrterminal.Config{ + Level: qrterminal.M, + Writer: os.Stdout, + BlackChar: qrterminal.WHITE, + WhiteChar: qrterminal.BLACK, + QuietZone: 1, + } + host := addr + ":" + port + qrterminal.GenerateWithConfig(host, qrconfig) + fmt.Printf("[*] Starting server using %s:%s\n", addr, port) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e325c7b --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/thegraydot/barcomic/cmd" + +func main() { + cmd.Execute() +} diff --git a/scripts/build_clean.sh b/scripts/build_clean.sh deleted file mode 100755 index 9a41238..0000000 --- a/scripts/build_clean.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - - -cd "$(dirname "$0")" || exit 1 -source ./export.sh - -go clean -rm -f "bin/$BINARY_PREFIX-linux" -rm -f "bin/$BINARY_PREFIX-windows.exe" -rm -f "bin/$BINARY_PREFIX-darwin" diff --git a/scripts/build_darwin.sh b/scripts/build_darwin.sh deleted file mode 100755 index 7faa5c0..0000000 --- a/scripts/build_darwin.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - - -cd "$(dirname "$0")" || exit 1 -source ./export.sh - -mkdir -p ../bin - -GOARCH=amd64 \ -GOOS=darwin \ -go build \ --ldflags \ -"-X main.Version=$PROJECT_VERSION \ --X main.Hash=$COMMIT_HASH" \ --o "../bin/$BINARY_PREFIX-darwin" \ -../cmd/barcomic/main.go - -chmod u+x "../bin/$BINARY_PREFIX-darwin" diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh deleted file mode 100755 index 1a95b0e..0000000 --- a/scripts/build_linux.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - - -cd "$(dirname "$0")" || exit 1 -source ./export.sh - -mkdir -p ../bin - -GOARCH=amd64 \ -GOOS=linux \ -go build \ --ldflags \ -"-X main.Version=$PROJECT_VERSION \ --X main.Hash=$COMMIT_HASH" \ --o "../bin/$BINARY_PREFIX-linux" \ -../cmd/barcomic/main.go - -chmod u+x "../bin/$BINARY_PREFIX-linux" diff --git a/scripts/build_windows.sh b/scripts/build_windows.sh deleted file mode 100755 index 1ce14a4..0000000 --- a/scripts/build_windows.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - - -cd "$(dirname "$0")" || exit 1 -source ./export.sh - -mkdir -p ../bin - -GOARCH=amd64 \ -GOOS=windows \ -CGO_ENABLED=1 \ -CC=x86_64-w64-mingw32-gcc \ -CXX=x86_64-w64-mingw32-g++ \ -go build \ --ldflags \ -"-X main.Version=$PROJECT_VERSION \ --X main.Hash=$COMMIT_HASH" \ --o "../bin/$BINARY_PREFIX-windows.exe" \ -../cmd/barcomic/main.go - -chmod u+x "../bin/$BINARY_PREFIX-windows.exe" diff --git a/scripts/export.sh b/scripts/export.sh deleted file mode 100755 index 436e553..0000000 --- a/scripts/export.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - - -BINARY_PREFIX="barcomic" -MAJOR="1" -MINOR="0" -PATCH="9" -PROJECT_VERSION="v$MAJOR.$MINOR.$PATCH" -COMMIT_HASH=$(git rev-parse HEAD) - -export BINARY_PREFIX -export PROJECT_VERSION -export COMMIT_HASH diff --git a/scripts/git_tag_and_release.sh b/scripts/git_tag_and_release.sh deleted file mode 100755 index cef0bdd..0000000 --- a/scripts/git_tag_and_release.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - - -cd "$(dirname "$0")" || exit 1 -source ./export.sh - -echo "[*] Current version: $PROJECT_VERSION" - -read -rp "[*] Tag and Release? (y/N) " yn -case $yn in - y ) git tag "$PROJECT_VERSION"; - git push --tags; - exit 0;; - n ) echo "[*] Exiting..."; - exit 0;; - * ) echo "[*] Invalid response... Exiting"; - exit 1;; -esac diff --git a/scripts/install_linux_deps.sh b/scripts/install_linux_deps.sh deleted file mode 100755 index 67fab1d..0000000 --- a/scripts/install_linux_deps.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - - -# Packages for compiling robotgo on linux -package_list_robotgo=( - libx11-dev - xorg-dev - libxtst-dev - xcb - libxcb-xkb-dev - x11-xkb-utils - libx11-xcb-dev - libxkbcommon-x11-dev - libxkbcommon-dev - # Windows CC packages - gcc-multilib - gcc-mingw-w64 - libz-mingw-w64-dev -) - -sudo apt-get update -sudo apt-get install -y "${package_list_robotgo[@]}" From a0adc226e7da2194f573cc03caad38b4138b75b7 Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Wed, 29 Apr 2026 20:52:53 +1200 Subject: [PATCH 2/3] Fixed lint error --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index deb803c..ded79e2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/thegraydot/barcomic/internal" "github.com/spf13/cobra" + "github.com/thegraydot/barcomic/internal" ) var Version = "dev" From 9ff8963c96093ca7d557af0f12d7df0548ff6efb Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Wed, 29 Apr 2026 20:56:16 +1200 Subject: [PATCH 3/3] Tidied go mod --- go.mod | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 0d1eeb7..83f2270 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,13 @@ go 1.24.0 toolchain go1.24.4 -require github.com/mdp/qrterminal/v3 v3.2.1 +require ( + github.com/mdp/qrterminal/v3 v3.2.1 + github.com/spf13/cobra v1.10.2 +) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.9 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.13.0 // indirect