Compare commits

...

3 commits

Author SHA1 Message Date
X9 Dev
8c60b5c74e ci: set runner log level to info (was debug)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:07:05 +02:00
X9 Dev
7976ae7b62 Add Forgejo CI/CD: auto-build and publish xetup.exe on push
- Forgejo Actions workflow: builds Windows x64 exe on push to main
- Runner config: golang:1.24-alpine container on xetup Docker network
- docker-compose.yml: runner with docker socket + config mount
- nginx: /dl shortlink + /forgejo-api proxy for landing page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:07:02 +02:00
X9 Dev
7850ee7f28 Fix embed.FS path separator on Windows
Use path.Join (always '/') for embed.FS reads, filepath.Join only for OS paths.
filepath.Join on Windows produces backslashes which embed.FS doesn't accept,
causing "failed to extract scripts" on startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:06:57 +02:00
7 changed files with 108 additions and 6 deletions

View file

@ -0,0 +1,74 @@
name: release
on:
push:
branches: [main]
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'scripts/**'
- 'assets/**'
- 'embed.go'
- '.forgejo/workflows/release.yml'
jobs:
build-and-release:
# Runner label 'ubuntu-latest' maps to golang:1.23-alpine container (see runner config)
runs-on: ubuntu-latest
defaults:
run:
shell: sh
working-directory: /repo
steps:
- name: Setup
working-directory: /
run: |
apk add --no-cache git curl jq
git clone --depth=1 \
"http://x9:${{ secrets.FORGEJO_TOKEN }}@xetup-forgejo:3000/${{ github.repository }}.git" \
/repo
cd /repo
git checkout "${{ github.sha }}"
- name: Build xetup.exe
run: |
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o xetup.exe ./cmd/xetup/
echo "Built: $(ls -lh xetup.exe | awk '{print $5}')"
- name: Publish latest release
env:
TOKEN: ${{ secrets.FORGEJO_TOKEN }}
API: http://xetup-forgejo:3000/api/v1
REPO: ${{ github.repository }}
run: |
SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
# Delete existing 'latest' release and tag to recreate cleanly
RID=$(curl -sf -H "Authorization: token $TOKEN" \
"$API/repos/$REPO/releases/tags/latest" | jq -r '.id // empty')
if [ -n "$RID" ]; then
curl -sf -X DELETE -H "Authorization: token $TOKEN" \
"$API/repos/$REPO/releases/$RID" || true
fi
curl -sf -X DELETE -H "Authorization: token $TOKEN" \
"$API/repos/$REPO/tags/latest" || true
# Create new 'latest' release
RID=$(curl -sf -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/releases" \
-d "{\"tag_name\":\"latest\",\"name\":\"latest\",\"body\":\"Auto-built from ${SHORT}\",\"prerelease\":true}" \
| jq -r '.id')
# Upload xetup.exe
curl -sf -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/octet-stream" \
"$API/repos/$REPO/releases/$RID/assets?name=xetup.exe" \
--data-binary @xetup.exe
echo "Released xetup.exe (commit ${SHORT})"

View file

@ -67,3 +67,4 @@ func main() {
log.Fatalf("TUI error: %v", err) log.Fatalf("TUI error: %v", err)
} }
} }
// build 1776332628

View file

@ -38,6 +38,8 @@ services:
image: code.forgejo.org/forgejo/runner:6.3.1 image: code.forgejo.org/forgejo/runner:6.3.1
container_name: xetup-runner container_name: xetup-runner
restart: unless-stopped restart: unless-stopped
entrypoint: ["/bin/sh", "-c", "forgejo-runner daemon --config /etc/runner/config.yml"]
user: "0:996" # root:docker - needed for /var/run/docker.sock access
depends_on: depends_on:
- forgejo - forgejo
environment: environment:
@ -45,6 +47,7 @@ services:
volumes: volumes:
- runner-data:/data - runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./runner-config.yml:/etc/runner/config.yml:ro
networks: networks:
- xetup - xetup

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -277,7 +278,8 @@ func ExtractScripts(fs interface{ ReadDir(string) ([]os.DirEntry, error); ReadFi
if e.IsDir() { if e.IsDir() {
continue continue
} }
data, err := fs.ReadFile(filepath.Join("scripts", e.Name())) // embed.FS always uses forward slashes regardless of OS
data, err := fs.ReadFile(path.Join("scripts", e.Name()))
if err != nil { if err != nil {
return err return err
} }
@ -298,13 +300,14 @@ func extractDir(fs interface{ ReadDir(string) ([]os.DirEntry, error); ReadFile(s
if err != nil { if err != nil {
return err return err
} }
dst := filepath.Join(dstBase, src) dst := filepath.Join(dstBase, filepath.FromSlash(src))
if err := os.MkdirAll(dst, 0755); err != nil { if err := os.MkdirAll(dst, 0755); err != nil {
return err return err
} }
for _, e := range entries { for _, e := range entries {
srcPath := filepath.Join(src, e.Name()) // embed.FS always uses forward slashes regardless of OS
dstPath := filepath.Join(dstBase, srcPath) srcPath := path.Join(src, e.Name())
dstPath := filepath.Join(dstBase, filepath.FromSlash(srcPath))
if e.IsDir() { if e.IsDir() {
if err := extractDir(fs, srcPath, dstBase); err != nil { if err := extractDir(fs, srcPath, dstBase); err != nil {
return err return err

21
runner-config.yml Normal file
View file

@ -0,0 +1,21 @@
log:
level: info
runner:
file: /data/.runner
capacity: 1
timeout: 3h
fetch_timeout: 5s
fetch_interval: 2s
report_interval: 1s
cache:
enabled: true
container:
network: xetup
privileged: false
valid_volumes:
- '**'
docker_host: "-"
force_pull: false

View file

@ -14,9 +14,9 @@ server {
add_header X-Content-Type-Options nosniff always; add_header X-Content-Type-Options nosniff always;
} }
# Permanent shortlink to latest xetup.exe update on each release # Permanent shortlink to latest xetup.exe never needs updating
location = /dl { location = /dl {
return 302 https://git.xetup.x9.cz/x9/xetup/releases/download/v0.1.0/xetup.exe; return 302 https://git.xetup.x9.cz/x9/xetup/releases/download/latest/xetup.exe;
} }
# Proxy Forgejo API calls so browser doesn't need CORS or direct access to Forgejo # Proxy Forgejo API calls so browser doesn't need CORS or direct access to Forgejo

BIN
xetup.exe

Binary file not shown.