#!/bin/bash set -o errexit -o nounset -o pipefail # Color output setup if [ -t 1 ]; then COLOR_BOLD="\033[1m" COLOR_GREEN="\033[32m" COLOR_RESET="\033[0m" else COLOR_BOLD="" COLOR_GREEN="" COLOR_RESET="" fi color_echo() { # Print with color unless line starts with two spaces if [[ "$1" =~ ^\ \ ]]; then echo "$1" else echo -e "${COLOR_BOLD}${COLOR_GREEN}$1${COLOR_RESET}" fi } color_printf() { # Print with color unless first arg starts with two spaces local fmt="$1" if [[ "$fmt" =~ ^\ \ ]]; then printf "$@" else echo -e "${COLOR_BOLD}${COLOR_GREEN}" printf "$@" echo -e "${COLOR_RESET}" fi } print_command() { if [ ! -t 0 ]; then while IFS= read -r line; do printf ' | %s\n' "$line" >&2 done fi printf ' > %s\n' "$*" >&2 } install_packages() { local packages=("$@") if [ "${#packages[@]}" -eq 0 ]; then return fi color_echo "Installing '${packages[*]}' (requires sudo privileges)..." local package_manager="" if command -v dnf &>/dev/null; then package_manager=dnf elif command -v apt &>/dev/null; then package_manager=apt elif command -v pacman &>/dev/null; then package_manager=pacman else echo "Could not find a supported package manager to install '${packages[*]}'." exit 1 fi case "$package_manager" in dnf) $SUDO_OR_PRINT dnf install -y "${packages[@]}" ;; apt) $SUDO_OR_PRINT apt update $SUDO_OR_PRINT apt install -y "${packages[@]}" ;; pacman) $SUDO_OR_PRINT pacman -Sy --noconfirm "${packages[@]}" ;; esac } SUDO_OR_PRINT="sudo" if [ "${1:-}" = "--dry-run" ]; then SUDO_OR_PRINT=print_command echo "Running in dry-run mode. No changes will be made." fi # Ensure gh CLI and jq are installed required_pkgs=() for pkg in gh jq; do command -v "$pkg" &>/dev/null || required_pkgs+=("$pkg") done install_packages "${required_pkgs[@]}" if ! gh auth status &>/dev/null; then echo "GitHub CLI is not authenticated. Attempting to log in..." gh auth login || { echo "Failed to authenticate with GitHub CLI." exit 1 } fi # Fetch members and real names using GraphQL GF_GRAPHQL_QUERY=' query { organization(login: "CGAL") { team(slug: "GeometryFactory") { members(first: 100) { edges { node { login name publicKeys(first: 100) { edges { node { id key } } } } } } } } }' # Fetch team members and their names from GitHub GraphQL API GRAPH_QL_RESULT=$(gh api graphql -f query="$GF_GRAPHQL_QUERY" --jq '.data.organization.team.members.edges.[].node') if [ -z "$GRAPH_QL_RESULT" ]; then echo "Error: No members found in the GeometryFactory team." >&2 exit 1 fi GF_MEMBERS=() declare -A GF_REAL_NAMES=() declare -A GF_SSH_KEYS=() # Parse the JSON result for logins, and names while IFS=$'\t' read -r login name; do GF_MEMBERS+=("$login") GF_REAL_NAMES["$login"]="$name" done < <( jq -r '[ .login, .name ] | @tsv' <<<"$GRAPH_QL_RESULT" ) # Parse the SSH keys and associate them with the respective members for login in "${GF_MEMBERS[@]}"; do keys=$(jq -r --arg login "$login" 'select(.login == $login) | (.publicKeys.edges[].node.key // empty)' <<<"$GRAPH_QL_RESULT") GF_SSH_KEYS["$login"]="" while IFS=$'\n' read -r key; do [[ -z "$key" ]] && continue GF_SSH_KEYS["$login"]+="$key"$'\n' done <<<"$keys" done # Mapping from GitHub login to local username (if different) declare -A GF_USERNAMES=( ["janetournois"]="jtournois" ["LeoValque"]="lvalque" ["MaelRL"]="mrouxell" ["SaillantNicolas"]="nsaillant" ) # Correcting the real names for specific users GF_REAL_NAMES["LeoValque"]="Léo Valque" GF_REAL_NAMES["MaelRL"]="Maël Rouxel-Labbé" GF_REAL_NAMES["sloriot"]="Sébastien Loriot" # Mapping from GitHub login to extra groups (if any) declare -A GF_GROUPS=( ["lrineau"]="wheel,docker" ["SaillantNicolas"]="wheel,docker" ["sloriot"]="wheel,docker" ) color_echo "Members of the geometryfactory team:" for m in "${GF_MEMBERS[@]}"; do keys="${GF_SSH_KEYS[$m]}" nb_of_keys=$(printf '%s' "$keys" | wc -l) color_printf " %s (%s) has %d ssh keys in Github\n" "$m" "${GF_REAL_NAMES[$m]:-$m}" "$nb_of_keys" done # Ensure groups exists for group in geometryfactory docker; do if ! getent group "$group" >/dev/null; then $SUDO_OR_PRINT groupadd "$group" fi done for member in "${GF_MEMBERS[@]}"; do user="${GF_USERNAMES[$member]:-$member}" groups="geometryfactory" if [[ -n "${GF_GROUPS[$member]:-}" ]]; then groups="geometryfactory,${GF_GROUPS[$member]}" fi if ! getent passwd "$user" >/dev/null; then color_echo "Creating user $user and adding to group(s) $groups." $SUDO_OR_PRINT useradd -m -s /bin/bash -c "${GF_REAL_NAMES[$member]:-$user}" "$user" -G "$groups" else color_echo "User $user already exists, adding to group(s) $groups." $SUDO_OR_PRINT usermod -aG "$groups" -c "${GF_REAL_NAMES[$member]:-$user}" "$user" fi $SUDO_OR_PRINT chmod g+rx,o+rx "/home/$user" $SUDO_OR_PRINT mkdir -p "/home/$user/.ssh" $SUDO_OR_PRINT chown "$user:$user" "/home/$user/.ssh" $SUDO_OR_PRINT chmod 700 "/home/$user/.ssh" # Add SSH keys from GraphQL color_echo " Adding SSH keys for user $member from Github." count=0 auth_keys_file="/home/$user/.ssh/authorized_keys" if ! sudo test -f "$auth_keys_file"; then $SUDO_OR_PRINT install -m 600 -o "$user" -g "$user" /dev/null "$auth_keys_file" fi if [[ -n "${GF_SSH_KEYS[$member]:-}" ]]; then while IFS=$'\n' read -r key; do [[ -z "$key" ]] && continue if ! $SUDO_OR_PRINT grep -qF -- "$key" "$auth_keys_file" /dev/null fi count=$((count + 1)) fi done <<<"${GF_SSH_KEYS[$member]}" fi color_echo " Added $count SSH keys for user $user." key_count=$(sudo bash -c "[ -f '$auth_keys_file' ] && wc -l <'$auth_keys_file' || echo 0") color_echo " User $user has $key_count SSH keys in $auth_keys_file" done # Add cgaltest user if not exists, and create SSH key if ! getent passwd cgaltest >/dev/null; then color_echo "Creating user cgaltest." $SUDO_OR_PRINT useradd -m -s /bin/bash -c "CGAL Test User" cgaltest fi $SUDO_OR_PRINT chmod g+rx,o+rx "/home/cgaltest" $SUDO_OR_PRINT mkdir -p /home/cgaltest/.ssh $SUDO_OR_PRINT chown cgaltest:cgaltest /home/cgaltest/.ssh $SUDO_OR_PRINT chmod 700 /home/cgaltest/.ssh $SUDO_OR_PRINT usermod -aG docker cgaltest $SUDO_OR_PRINT loginctl enable-linger cgaltest keyfile="/home/cgaltest/.ssh/id_ed25519" if ! $SUDO_OR_PRINT test -f "$keyfile"; then color_echo "Generating SSH key for cgaltest." if [ "$SUDO_OR_PRINT" = "sudo" ]; then sudo -u cgaltest ssh-keygen -t ed25519 -N "" -f "$keyfile" else ssh-keygen -t ed25519 -N "" -f "$keyfile" fi else color_echo "SSH key for cgaltest already exists." $SUDO_OR_PRINT cat "$keyfile.pub" fi color_echo "All users have been processed." color_echo "Remove users not in the GeometryFactory team." current_members=$(getent group geometryfactory | awk -F: '{print $4}' | tr ',' ' ') for user in $current_members; do # Map local usernames to GitHub logins if needed login="$user" for k in "${!GF_USERNAMES[@]}"; do if [[ "${GF_USERNAMES[$k]}" == "$user" ]]; then login="$k" break fi done found=false for m in "${GF_MEMBERS[@]}"; do if [[ "$login" == "$m" ]]; then found=true break fi done if ! $found; then color_echo " Removing user $user but keep its home directory." read -p " Are you sure you want to remove user $user? [y/N] " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then color_echo " Skipping removal of user $user." continue fi $SUDO_OR_PRINT userdel -f "$user" fi done # Optionally install bat, strace, and podman-docker if not present need_install=() for cmd in bat strace python3-docker python3-pyxdg python3-sdnotify; do command -v "$cmd" &>/dev/null || need_install+=("$cmd") done # Check if docker is missing or not emulated by podman need_podman_docker=false if ! command -v docker &>/dev/null; then need_podman_docker=true elif docker --version 2>&1 | grep -q "podman"; then need_podman_docker=true fi if $need_podman_docker; then if [[ ! " ${need_install[*]} " =~ " podman-docker " ]]; then need_install+=("podman-docker") fi fi install_packages "${need_install[@]}" color_echo "Update the systemd tmpfiles configuration for podman." $SUDO_OR_PRINT touch /etc/containers/nodocker $SUDO_OR_PRINT mkdir -p /etc/tmpfiles.d sed 's|podman 0700 root root|podman 0770 root docker|g' /usr/lib/tmpfiles.d/podman.conf | $SUDO_OR_PRINT tee /etc/tmpfiles.d/podman.conf >/dev/null color_echo "Update the systemd socket configuration for podman." $SUDO_OR_PRINT mkdir -p /etc/systemd/system/podman.socket.d $SUDO_OR_PRINT tee /etc/systemd/system/podman.socket.d/override.conf >/dev/null <