Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions scripts/vm_admin/opt_admin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/bin/bash
# Utility functions for managing VM global packages

function install_conda_globally() {
local group_owner="${1:-root}"

# shellcheck disable=SC2155
local installer="Miniforge3-$(uname)-$(uname -m).sh"
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/$installer"

sudo bash "$installer" -b -p /opt/conda

# shellcheck disable=SC2016
echo 'export PATH=$PATH:/opt/conda/bin' | sudo tee /etc/profile.d/conda.sh
sudo chmod +x /etc/profile.d/conda.sh

sudo tee /opt/conda/.condarc >/dev/null <<'EOF'
pkgs_dirs:
- /opt/conda/pkgs
envs_dirs:
- ~/scratch/.conda/envs
auto_activate_base: false
EOF

sudo chown -R root:root /opt/conda
sudo chmod -R a+rX /opt/conda

sudo mkdir -p /opt/conda/pkgs
sudo chown -R root:"$group_owner" /opt/conda/pkgs
sudo chmod -R 2775 /opt/conda/pkgs

rm -f "$installer"

echo "Conda installed globally in /opt/conda."
echo "Shared package cache at /opt/conda/pkgs (group: $group_owner)."
echo "User environments will default to ~/scratch/.conda/envs."
echo "Reload your shell or run: source /etc/profile.d/conda.sh"
}

function install_pypoetry_globally() {
local group_owner="${1:-root}"

local install_dir="/opt/poetry"
local cache_dir="/opt/poetry/cache"

if ! command -v python3 >/dev/null 2>&1; then
echo "Python3 not found. Installing..."
sudo apt update
sudo apt install -y python3 python3-venv python3-pip
fi

curl -sSL https://install.python-poetry.org | sudo env POETRY_HOME="$install_dir" python3 -

sudo chown -R root:"$group_owner" "$install_dir"
sudo chmod -R 755 "$install_dir"

sudo mkdir -p "$cache_dir"
sudo chown -R root:"$group_owner" "$cache_dir"
sudo chmod -R 2775 "$cache_dir"

sudo tee /etc/profile.d/poetry.sh >/dev/null <<EOF
export PATH=\$PATH:$install_dir/bin
export POETRY_CACHE_DIR=$cache_dir
EOF
sudo chmod +x /etc/profile.d/poetry.sh

echo "Poetry installed globally in $install_dir."
echo "Global Poetry cache: $cache_dir (group: $group_owner)."
echo "Reload your shell or run: source /etc/profile.d/poetry.sh"
}

function install_uv_globally() {
local group_owner="${1:-root}"

local install_dir="/opt/uv"
local cache_dir="/opt/uv/cache"
local python_install_dir="/opt/uv/python"
local python_bin_dir="/opt/uv/bin"

curl -LsSf https://astral.sh/uv/install.sh | sudo env UV_INSTALL_DIR="$install_dir" sh

sudo chown -R root:"$group_owner" "$install_dir"
sudo chmod -R 755 "$install_dir"

sudo mkdir -p "$cache_dir"
sudo chown -R root:"$group_owner" "$cache_dir"
sudo chmod -R 2775 "$cache_dir"

sudo mkdir -p "$python_install_dir"
sudo chown -R root:"$group_owner" "$python_install_dir"
sudo chmod -R 2775 "$python_install_dir"

sudo mkdir -p "$python_bin_dir"
sudo chown -R root:"$group_owner" "$python_install_dir"
sudo chmod -R 2775 "$python_install_dir"
Comment on lines +94 to +95

Copilot AI Sep 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The chown and chmod operations on lines 94-95 should target $python_bin_dir instead of $python_install_dir, as the mkdir operation on line 93 creates the $python_bin_dir directory.

Suggested change
sudo chown -R root:"$group_owner" "$python_install_dir"
sudo chmod -R 2775 "$python_install_dir"
sudo chown -R root:"$group_owner" "$python_bin_dir"
sudo chmod -R 2775 "$python_bin_dir"

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +95

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be $python_bin_dir?


sudo tee /etc/profile.d/uv.sh >/dev/null <<EOF
export PATH=$install_dir:\$PATH
export UV_CACHE_DIR=$cache_dir
export UV_PYTHON_INSTALL_DIR=$python_install_dir
export UV_PYTHON_BIN_DIR=$python_bin_dir
EOF
sudo chmod +x /etc/profile.d/uv.sh

echo "uv installed globally in $install_dir."
echo "Shared cache: $cache_dir"
echo "Reload your shell or run: source /etc/profile.d/uv.sh"
}
215 changes: 215 additions & 0 deletions scripts/vm_admin/user_admin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#!/bin/bash
# Utility functions for managing VM users

function delete_user_and_dirs {
local username="$1"; shift
local basedir=""
local force="false"

while [[ $# -gt 0 ]]; do
case "$1" in
--basedir)
basedir="$2";
shift 2
;;
--force)
force="true";
shift
;;
*)
echo "Unknown option for delete_user_and_dirs: $1" >&2; exit 1 ;;
esac
done

local userdir=""
if [[ -n "$basedir" ]]; then
userdir="$basedir/$username"
fi

sudo deluser --remove-home "$username" || true

if [[ -n "$userdir" && -d "$userdir" ]]; then

if [[ "$force" == "true" ]]; then
echo "Force enabled: deleting $userdir"
sudo rm -rf "$userdir" || true
else
echo "Skipping deletion of $userdir (use --force to override)"
fi
fi
}

function add_user_to_groups() {
local username="$1"; shift
local groups=("$@")

if [ ${#groups[@]} -eq 0 ]; then
echo "No groups specified for user"
return 1
fi

for group in "${groups[@]}"; do
if getent group "$group" >/dev/null; then
echo "Adding $username to $group..."
sudo usermod -aG "$group" "$username"
else
echo "Group $group does not exist, skipping."
fi
done
}

function create_user_and_dirs() {
local username="$1"; shift
local basedir=""
local force="false"
local groups=()

while [[ $# -gt 0 ]]; do
case "$1" in
--basedir)
basedir="$2";
if [[ ! -d "$basedir" ]]; then
echo "Error: base directory '$basedir' does not exist."
return 1
fi
shift 2
;;
--force)
force="true";
shift
;;
--group)
groups+=("$2")
shift 2
;;
*)
echo "Unknown option for create_user_and_dirs: $1" >&2; exit 1 ;;
esac
done

local userdir=""
if [[ -n "$basedir" ]]; then
userdir="$basedir/$username"

if [[ -d "$userdir" ]]; then
echo "Error: userdir '$userdir' already exists."
return 1
fi
fi

# shellcheck disable=SC2064
trap "delete_user_and_dirs '$username' ${basedir:+--basedir \"$basedir\"} ${force:+--force}" ERR

sudo adduser "$username"

local homedir
homedir=$(eval echo "~$username")

local scratch_link="$homedir/scratch"
if [[ -e "$scratch_link" || -L "$scratch_link" ]]; then
sudo rm -rf "$scratch_link"
fi

if [[ -n "$userdir" ]]; then
if [[ -d "$userdir" && "$force" == "true" ]]; then
sudo rm -rf "$userdir"
fi

sudo mkdir -p "$userdir"
sudo chown -R "$username" "$userdir"
sudo chmod -R 700 "$userdir"

sudo ln -s "$userdir" "$scratch_link"
sudo chown -h "$username:$username" "$scratch_link"

add_user_to_groups "$username" "${groups[@]}"

Copilot AI Sep 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function call will fail if the groups array is empty because add_user_to_groups returns 1 when no groups are specified, but this is inside the conditional block that only executes when userdir is set. Consider moving this outside the conditional or handling the empty groups case differently.

Copilot uses AI. Check for mistakes.
fi

trap - ERR
echo "User '$username' created successfully${userdir:+ with home dir $userdir}"
}

function setup_authorized_keys() {
local username="$1"
local pubkey="${2:-}"

local homedir
homedir=$(eval echo "~$username")

local sshdir="$homedir/.ssh"
local authfile="$sshdir/authorized_keys"

sudo mkdir -p "$sshdir"
sudo chown "$username:$username" "$sshdir"
sudo chmod 700 "$sshdir"

sudo touch "$authfile"
sudo chown "$username:$username" "$authfile"
sudo chmod 600 "$authfile"

if [[ -n "$pubkey" ]]; then
echo "$pubkey" | sudo tee -a "$authfile" > /dev/null
sudo chown "$username:$username" "$authfile"
fi

echo "authorized_keys set up for $username at $authfile"
}

function setup_user_env() {
local username="$1"
local userdir="$2" # e.g., /mnt/data/eniyansoro
local shell_profile

local homedir
homedir=$(eval echo "~$username")

if [[ ! -d "$userdir" ]]; then
echo "Error: userdir '$userdir' does not exist."
return 1
fi

# Choose a shell profile to update
if [[ -f "$homedir/.bashrc" ]]; then
shell_profile="$homedir/.bashrc"
else
shell_profile="$homedir/.profile"
fi

sudo bash -c "echo 'export XDG_CACHE_HOME=\"$userdir/.cache\"' >> $shell_profile"
sudo chown "$username:$username" "$shell_profile"

sudo mkdir -p "$userdir/.cache"
sudo chown "$username:$username" "$userdir/.cache"
sudo chmod 700 "$userdir/.cache"

echo "Set XDG_CACHE_HOME for $username to $userdir/.cache"
}

# -------------------------------------------------------
# DEPRECATED: We now set XDG_CACHE_HOME in setup_user_env
# -------------------------------------------------------
# function symlink_user_cache() {
# local username="$1"
# local target_basedir="$2"

# local homedir
# homedir=$(eval echo "~$username")

# local target_cache="$target_basedir/.cache"
# local user_cache="$homedir/.cache"

# sudo mkdir -p "$target_cache"
# sudo chown "$username:$username" "$target_cache"
# sudo chmod 700 "$target_cache"

# if [[ -e "$user_cache" || -L "$user_cache" ]]; then
# sudo rm -rf "$user_cache"
# fi


# sudo ln -s "$target_cache" "$user_cache"
# sudo chown -h "$username:$username" "$user_cache"

# echo "Linked $homedir/.cache -> $target_cache"
# }
# -------------------------------------------------------