A set of Docker Container usefull to automate build of Godot games with Woodpecker CI.

Add steamci

Bigaston e2ae52cc 80e2cdf1

Changed files
+424 -16
.woodpecker
examples
steamci
+28
.woodpecker/build_steam.yml
···
··· 1 + when: 2 + - event: push 3 + branch: main 4 + 5 + steps: 6 + - name: Build plugin 7 + image: docker 8 + commands: 9 + - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD ghcr.io 10 + - docker build -t ghcr.io/bigaston/steamci:latest steamci 11 + - docker push ghcr.io/bigaston/steamci:latest 12 + 13 + # Codeberg 14 + - docker login -u $DOCKER_CODEBERG_USERNAME -p $DOCKER_CODEBERG_PASSWORD codeberg.org 15 + - docker image tag ghcr.io/bigaston/steamci:latest codeberg.org/bigaston/steamci:latest 16 + - docker push codeberg.org/bigaston/steamci:latest 17 + 18 + volumes: 19 + - /var/run/docker.sock:/var/run/docker.sock 20 + environment: 21 + DOCKER_USERNAME: 22 + from_secret: DOCKER_USERNAME 23 + DOCKER_PASSWORD: 24 + from_secret: DOCKER_PASSWORD 25 + DOCKER_CODEBERG_USERNAME: 26 + from_secret: DOCKER_CODEBERG_USERNAME 27 + DOCKER_CODEBERG_PASSWORD: 28 + from_secret: DOCKER_CODEBERG_PASSWORD
+14 -16
examples/build_steam.yml
··· 24 - godot --headless --export-release ${TARGET} 25 26 - name: Upload Steam 27 - image: debian 28 - commands: 29 - - apt update 30 - - apt install -y lib32gcc-s1 curl tar 31 - 32 - - curl -sqL "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar zxvf - 33 - - chmod +x steamcmd.sh 34 - 35 - - mkdir -p /root/Steam/config 36 - - echo "$STEAM_AUTH_VDF" | base64 -d > /root/Steam/config/config.vdf 37 - - chmod 777 /root/Steam/config/config.vdf 38 - 39 - - ./steamcmd.sh +login username +quit 40 - - ./steamcmd.sh +login username +run_app_build support/steam/upload_config/upload_${TARGET}.vdf +quit 41 - environment: 42 - STEAM_AUTH_VDF: 43 from_secret: STEAM_AUTH_VDF 44 45 - name: Discord Notification Success 46 image: ghcr.io/bigaston/discordnotification
··· 24 - godot --headless --export-release ${TARGET} 25 26 - name: Upload Steam 27 + image: ghcr.io/bigaston/steamci 28 + settings: 29 + steam_username: something_deploy 30 + steam_auth_vdf: 31 from_secret: STEAM_AUTH_VDF 32 + app_id: 000 33 + content_root: ".build" 34 + depot_id: 35 + Windows: 000 36 + Linux: 001 37 + local_path: 38 + Windows: "Windows/*" 39 + Linux: "Linux/*" 40 + matrix: ${TARGET} 41 + set_live: "beta" 42 43 - name: Discord Notification Success 44 image: ghcr.io/bigaston/discordnotification
+62
examples/build_steam_old.yml
···
··· 1 + when: 2 + - event: push 3 + branch: main 4 + 5 + matrix: 6 + TARGET: 7 + - Linux 8 + - Windows 9 + 10 + steps: 11 + - name: Build the game for ${TARGET} 12 + image: ghcr.io/bigaston/godotci:4.5-beta3 13 + commands: 14 + - godot --version 15 + 16 + - "> commit.txt" 17 + - short_commit=$(git rev-parse --short HEAD) 18 + - echo "$short_commit" > commit.txt 19 + 20 + - godot --headless --editor --import 21 + 22 + - mkdir -p .build/${TARGET} 23 + 24 + - godot --headless --export-release ${TARGET} 25 + 26 + - name: Upload Steam 27 + image: debian 28 + commands: 29 + - apt update 30 + - apt install -y lib32gcc-s1 curl tar 31 + 32 + - curl -sqL "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar zxvf - 33 + - chmod +x steamcmd.sh 34 + 35 + - mkdir -p /root/Steam/config 36 + - echo "$STEAM_AUTH_VDF" | base64 -d > /root/Steam/config/config.vdf 37 + - chmod 777 /root/Steam/config/config.vdf 38 + 39 + - ./steamcmd.sh +login username +quit 40 + - ./steamcmd.sh +login username +run_app_build support/steam/upload_config/upload_${TARGET}.vdf +quit 41 + environment: 42 + STEAM_AUTH_VDF: 43 + from_secret: STEAM_AUTH_VDF 44 + 45 + - name: Discord Notification Success 46 + image: ghcr.io/bigaston/discordnotification 47 + settings: 48 + result: success 49 + matrix: ${TARGET} 50 + webhook: 51 + from_secret: WEBHOOK 52 + when: 53 + - status: ["success"] 54 + - name: Discord Notification Failure 55 + image: ghcr.io/bigaston/discordnotification 56 + settings: 57 + result: failure 58 + matrix: ${TARGET} 59 + webhook: 60 + from_secret: WEBHOOK 61 + when: 62 + - status: ["failure"]
+28
steamci/.gitignore
···
··· 1 + # ---> Go 2 + # If you prefer the allow list template instead of the deny list, see community template: 3 + # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 4 + # 5 + # Binaries for programs and plugins 6 + *.exe 7 + *.exe~ 8 + *.dll 9 + *.so 10 + *.dylib 11 + 12 + # Test binary, built with `go test -c` 13 + *.test 14 + 15 + # Output of the go coverage tool, specifically when used with LiteIDE 16 + *.out 17 + 18 + # Dependency directories (remove the comment below to include it) 19 + # vendor/ 20 + 21 + # Go workspace file 22 + go.work 23 + go.work.sum 24 + 25 + # env file 26 + .env 27 + 28 + steamci
+21
steamci/.woodpecker/build.yml
···
··· 1 + when: 2 + - event: push 3 + branch: main 4 + 5 + steps: 6 + - name: Build SteamCI 7 + image: docker 8 + commands: 9 + - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY 10 + - docker build -t $DOCKER_REGISTRY/${CI_REPO_OWNER,,}/${CI_REPO_NAME,,}:latest . 11 + - docker push $DOCKER_REGISTRY/${CI_REPO_OWNER,,}/${CI_REPO_NAME,,}:latest 12 + 13 + volumes: 14 + - /var/run/docker.sock:/var/run/docker.sock 15 + environment: 16 + DOCKER_USERNAME: 17 + from_secret: DOCKER_USERNAME 18 + DOCKER_PASSWORD: 19 + from_secret: DOCKER_PASSWORD 20 + DOCKER_REGISTRY: 21 + from_secret: DOCKER_REGISTRY
+23
steamci/Dockerfile
···
··· 1 + FROM golang:bookworm 2 + 3 + COPY * . 4 + ADD templates templates 5 + 6 + RUN go build -o /bin/steamci . 7 + RUN ls 8 + 9 + FROM debian 10 + 11 + COPY --from=0 /bin/steamci /bin/steamci 12 + 13 + WORKDIR /home/steam 14 + RUN apt update 15 + RUN apt install -y lib32gcc-s1 curl tar 16 + 17 + RUN curl -sqL "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar zxvf - 18 + RUN chmod +x steamcmd.sh 19 + RUN /home/steam/steamcmd.sh +quit 20 + 21 + RUN rm -rf /var/lib/apt/lists/* 22 + 23 + CMD ["/bin/steamci"]
+52
steamci/README.md
···
··· 1 + # SteamCI 2 + 3 + This is a all contained Woodpecker CI plugins that will generate the .vdf of Steam for you, and then upload your game on the specified depot. 4 + 5 + ## Settings 6 + - steam_username: The username linked with the auth vdf 7 + - steam_auth_vdf: (More doc coming) A base64 encoded string that contains all the content of config.vdf that you will get when you execute the local +login command. A bit clanky because Steam don't provide easy access token, so you have to login on local to pass SteamGuard, and then copy the content of the file 8 + - app_id: Your Steam App Id 9 + - content_root: The root of the published folder 10 + - depot_id: Can be a single int or a mapping from matrix:depot_id 11 + - local_path: Where your builded game finish. Can be a single string or a mapping from matrix:local_path 12 + - matrix: A parameter to map your depot_id and local_path (Like Windows, Linux...) 13 + - set_live: Optionnal, deploy the build on a Steam branch. Can't be *default* due to security reason 14 + - description: Optionnal, a description that you may find on your Steamworks Dashboard (Default to the commit SHA) 15 + 16 + ## Example 17 + ```yml 18 + when: 19 + - event: push 20 + branch: main 21 + 22 + matrix: 23 + TARGET: 24 + - Windows 25 + - Linux 26 + 27 + steps: 28 + - name: Build the game for ${TARGET} 29 + image: ghcr.io/bigaston/godotci:4.5-beta3 30 + commands: 31 + - godot --version 32 + - godot --headless --editor --import 33 + - mkdir -p .build/${TARGET} 34 + - godot --headless --export-release ${TARGET} 35 + 36 + - name: Upload Steam 37 + image: ghcr.io/bigaston/steamci 38 + settings: 39 + steam_username: something_deploy 40 + steam_auth_vdf: 41 + from_secret: STEAM_AUTH_VDF 42 + app_id: 000 43 + content_root: ".build" 44 + depot_id: 45 + Windows: 000 46 + Linux: 001 47 + local_path: 48 + Windows: "Windows/*" 49 + Linux: "Linux/*" 50 + matrix: ${TARGET} 51 + set_live: "beta" 52 + ```
+3
steamci/go.mod
···
··· 1 + module bigaston/steamci 2 + 3 + go 1.24.5
+153
steamci/main.go
···
··· 1 + package main 2 + 3 + import ( 4 + "bytes" 5 + "encoding/base64" 6 + "encoding/json" 7 + "fmt" 8 + "html/template" 9 + "os" 10 + "os/exec" 11 + "path" 12 + "strconv" 13 + ) 14 + 15 + func main() { 16 + vdfData := VdfTemplate{ 17 + AppId: getRequiredEnv("PLUGIN_APP_ID"), 18 + ContentRoot: getRequiredEnv("PLUGIN_CONTENT_ROOT"), 19 + } 20 + 21 + description, hasDescription := os.LookupEnv("PLUGIN_DESCRIPTION") 22 + 23 + if hasDescription { 24 + vdfData.Description = description 25 + } else { 26 + vdfData.Description = fmt.Sprintf("Pushed from CI at commit %s", os.Getenv("CI_COMMIT_SHA")) 27 + } 28 + 29 + setLive, hasSetLive := os.LookupEnv("PLUGIN_SET_LIVE") 30 + 31 + if hasSetLive { 32 + vdfData.HasSetLive = true 33 + vdfData.SetLive = setLive 34 + } 35 + 36 + // Get Depot Id 37 + depotId := getRequiredEnv("PLUGIN_DEPOT_ID") 38 + parsed := make(map[string]json.RawMessage) 39 + err := json.Unmarshal([]byte(depotId), &parsed) 40 + 41 + if err == nil { 42 + matrix := getRequiredEnv("PLUGIN_MATRIX") 43 + 44 + var depot_id int 45 + parsedValue, ok := parsed[matrix] 46 + 47 + if !ok { 48 + panic(fmt.Sprintf("You have specified %s in your matrix, but field doesn't exist in depot_id", matrix)) 49 + } 50 + 51 + err := json.Unmarshal(parsedValue, &depot_id) 52 + check(err) 53 + 54 + fmt.Printf("Found DepotID %d for matrix %s\n", depot_id, matrix) 55 + 56 + vdfData.DepotId = strconv.Itoa(depot_id) 57 + } else { 58 + vdfData.DepotId = depotId 59 + } 60 + 61 + // Get Local Path 62 + localPath := getRequiredEnv("PLUGIN_LOCAL_PATH") 63 + parsed = make(map[string]json.RawMessage) 64 + err = json.Unmarshal([]byte(localPath), &parsed) 65 + 66 + if err == nil { 67 + matrix := getRequiredEnv("PLUGIN_MATRIX") 68 + 69 + var local_path string 70 + parsedValue, ok := parsed[matrix] 71 + 72 + if !ok { 73 + panic(fmt.Sprintf("You have specified %s in your matrix, but field doesn't exist in local_path", matrix)) 74 + } 75 + 76 + err := json.Unmarshal(parsedValue, &local_path) 77 + check(err) 78 + 79 + fmt.Printf("Found LocalPath %s for matrix %s\n", local_path, matrix) 80 + 81 + vdfData.LocalPath = local_path 82 + } else { 83 + vdfData.LocalPath = localPath 84 + } 85 + 86 + tmpl, err := template.New("vdf.tmpl").ParseFS(templates, "templates/vdf.tmpl") 87 + check(err) 88 + 89 + buf := &bytes.Buffer{} 90 + err = tmpl.Execute(buf, vdfData) 91 + check(err) 92 + 93 + err = os.WriteFile(path.Join(os.Getenv("CI_WORKSPACE"), "__upload__.vdf"), buf.Bytes(), os.ModeAppend) 94 + check(err) 95 + 96 + fmt.Println("Uploading on Steam using VDF:") 97 + fmt.Println(buf.String()) 98 + 99 + // Start the Steam Command 100 + steamAuthVdf := getRequiredEnv("PLUGIN_STEAM_AUTH_VDF") 101 + base64Decoded, hasError := base64Decode(steamAuthVdf) 102 + 103 + if hasError { 104 + panic("Error while decoding STEAM_AUTH_VDF base64") 105 + } 106 + 107 + err = os.WriteFile("/root/Steam/config/config.vdf", base64Decoded, 0777) 108 + check(err) 109 + 110 + steamUsername := getRequiredEnv("PLUGIN_STEAM_USERNAME") 111 + 112 + err = runSteamCommand([]string{"+login", steamUsername, "+quit"}) 113 + check(err) 114 + 115 + err = runSteamCommand([]string{"+login", steamUsername, "+run_app_build", path.Join(os.Getenv("CI_WORKSPACE"), "__upload__.vdf"), "+quit"}) 116 + check(err) 117 + 118 + fmt.Println("Everything went fine!") 119 + } 120 + 121 + func getRequiredEnv(envName string) string { 122 + result, isPresent := os.LookupEnv(envName) 123 + 124 + if !isPresent { 125 + panic(fmt.Sprintf("Variable %s is not present but required", envName)) 126 + } 127 + 128 + return result 129 + } 130 + 131 + func base64Decode(str string) ([]byte, bool) { 132 + data, err := base64.StdEncoding.DecodeString(str) 133 + if err != nil { 134 + return []byte{}, true 135 + } 136 + return data, false 137 + } 138 + 139 + func runSteamCommand(params []string) error { 140 + cmd := exec.Command("/home/steam/steamcmd.sh", params...) 141 + 142 + cmd.Dir = "/woodpecker" 143 + cmd.Stderr = os.Stderr 144 + cmd.Stdout = os.Stdout 145 + 146 + return cmd.Run() 147 + } 148 + 149 + func check(err error) { 150 + if err != nil { 151 + panic(err) 152 + } 153 + }
+16
steamci/templates.go
···
··· 1 + package main 2 + 3 + import "embed" 4 + 5 + //go:embed templates/* 6 + var templates embed.FS 7 + 8 + type VdfTemplate struct { 9 + AppId string 10 + Description string 11 + ContentRoot string 12 + HasSetLive bool 13 + SetLive string 14 + DepotId string 15 + LocalPath string 16 + }
+24
steamci/templates/vdf.tmpl
···
··· 1 + "AppBuild" 2 + { 3 + "AppID" "{{ .AppId }}" 4 + "Desc" "{{ .Description }}" 5 + 6 + "ContentRoot" "{{ .ContentRoot }}" 7 + "BuildOutput" ".\output\" 8 + {{ if .HasSetLive}} 9 + "SetLive" "{{ .SetLive }}" 10 + {{ end }} 11 + 12 + "Depots" 13 + { 14 + "{{ .DepotId }}" 15 + { 16 + "FileMapping" 17 + { 18 + "LocalPath" "{{ .LocalPath }}" 19 + "DepotPath" "." 20 + "recursive" "1" 21 + } 22 + } 23 + } 24 + }