setup CI and a more complete installation script

Use woodpecker-ci to deploy binaries to the R2 object storage. Use
multi-target builds to deploy multiple binaries for one or more CPU
architectures.

Now the installation script lives on the root of the repo, and it is
more complete checking the machine's requirements, operating system and
cpu architecture.
This commit is contained in:
Nikos Papadakis 2023-07-13 15:33:15 +03:00
parent 749780a1d5
commit 7fe45ca94b
Signed by untrusted user who does not match committer: nikos
GPG key ID: 78871F9905ADFF02
11 changed files with 564 additions and 394 deletions

View file

@ -0,0 +1,32 @@
branches: [main]
matrix:
BUILD_TARGET:
- aarch64-unknown-linux-gnu
- x86_64-unknown-linux-gnu
steps:
build:
image: git.nikos.gg/prymn/rust/aarch64:latest
# when:
# - path: "agent/src/*"
commands:
- protoc --version
- cd agent
- cargo build --release --target "${BUILD_TARGET}"
- mkdir -p "dist/${BUILD_TARGET}"
- cp "target/${BUILD_TARGET}/release/prymn_agent" "dist/${BUILD_TARGET}"
release-binaries:
image: woodpeckerci/plugin-s3
# when:
# - path: "agent/src/*"
settings:
bucket: prymn-static
endpoint: https://75178f9eca227dea51b3db4db2c15a5a.r2.cloudflarestorage.com
access_key:
from_secret: r2_access_key
secret_key:
from_secret: r2_secret_key
source: agent/dist/**/*
target: /

View file

@ -0,0 +1,17 @@
branches: [main]
when:
- path: "get_prymn.sh"
steps:
upload:
image: woodpeckerci/plugin-s3
settings:
bucket: prymn-static
endpoint: https://75178f9eca227dea51b3db4db2c15a5a.r2.cloudflarestorage.com
access_key:
from_secret: r2_access_key
secret_key:
from_secret: r2_secret_key
source: "get_prymn.sh"
target: "/agent/dist"

656
agent/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,12 +8,12 @@ anyhow = "1.0.71"
clap = { version = "4.3.9" } clap = { version = "4.3.9" }
nix = "0.26.2" nix = "0.26.2"
prost = "0.11.9" prost = "0.11.9"
reqwest = { version = "0.11.18", features = ["blocking"] } reqwest = { version = "0.11.18", features = ["blocking", "rustls-tls"], default-features = false }
serde_json = "1.0.99" serde_json = "1.0.99"
sysinfo = { version = "0.29.2", default-features = false } sysinfo = { version = "0.29.2", default-features = false }
tokio = { version = "1.28.2", features = ["rt-multi-thread", "io-util", "process", "macros", "signal"] } tokio = { version = "1.28.2", features = ["rt-multi-thread", "io-util", "process", "macros", "signal"] }
tokio-stream = { version = "0.1.14", features = ["net"] } tokio-stream = { version = "0.1.14", features = ["net"] }
tonic = "0.9.2" tonic = { version = "0.9.2", features = ["tls"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = "0.3.17" tracing-subscriber = "0.3.17"

View file

@ -1,63 +0,0 @@
#!/bin/sh
# shellcheck shell=dash
# A simple script that is run on a remote host to download and install the
# agent binary.
set -u
# For Dev: file:///home/nikos/Projects/prymn/agent/target/debug/prymn_agent
GET_PRYMN_ROOT="${GET_PRYMN_ROOT:?You need to set GET_PRYMN_ROOT to the prymn_agent path for now}"
main() {
# Check for dependencies
check_for_command curl
check_for_command mktemp
local dir
if ! dir="$(mktemp -d)"; then
error "mktemp was not executed successfully"
fi
local file="${dir}/prymn_agent"
local url="${GET_PRYMN_ROOT}"
printf "downloading prymn agent...\n" 1>&2
ensure download "${url}" "${file}"
ensure chmod u+x "$file"
# Run the installer
"$file" --install "$@"
local ret=$?
rm "${file}"
rmdir "${dir}"
return $ret
}
download() {
local err
err=$(curl -sSfL "$1" -o "$2" 2>&1)
if [ -n "$err" ]; then
error "$err"
fi
}
error() {
printf "Error: %s\n" "$1" >&2
exit 1
}
check_for_command() {
if ! command -v "$1" > /dev/null 2>&1; then
error "get_prymn.sh requires the program '$1' but it was not found in your system."
fi
}
ensure() {
"$@" || error "failed to execute: $*"
}
main "$@" || exit 1

View file

@ -3,10 +3,6 @@ use clap::arg;
use prymn_agent::{self_update, server}; use prymn_agent::{self_update, server};
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
if std::env::consts::OS != "linux" {
return Err(anyhow!("platform is not linux!"));
}
tracing_subscriber::fmt().init(); tracing_subscriber::fmt().init();
let command = clap::Command::new(env!("CARGO_BIN_NAME")) let command = clap::Command::new(env!("CARGO_BIN_NAME"))
@ -18,10 +14,10 @@ fn main() -> anyhow::Result<()> {
.unwrap_or_else(|e| e.exit()); .unwrap_or_else(|e| e.exit());
if command.get_flag("daemon") { if command.get_flag("daemon") {
tracing::info!("running as daemon"); tracing::info!("starting agent");
server::main() server::main()
} else if let Some(token) = command.get_one::<String>("install") { } else if let Some(token) = command.get_one::<String>("install") {
tracing::info!("running install mode"); tracing::info!("starting installation...");
self_update::install(token) self_update::install(token)
} else { } else {
unreachable!() unreachable!()

View file

@ -5,7 +5,9 @@ import Config
# manifest is generated by the `mix assets.deploy` task, # manifest is generated by the `mix assets.deploy` task,
# which you should run after static files are built and # which you should run after static files are built and
# before starting your production server. # before starting your production server.
config :prymn, PrymnWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" config :prymn, PrymnWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json",
http: [host: "prymn.net"]
# Configures Swoosh API Client # Configures Swoosh API Client
config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Prymn.Finch config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Prymn.Finch

View file

@ -127,19 +127,10 @@ defmodule Prymn.Servers do
""" """
@spec create_setup_command(%Server{}) :: String.t() @spec create_setup_command(%Server{}) :: String.t()
def create_setup_command(%Server{registration_token: token}) do def create_setup_command(%Server{registration_token: token}) do
build_command = fn token ->
if Application.get_env(:prymn, :environment) == :prod do
"curl -sSf https://static.prymn.net/get_prymn.sh | sh -s " <> token
else
# On a dev environment we want to download the local version of the agent
agent_path = Path.expand("../agent/target/debug/prymn_agent")
get_prymn_path = Path.expand("../agent/get_prymn.sh")
"GET_PRYMN_ROOT=file://#{agent_path} #{get_prymn_path} #{token}"
end
end
token token
|> Base.encode64() |> Base.encode64()
|> then(build_command) |> then(fn token ->
"curl -sSfL " <> PrymnWeb.Endpoint.url() <> "/install | sh -s " <> token
end)
end end
end end

View file

@ -6,4 +6,9 @@ defmodule PrymnWeb.PageController do
# so skip the default app layout. # so skip the default app layout.
render(conn, :home, layout: false) render(conn, :home, layout: false)
end end
def install(conn, _params) do
# TODO: Check for Production vs Development download
redirect(conn, external: "https://static.prymn.net/agent/dist/get_prymn.sh")
end
end end

View file

@ -18,6 +18,7 @@ defmodule PrymnWeb.Router do
pipe_through :browser pipe_through :browser
get "/", PageController, :home get "/", PageController, :home
get "/install", PageController, :install
live "/servers", ServerLive.Index, :index live "/servers", ServerLive.Index, :index
live "/servers/new", ServerLive.Index, :new live "/servers/new", ServerLive.Index, :new

153
get_prymn.sh Executable file
View file

@ -0,0 +1,153 @@
#!/bin/sh
# shellcheck shell=dash
# A simple script that is run on a remote host to download and install the
# agent binary.
set -u
ROOT_URL=${ROOT_URL:-"https://static.prymn.net/agent/dist"}
main() {
if [ $# -eq 0 ]; then
exit_error "missing command parameters. please make sure you copied the installation command correctly."
fi
check_user_is_root
check_os
local arch=${RETURN_VALUE}
check_for_command curl
check_for_command mktemp
local dir
dir="$(mktemp -d)"
local file="${dir}/prymn_agent"
local url="${ROOT_URL}/${arch}/prymn_agent"
printf "info: downloading prymn agent...\n" 1>&2
ensure download "${url}" "${file}"
ensure chmod u+x "${file}"
# Run the installer
"${file}" --install "$@"
local ret=$?
rm "${file}"
rmdir "${dir}"
return $ret
}
check_user_is_root() {
if [ "$(id -u)" -ne 0 ]; then
exit_error "this script must be run as the root user."
fi
}
check_os() {
local os_release os_id os_version_id os_pretty os_old_flag
if test -e /etc/os-release; then
os_release="/etc/os-release"
elif test -e /usr/lib/os-release; then
os_release="/usr/lib/os-release"
else
exit_error "could not determine your operating system; it might not be supported."
fi
# shellcheck disable=SC1090
. "${os_release}"
local os_id=${ID:-0}
local os_version_id=${VERSION_ID:-0}
local os_pretty=${PRETTY_NAME:-Linux}
if [ "${os_id}" = 0 ] || [ "${os_version_id}" = 0 ]; then
exit_error "could not detect your operating system type or version."
fi
case "${os_id}" in
debian)
local min_version=10
if [ "${os_version_id}" -lt ${min_version} ]; then
warning "detected Debian version ${os_version_id} which may be too old."
fi
;;
ubuntu)
local min_version="20.09"
os_old_flag="$(echo "${os_version_id}" | awk -v n=${min_version} '{print $1 < n}')"
if [ "${os_old_flag}" -eq 1 ]; then
warning "detected Ubuntu version ${os_version_id} which may be too old."
fi
;;
*)
exit_error "your operating system ${os_pretty} is not be supported."
;;
esac
local arch clib kernel cputype
# TODO: check for musl (probably not be needed because we support distros
# that don't use it)
clib="gnu"
kernel="$(uname -s)"
cputype="$(uname -m)"
case "${kernel}" in
Linux)
arch=unknown-linux-${clib}
;;
*)
exit_error "your operating system type ${kernel} is not supported."
;;
esac
case "${cputype}" in
x86_64 | x64)
arch="x86_64-${arch}"
;;
aarch64 | arm64)
arch="aarch64-${arch}"
;;
*)
exit_error "your CPU architecture ${cputype} is not supported."
;;
esac
RETURN_VALUE="${arch}"
}
download() {
local err
err=$(curl -sSfL "$1" -o "$2" 2>&1)
if [ -n "$err" ]; then
exit_error "$err"
fi
}
warning() {
printf "warning: %s\n" "$1" >&2
}
exit_error() {
printf "error: %s\n" "$1" >&2
exit 1
}
check_for_command() {
if ! command -v "$1" > /dev/null 2>&1; then
exit_error "the program '$1' is required, but it was not found in your system."
fi
}
ensure() {
"$@" || exit_error "failed to execute: $*"
}
main "$@" || exit 1