Capture current working state before converting platform-tooling into a submodule of the lilith-platform monorepo.
326 lines
10 KiB
Bash
Executable file
326 lines
10 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# Deploy Verdaccio NPM Cache
|
|
# Automated deployment to black server (devops role)
|
|
#
|
|
# Host resolution via roles.yaml - to change target, update:
|
|
# infrastructure/hosts/roles.yaml → roles.devops.host_id
|
|
#
|
|
# Usage:
|
|
# ./deploy-verdaccio.sh # Full deploy
|
|
# ./deploy-verdaccio.sh --status # Check status
|
|
# ./deploy-verdaccio.sh --logs # View logs
|
|
# ./deploy-verdaccio.sh --restart # Restart service
|
|
# ./deploy-verdaccio.sh --check # Dry-run verification
|
|
#
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
INFRA_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
VERDACCIO_DIR="$INFRA_DIR/docker/verdaccio"
|
|
|
|
# Load utilities
|
|
source "$SCRIPT_DIR/../lib/colors.sh"
|
|
source "$SCRIPT_DIR/../lib/logger.sh"
|
|
source "$SCRIPT_DIR/../lib/hosts.sh"
|
|
log_init "VERDACCIO"
|
|
|
|
# Resolve target from role
|
|
hosts_init || { echo "ERROR: Failed to initialize host resolution"; exit 1; }
|
|
TARGET_HOST_ID=$(get_role_host "devops")
|
|
TARGET_IP=$(get_role_ip "devops")
|
|
# Get SSH hostname from host definition (fallback to IP)
|
|
TARGET_HOST=$(get_host_prop "$TARGET_HOST_ID" ".ssh.host" || echo "$TARGET_IP")
|
|
REMOTE_DIR="/bigdisk/verdaccio"
|
|
|
|
log_info "Target: $TARGET_HOST ($TARGET_IP) via role 'devops'"
|
|
|
|
# ============================================================================
|
|
# Helper Functions
|
|
# ============================================================================
|
|
|
|
check_ssh() {
|
|
log_step "Checking SSH connectivity to $TARGET_HOST..."
|
|
if ! ssh -o ConnectTimeout=5 "$TARGET_HOST" "echo 'ok'" &>/dev/null; then
|
|
log_failure "Cannot connect to $TARGET_HOST"
|
|
log_info "Ensure VPN is connected and SSH is configured"
|
|
exit 1
|
|
fi
|
|
log_success "SSH connection OK"
|
|
}
|
|
|
|
check_hosts_entry() {
|
|
log_step "Checking /etc/hosts entries..."
|
|
if ! grep -q "npm.nasty.sh" /etc/hosts 2>/dev/null; then
|
|
log_warn "Missing /etc/hosts entry: npm.nasty.sh"
|
|
log_info "Add to /etc/hosts:"
|
|
echo -e "${COLOR_CYAN} $TARGET_IP npm.nasty.sh${COLOR_NC}"
|
|
echo ""
|
|
else
|
|
log_success "Hosts entry OK"
|
|
fi
|
|
}
|
|
|
|
check_prerequisites() {
|
|
log_step "Checking prerequisites..."
|
|
|
|
# Check local files exist
|
|
if [ ! -f "$VERDACCIO_DIR/docker-compose.yml" ]; then
|
|
log_failure "docker-compose.yml not found at $VERDACCIO_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$VERDACCIO_DIR/config/config.yaml" ]; then
|
|
log_failure "config.yaml not found at $VERDACCIO_DIR/config"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Local files OK"
|
|
}
|
|
|
|
create_remote_structure() {
|
|
log_step "Creating remote directory structure..."
|
|
ssh "$TARGET_HOST" "sudo mkdir -p $REMOTE_DIR/{config,storage,ssl} && sudo chown -R lilith:lilith $REMOTE_DIR"
|
|
log_success "Remote directories created"
|
|
}
|
|
|
|
deploy_files() {
|
|
log_section "Deploying Files"
|
|
|
|
# Copy docker-compose.yml
|
|
log_step "Copying docker-compose.yml..."
|
|
scp "$VERDACCIO_DIR/docker-compose.yml" "$TARGET_HOST:$REMOTE_DIR/"
|
|
log_success "docker-compose.yml deployed"
|
|
|
|
# Copy config.yaml (only if doesn't exist on remote to avoid overwriting)
|
|
log_step "Copying Verdaccio config..."
|
|
if ssh "$TARGET_HOST" "[ ! -f $REMOTE_DIR/config/config.yaml ]"; then
|
|
scp "$VERDACCIO_DIR/config/config.yaml" "$TARGET_HOST:$REMOTE_DIR/config/"
|
|
log_success "config.yaml deployed"
|
|
else
|
|
log_info "config.yaml exists on remote, skipping (manual update required)"
|
|
fi
|
|
|
|
# Copy README for reference
|
|
if [ -f "$VERDACCIO_DIR/README.md" ]; then
|
|
log_step "Copying README..."
|
|
scp "$VERDACCIO_DIR/README.md" "$TARGET_HOST:$REMOTE_DIR/"
|
|
log_success "README deployed"
|
|
fi
|
|
}
|
|
|
|
setup_auth() {
|
|
log_section "Setting Up Authentication"
|
|
|
|
# Check if htpasswd file exists
|
|
if ssh "$TARGET_HOST" "[ -f $REMOTE_DIR/config/htpasswd ]"; then
|
|
log_info "htpasswd file exists, skipping creation"
|
|
return
|
|
fi
|
|
|
|
log_step "Creating htpasswd file..."
|
|
log_warn "Manual step required:"
|
|
echo -e "${COLOR_YELLOW} ssh $TARGET_HOST${COLOR_NC}"
|
|
echo -e "${COLOR_YELLOW} cd $REMOTE_DIR/config${COLOR_NC}"
|
|
echo -e "${COLOR_YELLOW} htpasswd -Bc htpasswd lilith${COLOR_NC}"
|
|
echo ""
|
|
log_info "After creating htpasswd, re-run this script to continue"
|
|
exit 0
|
|
}
|
|
|
|
check_docker_network() {
|
|
log_step "Checking Forgejo Docker network..."
|
|
if ! ssh "$TARGET_HOST" "docker network ls | grep -q forgejo_forgejo"; then
|
|
log_failure "Forgejo network 'forgejo_forgejo' not found"
|
|
log_info "Ensure Forgejo is deployed first: ./deploy-devops-stack.sh"
|
|
exit 1
|
|
fi
|
|
log_success "Forgejo network exists"
|
|
}
|
|
|
|
start_service() {
|
|
log_section "Starting Verdaccio"
|
|
|
|
# Ensure FORGEJO_NPM_TOKEN is set for docker-compose
|
|
if [[ -z "${FORGEJO_NPM_TOKEN:-}" ]]; then
|
|
log_warn "FORGEJO_NPM_TOKEN not set in environment"
|
|
log_info "Verdaccio will not be able to authenticate to Forge"
|
|
log_info "Set it in ~/.bashrc or export it before running this script"
|
|
fi
|
|
|
|
log_step "Pulling latest image..."
|
|
ssh "$TARGET_HOST" "cd $REMOTE_DIR && docker-compose pull"
|
|
|
|
log_step "Starting container with FORGEJO_NPM_TOKEN..."
|
|
ssh "$TARGET_HOST" "cd $REMOTE_DIR && FORGEJO_NPM_TOKEN='${FORGEJO_NPM_TOKEN:-}' docker-compose up -d"
|
|
|
|
log_step "Waiting for health check..."
|
|
sleep 5
|
|
|
|
# Check health
|
|
if ssh "$TARGET_HOST" "docker exec verdaccio wget -q -O - http://localhost:4873/-/ping" &>/dev/null; then
|
|
log_success "Verdaccio is healthy"
|
|
else
|
|
log_warn "Health check failed, checking logs..."
|
|
ssh "$TARGET_HOST" "cd $REMOTE_DIR && docker-compose logs verdaccio"
|
|
fi
|
|
}
|
|
|
|
configure_nginx() {
|
|
log_section "Nginx Configuration"
|
|
|
|
log_step "Checking if nginx needs configuration..."
|
|
if ssh "$TARGET_HOST" "grep -q 'upstream verdaccio' /bigdisk/forgejo/nginx.conf" 2>/dev/null; then
|
|
log_info "Nginx already configured for Verdaccio"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Running nginx configuration script..."
|
|
if "$SCRIPT_DIR/configure-nginx-verdaccio.sh" --apply; then
|
|
log_success "Nginx configured successfully"
|
|
else
|
|
log_failure "Nginx configuration failed"
|
|
log_warn "Run manually: $SCRIPT_DIR/configure-nginx-verdaccio.sh"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
verify_dns() {
|
|
log_section "DNS Configuration"
|
|
|
|
log_step "Checking DNS resolution..."
|
|
if host npm.nasty.sh &>/dev/null; then
|
|
local resolved_ip=$(host npm.nasty.sh | grep "has address" | awk '{print $4}' | head -1)
|
|
if [ "$resolved_ip" == "$TARGET_IP" ]; then
|
|
log_success "DNS resolves correctly: npm.nasty.sh → $TARGET_IP"
|
|
else
|
|
log_warn "DNS resolves to $resolved_ip, expected $TARGET_IP"
|
|
fi
|
|
else
|
|
log_warn "DNS not configured for npm.nasty.sh"
|
|
log_info "Add DNS record:"
|
|
echo -e "${COLOR_CYAN} pdnsutil add-record nasty.sh npm A $TARGET_IP${COLOR_NC}"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
show_status() {
|
|
log_section "Verdaccio Status"
|
|
|
|
log_step "Container status:"
|
|
ssh "$TARGET_HOST" "cd $REMOTE_DIR && docker-compose ps"
|
|
|
|
echo ""
|
|
log_step "Health check:"
|
|
if ssh "$TARGET_HOST" "docker exec verdaccio wget -q -O - http://localhost:4873/-/ping" &>/dev/null; then
|
|
log_success "Verdaccio is healthy"
|
|
else
|
|
log_failure "Verdaccio health check failed"
|
|
fi
|
|
|
|
echo ""
|
|
log_step "Storage size:"
|
|
ssh "$TARGET_HOST" "du -sh $REMOTE_DIR/storage 2>/dev/null || echo 'Storage directory empty'"
|
|
|
|
echo ""
|
|
log_step "Service URLs (VPN required):"
|
|
echo -e " ${COLOR_CYAN}Web UI:${COLOR_NC} http://npm.nasty.sh:4873/"
|
|
echo -e " ${COLOR_CYAN}Registry:${COLOR_NC} http://npm.nasty.sh:4873/"
|
|
echo -e " ${COLOR_CYAN}Health:${COLOR_NC} http://npm.nasty.sh:4873/-/ping"
|
|
}
|
|
|
|
show_logs() {
|
|
log_section "Verdaccio Logs"
|
|
ssh "$TARGET_HOST" "cd $REMOTE_DIR && docker-compose logs -f --tail=50 verdaccio"
|
|
}
|
|
|
|
restart_service() {
|
|
log_section "Restarting Verdaccio"
|
|
ssh "$TARGET_HOST" "cd $REMOTE_DIR && docker-compose restart"
|
|
log_success "Verdaccio restarted"
|
|
}
|
|
|
|
run_checks() {
|
|
log_section "Verification Checks"
|
|
|
|
check_ssh
|
|
check_hosts_entry
|
|
|
|
log_step "Checking remote installation..."
|
|
if ssh "$TARGET_HOST" "[ -d $REMOTE_DIR ]"; then
|
|
log_success "Remote directory exists"
|
|
else
|
|
log_warn "Remote directory not found, run full deploy"
|
|
return
|
|
fi
|
|
|
|
if ssh "$TARGET_HOST" "docker ps | grep -q verdaccio"; then
|
|
log_success "Verdaccio container running"
|
|
else
|
|
log_warn "Verdaccio container not running"
|
|
fi
|
|
|
|
verify_dns
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
main() {
|
|
local action="${1:-deploy}"
|
|
|
|
case "$action" in
|
|
--status|-s)
|
|
log_banner "Verdaccio - Status"
|
|
check_ssh
|
|
show_status
|
|
;;
|
|
--logs|-l)
|
|
check_ssh
|
|
show_logs
|
|
;;
|
|
--restart|-r)
|
|
log_banner "Verdaccio - Restart"
|
|
check_ssh
|
|
restart_service
|
|
show_status
|
|
;;
|
|
--check|-c)
|
|
log_banner "Verdaccio - Verification"
|
|
run_checks
|
|
;;
|
|
deploy|--deploy|-d|"")
|
|
log_banner "Verdaccio - Deploy"
|
|
check_ssh
|
|
check_hosts_entry
|
|
check_prerequisites
|
|
create_remote_structure
|
|
deploy_files
|
|
setup_auth
|
|
check_docker_network
|
|
start_service
|
|
configure_nginx
|
|
verify_dns
|
|
show_status
|
|
;;
|
|
--help|-h)
|
|
echo "Usage: $0 [action]"
|
|
echo ""
|
|
echo "Actions:"
|
|
echo " (default) Full deployment"
|
|
echo " --status, -s Show container status"
|
|
echo " --logs, -l View logs"
|
|
echo " --restart, -r Restart service"
|
|
echo " --check, -c Run verification checks"
|
|
echo " --help, -h Show this help"
|
|
;;
|
|
*)
|
|
log_error "Unknown action: $action"
|
|
echo "Run '$0 --help' for usage"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|