mirror of
https://github.com/jakejarvis/npqueue.git
synced 2025-04-26 06:55:21 -04:00
deploy to heroku with docker and github action
This commit is contained in:
parent
4fbcdbe2a6
commit
016242bcf3
44
.github/workflows/deploy.yml
vendored
Normal file
44
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- name: Lint
|
||||||
|
uses: actions-contrib/golangci-lint@master
|
||||||
|
with:
|
||||||
|
args: run
|
||||||
|
- name: Setup Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
- name: Get dependencies
|
||||||
|
run: go mod download
|
||||||
|
- name: Build
|
||||||
|
run: go build -v .
|
||||||
|
- name: Heroku Login
|
||||||
|
uses: actions/heroku@master
|
||||||
|
env:
|
||||||
|
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
|
||||||
|
with:
|
||||||
|
args: container:login
|
||||||
|
- name: Heroku Push
|
||||||
|
uses: actions/heroku@master
|
||||||
|
env:
|
||||||
|
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
|
||||||
|
with:
|
||||||
|
args: container:push -a npqueue web
|
||||||
|
- name: Heroku Release
|
||||||
|
uses: actions/heroku@master
|
||||||
|
env:
|
||||||
|
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
|
||||||
|
with:
|
||||||
|
args: container:release -a npqueue web
|
40
.github/workflows/go.yml
vendored
40
.github/workflows/go.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
name: Go
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Set up Go 1.12
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.12
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
|
|
||||||
- name: Download and install golangci-lint
|
|
||||||
run: |
|
|
||||||
curl -L https://github.com/golangci/golangci-lint/releases/download/v1.17.1/golangci-lint-1.17.1-linux-amd64.tar.gz | tar xzf - -C /tmp
|
|
||||||
sudo mv /tmp/golangci-lint-1.17.1-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
|
|
||||||
|
|
||||||
- name: Run the linter
|
|
||||||
run: golangci-lint run
|
|
||||||
|
|
||||||
- name: Install deps
|
|
||||||
run: go mod download
|
|
||||||
|
|
||||||
- name: Get dependencies
|
|
||||||
run: |
|
|
||||||
go get -v -t -d ./...
|
|
||||||
if [ -f Gopkg.toml ]; then
|
|
||||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
|
||||||
dep ensure
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: go build -v .
|
|
||||||
|
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
FROM golang:1.13 AS builder
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . /src
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o npqueue .
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
FROM heroku/heroku:18
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /src /app
|
||||||
|
|
||||||
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
|
CMD ["./npqueue"]
|
@ -1,6 +1,6 @@
|
|||||||
# [NoPixel Queue List](https://np.pogge.rs/) 👾
|
# [NoPixel Queue List](https://np.pogge.rs/) 👾
|
||||||
|
|
||||||
[](https://travis-ci.org/jakejarvis/npqueue)
|

|
||||||
|
|
||||||
Active and queued player list for [NoPixel GTA5 RP](https://www.nopixel.net/) server. Based on [@shivammeh/nplists3](https://github.com/shivammeh/nplists3).
|
Active and queued player list for [NoPixel GTA5 RP](https://www.nopixel.net/) server. Based on [@shivammeh/nplists3](https://github.com/shivammeh/nplists3).
|
||||||
|
|
||||||
|
337
main.go
337
main.go
@ -1,243 +1,242 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/contrib/static"
|
"github.com/gin-gonic/contrib/static"
|
||||||
"github.com/gin-contrib/secure"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin"
|
_ "github.com/heroku/x/hmetrics/onload"
|
||||||
_ "github.com/heroku/x/hmetrics/onload"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerDetailsStruct struct {
|
type ServerDetailsStruct struct {
|
||||||
Players `json:"players"`
|
Players `json:"players"`
|
||||||
ServerQueue
|
ServerQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerQueue struct {
|
type ServerQueue struct {
|
||||||
CurrentPlayers int64 `json:"currentPlayers"`
|
CurrentPlayers int64 `json:"currentPlayers"`
|
||||||
CurrentQueue int64 `json:"currentQueue"`
|
CurrentQueue int64 `json:"currentQueue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Players []Player
|
type Players []Player
|
||||||
|
|
||||||
// Player details
|
// Player details
|
||||||
type Player struct {
|
type Player struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Identifiers []string `json:"identifiers"`
|
Identifiers []string `json:"identifiers"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Ping int64 `json:"ping"`
|
Ping int64 `json:"ping"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Nopixeldata []NoPixelPlayer
|
type Nopixeldata []NoPixelPlayer
|
||||||
|
|
||||||
type NoPixelPlayer struct {
|
type NoPixelPlayer struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
NoPixelID string `json:"noPixelID"`
|
NoPixelID string `json:"noPixelID"`
|
||||||
SteamID string `json:"steamID"`
|
SteamID string `json:"steamID"`
|
||||||
Twitch string `json:"twitch"`
|
Twitch string `json:"twitch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
jsonGet = &http.Client{Timeout: 10 * time.Second}
|
jsonGet = &http.Client{Timeout: 10 * time.Second}
|
||||||
// Using an environment variable to protect IP
|
// Using an environment variable to protect IP
|
||||||
ServerAddress = os.Getenv("SERVER_IP")
|
ServerAddress = os.Getenv("SERVER_IP")
|
||||||
// ServerDetails struct to hold PlayerList & ServerDetails struct
|
// ServerDetails struct to hold PlayerList & ServerDetails struct
|
||||||
ServerDetails = &ServerDetailsStruct{}
|
ServerDetails = &ServerDetailsStruct{}
|
||||||
// NoPixelData struct
|
// NoPixelData struct
|
||||||
NoPixelData Nopixeldata
|
NoPixelData Nopixeldata
|
||||||
)
|
)
|
||||||
|
|
||||||
// getPlayerList sends HTTP get request to get list of players from /players.json
|
// getPlayerList sends HTTP get request to get list of players from /players.json
|
||||||
func getPlayerList() (err error) {
|
func getPlayerList() (err error) {
|
||||||
server := strings.Builder{}
|
server := strings.Builder{}
|
||||||
fmt.Fprintf(&server, "http://%s/players.json", ServerAddress)
|
fmt.Fprintf(&server, "http://%s/players.json", ServerAddress)
|
||||||
|
|
||||||
req, err := jsonGet.Get(server.String())
|
req, err := jsonGet.Get(server.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
|
|
||||||
err = json.NewDecoder(req.Body).Decode(&ServerDetails.Players)
|
err = json.NewDecoder(req.Body).Decode(&ServerDetails.Players)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// getServerQueue opens UDP socket to get queue count
|
// getServerQueue opens UDP socket to get queue count
|
||||||
func getServerQueue() (err error) {
|
func getServerQueue() (err error) {
|
||||||
serverData := make([]byte, 256)
|
serverData := make([]byte, 256)
|
||||||
serverConnection, err := net.Dial("udp", ServerAddress)
|
serverConnection, err := net.Dial("udp", ServerAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
defer serverConnection.Close()
|
defer serverConnection.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP voodoo to get server info -- https://github.com/LiquidObsidian/fivereborn-query/blob/master/index.js#L54
|
// UDP voodoo to get server info -- https://github.com/LiquidObsidian/fivereborn-query/blob/master/index.js#L54
|
||||||
fmt.Fprintf(serverConnection, "\xFF\xFF\xFF\xFFgetinfo f")
|
fmt.Fprintf(serverConnection, "\xFF\xFF\xFF\xFFgetinfo f")
|
||||||
_, err = bufio.NewReader(serverConnection).Read(serverData)
|
_, err = bufio.NewReader(serverConnection).Read(serverData)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
serverData := bytes.Split(serverData, []byte("\n"))
|
serverData := bytes.Split(serverData, []byte("\n"))
|
||||||
serverDetails := bytes.Split(serverData[1], []byte("\\"))
|
serverDetails := bytes.Split(serverData[1], []byte("\\"))
|
||||||
serverQueue := bytes.FieldsFunc(serverDetails[12], func(c rune) bool { return c == '[' || c == ']' })
|
serverQueue := bytes.FieldsFunc(serverDetails[12], func(c rune) bool { return c == '[' || c == ']' })
|
||||||
|
|
||||||
currentPlayerValues, _ := strconv.ParseInt(string(serverDetails[4]), 0, 64)
|
currentPlayerValues, _ := strconv.ParseInt(string(serverDetails[4]), 0, 64)
|
||||||
currentserverQueueValues, _ := strconv.ParseInt(string(serverQueue[0]), 0, 64)
|
currentserverQueueValues, _ := strconv.ParseInt(string(serverQueue[0]), 0, 64)
|
||||||
ServerDetails.ServerQueue.CurrentPlayers = currentPlayerValues
|
ServerDetails.ServerQueue.CurrentPlayers = currentPlayerValues
|
||||||
|
|
||||||
if currentserverQueueValues >= 1 {
|
if currentserverQueueValues >= 1 {
|
||||||
ServerDetails.ServerQueue.CurrentQueue = currentserverQueueValues
|
ServerDetails.ServerQueue.CurrentQueue = currentserverQueueValues
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func steam64toSteam(input int64) (steamid string) {
|
func steam64toSteam(input int64) (steamid string) {
|
||||||
legacySteamid := ((input - 76561197960265728) / 2)
|
legacySteamid := ((input - 76561197960265728) / 2)
|
||||||
steamid = fmt.Sprintf("STEAM_0:%d:%d", (input % 2), legacySteamid)
|
steamid = fmt.Sprintf("STEAM_0:%d:%d", (input % 2), legacySteamid)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePlayers() (err error) {
|
func parsePlayers() (err error) {
|
||||||
var steamIDs []string
|
var steamIDs []string
|
||||||
for i, v := range ServerDetails.Players {
|
for i, v := range ServerDetails.Players {
|
||||||
steamIDs = nil
|
steamIDs = nil
|
||||||
for ii, vv := range v.Identifiers {
|
for ii, vv := range v.Identifiers {
|
||||||
if ii == 0 {
|
if ii == 0 {
|
||||||
hexID := strings.Replace(vv, "steam:", "0x", -1)
|
hexID := strings.Replace(vv, "steam:", "0x", -1)
|
||||||
steamID, _ := strconv.ParseInt(hexID, 0, 64)
|
steamID, _ := strconv.ParseInt(hexID, 0, 64)
|
||||||
s := strconv.FormatInt(steamID, 10)
|
s := strconv.FormatInt(steamID, 10)
|
||||||
p := getPlayerNoPixelInformation(s)
|
p := getPlayerNoPixelInformation(s)
|
||||||
|
|
||||||
steamIDs = append(steamIDs,
|
steamIDs = append(steamIDs,
|
||||||
p.Name,
|
p.Name,
|
||||||
steam64toSteam(steamID),
|
steam64toSteam(steamID),
|
||||||
fmt.Sprintf("%d", steamID),
|
fmt.Sprintf("%d", steamID),
|
||||||
p.Twitch,
|
p.Twitch,
|
||||||
p.NoPixelID)
|
p.NoPixelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerDetails.Players[i].Identifiers = steamIDs
|
ServerDetails.Players[i].Identifiers = steamIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPlayersJSON() (err error) {
|
func loadPlayersJSON() (err error) {
|
||||||
jsonFile, err := jsonGet.Get("https://raw.githubusercontent.com/jakejarvis/npqueue/master/directory.json")
|
jsonFile, err := jsonGet.Get("https://raw.githubusercontent.com/jakejarvis/npqueue/master/directory.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(jsonFile.Body).Decode(&NoPixelData)
|
err = json.NewDecoder(jsonFile.Body).Decode(&NoPixelData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPlayerNoPixelInformation(id string) (p NoPixelPlayer) {
|
func getPlayerNoPixelInformation(id string) (p NoPixelPlayer) {
|
||||||
for i := range NoPixelData {
|
for i := range NoPixelData {
|
||||||
if NoPixelData[i].SteamID == id {
|
if NoPixelData[i].SteamID == id {
|
||||||
return NoPixelData[i]
|
return NoPixelData[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// List handler for /api/list route
|
// List handler for /api/list route
|
||||||
func ListHandler(c *gin.Context) {
|
func ListHandler(c *gin.Context) {
|
||||||
// Load players JSON
|
// Load players JSON
|
||||||
err := loadPlayersJSON()
|
err := loadPlayersJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to load players JSON: %v", err)
|
log.Fatalf("Failed to load players JSON: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get player list
|
// Get player list
|
||||||
err = getPlayerList()
|
err = getPlayerList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to get player list: %v", err)
|
log.Fatalf("Failed to get player list: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get server queue count
|
// Get server queue count
|
||||||
err = getServerQueue()
|
err = getServerQueue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to get server queue count: %v", err)
|
log.Fatalf("Failed to get server queue count: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse players JSON
|
// Parse players JSON
|
||||||
err = parsePlayers()
|
err = parsePlayers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to parse players JSON: %v", err)
|
log.Fatalf("Failed to parse players JSON: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("Content-Type", "application/json")
|
c.Header("Content-Type", "application/json")
|
||||||
c.Header("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ServerDetails)
|
c.JSON(http.StatusOK, ServerDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
port := ":" + os.Getenv("PORT")
|
port := ":" + os.Getenv("PORT")
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
/*
|
||||||
|
router.Use(secure.New(secure.Config{
|
||||||
|
SSLRedirect: true,
|
||||||
|
SSLHost: "np.pogge.rs",
|
||||||
|
STSSeconds: 315360000,
|
||||||
|
STSIncludeSubdomains: false,
|
||||||
|
FrameDeny: true,
|
||||||
|
ContentTypeNosniff: true,
|
||||||
|
BrowserXssFilter: true,
|
||||||
|
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
|
||||||
|
}))
|
||||||
|
*/
|
||||||
|
// Serve frontend static files
|
||||||
|
router.Use(static.Serve("/", static.LocalFile("./public", true)))
|
||||||
|
|
||||||
router.Use(secure.New(secure.Config{
|
// Setup route group for the API
|
||||||
SSLRedirect: true,
|
api := router.Group("/api")
|
||||||
SSLHost: "np.pogge.rs",
|
{
|
||||||
STSSeconds: 315360000,
|
api.GET("/", func(c *gin.Context) {
|
||||||
STSIncludeSubdomains: false,
|
c.JSON(http.StatusOK, gin.H{
|
||||||
FrameDeny: true,
|
"message": "Nothing to see here.",
|
||||||
ContentTypeNosniff: true,
|
})
|
||||||
BrowserXssFilter: true,
|
})
|
||||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
|
}
|
||||||
}))
|
|
||||||
|
|
||||||
// Serve frontend static files
|
// List handler for /api/list
|
||||||
router.Use(static.Serve("/", static.LocalFile("./public", true)))
|
api.GET("/list", ListHandler)
|
||||||
|
|
||||||
// Setup route group for the API
|
// Run the Gin router
|
||||||
api := router.Group("/api")
|
if err := router.Run(port); err != nil {
|
||||||
{
|
log.Fatalf("Gin fatal error: %v", err)
|
||||||
api.GET("/", func(c *gin.Context) {
|
} else {
|
||||||
c.JSON(http.StatusOK, gin.H {
|
log.Printf("Listening on %s...\n", port)
|
||||||
"message": "Nothing to see here.",
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// List handler for /api/list
|
|
||||||
api.GET("/list", ListHandler)
|
|
||||||
|
|
||||||
// Run the Gin router
|
|
||||||
if err := router.Run(port); err != nil {
|
|
||||||
log.Fatalf("Gin fatal error: %v", err)
|
|
||||||
} else {
|
|
||||||
log.Printf("Listening on %s...\n", port)
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user