#!/bin/bash
# ----------------------------------------------------------------------------
# 作者: 李毓
# 版本: 1.0.0
# 创建日期: 2024-05-29
# 更新日期: 2024-05-29
# 简介: 因为外出部署经常涉及到做离线安装包,部署一个干净的harobr比较麻烦。所以写了一个部署harbor的脚本。为了贴合现场环境,和网上其他的部署版本不同的是增加了ssl证书的配置。稍加改动甚至可以在wsl部署,不占用服务器资源。这个harbor的脚本,运行环境为ubuntu20.04开发,但是其他ubuntu环境也能用,其中必须修改选项为变量中的harbor_ip地址。其余酌情修改。另外脚本中下载的docker-compose和离线harbor包自己准备。我这里是内网下载的。
#
# Copyright (C): 2024 All rights reserved
# ----------------------------------------------------------------------------

basepath=$(cd `dirname $0`; pwd)
city="ShenZhen"
domain_name="harbor.internal.cn"
domain_port="8443"
damain_cerds_name="${domain_name}:${domain_port}"
domain_name1=$(echo $domain_name | awk -F . '{print $1"."$2}')
domain_name2=$(echo $domain_name | awk -F . '{print $2}')
docker_config_path="/etc/docker"
project_name="internal"
ssl_path="${basepath}/ssl"
ssl_path_volume="data"
cert_path="${ssl_path}/data/${domain_name}.crt"
key_path="${ssl_path}/data/${domain_name}.key"
cert_path_volume="certs.d/${damain_cerds_name}"
version="v2.2.2"
dir="/data/harbor"
password="Ts2LfGuTVmxTHrk2"
harbor_ip="172.18.177.15"

log_info() {
    echo -e "\033[35m [`hostname` `whoami` `date '+%Y-%m-%d %H:%M:%S'`]\033[0m \033[34m$1\033[0m"
}

create_ca_key() {
    openssl genrsa -out ca.key 4096 &>/dev/null
    openssl req -x509 -new -nodes -sha512 -days 3650 \
        -subj "/C=CN/ST=${city}/L=${city}/O=example/OU=Personal/CN=${domain_name}" \
        -key ca.key \
        -out ca.crt &>/dev/null
}

create_key() {
    openssl genrsa -out ${domain_name}.key 4096 &>/dev/null
}

create_csr() {
    openssl req -sha512 -new \
        -subj "/C=CN/ST=${city}/L=${city}/O=example/OU=Personal/CN=${domain_name}" \
        -key ${domain_name}.key \
        -out ${domain_name}.csr &>/dev/null
}

create_v3_ext() {
    cat <<EOF > v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1=${domain_name}
DNS.2=${domain_name1}
DNS.3=${domain_name2}
IP.1=${harbor_ip}
EOF
}

create_harbor_cert() {
    openssl x509 -req -sha512 -days 3650 \
        -extfile v3.ext \
        -CA ca.crt -CAkey ca.key -CAcreateserial \
        -in ${domain_name}.csr \
        -out ${domain_name}.crt &>/dev/null
}

setup_docker_cert() {
    mkdir -p ${ssl_path_volume}
    cp ${domain_name}.crt ${ssl_path_volume}
    cp ${domain_name}.key ${ssl_path_volume}

    mkdir -p ${cert_path_volume}
    openssl x509 -inform PEM -in ${domain_name}.crt -out ${domain_name}.cert &>/dev/null
    cp ${domain_name}.cert ${cert_path_volume}
    cp ${domain_name}.key ${cert_path_volume}
    cp ca.crt ${cert_path_volume}
}

run_ca_start() {
    log_info "生成CA证书..."
    create_ca_key
    log_info "生成私钥..."
    create_key
    log_info "生成证书签名请求 (CSR)..."
    create_csr
    log_info "生成 x509 v3 的扩展文件..."
    create_v3_ext
    log_info "使用V3文件为harbor生成证书..."
    create_harbor_cert
    log_info "提供证书给harbor和docker..."
    setup_docker_cert
    log_info "提供证书给harbor客户端..."
    tar -zcf certs_d.tar.gz certs.d
    tar -zxf certs_d.tar.gz -C /etc/docker/
    if [ $? -eq 0 ]; then
        log_info "证书文件拷贝到 /etc/docker/"
        log_info "Update daemon.json with \"insecure-registries\": [\"${domain_name}\"]"
    else
        exit 1
    fi
}

setup_environment() {
    mkdir -p ${ssl_path}
    log_info "${ssl_path} directory created successfully"
    cd ${ssl_path}
    sed -i "\$a $harbor_ip $domain_name" /etc/hosts
    mkdir -p ${docker_config_path}
}

install_docker() {
    if ! docker --version &>/dev/null; then
        echo "Docker is not installed. Installing Docker..."
        apt update
        apt install -y docker.io
        cat <<EOF > /etc/docker/daemon.json
{
  "data-root": "/var/lib/docker",
  "exec-opts": ["native.cgroupdriver=cgroupfs"],
  "insecure-registries": ["127.0.0.1/8"],
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "15m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "experimental": true
}
EOF

        systemctl daemon-reload
        systemctl restart docker
        systemctl enable docker
    else
        echo "Docker is already installed."
    fi
}

install_docker_compose() {
    if ! docker-compose version &>/dev/null; then
        echo "Docker Compose is not installed. Downloading..."
        curl -L http://192.168.1.200:2005/iqi/docker-compose -o /usr/local/bin/docker-compose &>/dev/null
        if [ $? -ne 0 ]; then
            echo "Failed to download Docker Compose. Please try again."
            rm -rf /usr/local/bin/docker-compose
            exit 1
        fi
        chmod +x /usr/local/bin/docker-compose &>/dev/null
    fi
}

download_harbor() {
    if [ ! -f harbor-offline-installer-$version.tgz ]; then
        wget http://192.168.1.200:2005/iqi/harbor-offline-installer-$version.tgz &>/dev/null
        if [ $? -ne 0 ]; then
            echo "Failed to download Harbor installer. Please try again."
            rm -rf harbor-offline-installer-$version.tgz
            exit 1
        fi
    fi
    mkdir -pv $dir
    tar xf harbor-offline-installer-$version.tgz -C $dir/
}

configure_harbor() {
    if [ -f $dir/harbor/harbor.yml.tmpl ]; then
        cp $dir/harbor/harbor.yml.tmpl $dir/harbor/harbor.yml
        sed -i "s|hostname: .*|hostname: $harbor_ip|" $dir/harbor/harbor.yml
        sed -i "s|certificate: .*|certificate: $cert_path|" $dir/harbor/harbor.yml
        sed -i "s|private_key: .*|private_key: $key_path|" $dir/harbor/harbor.yml
        sed -i "/^harbor_admin_password:/ s/: .*/: $password/" $dir/harbor/harbor.yml
        sed -i 's/data_volume: \/data/data_volume: \/data\/harbor/g' $dir/harbor/harbor.yml
        sed -i "/port:/ s/443/$domain_port/" $dir/harbor/harbor.yml
    else
        echo "Harbor configuration template not found. Deployment failed."
        exit 1
    fi
    cd $dir/harbor
    ./install.sh --with-chartmuseum
    if [ $? -ne 0 ]; then
        echo "Harbor installation failed."
        rm -rf harbor-offline-installer-$version.tgz
        exit 1
    fi
}

setup_harbor_service() {
    cat > /etc/systemd/system/harbor.service <<EOF
{
[Unit]
Description=Harbor
After=docker.service systemd-networkd.service systemd-resolved.service
Requires=docker.service
Documentation=http://github.com/vmware/harbor

[Service]
Type=simple
Restart=on-failure
RestartSec=5
ExecStart=/usr/local/bin/docker-compose -f $dir/harbor/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -f $dir/harbor/docker-compose.yml down

[Install]
WantedBy=multi-user.target

}
EOF

    systemctl daemon-reload &>/dev/null
    systemctl enable harbor &>/dev/null
    systemctl restart harbor &>/dev/null
    if ss -ntl | grep -q ${domain_port}; then
        echo -e "Please visit: \E[32;1mhttps://$harbor_ip\E[0m"
        echo -e "Username: \E[32;1madmin\E[0m"
        echo -e "Password: \E[32;1m$password\E[0m"
        echo "Waiting for 30 seconds before login"
        sleep 30
    else
        echo "Harbor failed to start."
    fi
}

docker_login() {
    log_info "正在登录 Harbor..."
    docker login ${domain_name}:${domain_port} -u admin -p ${password}
    if [ $? -eq 0 ]; then
        log_info "登录 ${domain_name}:${domain_port} 成功."
    else
        echo "Docker login failed with exit code $?"
        exit 1
    fi
}

create_harbor_project() {
    local project_name="$1"
    local body="{\"project_name\":\"${project_name}\",\"metadata\":{\"public\":\"true\"},\"storage_limit\":-1}"

    local http_status=$(curl -s -k -w "%{http_code}\n" -o /dev/null \
        -u "admin:${password}" \
        -H "Content-Type: application/json" \
        -d "$body" \
        "https://${domain_name}:${domain_port}/api/v2.0/projects")

    case $http_status in
        200|201|409)
            log_info "项目 ${project_name} 创建成功或者已经存在."
            ;;
        *)
            echo "Failed to create project, HTTP status code: $http_status"
            ;;
    esac
}

main() {
    setup_environment
    run_ca_start
    install_docker
    install_docker_compose
    download_harbor
    configure_harbor
    setup_harbor_service
    docker_login
    if [ $? -eq 0 ]; then
        create_harbor_project "$project_name"
    else
        echo "Failed to login, unable to create project."
    fi
}

main