├── VERSION ├── DESIGN.md ├── utils ├── reprepro-template │ └── conf │ │ ├── blacklist.pkg │ │ ├── options.orig │ │ ├── updates.orig │ │ ├── distributions.orig │ │ └── changelogs.sh ├── docker │ ├── pbuilder │ │ ├── README.md │ │ └── Dockerfile │ └── build │ │ └── Dockerfile ├── assets │ ├── irgsh-flow.png │ └── irgsh-distributed-architecture.png ├── scripts │ ├── deploy.sh │ ├── uninstall.sh │ └── init.sh ├── systemctl │ ├── irgsh-iso.service │ ├── irgsh-repo.service │ ├── irgsh-chief.service │ └── irgsh-builder.service ├── init │ ├── irgsh-iso │ ├── irgsh-repo │ ├── irgsh-chief │ └── irgsh-builder └── config.yml ├── .gitignore ├── .gitpod.yml ├── .gitpod.Dockerfile ├── internal ├── artifact │ ├── repo │ │ ├── artifact.go │ │ ├── file_impl.go │ │ ├── artifact_repo_moq.go │ │ └── file_impl_test.go │ ├── endpoint │ │ └── artifact.go │ └── service │ │ ├── artifact.go │ │ └── artifact_test.go └── config │ └── config.go ├── cmd ├── builder │ ├── init_test.go │ ├── main.go │ ├── builder_test.go │ ├── builder.go │ └── init.go ├── repo │ ├── repo_test.go │ ├── main.go │ └── repo.go ├── iso │ └── main.go ├── chief │ ├── main.go │ └── handler.go └── cli │ └── main.go ├── go.mod ├── pkg ├── httputil │ ├── response.go │ └── response_test.go └── systemutil │ └── util.go ├── .github └── workflows │ ├── pull-request.yml │ ├── master.yml │ └── generic.yml ├── install-cli.sh ├── COPYING ├── HACKING.md ├── Makefile ├── install.sh ├── GPG-EN.md ├── GPG-ID.md ├── README.md └── go.sum /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.28-beta 2 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # TO BE WRITTEN 2 | -------------------------------------------------------------------------------- /utils/reprepro-template/conf/blacklist.pkg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/docker/pbuilder/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | docker run -ti pbuilder pbuilder --build /to/path 3 | ``` 4 | -------------------------------------------------------------------------------- /utils/assets/irgsh-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankOn/irgsh-go/HEAD/utils/assets/irgsh-flow.png -------------------------------------------------------------------------------- /utils/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | tar -xvf release.tar.gz 3 | sed -i /wget/d install.sh 4 | ./install.sh 5 | -------------------------------------------------------------------------------- /utils/assets/irgsh-distributed-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankOn/irgsh-go/HEAD/utils/assets/irgsh-distributed-architecture.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | latest 3 | tmp 4 | target 5 | irgsh-go 6 | builder/utils.go 7 | repo/utils.go 8 | cli/utils.go 9 | coverage.txt 10 | tmp 11 | -------------------------------------------------------------------------------- /utils/docker/pbuilder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch 2 | 3 | RUN apt-get update 4 | RUN apt-get -y install pbuilder 5 | 6 | COPY base.tgz /var/cache/pbuilder/ 7 | -------------------------------------------------------------------------------- /utils/reprepro-template/conf/options.orig: -------------------------------------------------------------------------------- 1 | ask-passphrase 2 | basedir IRGSH_REPO_WORKDIR 3 | confdir IRGSH_REPO_WORKDIR/conf 4 | dbdir IRGSH_REPO_WORKDIR/db 5 | outdir IRGSH_REPO_WORKDIR/www 6 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | github: 2 | prebuilds: 3 | addComment: true 4 | tasks: 5 | - command: | 6 | export PATH="${PATH}:/usr/local/go/bin" 7 | image: 8 | file: .gitpod.Dockerfile 9 | ports: 10 | - port: 8080 11 | onOpen: open-preview 12 | - port: 8081 13 | - port: 8082 14 | -------------------------------------------------------------------------------- /utils/systemctl/irgsh-iso.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=irgsh-iso 3 | 4 | [Service] 5 | User=irgsh 6 | WorkingDirectory=/var/lib/irgsh 7 | ExecStart=/usr/bin/irgsh-iso 8 | ExecStop=/bin/kill -9 $MAINPID 9 | StandardOutput=file:/var/log/irgsh/iso.log 10 | StandardError=file:/var/log/irgsh/iso.log 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /utils/systemctl/irgsh-repo.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=irgsh-repo 3 | 4 | [Service] 5 | User=irgsh 6 | WorkingDirectory=/var/lib/irgsh 7 | ExecStart=/usr/bin/irgsh-repo 8 | ExecStop=/bin/kill -9 $MAINPID 9 | StandardOutput=file:/var/log/irgsh/repo.log 10 | StandardError=file:/var/log/irgsh/repo.log 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /utils/systemctl/irgsh-chief.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=irgsh-chief 3 | 4 | [Service] 5 | User=irgsh 6 | WorkingDirectory=/var/lib/irgsh 7 | ExecStart=/usr/bin/irgsh-chief 8 | ExecStop=/bin/kill -9 $MAINPID 9 | StandardOutput=file:/var/log/irgsh/chief.log 10 | StandardError=file:/var/log/irgsh/chief.log 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /utils/systemctl/irgsh-builder.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=irgsh-builder 3 | 4 | [Service] 5 | User=irgsh 6 | WorkingDirectory=/var/lib/irgsh 7 | ExecStart=/usr/bin/irgsh-builder 8 | ExecStop=/bin/kill -9 $MAINPID 9 | StandardOutput=file:/var/log/irgsh/builder.log 10 | StandardError=file:/var/log/irgsh/builder.log 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | RUN apt update && apt install -y gpg pbuilder debootstrap devscripts python3-apt reprepro make && \ 3 | curl -O https://storage.googleapis.com/golang/go1.13.14.linux-amd64.tar.gz && \ 4 | tar -xf go1.13.14.linux-amd64.tar.gz && \ 5 | mv go /usr/local && \ 6 | rm -rf go1.13.14.linux-amd64.tar.gz 7 | ENV PATH="${PATH}:/usr/local/go/bin" 8 | -------------------------------------------------------------------------------- /utils/docker/build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.1 2 | RUN mkdir -p /tmp/src 3 | COPY . /tmp/src 4 | WORKDIR /tmp/src 5 | RUN (cd chief && go install) 6 | RUN (cp chief/utils.go builder/utils.go && cd builder && go install) 7 | RUN (cp chief/utils.go iso/utils.go && cd iso && go install) 8 | RUN (cp chief/utils.go repo/utils.go && cd repo && go install) 9 | RUN (cp chief/utils.go cli/utils.go && cd cli && go install) 10 | -------------------------------------------------------------------------------- /utils/reprepro-template/conf/updates.orig: -------------------------------------------------------------------------------- 1 | Name: UPSTREAM_NAME 2 | Suite: UPSTREAM_DIST_CODENAME 3 | VerifyRelease: blindtrust 4 | Method: UPSTREAM_DIST_URL 5 | Architectures: DIST_SUPPORTED_ARCHITECTURES 6 | # This will redirect non-free to restricted and contrib to extras. 7 | Components: UPSTREAM_DIST_COMPONENTS 8 | # Enable this blacklist when we want to put our own packages into the repository 9 | FilterList: install blacklist.pkg 10 | -------------------------------------------------------------------------------- /internal/artifact/repo/artifact.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | //go:generate moq -out artifact_repo_moq.go . Repo 4 | 5 | // ArtifactModel represent artifact data 6 | type ArtifactModel struct { 7 | Name string 8 | } 9 | 10 | // ArtifactList list of artifacts 11 | type ArtifactList struct { 12 | TotalData int 13 | Artifacts []ArtifactModel 14 | } 15 | 16 | // Repo interface to operate with artifact 17 | type Repo interface { 18 | GetArtifactList(pageNum int64, rows int64) (ArtifactList, error) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/builder/init_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os/exec" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestBaseInitBase(t *testing.T) { 13 | t.Skip() // This still need sudo 14 | err := InitBase() 15 | if err != nil { 16 | log.Println(err.Error()) 17 | assert.Equal(t, true, false, "Should not reach here") 18 | } 19 | 20 | cmdStr := "du -s /var/cache/pbuilder/base.tgz | " 21 | cmdStr += "cut -d '/' -f1 | head -n 1 | sed 's/ //g' | " 22 | cmdStr += "tr -d '\n' | tr -d '\t' " 23 | cmd := exec.Command("bash", "-c", cmdStr) 24 | out, _ := cmd.CombinedOutput() 25 | cmd.Run() 26 | size, err := strconv.Atoi(string(out)) 27 | if err != nil { 28 | log.Println(err.Error()) 29 | assert.Equal(t, true, false, "Should not reach here") 30 | } 31 | assert.NotEqual(t, size, int(0)) 32 | } 33 | -------------------------------------------------------------------------------- /internal/artifact/endpoint/artifact.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "net/http" 5 | 6 | service "github.com/blankon/irgsh-go/internal/artifact/service" 7 | httputil "github.com/blankon/irgsh-go/pkg/httputil" 8 | ) 9 | 10 | // ArtifactHTTPEndpoint http endpoint for artifact 11 | type ArtifactHTTPEndpoint struct { 12 | service *service.ArtifactService 13 | } 14 | 15 | // NewArtifactHTTPEndpoint returns new artifact instance 16 | func NewArtifactHTTPEndpoint(service *service.ArtifactService) *ArtifactHTTPEndpoint { 17 | return &ArtifactHTTPEndpoint{ 18 | service: service, 19 | } 20 | } 21 | 22 | // GetArtifactListHandler get artifact 23 | func (A *ArtifactHTTPEndpoint) GetArtifactListHandler(w http.ResponseWriter, r *http.Request) { 24 | artifactList, err := A.service.GetArtifactList(1, 1) 25 | if err != nil { 26 | httputil.ResponseError("Can't get artifact", 500, w) 27 | } 28 | 29 | httputil.ResponseJSON(artifactList, 200, w) 30 | } 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/blankon/irgsh-go 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.1 // indirect 5 | github.com/RichardKnop/machinery v1.5.5 6 | github.com/ghodss/yaml v1.0.0 7 | github.com/go-playground/locales v0.12.1 // indirect 8 | github.com/go-playground/universal-translator v0.16.0 // indirect 9 | github.com/google/uuid v1.1.0 10 | github.com/hpcloud/tail v1.0.0 11 | github.com/imroc/req v0.2.3 12 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf 13 | github.com/jinzhu/configor v1.0.0 14 | github.com/julienschmidt/httprouter v1.3.0 15 | github.com/leodido/go-urn v1.1.0 // indirect 16 | github.com/manifoldco/promptui v0.3.2 17 | github.com/stretchr/testify v1.2.2 18 | github.com/urfave/cli v1.20.0 19 | gopkg.in/fsnotify.v1 v1.4.7 // indirect 20 | gopkg.in/go-playground/validator.v9 v9.27.0 21 | gopkg.in/src-d/go-git.v4 v4.8.1 22 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 23 | gopkg.in/yaml.v2 v2.2.1 24 | mvdan.cc/sh v2.6.4+incompatible // indirect 25 | ) 26 | 27 | go 1.13 28 | -------------------------------------------------------------------------------- /pkg/httputil/response.go: -------------------------------------------------------------------------------- 1 | package httputil 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | // StandardError response 10 | type StandardError struct { 11 | Message string `json:"message"` 12 | } 13 | 14 | //ResponseJSON response http request with application/json 15 | func ResponseJSON(data interface{}, status int, writer http.ResponseWriter) (err error) { 16 | writer.Header().Set("Content-type", "application/json") 17 | writer.WriteHeader(status) 18 | 19 | d, err := json.Marshal(data) 20 | if err != nil { 21 | writer.WriteHeader(http.StatusInternalServerError) 22 | d, _ = json.Marshal(StandardError{Message: "ResponseJSON: Failed to response " + err.Error()}) 23 | err = fmt.Errorf("ResponseJSON: Failed to response : %s", err) 24 | } 25 | 26 | writer.Write(d) 27 | return 28 | } 29 | 30 | // ResponseError response http request with standard error 31 | func ResponseError(message string, status int, writer http.ResponseWriter) (err error) { 32 | return ResponseJSON(StandardError{Message: message}, status, writer) 33 | } 34 | -------------------------------------------------------------------------------- /utils/scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm -f irgsh-go/ 3 | rm -f irgsh-go/etc/ 4 | rm -f irgsh-go/etc/irgsh/ 5 | rm -f irgsh-go/etc/irgsh/config.yml 6 | rm -f irgsh-go/etc/init.d/ 7 | rm -f irgsh-go/etc/init.d/irgsh-builder 8 | rm -f irgsh-go/etc/init.d/irgsh-chief 9 | rm -f irgsh-go/etc/init.d/irgsh-repo 10 | rm -f irgsh-go/usr/ 11 | rm -f irgsh-go/usr/share/ 12 | rm -f irgsh-go/usr/share/irgsh/ 13 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/ 14 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/ 15 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/blacklist.pkg 16 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/tiffany.py 17 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/changelogs.sh 18 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/distributions.orig 19 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/options.orig 20 | rm -f irgsh-go/usr/share/irgsh/reprepro-template/conf/updates.orig 21 | rm -f irgsh-go/usr/bin/ 22 | rm -f irgsh-go/usr/bin/irgsh-cli 23 | rm -f irgsh-go/usr/bin/irgsh-builder 24 | rm -f irgsh-go/usr/bin/irgsh-chief 25 | rm -f irgsh-go/usr/bin/irgsh-repo 26 | -------------------------------------------------------------------------------- /internal/artifact/repo/file_impl.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | ) 7 | 8 | // FileRepo interface with file system based artifact information 9 | type FileRepo struct { 10 | Workdir string 11 | } 12 | 13 | // NewFileRepo create new instance 14 | func NewFileRepo(Workdir string) *FileRepo { 15 | return &FileRepo{ 16 | Workdir: Workdir, 17 | } 18 | } 19 | 20 | // GetArtifactList ... 21 | // paging is not implemented yet 22 | func (A *FileRepo) GetArtifactList(pageNum int64, rows int64) (artifactsList ArtifactList, err error) { 23 | files, err := filepath.Glob(A.Workdir + "/artifacts/*") 24 | if err != nil { 25 | return 26 | } 27 | 28 | artifactsList.Artifacts = []ArtifactModel{} 29 | 30 | for _, file := range files { 31 | artifactsList.Artifacts = append(artifactsList.Artifacts, ArtifactModel{Name: getArtifactFilename(file)}) 32 | } 33 | artifactsList.TotalData = len(artifactsList.Artifacts) 34 | 35 | return 36 | } 37 | 38 | func getArtifactFilename(filePath string) (fileName string) { 39 | path := strings.Split(filePath, "artifacts/") 40 | if len(path) > 1 { 41 | fileName = path[1] 42 | } 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /cmd/repo/repo_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/exec" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/blankon/irgsh-go/internal/config" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | log.SetFlags(log.LstdFlags | log.Lshortfile) 16 | 17 | irgshConfig, _ = config.LoadConfig() 18 | dir, _ := os.Getwd() 19 | irgshConfig.Builder.Workdir = dir + "/../tmp" 20 | 21 | m.Run() 22 | } 23 | 24 | func TestBaseInitRepo(t *testing.T) { 25 | err := InitRepo() 26 | if err != nil { 27 | log.Println(err.Error()) 28 | assert.Equal(t, true, false, "Should not reach here") 29 | } 30 | 31 | cmdStr := "du -s " + irgshConfig.Repo.Workdir + "/verbeek | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 32 | cmd := exec.Command("bash", "-c", cmdStr) 33 | out, _ := cmd.CombinedOutput() 34 | cmd.Run() 35 | size, err := strconv.Atoi(string(out)) 36 | if err != nil { 37 | log.Println(err.Error()) 38 | assert.Equal(t, true, false, "Should not reach here") 39 | } 40 | assert.NotEqual(t, size, int(0)) 41 | } 42 | 43 | func TestBaseInitRepoConfigCheck(t *testing.T) { 44 | t.Skip() 45 | } 46 | -------------------------------------------------------------------------------- /pkg/httputil/response_test.go: -------------------------------------------------------------------------------- 1 | package httputil 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestResponseJSON(t *testing.T) { 13 | // null status ok 14 | handler := func(w http.ResponseWriter, r *http.Request) { 15 | ResponseJSON(nil, http.StatusOK, w) 16 | } 17 | req := httptest.NewRequest("GET", "http://example.com/foo", nil) 18 | w := httptest.NewRecorder() 19 | handler(w, req) 20 | resp := w.Result() 21 | body, _ := ioutil.ReadAll(resp.Body) 22 | 23 | assert.Equal(t, body, []byte("null")) 24 | assert.Equal(t, w.Header(), http.Header(http.Header{"Content-Type": []string{"application/json"}})) 25 | assert.Equal(t, w.Code, 200) 26 | 27 | // interface status 500 28 | handler = func(w http.ResponseWriter, r *http.Request) { 29 | ResponseError("Not OK", http.StatusInternalServerError, w) 30 | } 31 | w = httptest.NewRecorder() 32 | handler(w, req) 33 | resp = w.Result() 34 | body, _ = ioutil.ReadAll(resp.Body) 35 | 36 | assert.Equal(t, body, []byte(`{"message":"Not OK"}`)) 37 | assert.Equal(t, w.Header(), http.Header(http.Header{"Content-Type": []string{"application/json"}})) 38 | assert.Equal(t, w.Code, 500) 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull request github actions 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | build-devel: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Get current date 13 | run: echo "irgsh_build_date=$(TZ='Asia/Jakarta' date +'%Y%m%d%H%M')" >> $GITHUB_ENV 14 | - uses: actions/checkout@v4 15 | - name: Install needed apt packages 16 | uses: awalsh128/cache-apt-pkgs-action@v1.6.0 17 | with: 18 | packages: gpg pbuilder debootstrap devscripts python3-apt reprepro make 19 | version: 1.0 20 | - uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.13.14' 23 | - uses: actions/cache@v4 24 | with: 25 | path: | 26 | ~/.cache/go-build 27 | ~/go/pkg/mod 28 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 29 | restore-keys: | 30 | ${{ runner.os }}-go- 31 | - name: Build development release 32 | run: | 33 | echo ${{ env.irgsh_build_date }}-development-build > VERSION 34 | make release 35 | mv target/{release,pre-release}.tar.gz 36 | - uses: actions/upload-artifact@v4 37 | with: 38 | name: pre-release.tar.gz 39 | path: target/ 40 | -------------------------------------------------------------------------------- /internal/artifact/service/artifact.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | artifactRepo "github.com/blankon/irgsh-go/internal/artifact/repo" 5 | ) 6 | 7 | // ArtifactItem representation of artifact data 8 | type ArtifactItem struct { 9 | Name string `json:"name"` 10 | } 11 | 12 | // ArtifactList list of artifact 13 | type ArtifactList struct { 14 | TotalData int 15 | Artifacts []ArtifactItem `json:"artifacts"` 16 | } 17 | 18 | // Service interface for artifact service 19 | type Service interface { 20 | GetArtifactList(pageNum int64, rows int64) (ArtifactList, error) 21 | } 22 | 23 | // ArtifactService implement service 24 | type ArtifactService struct { 25 | repo artifactRepo.Repo 26 | } 27 | 28 | // NewArtifactService return artifact service instance 29 | func NewArtifactService(repo artifactRepo.Repo) *ArtifactService { 30 | return &ArtifactService{ 31 | repo: repo, 32 | } 33 | } 34 | 35 | // GetArtifactList get list of artifact 36 | // paging is not yet functional 37 | func (A *ArtifactService) GetArtifactList(pageNum int64, rows int64) (list ArtifactList, err error) { 38 | alist, err := A.repo.GetArtifactList(pageNum, rows) 39 | if err != nil { 40 | return 41 | } 42 | 43 | list.TotalData = alist.TotalData 44 | list.Artifacts = []ArtifactItem{} 45 | 46 | for _, a := range alist.Artifacts { 47 | list.Artifacts = append(list.Artifacts, ArtifactItem{Name: a.Name}) 48 | } 49 | 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /install-cli.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIST=$(cat /etc/*release | grep "^ID=" | cut -d '=' -f 2) 4 | 5 | # Require sudo or root privilege 6 | if [ $EUID != 0 ]; then 7 | sudo "$0" "$@" 8 | exit $? 9 | fi 10 | 11 | TEMP_PATH=/tmp 12 | DEV_INSTALL=0 13 | 14 | apt update 15 | apt install -y gnupg build-essential devscripts debhelper jq 16 | 17 | if [ -f ./target/release.tar.gz ]; then 18 | # For development/testing purpose 19 | TEMP_PATH=$(pwd)/target 20 | DEV_INSTALL=1 21 | 22 | pushd $TEMP_PATH 23 | 24 | echo "Extracting ... " 25 | rm -rf irgsh-go && tar -xf release.tar.gz 26 | echo "Extracting [OK]" 27 | echo 28 | 29 | echo "Installing files ... " 30 | cp -v $TEMP_PATH/irgsh-go/usr/bin/irgsh-cli /usr/bin/irgsh-cli 31 | else 32 | # Download and extract 33 | pushd $TEMP_PATH 34 | 35 | DOWNLOAD_URL=$(curl -ksL "https://api.github.com/repos/BlankOn/irgsh-go/releases/latest" | jq -r '.assets | .[] | select(.name == "irgsh-cli")| .browser_download_url') 36 | echo "Downloading ... " 37 | echo "$DOWNLOAD_URL" 38 | rm -f $TEMP_PATH/irgsh-cli && curl -L -f -o ./irgsh-cli $DOWNLOAD_URL 39 | if test $? -gt 0; then 40 | echo "Downloding [FAILED]" 41 | exit 1 42 | fi 43 | echo "Downloding [OK]" 44 | echo 45 | 46 | echo "Installing file ... " 47 | cp -v $TEMP_PATH/irgsh-cli /usr/bin/irgsh-cli 48 | ln -sf /usr/bin/irgsh-cli /usr/bin/irgsh 49 | chmod +x /usr/bin/irgsh-cli 50 | fi 51 | 52 | popd >/dev/null 53 | 54 | echo "Happy hacking!" 55 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2019, BlankOn Project 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of Redis nor the names of its contributors may be used 16 | to endorse or promote products derived from this software without specific 17 | prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /pkg/systemutil/util.go: -------------------------------------------------------------------------------- 1 | package systemutil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | 11 | "github.com/hpcloud/tail" 12 | ) 13 | 14 | // CmdExec run os command 15 | func CmdExec(cmdStr string, cmdDesc string, logPath string) (out string, err error) { 16 | if len(cmdStr) == 0 { 17 | return "", errors.New("No command string provided.") 18 | } 19 | 20 | if len(logPath) > 0 { 21 | 22 | logPathArr := strings.Split(logPath, "/") 23 | logPathArr = logPathArr[:len(logPathArr)-1] 24 | logDir := "/" + strings.Join(logPathArr, "/") 25 | os.MkdirAll(logDir, os.ModePerm) 26 | f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 27 | if err != nil { 28 | return "", err 29 | } 30 | defer f.Close() 31 | _, _ = f.WriteString("\n") 32 | if len(cmdDesc) > 0 { 33 | cmdDescSplitted := strings.Split(cmdDesc, "\n") 34 | for _, desc := range cmdDescSplitted { 35 | _, _ = f.WriteString("##### " + desc + "\n") 36 | } 37 | } 38 | _, _ = f.WriteString("##### RUN " + cmdStr + "\n") 39 | f.Close() 40 | cmdStr += " 2>&1 | tee -a " + logPath 41 | } 42 | // `set -o pipefail` will forces to return the original exit code 43 | output, err := exec.Command("bash", "-c", "set -o pipefail && "+cmdStr).Output() 44 | out = string(output) 45 | 46 | return 47 | } 48 | 49 | // StreamLog tailing a file 50 | func StreamLog(path string) { 51 | t, err := tail.TailFile(path, tail.Config{Follow: true}) 52 | if err != nil { 53 | log.Printf("error: %v\n", err) 54 | } 55 | for line := range t.Lines { 56 | fmt.Println(line.Text) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /utils/init/irgsh-iso: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: irgsh-iso 5 | # Required-Start: $local_fs $network $syslog 6 | # Required-Stop: $local_fs $network $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: irgsh-iso 10 | # Description: irgsh-iso 11 | ### END INIT INFO 12 | 13 | NAME="irgsh-iso" 14 | PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" 15 | APPDIR="/" 16 | APPBIN="/usr/bin/irgsh-iso" 17 | USER="irgsh" 18 | GROUP="irgsh" 19 | LOGFILE="/var/log/irgsh/iso.log" 20 | 21 | # Include functions 22 | set -e 23 | . /lib/lsb/init-functions 24 | 25 | start() { 26 | printf "Starting '$NAME'... " 27 | start-stop-daemon --start --chuid "$USER:$GROUP" --background -C --make-pidfile --pidfile /var/run/$NAME.pid --chdir $APPDIR --exec $APPBIN > $LOGFILE 2>&1 28 | printf "done\n" 29 | } 30 | 31 | #We need this function to ensure the whole process tree will be killed 32 | killtree() { 33 | local _pid=$1 34 | local _sig=${2-TERM} 35 | for _child in $(ps -o pid --no-headers --ppid ${_pid}); do 36 | killtree ${_child} ${_sig} 37 | done 38 | kill -${_sig} ${_pid} 39 | } 40 | 41 | stop() { 42 | printf "Stopping '$NAME'... " 43 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || \ 44 | while test -d /proc/$(cat /var/run/$NAME.pid); do 45 | killtree $(cat /var/run/$NAME.pid) 15 46 | sleep 0.5 47 | done 48 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || rm /var/run/$NAME.pid 49 | printf "done\n" 50 | } 51 | 52 | status() { 53 | status_of_proc -p /var/run/$NAME.pid "" $NAME && exit 0 || exit $? 54 | } 55 | 56 | case "$1" in 57 | start) 58 | start 59 | ;; 60 | stop) 61 | stop 62 | ;; 63 | restart) 64 | stop 65 | start 66 | ;; 67 | status) 68 | status 69 | ;; 70 | *) 71 | echo "Usage: $NAME {start|stop|restart|status}" >&2 72 | exit 1 73 | ;; 74 | esac 75 | 76 | exit 0 77 | -------------------------------------------------------------------------------- /utils/init/irgsh-repo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: irgsh-repo 5 | # Required-Start: $local_fs $network $syslog 6 | # Required-Stop: $local_fs $network $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: irgsh-repo 10 | # Description: irgsh-repo 11 | ### END INIT INFO 12 | 13 | NAME="irgsh-repo" 14 | PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" 15 | APPDIR="/" 16 | APPBIN="/usr/bin/irgsh-repo" 17 | USER="irgsh" 18 | GROUP="irgsh" 19 | LOGFILE="/var/log/irgsh/repo.log" 20 | 21 | # Include functions 22 | set -e 23 | . /lib/lsb/init-functions 24 | 25 | start() { 26 | printf "Starting '$NAME'... " 27 | start-stop-daemon --start --chuid "$USER:$GROUP" --background -C --make-pidfile --pidfile /var/run/$NAME.pid --chdir $APPDIR --exec $APPBIN > $LOGFILE 2>&1 28 | printf "done\n" 29 | } 30 | 31 | #We need this function to ensure the whole process tree will be killed 32 | killtree() { 33 | local _pid=$1 34 | local _sig=${2-TERM} 35 | for _child in $(ps -o pid --no-headers --ppid ${_pid}); do 36 | killtree ${_child} ${_sig} 37 | done 38 | kill -${_sig} ${_pid} 39 | } 40 | 41 | stop() { 42 | printf "Stopping '$NAME'... " 43 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || \ 44 | while test -d /proc/$(cat /var/run/$NAME.pid); do 45 | killtree $(cat /var/run/$NAME.pid) 15 46 | sleep 0.5 47 | done 48 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || rm /var/run/$NAME.pid 49 | printf "done\n" 50 | } 51 | 52 | status() { 53 | status_of_proc -p /var/run/$NAME.pid "" $NAME && exit 0 || exit $? 54 | } 55 | 56 | case "$1" in 57 | start) 58 | start 59 | ;; 60 | stop) 61 | stop 62 | ;; 63 | restart) 64 | stop 65 | start 66 | ;; 67 | status) 68 | status 69 | ;; 70 | *) 71 | echo "Usage: $NAME {start|stop|restart|status}" >&2 72 | exit 1 73 | ;; 74 | esac 75 | 76 | exit 0 77 | -------------------------------------------------------------------------------- /utils/init/irgsh-chief: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: irgsh-chief 5 | # Required-Start: $local_fs $network $syslog 6 | # Required-Stop: $local_fs $network $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: irgsh-chief 10 | # Description: irgsh-chief 11 | ### END INIT INFO 12 | 13 | NAME="irgsh-chief" 14 | PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" 15 | APPDIR="/" 16 | APPBIN="/usr/bin/irgsh-chief" 17 | USER="irgsh" 18 | GROUP="irgsh" 19 | LOGFILE="/var/log/irgsh/chief.log" 20 | 21 | # Include functions 22 | set -e 23 | . /lib/lsb/init-functions 24 | 25 | start() { 26 | printf "Starting '$NAME'... " 27 | start-stop-daemon --start --chuid "$USER:$GROUP" --background -C --make-pidfile --pidfile /var/run/$NAME.pid --chdir $APPDIR --exec $APPBIN > $LOGFILE 2>&1 28 | printf "done\n" 29 | } 30 | 31 | #We need this function to ensure the whole process tree will be killed 32 | killtree() { 33 | local _pid=$1 34 | local _sig=${2-TERM} 35 | for _child in $(ps -o pid --no-headers --ppid ${_pid}); do 36 | killtree ${_child} ${_sig} 37 | done 38 | kill -${_sig} ${_pid} 39 | } 40 | 41 | stop() { 42 | printf "Stopping '$NAME'... " 43 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || \ 44 | while test -d /proc/$(cat /var/run/$NAME.pid); do 45 | killtree $(cat /var/run/$NAME.pid) 15 46 | sleep 0.5 47 | done 48 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || rm /var/run/$NAME.pid 49 | printf "done\n" 50 | } 51 | 52 | status() { 53 | status_of_proc -p /var/run/$NAME.pid "" $NAME && exit 0 || exit $? 54 | } 55 | 56 | case "$1" in 57 | start) 58 | start 59 | ;; 60 | stop) 61 | stop 62 | ;; 63 | restart) 64 | stop 65 | start 66 | ;; 67 | status) 68 | status 69 | ;; 70 | *) 71 | echo "Usage: $NAME {start|stop|restart|status}" >&2 72 | exit 1 73 | ;; 74 | esac 75 | 76 | exit 0 77 | -------------------------------------------------------------------------------- /utils/init/irgsh-builder: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: irgsh-builder 5 | # Required-Start: $local_fs $network $syslog 6 | # Required-Stop: $local_fs $network $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: irgsh-builder 10 | # Description: irgsh-builder 11 | ### END INIT INFO 12 | 13 | NAME="irgsh-builder" 14 | PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" 15 | APPDIR="/" 16 | APPBIN="/usr/bin/irgsh-builder" 17 | USER="irgsh" 18 | GROUP="irgsh" 19 | LOGFILE="/var/log/irgsh/builder.log" 20 | 21 | # Include functions 22 | set -e 23 | . /lib/lsb/init-functions 24 | 25 | start() { 26 | printf "Starting '$NAME'... " 27 | start-stop-daemon --start --chuid "$USER:$GROUP" --background -C --make-pidfile --pidfile /var/run/$NAME.pid --chdir $APPDIR --exec $APPBIN > $LOGFILE 2>&1 28 | printf "done\n" 29 | } 30 | 31 | #We need this function to ensure the whole process tree will be killed 32 | killtree() { 33 | local _pid=$1 34 | local _sig=${2-TERM} 35 | for _child in $(ps -o pid --no-headers --ppid ${_pid}); do 36 | killtree ${_child} ${_sig} 37 | done 38 | kill -${_sig} ${_pid} 39 | } 40 | 41 | stop() { 42 | printf "Stopping '$NAME'... " 43 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || \ 44 | while test -d /proc/$(cat /var/run/$NAME.pid); do 45 | killtree $(cat /var/run/$NAME.pid) 15 46 | sleep 0.5 47 | done 48 | [ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || rm /var/run/$NAME.pid 49 | printf "done\n" 50 | } 51 | 52 | status() { 53 | status_of_proc -p /var/run/$NAME.pid "" $NAME && exit 0 || exit $? 54 | } 55 | 56 | case "$1" in 57 | start) 58 | start 59 | ;; 60 | stop) 61 | stop 62 | ;; 63 | restart) 64 | stop 65 | start 66 | ;; 67 | status) 68 | status 69 | ;; 70 | *) 71 | echo "Usage: $NAME {start|stop|restart|status}" >&2 72 | exit 1 73 | ;; 74 | esac 75 | 76 | exit 0 77 | -------------------------------------------------------------------------------- /cmd/iso/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "os/exec" 9 | 10 | machinery "github.com/RichardKnop/machinery/v1" 11 | machineryConfig "github.com/RichardKnop/machinery/v1/config" 12 | "github.com/blankon/irgsh-go/internal/config" 13 | "github.com/urfave/cli" 14 | ) 15 | 16 | var ( 17 | app *cli.App 18 | configPath string 19 | server *machinery.Server 20 | version string 21 | 22 | irgshConfig = config.IrgshConfig{} 23 | ) 24 | 25 | func main() { 26 | log.SetFlags(log.LstdFlags | log.Lshortfile) 27 | 28 | irgshConfig, err := config.LoadConfig() 29 | if err != nil { 30 | log.Fatalln("couldn't load config : ", err) 31 | } 32 | 33 | _ = exec.Command("bash", "-c", "mkdir -p "+irgshConfig.ISO.Workdir) 34 | 35 | app = cli.NewApp() 36 | app.Name = "irgsh-go" 37 | app.Usage = "irgsh-go distributed packager" 38 | app.Author = "BlankOn Developer" 39 | app.Email = "blankon-dev@googlegroups.com" 40 | app.Version = version 41 | 42 | app.Commands = []cli.Command{ 43 | { 44 | Name: "init", 45 | Aliases: []string{"i"}, 46 | Usage: "initialize iso", 47 | Action: func(c *cli.Context) error { 48 | // Do nothing 49 | return nil 50 | }, 51 | }, 52 | } 53 | 54 | app.Action = func(c *cli.Context) error { 55 | 56 | go serve() 57 | 58 | server, err = machinery.NewServer( 59 | &machineryConfig.Config{ 60 | Broker: irgshConfig.Redis, 61 | ResultBackend: irgshConfig.Redis, 62 | DefaultQueue: "irgsh", 63 | }, 64 | ) 65 | if err != nil { 66 | fmt.Println("Could not create server : " + err.Error()) 67 | } 68 | 69 | server.RegisterTask("iso", BuildISO) 70 | 71 | worker := server.NewWorker("iso", 2) 72 | err = worker.Launch() 73 | if err != nil { 74 | fmt.Println("Could not launch worker : " + err.Error()) 75 | } 76 | 77 | return nil 78 | 79 | } 80 | app.Run(os.Args) 81 | } 82 | 83 | func serve() { 84 | fs := http.FileServer(http.Dir(irgshConfig.ISO.Workdir)) 85 | http.Handle("/", fs) 86 | log.Println("irgsh-go iso now live on port 8083, serving path : " + irgshConfig.ISO.Workdir) 87 | log.Fatal(http.ListenAndServe(":8083", nil)) 88 | } 89 | 90 | func BuildISO(payload string) (next string, err error) { 91 | fmt.Println("Done") 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /utils/reprepro-template/conf/distributions.orig: -------------------------------------------------------------------------------- 1 | # WARNING 2 | # Avoid giving - string into Update field as it could removing blankon specific packages 3 | 4 | Origin: DIST_NAME 5 | Label: DIST_LABEL 6 | Codename: DIST_CODENAME 7 | Suite: DIST_CODENAME 8 | Components: DIST_COMPONENTS 9 | UDebComponents: main 10 | Architectures: DIST_SUPPORTED_ARCHITECTURES 11 | Version: DIST_VERSION 12 | Description: DIST_VERSION_DESC 13 | Update: UPSTREAM_NAME 14 | SignWith: DIST_SIGNING_KEY 15 | DebIndices: Packages Release . .gz .bz2 /usr/bin/rredtool 16 | UDebIndices: Packages . .gz .bz2 17 | DscIndices: Sources Release . .gz .bz2 /usr/bin/rredtool 18 | #Pull: pull.verbeek 19 | Contents: udebs nodebs . .gz 20 | ContentsArchitectures: DIST_SUPPORTED_ARCHITECTURES 21 | ContentsComponents: DIST_COMPONENTS 22 | ContentsUComponents: main 23 | Log: DIST_CODENAME.log 24 | --type=dsc changelogs.sh 25 | 26 | Origin: DIST_NAME 27 | Label: DIST_LABEL 28 | Codename: DIST_CODENAME-security 29 | Suite: DIST_CODENAME-security 30 | Components: DIST_COMPONENTS 31 | UDebComponents: main 32 | Architectures: DIST_SUPPORTED_ARCHITECTURES 33 | Version: DIST_VERSION 34 | Description: DIST_VERSION_DESC Security 35 | Update: 36 | SignWith: DIST_SIGNING_KEY 37 | DebIndices: Packages Release . .gz .bz2 /usr/bin/rredtool 38 | UDebIndices: Packages . .gz .bz2 39 | DscIndices: Sources Release . .gz .bz2 /usr/bin/rredtool 40 | Contents: udebs nodebs . .gz 41 | ContentsArchitectures: DIST_SUPPORTED_ARCHITECTURES 42 | ContentsComponents: DIST_COMPONENTS 43 | ContentsUComponents: main 44 | Log: DIST_CODENAME-security.log 45 | --type=dsc changelogs.sh 46 | 47 | Origin: DIST_NAME 48 | Label: DIST_LABEL 49 | Codename: DIST_CODENAME-updates 50 | Suite: DIST_CODENAME-updates 51 | Components: DIST_COMPONENTS 52 | UDebComponents: main 53 | Architectures: DIST_SUPPORTED_ARCHITECTURES 54 | Version: DIST_VERSION 55 | Description: DIST_VERSION_DESC Updates 56 | Update: 57 | SignWith: DIST_SIGNING_KEY 58 | DebIndices: Packages Release . .gz .bz2 /usr/bin/rredtool 59 | UDebIndices: Packages . .gz .bz2 60 | DscIndices: Sources Release . .gz .bz2 /usr/bin/rredtool 61 | Contents: udebs nodebs . .gz 62 | ContentsArchitectures: DIST_SUPPORTED_ARCHITECTURES 63 | ContentsComponents: DIST_COMPONENTS 64 | ContentsUComponents: main 65 | Log: DIST_CODENAME-updates.log 66 | --type=dsc changelogs.sh 67 | 68 | -------------------------------------------------------------------------------- /utils/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | redis: 'redis://localhost:6379' 3 | 4 | chief: 5 | address: 'http://localhost:8080' 6 | workdir: '/var/lib/irgsh/chief' 7 | gnupg_dir: '/var/lib/irgsh/gnupg' 8 | 9 | builder: 10 | workdir: '/var/lib/irgsh/builder' 11 | upstream_dist_url: 'http://kartolo.sby.datautama.net.id/debian' 12 | 13 | repo: 14 | workdir: '/var/lib/irgsh/repo' 15 | dist_name: 'BlankOn' 16 | dist_label: 'BlankOn' 17 | dist_codename: 'verbeek' 18 | dist_components: 'main restricted extras extras-restricted' 19 | dist_supported_architectures: 'amd64 source' 20 | dist_version: '12.0' 21 | dist_version_desc: 'BlankOn Linux 12.0 Verbeek' 22 | dist_signing_key: 'GPG_SIGN_KEY' 23 | upstream_name: 'merge.sid' 24 | upstream_dist_codename: 'sid' 25 | upstream_dist_url: 'http://kartolo.sby.datautama.net.id/debian' 26 | upstream_dist_components: 'main non-free>restricted contrib>extras' 27 | gnupg_dir: '/var/lib/irgsh/gnupg' 28 | 29 | iso: 30 | workdir: '/var/lib/irgsh/iso' 31 | 32 | variant: desktop 33 | archs: 'amd64' 34 | keyring: '/usr/share/keyrings/blankon-archive-keyring.gpg' 35 | gnupg_dir: '/root/.gnupg' 36 | dist: 'verbeek' 37 | mirror: 'http://arsip-dev.blankonlinux.or.id/blankon/' 38 | script: './uluwatu.debootstrap' 39 | components: 'main restricted extras extras-restricted' 40 | local_repo_source_path: '`pwd`/$DIST-local-repo`' 41 | local_repo_keyring_path: '`pwd`/$DIST-local-repo/.gnupg-archive/' 42 | local_repo_keyring: 9120A048 43 | packages: 'blankon-role-keyring blankon-keyring blankon-desktop plymouth-theme-blankon blankon-kernel-headers blankon-keyring aufs-tools gnome-control-center manokwari manokwari-theme-greeter openssh-client openssh-server tebu-flat-icon-theme gnome-terminal plymouth-theme-blankon sudo lsb-release midori libglib2.0-bin curl locales-all vim tempel' 44 | blacklist: 'gdm3 gnome-session lightdm-gtk-greeter' 45 | kernel: linux-image-generic 46 | kernel_amd64: linux-image-4.9.0-3-amd64 47 | squashfs: squashfs-tools 48 | cdvolume: blankon 49 | live_packages: '' 50 | live_system: live 51 | live_packages: 'live-boot live-config blankon-live-config blankon-repository-setup blankon-installer gparted' 52 | max_size: 1000 53 | templates_path: '/var/lib/irgsh/src/pabrik-cc/templates-dvd/' 54 | recipient: blankon-gev@googlegroups.com 55 | publish_path: '/home/cdimage/images/livedvd-harian/' 56 | publish_url: 'http://cdimage-dev.blankonlinux.or.id/blankon/livedvd-harian/' 57 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Master github actions 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Get current date 16 | run: echo "irgsh_build_date=$(TZ='Asia/Jakarta' date +'%Y%m%d%H%M')" >> $GITHUB_ENV 17 | - uses: actions/checkout@v3 18 | - name: Install needed apt packages 19 | uses: awalsh128/cache-apt-pkgs-action@v1.2.3 20 | with: 21 | packages: gpg pbuilder debootstrap devscripts python3-apt reprepro make 22 | version: 1.0 23 | - uses: actions/setup-go@v3 24 | with: 25 | go-version: "1.13.14" 26 | - uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.cache/go-build 30 | ~/go/pkg/mod 31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-go- 34 | - name: Build nightly release 35 | run: | 36 | echo ${{ steps.date.outputs.date }}-nightly-build > VERSION 37 | make release 38 | - uses: actions/upload-artifact@v3 39 | with: 40 | name: release.tar.gz 41 | path: target/ 42 | outputs: 43 | irgsh_build_date: ${{ env.irgsh_build_date }} 44 | 45 | release: 46 | runs-on: ubuntu-latest 47 | needs: [build-release] 48 | steps: 49 | - uses: actions/download-artifact@v3 50 | with: 51 | name: release.tar.gz 52 | - name: Extract release 53 | run: | 54 | tar xvzf release.tar.gz 55 | cp $(find . -type f -name "irgsh-cli") . 56 | - name: Create github release 57 | uses: softprops/action-gh-release@v0.1.15 58 | with: 59 | name: ${{ needs.build-release.outputs.irgsh_build_date }} Nightly Release 60 | body: Nightly release 61 | draft: false 62 | prerelease: false 63 | tag_name: ${{ needs.build-release.outputs.irgsh_build_date }}-nightly-build 64 | files: | 65 | release.tar.gz 66 | irgsh-cli 67 | 68 | deploy: 69 | runs-on: ubuntu-latest 70 | needs: [release] 71 | steps: 72 | - name: Deploy to irgsh server 73 | uses: kudaliar032/tendang-action@v1 74 | with: 75 | url: ${{ secrets.RANI_TENDANG_URL }} 76 | token: ${{ secrets.RANI_DEPLOYMENT_KEY }} 77 | name: ${{ secrets.RANI_DEPLOYMENT_NAME }} 78 | -------------------------------------------------------------------------------- /internal/artifact/repo/artifact_repo_moq.go: -------------------------------------------------------------------------------- 1 | // Code generated by moq; DO NOT EDIT. 2 | // github.com/matryer/moq 3 | 4 | package repo 5 | 6 | import ( 7 | "sync" 8 | ) 9 | 10 | var ( 11 | lockRepoMockGetArtifactList sync.RWMutex 12 | ) 13 | 14 | // Ensure, that RepoMock does implement Repo. 15 | // If this is not the case, regenerate this file with moq. 16 | var _ Repo = &RepoMock{} 17 | 18 | // RepoMock is a mock implementation of Repo. 19 | // 20 | // func TestSomethingThatUsesRepo(t *testing.T) { 21 | // 22 | // // make and configure a mocked Repo 23 | // mockedRepo := &RepoMock{ 24 | // GetArtifactListFunc: func(pageNum int64, rows int64) (ArtifactList, error) { 25 | // panic("mock out the GetArtifactList method") 26 | // }, 27 | // } 28 | // 29 | // // use mockedRepo in code that requires Repo 30 | // // and then make assertions. 31 | // 32 | // } 33 | type RepoMock struct { 34 | // GetArtifactListFunc mocks the GetArtifactList method. 35 | GetArtifactListFunc func(pageNum int64, rows int64) (ArtifactList, error) 36 | 37 | // calls tracks calls to the methods. 38 | calls struct { 39 | // GetArtifactList holds details about calls to the GetArtifactList method. 40 | GetArtifactList []struct { 41 | // PageNum is the pageNum argument value. 42 | PageNum int64 43 | // Rows is the rows argument value. 44 | Rows int64 45 | } 46 | } 47 | } 48 | 49 | // GetArtifactList calls GetArtifactListFunc. 50 | func (mock *RepoMock) GetArtifactList(pageNum int64, rows int64) (ArtifactList, error) { 51 | if mock.GetArtifactListFunc == nil { 52 | panic("RepoMock.GetArtifactListFunc: method is nil but Repo.GetArtifactList was just called") 53 | } 54 | callInfo := struct { 55 | PageNum int64 56 | Rows int64 57 | }{ 58 | PageNum: pageNum, 59 | Rows: rows, 60 | } 61 | lockRepoMockGetArtifactList.Lock() 62 | mock.calls.GetArtifactList = append(mock.calls.GetArtifactList, callInfo) 63 | lockRepoMockGetArtifactList.Unlock() 64 | return mock.GetArtifactListFunc(pageNum, rows) 65 | } 66 | 67 | // GetArtifactListCalls gets all the calls that were made to GetArtifactList. 68 | // Check the length with: 69 | // len(mockedRepo.GetArtifactListCalls()) 70 | func (mock *RepoMock) GetArtifactListCalls() []struct { 71 | PageNum int64 72 | Rows int64 73 | } { 74 | var calls []struct { 75 | PageNum int64 76 | Rows int64 77 | } 78 | lockRepoMockGetArtifactList.RLock() 79 | calls = mock.calls.GetArtifactList 80 | lockRepoMockGetArtifactList.RUnlock() 81 | return calls 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/generic.yml: -------------------------------------------------------------------------------- 1 | name: Generic github actions 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - "master" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build-devel: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Get current date 16 | run: echo "irgsh_build_date=$(TZ='Asia/Jakarta' date +'%Y%m%d%H%M')" >> $GITHUB_ENV 17 | - uses: actions/checkout@v3 18 | - name: Install needed apt packages 19 | uses: awalsh128/cache-apt-pkgs-action@v1.2.3 20 | with: 21 | packages: gpg pbuilder debootstrap devscripts python3-apt reprepro make 22 | version: 1.0 23 | - uses: actions/setup-go@v3 24 | with: 25 | go-version: "1.13.14" 26 | - uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.cache/go-build 30 | ~/go/pkg/mod 31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-go- 34 | - name: Build development release 35 | run: | 36 | echo ${{ env.irgsh_build_date }}-development-build > VERSION 37 | make release 38 | mv target/{release,pre-release}.tar.gz 39 | - uses: actions/upload-artifact@v3 40 | with: 41 | name: pre-release.tar.gz 42 | path: target/ 43 | outputs: 44 | irgsh_build_date: ${{ env.irgsh_build_date }} 45 | 46 | release: 47 | runs-on: ubuntu-latest 48 | needs: [build-devel] 49 | steps: 50 | - uses: actions/download-artifact@v3 51 | with: 52 | name: pre-release.tar.gz 53 | - name: Extract pre-release 54 | run: | 55 | tar xvzf pre-release.tar.gz 56 | cp $(find . -type f -name "irgsh-cli") . 57 | - name: Create github pre-release 58 | uses: softprops/action-gh-release@v0.1.15 59 | with: 60 | name: ${{ needs.build-devel.outputs.irgsh_build_date }} Development Release 61 | body: Development release ${{ needs.build-devel.outputs.irgsh_build_date }} 62 | draft: false 63 | prerelease: true 64 | tag_name: ${{ needs.build-devel.outputs.irgsh_build_date }}-development-build 65 | files: | 66 | pre-release.tar.gz 67 | irgsh-cli 68 | 69 | deploy: 70 | runs-on: ubuntu-latest 71 | needs: [release] 72 | steps: 73 | - name: Deploy to irgsh development server 74 | uses: kudaliar032/tendang-action@v1 75 | with: 76 | url: ${{ secrets.RAFI_TENDANG_URL }} 77 | token: ${{ secrets.RAFI_DEPLOYMENT_KEY }} 78 | name: ${{ secrets.RAFI_DEPLOYMENT_NAME }} 79 | -------------------------------------------------------------------------------- /internal/artifact/repo/file_impl_test.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestMain(m *testing.M) { 10 | // prepare artifact file 11 | os.Mkdir("./artifacts", os.ModePerm) 12 | file001, _ := os.Create("./artifacts/file001") 13 | file001.Close() 14 | file002, _ := os.Create("./artifacts/file002") 15 | file002.Close() 16 | 17 | exitVal := m.Run() 18 | // time.Sleep(2 * time.Second) 19 | 20 | // clean up test directory 21 | os.RemoveAll("./artifacts") 22 | 23 | os.Exit(exitVal) 24 | } 25 | 26 | func Test_getArtifactFilename(t *testing.T) { 27 | type args struct { 28 | filePath string 29 | } 30 | tests := []struct { 31 | name string 32 | args args 33 | wantFileName string 34 | }{ 35 | { 36 | name: "empty", 37 | }, 38 | { 39 | name: "correct : /var/www/artifacts/xxxyyyzzz", 40 | args: args{ 41 | filePath: "/var/www/artifacts/xxxyyyzzz", 42 | }, 43 | wantFileName: "xxxyyyzzz", 44 | }, 45 | { 46 | name: "error : /var/www/xxxyyyzzz", 47 | args: args{ 48 | filePath: "/var/www/xxxyyyzzz", 49 | }, 50 | wantFileName: "", 51 | }, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | if gotFileName := getArtifactFilename(tt.args.filePath); gotFileName != tt.wantFileName { 56 | t.Errorf("getArtifactFilename() = %v, want %v", gotFileName, tt.wantFileName) 57 | } 58 | }) 59 | } 60 | } 61 | 62 | func TestFileRepo_GetArtifactList(t *testing.T) { 63 | type fields struct { 64 | Workdir string 65 | } 66 | type args struct { 67 | pageNum int64 68 | rows int64 69 | } 70 | tests := []struct { 71 | name string 72 | fields fields 73 | args args 74 | wantArtifactsList ArtifactList 75 | wantErr bool 76 | }{ 77 | { 78 | name: "get files", 79 | fields: fields{ 80 | Workdir: ".", 81 | }, 82 | wantArtifactsList: ArtifactList{ 83 | TotalData: 2, 84 | Artifacts: []ArtifactModel{ 85 | ArtifactModel{Name: "file001"}, 86 | ArtifactModel{Name: "file002"}, 87 | }, 88 | }, 89 | }, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | A := &FileRepo{ 94 | Workdir: tt.fields.Workdir, 95 | } 96 | gotArtifactsList, err := A.GetArtifactList(tt.args.pageNum, tt.args.rows) 97 | if (err != nil) != tt.wantErr { 98 | t.Errorf("FileRepo.GetArtifactList() error = %v, wantErr %v", err, tt.wantErr) 99 | return 100 | } 101 | if !reflect.DeepEqual(gotArtifactsList, tt.wantArtifactsList) { 102 | t.Errorf("FileRepo.GetArtifactList() = %v, want %v", gotArtifactsList, tt.wantArtifactsList) 103 | } 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /cmd/builder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | machinery "github.com/RichardKnop/machinery/v1" 10 | machineryConfig "github.com/RichardKnop/machinery/v1/config" 11 | "github.com/urfave/cli" 12 | 13 | "github.com/blankon/irgsh-go/internal/config" 14 | ) 15 | 16 | var ( 17 | app *cli.App 18 | configPath string 19 | server *machinery.Server 20 | version string 21 | 22 | irgshConfig = config.IrgshConfig{} 23 | ) 24 | 25 | func main() { 26 | log.SetFlags(log.LstdFlags | log.Lshortfile) 27 | var err error 28 | irgshConfig, err = config.LoadConfig() 29 | if err != nil { 30 | log.Fatalln("couldn't load config : ", err) 31 | } 32 | // Prepare workdir 33 | err = os.MkdirAll(irgshConfig.Builder.Workdir, 0755) 34 | if err != nil { 35 | log.Fatalln(err) 36 | } 37 | 38 | app = cli.NewApp() 39 | app.Name = "irgsh-go" 40 | app.Usage = "irgsh-go distributed packager" 41 | app.Author = "BlankOn Developer" 42 | app.Email = "blankon-dev@googlegroups.com" 43 | app.Version = version 44 | 45 | app.Commands = []cli.Command{ 46 | { 47 | Name: "init-builder", 48 | Aliases: []string{"i"}, 49 | Usage: "Initialize builder", 50 | Action: func(c *cli.Context) error { 51 | err := InitBuilder() 52 | return err 53 | }, 54 | }, 55 | { 56 | Name: "init-base", 57 | Aliases: []string{"i"}, 58 | Usage: "Initialize pbuilder base.tgz. This need to be run under sudo or root", 59 | Action: func(c *cli.Context) error { 60 | err := InitBase() 61 | return err 62 | }, 63 | }, 64 | { 65 | Name: "update-base", 66 | Aliases: []string{"i"}, 67 | Usage: "update base.tgz", 68 | Action: func(c *cli.Context) error { 69 | err := UpdateBase() 70 | return err 71 | }, 72 | }, 73 | } 74 | 75 | app.Action = func(c *cli.Context) error { 76 | 77 | go serve() 78 | 79 | server, err = machinery.NewServer( 80 | &machineryConfig.Config{ 81 | Broker: irgshConfig.Redis, 82 | ResultBackend: irgshConfig.Redis, 83 | DefaultQueue: "irgsh", 84 | }, 85 | ) 86 | if err != nil { 87 | fmt.Println("Could not create server : " + err.Error()) 88 | } 89 | 90 | server.RegisterTask("build", Build) 91 | 92 | worker := server.NewWorker("builder", 1) 93 | err = worker.Launch() 94 | if err != nil { 95 | fmt.Println("Could not launch worker : " + err.Error()) 96 | } 97 | 98 | return nil 99 | 100 | } 101 | app.Run(os.Args) 102 | } 103 | 104 | func serve() { 105 | port := os.Getenv("PORT") 106 | if len(port) < 1 { 107 | port = "8081" 108 | } 109 | fs := http.FileServer(http.Dir(irgshConfig.Builder.Workdir)) 110 | http.Handle("/", fs) 111 | log.Println("irgsh-go builder now live on port " + port + ", serving path : " + irgshConfig.Builder.Workdir) 112 | log.Fatal(http.ListenAndServe(":"+port, nil)) 113 | } 114 | -------------------------------------------------------------------------------- /cmd/repo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | machinery "github.com/RichardKnop/machinery/v1" 10 | machineryConfig "github.com/RichardKnop/machinery/v1/config" 11 | "github.com/urfave/cli" 12 | 13 | "github.com/blankon/irgsh-go/internal/config" 14 | ) 15 | 16 | var ( 17 | app *cli.App 18 | configPath string 19 | server *machinery.Server 20 | version string 21 | 22 | irgshConfig = config.IrgshConfig{} 23 | ) 24 | 25 | func main() { 26 | log.SetFlags(log.LstdFlags | log.Lshortfile) 27 | 28 | var err error 29 | irgshConfig, err = config.LoadConfig() 30 | if err != nil { 31 | log.Fatalln("couldn't load config : ", err) 32 | } 33 | // Prepare workdir 34 | err = os.MkdirAll(irgshConfig.Repo.Workdir, 0755) 35 | if err != nil { 36 | log.Fatalln(err) 37 | } 38 | 39 | err = os.MkdirAll(irgshConfig.Repo.Workdir, 0755) 40 | if err != nil { 41 | log.Fatalln(err) 42 | } 43 | 44 | app = cli.NewApp() 45 | app.Name = "irgsh-go" 46 | app.Usage = "irgsh-go distributed packager" 47 | app.Author = "BlankOn Developer" 48 | app.Email = "blankon-dev@googlegroups.com" 49 | app.Version = version 50 | 51 | app.Commands = []cli.Command{ 52 | { 53 | Name: "init", 54 | Aliases: []string{"i"}, 55 | Usage: "initialize repository", 56 | Action: func(c *cli.Context) (err error) { 57 | err = InitRepo() 58 | return err 59 | }, 60 | }, 61 | { 62 | Name: "sync", 63 | Aliases: []string{"i"}, 64 | Usage: "update base.tgz", 65 | Action: func(c *cli.Context) (err error) { 66 | err = UpdateRepo() 67 | return err 68 | }, 69 | }, 70 | } 71 | 72 | app.Action = func(c *cli.Context) error { 73 | 74 | go serve() 75 | 76 | server, err := machinery.NewServer( 77 | &machineryConfig.Config{ 78 | Broker: irgshConfig.Redis, 79 | ResultBackend: irgshConfig.Redis, 80 | DefaultQueue: "irgsh", 81 | }, 82 | ) 83 | if err != nil { 84 | fmt.Println("Could not create server : " + err.Error()) 85 | } 86 | 87 | server.RegisterTask("repo", Repo) 88 | // One worker for synchronous 89 | worker := server.NewWorker("repo", 1) 90 | err = worker.Launch() 91 | if err != nil { 92 | fmt.Println("Could not launch worker : " + err.Error()) 93 | } 94 | return nil 95 | 96 | } 97 | app.Run(os.Args) 98 | } 99 | 100 | func IndexHandler(w http.ResponseWriter, r *http.Request) { 101 | fmt.Fprintf(w, "irgsh-repo "+app.Version) 102 | } 103 | 104 | func serve() { 105 | http.HandleFunc("/", IndexHandler) 106 | http.Handle("/dev/", 107 | http.StripPrefix("/dev/", 108 | http.FileServer( 109 | http.Dir(irgshConfig.Repo.Workdir+"/"+irgshConfig.Repo.DistCodename+"/www"), 110 | ), 111 | ), 112 | ) 113 | http.Handle("/experimental/", 114 | http.StripPrefix("/experimental/", 115 | http.FileServer( 116 | http.Dir(irgshConfig.Repo.Workdir+"/"+irgshConfig.Repo.DistCodename+"-experimental/www"), 117 | ), 118 | ), 119 | ) 120 | port := os.Getenv("PORT") 121 | if len(port) < 1 { 122 | port = "8082" 123 | } 124 | log.Println("irgsh-go repo is now live on port " + port) 125 | log.Fatal(http.ListenAndServe(":"+port, nil)) 126 | } 127 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Guide for developer 2 | 3 | ## Requirements 4 | 5 | Ensure that you have: 6 | - Golang 7 | - Docker 8 | - These packages installed: `build-essential gpg pbuilder debootstrap devscripts curl reprepro` 9 | 10 | ## Cloning 11 | 12 | `git clone git@github.com:BlankOn/irgsh-go.git && cd irgsh-go` 13 | 14 | ## Preparation 15 | 16 | ### GPG Key 17 | 18 | You need to have a pair of GPG key in your GPG store. If you don't have one, please create it with `gpg --generate-key`. When generating GPG key for irgsh infrastructure, please do not set any passphrase. Check it by running `gpg --list-key` 19 | 20 | ``` 21 | $ gpg --list-key 22 | /home/herpiko/.gnupg/pubring.kbx 23 | -------------------------------- 24 | pub rsa4096 2020-10-17 [SC] [expires: 2021-10-17] 25 | 41B4FC0A57E7F7F8DD94E0AA2D21BB5FAA32AF3F 26 | uid [ultimate] Herpiko Dwi Aguno 27 | sub rsa4096 2020-10-17 [E] [expires: 2021-10-17] 28 | ``` 29 | Copy the key identity (in my case, it's the `41B4FC0A57E7F7F8DD94E0AA2D21BB5FAA32AF3F` string) then paste it to replace `GPG_SIGN_KEY` in `utils/config.yml` 30 | 31 | In dev environment, this single key will acts as both repository signing key and package maintainer signing key. On prod, they will be different keys. 32 | 33 | ### Initialization 34 | 35 | #### Client 36 | 37 | You need to build then initialize the CLI client to point out to the chief and your signing key (see `GPG Key` section). 38 | 39 | - `make client` 40 | - `./bin/irgsh-cli config --chief http://localhost:8080 --key 41B4FC0A57E7F7F8DD94E0AA2D21BB5FAA32AF3F` 41 | 42 | #### Builder 43 | 44 | `make builder-init` 45 | 46 | This command will: 47 | - Create pbuilder base.tgz that follow our configuration. This step need root privilege, you'll be asked for root password. 48 | - Create docker image that will be used to build packages. 49 | 50 | This one may take longer as it need to build an entire chroot environment. 51 | 52 | #### Repository 53 | 54 | You need to set the `repo.dist_signing_key` in `./utils/config.yaml` to your GPG key identity. For local development, it's okay to use the same key. In production, the repo signing key should be something else than maintainer's keys. Then, 55 | 56 | `make repo-init` 57 | 58 | This command will remove existing repositories if any and reinit the new one. You may be asked for your GPG key passphrase. You can tweak repository configuration in `repo` section of `utils/config.yml` 59 | 60 | ### Redis 61 | 62 | `make redis` 63 | 64 | ## Starting up 65 | 66 | Open three different terminal and run these command for each: 67 | - `make chief` occupying port 8080 68 | - `make builder`, occupying port 8081 69 | - `make repo`, occupying port 8082 70 | 71 | ## Testing 72 | 73 | Open the fourth terminal and try to submit dummy package using this command bellow: 74 | 75 | - `./bin/irgsh-cli submit --experimental --source https://github.com/BlankOn/bromo-theme.git --package https://github.com/BlankOn-packages/bromo-theme.git --ignore-checks` 76 | 77 | You may be asked for your GPG key passphrase. You'll see the package preprared in this terminal, then in the chief terminal (job coordination), then in builder terminal (package building), then in repo terminal (package submission into the repository). 78 | 79 | If all is well, you can see the result by opening `http://localhost:8082/experimental/` on your web browser. At this point, you have explored the full cycle of the basic usage. You may want to start to hack. 80 | 81 | Check the status of a pipeline 82 | 83 | ``` 84 | curl http://localhost:8080/api/v1/status?uuid=uuidstring 85 | ``` 86 | 87 | ## Test & Coverage 88 | 89 | ``` 90 | make test 91 | ``` 92 | 93 | It will test the code and open the coverage result on your browser. 94 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LDFLAGS := "-X main.version=$$(cat ./VERSION)" 2 | 3 | release: 4 | # Build 5 | make build 6 | # Bundling 7 | mkdir -p irgsh-go/usr/bin 8 | mkdir -p irgsh-go/etc/irgsh 9 | mkdir -p irgsh-go/etc/init.d 10 | mkdir -p irgsh-go/lib/systemd/system 11 | mkdir -p irgsh-go/usr/share/irgsh 12 | cp -rf bin/* irgsh-go/usr/bin/ 13 | cp -rf utils/config.yml irgsh-go/etc/irgsh/ 14 | cp -rf utils/config.yml irgsh-go/usr/share/irgsh/config.yml 15 | cp -rf utils/init/* irgsh-go/etc/init.d/ 16 | cp -rf utils/systemctl/* irgsh-go/lib/systemd/system 17 | cp -rf utils/scripts/init.sh irgsh-go/usr/share/irgsh/init.sh 18 | cp -rf -R utils/reprepro-template irgsh-go/usr/share/irgsh/reprepro-template 19 | tar -zcvf release.tar.gz irgsh-go 20 | mkdir -p target 21 | mv release.tar.gz target/ 22 | 23 | release-in-docker: release 24 | # It's possible this release command will be used inside a container 25 | # Let it rewriteable for host environment 26 | chmod -vR a+rw target 27 | chown -vR :users target 28 | 29 | preinstall: 30 | sudo /etc/init.d/irgsh-chief stop || true 31 | sudo /etc/init.d/irgsh-builder stop || true 32 | sudo /etc/init.d/irgsh-iso stop || true 33 | sudo /etc/init.d/irgsh-repo stop || true 34 | sudo killall irgsh-chief || true 35 | sudo killall irgsh-builder || true 36 | sudo killall irgsh-iso || true 37 | sudo killall irgsh-repo || true 38 | 39 | build-in-docker: 40 | cp -rf utils/docker/build/Dockerfile . 41 | docker build --no-cache -t irgsh-build . 42 | docker run -v $(pwd)/target:/tmp/src/target irgsh-build make release-in-docker 43 | 44 | build: 45 | mkdir -p bin 46 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-repo ./cmd/repo 47 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-chief ./cmd/chief 48 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-builder ./cmd/builder 49 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-iso ./cmd/iso 50 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-cli ./cmd/cli 51 | 52 | build-install: release 53 | ./install.sh 54 | sudo systemctl daemon-reload 55 | sudo /lib/systemd/systemd-sysv-install enable irgsh-chief 56 | sudo /lib/systemd/systemd-sysv-install enable irgsh-builder 57 | sudo /lib/systemd/systemd-sysv-install enable irgsh-repo 58 | sudo systemctl start irgsh-chief 59 | sudo systemctl start irgsh-builder 60 | sudo systemctl start irgsh-repo 61 | 62 | test: 63 | mkdir -p tmp 64 | go test -race -coverprofile=coverage.txt -covermode=atomic ./cmd/builder 65 | go test -race -coverprofile=coverage.txt -covermode=atomic ./cmd/iso 66 | go test -race -coverprofile=coverage.txt -covermode=atomic ./cmd/repo 67 | 68 | coverage:test 69 | go tool cover -html=coverage.txt 70 | 71 | client: 72 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-cli ./cmd/cli 73 | 74 | chief: 75 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-chief ./cmd/chief && DEV=1 ./bin/irgsh-chief 76 | 77 | builder-init: 78 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-builder ./cmd/builder && sudo DEV=1 ./bin/irgsh-builder init-base 79 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-builder ./cmd/builder && DEV=1 ./bin/irgsh-builder init-builder 80 | 81 | builder: 82 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-builder ./cmd/builder && DEV=1 ./bin/irgsh-builder 83 | 84 | iso: 85 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-iso ./cmd/iso && DEV=1 ./bin/irgsh-iso 86 | 87 | repo-init: 88 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-repo ./cmd/repo && DEV=1 ./bin/irgsh-repo init 89 | 90 | repo: 91 | go build -ldflags $(LDFLAGS) -o ./bin/irgsh-repo ./cmd/repo && DEV=1 ./bin/irgsh-repo 92 | 93 | redis: 94 | docker run -d --network host redis 95 | 96 | submit: 97 | curl --header "Content-Type: application/json" --request POST --data '{"sourceUrl":"https://github.com/BlankOn/bromo-theme.git","packageUrl":"https://github.com/BlankOn-packages/bromo-theme.git"}' http://localhost:8080/api/v1/submit 98 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/ghodss/yaml" 10 | validator "gopkg.in/go-playground/validator.v9" 11 | ) 12 | 13 | type IrgshConfig struct { 14 | Redis string `json:"redis"` 15 | Chief ChiefConfig `json:"chief"` 16 | Builder BuilderConfig `json:"builder"` 17 | ISO ISOConfig `json:"iso"` 18 | Repo RepoConfig `json:"repo"` 19 | IsTest bool `json:"is_test"` 20 | IsDev bool `json:"is_dev"` 21 | } 22 | 23 | type ChiefConfig struct { 24 | Address string `json:"address" validate:"required"` 25 | Workdir string `json:"workdir" validate:"required"` 26 | GnupgDir string `json:"gnupg_dir" validate:"required"` // GNUPG dir path 27 | } 28 | 29 | type BuilderConfig struct { 30 | Workdir string `json:"workdir" validate:"required"` 31 | UpstreamDistUrl string `json:"upstream_dist_url" validate:"required"` // http://kartolo.sby.datautama.net.id/debian 32 | } 33 | 34 | type ISOConfig struct { 35 | Workdir string `json:"workdir" validate:"required"` 36 | } 37 | 38 | type RepoConfig struct { 39 | Workdir string `json:"workdir" validate:"required"` 40 | DistName string `json:"dist_name" validate:"required"` // BlankOn 41 | DistLabel string `json:"dist_label" validate:"required"` // BlankOn 42 | DistCodename string `json:"dist_codename" validate:"required"` // verbeek 43 | DistComponents string `json:"dist_components" validate:"required"` // main restricted extras extras-restricted 44 | DistSupportedArchitectures string `json:"dist_supported_architectures" validate:"required"` // amd64 source 45 | DistVersion string `json:"dist_version" validate:"required"` // 12.0 46 | DistVersionDesc string `json:"dist_version_desc" validate:"required"` // BlankOn Linux 12.0 Verbeek 47 | DistSigningKey string `json:"dist_signing_key" validate:"required"` // 55BD65A0B3DA3A59ACA60932E2FE388D53B56A71 48 | UpstreamName string `json:"upstream_name" validate:"required"` // merge.sid 49 | UpstreamDistCodename string `json:"upstream_dist_codename" validate:"required"` // sid 50 | UpstreamDistUrl string `json:"upstream_dist_url" validate:"required"` // http://kartolo.sby.datautama.net.id/debian 51 | UpstreamDistComponents string `json:"upstream_dist_components" validate:"required"` // main non-free>restricted contrib>extras 52 | GnupgDir string `json:"gnupg_dir" validate:"required"` // GNUPG dir path 53 | } 54 | 55 | // LoadConfig load irgsh config from file 56 | func LoadConfig() (config IrgshConfig, err error) { 57 | configPaths := []string{ 58 | "/etc/irgsh/config.yml", 59 | "../../utils/config.yml", 60 | "./utils/config.yml", 61 | } 62 | configPath := os.Getenv("IRGSH_CONFIG_PATH") 63 | isDev := os.Getenv("DEV") == "1" 64 | yamlFile, err := ioutil.ReadFile(configPath) 65 | if err != nil { 66 | // load from predefined configPaths when no IRGSH_CONFIG_PATH set 67 | for _, config := range configPaths { 68 | yamlFile, err = ioutil.ReadFile(config) 69 | if err == nil { 70 | log.Println("load config from : ", config) 71 | break 72 | } 73 | } 74 | if err != nil { 75 | return 76 | } 77 | } 78 | if isDev { 79 | yamlFile, err = ioutil.ReadFile("./utils/config.yml") 80 | if err != nil { 81 | return 82 | } 83 | } 84 | 85 | err = yaml.Unmarshal(yamlFile, &config) 86 | if err != nil { 87 | return 88 | } 89 | 90 | if isDev { 91 | // Since it's in dev env, let's move some path to ./tmp 92 | cwd, _ := os.Getwd() 93 | tmpDir := cwd + "/tmp/" 94 | if _, err := os.Stat(tmpDir); os.IsNotExist(err) { 95 | os.Mkdir(tmpDir, 0755) 96 | } 97 | config.Chief.Workdir = strings.ReplaceAll(config.Chief.Workdir, "/var/lib/", tmpDir) 98 | config.Builder.Workdir = strings.ReplaceAll(config.Builder.Workdir, "/var/lib/", tmpDir) 99 | config.Repo.Workdir = strings.ReplaceAll(config.Repo.Workdir, "/var/lib/", tmpDir) 100 | } 101 | config.IsDev = isDev 102 | validate := validator.New() 103 | err = validate.Struct(config) 104 | 105 | return 106 | } 107 | -------------------------------------------------------------------------------- /cmd/builder/builder_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/exec" 7 | "strconv" 8 | "testing" 9 | "time" 10 | 11 | "github.com/blankon/irgsh-go/internal/config" 12 | "github.com/google/uuid" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestMain(m *testing.M) { 17 | log.SetFlags(log.LstdFlags | log.Lshortfile) 18 | 19 | irgshConfig, _ = config.LoadConfig() 20 | dir, _ := os.Getwd() 21 | irgshConfig.Builder.Workdir = dir + "/../tmp" 22 | 23 | m.Run() 24 | } 25 | 26 | func TestBuilderClone(t *testing.T) { 27 | id := time.Now().Format("2006-01-02-150405") + "_" + uuid.New().String() 28 | log.Println(id) 29 | payload := "{\"taskUUID\":\"" + id + "\",\"timestamp\":\"2019-04-03T07:23:02.826753827-04:00\",\"sourceUrl\":\"https://github.com/BlankOn/bromo-theme.git\",\"packageUrl\":\"https://github.com/BlankOn-packages/bromo-theme.git\"}" 30 | _, err := Clone(payload) 31 | if err != nil { 32 | log.Println(err.Error()) 33 | assert.Equal(t, true, false, "Should not reach here") 34 | } 35 | 36 | cmdStr := "du -s " + irgshConfig.Builder.Workdir + "/" + id + "/source | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 37 | cmd := exec.Command("bash", "-c", cmdStr) 38 | out, _ := cmd.CombinedOutput() 39 | cmd.Run() 40 | log.Println(string(out)) 41 | size, err := strconv.Atoi(string(out)) 42 | if err != nil { 43 | log.Println(err.Error()) 44 | assert.Equal(t, true, false, "Should not reach here") 45 | } 46 | assert.NotEqual(t, size, int(0)) 47 | 48 | cmdStr = "du -s " + irgshConfig.Builder.Workdir + "/" + id + "/package | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 49 | cmd = exec.Command("bash", "-c", cmdStr) 50 | out, _ = cmd.CombinedOutput() 51 | cmd.Run() 52 | size, err = strconv.Atoi(string(out)) 53 | if err != nil { 54 | log.Println(err.Error()) 55 | assert.Equal(t, true, false, "Should not reach here") 56 | } 57 | assert.NotEqual(t, size, int(0)) 58 | } 59 | 60 | func TestBuilderCloneInvalidSourceUrl(t *testing.T) { 61 | id := time.Now().Format("2006-01-02-150405") + "_" + uuid.New().String() 62 | log.Println(id) 63 | payload := "{\"taskUUID\":\"" + id + "\",\"timestamp\":\"2019-04-03T07:23:02.826753827-04:00\",\"sourceUrl\":\"https://github.com/BlankOn/bromo-theme-xyz.git\",\"packageUrl\":\"https://github.com/BlankOn-packages/bromo-theme.git\"}" 64 | _, err := Clone(payload) 65 | assert.Equal(t, err != nil, true, "Should be error") 66 | 67 | cmdStr := "du -s " + irgshConfig.Builder.Workdir + "/" + id + "/source | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 68 | cmd := exec.Command("bash", "-c", cmdStr) 69 | out, _ := cmd.CombinedOutput() 70 | cmd.Run() 71 | size, err := strconv.Atoi(string(out)) 72 | assert.Equal(t, err != nil, true, "Should be error") 73 | assert.Equal(t, size, int(0)) 74 | 75 | cmdStr = "du -s " + irgshConfig.Builder.Workdir + "/" + id + "/package | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 76 | cmd = exec.Command("bash", "-c", cmdStr) 77 | out, _ = cmd.CombinedOutput() 78 | cmd.Run() 79 | size, err = strconv.Atoi(string(out)) 80 | assert.Equal(t, err != nil, true, "Should be error") 81 | assert.Equal(t, size, int(0)) 82 | } 83 | 84 | func TestBuilderCloneInvalidPackadeUrl(t *testing.T) { 85 | id := time.Now().Format("2006-01-02-150405") + "_" + uuid.New().String() 86 | log.Println(id) 87 | payload := "{\"taskUUID\":\"" + id + "\",\"timestamp\":\"2019-04-03T07:23:02.826753827-04:00\",\"sourceUrl\":\"https://github.com/BlankOn/bromo-theme.git\",\"packageUrl\":\"https://github.com/BlankOn-packages/bromo-theme-xyz.git\"}" 88 | _, err := Clone(payload) 89 | assert.Equal(t, err != nil, true, "Should be error") 90 | 91 | cmdStr := "du -s " + irgshConfig.Builder.Workdir + "/" + id + "/source | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 92 | cmd := exec.Command("bash", "-c", cmdStr) 93 | out, _ := cmd.CombinedOutput() 94 | cmd.Run() 95 | size, err := strconv.Atoi(string(out)) 96 | if err != nil { 97 | log.Println(err.Error()) 98 | assert.Equal(t, true, false, "Should not reach here") 99 | } 100 | assert.NotEqual(t, size, int(0)) 101 | 102 | cmdStr = "du -s " + irgshConfig.Builder.Workdir + "/" + id + "/package | cut -d '/' -f1 | head -n 1 | sed 's/ //g' | tr -d '\n' | tr -d '\t' " 103 | cmd = exec.Command("bash", "-c", cmdStr) 104 | out, _ = cmd.CombinedOutput() 105 | cmd.Run() 106 | size, err = strconv.Atoi(string(out)) 107 | assert.Equal(t, err != nil, true, "Should be error") 108 | assert.Equal(t, size, int(0)) 109 | } 110 | 111 | // This tests below need pbuilder/sudo 112 | 113 | func TestBuilderBuildPreparation(t *testing.T) { 114 | t.Skip() 115 | } 116 | 117 | func TestBuilderBuildPackage(t *testing.T) { 118 | t.Skip() 119 | } 120 | 121 | func TestBuilderStorePackage(t *testing.T) { 122 | t.Skip() 123 | } 124 | -------------------------------------------------------------------------------- /cmd/chief/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | machinery "github.com/RichardKnop/machinery/v1" 12 | machineryConfig "github.com/RichardKnop/machinery/v1/config" 13 | "github.com/urfave/cli" 14 | 15 | "github.com/blankon/irgsh-go/internal/config" 16 | 17 | artifactEndpoint "github.com/blankon/irgsh-go/internal/artifact/endpoint" 18 | artifactRepo "github.com/blankon/irgsh-go/internal/artifact/repo" 19 | artifactService "github.com/blankon/irgsh-go/internal/artifact/service" 20 | ) 21 | 22 | var ( 23 | app *cli.App 24 | server *machinery.Server 25 | version string 26 | 27 | irgshConfig config.IrgshConfig 28 | 29 | artifactHTTPEndpoint *artifactEndpoint.ArtifactHTTPEndpoint 30 | ) 31 | 32 | type Submission struct { 33 | TaskUUID string `json:"taskUUID"` 34 | Timestamp time.Time `json:"timestamp"` 35 | PackageName string `json:"packageName"` 36 | PackageVersion string `json:"packageVersion"` 37 | PackageExtendedVersion string `json:"packageExtendedVersion"` 38 | PackageURL string `json:"packageUrl"` 39 | SourceURL string `json:"sourceUrl"` 40 | Maintainer string `json:"maintainer"` 41 | MaintainerFingerprint string `json:"maintainerFingerprint"` 42 | Component string `json:"component"` 43 | IsExperimental bool `json:"isExperimental"` 44 | Tarball string `json:"tarball"` 45 | PackageBranch string `json:"packageBranch"` 46 | SourceBranch string `json:"sourceBranch"` 47 | } 48 | 49 | type ArtifactsPayloadResponse struct { 50 | Data []string `json:"data"` 51 | } 52 | 53 | type SubmitPayloadResponse struct { 54 | PipelineId string `json:"pipelineId"` 55 | Jobs []string `json:"jobs,omitempty"` 56 | } 57 | 58 | func main() { 59 | log.SetFlags(log.LstdFlags | log.Lshortfile) 60 | 61 | var err error 62 | irgshConfig, err = config.LoadConfig() 63 | if err != nil { 64 | log.Fatalln(err) 65 | } 66 | 67 | // Prepare workdir 68 | err = os.MkdirAll(irgshConfig.Chief.Workdir, 0755) 69 | if err != nil { 70 | log.Fatalln(err) 71 | } 72 | log.Println(irgshConfig.Chief.Workdir) 73 | 74 | artifactHTTPEndpoint = artifactEndpoint.NewArtifactHTTPEndpoint( 75 | artifactService.NewArtifactService( 76 | artifactRepo.NewFileRepo(irgshConfig.Chief.Workdir))) 77 | 78 | app = cli.NewApp() 79 | app.Name = "irgsh-go" 80 | app.Usage = "irgsh-go distributed packager" 81 | app.Author = "BlankOn Developer" 82 | app.Email = "blankon-dev@googlegroups.com" 83 | app.Version = version 84 | 85 | app.Action = func(c *cli.Context) error { 86 | server, err = machinery.NewServer( 87 | &machineryConfig.Config{ 88 | Broker: irgshConfig.Redis, 89 | ResultBackend: irgshConfig.Redis, 90 | DefaultQueue: "irgsh", 91 | }, 92 | ) 93 | if err != nil { 94 | fmt.Println("Could not create server : " + err.Error()) 95 | } 96 | 97 | serve() 98 | 99 | return nil 100 | } 101 | app.Run(os.Args) 102 | 103 | } 104 | 105 | func serve() { 106 | http.HandleFunc("/", indexHandler) 107 | 108 | // APIs 109 | http.HandleFunc("/api/v1/artifacts", artifactHTTPEndpoint.GetArtifactListHandler) 110 | http.HandleFunc("/api/v1/submit", PackageSubmitHandler) 111 | http.HandleFunc("/api/v1/status", BuildStatusHandler) 112 | http.HandleFunc("/api/v1/artifact-upload", artifactUploadHandler()) 113 | http.HandleFunc("/api/v1/log-upload", logUploadHandler()) 114 | http.HandleFunc("/api/v1/submission-upload", submissionUploadHandler()) 115 | http.HandleFunc("/api/v1/build-iso", BuildISOHandler) 116 | http.HandleFunc("/api/v1/version", VersionHandler) 117 | 118 | // Pages 119 | http.HandleFunc("/maintainers", MaintainersHandler) 120 | // Static file routes 121 | artifactFs := http.FileServer(http.Dir(irgshConfig.Chief.Workdir + "/artifacts")) 122 | http.Handle("/artifacts/", http.StripPrefix("/artifacts/", artifactFs)) 123 | logFs := http.FileServer(http.Dir(irgshConfig.Chief.Workdir + "/logs")) 124 | http.Handle("/logs/", http.StripPrefix("/logs/", logFs)) 125 | submissionFs := http.FileServer(http.Dir(irgshConfig.Chief.Workdir + "/submissions")) 126 | http.Handle("/submissions/", http.StripPrefix("/submissions/", submissionFs)) 127 | 128 | port := os.Getenv("PORT") 129 | if len(port) < 1 { 130 | port = "8080" 131 | } 132 | log.Println("irgsh-go chief now live on port " + port) 133 | log.Fatal(http.ListenAndServe(":"+port, nil)) 134 | } 135 | 136 | func Move(src, dst string) error { 137 | in, err := os.Open(src) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | out, err := os.Create(dst) 143 | if err != nil { 144 | return err 145 | } 146 | 147 | _, err = io.Copy(out, in) 148 | if err != nil { 149 | return err 150 | } 151 | in.Close() 152 | out.Close() 153 | 154 | return os.Remove(src) 155 | } 156 | -------------------------------------------------------------------------------- /internal/artifact/service/artifact_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | artifactRepo "github.com/blankon/irgsh-go/internal/artifact/repo" 9 | ) 10 | 11 | // func TestArtifactService_GetArtifactList(t *testing.T) { 12 | // type fields struct { 13 | // repo artifactRepo.Repo 14 | // } 15 | // type args struct { 16 | // pageNum int64 17 | // rows int64 18 | // } 19 | // tests := []struct { 20 | // name string 21 | // fields fields 22 | // args args 23 | // wantItems []ArtifactItem 24 | // wantErr bool 25 | // }{ 26 | // { 27 | // name: "empty", 28 | // fields: fields{ 29 | // repo: &artifactRepo.RepoMock{ 30 | // GetArtifactListFunc: func(pageNum int64, rows int64) (m []repo.ArtifactModel, e error) { 31 | // return 32 | // }, 33 | // }, 34 | // }, 35 | // }, 36 | // { 37 | // name: "error", 38 | // fields: fields{ 39 | // repo: &artifactRepo.RepoMock{ 40 | // GetArtifactListFunc: func(pageNum int64, rows int64) (m []repo.ArtifactModel, e error) { 41 | // return m, fmt.Errorf("") 42 | // }, 43 | // }, 44 | // }, 45 | // wantErr: true, 46 | // }, 47 | // { 48 | // name: "2 items", 49 | // fields: fields{ 50 | // repo: &artifactRepo.RepoMock{ 51 | // GetArtifactListFunc: func(pageNum int64, rows int64) (m []repo.ArtifactModel, e error) { 52 | // m = append(m, repo.ArtifactModel{Name: "test1"}) 53 | // m = append(m, repo.ArtifactModel{Name: "test2"}) 54 | // return 55 | // }, 56 | // }, 57 | // }, 58 | // wantItems: []ArtifactItem{ArtifactItem{Name: "test1"}, ArtifactItem{Name: "test2"}}, 59 | // }, 60 | // } 61 | // for _, tt := range tests { 62 | // t.Run(tt.name, func(t *testing.T) { 63 | // A := &ArtifactService{ 64 | // repo: tt.fields.repo, 65 | // } 66 | // gotItems, err := A.GetArtifactList(tt.args.pageNum, tt.args.rows) 67 | // if (err != nil) != tt.wantErr { 68 | // t.Errorf("ArtifactService.GetArtifactList() error = %v, wantErr %v", err, tt.wantErr) 69 | // return 70 | // } 71 | // if !reflect.DeepEqual(gotItems, tt.wantItems) { 72 | // t.Errorf("ArtifactService.GetArtifactList() = %v, want %v", gotItems, tt.wantItems) 73 | // } 74 | // }) 75 | // } 76 | // } 77 | 78 | func TestNewArtifactService(t *testing.T) { 79 | type args struct { 80 | repo artifactRepo.Repo 81 | } 82 | tests := []struct { 83 | name string 84 | args args 85 | want *ArtifactService 86 | }{ 87 | { 88 | name: "empty", 89 | want: &ArtifactService{}, 90 | }, 91 | } 92 | for _, tt := range tests { 93 | t.Run(tt.name, func(t *testing.T) { 94 | if got := NewArtifactService(tt.args.repo); !reflect.DeepEqual(got, tt.want) { 95 | t.Errorf("NewArtifactService() = %v, want %v", got, tt.want) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func TestArtifactService_GetArtifactList(t *testing.T) { 102 | type fields struct { 103 | repo artifactRepo.Repo 104 | } 105 | type args struct { 106 | pageNum int64 107 | rows int64 108 | } 109 | tests := []struct { 110 | name string 111 | fields fields 112 | args args 113 | wantArtifacts ArtifactList 114 | wantErr bool 115 | }{ 116 | { 117 | name: "empty", 118 | fields: fields{ 119 | repo: &artifactRepo.RepoMock{ 120 | GetArtifactListFunc: func(pageNum int64, rows int64) (l artifactRepo.ArtifactList, e error) { 121 | return 122 | }, 123 | }, 124 | }, 125 | wantArtifacts: ArtifactList{TotalData: 0, Artifacts: []ArtifactItem{}}, 126 | }, 127 | { 128 | name: "error", 129 | fields: fields{ 130 | repo: &artifactRepo.RepoMock{ 131 | GetArtifactListFunc: func(pageNum int64, rows int64) (l artifactRepo.ArtifactList, e error) { 132 | return l, fmt.Errorf("") 133 | }, 134 | }, 135 | }, 136 | wantErr: true, 137 | }, 138 | { 139 | name: "2 items", 140 | fields: fields{ 141 | repo: &artifactRepo.RepoMock{ 142 | GetArtifactListFunc: func(pageNum int64, rows int64) (l artifactRepo.ArtifactList, e error) { 143 | l.Artifacts = append(l.Artifacts, artifactRepo.ArtifactModel{Name: "test1"}) 144 | l.Artifacts = append(l.Artifacts, artifactRepo.ArtifactModel{Name: "test2"}) 145 | l.TotalData = 2 146 | return 147 | }, 148 | }, 149 | }, 150 | wantArtifacts: ArtifactList{ 151 | TotalData: 2, 152 | Artifacts: []ArtifactItem{ArtifactItem{Name: "test1"}, ArtifactItem{Name: "test2"}}, 153 | }, 154 | }, 155 | } 156 | for _, tt := range tests { 157 | t.Run(tt.name, func(t *testing.T) { 158 | A := &ArtifactService{ 159 | repo: tt.fields.repo, 160 | } 161 | gotArtifacts, err := A.GetArtifactList(tt.args.pageNum, tt.args.rows) 162 | if (err != nil) != tt.wantErr { 163 | t.Errorf("ArtifactService.GetArtifactList() error = %v, wantErr %v", err, tt.wantErr) 164 | return 165 | } 166 | if !reflect.DeepEqual(gotArtifacts, tt.wantArtifacts) { 167 | t.Errorf("ArtifactService.GetArtifactList() = %v, want %v", gotArtifacts, tt.wantArtifacts) 168 | } 169 | }) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # stop when error 4 | set -e 5 | 6 | DIST=$(cat /etc/*release | grep "^ID=" | cut -d '=' -f 2) 7 | 8 | # Require sudo or root privilege 9 | if [ $EUID != 0 ]; then 10 | sudo "$0" "$@" 11 | exit $? 12 | fi 13 | 14 | TEMP_PATH=/tmp 15 | DEV_INSTALL=0 16 | 17 | apt update 18 | 19 | # Check if docker is installed 20 | if [[ -x "$(command -v docker)" && $(docker --version) ]]; then 21 | echo "Docker installed [OK]" 22 | else 23 | echo "Installing docker" 24 | apt install -y docker.io 25 | fi 26 | 27 | apt install -y gnupg pbuilder debootstrap devscripts debhelper python3-apt reprepro jq 28 | 29 | if [ -f ./target/release.tar.gz ]; then 30 | # For development/testing purpose 31 | TEMP_PATH=$(pwd)/target 32 | DEV_INSTALL=1 33 | else 34 | # Download and extract 35 | DOWNLOAD_URL=$(curl -ksL "https://api.github.com/repos/BlankOn/irgsh-go/releases/latest" | jq -r '.assets | .[] | select(.name == "release.tar.gz")| .browser_download_url') 36 | echo "Downloading ... " 37 | echo "$DOWNLOAD_URL" 38 | rm -f $TEMP_PATH/release.tar.gz && cd $TEMP_PATH && curl -L -f -o ./release.tar.gz $DOWNLOAD_URL 39 | if test $? -gt 0; then 40 | echo "Downloading [FAILED]" 41 | exit 1 42 | fi 43 | echo "Downloading [OK]" 44 | echo 45 | fi 46 | 47 | pushd $TEMP_PATH 48 | 49 | echo "Extracting ... " 50 | rm -rf irgsh-go && tar -xf release.tar.gz 51 | echo "Extracting [OK]" 52 | echo 53 | 54 | # Stop any existing instances if installed 55 | if [ -x "$(command -v irgsh-chief )" ]; then 56 | echo "Stopping existing instance(s) ... " 57 | systemctl daemon-reload 58 | /etc/init.d/irgsh-chief stop || true 59 | /etc/init.d/irgsh-builder stop || true 60 | /etc/init.d/irgsh-repo stop || true 61 | systemctl stop irgsh-chief 62 | systemctl stop irgsh-builder 63 | systemctl stop irgsh-repo 64 | killall irgsh-chief || true 65 | killall irgsh-builder || true 66 | killall irgsh-repo || true 67 | echo "Stopping existing instance(s) [OK]" 68 | echo 69 | fi 70 | 71 | if [ $DEV_INSTALL = 1 ]; then 72 | # For development/testing purpose 73 | # Clean up 74 | rm -rf /etc/irgsh/config.yml 75 | rm -rf /var/lib/irgsh/chief 76 | rm -rf /var/lib/irgsh/repo 77 | rm -rf /var/lib/irgsh/gnupg 78 | # Do not overwrite /var/lib/irgsh/builder 79 | #rm -rf /var/lib/irgsh/builder 80 | fi 81 | 82 | # Create required dirs 83 | mkdir -p /etc/irgsh 84 | mkdir -p /usr/share/irgsh 85 | mkdir -p /var/lib/irgsh/chief/submissions 86 | mkdir -p /var/lib/irgsh/chief/artifacts 87 | mkdir -p /var/lib/irgsh/chief/logs 88 | mkdir -p /var/lib/irgsh/builder 89 | mkdir -p /var/lib/irgsh/repo 90 | mkdir -p /var/log/irgsh 91 | 92 | # Install the files 93 | echo "Installing files ... " 94 | cp -v $TEMP_PATH/irgsh-go/usr/bin/* /usr/bin/ 95 | cp -v $TEMP_PATH/irgsh-go/usr/share/irgsh/init.sh /usr/bin/irgsh-init 96 | cp -vR $TEMP_PATH/irgsh-go/usr/share/irgsh/* /usr/share/irgsh/ 97 | cp -v $TEMP_PATH/irgsh-go/etc/init.d/* /etc/init.d/ 98 | systemctl daemon-reload 99 | # Configuration file 100 | if [ ! -f "/etc/irgsh/config.yml" ]; then 101 | cp -v $TEMP_PATH/irgsh-go/etc/irgsh/config.yml /etc/irgsh/config.yml 102 | fi 103 | # irgsh user 104 | #groupadd irgsh || true 105 | if getent passwd irgsh >/dev/null 2>&1; then 106 | echo "irgsh user is already exists" 107 | else 108 | useradd -d /var/lib/irgsh -s /bin/bash -G root -u 880 -U irgsh 109 | chown -R irgsh:irgsh /var/lib/irgsh 110 | chmod -R u+rw /var/lib/irgsh 111 | usermod -aG docker irgsh 112 | echo "irgsh user added to system" 113 | fi 114 | #usermod -aG irgsh irgsh 115 | echo "Installing files [OK]" 116 | echo 117 | 118 | if [ $DEV_INSTALL = 1 ]; then 119 | # For development/testing purpose 120 | GPG_KEY_NAME="BlankOn Project" 121 | GPG_KEY_EMAIL="blankon-dev@googlegroups.com" 122 | echo "Generating GPG key ..." 123 | su -c "mkdir -p /var/lib/irgsh/gnupg/private-keys-v1.d" -s /bin/bash irgsh 124 | su -c "echo 'export GNUPGHOME=/var/lib/irgsh/gnupg' > ~/.bashrc" -s /bin/bash irgsh 125 | su -c "echo 'cd ~/' >> ~/.bashrc" -s /bin/bash irgsh 126 | su -c "echo '%no-protection' > ~/gen-key-script" -s /bin/bash irgsh 127 | su -c "echo 'Key-Type: 1' >> ~//gen-key-script" -s /bin/bash irgsh 128 | su -c "echo 'Key-Length: 4096' >> ~//gen-key-script" -s /bin/bash irgsh 129 | su -c "echo 'Subkey-Type: 1' >> ~//gen-key-script" -s /bin/bash irgsh 130 | su -c "echo 'Subkey-Length: 4096' >> ~//gen-key-script" -s /bin/bash irgsh 131 | su -c "echo 'Name-Real: $GPG_KEY_NAME' >> ~//gen-key-script" -s /bin/bash irgsh 132 | su -c "echo 'Name-Email: $GPG_KEY_EMAIL' >> ~//gen-key-script" -s /bin/bash irgsh 133 | su -c "echo 'Expire-Date: 5y' >> ~//gen-key-script" -s /bin/bash irgsh 134 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg -k > /dev/null" -s /bin/bash irgsh 135 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg --batch --gen-key ~/gen-key-script > /dev/null" -s /bin/bash irgsh 136 | GPG_SIGN_KEY=$(su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg -K | grep uid -B 1 | head -n 1 | xargs" -s /bin/bash irgsh) 137 | sed -i "/dist_signing_key/c\ dist_signing_key: 'GPG_SIGN_KEY'" /etc/irgsh/config.yml 138 | sed -i "s/GPG_SIGN_KEY/$GPG_SIGN_KEY/g" /etc/irgsh/config.yml 139 | su -c "chmod -R 700 /var/lib/irgsh/gnupg" -s /bin/bash irgsh 140 | echo "Generating GPG key [OK]" 141 | gpg --armor --export >/tmp/pubkey 142 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg --import < /tmp/pubkey" -s /bin/bash irgsh 143 | 144 | # reinit repo 145 | su -c "irgsh-repo init > /dev/null" -s /bin/bash irgsh 146 | 147 | fi 148 | 149 | popd >/dev/null 150 | 151 | # Enable the services 152 | /lib/systemd/systemd-sysv-install enable irgsh-chief 153 | /lib/systemd/systemd-sysv-install enable irgsh-builder 154 | /lib/systemd/systemd-sysv-install enable irgsh-repo 155 | 156 | echo "Happy hacking!" 157 | -------------------------------------------------------------------------------- /utils/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Require sudo or root privilege 4 | if [ $EUID != 0 ]; then 5 | sudo "$0" "$@" 6 | exit $? 7 | fi 8 | 9 | # Doctor, check requirements 10 | 11 | OVERWRITE_WORKDIR=0 12 | OVERWRITE_GPG=0 13 | OVERWRITE_BASE_TGZ=0 14 | OVERWRITE_PBUILDER=0 15 | OVERWRITE_REPO=0 16 | 17 | if [ ! -f "/etc/irgsh/config.yml" ]; then 18 | cp -v /usr/share/irgsh/config.yml /etc/irgsh/config.yml 19 | fi 20 | 21 | echo "Before continue, please consider to review your onfiguration file (/etc/irgsh/config.yml)" 22 | read -p "Do you want to continue? (y/N) " -n 1 -r 23 | echo 24 | if [[ $REPLY =~ ^[Yy]$ ]]; then 25 | echo 26 | else 27 | exit 0 28 | fi 29 | 30 | if [ -d "/var/lib/irgsh" ]; then 31 | echo "Working dir (/var/lib/irgsh) is already exists." 32 | read -p "Do you want to recreate it? (y/N) " -n 1 -r 33 | echo 34 | if [[ $REPLY =~ ^[Yy]$ ]]; then 35 | OVERWRITE_WORKDIR=1 36 | OVERWRITE_REPO=1 37 | OVERWRITE_GPG=1 38 | echo 39 | echo "Please enter your GPG key configuration," 40 | read -p 'Name : ' GPG_KEY_NAME 41 | read -p 'Email : ' GPG_KEY_EMAIL 42 | else 43 | if [ ! -f "/var/lib/irgsh/repo/init.log" ]; then 44 | OVERWRITE_REPO=1 45 | fi 46 | if [ -d "/var/lib/irgsh/gnupg/private-keys-v1.d" ]; then 47 | echo 48 | echo "GPG key is already exists. Regenerating GPG key will also recreate your repository" 49 | read -p "Do you want to regenerate it? (y/N) " -n 1 -r 50 | echo 51 | if [[ $REPLY =~ ^[Yy]$ ]]; then 52 | if [ ! -f "/var/lib/irgsh/builder/pbocker/base.tgz" ]; then 53 | OVERWRITE_BASE_TGZ=1 54 | OVERWRITE_PBUILDER=1 55 | fi 56 | OVERWRITE_REPO=1 57 | OVERWRITE_GPG=1 58 | echo 59 | echo "Please enter your GPG key configuration," 60 | read -p 'Name : ' GPG_KEY_NAME 61 | read -p 'Email : ' GPG_KEY_EMAIL 62 | fi 63 | fi 64 | fi 65 | fi 66 | 67 | if [ ! -f "/var/lib/irgsh/builder/pbocker/base.tgz" ]; then 68 | OVERWRITE_BASE_TGZ=1 69 | OVERWRITE_PBUILDER=1 70 | else 71 | if [ "$(docker images | grep pbocker | cut -d ' ' -f 1)" = "pbocker" ]; then 72 | echo 73 | else 74 | OVERWRITE_PBUILDER=1 75 | fi 76 | fi 77 | 78 | if [ $OVERWRITE_REPO = 0 ]; then 79 | if [ $OVERWRITE_GPG = 0 ]; then 80 | echo 81 | read -p "Do you want to recreate the repository? (y/N) " -n 1 -r 82 | echo 83 | if [[ $REPLY =~ ^[Yy]$ ]]; then 84 | OVERWRITE_REPO=1 85 | fi 86 | fi 87 | fi 88 | 89 | # ==================================================================== 90 | echo 91 | echo 92 | echo "Please review your current init configuration:" 93 | echo "---------------------------" 94 | echo "OVERWRITE_WORKDIR=$OVERWRITE_WORKDIR" 95 | echo "OVERWRITE_BASE_TGZ=$OVERWRITE_BASE_TGZ" 96 | echo "OVERWRITE_PBUILDER=$OVERWRITE_PBUILDER" 97 | echo "OVERWRITE_REPO=$OVERWRITE_REPO" 98 | echo "OVERWRITE_GPG=$OVERWRITE_WORKDIR" 99 | echo "GPG_KEY_NAME=$GPG_KEY_NAME" 100 | echo "GPG_KEY_EMAIL=$GPG_KEY_EMAIL" 101 | echo 102 | read -p "Do you want to continue? (y/N) " -n 1 -r 103 | echo 104 | if [[ $REPLY =~ ^[Yy]$ ]]; then 105 | echo 106 | else 107 | exit 0 108 | fi 109 | echo 110 | echo 111 | # Working directory 112 | if [ $OVERWRITE_WORKDIR = 1 ]; then 113 | rm -rf /var/lib/irgsh 114 | mkdir -p /var/lib/irgsh/chief/submissions 115 | mkdir -p /var/lib/irgsh/chief/artifacts 116 | mkdir -p /var/lib/irgsh/chief/logs 117 | mkdir -p /var/lib/irgsh/builder 118 | mkdir -p /var/lib/irgsh/iso 119 | mkdir -p /var/lib/irgsh/repo 120 | fi 121 | 122 | chown -R irgsh:irgsh /var/lib/irgsh 123 | chmod -R u+rw /var/lib/irgsh 124 | 125 | # GPG key 126 | if [ $OVERWRITE_GPG = 1 ]; then 127 | echo "Generating GPG key ..." 128 | su -c "mkdir -p /var/lib/irgsh/gnupg/private-keys-v1.d" -s /bin/bash irgsh 129 | su -c "echo 'export GNUPGHOME=/var/lib/irgsh/gnupg' > ~/.bashrc" -s /bin/bash irgsh 130 | su -c "echo 'cd ~/' >> ~/.bashrc" -s /bin/bash irgsh 131 | su -c "echo '%no-protection' > ~/gen-key-script" -s /bin/bash irgsh 132 | su -c "echo 'Key-Type: 1' >> ~//gen-key-script" -s /bin/bash irgsh 133 | su -c "echo 'Key-Length: 4096' >> ~//gen-key-script" -s /bin/bash irgsh 134 | su -c "echo 'Subkey-Type: 1' >> ~//gen-key-script" -s /bin/bash irgsh 135 | su -c "echo 'Subkey-Length: 4096' >> ~//gen-key-script" -s /bin/bash irgsh 136 | su -c "echo 'Name-Real: $GPG_KEY_NAME' >> ~//gen-key-script" -s /bin/bash irgsh 137 | su -c "echo 'Name-Email: $GPG_KEY_EMAIL' >> ~//gen-key-script" -s /bin/bash irgsh 138 | su -c "echo 'Expire-Date: 5y' >> ~//gen-key-script" -s /bin/bash irgsh 139 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg -k > /dev/null" -s /bin/bash irgsh 140 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg --batch --gen-key ~/gen-key-script > /dev/null" -s /bin/bash irgsh 141 | GPG_SIGN_KEY=$(su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg -K | grep uid -B 1 | head -n 1 | xargs" -s /bin/bash irgsh) 142 | sed -i "/dist_signing_key/c\ dist_signing_key: 'GPG_SIGN_KEY'" /etc/irgsh/config.yml 143 | sed -i "s/GPG_SIGN_KEY/$GPG_SIGN_KEY/g" /etc/irgsh/config.yml 144 | su -c "chmod -R 700 /var/lib/irgsh/gnupg" -s /bin/bash irgsh 145 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg gpg -K" -s /bin/bash irgsh 146 | echo "Generating GPG key [OK]" 147 | fi 148 | 149 | # Repo init 150 | if [ $OVERWRITE_REPO = 1 ]; then 151 | su -c "GNUPGHOME=/var/lib/irgsh/gnupg irgsh-repo init" -s /bin/bash irgsh 152 | fi 153 | 154 | # Base.tgz init 155 | if [ $OVERWRITE_BASE_TGZ = 1 ]; then 156 | irgsh-builder init-base 157 | fi 158 | 159 | # Pbuilder init 160 | if [ $OVERWRITE_PBUILDER = 1 ]; then 161 | echo 162 | su -c "irgsh-builder init-builder" -s /bin/bash irgsh 163 | fi 164 | 165 | if [ $OVERWRITE_GPG = 1 ]; then 166 | echo 167 | echo "IMPORTANT: Do not forget add maintaner's public key(s) to irgsh's GPG keystore" 168 | echo "Initialization done!" 169 | fi 170 | -------------------------------------------------------------------------------- /utils/reprepro-template/conf/changelogs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is an example script that can be hooked into reprepro 3 | # to generate a hierachy like packages.debian.org/changelogs/ 4 | # All you have to do is to copy it into you conf/ directory, 5 | # and add the following to any distribution in conf/distributions 6 | # you want to have changelogs and copyright files extracted: 7 | #Log: 8 | # --type=dsc changelogs.example 9 | # (note the space at the beginning of the second line). 10 | # This will cause this script to extract changelogs for all 11 | # newly added source packages. (To generate them for already 12 | # existing packages, call "reprepro rerunnotifiers"). 13 | 14 | # DEPENDENCIES: dpkg >= 1.13.9 15 | 16 | if test "x${REPREPRO_OUT_DIR:+set}" = xset ; then 17 | # Note: due to cd, REPREPRO_*_DIR will no longer 18 | # be usable. And only things relative to outdir will work... 19 | # (as the filekeys given to this are, though for changesfiles 20 | # and CAUSING_FILE it would be different) 21 | cd "${REPREPRO_OUT_DIR}" || exit 1 22 | else 23 | cat "This script is written for reprepro 3.5.1 upwoards" >&2 24 | cat "for an older version, disable that warning and add" >&2 25 | cat "a proper cd or always use from the basedir" >&2 26 | exit 1 27 | fi 28 | 29 | # You will either always have to call it with the basedir as 30 | # your current directory, use reprepro >= 3.5.1 31 | # or uncomment and edit the following to be your base directory. 32 | 33 | # cd /path/to/your/repository 34 | 35 | # place where the changelogs should be put into: 36 | 37 | CHANGELOGDIR=changelogs 38 | 39 | # Set to avoid using some predefined TMPDIR or even /tmp as 40 | # tempdir: 41 | 42 | # TMPDIR=/var/cache/whateveryoucreated 43 | 44 | addsource() { 45 | DSCFILE="$1" 46 | CANONDSCFILE="$(readlink --canonicalize "$DSCFILE")" 47 | TARGETDIR="$CHANGELOGDIR"/"$(echo $DSCFILE | sed -e 's/\.dsc$//')" 48 | SUBDIR="$(basename $TARGETDIR)" 49 | BASEDIR="$(dirname $TARGETDIR)" 50 | if ! [ -d "$TARGETDIR" ] ; then 51 | echo "extract $CANONDSCFILE information to $TARGETDIR" 52 | mkdir -p -- "$TARGETDIR" 53 | EXTRACTDIR="$(mktemp -d)" 54 | (cd -- "$EXTRACTDIR" && dpkg-source -sn -x "$CANONDSCFILE" > /dev/null) 55 | install -D -- "$EXTRACTDIR"/*/debian/copyright "$TARGETDIR/copyright" 56 | install -D -- "$EXTRACTDIR"/*/debian/changelog "$TARGETDIR/changelog" 57 | chmod -R u+rwX -- "$EXTRACTDIR" 58 | rm -r -- "$EXTRACTDIR" 59 | fi 60 | if [ -L "$BASEDIR"/current."$CODENAME" ] ; then 61 | # should not be there, just to be sure 62 | rm -f -- "$BASEDIR"/current."$CODENAME" 63 | fi 64 | # mark this as needed by this distribution 65 | ln -s -- "$SUBDIR" "$BASEDIR/current.$CODENAME" 66 | JUSTADDED="$TARGETDIR" 67 | } 68 | delsource() { 69 | DSCFILE="$1" 70 | TARGETDIR=changelogs/"$(echo $DSCFILE | sed -e 's/\.dsc$//')" 71 | SUBDIR="$(basename $TARGETDIR)" 72 | BASEDIR="$(dirname $TARGETDIR)" 73 | if [ "x$JUSTADDED" = "x$TARGETDIR" ] ; then 74 | exit 0 75 | fi 76 | # echo "delete, basedir=$BASEDIR targetdir=$TARGETDIR, dscfile=$DSCFILE, " 77 | if [ "x$(readlink "$BASEDIR/current.$CODENAME")" = "x$SUBDIR" ] ; then 78 | rm -- "$BASEDIR/current.$CODENAME" 79 | fi 80 | NEEDED=0 81 | for c in "$BASEDIR"/current.* ; do 82 | if [ "x$(readlink -- "$c")" = "x$SUBDIR" ] ; then 83 | NEEDED=1 84 | fi 85 | done 86 | if [ "$NEEDED" -eq 0 -a -d "$TARGETDIR" ] ; then 87 | rm -r -- "$TARGETDIR" 88 | # to remove the directory if now empty 89 | rmdir --ignore-fail-on-non-empty -- "$BASEDIR" 90 | fi 91 | } 92 | 93 | ACTION="$1" 94 | CODENAME="$2" 95 | PACKAGETYPE="$3" 96 | if [ "x$PACKAGETYPE" != "xdsc" ] ; then 97 | # the --type=dsc should cause this to never happen, but better safe than sorry. 98 | exit 1 99 | fi 100 | COMPONENT="$4" 101 | ARCHITECTURE="$5" 102 | if [ "x$ARCHITECTURE" != "xsource" ] ; then 103 | exit 1 104 | fi 105 | NAME="$6" 106 | shift 6 107 | JUSTADDED="" 108 | if [ "x$ACTION" = "xadd" -o "x$ACTION" = "xinfo" ] ; then 109 | VERSION="$1" 110 | shift 111 | if [ "x$1" != "x--" ] ; then 112 | exit 2 113 | fi 114 | shift 115 | while [ "$#" -gt 0 ] ; do 116 | case "$1" in 117 | *.dsc) 118 | addsource "$1" 119 | ;; 120 | --) 121 | exit 2 122 | ;; 123 | esac 124 | shift 125 | done 126 | elif [ "x$ACTION" = "xremove" ] ; then 127 | OLDVERSION="$1" 128 | shift 129 | if [ "x$1" != "x--" ] ; then 130 | exit 2 131 | fi 132 | shift 133 | while [ "$#" -gt 0 ] ; do 134 | case "$1" in 135 | *.dsc) 136 | delsource "$1" 137 | ;; 138 | --) 139 | exit 2 140 | ;; 141 | esac 142 | shift 143 | done 144 | elif [ "x$ACTION" = "xreplace" ] ; then 145 | VERSION="$1" 146 | shift 147 | OLDVERSION="$1" 148 | shift 149 | if [ "x$1" != "x--" ] ; then 150 | exit 2 151 | fi 152 | shift 153 | while [ "$#" -gt 0 -a "x$1" != "x--" ] ; do 154 | case "$1" in 155 | *.dsc) 156 | addsource "$1" 157 | ;; 158 | esac 159 | shift 160 | done 161 | if [ "x$1" != "x--" ] ; then 162 | exit 2 163 | fi 164 | shift 165 | while [ "$#" -gt 0 ] ; do 166 | case "$1" in 167 | *.dsc) 168 | delsource "$1" 169 | ;; 170 | --) 171 | exit 2 172 | ;; 173 | esac 174 | shift 175 | done 176 | fi 177 | 178 | exit 0 179 | # Copyright 2007,2008 Bernhard R. Link 180 | # 181 | # This program is free software; you can redistribute it and/or modify 182 | # it under the terms of the GNU General Public License version 2 as 183 | # published by the Free Software Foundation. 184 | # 185 | # This program is distributed in the hope that it will be useful, 186 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 187 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 188 | # GNU General Public License for more details. 189 | # 190 | # You should have received a copy of the GNU General Public License 191 | # along with this program; if not, write to the Free Software 192 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA 193 | -------------------------------------------------------------------------------- /cmd/builder/builder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/blankon/irgsh-go/pkg/systemutil" 10 | ) 11 | 12 | func uploadLog(logPath string, id string) { 13 | // Upload the log to chief 14 | cmdStr := "curl -v -F 'uploadFile=@" + logPath + "' '" 15 | cmdStr += irgshConfig.Chief.Address + "/api/v1/log-upload?id=" + id + "&type=build'" 16 | _, err := systemutil.CmdExec( 17 | cmdStr, 18 | "Uploading log file to chief", 19 | "", 20 | ) 21 | if err != nil { 22 | fmt.Println(err.Error()) 23 | } 24 | } 25 | 26 | // Main task wrapper 27 | func Build(payload string) (next string, err error) { 28 | in := []byte(payload) 29 | var raw map[string]interface{} 30 | json.Unmarshal(in, &raw) 31 | 32 | fmt.Println("Processing pipeline :" + raw["taskUUID"].(string)) 33 | 34 | logPath := irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) + "/build.log" 35 | go systemutil.StreamLog(logPath) 36 | 37 | next, err = BuildPreparation(payload) 38 | if err != nil { 39 | uploadLog(logPath, raw["taskUUID"].(string)) 40 | return 41 | } 42 | 43 | next, err = BuildPackage(payload) 44 | if err != nil { 45 | uploadLog(logPath, raw["taskUUID"].(string)) 46 | return 47 | } 48 | 49 | next, err = StorePackage(payload) 50 | 51 | if err != nil { 52 | uploadLog(logPath, raw["taskUUID"].(string)) 53 | return 54 | } 55 | 56 | uploadLog(logPath, raw["taskUUID"].(string)) 57 | 58 | fmt.Println("Done.") 59 | 60 | return 61 | } 62 | 63 | func BuildPreparation(payload string) (next string, err error) { 64 | in := []byte(payload) 65 | var raw map[string]interface{} 66 | json.Unmarshal(in, &raw) 67 | 68 | buildPath := irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 69 | logPath := buildPath + "/build.log" 70 | 71 | targetDir := irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 72 | err = os.MkdirAll(targetDir, 0755) 73 | if err != nil { 74 | log.Printf("error: %v\n", err) 75 | return 76 | } 77 | 78 | target := irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) + "/debuild.tar.gz" 79 | // Downloading the submission tarball from chief 80 | cmdStr := "curl -v -o " + target + " " 81 | cmdStr += irgshConfig.Chief.Address + "/submissions/" + raw["taskUUID"].(string) + ".tar.gz" 82 | log.Println(cmdStr) 83 | _, err = systemutil.CmdExec( 84 | cmdStr, 85 | "Fetching the submission tarball from chief", 86 | logPath, 87 | ) 88 | if err != nil { 89 | log.Printf("error: %v\n", err) 90 | return 91 | } 92 | 93 | // Extract the signed dsc 94 | cmdStr = "cd " + irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 95 | cmdStr += " && tar -xvf debuild.tar.gz " 96 | cmdStr += " && rm -rf debuild.tar.gz " 97 | _, err = systemutil.CmdExec( 98 | cmdStr, 99 | "Backup the maintainer tarball and its signature", 100 | logPath, 101 | ) 102 | if err != nil { 103 | log.Printf("error: %v\n", err) 104 | return 105 | } 106 | 107 | next = payload 108 | return 109 | } 110 | 111 | func BuildPackage(payload string) (next string, err error) { 112 | in := []byte(payload) 113 | var raw map[string]interface{} 114 | json.Unmarshal(in, &raw) 115 | 116 | buildPath := irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 117 | err = os.MkdirAll(buildPath, 0755) 118 | if err != nil { 119 | log.Printf("error: %v\n", err) 120 | return 121 | } 122 | 123 | logPath := buildPath + "/build.log" 124 | 125 | packageNameVersion := raw["packageName"].(string) + "-" + raw["packageVersion"].(string) 126 | if len(raw["packageExtendedVersion"].(string)) > 0 { 127 | packageNameVersion += "-" + raw["packageExtendedVersion"].(string) 128 | } 129 | 130 | // Copy the maintainer's generated files from signed dir 131 | cmdStr := "cd " + irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 132 | cmdStr += " && cp signed/* ." 133 | log.Println(cmdStr) 134 | _, err = systemutil.CmdExec( 135 | cmdStr, 136 | "Copy the maintainer's generated files from signed dir.", 137 | logPath, 138 | ) 139 | if err != nil { 140 | log.Printf("error: %v\n", err) 141 | return 142 | } 143 | 144 | // Cleanup pbuilder cache result 145 | _, _ = systemutil.CmdExec( 146 | "rm -rf /var/cache/pbuilder/result/*", 147 | "", 148 | "", 149 | ) 150 | 151 | // Building the package 152 | cmdStr = "docker run -v " + irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 153 | cmdStr += ":/tmp/build --privileged=true -i pbocker bash -c /build.sh" // See builder/init.go to modify this script 154 | fmt.Println(cmdStr) 155 | _, err = systemutil.CmdExec( 156 | cmdStr, 157 | "Building the package", 158 | logPath, 159 | ) 160 | if err != nil { 161 | log.Println(err.Error()) 162 | return 163 | } 164 | 165 | // Use the generated files from maintainer 166 | if len(raw["sourceUrl"].(string)) > 0 { 167 | cmdStr := "cd " + irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) 168 | cmdStr += " && cp signed/* . " 169 | log.Println(cmdStr) 170 | _, err = systemutil.CmdExec( 171 | cmdStr, 172 | "Use the generated files from maintainer.", 173 | logPath, 174 | ) 175 | if err != nil { 176 | log.Printf("error: %v\n", err) 177 | return 178 | } 179 | } 180 | 181 | next = payload 182 | return 183 | } 184 | 185 | func StorePackage(payload string) (next string, err error) { 186 | in := []byte(payload) 187 | var raw map[string]interface{} 188 | json.Unmarshal(in, &raw) 189 | 190 | logPath := irgshConfig.Builder.Workdir + "/artifacts/" + raw["taskUUID"].(string) + "/build.log" 191 | 192 | cmdStr := "cd " + irgshConfig.Builder.Workdir + "/artifacts/ && " 193 | cmdStr += "tar -zcvf " + raw["taskUUID"].(string) + ".tar.gz " + raw["taskUUID"].(string) 194 | cmdStr += " && curl -v -F 'uploadFile=@" + irgshConfig.Builder.Workdir 195 | cmdStr += "/artifacts/" + raw["taskUUID"].(string) + ".tar.gz' " 196 | cmdStr += irgshConfig.Chief.Address + "/api/v1/artifact-upload?id=" 197 | cmdStr += raw["taskUUID"].(string) 198 | _, err = systemutil.CmdExec( 199 | cmdStr, 200 | "", 201 | logPath, 202 | ) 203 | if err != nil { 204 | log.Printf("error: %v\n", err) 205 | return 206 | } 207 | 208 | next = payload 209 | return 210 | } 211 | -------------------------------------------------------------------------------- /cmd/builder/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/blankon/irgsh-go/pkg/systemutil" 9 | "github.com/google/uuid" 10 | "github.com/manifoldco/promptui" 11 | ) 12 | 13 | func InitBase() (err error) { 14 | logPath := irgshConfig.Builder.Workdir 15 | logPath += "/irgsh-builder-init-base-" + uuid.New().String() + ".log" 16 | go systemutil.StreamLog(logPath) 17 | 18 | cmdStr := "lsb_release -a | grep Distributor | cut -d ':' -f 2 | awk '{print $1=$1;1}'" 19 | distribution, _ := systemutil.CmdExec( 20 | cmdStr, 21 | "", 22 | logPath, 23 | ) 24 | 25 | // TODO base.tgz file name should be based on distribution code name 26 | fmt.Println("WARNING: This subcommand need to be run under root or sudo.") 27 | prompt := promptui.Prompt{ 28 | Label: "irgsh-builder init-base will create (or recreate if already exists) the pbuilder base.tgz on your system and may took long time to be complete. Are you sure?", 29 | IsConfirm: true, 30 | } 31 | result, promptErr := prompt.Run() 32 | // Avoid shadowed err 33 | err = promptErr 34 | if err != nil { 35 | return 36 | } 37 | if strings.ToLower(result) != "y" { 38 | return 39 | } 40 | 41 | fmt.Println("Installing and preparing pbuilder and friends...") 42 | 43 | cmdStr = "apt-get update && apt-get install -y pbuilder debootstrap devscripts equivs" 44 | _, err = systemutil.CmdExec( 45 | cmdStr, 46 | "Preparing pbuilder and it's dependencies", 47 | logPath, 48 | ) 49 | if err != nil { 50 | fmt.Printf("error: %v\n", err) 51 | return 52 | } 53 | 54 | cmdStr = "rm /var/cache/pbuilder/base*" 55 | _, _ = systemutil.CmdExec( 56 | cmdStr, 57 | "", 58 | logPath, 59 | ) 60 | 61 | if err != nil { 62 | fmt.Printf("error: %v\n", err) 63 | return 64 | } 65 | cmdStr = "pbuilder create --debootstrapopts --variant=buildd" 66 | if strings.Contains(irgshConfig.Repo.UpstreamDistUrl, "debian") && (strings.Contains(distribution, "Ubuntu") || 67 | strings.Contains(distribution, "Pop")) { 68 | _, err = systemutil.CmdExec( 69 | "apt-get update && apt-get -y install debian-archive-keyring", 70 | "Creating pbuilder base.tgz", 71 | logPath, 72 | ) 73 | if err != nil { 74 | fmt.Printf("error: %v\n", err) 75 | return 76 | } 77 | cmdStr = "pbuilder create --distribution " + irgshConfig.Repo.UpstreamDistCodename + " --mirror " + irgshConfig.Repo.UpstreamDistUrl + " --debootstrapopts \"--keyring=/usr/share/keyrings/debian-archive-keyring.gpg\"" 78 | } 79 | _, err = systemutil.CmdExec( 80 | cmdStr, 81 | "Creating pbuilder base.tgz", 82 | logPath, 83 | ) 84 | if err != nil { 85 | fmt.Printf("error: %v\n", err) 86 | return 87 | } 88 | 89 | cmdStr = "pbuilder update" 90 | _, err = systemutil.CmdExec( 91 | cmdStr, 92 | "Updating base.tgz", 93 | logPath, 94 | ) 95 | if err != nil { 96 | fmt.Printf("error: %v\n", err) 97 | return 98 | } 99 | 100 | cmdStr = "chmod a+rw /var/cache/pbuilder/base*" 101 | _, err = systemutil.CmdExec( 102 | cmdStr, 103 | "Fixing permission for /var/cache/pbuilder/base*", 104 | logPath, 105 | ) 106 | if err != nil { 107 | fmt.Printf("error: %v\n", err) 108 | return 109 | } 110 | 111 | if irgshConfig.IsDev { 112 | cwd, _ := os.Getwd() 113 | tmpDir := cwd + "/tmp/" 114 | cmdStr = "chmod -vR 777 " + tmpDir 115 | _, err = systemutil.CmdExec( 116 | cmdStr, 117 | "Fixing permission for "+tmpDir, 118 | logPath, 119 | ) 120 | if err != nil { 121 | fmt.Printf("error: %v\n", err) 122 | return 123 | } 124 | } 125 | 126 | fmt.Println("Done.") 127 | 128 | return 129 | } 130 | 131 | func UpdateBase() (err error) { 132 | fmt.Println("WARNING: This subcommand need to be run under root or sudo.") 133 | logPath := "/tmp/irgsh-builder-update-base-" + uuid.New().String() + ".log" 134 | go systemutil.StreamLog(logPath) 135 | 136 | fmt.Println("Updating base.tgz...") 137 | cmdStr := "sudo pbuilder update" 138 | _, err = systemutil.CmdExec( 139 | cmdStr, 140 | "Updating base.tgz", 141 | logPath, 142 | ) 143 | if err != nil { 144 | fmt.Printf("error: %v\n", err) 145 | return 146 | } 147 | 148 | fmt.Println("Done.") 149 | 150 | return 151 | } 152 | 153 | func InitBuilder() (err error) { 154 | logPath := irgshConfig.Builder.Workdir 155 | logPath += "/irgsh-builder-init-" + uuid.New().String() + ".log" 156 | go systemutil.StreamLog(logPath) 157 | 158 | fmt.Println("Preparing containerized pbuilder...") 159 | 160 | cmdStr := `mkdir -p ` + irgshConfig.Builder.Workdir + `/pbocker && \ 161 | cp /var/cache/pbuilder/base.tgz ` + irgshConfig.Builder.Workdir + `/pbocker/base.tgz` 162 | _, err = systemutil.CmdExec( 163 | cmdStr, 164 | "Copying base.tgz", 165 | logPath, 166 | ) 167 | if err != nil { 168 | fmt.Printf("error: %v\n", err) 169 | return 170 | } 171 | 172 | // build.sh script is written here. 173 | // We're only taking the *.deb and *.buildinfo (if any) files from pbuilder result 174 | cmdStr = `echo 'FROM debian:latest' > ` + irgshConfig.Builder.Workdir + `/pbocker/Dockerfile && \ 175 | echo 'RUN apt-get update && apt-get -y install pbuilder' >> ` + irgshConfig.Builder.Workdir + `/pbocker/Dockerfile && \ 176 | echo 'RUN echo "MIRRORSITE=` + irgshConfig.Builder.UpstreamDistUrl + `" > /root/.pbuilderrc' >> ` + irgshConfig.Builder.Workdir + `/pbocker/Dockerfile && \ 177 | echo 'RUN echo "USENETWORK=yes"' >> ` + irgshConfig.Builder.Workdir + `/pbocker/Dockerfile && \ 178 | echo 'COPY base.tgz /var/cache/pbuilder/' >> ` + irgshConfig.Builder.Workdir + `/pbocker/Dockerfile && \ 179 | echo 'RUN echo "pbuilder --build /tmp/build/*.dsc \n cp -vR /var/cache/pbuilder/result/*.deb /tmp/build/ \n cp -vR /var/cache/pbuilder/result/*.buildinfo /tmp/build/ || true" > /build.sh && chmod a+x /build.sh' >> ` + irgshConfig.Builder.Workdir + `/pbocker/Dockerfile` 180 | _, err = systemutil.CmdExec( 181 | cmdStr, 182 | "Preparing Dockerfile", 183 | logPath, 184 | ) 185 | if err != nil { 186 | fmt.Printf("error: %v\n", err) 187 | return 188 | } 189 | 190 | cmdStr = `cd ` + irgshConfig.Builder.Workdir + 191 | `/pbocker && docker build --no-cache -t pbocker .` 192 | _, err = systemutil.CmdExec( 193 | cmdStr, 194 | "Building pbocker docker image", 195 | logPath, 196 | ) 197 | if err != nil { 198 | fmt.Printf("error: %v\n", err) 199 | return 200 | } 201 | 202 | fmt.Println("Done.") 203 | 204 | return 205 | } 206 | -------------------------------------------------------------------------------- /GPG-EN.md: -------------------------------------------------------------------------------- 1 | ## Persiapan kunci penanda tangan paket 2 | 3 | Keaslian paket di lumbung turunan Debian dibantu oleh verifikasi tanda tangan digital dengan kunci GPG (itu sebabnya alamat lumbung tersebut tidak perlu lagi dilindungi oleh HTTPS/TLS, lihat https://whydoesaptnotusehttps.com/). Kita memerlukan kunci GPG untuk menandatangani paket-paket nantinya. Setelah dibuat sesuai panduan di bawah ini, kunci-kunci ini akan tersimpan di `/.gnugpg`. 4 | 5 | ### Mempersiapkan `rng` untuk mempercepat generate entropy 6 | 7 | ``` 8 | $ sudo apt-get install rng-tools 9 | $ sudo rngd -r /dev/urandom 10 | ``` 11 | 12 | ### Membuat kunci GPG utama. 13 | 14 | Abaikan permintaan `passphrase` untuk menunjang otomasi penandatanganan paket. 15 | 16 | - `gpg --full-generate-key` 17 | ``` 18 | gpg (GnuPG) 2.1.18; Copyright (C) 2017 Free Software Foundation, Inc. 19 | This is free software: you are free to change and redistribute it. 20 | There is NO WARRANTY, to the extent permitted by law. 21 | 22 | Please select what kind of key you want: 23 | (1) RSA and RSA (default) 24 | (2) DSA and Elgamal 25 | (3) DSA (sign only) 26 | (4) RSA (sign only) 27 | Your selection? 1 28 | RSA keys may be between 1024 and 4096 bits long. 29 | What keysize do you want? (2048) 4096 30 | Requested keysize is 4096 bits 31 | Please specify how long the key should be valid. 32 | 0 = key does not expire 33 | = key expires in n days 34 | w = key expires in n weeks 35 | m = key expires in n months 36 | y = key expires in n years 37 | Key is valid for? (0) 5y 38 | Key expires at Wed Jan 24 04:58:41 2024 EST 39 | Is this correct? (y/N) y 40 | 41 | GnuPG needs to construct a user ID to identify your key. 42 | 43 | Real name: BlankOn Developer 44 | Email address: blankon-dev@googlegroups.com 45 | Comment: 46 | You selected this USER-ID: 47 | "BlankOn Developer " 48 | 49 | Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O 50 | We need to generate a lot of random bytes. It is a good idea to perform 51 | some other action (type on the keyboard, move the mouse, utilize the 52 | disks) during the prime generation; this gives the random number 53 | generator a better chance to gain enough entropy. 54 | 55 | gpg: key 17963DC67219B965 marked as ultimately trusted 56 | gpg: revocation certificate stored as '/home/arsipdev-reboot/.gnupg/openpgp-revocs.d/9584C1230204D624A15D215117963DC67219B965.rev' 57 | public and secret key created and signed. 58 | pub rsa4096 2019-01-25 [SC] [expires: 2024-01-24] 59 | 9584C1230204D624A15D215117963DC67219B965 60 | 9584C1230204D624A15D215117963DC67219B965 61 | uid BlankOn Developer 62 | sub rsa4096 2019-01-25 [E] [expires: 2024-01-24] 63 | ``` 64 | 65 | ### Membuat sub kunci untuk keperluan penandatanganan paket 66 | 67 | Parameternya adalah identitas kunci master. 68 | 69 | - `gpg --edit-key 05657D94F29BDACB99F6CE7D0B352C08D746A9A6` 70 | ``` 71 | gpg (GnuPG) 2.1.18; Copyright (C) 2017 Free Software Foundation, Inc. 72 | This is free software: you are free to change and redistribute it. 73 | There is NO WARRANTY, to the extent permitted by law. 74 | 75 | Secret key is available. 76 | 77 | gpg: checking the trustdb 78 | gpg: marginals needed: 3 completes needed: 1 trust model: pgp 79 | gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u 80 | gpg: next trustdb check due at 2021-01-24 81 | sec rsa2048/0B352C08D746A9A6 82 | created: 2019-01-25 expires: 2021-01-24 usage: SC 83 | trust: ultimate validity: ultimate 84 | ssb rsa2048/BE8FF591E6569748 85 | created: 2019-01-25 expires: 2021-01-24 usage: E 86 | [ultimate] (1). BlankOn Developer 87 | 88 | gpg> addkey 89 | Please select what kind of key you want: 90 | (3) DSA (sign only) 91 | (4) RSA (sign only) 92 | (5) Elgamal (encrypt only) 93 | (6) RSA (encrypt only) 94 | Your selection? 4 95 | RSA keys may be between 1024 and 4096 bits long. 96 | What keysize do you want? (2048) 4096 97 | Requested keysize is 4096 bits 98 | Please specify how long the key should be valid. 99 | 0 = key does not expire 100 | = key expires in n days 101 | w = key expires in n weeks 102 | m = key expires in n months 103 | y = key expires in n years 104 | Key is valid for? (0) 5y 105 | Key expires at Wed Jan 24 05:06:05 2024 EST 106 | Is this correct? (y/N) y 107 | Really create? (y/N) y 108 | 109 | We need to generate a lot of random bytes. It is a good idea to perform 110 | some other action (type on the keyboard, move the mouse, utilize the 111 | disks) during the prime generation; this gives the random number 112 | generator a better chance to gain enough entropy. 113 | 114 | sec rsa2048/0B352C08D746A9A6 115 | created: 2019-01-25 expires: 2021-01-24 usage: SC 116 | trust: ultimate validity: ultimate 117 | ssb rsa2048/BE8FF591E6569748 118 | created: 2019-01-25 expires: 2021-01-24 usage: E 119 | ssb rsa4096/1C608FE2ECC8842B 120 | created: 2019-01-25 expires: 2024-01-24 usage: S 121 | [ultimate] (1). BlankOn Developer 122 | 123 | gpg> save 124 | ``` 125 | 126 | Identitas kunci anak ini (string `0B352C08D746A9A6`) yang akan dipakai di konfigurasi lumbung nantinya. 127 | 128 | ### Memisahkan kunci master 129 | 130 | Tujuan penggunaan subkey dan pemisahan kunci master adalah supaya bila kunci tanda tangan terkena kompromi, kunci penanda tangan baru masih bisa diterbitkan dan paket lama masih bisa diverifikasi. 131 | 132 | ``` 133 | $ gpg --armor --export-secret-key 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 > private.key 134 | $ gpg --armor --export 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 >> private.key 135 | ``` 136 | 137 | Simpan berkas `private.key` ini ke tempat yang aman. 138 | 139 | Pisahkan kunci publik master dan kunci privat anak. 140 | 141 | ``` 142 | $ gpg --armor --export 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 > public.key 143 | $ gpg --armor --export-secret-subkeys 0B352C08D746A9A6 > signing.key 144 | ``` 145 | Hapus kunci privat master dari `gnupg`. 146 | 147 | ``` 148 | $ gpg --delete-secret-key 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 149 | ``` 150 | 151 | Impor kembali kunci publik master dan kunci privat anak. 152 | 153 | ``` 154 | $ gpg --import public.key signing.key 155 | ``` 156 | 157 | Pastikan kunci privat master sudah tidak terdaftar di `gnupg`. 158 | 159 | ``` 160 | $ gpg --list-secret-keys 161 | /home/arsipdev/.gnupg/pubring.kbx 162 | ---------------------------------------- 163 | sec# rsa4096 2019-01-25 [SC] [expires: 2024-01-24] 164 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 165 | uid [ultimate] BlankOn Developer 166 | ssb rsa4096 2019-01-25 [E] [expires: 2024-01-24] 167 | ssb rsa4096 2019-01-25 [S] [expires: 2024-01-24] 168 | ``` 169 | 170 | Simbol # setelah `sec` menandakan tidak ada kunci privat master di `gnupg`. 171 | -------------------------------------------------------------------------------- /GPG-ID.md: -------------------------------------------------------------------------------- 1 | ## Persiapan kunci penanda tangan paket 2 | 3 | Keaslian paket di lumbung turunan Debian dibantu oleh verifikasi tanda tangan digital dengan kunci GPG (itu sebabnya alamat lumbung tersebut tidak perlu lagi dilindungi oleh HTTPS/TLS, lihat https://whydoesaptnotusehttps.com/). Kita memerlukan kunci GPG untuk menandatangani paket-paket nantinya. Setelah dibuat sesuai panduan di bawah ini, kunci-kunci ini akan tersimpan di `/.gnugpg`. 4 | 5 | ### Mempersiapkan `rng` untuk mempercepat generate entropy 6 | 7 | ``` 8 | $ sudo apt-get install rng-tools 9 | $ sudo rngd -r /dev/urandom 10 | ``` 11 | 12 | ### Membuat kunci GPG utama. 13 | 14 | Abaikan permintaan `passphrase` untuk menunjang otomasi penandatanganan paket. 15 | 16 | - `gpg --full-generate-key` 17 | ``` 18 | gpg (GnuPG) 2.1.18; Copyright (C) 2017 Free Software Foundation, Inc. 19 | This is free software: you are free to change and redistribute it. 20 | There is NO WARRANTY, to the extent permitted by law. 21 | 22 | Please select what kind of key you want: 23 | (1) RSA and RSA (default) 24 | (2) DSA and Elgamal 25 | (3) DSA (sign only) 26 | (4) RSA (sign only) 27 | Your selection? 1 28 | RSA keys may be between 1024 and 4096 bits long. 29 | What keysize do you want? (2048) 4096 30 | Requested keysize is 4096 bits 31 | Please specify how long the key should be valid. 32 | 0 = key does not expire 33 | = key expires in n days 34 | w = key expires in n weeks 35 | m = key expires in n months 36 | y = key expires in n years 37 | Key is valid for? (0) 5y 38 | Key expires at Wed Jan 24 04:58:41 2024 EST 39 | Is this correct? (y/N) y 40 | 41 | GnuPG needs to construct a user ID to identify your key. 42 | 43 | Real name: BlankOn Developer 44 | Email address: blankon-dev@googlegroups.com 45 | Comment: 46 | You selected this USER-ID: 47 | "BlankOn Developer " 48 | 49 | Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O 50 | We need to generate a lot of random bytes. It is a good idea to perform 51 | some other action (type on the keyboard, move the mouse, utilize the 52 | disks) during the prime generation; this gives the random number 53 | generator a better chance to gain enough entropy. 54 | 55 | gpg: key 17963DC67219B965 marked as ultimately trusted 56 | gpg: revocation certificate stored as '/home/arsipdev-reboot/.gnupg/openpgp-revocs.d/9584C1230204D624A15D215117963DC67219B965.rev' 57 | public and secret key created and signed. 58 | pub rsa4096 2019-01-25 [SC] [expires: 2024-01-24] 59 | 9584C1230204D624A15D215117963DC67219B965 60 | 9584C1230204D624A15D215117963DC67219B965 61 | uid BlankOn Developer 62 | sub rsa4096 2019-01-25 [E] [expires: 2024-01-24] 63 | ``` 64 | 65 | ### Membuat sub kunci untuk keperluan penandatanganan paket 66 | 67 | Parameternya adalah identitas kunci master. 68 | 69 | - `gpg --edit-key 05657D94F29BDACB99F6CE7D0B352C08D746A9A6` 70 | ``` 71 | gpg (GnuPG) 2.1.18; Copyright (C) 2017 Free Software Foundation, Inc. 72 | This is free software: you are free to change and redistribute it. 73 | There is NO WARRANTY, to the extent permitted by law. 74 | 75 | Secret key is available. 76 | 77 | gpg: checking the trustdb 78 | gpg: marginals needed: 3 completes needed: 1 trust model: pgp 79 | gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u 80 | gpg: next trustdb check due at 2021-01-24 81 | sec rsa2048/0B352C08D746A9A6 82 | created: 2019-01-25 expires: 2021-01-24 usage: SC 83 | trust: ultimate validity: ultimate 84 | ssb rsa2048/BE8FF591E6569748 85 | created: 2019-01-25 expires: 2021-01-24 usage: E 86 | [ultimate] (1). BlankOn Developer 87 | 88 | gpg> addkey 89 | Please select what kind of key you want: 90 | (3) DSA (sign only) 91 | (4) RSA (sign only) 92 | (5) Elgamal (encrypt only) 93 | (6) RSA (encrypt only) 94 | Your selection? 4 95 | RSA keys may be between 1024 and 4096 bits long. 96 | What keysize do you want? (2048) 4096 97 | Requested keysize is 4096 bits 98 | Please specify how long the key should be valid. 99 | 0 = key does not expire 100 | = key expires in n days 101 | w = key expires in n weeks 102 | m = key expires in n months 103 | y = key expires in n years 104 | Key is valid for? (0) 5y 105 | Key expires at Wed Jan 24 05:06:05 2024 EST 106 | Is this correct? (y/N) y 107 | Really create? (y/N) y 108 | 109 | We need to generate a lot of random bytes. It is a good idea to perform 110 | some other action (type on the keyboard, move the mouse, utilize the 111 | disks) during the prime generation; this gives the random number 112 | generator a better chance to gain enough entropy. 113 | 114 | sec rsa2048/0B352C08D746A9A6 115 | created: 2019-01-25 expires: 2021-01-24 usage: SC 116 | trust: ultimate validity: ultimate 117 | ssb rsa2048/BE8FF591E6569748 118 | created: 2019-01-25 expires: 2021-01-24 usage: E 119 | ssb rsa4096/1C608FE2ECC8842B 120 | created: 2019-01-25 expires: 2024-01-24 usage: S 121 | [ultimate] (1). BlankOn Developer 122 | 123 | gpg> save 124 | ``` 125 | 126 | Identitas kunci anak ini (string `0B352C08D746A9A6`) yang akan dipakai di konfigurasi lumbung nantinya. 127 | 128 | ### Memisahkan kunci master 129 | 130 | Tujuan penggunaan subkey dan pemisahan kunci master adalah supaya bila kunci tanda tangan terkena kompromi, kunci penanda tangan baru masih bisa diterbitkan dan paket lama masih bisa diverifikasi. 131 | 132 | ``` 133 | $ gpg --armor --export-secret-key 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 > private.key 134 | $ gpg --armor --export 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 >> private.key 135 | ``` 136 | 137 | Simpan berkas `private.key` ini ke tempat yang aman. 138 | 139 | Pisahkan kunci publik master dan kunci privat anak. 140 | 141 | ``` 142 | $ gpg --armor --export 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 > public.key 143 | $ gpg --armor --export-secret-subkeys 0B352C08D746A9A6 > signing.key 144 | ``` 145 | Hapus kunci privat master dari `gnupg`. 146 | 147 | ``` 148 | $ gpg --delete-secret-key 05657D94F29BDACB99F6CE7D0B352C08D746A9A6 149 | ``` 150 | 151 | Impor kembali kunci publik master dan kunci privat anak. 152 | 153 | ``` 154 | $ gpg --import public.key signing.key 155 | ``` 156 | 157 | Pastikan kunci privat master sudah tidak terdaftar di `gnupg`. 158 | 159 | ``` 160 | $ gpg --list-secret-keys 161 | /home/arsipdev/.gnupg/pubring.kbx 162 | ---------------------------------------- 163 | sec# rsa4096 2019-01-25 [SC] [expires: 2024-01-24] 164 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 165 | uid [ultimate] BlankOn Developer 166 | ssb rsa4096 2019-01-25 [E] [expires: 2024-01-24] 167 | ssb rsa4096 2019-01-25 [S] [expires: 2024-01-24] 168 | ``` 169 | 170 | Simbol # setelah `sec` menandakan tidak ada kunci privat master di `gnupg`. 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # irgsh-go 2 | 3 | [![Actions Status](https://github.com/BlankOn/irgsh-go/workflows/master/badge.svg)](https://github.com/BlankOn/irgsh-go/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/BlankOn/irgsh-go)](https://goreportcard.com/report/github.com/BlankOn/irgsh-go) [![codecov](https://codecov.io/gh/BlankOn/irgsh-go/branch/master/graph/badge.svg)](https://codecov.io/gh/BlankOn/irgsh-go) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/BlankOn/irgsh-go) 4 | 5 | IRGSH (https://groups.google.com/d/msg/blankon-dev/yvceclWjSw8/HZUL_m6-BS4J, pronunciation: *irgis*) is an all-in-one tool to create and maintain Debian-derived GNU/Linux distribution: from packaging to repository, from ISO build to release management. This codebase is a complete rewrite of the old IRGSH components (https://github.com/BlankOn?q=irgsh). 6 | 7 | This is still under heavy development, therefore you should not rely on this for production since it still subject to breaking API changes. 8 | 9 | Patches, suggestions and comments are welcome! 10 | 11 | ## Complete installation 12 | 13 | 14 | You need Docker, Redis and these packages, 15 | 16 | ``` 17 | gpg pbuilder debootstrap devscripts curl reprepro dh-make 18 | ``` 19 | 20 | Install all deps + released IRGSH with this command bellow, 21 | 22 | ``` 23 | curl -L -o- https://raw.githubusercontent.com/BlankOn/irgsh-go/master/install.sh | bash 24 | ``` 25 | 26 | The command will install the irgsh binaries, default configuration and daemons. A spesial user named `irgsh` will also be added to your system. 27 | 28 | ## Package maintainer installation 29 | 30 | ``` 31 | curl -L -o- https://raw.githubusercontent.com/BlankOn/irgsh-go/master/install-cli.sh | bash 32 | ``` 33 | 34 | The command will install the irgsh-cli binary to your system. 35 | 36 | ## Package maintainer update 37 | 38 | Package maintainer can update their irgsh-cli by running the same command as installation or using `sudo irgsh-cli update` to let irgsh-cli updates itself. 39 | 40 | ## Components 41 | 42 | A minimal IRGSH ecosystem contains three services and a CLI tool. 43 | 44 | - `irgsh-chief` acts as the master. The others (also applied to`irgsh-cli`) will talk to the chief. The chief also provides a web user interface for workers and pipelines monitoring. 45 | - `irgsh-builder` is the builder worker of IRGSH. 46 | - `irgsh-repo` will serves as repository so it may need huge volume of storage. 47 | - `irgsh-cli` is a client-side tool to maintain packages. 48 | 49 | ### Architecture 50 | 51 | 52 | 53 | 54 | 55 | GPG signature is used as authentication bearer on any submission attempt. Hence, you will need to register maintainer's public key to `irgsh`'s GPG keystore (read Initial setup). 56 | 57 | ## Initial setup 58 | 59 | #### IRGSH 60 | 61 | Please refer to `/etc/irgsh/config.yml` for available preferences. Change it as you need. 62 | 63 | ``` 64 | irgsh-init 65 | ``` 66 | 67 | #### CLI tool 68 | 69 | This CLI tool intended to be used on maintainer's local system. It need to be configured first to define the `irgsh-chief` instance address and your GPG key as package mantainer, 70 | 71 | ``` 72 | irgsh-cli config --chief http://irgsh.blankonlinux.or.id:8080 --key B113D905C417D9C31DAD9F0E509A356412B6E77F 73 | ``` 74 | 75 | #### Registering a maintainer 76 | 77 | A maintaner should have a GPG keypair on his machine. His/her public key need to be registered to `irgsh`'s GPG key store. 78 | 79 | Export maintainer's GPG public key 80 | 81 | ``` 82 | gpg --armor --export 0D7D9A42E03ACFA2933227F7A13769F4DB99B6CD > /path/to/maintainer-public.key 83 | ``` 84 | 85 | The `public.key` file need to be transfered into where `irgsh-chief` live. Then import the maintainer's public key (on behalf of `irgsh`), 86 | 87 | ``` 88 | gpg --import < /path/to/maintainer-public.key 89 | ``` 90 | 91 | You can check the list of registered maintainer (on behalf of `irgsh`), 92 | 93 | ``` 94 | gpg -k 95 | ``` 96 | 97 | ## Run 98 | 99 | #### The Services 100 | 101 | You can start them with, 102 | 103 | ``` 104 | sudo systemctl start irgsh-chief 105 | sudo systemctl start irgsh-builder 106 | sudo systemctl start irgsh-repo 107 | ``` 108 | Their logs are available at `/var/log/irgsh/`. After these three services are up and running, you may continue to work with `irgsh-cli` 109 | 110 | #### CLI 111 | 112 | Submit a package, 113 | 114 | ``` 115 | irgsh-cli submit --source https://github.com/BlankOn/bromo-theme.git --package https://github.com/BlankOn-packages/bromo-theme.git 116 | ``` 117 | 118 | Check the status of a pipeline, 119 | 120 | ``` 121 | irgsh-cli status 2019-04-01-174135_1ddbb9fe-0517-4cb0-9096-640f17532cf9 122 | ``` 123 | 124 | Inspect the log of a pipeline, 125 | 126 | ``` 127 | irgsh-cli log 2019-04-01-174135_1ddbb9fe-0517-4cb0-9096-640f17532cf9 128 | ``` 129 | 130 | Running `irgsh-cli status` and `irgsh-cli log` without argument will referenced to the latest submitted pipeline ID. 131 | 132 | ## FAQ 133 | 134 | ### Why rewrite it? 135 | 136 | IRGSH was written in Python 2.6.x and it depends on some old and deprecated libraries. Even one of them (in a specific version, respectively) is no longer exists on the internet. A real dependency hell. It’s hard to deploy IRGSH in a modern operating system and it keeps alynne.blankonlinux.or.id from an important system upgrade. The IRGSH was also very modular but combining them into a working distributed cluster takes time and quite steep learning curve. We still need to prepare a lot of things manually before doing that. Pbuilder needs to be configured and setup. Which also true for the reprepro repository. No easy way. 137 | 138 | Although, there is no doubt that the old IRGSH does its work well. 139 | 140 | ### Why Go? 141 | 142 | For its portable compiled binary. 143 | 144 | ### Can I run the workers on different machines? 145 | 146 | You can. Just make sure these workers pointed out to the same Redis server (see `/etc/irgsh/config.yml`). Also please consider this, https://redis.io/topics/security. 147 | 148 | ### Why is Docker required? 149 | 150 | To build a package using `pbuilder`, `sudo` or root privilege is required but it's not okay to rely on root privilege for repetitive tasks. To get rid of this, we containerized the build process. 151 | 152 | ## Troubleshooting notes 153 | 154 | ### No secret key 155 | 156 | The error message may be like this, 157 | ``` 158 | gpg: skipped "41B4FC0A57E7F7F8DD94E0AA2D21BB5FAA32AF3F": No secret key 159 | gpg: /tmp/debsign.QNCSozgK/blankon-keyring_2016.09.04-4.2.dsc: clear-sign failed: No secret key 160 | debsign: gpg error occurred! Aborting.... 161 | debuild: fatal error at line 1112: 162 | running debsign failed 163 | ``` 164 | 165 | It may caused either by: 166 | 167 | - Your defined key for signing is not available on your GPG keystore. Please redefine it with `rgsh-cli config --chief https://irgsh.blankonlinux.or.id --key YOURKEYIDENTITY` 168 | - The latest maintainer information that written in the debian/changelog does not matched with the one on your GPG keystore. Please adjust the changelog file. 169 | 170 | ## Todos 171 | 172 | ### CLI 173 | 174 | - Submit :heavy_check_mark: 175 | - GPG signing :heavy_check_mark: 176 | - Logging :heavy_check_mark: 177 | 178 | ### Chief 179 | 180 | - Auth (GPG or mutual auth) :heavy_check_mark: 181 | - WebSocket 182 | - Builder registration 183 | - Repo registration 184 | 185 | ### Builder 186 | 187 | - Init: 188 | - base.tgz :heavy_check_mark: 189 | - Clone :heavy_check_mark: 190 | - Signing :heavy_check_mark: 191 | - Build :heavy_check_mark: 192 | - Upload :heavy_check_mark: 193 | - Dockerized pbuilder :heavy_check_mark: 194 | - Multiarch support 195 | - RPM support 196 | 197 | ### Repo 198 | 199 | - Init :heavy_check_mark: 200 | - Sync :heavy_check_mark: 201 | - Download :heavy_check_mark: 202 | - Inject :heavy_check_mark: 203 | - Rebuild repo :heavy_check_mark: 204 | - Multiarch support 205 | - RPM support 206 | 207 | ### PabrikCD 208 | 209 | - Build 210 | - Upload 211 | 212 | ### Release management 213 | 214 | - Release cycle (RC, alpha, beta, final) 215 | - Patches/Updates after release 216 | 217 | ### Others 218 | 219 | - No sudo needed :heavy_check_mark: 220 | - Daemonized instances :heavy_check_mark: 221 | - Dockerized instances (docker-compose) 222 | -------------------------------------------------------------------------------- /cmd/repo/repo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/blankon/irgsh-go/pkg/systemutil" 11 | "github.com/manifoldco/promptui" 12 | ) 13 | 14 | func uploadLog(logPath string, id string) { 15 | // Upload the log to chief 16 | cmdStr := "curl -v -F 'uploadFile=@" + logPath + "' '" + irgshConfig.Chief.Address + "/api/v1/log-upload?id=" + id + "&type=repo'" 17 | fmt.Println(cmdStr) 18 | _, err := systemutil.CmdExec( 19 | cmdStr, 20 | "Uploading log file to chief", 21 | "", 22 | ) 23 | if err != nil { 24 | fmt.Println(err.Error()) 25 | } 26 | } 27 | 28 | // Main task wrapper 29 | func Repo(payload string) (err error) { 30 | fmt.Println("##### Submitting the package into the repository") 31 | in := []byte(payload) 32 | var raw map[string]interface{} 33 | json.Unmarshal(in, &raw) 34 | 35 | experimentalSuffix := "-experimental" 36 | if !raw["isExperimental"].(bool) { 37 | experimentalSuffix = "" 38 | } 39 | 40 | logPath := irgshConfig.Repo.Workdir + "/artifacts/" 41 | logPath += raw["taskUUID"].(string) + "/repo.log" 42 | go systemutil.StreamLog(logPath) 43 | 44 | cmdStr := fmt.Sprintf(`mkdir -p %s/artifacts && \ 45 | cd %s/artifacts/ && \ 46 | wget %s/artifacts/%s.tar.gz && \ 47 | tar -xvf %s.tar.gz`, 48 | irgshConfig.Repo.Workdir, 49 | irgshConfig.Repo.Workdir, 50 | irgshConfig.Chief.Address, 51 | raw["taskUUID"].(string), 52 | raw["taskUUID"].(string), 53 | ) 54 | _, err = systemutil.CmdExec(cmdStr, "Downloading the artifact", logPath) 55 | if err != nil { 56 | fmt.Printf("error: %v\n", err) 57 | uploadLog(logPath, raw["taskUUID"].(string)) 58 | return 59 | } 60 | 61 | gnupgDir := "GNUPGHOME=" + irgshConfig.Repo.GnupgDir 62 | if irgshConfig.IsDev { 63 | gnupgDir = "" 64 | } 65 | if raw["isExperimental"].(bool) { 66 | // Ignore version conflict 67 | cmdStr = fmt.Sprintf(`cd %s/%s/ && \ 68 | %s reprepro -v -v -v --nothingiserror remove %s \ 69 | $(cat %s/artifacts/%s/*.dsc | grep 'Source:' | cut -d ' ' -f 2)`, 70 | irgshConfig.Repo.Workdir, 71 | irgshConfig.Repo.DistCodename+experimentalSuffix, 72 | gnupgDir, 73 | irgshConfig.Repo.DistCodename+experimentalSuffix, 74 | irgshConfig.Repo.Workdir, 75 | raw["taskUUID"], 76 | ) 77 | _, err := systemutil.CmdExec( 78 | cmdStr, 79 | "This is experimental package, remove any existing package.", 80 | logPath, 81 | ) 82 | if err != nil { 83 | // Ignore err 84 | fmt.Printf("error: %v\n", err) 85 | } 86 | } 87 | 88 | // Injecting changes 89 | ignoreDistribution := "" 90 | if raw["isExperimental"].(bool) { 91 | ignoreDistribution = "--ignore=wrongdistribution" 92 | } 93 | 94 | cmdStr = fmt.Sprintf(`cd %s/%s/ && \ 95 | %s reprepro -v -v -v --nothingiserror --ignore=missingfile %s --component %s include %s %s/artifacts/%s/*source.changes`, 96 | irgshConfig.Repo.Workdir, 97 | irgshConfig.Repo.DistCodename+experimentalSuffix, 98 | gnupgDir, 99 | ignoreDistribution, 100 | raw["component"], 101 | irgshConfig.Repo.DistCodename+experimentalSuffix, 102 | irgshConfig.Repo.Workdir, 103 | raw["taskUUID"], 104 | ) 105 | 106 | _, err = systemutil.CmdExec( 107 | cmdStr, 108 | "Injecting the changes file from artifact to the repository", 109 | logPath, 110 | ) 111 | if err != nil { 112 | fmt.Printf("error: %v\n", err) 113 | uploadLog(logPath, raw["taskUUID"].(string)) 114 | return 115 | } 116 | 117 | // Injecting the package 118 | cmdStr = fmt.Sprintf(`cd %s/%s/ && \ 119 | %s reprepro -v -v -v --nothingiserror --component %s includedeb %s %s/artifacts/%s/*.deb`, 120 | irgshConfig.Repo.Workdir, 121 | irgshConfig.Repo.DistCodename+experimentalSuffix, 122 | gnupgDir, 123 | raw["component"], 124 | irgshConfig.Repo.DistCodename+experimentalSuffix, 125 | irgshConfig.Repo.Workdir, 126 | raw["taskUUID"], 127 | ) 128 | 129 | _, err = systemutil.CmdExec( 130 | cmdStr, 131 | "Injecting the deb files from artifact to the repository", 132 | logPath, 133 | ) 134 | if err != nil { 135 | fmt.Printf("error: %v\n", err) 136 | uploadLog(logPath, raw["taskUUID"].(string)) 137 | return 138 | } 139 | 140 | cmdStr = fmt.Sprintf("cd %s/%s/ && reprepro -v -v -v export", 141 | irgshConfig.Repo.Workdir, 142 | irgshConfig.Repo.DistCodename, 143 | ) 144 | _, err = systemutil.CmdExec( 145 | cmdStr, 146 | "Re-export and publish the reprepro repository", 147 | logPath, 148 | ) 149 | if err != nil { 150 | fmt.Printf("error: %v\n", err) 151 | return 152 | } 153 | 154 | uploadLog(logPath, raw["taskUUID"].(string)) 155 | fmt.Println("[ BUILD DONE ]") 156 | return 157 | } 158 | 159 | func InitRepo() (err error) { 160 | prompt := promptui.Prompt{ 161 | Label: "Are you sure you want to initialize new repository? Any existing distribution will be flushed.", 162 | IsConfirm: true, 163 | } 164 | result, err := prompt.Run() 165 | if err != nil { 166 | return 167 | } 168 | if strings.ToLower(result) != "y" { 169 | return 170 | } 171 | 172 | // TODO ask for matched distribution name as this command is super dangerous 173 | // Prepare workdir 174 | err = os.MkdirAll(irgshConfig.Repo.Workdir, 0755) 175 | if err != nil { 176 | log.Fatalln(err) 177 | } 178 | 179 | fmt.Println("##### Initializing new repository for " + irgshConfig.Repo.DistCodename) 180 | 181 | logPath := irgshConfig.Repo.Workdir + "/init.log" 182 | go systemutil.StreamLog(logPath) 183 | 184 | repoTemplatePath := "/usr/share/irgsh/reprepro-template" 185 | if irgshConfig.IsDev { 186 | cwd, _ := os.Getwd() 187 | repoTemplatePath = cwd + "/utils/reprepro-template" 188 | } else if irgshConfig.IsTest { 189 | dir, _ := os.Getwd() 190 | repoTemplatePath = dir + "/../utils/reprepro-template" 191 | } 192 | cmdStr := fmt.Sprintf("mkdir -p %s && rm -rf %s/%s; cp -R %s %s/%s", 193 | irgshConfig.Repo.Workdir, 194 | irgshConfig.Repo.Workdir, 195 | irgshConfig.Repo.DistCodename, 196 | repoTemplatePath, 197 | irgshConfig.Repo.Workdir, 198 | irgshConfig.Repo.DistCodename, 199 | ) 200 | _, err = systemutil.CmdExec(cmdStr, "Preparing reprepro template", logPath) 201 | if err != nil { 202 | fmt.Printf("error: %v\n", err) 203 | return 204 | } 205 | 206 | cmdStr = fmt.Sprintf(`cd %s/%s/conf && cat updates.orig | 207 | sed 's/UPSTREAM_NAME/%s/g' | 208 | sed 's/UPSTREAM_DIST_CODENAME/%s/g' | 209 | sed 's/UPSTREAM_DIST_URL/%s/g' | 210 | sed 's/DIST_SUPPORTED_ARCHITECTURES/%s/g' | 211 | sed 's/UPSTREAM_DIST_COMPONENTS/%s/g' > updates && rm updates.orig`, 212 | irgshConfig.Repo.Workdir, 213 | irgshConfig.Repo.DistCodename, 214 | irgshConfig.Repo.UpstreamName, 215 | irgshConfig.Repo.UpstreamDistCodename, 216 | strings.Replace(irgshConfig.Repo.UpstreamDistUrl, "/", "\\/", -1), 217 | irgshConfig.Repo.DistSupportedArchitectures, 218 | irgshConfig.Repo.UpstreamDistComponents, 219 | ) 220 | _, err = systemutil.CmdExec( 221 | cmdStr, 222 | "Populate the reprepro's updates config file with values from irgsh's config.yml", 223 | logPath, 224 | ) 225 | if err != nil { 226 | fmt.Printf("error: %v\n", err) 227 | return 228 | } 229 | 230 | cmdStr = fmt.Sprintf(`cd %s/%s/conf && cat distributions.orig | 231 | sed 's/DIST_NAME/%s/g' | 232 | sed 's/DIST_LABEL/%s/g' | 233 | sed 's/DIST_CODENAME/%s/g' | 234 | sed 's/DIST_COMPONENTS/%s/g' | 235 | sed 's/DIST_SUPPORTED_ARCHITECTURES/%s/g' | 236 | sed 's/DIST_VERSION_DESC/%s/g' | 237 | sed 's/DIST_VERSION/%s/g' | 238 | sed 's/DIST_SIGNING_KEY/%s/g' | 239 | sed 's/UPSTREAM_NAME/%s/g'> distributions && rm distributions.orig`, 240 | irgshConfig.Repo.Workdir, 241 | irgshConfig.Repo.DistCodename, 242 | irgshConfig.Repo.DistName, 243 | irgshConfig.Repo.DistLabel, 244 | irgshConfig.Repo.DistCodename, 245 | irgshConfig.Repo.DistComponents, 246 | irgshConfig.Repo.DistSupportedArchitectures, 247 | irgshConfig.Repo.DistVersionDesc, 248 | irgshConfig.Repo.DistVersion, 249 | irgshConfig.Repo.DistSigningKey, 250 | irgshConfig.Repo.UpstreamName, 251 | ) 252 | _, err = systemutil.CmdExec( 253 | cmdStr, 254 | "Populate the reprepro's distributions config file with values from irgsh's config.yml", 255 | logPath, 256 | ) 257 | if err != nil { 258 | fmt.Printf("error: %v\n", err) 259 | return 260 | } 261 | 262 | repositoryPath := strings.Replace( 263 | irgshConfig.Repo.Workdir+"/"+irgshConfig.Repo.DistCodename, 264 | "/", 265 | "\\/", 266 | -1, 267 | ) 268 | cmdStr = fmt.Sprintf(`cd %s/%s/conf && \ 269 | cat options.orig | sed 's/IRGSH_REPO_WORKDIR/%s/g' > options && \ 270 | rm options.orig`, 271 | irgshConfig.Repo.Workdir, 272 | irgshConfig.Repo.DistCodename, 273 | repositoryPath, 274 | ) 275 | _, err = systemutil.CmdExec( 276 | cmdStr, 277 | "Populate the reprepro's options config file with values from irgsh's config.yml", 278 | logPath, 279 | ) 280 | if err != nil { 281 | fmt.Printf("error: %v\n", err) 282 | return 283 | } 284 | 285 | cmdStr = fmt.Sprintf("cd %s/%s/ && reprepro -v -v -v export", 286 | irgshConfig.Repo.Workdir, 287 | irgshConfig.Repo.DistCodename, 288 | ) 289 | _, err = systemutil.CmdExec( 290 | cmdStr, 291 | "Re-export and publish the reprepro repository", 292 | logPath, 293 | ) 294 | if err != nil { 295 | fmt.Printf("error: %v\n", err) 296 | return 297 | } 298 | 299 | fmt.Println("##### Initializing the experimental repository for " + irgshConfig.Repo.DistCodename) 300 | // With -experimental suffix 301 | 302 | cmdStr = fmt.Sprintf("mkdir -p %s && rm -rf %s/%s; cp -R %s %s/%s", 303 | irgshConfig.Repo.Workdir, 304 | irgshConfig.Repo.Workdir, 305 | irgshConfig.Repo.DistCodename+"-experimental", 306 | repoTemplatePath, 307 | irgshConfig.Repo.Workdir, 308 | irgshConfig.Repo.DistCodename+"-experimental", 309 | ) 310 | _, err = systemutil.CmdExec(cmdStr, "Preparing reprepro template", logPath) 311 | if err != nil { 312 | fmt.Printf("error: %v\n", err) 313 | return 314 | } 315 | 316 | cmdStr = fmt.Sprintf(`cd %s/%s/conf && cat updates.orig | 317 | sed 's/UPSTREAM_NAME/%s/g' | 318 | sed 's/UPSTREAM_DIST_CODENAME/%s/g' | 319 | sed 's/UPSTREAM_DIST_URL/%s/g' | 320 | sed 's/DIST_SUPPORTED_ARCHITECTURES/%s/g' | 321 | sed 's/UPSTREAM_DIST_COMPONENTS/%s/g' > updates && rm updates.orig`, 322 | irgshConfig.Repo.Workdir, 323 | irgshConfig.Repo.DistCodename+"-experimental", 324 | irgshConfig.Repo.UpstreamName, 325 | irgshConfig.Repo.UpstreamDistCodename+"-experimental", 326 | strings.Replace(irgshConfig.Repo.UpstreamDistUrl, "/", "\\/", -1), 327 | irgshConfig.Repo.DistSupportedArchitectures, 328 | irgshConfig.Repo.UpstreamDistComponents, 329 | ) 330 | _, err = systemutil.CmdExec( 331 | cmdStr, 332 | "Populate the reprepro's updates config file with values from irgsh's config.yml", 333 | logPath, 334 | ) 335 | if err != nil { 336 | fmt.Printf("error: %v\n", err) 337 | return 338 | } 339 | 340 | cmdStr = fmt.Sprintf(`cd %s/%s/conf && cat distributions.orig | 341 | sed 's/DIST_NAME/%s/g' | 342 | sed 's/DIST_LABEL/%s/g' | 343 | sed 's/DIST_CODENAME/%s/g' | 344 | sed 's/DIST_COMPONENTS/%s/g' | 345 | sed 's/DIST_SUPPORTED_ARCHITECTURES/%s/g' | 346 | sed 's/DIST_VERSION_DESC/%s/g' | 347 | sed 's/DIST_VERSION/%s/g' | 348 | sed 's/DIST_SIGNING_KEY/%s/g' | 349 | sed 's/UPSTREAM_NAME/%s/g'> distributions && rm distributions.orig`, 350 | irgshConfig.Repo.Workdir, 351 | irgshConfig.Repo.DistCodename+"-experimental", 352 | irgshConfig.Repo.DistName, 353 | irgshConfig.Repo.DistLabel, 354 | irgshConfig.Repo.DistCodename+"-experimental", 355 | irgshConfig.Repo.DistComponents, 356 | irgshConfig.Repo.DistSupportedArchitectures, 357 | irgshConfig.Repo.DistVersionDesc, 358 | irgshConfig.Repo.DistVersion, 359 | irgshConfig.Repo.DistSigningKey, 360 | irgshConfig.Repo.UpstreamName, 361 | ) 362 | _, err = systemutil.CmdExec( 363 | cmdStr, 364 | "Populate the reprepro's distributions config file with values from irgsh's config.yml", 365 | logPath, 366 | ) 367 | if err != nil { 368 | fmt.Printf("error: %v\n", err) 369 | return 370 | } 371 | 372 | repositoryPath = strings.Replace( 373 | irgshConfig.Repo.Workdir+"/"+irgshConfig.Repo.DistCodename+"-experimental", 374 | "/", 375 | "\\/", 376 | -1, 377 | ) 378 | cmdStr = fmt.Sprintf(`cd %s/%s/conf && \ 379 | cat options.orig | sed 's/IRGSH_REPO_WORKDIR/%s/g' > options && \ 380 | rm options.orig`, 381 | irgshConfig.Repo.Workdir, 382 | irgshConfig.Repo.DistCodename+"-experimental", 383 | repositoryPath, 384 | ) 385 | _, err = systemutil.CmdExec( 386 | cmdStr, 387 | "Populate the reprepro's options config file with values from irgsh's config.yml", 388 | logPath, 389 | ) 390 | if err != nil { 391 | fmt.Printf("error: %v\n", err) 392 | return 393 | } 394 | 395 | cmdStr = fmt.Sprintf("cd %s/%s/ && reprepro -v -v -v export", 396 | irgshConfig.Repo.Workdir, 397 | irgshConfig.Repo.DistCodename+"-experimental", 398 | ) 399 | _, err = systemutil.CmdExec( 400 | cmdStr, 401 | "Re-export and publish the reprepro repository", 402 | logPath, 403 | ) 404 | if err != nil { 405 | fmt.Printf("error: %v\n", err) 406 | return 407 | } 408 | 409 | return 410 | } 411 | 412 | func UpdateRepo() (err error) { 413 | fmt.Printf("Syncing irgshConfig.Repo.against %s at %s...", 414 | irgshConfig.Repo.UpstreamDistCodename, 415 | irgshConfig.Repo.UpstreamDistUrl, 416 | ) 417 | 418 | logPath := irgshConfig.Repo.Workdir + "/update.log" 419 | go systemutil.StreamLog(logPath) 420 | 421 | cmdStr := fmt.Sprintf("cd %s/%s/ && reprepro -v -v -v update > %s", 422 | irgshConfig.Repo.Workdir, 423 | irgshConfig.Repo.DistCodename, 424 | logPath, 425 | ) 426 | _, err = systemutil.CmdExec(cmdStr, "Sync the repository against upstream repository", logPath) 427 | if err != nil { 428 | fmt.Printf("error: %v\n", err) 429 | return 430 | } 431 | 432 | cmdStr = fmt.Sprintf("cd %s/%s/ && reprepro -v -v -v export", 433 | irgshConfig.Repo.Workdir, 434 | irgshConfig.Repo.DistCodename, 435 | ) 436 | _, err = systemutil.CmdExec( 437 | cmdStr, 438 | "Re-export and publish the reprepro repository", 439 | logPath, 440 | ) 441 | if err != nil { 442 | fmt.Printf("error: %v\n", err) 443 | return 444 | } 445 | 446 | return 447 | } 448 | -------------------------------------------------------------------------------- /cmd/chief/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | 15 | "github.com/RichardKnop/machinery/v1/backends/result" 16 | "github.com/RichardKnop/machinery/v1/tasks" 17 | "github.com/google/uuid" 18 | ) 19 | 20 | func indexHandler(w http.ResponseWriter, r *http.Request) { 21 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 22 | resp := "
" 23 | resp += " _       " 24 | resp += "        _
" 25 | resp += "(_)_ __ __ _ ___| |_
" 26 | resp += "| | '__/ _` / __| '_ \\
" 27 | resp += "| | | | (_| \\__ \\ | | |
" 28 | resp += "|_|_|  \\__, |___/_| |_|
" 29 | resp += "       |___/
" 30 | resp += "irgsh-chief " + app.Version 31 | resp += "
" 32 | resp += "
maintainers | " 33 | resp += "submissions | " 34 | resp += "logs | " 35 | resp += "artifacts | " 36 | resp += "about" 37 | resp += "
" 38 | fmt.Fprintf(w, resp) 39 | } 40 | 41 | func PackageSubmitHandler(w http.ResponseWriter, r *http.Request) { 42 | submission := Submission{} 43 | decoder := json.NewDecoder(r.Body) 44 | err := decoder.Decode(&submission) 45 | if err != nil { 46 | fmt.Println(err.Error()) 47 | w.WriteHeader(http.StatusBadRequest) 48 | fmt.Fprintf(w, "400") 49 | return 50 | } 51 | submission.Timestamp = time.Now() 52 | submission.TaskUUID = submission.Timestamp.Format("2006-01-02-150405") + "_" + uuid.New().String() + "_" + submission.MaintainerFingerprint + "_" + submission.PackageName 53 | 54 | // Verifying the signature against current gpg keyring 55 | cmdStr := "mkdir -p " + irgshConfig.Chief.Workdir + "/submissions/" + submission.TaskUUID 56 | fmt.Println(cmdStr) 57 | cmd := exec.Command("bash", "-c", cmdStr) 58 | err = cmd.Run() 59 | if err != nil { 60 | log.Println(err) 61 | w.WriteHeader(http.StatusInternalServerError) 62 | fmt.Fprintf(w, "500") 63 | return 64 | } 65 | 66 | src := irgshConfig.Chief.Workdir + "/submissions/" + submission.Tarball + ".tar.gz" 67 | path := irgshConfig.Chief.Workdir + "/submissions/" + submission.TaskUUID + ".tar.gz" 68 | err = Move(src, path) 69 | if err != nil { 70 | log.Println(err) 71 | w.WriteHeader(http.StatusInternalServerError) 72 | fmt.Fprintf(w, "500") 73 | return 74 | } 75 | 76 | cmdStr = "cd " + irgshConfig.Chief.Workdir + "/submissions/ " 77 | cmdStr += " && tar -xvf " + submission.TaskUUID + ".tar.gz -C " + submission.TaskUUID 78 | fmt.Println(cmdStr) 79 | err = exec.Command("bash", "-c", cmdStr).Run() 80 | if err != nil { 81 | log.Println(err) 82 | w.WriteHeader(http.StatusInternalServerError) 83 | fmt.Fprintf(w, "500") 84 | return 85 | } 86 | 87 | src = irgshConfig.Chief.Workdir + "/submissions/" + submission.Tarball + ".token" 88 | path = irgshConfig.Chief.Workdir + "/submissions/" + submission.TaskUUID + ".sig.txt" 89 | err = Move(src, path) 90 | if err != nil { 91 | log.Println(err) 92 | w.WriteHeader(http.StatusInternalServerError) 93 | fmt.Fprintf(w, "500") 94 | return 95 | } 96 | 97 | gnupgDir := "GNUPGHOME=" + irgshConfig.Chief.GnupgDir 98 | if irgshConfig.IsDev { 99 | gnupgDir = "" 100 | } 101 | 102 | cmdStr = "cd " + irgshConfig.Chief.Workdir + "/submissions/" + submission.TaskUUID + " && " 103 | cmdStr += gnupgDir + " gpg --verify signed/*.dsc" 104 | err = exec.Command("bash", "-c", cmdStr).Run() 105 | if err != nil { 106 | log.Println(err) 107 | w.WriteHeader(http.StatusUnauthorized) 108 | fmt.Fprintf(w, "401 Unauthorized") 109 | return 110 | } 111 | 112 | jsonStr, err := json.Marshal(submission) 113 | if err != nil { 114 | fmt.Println(err.Error()) 115 | w.WriteHeader(http.StatusBadRequest) 116 | fmt.Fprintf(w, "400") 117 | return 118 | } 119 | 120 | buildSignature := tasks.Signature{ 121 | Name: "build", 122 | UUID: submission.TaskUUID, 123 | Args: []tasks.Arg{ 124 | { 125 | Type: "string", 126 | Value: string(jsonStr), 127 | }, 128 | }, 129 | } 130 | 131 | repoSignature := tasks.Signature{ 132 | Name: "repo", 133 | UUID: submission.TaskUUID, 134 | } 135 | 136 | chain, _ := tasks.NewChain(&buildSignature, &repoSignature) 137 | _, err = server.SendChain(chain) 138 | if err != nil { 139 | fmt.Println("Could not send chain : " + err.Error()) 140 | } 141 | 142 | payload := SubmitPayloadResponse{PipelineId: submission.TaskUUID} 143 | jsonStr, _ = json.Marshal(payload) 144 | fmt.Fprintf(w, string(jsonStr)) 145 | 146 | } 147 | 148 | func BuildStatusHandler(w http.ResponseWriter, r *http.Request) { 149 | keys, ok := r.URL.Query()["uuid"] 150 | if !ok { 151 | w.WriteHeader(http.StatusBadRequest) 152 | fmt.Fprintf(w, "403") 153 | return 154 | } 155 | var UUID string 156 | UUID = keys[0] 157 | 158 | buildSignature := tasks.Signature{ 159 | Name: "build", 160 | UUID: UUID, 161 | Args: []tasks.Arg{ 162 | { 163 | Type: "string", 164 | Value: "xyz", 165 | }, 166 | }, 167 | } 168 | // Recreate the AsyncResult instance using the signature and server.backend 169 | car := result.NewAsyncResult(&buildSignature, server.GetBackend()) 170 | car.Touch() 171 | taskState := car.GetState() 172 | res := fmt.Sprintf("{ \"pipelineId\": \"" + taskState.TaskUUID + "\", \"state\": \"" + taskState.State + "\" }") 173 | fmt.Fprintf(w, res) 174 | } 175 | 176 | func artifactUploadHandler() http.HandlerFunc { 177 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 178 | var err error 179 | 180 | keys, ok := r.URL.Query()["id"] 181 | 182 | if !ok || len(keys[0]) < 1 { 183 | log.Println("Url Param 'uuid' is missing") 184 | w.WriteHeader(http.StatusBadRequest) 185 | return 186 | } 187 | 188 | id := keys[0] 189 | 190 | targetPath := irgshConfig.Chief.Workdir + "/artifacts" 191 | err = os.MkdirAll(targetPath, 0755) 192 | if err != nil { 193 | log.Println(err.Error()) 194 | w.WriteHeader(http.StatusInternalServerError) 195 | return 196 | } 197 | 198 | // parse and validate file and post parameters 199 | file, _, err := r.FormFile("uploadFile") 200 | if err != nil { 201 | log.Println(err.Error()) 202 | w.WriteHeader(http.StatusBadRequest) 203 | return 204 | } 205 | defer file.Close() 206 | fileBytes, err := ioutil.ReadAll(file) 207 | if err != nil { 208 | log.Println(err.Error()) 209 | w.WriteHeader(http.StatusBadRequest) 210 | return 211 | } 212 | 213 | // check file type, detectcontenttype only needs the first 512 bytes 214 | filetype := http.DetectContentType(fileBytes) 215 | switch filetype { 216 | case "application/gzip", "application/x-gzip": 217 | break 218 | default: 219 | log.Println("File upload rejected: should be a compressed tar.gz file.") 220 | w.WriteHeader(http.StatusBadRequest) 221 | } 222 | 223 | fileName := id + ".tar.gz" 224 | newPath := filepath.Join(targetPath, fileName) 225 | 226 | // write file 227 | newFile, err := os.Create(newPath) 228 | if err != nil { 229 | log.Println(err.Error()) 230 | w.WriteHeader(http.StatusInternalServerError) 231 | return 232 | } 233 | defer newFile.Close() 234 | if _, err := newFile.Write(fileBytes); err != nil || newFile.Close() != nil { 235 | log.Println(err.Error()) 236 | w.WriteHeader(http.StatusInternalServerError) 237 | return 238 | } 239 | 240 | // TODO should be in JSON string 241 | w.WriteHeader(http.StatusOK) 242 | }) 243 | } 244 | 245 | func logUploadHandler() http.HandlerFunc { 246 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 247 | var err error 248 | 249 | keys, ok := r.URL.Query()["id"] 250 | 251 | if !ok || len(keys[0]) < 1 { 252 | log.Println("Url Param 'id' is missing") 253 | w.WriteHeader(http.StatusBadRequest) 254 | return 255 | } 256 | 257 | id := keys[0] 258 | 259 | keys, ok = r.URL.Query()["type"] 260 | 261 | if !ok || len(keys[0]) < 1 { 262 | log.Println("Url Param 'type' is missing") 263 | w.WriteHeader(http.StatusBadRequest) 264 | return 265 | } 266 | 267 | logType := keys[0] 268 | 269 | targetPath := irgshConfig.Chief.Workdir + "/logs" 270 | err = os.MkdirAll(targetPath, 0755) 271 | if err != nil { 272 | log.Println(err.Error()) 273 | w.WriteHeader(http.StatusInternalServerError) 274 | return 275 | } 276 | 277 | // parse and validate file and post parameters 278 | file, _, err := r.FormFile("uploadFile") 279 | if err != nil { 280 | log.Println(err.Error()) 281 | w.WriteHeader(http.StatusBadRequest) 282 | return 283 | } 284 | defer file.Close() 285 | fileBytes, err := ioutil.ReadAll(file) 286 | if err != nil { 287 | log.Println(err.Error()) 288 | w.WriteHeader(http.StatusBadRequest) 289 | return 290 | } 291 | 292 | // check file type, detectcontenttype only needs the first 512 bytes 293 | filetype := strings.Split(http.DetectContentType(fileBytes), ";")[0] 294 | switch filetype { 295 | case "text/plain": 296 | break 297 | default: 298 | log.Println("File upload rejected: should be a plain text log file.") 299 | w.WriteHeader(http.StatusBadRequest) 300 | } 301 | 302 | fileName := id + "." + logType + ".log" 303 | newPath := filepath.Join(targetPath, fileName) 304 | 305 | // write file 306 | newFile, err := os.Create(newPath) 307 | if err != nil { 308 | log.Println(err.Error()) 309 | w.WriteHeader(http.StatusInternalServerError) 310 | return 311 | } 312 | defer newFile.Close() 313 | if _, err := newFile.Write(fileBytes); err != nil || newFile.Close() != nil { 314 | log.Println(err.Error()) 315 | w.WriteHeader(http.StatusInternalServerError) 316 | return 317 | } 318 | 319 | // TODO should be in JSON string 320 | w.WriteHeader(http.StatusOK) 321 | }) 322 | } 323 | 324 | func BuildISOHandler(w http.ResponseWriter, r *http.Request) { 325 | fmt.Println("iso") 326 | signature := tasks.Signature{ 327 | Name: "iso", 328 | UUID: uuid.New().String(), 329 | Args: []tasks.Arg{ 330 | { 331 | Type: "string", 332 | Value: "iso-specific-value", 333 | }, 334 | }, 335 | } 336 | // TODO grab the asyncResult here 337 | _, err := server.SendTask(&signature) 338 | if err != nil { 339 | w.WriteHeader(http.StatusInternalServerError) 340 | fmt.Println("Could not send task : " + err.Error()) 341 | fmt.Fprintf(w, "500") 342 | } 343 | // TODO should be in JSON string 344 | w.WriteHeader(http.StatusOK) 345 | } 346 | 347 | func submissionUploadHandler() http.HandlerFunc { 348 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 349 | var err error 350 | 351 | targetPath := irgshConfig.Chief.Workdir + "/submissions" 352 | err = os.MkdirAll(targetPath, 0755) 353 | if err != nil { 354 | log.Println(err.Error()) 355 | w.WriteHeader(http.StatusInternalServerError) 356 | return 357 | } 358 | 359 | // Check for auth token first 360 | file, _, err := r.FormFile("token") 361 | if err != nil { 362 | log.Println(err.Error()) 363 | w.WriteHeader(http.StatusBadRequest) 364 | return 365 | } 366 | defer file.Close() 367 | fileBytes, err := ioutil.ReadAll(file) 368 | if err != nil { 369 | log.Println(err.Error()) 370 | w.WriteHeader(http.StatusBadRequest) 371 | return 372 | } 373 | // write file 374 | id := uuid.New().String() 375 | fileName := id + ".token" 376 | newPath := filepath.Join(targetPath, fileName) 377 | newFile, err := os.Create(newPath) 378 | if err != nil { 379 | log.Println(err.Error()) 380 | w.WriteHeader(http.StatusInternalServerError) 381 | return 382 | } 383 | defer newFile.Close() 384 | if _, err := newFile.Write(fileBytes); err != nil || newFile.Close() != nil { 385 | log.Println(err.Error()) 386 | w.WriteHeader(http.StatusInternalServerError) 387 | return 388 | } 389 | 390 | gnupgDir := "GNUPGHOME=" + irgshConfig.Chief.GnupgDir 391 | if irgshConfig.IsDev { 392 | gnupgDir = "" 393 | } 394 | 395 | cmdStr := "cd " + targetPath + " && " 396 | cmdStr += gnupgDir + " gpg --verify " + newPath 397 | err = exec.Command("bash", "-c", cmdStr).Run() 398 | if err != nil { 399 | log.Println(err) 400 | w.WriteHeader(http.StatusUnauthorized) 401 | fmt.Fprintf(w, "401 Unauthorized") 402 | return 403 | } 404 | 405 | // parse and validate file and post parameters 406 | file, _, err = r.FormFile("blob") 407 | if err != nil { 408 | log.Println(err.Error()) 409 | w.WriteHeader(http.StatusBadRequest) 410 | return 411 | } 412 | defer file.Close() 413 | fileBytes, err = ioutil.ReadAll(file) 414 | if err != nil { 415 | log.Println(err.Error()) 416 | w.WriteHeader(http.StatusBadRequest) 417 | return 418 | } 419 | 420 | // check file type, detectcontenttype only needs the first 512 bytes 421 | filetype := strings.Split(http.DetectContentType(fileBytes), ";")[0] 422 | log.Println(filetype) 423 | if !strings.Contains(filetype, "gzip") { 424 | log.Println("File upload rejected: should be a tar.gz file.") 425 | w.WriteHeader(http.StatusBadRequest) 426 | } 427 | fileName = id + ".tar.gz" 428 | newPath = filepath.Join(targetPath, fileName) 429 | 430 | // write file 431 | newFile, err = os.Create(newPath) 432 | if err != nil { 433 | log.Println(err.Error()) 434 | w.WriteHeader(http.StatusInternalServerError) 435 | return 436 | } 437 | defer newFile.Close() 438 | if _, err := newFile.Write(fileBytes); err != nil || newFile.Close() != nil { 439 | log.Println(err.Error()) 440 | w.WriteHeader(http.StatusInternalServerError) 441 | return 442 | } 443 | 444 | w.WriteHeader(http.StatusOK) 445 | fmt.Fprintf(w, "{\"id\":\""+id+"\"}") 446 | }) 447 | } 448 | 449 | func MaintainersHandler(w http.ResponseWriter, r *http.Request) { 450 | gnupgDir := "GNUPGHOME=" + irgshConfig.Chief.GnupgDir 451 | if irgshConfig.IsDev { 452 | gnupgDir = "" 453 | } 454 | 455 | cmdStr := gnupgDir + " gpg --list-key | tail -n +2" 456 | 457 | output, err := exec.Command("bash", "-c", cmdStr).Output() 458 | if err != nil { 459 | log.Println(err) 460 | w.WriteHeader(http.StatusInternalServerError) 461 | fmt.Fprintf(w, "500") 462 | return 463 | } 464 | fmt.Fprintf(w, string(output)) 465 | } 466 | 467 | func VersionHandler(w http.ResponseWriter, r *http.Request) { 468 | fmt.Fprintf(w, "{\"version\":\""+app.Version+"\"}") 469 | } 470 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.31.0 h1:o9K5MWWt2wk+d9jkGn2DAZ7Q9nUdnFLOpK9eIkDwONQ= 3 | cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 5 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/RichardKnop/logging v0.0.0-20180729160517-75cec7213f7c h1:F617MLa8qKTMzu0OV/vdy1QiCihA7etWZBZUHLkZrks= 8 | github.com/RichardKnop/logging v0.0.0-20180729160517-75cec7213f7c/go.mod h1:GN1ovZ77t2jiz0kTaWhgtQe271GODCgheqxlxGt7wIo= 9 | github.com/RichardKnop/machinery v1.5.5 h1:Z5Ge5nZasLcy6DyC1tevZFo9RmpMjQ4dQxKrRV6YQtE= 10 | github.com/RichardKnop/machinery v1.5.5/go.mod h1:nZh5Q14McSl+er5moTFI5Tho1TjhAN2U0yWHEbh23Vk= 11 | github.com/RichardKnop/redsync v1.2.0 h1:gK35hR3zZkQigHKm8wOGb9MpJ9BsrW6MzxezwjTcHP0= 12 | github.com/RichardKnop/redsync v1.2.0/go.mod h1:9b8nBGAX3bE2uCfJGSnsDvF23mKyHTZzmvmj5FH3Tp0= 13 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 14 | github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= 15 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 16 | github.com/aws/aws-sdk-go v1.15.66 h1:c2ScQzjFUoD1pK+6GQzX8yMXwppIjP5Oy8ljMRr2cbo= 17 | github.com/aws/aws-sdk-go v1.15.66/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= 18 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 19 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= 20 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 21 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 22 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 23 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 24 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 25 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= 29 | github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 30 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 31 | github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= 32 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 33 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 34 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 35 | github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= 36 | github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= 37 | github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= 38 | github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= 39 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 40 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 41 | github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 42 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 43 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 44 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= 46 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 47 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 48 | github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= 49 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 50 | github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s= 51 | github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 52 | github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= 53 | github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 54 | github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= 55 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 56 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 57 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 58 | github.com/imroc/req v0.2.3 h1:ElMCifcqg/1GonGloyyTUrj6D6IITL6EiNEKHUl4xZM= 59 | github.com/imroc/req v0.2.3/go.mod h1:J9FsaNHDTIVyW/b5r6/Df5qKEEEq2WzZKIgKSajd1AE= 60 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= 61 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= 62 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 63 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 64 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 65 | github.com/jinzhu/configor v1.0.0 h1:F8ck+vczDAvSbsz77Mj8pttFZQJTFau1uJ3efJkHoYU= 66 | github.com/jinzhu/configor v1.0.0/go.mod h1:xycrO0mK6seJRAHXsdyk54QgPJ20aQNpTGi5xv8jQg8= 67 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 68 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 69 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 70 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= 71 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 72 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 73 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 74 | github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= 75 | github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 76 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= 77 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 78 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 79 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 80 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 81 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 82 | github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= 83 | github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= 84 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= 85 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 86 | github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8= 87 | github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw= 88 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 89 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 90 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 91 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 92 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 93 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 94 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 95 | github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= 96 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 97 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 98 | github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= 99 | github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= 100 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 101 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 102 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 103 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 104 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 105 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 106 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 107 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 108 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 109 | github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= 110 | github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 111 | github.com/streadway/amqp v0.0.0-20180806233856-70e15c650864 h1:Oj3PUEs+OUSYUpn35O+BE/ivHGirKixA3+vqA0Atu9A= 112 | github.com/streadway/amqp v0.0.0-20180806233856-70e15c650864/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY= 113 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 114 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 115 | github.com/stvp/tempredis v0.0.0-20160122230306-83f7aae7ea49/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= 116 | github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= 117 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 118 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 119 | github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= 120 | github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= 121 | go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= 122 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= 123 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= 124 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 125 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 126 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 127 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 128 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 129 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= 130 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 131 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 132 | golang.org/x/oauth2 v0.0.0-20181031022657-8527f56f7107 h1:63fpDttzclb8owmRoxSaFNbnT1CG25L0Yvnhh9lU1SE= 133 | golang.org/x/oauth2 v0.0.0-20181031022657-8527f56f7107/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 134 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 135 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 137 | golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 138 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 139 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= 141 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 142 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 143 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 144 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 145 | golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 146 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 147 | google.golang.org/api v0.0.0-20181101000641-61ce27ee8154 h1:DqMjeNv3mOVIVXy5mVpZ+IY8VW9+1qAE1jsgNyMNB3Y= 148 | google.golang.org/api v0.0.0-20181101000641-61ce27ee8154/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 149 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= 150 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 151 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 152 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 153 | google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 h1:67iHsV9djwGdZpdZNbLuQj6FOzCaZe3w+vhLjn5AcFA= 154 | google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 155 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 156 | google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= 157 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 158 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 159 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 160 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 161 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 162 | gopkg.in/go-playground/validator.v9 v9.27.0 h1:wCg/0hk9RzcB0CYw8pYV6FiBYug1on0cpco9YZF8jqA= 163 | gopkg.in/go-playground/validator.v9 v9.27.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= 164 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= 165 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 166 | gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo= 167 | gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= 168 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= 169 | gopkg.in/src-d/go-git.v4 v4.8.1 h1:aAyBmkdE1QUUEHcP4YFCGKmsMQRAuRmUcPEQR7lOAa0= 170 | gopkg.in/src-d/go-git.v4 v4.8.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= 171 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 172 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 173 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 174 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 175 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 176 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 177 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 178 | mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM= 179 | mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= 180 | -------------------------------------------------------------------------------- /cmd/cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | b64 "encoding/base64" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "log" 13 | "net/http" 14 | "net/url" 15 | "os" 16 | "os/exec" 17 | "os/user" 18 | "path" 19 | "strings" 20 | 21 | "github.com/google/uuid" 22 | "github.com/imroc/req" 23 | "github.com/inconshreveable/go-update" 24 | "github.com/manifoldco/promptui" 25 | "github.com/urfave/cli" 26 | "gopkg.in/src-d/go-git.v4" 27 | "gopkg.in/src-d/go-git.v4/plumbing" 28 | ) 29 | 30 | type Submission struct { 31 | PackageName string `json:"packageName"` 32 | PackageVersion string `json:"packageVersion"` 33 | PackageExtendedVersion string `json:"packageExtendedVersion"` 34 | PackageURL string `json:"packageUrl"` 35 | SourceURL string `json:"sourceUrl"` 36 | Maintainer string `json:"maintainer"` 37 | MaintainerFingerprint string `json:"maintainerFingerprint"` 38 | Component string `json:"component"` 39 | IsExperimental bool `json:"isExperimental"` 40 | Tarball string `json:"tarball"` 41 | PackageBranch string `json:"packageBranch"` 42 | SourceBranch string `json:"sourceBranch"` 43 | } 44 | 45 | type GithubReleaseResponse struct { 46 | Url string `json:"url"` 47 | Assets []struct { 48 | Name string `json:"name"` 49 | BrowserDownloadUrl string `json:"browser_download_url"` 50 | } 51 | } 52 | 53 | var ( 54 | app *cli.App 55 | homeDir string 56 | chiefAddress string 57 | maintainerSigningKey string 58 | sourceUrl string 59 | component string 60 | packageBranch string 61 | sourceBranch string 62 | packageUrl string 63 | version string 64 | isExperimental bool 65 | pipelineId string 66 | ) 67 | 68 | func checkForInitValues() (err error) { 69 | dat0, _ := ioutil.ReadFile(homeDir + "/.irgsh/IRGSH_CHIEF_ADDRESS") 70 | chiefAddress = string(dat0) 71 | dat1, _ := ioutil.ReadFile(homeDir + "/.irgsh/IRGSH_MAINTAINER_SIGNING_KEY") 72 | maintainerSigningKey = string(dat1) 73 | if len(chiefAddress) < 1 || len(maintainerSigningKey) < 1 { 74 | errMsg := "irgsh-cli need to be configured first. Run: " 75 | errMsg += "irgsh-cli config --chief yourirgshchiefaddress --key yourgpgkeyfingerprint" 76 | err = errors.New(errMsg) 77 | fmt.Println(err.Error()) 78 | } 79 | return 80 | } 81 | 82 | func main() { 83 | log.SetFlags(log.LstdFlags | log.Lshortfile) 84 | 85 | usr, err := user.Current() 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | homeDir = usr.HomeDir 90 | 91 | app = cli.NewApp() 92 | app.Name = "irgsh-go" 93 | app.Usage = "irgsh-go distributed packager" 94 | app.Author = "BlankOn Developer" 95 | app.Email = "blankon-dev@googlegroups.com" 96 | app.Version = version 97 | 98 | app.Commands = []cli.Command{ 99 | 100 | { 101 | Name: "config", 102 | Usage: "Configure irgsh-cli", 103 | Flags: []cli.Flag{ 104 | cli.StringFlag{ 105 | Name: "chief", 106 | Value: "", 107 | Destination: &chiefAddress, 108 | Usage: "Chief address", 109 | }, 110 | cli.StringFlag{ 111 | Name: "key", 112 | Value: "", 113 | Destination: &maintainerSigningKey, 114 | Usage: "Maintainer signing key", 115 | }, 116 | }, 117 | Action: func(c *cli.Context) (err error) { 118 | if len(chiefAddress) < 1 { 119 | msg := "Chief address should not be empty. Example: " 120 | msg += "irgsh-cli config --chief https://irgsh.blankonlinux.or.id --key B113D905C417D" 121 | err = errors.New(msg) 122 | return 123 | } 124 | if len(maintainerSigningKey) < 1 { 125 | msg := "Signing key should not be empty. Example: " 126 | msg += "irgsh-cli config --chief https://irgsh.blankonlinux.or.id --key B113D905C417D" 127 | err = errors.New(msg) 128 | return 129 | } 130 | _, err = url.ParseRequestURI(chiefAddress) 131 | if err != nil { 132 | return 133 | } 134 | 135 | cmdStr := "mkdir -p " + homeDir + "/.irgsh/tmp && echo -n '" + chiefAddress 136 | cmdStr += "' > " + homeDir + "/.irgsh/IRGSH_CHIEF_ADDRESS" 137 | cmd := exec.Command("bash", "-c", cmdStr) 138 | err = cmd.Run() 139 | if err != nil { 140 | log.Println(cmdStr) 141 | log.Println("error: %v\n", err) 142 | return 143 | } 144 | cmdStr = "mkdir -p " + homeDir + "/.irgsh/tmp && echo -n '" 145 | cmdStr += maintainerSigningKey + "' > " + homeDir + "/.irgsh/IRGSH_MAINTAINER_SIGNING_KEY" 146 | cmd = exec.Command("bash", "-c", cmdStr) 147 | err = cmd.Run() 148 | if err != nil { 149 | log.Println(cmdStr) 150 | log.Println("error: %v\n", err) 151 | return 152 | } 153 | // TODO test a connection against the chief 154 | fmt.Println("irgsh-cli is successfully configured. Happy hacking!") 155 | return err 156 | }, 157 | }, 158 | 159 | { 160 | Name: "submit", 161 | Usage: "Submit new build", 162 | Flags: []cli.Flag{ 163 | cli.StringFlag{ 164 | Name: "source", 165 | Value: "", 166 | Destination: &sourceUrl, 167 | Usage: "Source URL", 168 | }, 169 | cli.StringFlag{ 170 | Name: "package", 171 | Value: "", 172 | Destination: &packageUrl, 173 | Usage: "Package URL", 174 | }, 175 | cli.StringFlag{ 176 | Name: "component", 177 | Value: "", 178 | Destination: &component, 179 | Usage: "Repository component", 180 | }, 181 | cli.StringFlag{ 182 | Name: "package-branch", 183 | Value: "", 184 | Destination: &packageBranch, 185 | Usage: "package git branch", 186 | }, 187 | cli.StringFlag{ 188 | Name: "source-branch", 189 | Value: "", 190 | Destination: &sourceBranch, 191 | Usage: "source git branch", 192 | }, 193 | cli.BoolFlag{ 194 | Name: "experimental", 195 | Usage: "Enable experimental flag", 196 | }, 197 | cli.BoolFlag{ 198 | Name: "ignore-checks", 199 | Usage: "Ignoring value checks", 200 | }, 201 | }, 202 | Action: func(ctx *cli.Context) (err error) { 203 | 204 | ignoreChecks := ctx.Bool("ignore-checks") && ctx.Bool("experimental") 205 | 206 | err = checkForInitValues() 207 | if err != nil { 208 | log.Println(err) 209 | return err 210 | } 211 | 212 | // Check version first 213 | header := make(http.Header) 214 | header.Set("Accept", "application/json") 215 | req.SetFlags(req.LrespBody) 216 | 217 | type VersionResponse struct { 218 | Version string `json:"version"` 219 | } 220 | result, err := req.Get(chiefAddress+"/api/v1/version", nil) 221 | if err != nil { 222 | log.Println(err) 223 | return err 224 | } 225 | responseStr := fmt.Sprintf("%+v", result) 226 | versionResponse := VersionResponse{} 227 | err = json.Unmarshal([]byte(responseStr), &versionResponse) 228 | if err != nil { 229 | log.Println(err) 230 | return 231 | } 232 | 233 | if versionResponse.Version != app.Version { 234 | log.Println("Target version", versionResponse.Version) 235 | log.Println("Local version", app.Version) 236 | err = errors.New("Client version mismatch. Please update your irgsh-cli.") 237 | return 238 | } 239 | 240 | // Default component is main 241 | if len(component) < 1 { 242 | component = "main" 243 | } 244 | 245 | // Default branch is master 246 | if len(packageBranch) < 1 { 247 | packageBranch = "master" 248 | } 249 | if len(sourceBranch) < 1 { 250 | sourceBranch = "master" 251 | } 252 | 253 | if len(sourceUrl) > 0 { 254 | _, err = url.ParseRequestURI(sourceUrl) 255 | if err != nil { 256 | log.Println(err) 257 | return 258 | } 259 | } 260 | 261 | if len(packageUrl) < 1 { 262 | err = errors.New("--package should not be empty") 263 | return 264 | } 265 | _, err = url.ParseRequestURI(packageUrl) 266 | if err != nil { 267 | log.Println(err) 268 | return 269 | } 270 | isExperimental = true 271 | if !ctx.Bool("experimental") { 272 | prompt := promptui.Prompt{ 273 | Label: "Experimental flag is not set which means the package will be injected to official dev repository. Are you sure you want to continue to submit and build this package?", 274 | IsConfirm: true, 275 | } 276 | result, promptErr := prompt.Run() 277 | // Avoid shadowed err 278 | err = promptErr 279 | if err != nil { 280 | log.Println(err) 281 | return 282 | } 283 | if strings.ToLower(result) != "y" { 284 | return 285 | } 286 | isExperimental = false 287 | } 288 | tmpID := uuid.New().String() 289 | var downloadableTarballURL string 290 | if len(sourceUrl) > 0 { 291 | // TODO Ensure that the debian spec's source format is quilt. 292 | // Otherwise (native), terminate the submission. 293 | fmt.Println("sourceUrl: " + sourceUrl) 294 | // Cloning Debian package files 295 | _, err = git.PlainClone( 296 | homeDir+"/.irgsh/tmp/"+tmpID+"/source", 297 | false, 298 | &git.CloneOptions{ 299 | URL: sourceUrl, 300 | Progress: os.Stdout, 301 | SingleBranch: true, 302 | Depth: 1, 303 | ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", sourceBranch)), 304 | }, 305 | ) 306 | if err != nil { 307 | fmt.Println(err.Error()) 308 | if strings.Contains(err.Error(), "repository not found") { 309 | // Downloadable tarball? Let's try. 310 | downloadableTarballURL = strings.TrimSuffix(string(sourceUrl), "\n") 311 | log.Println(downloadableTarballURL) 312 | log.Println("Downloading the tarball " + downloadableTarballURL) 313 | resp, err1 := http.Get(downloadableTarballURL) 314 | if err1 != nil { 315 | log.Println(err) 316 | err = err1 317 | return 318 | panic(err) 319 | } 320 | defer resp.Body.Close() 321 | // Prepare dirs 322 | targetDir := homeDir + "/.irgsh/tmp/" + tmpID 323 | err = os.MkdirAll(targetDir, 0755) 324 | if err != nil { 325 | log.Printf("error: %v\n", err) 326 | return 327 | } 328 | 329 | // Write the tarball 330 | out, err := os.Create(targetDir + "/" + path.Base(downloadableTarballURL)) 331 | defer out.Close() 332 | if err != nil { 333 | log.Println(err.Error()) 334 | panic(err) 335 | } 336 | io.Copy(out, resp.Body) 337 | 338 | } else { 339 | log.Println(err.Error()) 340 | return 341 | } 342 | } 343 | } 344 | fmt.Println("packageUrl: " + packageUrl) 345 | 346 | // Cloning Debian package files 347 | _, err = git.PlainClone( 348 | homeDir+"/.irgsh/tmp/"+tmpID+"/package", 349 | false, 350 | &git.CloneOptions{ 351 | URL: packageUrl, 352 | Progress: os.Stdout, 353 | SingleBranch: true, 354 | Depth: 1, 355 | ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", packageBranch)), 356 | }, 357 | ) 358 | if err != nil { 359 | fmt.Println(err.Error()) 360 | return 361 | } 362 | 363 | var packageName, packageVersion, packageExtendedVersion, packageLastMaintainer, uploaders string 364 | 365 | // Getting package name 366 | log.Println("Getting package name...") 367 | cmdStr := "cd " + homeDir + "/.irgsh/tmp/" + tmpID 368 | cmdStr += "/package && cat debian/control | grep 'Source:' | head -n 1 | cut -d ' ' -f 2" 369 | fmt.Println(cmdStr) 370 | output, err := exec.Command("bash", "-c", cmdStr).Output() 371 | if err != nil { 372 | log.Println("error: %v\n", err) 373 | log.Println("Failed to get package name.") 374 | return 375 | } 376 | packageName = strings.TrimSuffix(string(output), "\n") 377 | if len(packageName) < 1 { 378 | log.Println("It seems the repository does not contain debian spec directory.") 379 | return 380 | 381 | } 382 | log.Println("Package name: " + packageName) 383 | 384 | // Getting package version 385 | log.Println("Getting package version ...") 386 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 387 | cmdStr += "/package && cat debian/changelog | head -n 1 | cut -d '(' -f 2 | cut -d ')' -f 1 | cut -d '-' -f 1" 388 | fmt.Println(cmdStr) 389 | output, err = exec.Command("bash", "-c", cmdStr).Output() 390 | if err != nil { 391 | log.Println("error: %v\n", err) 392 | log.Println("Failed to get package version.") 393 | return 394 | } 395 | packageVersion = strings.TrimSuffix(string(output), "\n") 396 | if strings.Contains(packageVersion, ":") { 397 | packageVersion = strings.Split(packageVersion, ":")[1] 398 | } 399 | log.Println("Package version: " + packageVersion) 400 | 401 | // Getting package extended version 402 | log.Println("Getting package extended version ...") 403 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 404 | cmdStr += "/package && cat debian/changelog | head -n 1 | cut -d '(' -f 2 | cut -d ')' -f 1 | cut -d '-' -f 2" 405 | fmt.Println(cmdStr) 406 | output, err = exec.Command("bash", "-c", cmdStr).Output() 407 | if err != nil { 408 | log.Println("error: %v\n", err) 409 | log.Println("Failed to get package extended version.") 410 | return 411 | } 412 | packageExtendedVersion = strings.TrimSuffix(string(output), "\n") 413 | if packageExtendedVersion == packageVersion { 414 | packageExtendedVersion = "" 415 | } 416 | log.Println("Package extended version: " + packageExtendedVersion) 417 | 418 | // Getting package last maintainer 419 | log.Println("Getting package last maintainer ...") 420 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 421 | cmdStr += "/package && echo $(cat debian/changelog | grep ' --' | cut -d '-' -f 3 | cut -d '>' -f 1 | head -n 1)'>'" 422 | fmt.Println(cmdStr) 423 | output, err = exec.Command("bash", "-c", cmdStr).Output() 424 | if err != nil { 425 | log.Println("error: %v\n", err) 426 | log.Println("Failed to get package extended version.") 427 | return 428 | } 429 | packageLastMaintainer = strings.TrimSuffix(string(output), "\n") 430 | log.Println(packageLastMaintainer) 431 | 432 | // Getting uploaders 433 | log.Println("Getting package name...") 434 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 435 | cmdStr += "/package && cat debian/control | grep 'Uploaders:' | head -n 1 | cut -d ':' -f 2" 436 | fmt.Println(cmdStr) 437 | output, err = exec.Command("bash", "-c", cmdStr).Output() 438 | if err != nil { 439 | log.Println("error: %v\n", err) 440 | log.Println("Failed to get uploaders value.") 441 | return 442 | } 443 | uploaders = strings.TrimSpace(strings.TrimSuffix(string(output), "\n")) 444 | 445 | // Getting maintainer identity 446 | log.Println("Getting maintainer identity...") 447 | maintainerIdentity := "" 448 | cmdStr = "gpg -K | grep -A 1 " + maintainerSigningKey + " | tail -n 1 | cut -d ']' -f 2" 449 | fmt.Println(cmdStr) 450 | output, err = exec.Command("bash", "-c", cmdStr).Output() 451 | if err != nil { 452 | log.Println("error: %v\n", err) 453 | log.Println("Failed to get maintainer identity.") 454 | return 455 | } 456 | maintainerIdentity = strings.TrimSpace(strings.TrimSuffix(string(output), "\n")) 457 | 458 | if strings.TrimSpace(uploaders) != strings.TrimSpace(maintainerIdentity) && !ignoreChecks { 459 | err = errors.New("The uploaders value in the debian/control does not matched with your identity. Please update the debian/control file.") 460 | log.Println("The uploader in the debian/control: " + uploaders) 461 | log.Println("Your signing key identity: " + maintainerIdentity) 462 | return 463 | } 464 | 465 | if strings.TrimSpace(packageLastMaintainer) != strings.TrimSpace(maintainerIdentity) && !ignoreChecks { 466 | err = errors.New("The last maintainer in the debian/changelog does not matched with your identity. Please update the debian/changelog file.") 467 | log.Println("The last maintainer in the debian/changelog: " + packageLastMaintainer) 468 | log.Println("Your signing key identity: " + maintainerIdentity) 469 | return 470 | } 471 | 472 | // Determine package name with version 473 | log.Println(packageVersion) 474 | packageNameVersion := packageName + "-" + packageVersion 475 | log.Println(packageNameVersion) 476 | log.Println(packageExtendedVersion) 477 | if len(packageExtendedVersion) > 0 { 478 | packageNameVersion += "-" + packageExtendedVersion 479 | } 480 | 481 | if len(sourceUrl) > 0 && len(downloadableTarballURL) < 1 { 482 | origFileName := packageName + "_" + strings.Split(packageVersion, "-")[0] // Discard quilt revision 483 | // Compress source to orig tarball 484 | log.Println("Creating orig tarball...") 485 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 486 | cmdStr += "/ && mkdir -p tmp && mv source tmp && cd tmp && mv source " + packageName + "-" + packageVersion + " && tar cfJ " + origFileName + ".orig.tar.xz " + packageName + "-" + packageVersion + " && rm -rf " + packageName + "-" + packageVersion + " && mv *.xz .. && cd .. && rm -rf tmp " 487 | fmt.Println(cmdStr) 488 | output, err = exec.Command("bash", "-c", cmdStr).Output() 489 | if err != nil { 490 | log.Println("error: %v\n", err) 491 | log.Println("Failed to rename workdir.") 492 | } 493 | } 494 | 495 | // Rename the package dir so we can run debuild without warning/error 496 | log.Println("Renaming workdir...") 497 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 498 | cmdStr += "/ && mv package " + packageNameVersion 499 | fmt.Println(cmdStr) 500 | output, err = exec.Command("bash", "-c", cmdStr).Output() 501 | if err != nil { 502 | log.Println("error: %v\n", err) 503 | log.Println("Failed to rename workdir.") 504 | return 505 | } 506 | 507 | // Generate the dsc file 508 | log.Println("Signing the dsc file...") 509 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 510 | cmdStr += "/" + packageNameVersion + " && dpkg-source --build . " 511 | fmt.Println(cmdStr) 512 | cmd := exec.Command("bash", "-c", cmdStr) 513 | // Make it interactive 514 | cmd.Stdout = os.Stdout 515 | cmd.Stdin = os.Stdin 516 | cmd.Stderr = os.Stderr 517 | err = cmd.Run() 518 | if err != nil { 519 | log.Println("error: %v\n", err) 520 | log.Println("Failed to sign the package. Either you've the wrong key or you've unmeet dependencies. Please the error message(s) above..") 521 | return 522 | } 523 | 524 | // Signing the dsc file 525 | log.Println("Signing the dsc file...") 526 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 527 | cmdStr += "/ && debsign -k" + maintainerSigningKey + " *.dsc" 528 | fmt.Println(cmdStr) 529 | cmd = exec.Command("bash", "-c", cmdStr) 530 | // Make it interactive 531 | cmd.Stdout = os.Stdout 532 | cmd.Stdin = os.Stdin 533 | cmd.Stderr = os.Stderr 534 | err = cmd.Run() 535 | if err != nil { 536 | log.Println("error: %v\n", err) 537 | log.Println("Failed to sign the package. Either you've the wrong key or you've unmeet dependencies. Please the error message(s) above..") 538 | return 539 | } 540 | 541 | log.Println("Generate buildinfo file...") 542 | // Generate the buildinfo file 543 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 544 | cmdStr += "/" + packageNameVersion + " && dpkg-genbuildinfo " 545 | fmt.Println(cmdStr) 546 | cmd = exec.Command("bash", "-c", cmdStr) 547 | // Make it interactive 548 | cmd.Stdout = os.Stdout 549 | cmd.Stdin = os.Stdin 550 | var buffer bytes.Buffer 551 | bufWriter := bufio.NewWriter(&buffer) 552 | cmd.Stderr = bufWriter 553 | err = cmd.Run() 554 | if err != nil && !strings.Contains(buffer.String(), ".buildinfo is meaningless") { 555 | log.Println("error: %v\n", err.Error()) 556 | log.Println("Failed to sign the package. Either you've the wrong key or you've unmeet dependencies. Please the error message(s) above..") 557 | return 558 | } 559 | err = nil 560 | 561 | // Generate the changes file 562 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 563 | cmdStr += "/" + packageNameVersion + " && dpkg-genchanges > ../$(ls .. | grep dsc | tr -d \".dsc\")_source.changes " 564 | fmt.Println(cmdStr) 565 | cmd = exec.Command("bash", "-c", cmdStr) 566 | // Make it interactive 567 | cmd.Stdout = os.Stdout 568 | cmd.Stdin = os.Stdin 569 | cmd.Stderr = os.Stderr 570 | err = cmd.Run() 571 | if err != nil { 572 | log.Println("error: %v\n", err) 573 | log.Println("Failed to sign the package. Either you've the wrong key or you've unmeet dependencies. Please the error message(s) above..") 574 | return 575 | } 576 | 577 | // Lintian 578 | log.Println("Lintian test...") 579 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 580 | cmdStr += "/" + packageNameVersion + " && lintian --profile blankon 2>&1" 581 | fmt.Println(cmdStr) 582 | output, err = exec.Command("bash", "-c", cmdStr).Output() 583 | log.Println(string(output)) // Print warnings as well 584 | // There is --fail-on error option on newer lintian version, 585 | // but let's just check the existence of "E:" string on output to determine error 586 | // to achieve backward compatibility with older lintian 587 | if !ignoreChecks && (err != nil || strings.Contains(string(output), "E:")) { 588 | log.Println("Failed to pass lintian.") 589 | return 590 | } 591 | 592 | log.Println("Rename move generated files to signed dir") 593 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 594 | cmdStr += " && mkdir signed" 595 | cmdStr += " && mv *.xz ./signed/ || true " // ignore if err, native package has no orig tarball 596 | cmdStr += " && mv *.dsc ./signed/ " 597 | cmdStr += " && mv *.changes ./signed/ " 598 | err = exec.Command("bash", "-c", cmdStr).Run() 599 | if err != nil { 600 | log.Println(err) 601 | return 602 | } 603 | 604 | // Clean up 605 | log.Println("Cleaning up...") 606 | cmdStr = "rm -rf " + homeDir + "/.irgsh/tmp/" + tmpID + "/package" 607 | err = exec.Command("bash", "-c", cmdStr).Run() 608 | if err != nil { 609 | log.Println(err) 610 | return 611 | } 612 | 613 | // Compressing 614 | log.Println("Compressing...") 615 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 616 | cmdStr += " && tar -zcvf ../" + tmpID + ".tar.gz ." 617 | err = exec.Command("bash", "-c", cmdStr).Run() 618 | if err != nil { 619 | log.Println(err) 620 | return err 621 | } 622 | 623 | submission := Submission{ 624 | PackageName: packageName, 625 | PackageVersion: packageVersion, 626 | PackageExtendedVersion: packageExtendedVersion, 627 | PackageURL: packageUrl, 628 | SourceURL: sourceUrl, 629 | Maintainer: maintainerIdentity, 630 | MaintainerFingerprint: maintainerSigningKey, 631 | Component: component, 632 | IsExperimental: isExperimental, 633 | PackageBranch: packageBranch, 634 | SourceBranch: sourceBranch, 635 | } 636 | jsonByte, _ := json.Marshal(submission) 637 | 638 | // Signing a token 639 | log.Println("Signing auth token...") 640 | cmdStr = "cd " + homeDir + "/.irgsh/tmp/" + tmpID 641 | cmdStr += "/ && echo '" + b64.StdEncoding.EncodeToString(jsonByte) + "' | base64 -d > token && gpg -u " + maintainerSigningKey + " --clearsign --output token.sig --sign token" 642 | fmt.Println(cmdStr) 643 | cmd = exec.Command("bash", "-c", cmdStr) 644 | // Make it interactive 645 | cmd.Stdout = os.Stdout 646 | cmd.Stdin = os.Stdin 647 | cmd.Stderr = os.Stderr 648 | err = cmd.Run() 649 | if err != nil { 650 | log.Println("error: %v\n", err) 651 | log.Println("Failed to sign the auth token using " + maintainerSigningKey + ". Please check your GPG key list.") 652 | return 653 | } 654 | 655 | // Upload 656 | log.Println("Uploading blob...") 657 | cmdStr = "curl -f -s --show-error -F 'blob=@" + homeDir + "/.irgsh/tmp/" + tmpID + ".tar.gz" + "' " 658 | cmdStr += " -F 'token=@" + homeDir + "/.irgsh/tmp/" + tmpID + "/token.sig" + "'" 659 | cmdStr += " '" + chiefAddress + "/api/v1/submission-upload' 2>&1" 660 | log.Println(cmdStr) 661 | output, err = exec.Command("bash", "-c", cmdStr).Output() 662 | if err != nil { 663 | log.Println(string(output)) 664 | return err 665 | } 666 | 667 | blobStr := strings.TrimSuffix(string(output), "\n") 668 | type Blob struct { 669 | ID string `json:"id"` 670 | } 671 | blob := Blob{} 672 | err = json.Unmarshal([]byte(blobStr), &blob) 673 | if err != nil { 674 | log.Println(err) 675 | return err 676 | } 677 | 678 | submission.Tarball = blob.ID 679 | jsonByte, _ = json.Marshal(submission) 680 | 681 | log.Println("Submitting...") 682 | result, err = req.Post(chiefAddress+"/api/v1/submit", header, req.BodyJSON(string(jsonByte))) 683 | if err != nil { 684 | log.Println(err) 685 | return 686 | } 687 | 688 | responseStr = fmt.Sprintf("%+v", result) 689 | if !strings.Contains(responseStr, "pipelineId") { 690 | log.Println(responseStr) 691 | fmt.Println("Submission failed.") 692 | return 693 | } 694 | type SubmitResponse struct { 695 | PipelineID string `json:"pipelineId"` 696 | } 697 | submissionResponse := SubmitResponse{} 698 | err = json.Unmarshal([]byte(responseStr), &submissionResponse) 699 | if err != nil { 700 | log.Println(err) 701 | return 702 | } 703 | fmt.Println("Submission succeeded. Pipeline ID:") 704 | fmt.Println(submissionResponse.PipelineID) 705 | cmdStr = "mkdir -p " + homeDir + "/.irgsh/tmp && echo -n '" 706 | cmdStr += submissionResponse.PipelineID + "' > " + homeDir + "/.irgsh/LAST_PIPELINE_ID" 707 | cmd = exec.Command("bash", "-c", cmdStr) 708 | err = cmd.Run() 709 | if err != nil { 710 | log.Println(err) 711 | return 712 | } 713 | 714 | return err 715 | }, 716 | }, 717 | { 718 | Name: "status", 719 | Usage: "Check status of a pipeline", 720 | Action: func(c *cli.Context) (err error) { 721 | pipelineId = c.Args().First() 722 | err = checkForInitValues() 723 | if err != nil { 724 | os.Exit(1) 725 | } 726 | if len(pipelineId) < 1 { 727 | dat, _ := ioutil.ReadFile(homeDir + "/.irgsh/LAST_PIPELINE_ID") 728 | pipelineId = string(dat) 729 | if len(pipelineId) < 1 { 730 | err = errors.New("--pipeline should not be empty") 731 | return 732 | } 733 | } 734 | fmt.Println("Checking the status of " + pipelineId + " ...") 735 | req.SetFlags(req.LrespBody) 736 | result, err := req.Get(chiefAddress+"/api/v1/status?uuid="+pipelineId, nil) 737 | if err != nil { 738 | return err 739 | } 740 | 741 | responseStr := fmt.Sprintf("%+v", result) 742 | type SubmitResponse struct { 743 | PipelineID string `json:"pipelineId"` 744 | State string `json:"state"` 745 | } 746 | responseJson := SubmitResponse{} 747 | err = json.Unmarshal([]byte(responseStr), &responseJson) 748 | if err != nil { 749 | return 750 | } 751 | fmt.Println(responseJson.State) 752 | 753 | return 754 | }, 755 | }, 756 | { 757 | Name: "log", 758 | Usage: "Read the logs of a pipeline", 759 | Action: func(c *cli.Context) (err error) { 760 | pipelineId = c.Args().First() 761 | err = checkForInitValues() 762 | if err != nil { 763 | os.Exit(1) 764 | } 765 | if len(pipelineId) < 1 { 766 | dat, _ := ioutil.ReadFile(homeDir + "/.irgsh/LAST_PIPELINE_ID") 767 | pipelineId = string(dat) 768 | if len(pipelineId) < 1 { 769 | err = errors.New("--pipeline should not be empty") 770 | return 771 | } 772 | } 773 | fmt.Println("Fetching the logs of " + pipelineId + " ...") 774 | req.SetFlags(req.LrespBody) 775 | result, err := req.Get(chiefAddress+"/api/v1/status?uuid="+pipelineId, nil) 776 | if err != nil { 777 | return err 778 | } 779 | 780 | responseStr := fmt.Sprintf("%+v", result) 781 | type SubmitResponse struct { 782 | PipelineID string `json:"pipelineId"` 783 | State string `json:"state"` 784 | } 785 | responseJson := SubmitResponse{} 786 | err = json.Unmarshal([]byte(responseStr), &responseJson) 787 | if err != nil { 788 | return 789 | } 790 | if responseJson.State == "STARTED" { 791 | fmt.Println("The pipeline is not finished yet") 792 | return 793 | } 794 | 795 | result, err = req.Get(chiefAddress+"/logs/"+pipelineId+".build.log", nil) 796 | if err != nil { 797 | log.Println(err.Error()) 798 | return err 799 | } 800 | logResult := fmt.Sprintf("%+v", result) 801 | if strings.Contains(logResult, "404 page not found") { 802 | err = errors.New("Builder log is not found. The worker/pipeline may terminated ungracefully.") 803 | return err 804 | } 805 | fmt.Println(logResult) 806 | 807 | result, err = req.Get(chiefAddress+"/logs/"+pipelineId+".repo.log", nil) 808 | if err != nil { 809 | log.Println(err.Error()) 810 | return err 811 | } 812 | logResult = fmt.Sprintf("%+v", result) 813 | if strings.Contains(logResult, "404 page not found") { 814 | err = errors.New("Repo log is not found. The worker/pipeline may terminated ungracefully.") 815 | return err 816 | } 817 | fmt.Println(logResult) 818 | 819 | return 820 | }, 821 | }, 822 | { 823 | Name: "update", 824 | Usage: "Update the irgsh-cli tool", 825 | Action: func(c *cli.Context) (err error) { 826 | var ( 827 | cmdStr = "ln -sf /usr/bin/irgsh-cli /usr/bin/irgsh && /usr/bin/irgsh-cli --version" 828 | downloadURL string 829 | githubResponse GithubReleaseResponse 830 | githubAssetName = "irgsh-cli" 831 | url = "https://api.github.com/repos/BlankOn/irgsh-go/releases/latest" 832 | ) 833 | 834 | response, err := http.Get(url) 835 | if err != nil { 836 | log.Printf("error: %v\n", err) 837 | log.Println("Failed to get package name.") 838 | 839 | return 840 | } 841 | defer response.Body.Close() 842 | 843 | body, err := ioutil.ReadAll(response.Body) 844 | if err != nil { 845 | log.Printf("error: %v\n", err) 846 | 847 | return 848 | } 849 | 850 | if err := json.Unmarshal(body, &githubResponse); err != nil { 851 | log.Printf("error: %v\n", err) 852 | 853 | return err 854 | } 855 | 856 | for _, asset := range githubResponse.Assets { 857 | if asset.Name == githubAssetName { 858 | downloadURL = strings.TrimSuffix(string(asset.BrowserDownloadUrl), "\n") 859 | break 860 | } 861 | } 862 | 863 | log.Println(downloadURL) 864 | log.Println("Self-updating...") 865 | 866 | resp, err := http.Get(downloadURL) 867 | if err != nil { 868 | log.Printf("error: %v\n", err) 869 | 870 | return err 871 | } 872 | 873 | defer resp.Body.Close() 874 | 875 | err = update.Apply(resp.Body, update.Options{}) 876 | if err != nil { 877 | log.Printf("error: %v\n", err) 878 | 879 | return err 880 | } 881 | 882 | log.Println(cmdStr) 883 | 884 | output, err := exec.Command("bash", "-c", cmdStr).Output() 885 | if err != nil { 886 | log.Println(output) 887 | log.Printf("error: %v\n", err) 888 | log.Println("Failed to get package name.") 889 | } 890 | log.Println("Updated to " + strings.TrimSuffix(string(output), "\n")) 891 | 892 | return 893 | }, 894 | }, 895 | } 896 | 897 | err = app.Run(os.Args) 898 | if err != nil { 899 | log.Fatal(err) 900 | } 901 | } 902 | --------------------------------------------------------------------------------