#!/usr/bin/env bash # Odincode.ai installer for Linux and macOS. Powered by Odincore.ai. # # curl -fsSL https://odincode.norddahl.dk/install | bash # curl -fsSL https://odincode.norddahl.dk/install | bash -s -- --version # # Downloads odincode--.tar.gz from /releases///, checks its sha256 # (pinned in this script for the current release, otherwise the .sha256 file published next to it) and # installs ~/.odincode/bin/odincode. Unless --no-modify-path is given, it adds that directory to PATH in your # shell profile with a "# odincode" marker line, which `odincode uninstall` removes again. # # This script is generated by the Odincode gateway; the values below come from its release manifest. set -euo pipefail SERVER_DEFAULT="https://odincode.norddahl.dk" CHANNEL="pilot" DEFAULT_VERSION="2.0.21-odc.4" # ":.tar.gz>" pairs for DEFAULT_VERSION PINNED_CHECKSUMS="darwin-arm64:d3df7c92270757589d3aee348bcc651e1a9ebf5a2d6ee1cd0d54c3cce66fa453 linux-x64:c63349a0c22beb31fce1e6d09d66cac34cb43d80359aaab0f57b50f332fe1a5e" APP=odincode INSTALL_DIR="$HOME/.odincode/bin" SERVER="${ODINCODE_SERVER:-$SERVER_DEFAULT}" SERVER="${SERVER%/}" lang=en case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in da*|nb*|nn*|no*) lang=da ;; esac if [ -t 2 ]; then MUTED=$'\033[0;2m' RED=$'\033[0;31m' NC=$'\033[0m' else MUTED="" RED="" NC="" fi say() { printf '%s\n' "$*" >&2 } # t t() { if [ "$lang" = "da" ]; then printf '%s' "$2"; else printf '%s' "$1"; fi } die() { printf '%s%s%s\n' "$RED" "$*" "$NC" >&2 exit 1 } usage() { cat < Install a specific version (default: ${DEFAULT_VERSION:-the current release}) --no-modify-path Do not add ~/.odincode/bin to PATH in shell config files Environment: ODINCODE_SERVER Odincode server to install from (default: $SERVER_DEFAULT) ODINCODE_VERSION Same as --version EOF } requested_version="${ODINCODE_VERSION:-}" no_modify_path=false while [ "$#" -gt 0 ]; do case "$1" in -h|--help) usage exit 0 ;; -v|--version) [ -n "${2:-}" ] || die "$(t "Error: --version needs a version, for example --version 2.0.21-odc.1" "Fejl: --version kræver en version, for eksempel --version 2.0.21-odc.1")" requested_version="$2" shift 2 ;; --version=*) requested_version="${1#--version=}" shift ;; --no-modify-path) no_modify_path=true shift ;; *) say "$(t "Warning: ignoring unknown option" "Advarsel: ignorerer ukendt tilvalg") '$1'" shift ;; esac done # --- server ------------------------------------------------------------------------------------------- case "$SERVER" in https://*) curl_proto=(--proto '=https' --tlsv1.2) ;; http://127.0.0.1|http://127.0.0.1:*|http://127.0.0.1/*|http://localhost|http://localhost:*|http://localhost/*|http://\[::1\]|http://\[::1\]:*|http://\[::1\]/*) curl_proto=() ;; *) die "$(t "Error: ODINCODE_SERVER must be an https:// address (plain http only for this machine):" "Fejl: ODINCODE_SERVER skal være en https://-adresse (almindelig http kun for denne maskine):") $SERVER" ;; esac for tool in curl tar uname mktemp; do command -v "$tool" >/dev/null 2>&1 || die "$(t "Error: this installer needs" "Fejl: installationen kræver") '$tool'." done sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1 else die "$(t "Error: sha256sum or shasum is required to verify the download." "Fejl: sha256sum eller shasum kræves for at kontrollere downloaden.")" fi } fetch() { # fetch : no redirects to plain http, a few retries, progress only on a terminal local progress=(--silent --show-error) if [ -t 2 ] && [ "${3:-}" = "progress" ]; then progress=(--progress-bar); fi curl ${curl_proto[@]+"${curl_proto[@]}"} --fail --location --retry 2 --connect-timeout 20 "${progress[@]}" -o "$2" "$1" } # --- platform ----------------------------------------------------------------------------------------- raw_os="$(uname -s)" case "$raw_os" in Darwin*) os=darwin ;; Linux*) os=linux ;; *) die "$(t "Error: Odincode supports Linux and macOS; this system is" "Fejl: Odincode understøtter Linux og macOS; dette system er") $raw_os." ;; esac arch="$(uname -m)" case "$arch" in x86_64|amd64) arch=x64 ;; aarch64|arm64) arch=arm64 ;; *) die "$(t "Error: unsupported processor architecture:" "Fejl: processorarkitekturen understøttes ikke:") $arch" ;; esac if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = "1" ]; then arch=arm64; fi fi if [ "$os" = "linux" ]; then if [ -f /etc/alpine-release ] || { command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; }; then die "$(t "Error: musl-based Linux (for example Alpine) is not supported yet." "Fejl: musl-baseret Linux (for eksempel Alpine) understøttes ikke endnu.")" fi fi target="$os-$arch" # --- version ------------------------------------------------------------------------------------------ if [ -n "$requested_version" ]; then version="${requested_version#v}" elif [ -n "$DEFAULT_VERSION" ]; then version="$DEFAULT_VERSION" else metadata="$(curl ${curl_proto[@]+"${curl_proto[@]}"} --fail --location --silent --show-error "$SERVER/update/api/$CHANNEL/cli/odincode" || true)" version="$(printf '%s' "$metadata" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p' | head -n 1)" [ -n "$version" ] || die "$(t "Error: could not read the current Odincode version from" "Fejl: kunne ikke læse den aktuelle Odincode-version fra") $SERVER." fi case "$version" in *[!0-9A-Za-z.+_-]*|"") die "$(t "Error: invalid version:" "Fejl: ugyldig version:") $version" ;; esac expected="" if [ "$version" = "$DEFAULT_VERSION" ]; then for pair in $PINNED_CHECKSUMS; do if [ "${pair%%:*}" = "$target" ]; then expected="${pair#*:}"; fi done if [ -n "$PINNED_CHECKSUMS" ] && [ -z "$expected" ]; then die "$(t "Error: Odincode $version is not available for $target." "Fejl: Odincode $version findes ikke til $target.")" fi fi # --- download and verify ------------------------------------------------------------------------------ archive_name="$APP-$target.tar.gz" url="$SERVER/releases/$CHANNEL/$version/$archive_name" tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/odincode-install.XXXXXX")" staged="" cleanup() { rm -rf "$tmp_dir" if [ -n "$staged" ]; then rm -f "$staged"; fi } trap cleanup EXIT say "" say "${MUTED}$(t "Installing" "Installerer")${NC} Odincode $version ${MUTED}($target)${NC}" if [ -z "$expected" ]; then fetch "$url.sha256" "$tmp_dir/archive.sha256" || die "$(t "Error: Odincode $version is not available for $target (no checksum at" "Fejl: Odincode $version findes ikke til $target (ingen kontrolsum på") $url.sha256)." expected="$(cut -d' ' -f1 <"$tmp_dir/archive.sha256" | tr -d '\r\n')" fi case "$expected" in *[!0-9a-f]*|"") expected="" ;; esac if [ "${#expected}" -ne 64 ]; then die "$(t "Error: the published checksum is not a sha256 value." "Fejl: den offentliggjorte kontrolsum er ikke en sha256-værdi.")" fi fetch "$url" "$tmp_dir/$archive_name" progress || die "$(t "Error: download failed:" "Fejl: download mislykkedes:") $url" actual="$(sha256_of "$tmp_dir/$archive_name")" if [ "$actual" != "$expected" ]; then die "$(t "Error: checksum mismatch for $archive_name (expected $expected, got $actual). Nothing was installed." "Fejl: kontrolsummen for $archive_name passer ikke (forventet $expected, fik $actual). Intet blev installeret.")" fi say "${MUTED}sha256 OK${NC} $actual" # The archive holds exactly one file, `odincode`. entries="$(tar -tzf "$tmp_dir/$archive_name")" [ "$entries" = "$APP" ] || die "$(t "Error: unexpected archive contents; nothing was installed." "Fejl: uventet indhold i arkivet; intet blev installeret.")" mkdir -p "$tmp_dir/x" tar -xzf "$tmp_dir/$archive_name" -C "$tmp_dir/x" if [ ! -f "$tmp_dir/x/$APP" ] || [ -L "$tmp_dir/x/$APP" ]; then die "$(t "Error: the archive did not contain the odincode program." "Fejl: arkivet indeholdt ikke odincode-programmet.")" fi # --- install (atomic replace; the directory holds only the binary afterwards) ------------------------ mkdir -p "$INSTALL_DIR" staged="$INSTALL_DIR/.$APP.install.$$" cp "$tmp_dir/x/$APP" "$staged" chmod 755 "$staged" mv -f "$staged" "$INSTALL_DIR/$APP" staged="" # --- PATH --------------------------------------------------------------------------------------------- add_to_path() { local config_file="$1" local command="$2" if grep -Fxq "$command" "$config_file" 2>/dev/null; then say "${MUTED}$(t "PATH entry already present in" "PATH-linjen findes allerede i")${NC} $config_file" elif [ -w "$config_file" ]; then printf '\n# odincode\n%s\n' "$command" >>"$config_file" say "${MUTED}$(t "Added ~/.odincode/bin to PATH in" "Tilføjede ~/.odincode/bin til PATH i")${NC} $config_file" else say "$(t "Add this line to $config_file (or a similar file):" "Tilføj denne linje til $config_file (eller en lignende fil):")" say " $command" fi } if [ "$no_modify_path" != "true" ]; then XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" current_shell="$(basename "${SHELL:-bash}")" case "$current_shell" in fish) config_files=("$HOME/.config/fish/config.fish") ;; zsh) config_files=("${ZDOTDIR:-$HOME}/.zshrc" "${ZDOTDIR:-$HOME}/.zshenv" "$XDG_CONFIG_HOME/zsh/.zshrc" "$XDG_CONFIG_HOME/zsh/.zshenv") ;; bash) config_files=("$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile" "$XDG_CONFIG_HOME/bash/.bashrc" "$XDG_CONFIG_HOME/bash/.bash_profile") ;; ash|sh) config_files=("$HOME/.ashrc" "$HOME/.profile") ;; *) config_files=("$HOME/.bashrc" "$HOME/.bash_profile" "$XDG_CONFIG_HOME/bash/.bashrc" "$XDG_CONFIG_HOME/bash/.bash_profile") ;; esac config_file="" for file in "${config_files[@]}"; do if [ -f "$file" ]; then config_file="$file" break fi done if [ "$current_shell" = "fish" ]; then path_command="fish_add_path $INSTALL_DIR" else path_command="export PATH=\"$INSTALL_DIR:\$PATH\"" fi if [ -z "$config_file" ]; then say "$(t "No shell config file found for $current_shell. Add this line to your shell profile:" "Ingen konfigurationsfil fundet for $current_shell. Tilføj denne linje til din shell-profil:")" say " $path_command" elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then add_to_path "$config_file" "$path_command" fi fi if [ "${GITHUB_ACTIONS:-}" = "true" ] && [ -n "${GITHUB_PATH:-}" ]; then printf '%s\n' "$INSTALL_DIR" >>"$GITHUB_PATH" fi say "" say "Odincode.ai $version $(t "installed in" "installeret i") $INSTALL_DIR/$APP" say "${MUTED}Powered by Odincore.ai${NC}" say "" say "$(t "Next steps:" "Næste skridt:")" # The installer runs in a child shell (curl | bash), so it cannot change the PATH of the user's current terminal. # Print the exact command that activates it there, instead of only "open a new terminal". if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then if [ "${no_modify_path:-false}" != "true" ] && [ -n "${config_file:-}" ] && [ "${current_shell:-}" != "fish" ]; then activate="source $config_file" else activate="export PATH=\"$INSTALL_DIR:\$PATH\"" fi say " $activate ${MUTED}# $(t "activate odincode in this terminal (new terminals get it automatically)" "aktivér odincode i denne terminal (nye terminaler får det automatisk)")${NC}" fi say " odincode login ${MUTED}# $(t "sign in with your work e-mail" "log ind med din arbejdsmail")${NC}" say " cd && odincode"