#!/bin/bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)prepare-tenancy 3.2.0 13.09.2025 (c)2025 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # #@ Run this script in CloudShell to provide a basic set of tooling and useful resources. # # Exit codes: # 01: Unsupported platform. # 02: No 'curl', 'oci' or 'jq' in PATH. # 03: No administrator privileges (needed to proceed). # 04: No internet connection. # 05: Unknown condition. # # Infos: # OCI_CLI_CLOUD_SHELL: "True" if CloudShell is in use (set by CloudShell) # OCI_TENANCY: OCID of the tenancy (set by CloudShell) # OCI_TENANCY_NAME: Name of the tenancy # OCI_REGION: Region where CloudShell is started in (set by CloudShell) # OCI_HOME_REGION_KEY: e.g. FRA # OCI_HOME_REGION: e.g. eu-frankfurt-1 # OCI_CS_USER_OCID: OCID of CloudShell user # OCI_CS_USER_NAME: Name of CloudShell user # OCI_CLOUD_ADMIN_NAME: Name of technical user "CloudAdmin" if configuration variable "cloudadmin" is set, otherwise name of ClouShell user # OCI_CLOUD_ADMIN_OCID: OCID of technical user "CloudAdmin" if configuration variable "cloudadmin" is set, otherwise OCID of ClouShell user # OCI_ADMINISTRATORS_OCID: OCID of group "Administrators" in Default domain # OCI_NAMESPACE: e.g. frelkczkpnqg # # Update history: # # V 3.2.0 13.09.2025 New version # # Set configuration variables rotatelog="false" # Set variable to true to overwrite logfiles each time script is started otherwise set variable to false cloudadmin="CloudAdmin" # Name of technical user with administrator rights. Leave empty to use CloudShellUser instead. opsname="Operations" # Name of the Operations compartment - Cannot be empty but can be renamed to anything useful netwname="Network" # Name of the Network compartment - Leave empty if not needed appname="Application" # Name of the Operations compartment - Leave empty if not needed dbname="Database" # Name of the Operations compartment - Leave empty if not needed images="OL9Base.oci OL9Bastion.oci" # Leave empty if you don't need master images # Repository where we load our scripts from readonly storageurl="https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/frcc4jd4wdkp/b/download/o" # Set some defaults exitcode=0 progstr="prepare-tenancy" scriptsfile="install-scripts" toolsfile="install-tools" logdir="${HOME}/logs" logfile="${logdir}/${progstr}.log" tcfgfile="${HOME}/.admintools" ocifolder="${HOME}/.oci" ocijson="${ocifolder}/config.json" projectjson="${ocifolder}/project.json" blzjson="${ocifolder}/blz.json" imp_apikey="${ocifolder}/oci_api_key.pem" # Key to import imp_tenancykey="${HOME}/.ssh/id_rsa" # Key to import jumphostname="jumphost" imp_jumphostkey="${HOME}/.ssh/${jumphostname}_id_rsa" # Key to import scratch="/tmp/scratch.tmp" keysfile="/tmp/keys.txt" usersfile="/tmp/users.txt" # Environment variables export ENVELOPE_TABLE=false # Suppress "content" in print-table export LOGFILE="$logfile" # Write everything to this logfile # Define useful aliases shopt -s expand_aliases alias stripcomment="sed 's|#.*$||'" # Ignore all comments alias tolower="tr '[:upper:]' '[:lower:]'" # Lowercase string alias toupper="tr '[:lower:]' '[:upper:]'" # Uppercase string alias ll="ls -als" # Full directory listing alias more="less -R" # Show color codes alias tailfromline2="tail -n +2" # Ignore first line alias taillastline="tail -n 1" # Only lis last line # Get current user and os ME=`whoami` # Current user OS=`uname -s` # Infos about the host os (e.g. Darwin, SunOS, Linux) # Set PATH PATH="/bin:/.local/bin:/usr/local/bin:$PATH" # Check for some tools curl=`which "curl" 2>/dev/null | sed 's|^no curl in .*||'` jq=`which "jq" 2>/dev/null | sed 's|^no jq in .*||'` oci=`which "oci" 2>/dev/null | sed 's|^no oci in .*||'` rclone=`which "rclone" 2>/dev/null | sed 's|^no rclone in .*||'` setuptools=`which "setup-tools" 2>/dev/null | sed 's|^no setup-tools in .*||'` function WriteLog { param=${1} if [ "$logfile" != "" ]; then logdir=`dirname "$logfile"` curdate=`date "+%Y-%m-%d %T"` # Check if logdir exists - otherwise create it if [ ! -d "$logdir" ]; then mkdir -m 0755 -p "$logdir" fi # Check if logfile exists - otherwise create it if [ ! -f "$logfile" ]; then touch "$logfile" fi printf "%s\t%s\n" "$curdate" "$param" >> "$logfile" fi } # Delete file function DeleteFile { local filename=${1} if [ "$filename" != "" ]; then if [ -f "$filename" ]; then rm -f "$filename" fi fi } # Delete tempfiles function Cleanup { DeleteFile "/tmp/$scriptsfile" DeleteFile "/tmp/$toolsfile" DeleteFile "${scratch}.pre" DeleteFile "${scratch}.err" DeleteFile "$scratch" DeleteFile "$keysfile" DeleteFile "$usersfile" # Reset terminal tset } # Download file from standby.cloud function DownloadFile { local filename=${1} local beta=${2} local stage="latest" local grepres="" local code="" local stat=1 if [ "$filename" != "" ]; then if [ "$beta" = "true" ]; then stage="beta" fi $curl -skL "${storageurl}/${stage}/$filename" -o "/tmp/$filename" stat=$? if [ $stat -eq 0 -a -r "/tmp/$filename" ]; then grepres=`grep '^{"code":"' "/tmp/$filename"` if [ "$grepres" != "" ]; then code=`echo "$grepres" | cut -d'"' -f4` echo "Errorcode: '$code'" stat=1 else chmod 755 "/tmp/$filename" fi fi fi return $stat } # Check for internet connection function CheckInternet { local i=0 local max=10 local stat=1 while [ $stat -ne 0 -a $i -lt $max ]; do DownloadFile "$scriptsfile" "false" stat=$? if [ $stat -ne 0 ]; then # Pause 3 seconds sleep 3 fi let "i++" done if [ $i -eq $max ]; then exitcode=4 echo "No internet connection - stat: '$stat'." Cleanup exit 4 fi } # Check for internet connection function GetNamespace { local i=0 local max=10 local stat=1 while [ $stat -ne 0 -a $i -lt $max ]; do $oci os ns get > $scratch 2>/dev/null stat=$? if [ $stat -eq 0 -a -s $scratch ]; then OCI_NAMESPACE=`cat $scratch | $jq -r '.data'` else # Pause 3 seconds sleep 3 fi let "i++" done if [ $i -eq $max ]; then exitcode=4 echo "Unable to get namespace - stat: '$stat'." Cleanup exit 4 fi } # Check if CloudShell User is in the Administrators group function CheckForAdminUser { local userid=${1} local max=10 local i=0 local stat=1 local admingroupid="" if [ "$userid" != "" ]; then while [ $stat -ne 0 -a $i -lt $max ]; do $oci iam user list-groups --user-id "$userid" > $scratch 2>/dev/null stat=$? if [ $stat -ne 0 ]; then sleep 3 fi let "i++" done if [ $stat -eq 0 -a -s $scratch ]; then admingroupid=`cat $scratch | $jq -r '.data[] | select(.name=="Administrators") | .id'` if [ "$admingroupid" != "" ]; then OCI_ADMINISTRATORS_OCID="$admingroupid" else stat=1 fi fi fi return $stat } # Call oci cli and try several times if it fails (maybe because of connection errors) function CallOCI { local max=10 local i=0 local stat=1 while [ $stat -ne 0 -a $i -lt $max ]; do $oci "$@" >${scratch}.pre 2>${scratch}.err stat=$? if [ $stat -eq 0 ]; then if [ -s ${scratch}.pre ]; then norm-json --quiet --import ${scratch}.pre stat=$? # norm-json failed - normally this should never be the case if [ $stat -ne 0 ]; then cat ${scratch}.pre stat=$? fi else cat ${scratch}.pre stat=$? fi else # Command was not successful. Wait for a few seconds until we try again sleep 3 fi let "i++" done DeleteFile ${scratch}.pre return $stat } # Create a user (if he doesn't exist) and assign him to 'Administrators' goup. Keep email empty for a technical user function CreateUser { local username=${1} local email=${2} local stat=0 local usertype="" local emailopt="" local result="" if [ "$username" != "" ]; then # Get a list of all existing users if [ ! -f "$usersfile" ]; then CallOCI iam user list --all > $usersfile stat=$? fi if [ $stat -eq 0 ]; then if [ "$email" = "" ]; then usertype="Technical User" else usertype="User" emailopt="--email $email" fi result=`cat $usersfile | $jq -r '.[] | select(.name=="'$username'") | .id'` if [ "$result" = "" ]; then # User does not exist yet CallOCI iam user create --name "$username" $emailopt --description "$usertype for Cloud Administration" \ --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then result=`cat $scratch | $jq -r '.[].id'` if [ "$result" != "" ]; then if [ "$username" = "$cloudadmin" ]; then OCI_CLOUD_ADMIN_OCID="$result" OCI_CLOUD_ADMIN_NAME="$cloudadmin" fi echo "$usertype '$username' created." CallOCI iam group add-user --user-id "$result" --group-id "$OCI_ADMINISTRATORS_OCID" > /dev/null stat=$? if [ $stat -eq 0 ]; then echo "$usertype '$username' assigned to group 'Administrators'." else echo "Unable to assign '$username' to group 'Administrators'. Error: '$stat'." fi fi fi else if [ "$username" = "$cloudadmin" ]; then OCI_CLOUD_ADMIN_OCID="$result" OCI_CLOUD_ADMIN_NAME="$cloudadmin" fi # User already exisits - check if he is assigned to administratos group echo "$usertype '$username' already exisits." CheckForAdminUser "$result" stat=$? if [ $stat -ne 0 ]; then CallOCI iam group add-user --user-id "$result" --group-id "$OCI_ADMINISTRATORS_OCID" > /dev/null stat=$? if [ $stat -eq 0 ]; then echo "$usertype '$username' assigned to group 'Administrators'." else echo "Unable to assign '$username' to group 'Administrators'. Error: '$stat'." fi fi fi if [ "$username" = "$cloudadmin" ]; then cloudadminid="$result" fi fi fi } # Create all keys for user (if they don't exist) function CreateKeys { local username=${1} local importfile=${2} local result="" if [ "$username" != "" -a -r "$keysfile" ]; then result=`grep "^$username " "$keysfile"` if [ "$result" = "" ]; then # Key doesn't exist yet if [ "$importfile" != "" -a -r "$importfile" ]; then key-management import "$importfile" --username "$username" else key-management create --username "$username" fi else echo "Key '$username' already exists." fi fi } # Copy images to our objectstorage function CopyImages { local writeuri="" local imagename="" local compname="" local compid="" local bucket="" local image="" local response=0 if [ -r "$projectjson" -a "$images" != "" ]; then # Copy images compname=`$jq -r '.project.name' "$projectjson"` compid=`$jq -r '.project.compartmentid' "$projectjson"` writeuri=`$jq -r '.project.writeuri' "$projectjson"` if [ "$writeuri" != "" ]; then writeuri=`dirname "$writeuri"` bucket=`basename "$writeuri"` for image in $images; do response=`$curl -o /dev/null --silent -Iw '%{http_code}' "${writeuri}/o/images/$image"` if [ $response -ne 200 ]; then echo "Downloading image '$image' to bucket '$bucket'." store-object --quiet --prefix "images" --image "$image" --url "${writeuri}/o/" else echo "Image '$image' already downloaded." fi # Create image imagename=`echo "${image%.*}"` if [ "$compid" != "" ]; then echo "Creating image '$imagename' in compartment '$compname'." CallOCI compute image import from-object-uri --uri "${writeuri}/o/images/$image" --compartment-id "$compid" --display-name "$imagename" fi done fi fi } # Check if we are running in CloudShell if [ "$OCI_CLI_CLOUD_SHELL" != "True" ]; then echo "Please run this script in CloudShell. Exiting." exitcode=1 else # Check if we have needed tools installed if [ "$curl" = "" -o "$oci" = "" -o "$jq" = "" ]; then echo "No 'curl', 'oci' or 'jq' in PATH. Exiting." exitcode=2 else # Check if we have administrator privileges CheckForAdminUser "$OCI_CS_USER_OCID" exitcode=$? if [ $exitcode -ne 0 ]; then exitcode=3 echo "Need to be in group 'Administrators'. Exiting." else # Create logdir if [ ! -d "$logdir" ]; then mkdir -p "$logdir" fi # Create new empty log if rotatelog = true if [ "$rotatelog" = "true" ]; then printf "" > $logfile fi # Write start time to log WriteLog "Tool '$progstr' started." # Check for internet connection CheckInternet # Install scripts (if not installed) if [ "$setuptools" = "" ]; then WriteLog "Installing admin scripts." "/tmp/$scriptsfile" DeleteFile "/home/opc/.bash_profile.old" else printf "\nAdmin scripts already installed.\n" fi # Install tools (if not installed) if [ "$rclone" = "" ]; then DownloadFile "$toolsfile" "false" stat=$? if [ $stat -eq 0 ]; then WriteLog "Installing tools." "/tmp/$toolsfile" fi else echo "Tools already installed. Update with 'setup-tools update' if needed." fi # Tools installed - now configure them printf '\n' echo "$progstr config" | toupper | print-header printf '\n' # Get namespace GetNamespace # Get Tenancy Name and Home Region CallOCI iam tenancy get --tenancy-id $OCI_TENANCY > $scratch stat=$? if [ $stat -eq 0 ]; then OCI_TENANCY_NAME=`cat $scratch | $jq -r '.[].name'` OCI_HOME_REGION_KEY=`cat $scratch | $jq -r '.[].homeRegionKey'` if [ "$OCI_HOME_REGION_KEY" != "" ]; then CallOCI iam region list --all > $scratch stat=$? if [ $stat -eq 0 ]; then OCI_HOME_REGION=`cat $scratch | $jq -r '.[] | select(.key=="'$OCI_HOME_REGION_KEY'") | .name'` fi fi fi # Create a technical user for cloud administration (if not already created) CreateUser "$cloudadmin" # Get User Name from User OCID CallOCI iam user get --user-id "$OCI_CS_USER_OCID" > $scratch stat=$? if [ $stat -eq 0 ]; then OCI_CS_USER_NAME=`cat $scratch | $jq -r '.[].name'` fi # If no technical user is specified in configuration variables - use CloudShell user if [ "$cloudadmin" = "" ]; then OCI_CLOUD_ADMIN_OCID="$OCI_CS_USER_OCID" OCI_CLOUD_ADMIN_NAME="$OCI_CS_USER_NAME" fi # Create a file with all existing keys (before key creation) key-management list tsv | tailfromline2 > $keysfile # # Debug # echo "OCI_CLI_CLOUD_SHELL: '$OCI_CLI_CLOUD_SHELL'." # echo "OCI_TENANCY: '$OCI_TENANCY'." # echo "OCI_TENANCY_NAME: '$OCI_TENANCY_NAME'." # echo "OCI_REGION: '$OCI_REGION'." # echo "OCI_HOME_REGION_KEY: '$OCI_HOME_REGION_KEY'." # echo "OCI_HOME_REGION: '$OCI_HOME_REGION'." # echo "OCI_CS_USER_OCID: '$OCI_CS_USER_OCID'." # echo "OCI_CS_USER_NAME: '$OCI_CS_USER_NAME'." # echo "OCI_CLOUD_ADMIN_NAME: '$OCI_CLOUD_ADMIN_NAME'." # echo "OCI_CLOUD_ADMIN_OCID: '$OCI_CLOUD_ADMIN_OCID'." # echo "OCI_ADMINISTRATORS_OCID: '$OCI_ADMINISTRATORS_OCID'." # echo "OCI_NAMESPACE: '$OCI_NAMESPACE'." # cat $keysfile # Cleanup # exit # Create keys for api access (oci user) CreateKeys "$OCI_CLOUD_ADMIN_NAME" "$imp_apikey" # Create keys for tenancy access (opc user) CreateKeys "$OCI_TENANCY_NAME" "$imp_tenancykey" # Create keys for jumphost user CreateKeys "$jumphostname" "$imp_jumphostkey" # Create a file with all existing keys (after key creation) key-management list tsv | tailfromline2 > $keysfile # Check if API key is attached to user if [ "$OCI_CLOUD_ADMIN_NAME" != "" -a "$OCI_CLOUD_ADMIN_OCID" != "" ]; then apifp=`cat $keysfile | grep "^${OCI_CLOUD_ADMIN_NAME} " | cut -d$'\t' -f3` apikey=`cat $keysfile | grep "^${OCI_CLOUD_ADMIN_NAME} " | cut -d$'\t' -f4` if [ -r "${apikey}.pk8" ]; then # Prefering this key apikey="${apikey}.pk8" fi CallOCI iam user api-key list --user-id $OCI_CLOUD_ADMIN_OCID > $scratch stat=$? else exitcode=5 errormsg $exitcode "Unknown condition. Could not get all variables. Exiting." Cleanup exit $exitcode fi if [ $stat -eq 0 -a "$apifp" != "" ]; then found="false" result=`cat $scratch | $jq -r '.[].fingerprint'` for fp in $result; do if [ "$fp" = "$apifp" ]; then found="true" fi done if [ "$found" = "false" ]; then echo "Attaching API Key." key-management show pem --username "$OCI_CLOUD_ADMIN_NAME" | grep . > $scratch CallOCI iam user api-key upload --user-id $OCI_CLOUD_ADMIN_OCID --key-file $scratch > /dev/null else echo "API key already attached to user." fi fi # Get jumphost key and tenancy keys (opc user) jumphostkey=`cat $keysfile | grep "^${jumphostname} " | cut -d$'\t' -f4` tenancykey=`cat $keysfile | grep "^${OCI_TENANCY_NAME} " | cut -d$'\t' -f4` if [ -r "${tenancykey}.pub" ]; then # Public key tenancykeypub="${tenancykey}.pub" fi # Convert $HOME to "~" in keys to use in json config files if [ "$apikey" != "" -a "$tenancykey" != "" -a "$tenancykeypub" != "" -a "$jumphostkey" != "" ]; then apikey=`echo "$apikey" | sed 's|'"$HOME"'|~|'` tenancykey=`echo "$tenancykey" | sed 's|'"$HOME"'|~|'` tenancykeypub=`echo "$tenancykeypub" | sed 's|'"$HOME"'|~|'` jumphostkey=`echo "$jumphostkey" | sed 's|'"$HOME"'|~|'` fi # Create secret key and if successful, create oci json file if [ $stat -eq 0 -a ! -r "$ocijson" ]; then CallOCI iam customer-secret-key create --display-name "S3 Access" --user-id $OCI_CLOUD_ADMIN_OCID > $scratch stat=$? if [ $stat -eq 0 ]; then echo "Secret key created." accesskeyid=`cat $scratch | $jq -r '.[].id'` secretaccesskey=`cat $scratch | $jq -r '.[].key'` # Create oci folder if [ ! -d "$ocifolder" ]; then mkdir -p "$ocifolder" fi # Create json file touch $ocijson chmod 600 $ocijson printf '{\n' > $ocijson printf ' "oci": {\n' >> $ocijson printf ' "tenancyname": "%s",\n' "$OCI_TENANCY_NAME" >> $ocijson printf ' "tenancyid": "%s",\n' "$OCI_TENANCY" >> $ocijson printf ' "homeregion": "%s"\n' "$OCI_REGION" >> $ocijson printf ' },\n' >> $ocijson printf ' "keys": {\n' >> $ocijson printf ' "username": "%s",\n' "$OCI_CLOUD_ADMIN_NAME" >> $ocijson printf ' "userid": "%s",\n' "$OCI_CLOUD_ADMIN_OCID" >> $ocijson printf ' "apikey": "%s",\n' "$apikey" >> $ocijson printf ' "fingerprint": "%s",\n' "$apifp" >> $ocijson printf ' "passphrase": "",\n' >> $ocijson printf ' "privkey": "%s",\n' "$tenancykey" >> $ocijson printf ' "pubkey": "%s"\n' "$tenancykeypub" >> $ocijson printf ' },\n' >> $ocijson printf ' "s3": {\n' >> $ocijson printf ' "namespace": "%s",\n' "$OCI_NAMESPACE" >> $ocijson printf ' "accesskey": "%s",\n' "$accesskeyid" >> $ocijson printf ' "secretkey": "%s"\n' "$secretaccesskey" >> $ocijson printf ' }\n' >> $ocijson printf '}\n' >> $ocijson else exitcode=5 errormsg $exitcode "Unknown condition. Could not create secret key. Exiting." Cleanup exit $exitcode fi else echo "Secret key is already configured." fi # Create blz json if [ $stat -eq 0 -a ! -r "$blzjson" ]; then # Create needed compartments (if the do not exist yet) CallOCI iam compartment list --compartment-id $OCI_TENANCY --all > $scratch stat=$? if [ $stat -eq 0 ]; then echo "Compartments created." operationsid=`cat $scratch | $jq -r '.[] | select(.name=="'$opsname'") | .id'` if [ "$netwname" != "" ]; then networkid=`cat $scratch | $jq -r '.[] | select(.name=="'$netwname'") | .id'` fi if [ "$appname" != "" ]; then applicationid=`cat $scratch | $jq -r '.[] | select(.name=="'$appname'") | .id'` fi if [ "$dbname" != "" ]; then dbid=`cat $scratch | $jq -r '.[] | select(.name=="'$dbname'") | .id'` fi # Keys printf '%s\t%s\t%s\n' "id" "file" "name" > ${scratch}.pre printf '%s\t%s\t%s\n' "oci" "$apikey" "$OCI_CLOUD_ADMIN_NAME" >> ${scratch}.pre printf '%s\t%s\t%s\n' "opc" "$tenancykey" "$OCI_TENANCY_NAME" >> ${scratch}.pre printf '%s\t%s\t%s\n' "jh" "$jumphostkey" "$jumphostname" >> ${scratch}.pre printf '{\n"%s": ' "keys" > $scratch print-table --import ${scratch}.pre --output json >> $scratch printf '}\n' >> $scratch # Compartments printf '%s\t%s\t%s\n' "id" "ocid" "name" > ${scratch}.pre printf '%s\t%s\t%s\n' "ops" "$operationsid" "$opsname" >> ${scratch}.pre if [ "$netwname" != "" ]; then printf '%s\t%s\t%s\n' "netw" "$networkid" "$netwname" >> ${scratch}.pre fi if [ "$appname" != "" ]; then printf '%s\t%s\t%s\n' "app" "$applicationid" "$appname" >> ${scratch}.pre fi if [ "$dbname" != "" ]; then printf '%s\t%s\t%s\n' "db" "$dbid" "$dbname" >> ${scratch}.pre fi printf '{\n"%s": ' "compartments" >> $scratch print-table --import ${scratch}.pre --output json >> $scratch printf '}\n' >> $scratch # Create json file touch $blzjson chmod 600 $blzjson cat $scratch | jq --slurp > $blzjson else exitcode=5 errormsg $exitcode "Unknown condition. Could not list compartments. Exiting." Cleanup exit $exitcode fi else echo "Compartments were already configured." fi # Configure tools and create project json if [ $stat -eq 0 -a ! -r "$projectjson" ]; then # Let setup-tools do the rest of configuration setup-tools config else echo "Tools were already configured." fi # Deploy basic policies and other resources printf '\n' echo "$progstr deploy" | toupper | print-header printf '\n' # Copy images CopyImages fi fi fi # Cleanup and exit Cleanup exit $exitcode