#!/usr/bin/env bash # c8k.in/stall.sh - Easiest Apache CloudStack Installer # Install with this command (from your Ubuntu/EL host): # # curl -sSfL https://c8k.in/stall.sh | bash # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # NOTE: This is work-in-progress set -e set -o noglob CS_VERSION=4.20 INTERFACE= BRIDGE=cloudbr0 HOST_IP= GATEWAY= DNS="9.9.9.9, 1.1.1.1" # --- helper functions for logs --- info() { echo '[INFO] ' "$@" } warn() { echo '[WARN] ' "$@" >&2 } fatal() { echo '[ERROR] ' "$@" >&2 exit 1 } ### Intro ### echo " ░█████╗░░█████╗░██╗░░██╗░░░██╗███╗░░██╗ ██╔══██╗██╔══██╗██║░██╔╝░░░██║████╗░██║ ██║░░╚═╝╚█████╔╝█████═╝░░░░██║██╔██╗██║ ██║░░██╗██╔══██╗██╔═██╗░░░░██║██║╚████║ ╚█████╔╝╚█████╔╝██║░╚██╗██╗██║██║░╚███║ ░╚════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝╚═╝░░╚══╝ Welcome to the CloudStack Installer " info "Installing Apache CloudStack All-In-One-Box" info "NOTE: this works only on Ubuntu 22.04 (tested), and run as 'root' user!" if [[ $EUID -ne 0 ]]; then fatal "This script must be run as root" exit 1 fi warn "Work in progress, try again while this is being hacked" ### Setup Prerequisites ### info "Installing dependencies" apt-get update apt-get install -y gpg apparmor openssh-server sudo wget jq htop tar nmap bridge-utils # FIXME: check for host spec (min 4-8G RAM?) /dev/kvm and ### Setup Bridge ### setup_bridge() { if brctl show $BRIDGE > /dev/null 2>&1; then info "Bridge $BRIDGE already exists, skipping create..." return fi interface=$(ip route get 8.8.8.8 | awk -F"dev " 'NR==1 {split($2,a," ");print a[1]}' | sort | head -1) gateway=$(ip route show 0.0.0.0/0 dev $interface | cut -d ' ' -f 3) hostipandsub=$(ip -4 -br addr show $interface | awk '{ print $3; }' ) info "Setting up bridge on $interface which has IP $hostipandsub and gateway $gateway" cat << EOF > /etc/netplan/01-netcfg.yaml network: version: 2 renderer: networkd ethernets: $interface: dhcp4: false dhcp6: false optional: true bridges: $BRIDGE: addresses: [$hostipandsub] routes: - to: default via: $gateway nameservers: addresses: [$DNS] interfaces: [$interface] dhcp4: false dhcp6: false parameters: stp: false forward-delay: 0 EOF # FIX netplan complaining about permissions chmod 600 /etc/netplan/01-netcfg.yaml # FIX "No such file or directory" error mkdir /etc/cloud/ && mkdir /etc/cloud/cloud.cfg.d/ info "Disabling cloud-init netplan config" rm -f /etc/netplan/50-cloud-init.yaml if [[ ! -f "/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg" && ! -f "/etc/cloud/cloud.cfg.d/99_disable-network-config.cfg" ]]; then echo "network: {config: disabled}" > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg fi # FIXME workaround for VMware tools generated network config if [[ -f "/etc/netplan/99-netcfg-vmware.yaml" ]]; then mv /etc/netplan/99-netcfg-vmware.yaml /etc/netplan/99-netcfg-vmware.yaml.bak fi netplan generate netplan apply export INTERFACE="$interface" } ### Setup CloudStack Packages ### configure_repo() { info "Configuring CloudStack $CS_VERSION repo" NAME=$(sed -rn 's|^deb\s+\S+\s+(\w+)\s+main.*$|\1|p' /etc/apt/sources.list | head -n 1) mkdir -p /etc/apt/keyrings wget -O - https://download.cloudstack.org/release.asc | sudo tee /etc/apt/trusted.gpg.d/cloudstack.asc wget -O- https://download.cloudstack.org/release.asc 2>/dev/null | gpg --dearmor | sudo tee /etc/apt/keyrings/cloudstack.gpg > /dev/null # NOTE: debian-based distro packages are now release agnostic echo deb [signed-by=/etc/apt/keyrings/cloudstack.gpg] https://download.cloudstack.org/ubuntu $NAME $CS_VERSION > /etc/apt/sources.list.d/cloudstack.list apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3D62B837F100E758 apt-get update } install_packages() { info "Installing CloudStack $CS_VERSION, MySQL and NFS server" if dpkg -l | grep cloudstack-management > /dev/null; then warn "CloudStack packages seem to be already installed, skipping CloudStack packages installation" apt-get install -y mysql-server nfs-kernel-server quota qemu-kvm else apt-get install -y cloudstack-management cloudstack-usage mysql-server nfs-kernel-server quota qemu-kvm cloudstack-agent systemctl daemon-reload systemctl stop cloudstack-management cloudstack-usage cloudstack-agent fi } ### Configure Methods ### configure_mysql() { info "Configuring MySQL Server: $(mysql -V)" if [[ -f "/etc/mysql/mysql.conf.d/cloudstack.cnf" ]]; then info "Skipping MySQL configuration setup, already done" return fi sqlmode="$(mysql -B -e "show global variables like 'sql_mode'" | grep sql_mode | awk '{ print $2; }' | sed -e 's/ONLY_FULL_GROUP_BY,//')" cat > /etc/mysql/mysql.conf.d/cloudstack.cnf < /dev/null; then info "Skipping NFS Storage configuration setup, already done" return fi echo "/export *(rw,async,no_root_squash,no_subtree_check)" >> /etc/exports mkdir -p /export/primary /export/secondary exportfs -a sed -i -e 's/^RPCMOUNTDOPTS="--manage-gids"$/RPCMOUNTDOPTS="-p 892 --manage-gids"/g' /etc/default/nfs-kernel-server sed -i -e 's/^STATDOPTS=$/STATDOPTS="--port 662 --outgoing-port 2020"/g' /etc/default/nfs-common if ! grep 'NEED_STATD=yes' /etc/default/nfs-common > /dev/null; then echo "NEED_STATD=yes" >> /etc/default/nfs-common fi sed -i -e 's/^RPCRQUOTADOPTS=$/RPCRQUOTADOPTS="-p 875"/g' /etc/default/quota service nfs-kernel-server restart info "NFS exports created: $(exportfs)" } configure_host() { info "Configuring KVM on this host" sed -i -e 's/\#vnc_listen.*$/vnc_listen = "0.0.0.0"/g' /etc/libvirt/qemu.conf if ! grep '^LIBVIRTD_ARGS="--listen"' /etc/default/libvirtd > /dev/null; then echo LIBVIRTD_ARGS=\"--listen\" >> /etc/default/libvirtd fi if ! grep 'listen_tcp=1' /etc/libvirt/libvirtd.conf > /dev/null; then echo 'listen_tcp=1' >> /etc/libvirt/libvirtd.conf echo 'listen_tls=0' >> /etc/libvirt/libvirtd.conf echo 'tcp_port = "16509"' >> /etc/libvirt/libvirtd.conf echo 'mdns_adv = 0' >> /etc/libvirt/libvirtd.conf echo 'auth_tcp = "none"' >> /etc/libvirt/libvirtd.conf systemctl mask libvirtd.socket libvirtd-ro.socket libvirtd-admin.socket libvirtd-tls.socket libvirtd-tcp.socket systemctl restart libvirtd # Ubuntu: disable apparmor ln -sf /etc/apparmor.d/usr.sbin.libvirtd /etc/apparmor.d/disable/ ln -sf /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper /etc/apparmor.d/disable/ apparmor_parser -R /etc/apparmor.d/usr.sbin.libvirtd --subdomainfs apparmor_parser -R /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper --subdomainfs fi if ! kvm-ok; then warn "KVM may not work on your host" else info "KVM host configured" virsh nodeinfo fi virsh net-destroy default || true virsh net-undefine default || true } deploy_cloudstack() { if systemctl is-active cloudstack-management > /dev/null; then info "CloudStack Management Server is already running, skipping DB deployment" return fi info "Deploying CloudStack Database" cloudstack-setup-databases cloud:cloud@localhost --deploy-as=root: -i $HOST_IP info "Deploying CloudStack Management Server" cloudstack-setup-management } install_completed() { info "CloudStack installation completed!" info "Access CloudStack UI at: http://$HOST_IP:8080/client with username 'admin' and password 'password'" echo } deploy_zone() { info "Deploying CloudStack Zone" wget -q https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.4.0/cmk.linux.x86-64 -O /usr/bin/cmk > /dev/null chmod +x /usr/bin/cmk cmk set username admin cmk set password password cmk set display json cmk set asyncblock true cmk sync info "Starting Zone deployment" zone_id=$(cmk create zone dns1=8.8.8.8 internaldns1=$GATEWAY name=AdvZone1 networktype=Advanced | jq '.zone.id') info "Created CloudStack Zone with ID $zone_id" phy_id=$(cmk create physicalnetwork name=cloudbr0 zoneid=$zone_id | jq '.physicalnetwork.id') cmk add traffictype traffictype=Management physicalnetworkid=$phy_id cmk add traffictype traffictype=Public physicalnetworkid=$phy_id cmk add traffictype traffictype=Guest physicalnetworkid=$phy_id cmk update physicalnetwork state=Enabled id=$phy_id info "Created CloudStack Physical Network in zone with ID $phy_id" nsp_id=$(cmk list networkserviceproviders name=VirtualRouter physicalnetworkid=$phy_id | jq -r '.networkserviceprovider[0].id') vre_id=$(cmk list virtualrouterelements nspid=$nsp_id | jq -r '.virtualrouterelement[0].id') cmk configure virtualrouterelement enabled=true id=$vre_id cmk update networkserviceprovider state=Enabled id=$nsp_id info "Configured VR Network Service Provider for zone" nsp_id=$(cmk list networkserviceproviders name=Internallbvm physicalnetworkid=$phy_id | jq -r '.networkserviceprovider[0].id') ilbvm_id=$(cmk list internalloadbalancerelements nspid=$nsp_id | jq -r '.internalloadbalancerelement[0].id') cmk configure internalloadbalancerelement enabled=true id=$ilbvm_id cmk update networkserviceprovider state=Enabled id=$nsp_id info "Configured ILBVM Network Service Provider for zone" nsp_id=$(cmk list networkserviceproviders name=VpcVirtualRouter physicalnetworkid=$phy_id | jq -r '.networkserviceprovider[0].id') vpcvre_id=$(cmk list virtualrouterelements nspid=$nsp_id | jq -r '.virtualrouterelement[0].id') cmk configure virtualrouterelement enabled=true id=$vpcvre_id cmk update networkserviceprovider state=Enabled id=$nsp_id info "Configured VPC VR Network Service Provider for zone" # TODO: use nmap to scan for free IPs in the range # sudo nmap -v -sn -n 192.168.1.0/24 -oG - | awk '/Status: Down/{print $2}' # FIXME: prompt for IP range? RANGE=$(echo $GATEWAY | sed 's/\..$//g') pod_start="$RANGE.200" pod_end="$RANGE.220" pod_gw=$GATEWAY pod_mask=255.255.255.0 ip_start="$RANGE.221" ip_end="$RANGE.240" ip_gw=$GATEWAY ip_mask=255.255.255.0 pod_id=$(cmk create pod name=AdvPod1 zoneid=$zone_id gateway=$pod_gw netmask=$pod_mask startip=$pod_start endip=$pod_end | jq '.pod.id') cmk create vlaniprange zoneid=$zone_id vlan=untagged gateway=$ip_gw netmask=$ip_mask startip=$ip_start endip=$ip_end forvirtualnetwork=true cmk update physicalnetwork id=$phy_id vlan=100-200 cluster_id=$(cmk add cluster zoneid=$zone_id hypervisor=KVM clustertype=CloudManaged podid=$pod_id clustername=Cluster1 | jq '.cluster[0].id') # Add by CloudStack Management Server's public key mkdir -p /root/.ssh cat /var/lib/cloudstack/management/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys cmk add host zoneid=$zone_id podid=$pod_id clusterid=$cluster_id clustertype=CloudManaged hypervisor=KVM username=root password= url=http://$HOST_IP cmk create storagepool zoneid=$zone_id podid=$pod_id clusterid=$cluster_id name=Primary-StoragePool1 scope=zone hypervisor=KVM url=nfs://$HOST_IP/export/primary cmk add imagestore provider=NFS zoneid=$zone_id name=Secondary-StoragePool1 url=nfs://$HOST_IP/export/secondary cmk update zone allocationstate=Enabled id=$zone_id } display_url() { echo " █████████████████████████████████████████████████████████████ █─▄▄▄─█▄─▄███─▄▄─█▄─██─▄█▄─▄▄▀█─▄▄▄▄█─▄─▄─██▀▄─██─▄▄▄─█▄─█─▄█ █─███▀██─██▀█─██─██─██─███─██─█▄▄▄▄─███─████─▀─██─███▀██─▄▀██ ▀▄▄▄▄▄▀▄▄▄▄▄▀▄▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▄▀▀▄▄▄▀▀▄▄▀▄▄▀▄▄▄▄▄▀▄▄▀▄▄▀ URL: http://$HOST_IP:8080/client User: admin Password: password " } ### Installer: Setup ### setup_bridge export HOST_IP=$(ip -4 -br addr show $BRIDGE | awk '{ print $3; }' | sed -e 's/\/[0-9]\+//') export GATEWAY=$(ip route show 0.0.0.0/0 dev $BRIDGE | cut -d ' ' -f 3) info "Bridge $BRIDGE is setup with IP $HOST_IP" configure_repo install_packages configure_mysql configure_storage configure_host deploy_cloudstack install_completed ### Installer: Deploy Zone ### # FIXME: configuration global setting & restart mgmt server deploy_zone # FIXME: register Ubuntu template & register ssh-key of this host, configure CKS etc. ### Installer: Finish ### display_url