all: dockerize scaleway builders
Fixes golang/go#20397
Change-Id: I353e879e61aeef56f0c228bbd37cc1eea2ce8b4e
Reviewed-on: https://go-review.googlesource.com/43612
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/buildlet/stage0/stage0.go b/cmd/buildlet/stage0/stage0.go
index afd6934..a5f02b7 100644
--- a/cmd/buildlet/stage0/stage0.go
+++ b/cmd/buildlet/stage0/stage0.go
@@ -11,19 +11,14 @@
package main
import (
- "bytes"
- "encoding/json"
"flag"
"fmt"
- "io/ioutil"
"log"
"net"
- "net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
- "strings"
"time"
"cloud.google.com/go/compute/metadata"
@@ -40,11 +35,6 @@
const attr = "buildlet-binary-url"
-var (
- onScaleway bool
- scalewayMeta scalewayMetadata
-)
-
// untar helper, for the Windows image prep script.
var (
untarFile = flag.String("untar-file", "", "if non-empty, tar.gz to untar to --untar-dest-dir")
@@ -61,12 +51,11 @@
switch osArch {
case "linux/arm":
- if os.Getenv("GO_BUILDER_ENV") == "linux-arm-arm5spacemonkey" {
+ switch env := os.Getenv("GO_BUILDER_ENV"); env {
+ case "linux-arm-arm5spacemonkey", "host-linux-arm-scaleway":
// No setup currently.
- } else {
- if _, err := os.Stat("/usr/local/bin/oc-metadata"); err == nil {
- initScaleway()
- }
+ default:
+ panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
}
case "linux/arm64":
switch env := os.Getenv("GO_BUILDER_ENV"); env {
@@ -112,11 +101,17 @@
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
- if onScaleway {
- cmd.Args = append(cmd.Args, scalewayBuildletArgs()...)
- }
- if os.Getenv("GO_BUILDER_ENV") == "linux-arm-arm5spacemonkey" {
- cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-arm-arm5spacemonkey")...)
+ switch buildenv := os.Getenv("GO_BUILDER_ENV"); buildenv {
+ case "linux-arm-arm5spacemonkey":
+ cmd.Args = append(cmd.Args, legacyReverseBuildletArgs(buildenv)...)
+ case "host-linux-arm-scaleway":
+ scalewayArgs := append(
+ legacyReverseBuildletArgs(buildenv),
+ "--hostname="+os.Getenv("HOSTNAME"),
+ )
+ cmd.Args = append(cmd.Args,
+ scalewayArgs...,
+ )
}
switch osArch {
case "linux/s390x":
@@ -159,38 +154,6 @@
}
}
-func scalewayBuildletArgs() []string {
- var modes []string // e.g. "linux-arm", "linux-arm-arm5"
- // tags are of form "buildkey_linux-arm_HEXHEXHEX"
- for _, tag := range scalewayMeta.Tags {
- if strings.HasPrefix(tag, "buildkey_") {
- parts := strings.Split(tag, "_")
- if len(parts) != 3 {
- log.Fatalf("invalid server tag %q", tag)
- }
- mode, buildkey := parts[1], parts[2]
- modes = append(modes, mode)
- file := "/root/.gobuildkey-" + mode
- if fi, err := os.Stat(file); err != nil || (err == nil && fi.Size() == 0) {
- if err := ioutil.WriteFile(file, []byte(buildkey), 0600); err != nil {
- log.Fatal(err)
- }
- }
- }
- }
- server := "farmer.golang.org:443"
- if scalewayMeta.IsStaging() {
- server = "104.154.113.235:443" // fixed IP, but no hostname.
- }
- return []string{
- "--workdir=/workdir",
- "--hostname=" + scalewayMeta.Hostname,
- "--halt=false",
- "--reverse=" + strings.Join(modes, ","),
- "--coordinator=" + server,
- }
-}
-
// awaitNetwork reports whether the network came up within 30 seconds,
// determined somewhat arbitrarily via a DNS lookup for google.com.
func awaitNetwork() bool {
@@ -228,13 +191,6 @@
if v := os.Getenv("META_BUILDLET_BINARY_URL"); v != "" {
return v
}
- if onScaleway {
- if scalewayMeta.IsStaging() {
- return "https://storage.googleapis.com/dev-go-builder-data/buildlet.linux-arm"
- } else {
- return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm"
- }
- }
sleepFatalf("Not on GCE, and no META_BUILDLET_BINARY_URL specified.")
}
v, err := metadata.InstanceAttributeValue(attr)
@@ -274,136 +230,6 @@
}
}
-func initScaleway() {
- log.Printf("On scaleway.")
- onScaleway = true
- initScalewaySwap()
- initScalewayWorkdir()
- initScalewayMeta()
- initScalewayDNS()
- initScalewayGo14()
- log.Printf("Scaleway init complete; metadata is %+v", scalewayMeta)
-}
-
-type scalewayMetadata struct {
- Name string `json:"name"`
- Hostname string `json:"hostname"`
- Tags []string `json:"tags"`
-}
-
-// IsStaging reports whether this instance has a "staging" tag.
-func (m *scalewayMetadata) IsStaging() bool {
- for _, t := range m.Tags {
- if t == "staging" {
- return true
- }
- }
- return false
-}
-
-func initScalewayMeta() {
- const metaURL = "http://169.254.42.42/conf?format=json"
- res, err := http.Get(metaURL)
- if err != nil {
- log.Fatalf("failed to get scaleway metadata: %v", err)
- }
- defer res.Body.Close()
- if res.StatusCode != http.StatusOK {
- log.Fatalf("failed to get scaleway metadata from %s: %v", metaURL, res.Status)
- }
- if err := json.NewDecoder(res.Body).Decode(&scalewayMeta); err != nil {
- log.Fatalf("invalid JSON from scaleway metadata URL %s: %v", metaURL, err)
- }
-}
-
-func initScalewayDNS() {
- setFileContents("/etc/resolv.conf", []byte("nameserver 8.8.8.8\n"))
-}
-
-func setFileContents(file string, contents []byte) {
- old, err := ioutil.ReadFile(file)
- if err == nil && bytes.Equal(old, contents) {
- return
- }
- if err := ioutil.WriteFile(file, contents, 0644); err != nil {
- log.Fatal(err)
- }
-}
-
-func initScalewaySwap() {
- const swapFile = "/swapfile"
- slurp, _ := ioutil.ReadFile("/proc/swaps")
- if strings.Contains(string(slurp), swapFile) {
- log.Printf("scaleway swapfile already active.")
- return
- }
- os.Remove(swapFile) // if it already exists, else ignore error
- log.Printf("Running fallocate on swapfile")
- if out, err := exec.Command("fallocate", "--length", "16GiB", swapFile).CombinedOutput(); err != nil {
- log.Fatalf("Failed to fallocate /swapfile: %v, %s", err, out)
- }
- log.Printf("Running mkswap")
- if out, err := exec.Command("mkswap", swapFile).CombinedOutput(); err != nil {
- log.Fatalf("Failed to mkswap /swapfile: %v, %s", err, out)
- }
- os.Chmod(swapFile, 0600)
- log.Printf("Running swapon")
- if out, err := exec.Command("swapon", swapFile).CombinedOutput(); err != nil {
- log.Fatalf("Failed to swapon /swapfile: %v, %s", err, out)
- }
-}
-
-func initScalewayWorkdir() {
- const dir = "/workdir"
- slurp, _ := ioutil.ReadFile("/proc/mounts")
- if strings.Contains(string(slurp), dir) {
- log.Printf("scaleway workdir already mounted")
- return
- }
- if err := os.MkdirAll("/workdir", 0755); err != nil {
- log.Fatal(err)
- }
- if out, err := exec.Command("mount",
- "-t", "tmpfs",
- "-o", "size=8589934592",
- "tmpfs", "/workdir").CombinedOutput(); err != nil {
- log.Fatalf("Failed to mount /buildtmp: %v, %s", err, out)
- }
-}
-
-func initScalewayGo14() {
- if fi, err := os.Stat("/usr/local/go"); err == nil && fi.IsDir() {
- log.Printf("go directory already exists.")
- return
- }
- os.RemoveAll("/usr/local/go") // in case it existed somehow, or as regular file
- if err := os.RemoveAll("/usr/local/go.tmp"); err != nil {
- log.Fatal(err)
- }
- if err := os.MkdirAll("/usr/local/go.tmp", 0755); err != nil {
- log.Fatal(err)
- }
- log.Printf("Downloading go1.4-linux-arm.tar.gz")
- if out, err := exec.Command("curl",
- "-o", "/usr/local/go.tmp/go.tar.gz",
- "--silent",
- "https://storage.googleapis.com/go-builder-data/go1.4-linux-arm.tar.gz",
- ).CombinedOutput(); err != nil {
- log.Fatalf("Failed to download go1.4-linux-arm.tar.gz: %v, %s", err, out)
- }
- log.Printf("Extracting go1.4-linux-arm.tar.gz")
- if out, err := exec.Command("tar",
- "-C", "/usr/local/go.tmp",
- "-zx",
- "-f", "/usr/local/go.tmp/go.tar.gz",
- ).CombinedOutput(); err != nil {
- log.Fatalf("Failed to untar go1.4-linux-arm.tar.gz: %v, %s", err, out)
- }
- if err := os.Rename("/usr/local/go.tmp", "/usr/local/go"); err != nil {
- log.Fatal(err)
- }
-}
-
func aptGetInstall(pkgs ...string) {
args := append([]string{"--yes", "install"}, pkgs...)
cmd := exec.Command("apt-get", args...)
diff --git a/cmd/rundockerbuildlet/rundockerbuildlet.go b/cmd/rundockerbuildlet/rundockerbuildlet.go
index 9bf600e..691f56e 100644
--- a/cmd/rundockerbuildlet/rundockerbuildlet.go
+++ b/cmd/rundockerbuildlet/rundockerbuildlet.go
@@ -10,10 +10,12 @@
import (
"bytes"
+ "encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
+ "net/http"
"os"
"os/exec"
"path/filepath"
@@ -29,14 +31,17 @@
keyFile = flag.String("key", "/etc/gobuild.key", "go build key file")
)
-var buildKey []byte
+var (
+ buildKey []byte
+ scalewayMeta = new(scalewayMetadata)
+)
func main() {
flag.Parse()
key, err := ioutil.ReadFile(*keyFile)
if err != nil {
- log.Fatalf("error reading build key from --key=%s: %v", buildKey, err)
+ log.Fatalf("error reading build key from --key=%s: %v", keyFile, err)
}
buildKey = bytes.TrimSpace(key)
@@ -44,6 +49,10 @@
log.Fatalf("docker --image is required")
}
+ if _, err := os.Stat("/usr/local/bin/oc-metadata"); err == nil {
+ initScalewayMeta()
+ }
+
log.Printf("Started. Will keep %d copies of %s running.", *numInst, *image)
for {
if err := checkFix(); err != nil {
@@ -71,7 +80,12 @@
continue
}
container, name, status := f[0], f[1], f[2]
- if !strings.HasPrefix(name, *basename) {
+ prefix := *basename
+ if scalewayMeta != nil {
+ // scaleway containers are named after their instance.
+ prefix = scalewayMeta.Hostname
+ }
+ if !strings.HasPrefix(name, prefix) {
continue
}
if strings.HasPrefix(status, "Exited") {
@@ -85,7 +99,15 @@
}
for num := 1; num <= *numInst; num++ {
- name := fmt.Sprintf("%s%02d", *basename, num)
+ var name string
+ if scalewayMeta != nil {
+ // The -name passed to 'docker run' should match the
+ // c1 instance hostname for debugability.
+ // There should only be one running container per c1 instance.
+ name = scalewayMeta.Hostname
+ } else {
+ name = fmt.Sprintf("%s%02d", *basename, num)
+ }
if running[name] {
continue
}
@@ -113,3 +135,24 @@
}
return nil
}
+
+type scalewayMetadata struct {
+ Name string `json:"name"`
+ Hostname string `json:"hostname"`
+ Tags []string `json:"tags"`
+}
+
+func initScalewayMeta() {
+ const metaURL = "http://169.254.42.42/conf?format=json"
+ res, err := http.Get(metaURL)
+ if err != nil {
+ log.Fatalf("failed to get scaleway metadata: %v", err)
+ }
+ defer res.Body.Close()
+ if res.StatusCode != http.StatusOK {
+ log.Fatalf("failed to get scaleway metadata from %s: %v", metaURL, res.Status)
+ }
+ if err := json.NewDecoder(res.Body).Decode(scalewayMeta); err != nil {
+ log.Fatalf("invalid JSON from scaleway metadata URL %s: %v", metaURL, err)
+ }
+}
diff --git a/cmd/scaleway/scaleway.go b/cmd/scaleway/scaleway.go
index 2b9d84a..f1fc8bb 100644
--- a/cmd/scaleway/scaleway.go
+++ b/cmd/scaleway/scaleway.go
@@ -23,12 +23,15 @@
var (
token = flag.String("token", "", "API token")
org = flag.String("org", "1f34701d-668b-441b-bf08-0b13544e99de", "Organization ID (default is bradfitz@golang.org's account)")
- image = flag.String("image", "bebe2c6f-bbb5-4182-9cce-04cab2f44b2b", "Disk image ID; default is the snapshot we made last")
+ image = flag.String("image", "e488d5e3-d278-47a7-8f7d-1154e1f61dc9", "Disk image ID; default is the snapshot we made last")
num = flag.Int("n", 0, "Number of servers to create; if zero, defaults to a value as a function of --staging")
tags = flag.String("tags", "", "Comma-separated list of tags. The build key tags should be of the form 'buildkey_linux-arm_HEXHEXHEXHEXHEX'. If empty, it's automatic.")
staging = flag.Bool("staging", false, "If true, deploy staging instances (with staging names and tags) instead of prod.")
)
+// ctype is the Commercial Type of server we use for the builders.
+const ctype = "C1"
+
func main() {
flag.Parse()
if *tags == "" {
@@ -76,10 +79,11 @@
tags = append(tags, "staging")
}
body, err := json.Marshal(createServerRequest{
- Org: *org,
- Name: name,
- Image: *image,
- Tags: tags,
+ Org: *org,
+ Name: name,
+ Image: *image,
+ CommercialType: ctype,
+ Tags: tags,
})
if err != nil {
log.Fatal(err)
@@ -115,10 +119,11 @@
}
type createServerRequest struct {
- Org string `json:"organization"`
- Name string `json:"name"`
- Image string `json:"image"`
- Tags []string `json:"tags"`
+ Org string `json:"organization"`
+ Name string `json:"name"`
+ Image string `json:"image"`
+ CommercialType string `json:"commercial_type"`
+ Tags []string `json:"tags"`
}
type Client struct {
diff --git a/dashboard/builders.go b/dashboard/builders.go
index b5d2ec1..70c6fb7 100644
--- a/dashboard/builders.go
+++ b/dashboard/builders.go
@@ -73,21 +73,11 @@
buildletURLTmpl: "http://storage.googleapis.com/$BUCKET/buildlet.linux-amd64",
env: []string{"GOROOT_BOOTSTRAP=/go1.4"},
},
- // TODO(shadams): remove once scaleway docker migration
- // is complete.
- "host-linux-arm": &HostConfig{
- IsReverse: true,
- ExpectNum: 50,
- env: []string{"GOROOT_BOOTSTRAP=/usr/local/go"},
- ReverseAliases: []string{"linux-arm", "linux-arm-arm5"},
- },
- // TODO(shadams): fix reverse aliases to match host-linux-arm
- // once scaleway docker migration is complete.
"host-linux-arm-scaleway": &HostConfig{
IsReverse: true,
ExpectNum: 50,
env: []string{"GOROOT_BOOTSTRAP=/usr/local/go"},
- ReverseAliases: []string{"linux-arm-TMP"},
+ ReverseAliases: []string{"linux-arm", "linux-arm-arm5"},
},
"host-linux-arm5spacemonkey": &HostConfig{
IsReverse: true,
@@ -912,22 +902,14 @@
HostType: "host-linux-sid",
Notes: "Debian sid (unstable)",
})
- // TODO(shadams): change HostType to host-linux-arm-scaleway
- // when scaleway docker migration is complete.
addBuilder(BuildConfig{
Name: "linux-arm",
- HostType: "host-linux-arm",
+ HostType: "host-linux-arm-scaleway",
TryBot: true,
FlakyNet: true,
numTestHelpers: 2,
numTryTestHelpers: 7,
})
- // TODO(shadams): remove when scaleway docker migration
- // is complete.
- addBuilder(BuildConfig{
- Name: "linux-arm-TMP",
- HostType: "host-linux-arm-scaleway",
- })
addBuilder(BuildConfig{
Name: "linux-arm-nativemake",
Notes: "runs make.bash on real ARM hardware, but does not run tests",
diff --git a/env/linux-arm/scaleway/Dockerfile b/env/linux-arm/scaleway/Dockerfile
new file mode 100644
index 0000000..f66c577
--- /dev/null
+++ b/env/linux-arm/scaleway/Dockerfile
@@ -0,0 +1,34 @@
+# Copyright 2017 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+FROM scaleway/ubuntu:armhf-xenial
+
+RUN apt-get update
+
+RUN apt-get install --yes \
+ gcc strace procps psmisc libc6-dev
+
+RUN curl -L -o go1.8.1.tar.gz https://golang.org/dl/go1.8.1.linux-armv6l.tar.gz && \
+ tar fxzv go1.8.1.tar.gz -C /usr/local
+
+ENV GO_BOOTSTRAP=/usr/local/go
+
+# compiled stage0 binary must be in working dir
+COPY stage0 /usr/local/bin/stage0
+
+ENV GO_BUILD_KEY_PATH /buildkey/gobuildkey
+ENV GO_BUILD_KEY_DELETE_AFTER_READ true
+
+# Not really, but we're in a container like Kubernetes, and this makes the syscall
+# package happy:
+ENV IN_KUBERNETES 1
+
+ENV GO_BUILDER_ENV host-linux-arm-scaleway
+
+# env specific
+ARG buildlet_bucket
+
+ENV META_BUILDLET_BINARY_URL "https://storage.googleapis.com/$buildlet_bucket/buildlet.linux-arm"
+
+CMD ["/usr/local/bin/stage0"]
\ No newline at end of file
diff --git a/env/linux-arm/scaleway/Makefile b/env/linux-arm/scaleway/Makefile
new file mode 100644
index 0000000..ab4ae4b
--- /dev/null
+++ b/env/linux-arm/scaleway/Makefile
@@ -0,0 +1,14 @@
+# Copyright 2017 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# Compiled stage0 binary must be in working dir.
+
+STAGING_BUCKET=dev-go-builder-data
+PROD_BUCKET=go-builder-data
+
+staging: Dockerfile
+ docker build --build-arg buildlet_bucket=$(STAGING_BUCKET) -t gobuilder-arm-scaleway:latest .
+
+prod: Dockerfile
+ docker build --build-arg buildlet_bucket=$(PROD_BUCKET) -t gobuilder-arm-scaleway:latest .
diff --git a/env/linux-arm/scaleway/README b/env/linux-arm/scaleway/README
index 3cb5492..516735d 100644
--- a/env/linux-arm/scaleway/README
+++ b/env/linux-arm/scaleway/README
@@ -1,35 +1,28 @@
-To create a fresh builder host: (should only be needed once)
+C1 image created with docker base image using
+`armv7l 4.10.8 docker #1` bootscript.
-Create a new Scaleway server with type "Docker 1.5" (second tab).
+Machines:
-Make a tmpfs:
-# echo "tmpfs /tmpfs tmpfs" >> /etc/fstab
-# mkdir /tmpfs
+ $ ssh -i ~/keys/id_ed25519_golang1 root@$C1_SERVER_IP
+ (key: http://go/go-builders-ssh)
-Make a 2GB swap file:
-# dd if=/dev/zero of=/swap count=2097152 obs=1024
-# mkswap /swap
-# chmod 0600 /swap
-# echo "/swap none swap loop 0 0" >> /etc/fstab
+Machine setup:
+ # local:
+ $ scp env/linux-arm/scaleway/* root@$C1_SERVER_IP:~/scaleway
-Reboot.
+ $ GOARCH=arm GOOS=linux go install golang.org/x/build/cmd/rundockerbuildlet && \
+ scp ~/bin/linux_arm/rundockerbuildlet root@$C1_SERVER_IP:/usr/local/bin
+ $ GOARCH=arm GOOS=linux go install golang.org/x/build/cmd/buildlet/stage0 && \
+ scp ~/bin/linux_arm/stage0 root@$C1_SERVER_IP:~/scaleway
+ # NOTE: commit SHA of rundockerbuildlet/stage0 on instance snapshot (06/19/17)
+ # golang.org/x/build is 282d813.
-Should see swaps in "cat /proc/swaps" and tmpfs in "cat /proc/mounts" now.
+ $ scp rundockerbuildlet.service root@$C1_SERVER_IP:/etc/systemd/user/
-* Copy the contents of this directory to the server.
+ # c1 server:
-* Go into that directory and:
- # docker build -t buildlet .
+ $ echo "<BUILDER KEY>" > /etc/gobuild.key > /root/.gobuildkey
-Add to /etc/rc.local:
+ $ systemctl enable /etc/systemd/user/rundockerbuildlet.service
+ $ systemctl start rundockerbuildlet.service
- (mkdir -p /tmpfs/buildertmp && docker run -e BUILDKEY_ARM=xxx -e BUILDKEY_ARM5=xxx -v=/tmpfs/buildertmp:/tmp --restart=always -d --name=buildlet buildlet) &
- sleep 5
-
-.. before the exit 0
-
-Power it down (with ARCHIVE),
-
-Snapshot the disk.
-
-Start a bunch of them. (see golang.org/x/build/cmd/scaleway)
diff --git a/env/linux-arm/scaleway/buildlet.service b/env/linux-arm/scaleway/buildlet.service
deleted file mode 100644
index 0fee9b1..0000000
--- a/env/linux-arm/scaleway/buildlet.service
+++ /dev/null
@@ -1,16 +0,0 @@
-# See NOTES files
-
-[Unit]
-Description=Go builder buildlet
-After=network.target
-
-[Install]
-WantedBy=network-online.target
-
-[Service]
-Type=simple
-ExecStartPre=/bin/sh -c '/usr/bin/curl -f -o /usr/local/bin/buildlet-stage0 https://storage.googleapis.com/go-builder-data/buildlet-stage0.linux-arm-scaleway?$(date +%s) && chmod +x /usr/local/bin/buildlet-stage0'
-ExecStart=/usr/local/bin/buildlet-stage0
-Restart=always
-RestartSec=2
-StartLimitInterval=0
diff --git a/env/linux-arm/scaleway/rundockerbuildlet.service b/env/linux-arm/scaleway/rundockerbuildlet.service
new file mode 100644
index 0000000..372da1d
--- /dev/null
+++ b/env/linux-arm/scaleway/rundockerbuildlet.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Run Buildlets in Docker
+After=network.target
+
+[Install]
+WantedBy=network-online.target
+
+[Service]
+Type=simple
+ExecStart=/usr/local/bin/rundockerbuildlet -basename=scaleway -image=gobuilder-arm-scaleway:latest -n=1
+Restart=always
+RestartSec=2
+StartLimitInterval=0
\ No newline at end of file
diff --git a/env/linux-arm64/linaro/build.sh b/env/linux-arm64/linaro/build.sh
index 391ecee..63c57a1 100755
--- a/env/linux-arm64/linaro/build.sh
+++ b/env/linux-arm64/linaro/build.sh
@@ -3,5 +3,4 @@
# This is run on the arm64 host with the Dockerfile in the same directory.
# The parent Dockerfile and build.sh (linux-arm64/*) must be in parent directory.
-
(cd ../ && ./build.sh) && docker build -t gobuilder-arm64-linaro:1 .
\ No newline at end of file
diff --git a/env/linux-arm64/packet/README b/env/linux-arm64/packet/README
index 933a9d5..4ad42ba 100644
--- a/env/linux-arm64/packet/README
+++ b/env/linux-arm64/packet/README
@@ -40,7 +40,7 @@
$ GOARCH=arm64 GOOS=linux go install golang.org/x/build/cmd/rundockerbuildlet && \
scp -i ~/keys/id_ed25519_golang1 ~/bin/linux_arm64/rundockerbuildlet root@147.75.109.230:/usr/local/bin
- $ scp -i ~/keys/id_ed25519_golang1 rundockerbuildlet.service root@147.75.109.230:/usr/local/bin:/etc/systemd/user/
+ $ scp -i ~/keys/id_ed25519_golang1 rundockerbuildlet.service root@147.75.109.230:/etc/systemd/user/
$ systemctl enable /etc/systemd/user/rundockerbuildlet.service
$ systemctl start rundockerbuildlet.service
\ No newline at end of file