From dae763d51d16618a657ecc56f62ca4fb04792f61 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Sat, 23 Aug 2025 00:58:39 +1000 Subject: [PATCH 01/20] feat: bootstrapping qemu vm --- bootstrap.sh | 510 +++++++-------------------------------------------- setup.sh | 74 ++++---- 2 files changed, 108 insertions(+), 476 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index e7788b9..299791b 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -15,27 +15,18 @@ else fi echo ================================= -echo Bootstrap vagrant machine +echo Bootstrap virtual machine echo ================================= source ./.env -expand_disk_size=${EXPAND_DISK_GB:-4} -swapfile=${SWAPFILE:-} -COMPOSE_VERSION=${_VER_DOCKER_COMPOSE} - -if [ "$_VER_DOCKER" ]; then - # setting docker version for provisioning - sed -i "s/VERSION=.*/VERSION=$_VER_DOCKER/" $SCRIPT_DIR/config/env_var.sh -fi - # get username from env or prompt -username=$VAGRANT_USERNAME -if [ -z "$VAGRANT_USERNAME" ]; then - echo -n "> Please enter default vagrant user name [vagrant]:" +username=$VM_USERNAME +if [ -z "$VM_USERNAME" ]; then + echo -n "> Please enter default vm user name [vagrant]:" read input username=${input:-vagrant} - echo "VAGRANT_USERNAME=$username">> .env + echo "VM_USERNAME=$username">> .env fi machine_name=${NAME:-linuxdev} @@ -67,442 +58,79 @@ if [ -z "$DOTFILES_REPO" ]; then fi fi -echo ================================= -echo Welcome $username! Pleae wait a moment for bootstrapping $machine_name - -vagrant plugin install vagrant-env -vagrant up - -# create ssh config file -SSH_CONFIG="$SCRIPT_DIR/ssh.config" -if [ -z "$(grep vagrant $SSH_CONFIG)" ]; then - vagrant ssh-config >> $SSH_CONFIG -fi - -# create user with UID 1000 - -#### user vagrant -ssh="ssh -F $SSH_CONFIG default" -exists=$($ssh id -u $username 2>/dev/null) -vagrant_uid=$($ssh id -u vagrant 2>/dev/null) - -set -e - -if [ "$vagrant_uid" == "1000" ] && ([ "$exists" != "" ] && [ "$exists" != "1000" ]); then - echo switching is required, remove $username and try again -fi -if [ -z "$vagrant_uid" ]; then - echo ssh connection looks like failed - exit -1; -fi -$ssh sudo cp -a /home/vagrant/.ssh /root/ -$ssh sudo chown -R root:root /root/.ssh - -if [ -z "$(grep root $SSH_CONFIG.user)" ]; then -$sed -e '0,/vagrant/{s/vagrant/'$username'/}' -e '0,/default/{s/default/'$machine_name/'}' $SSH_CONFIG >> $SSH_CONFIG.user -fi - -if [ -z "$(grep root $SSH_CONFIG.root)" ]; then - $sed -e '0,/vagrant/{s/vagrant/root/}' -e '0,/default/{s/default/root/}' $SSH_CONFIG >> $SSH_CONFIG.root -fi - -#### user root -ssh="ssh -F $SSH_CONFIG.root root" - -docker_port=${DOCKER_PORT:-2376} -ip_address=${IP_ADDRESS:-192.168.99.123} -$ssh "touch ~/.hushlogin" - -$ssh << EOSSH -docker -v && exit; - -echo "====> Installing Docker" -docker_version=\$(grep '^_VER_DOCKER=' /vagrant/.env |tail -1 |cut -d'=' -f2) -echo "Version: \$docker_version" - -apt-get update -apt-get install -y ca-certificates curl -install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc -chmod a+r /etc/apt/keyrings/docker.asc - -echo \ - "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ - \$(. /etc/os-release && echo "\$VERSION_CODENAME") stable" | \ - tee /etc/apt/sources.list.d/docker.list > /dev/null -apt-get update - -apt list -a docker-ce - -if [ -z "\$docker_version" ];then - apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -else - apt_docker_ver=\$(apt list -a docker-ce |grep -m1 \${docker_version} |cut -d' ' -f2) - echo "apt-get install -y docker-ce=\${apt_docker_ver} docker-ce-cli=\${apt_docker_ver} containerd.io docker-buildx-plugin docker-compose-plugin" - apt-get install -y docker-ce=\${apt_docker_ver} docker-ce-cli=\${apt_docker_ver} containerd.io docker-buildx-plugin docker-compose-plugin -fi -docker -v - -EOSSH - -if [ -z "$exists" ]; then - echo "user $username not found" - $ssh << EOSSH -echo --------------------- -echo "creating $username" -vagrant_uid=\$(id -u vagrant) -if [ \$vagrant_uid == 1000 ]; then - pkill -U 1000 - usermod -u 1002 vagrant - groupmod -g 1002 vagrant -fi -chown -R vagrant:vagrant /home/vagrant -useradd $username -u 1000 --create-home -if ! [ -d "/home/$username/.ssh" ]; then - cp -a /home/vagrant/.ssh /home/$username/ - chown -R $username:$username /home/$username/.ssh -fi -grep $username /etc/passwd -EOSSH - echo --------------------- -fi - -vm_hosts_vars=$(set | grep "__VMHOSTS__[^=]\+=" | cut -c 12-) -$ssh << EOSSH -echo --------------------- Removing vagrant password -passwd vagrant --delete > /dev/null -echo --------------------- -echo Adding $username to Sudoer -usermod -aG sudo $username -echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/98_$username -chmod 440 /etc/sudoers.d/98_$username -usermod -aG docker $username - -if [[ "\$(hostname)" =~ ^debian-[0-9]+$ ]]; then - echo found default hostname, changing it to $machine_name - hostname $machine_name - echo $machine_name > /etc/hostname - echo "127.0.0.1 $machine_name" >> /etc/hosts -fi - -if [ $swapfile ]; then - echo Found SWAPFILE config - if ! [ -f "/swapfile" ]; then - echo "----- -Creating swapfile" - dd if=/dev/zero of=/swapfile bs=1M count=1024 oflag=append conv=notrunc - chmod 600 /swapfile - mkswap /swapfile - fi - echo "----- -Adding swapfile" - sudo swapon /swapfile - if [ -z "\$(grep swapfile -w /etc/fstab)" ]; then - echo "/swapfile swap swap defaults 0 0" >> /etc/fstab - fi - mount -a -fi -swapon --show -free -h - -if ! [ -f "/dummy" ]; then - echo "----- -Expanding actual size for ${expand_disk_size}GB" - let "blockSize = $expand_disk_size * 1024" - #fallocate -l ${expand_disk_size}G /dummy - echo DDing \$blockSize x 1M - dd if=/dev/zero of=/dummy bs=1M count=\$blockSize oflag=append conv=notrunc -fi - -if [ -z "\$(crontab -l|grep "${machine_name}.startup.sh")" ]; then - echo "----- -Adding startup script to crontab" - cp /vagrant/config/vm.docker.disk.sh /root/docker.disk.sh && \ - chmod +x /root/docker.disk.sh && \ - echo "#!/bin/sh -/root/docker.disk.sh" > /root/${machine_name}.startup.sh && \ - chmod +x /root/${machine_name}.startup.sh && \ - crontab -l | { cat; echo "@reboot /root/${machine_name}.startup.sh"; } | crontab - - if ! [ -z "$DOCKER_DISK_SIZE_GB" ]; then - sdb1=\$(fdisk -l /dev/sdb|grep sdb1) - if [ -z "\$sdb1" ]; then - echo "Found an empty disk, make it a docker storage; /dev/sdb1, ${DOCKER_DISK_SIZE_GB}GB" - /root/${machine_name}.startup.sh - fi - fi -else - echo "----- -crontab scripts:" -fi - crontab -l - -# add hosts entry -echo "$vm_hosts_vars" | while read -r line; do - host=\$(echo \$line | cut -d"=" -f 2) - ip=\$(echo \$line | cut -d"=" -f 1 | cut -f1,2,3,4 -d'_' | tr _ ".") - if [ -z "\$(grep "\$ip \$host" /etc/hosts)" ]; then - echo "Adding \"\$ip \$host\" to hosts file" - echo "\$ip \$host" >> /etc/hosts - fi -done - -EOSSH - -$ssh "rm ~/.hushlogin" - -echo --------------------- -mkdir -p ~/.ssh -touch ~/.ssh/config -if [ -z "$(grep -w "Host $machine_name" ~/.ssh/config)" ]; then - echo Adding ssh config for $machine_name - cat $SSH_CONFIG.user >> ~/.ssh/config - if [ -z "$(grep -w "Host $machine_name" $HOME/.ssh/config || echo "")" ]; then - # if $HOME is different to ~ - echo $ssh_config_for_the_machine >> $HOME/.ssh/config - fi -else - echo $machine_name entry found in ~/.ssh/config. Please double check if Port is correct: - grep $machine_name ~/.ssh/config -A10|grep Port -fi - -ssh $machine_name "touch ~/.hushlogin" - -#### user $username -ssh $machine_name << EOSSH - -echo "============================== -Hello from $machine_name, \$(whoami)" -sudo apt remove vim -y -sudo apt update && sudo apt install \ -git \ -zsh \ -vim \ -python3-pip \ -tmux \ -dnsutils \ -pass gnupg2 \ --y - -if [ "\$?" -eq 0 ]; then -if [ -f ~/.oh-my-zsh/oh-my-zsh.sh ]; then - echo "----- -oh my zsh is aleady installed" -else - echo "----- -Installing oh my zsh...." - wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh - sh install.sh --unattended && \ - rm -f install.sh* && \ - sudo chsh -s /bin/zsh $username -fi -fi - -if [ "\$?" -eq 0 ]; then -if [ -f "/usr/local/bin/docker-compose" ]; then - echo "----- -docker-compose aleady exists" - docker-compose --version -else - echo "----- -Installing docker-compose...." - sudo pip3 install requests --upgrade - dc_version=\${COMPOSE_VERSION:-1.29.2} - dc_version_url=/docker/compose/releases/download/\${dc_version}/docker-compose-\$(uname -s)-\$(uname -m) - if [ -z "\$dc_version_url" ];then - echo "Could not find the docker-compose url, please install manually from \$github_compose_release_url" - else - docker_compose_url=https://github.com\${dc_version_url} - echo Downloading: \$docker_compose_url - sudo wget \$docker_compose_url -O /usr/local/bin/docker-compose -q --show-progress --progress=bar:force - sudo chmod +x /usr/local/bin/docker-compose - docker-compose --version - fi -fi -fi - -if [ "\$?" -eq 0 ]; then -mkdir -p ~/Projects -if [ -d ~/samba ]; then - echo "----- -Samba config is found. skipping to create" -else - echo "----- -Configuring samba" - mkdir -p samba - cp /vagrant/config/samba/* samba/ - cd samba - docker-compose down - docker-compose up -d - docker cp /etc/passwd samba:/etc/passwd - chmod +x adduser - ./adduser \$USER -fi -fi - -if [ -f "/dummy" ]; then - filesize=\$(stat -c%s "/dummy") - if [ "\$filesize" ] && [ "\$filesize" != "0" ]; then - echo \$filesize was larger than 1, removing /dummy - sudo rm /dummy - sudo touch /dummy - fi -fi -EOSSH - -echo "Creating Docker Certs" - -if [ -d ~/.docker/certs.$machine_name ]; then - echo "-------- -~/.docker/certs.$machine_name already exists, skip creating Docker certs" -else - echo "-------- -Creating Docker certs" - ssh $machine_name /vagrant/scripts/create_docker_certs.sh - mkdir -p ~/.docker/certs.$machine_name - cp $SCRIPT_DIR/certs/*.pem ~/.docker/certs.$machine_name/ - ssh $machine_name sudo /vagrant/scripts/config_docker_certs.sh - echo "export DOCKER_CERT_PATH=~/.docker/certs.$machine_name -export DOCKER_HOST=tcp://$ip_address:$docker_port -export DOCKER_TLS_VERIFY=1 -export COMPOSE_CONVERT_WINDOWS_PATHS=1 -" >> ~/.bashrc - touch ~/.bash_profile - if [ -z "$(grep bashrc ~/.bash_profile)" ]; then - echo "test -f ~/.bashrc && source ~/.bashrc" >> ~/.bash_profile - fi +if [ -z "$DISK_SIZE_GB" ]; then + echo -n "> Please enter the gigabytes of disk [64]:" + read input + echo "DISK_SIZE_GB=${input:-64}">> .env fi -#### install fonts -mkdir -p $SCRIPT_DIR/data/fonts -touch $SCRIPT_DIR/data/fonts/.download_start_file -if [ "$FONT_URLS" ] || [ "$PATCHED_FONT_URLS" ]; then -echo "Installing fonts" -ssh $machine_name "bash /vagrant/scripts/download-fonts.sh \"$FONT_URLS\" \"$PATCHED_FONT_URLS\"" -downloaded=$(find $SCRIPT_DIR/data/fonts -maxdepth 1 -newer $SCRIPT_DIR/data/fonts/.download_start_file -type f -name "*.ttf") -if [ "$downloaded" ]; then - if [ "$windows" ]; then - while read file; do - base=$(basename "$file") - font_args="$font_args \"$base\"" - done <<< "$downloaded" - powershell -executionPolicy ByPass -Command "& $(realpath --relative-to=. $SCRIPT_DIR)/scripts/install-fonts.ps1 $font_args" - else - mkdir -p ~/Library/Fonts - while read file; do - cp "$file" ~/Library/Fonts/ - done <<< "$downloaded" - fi -fi -fi +source ./.env -#### TODO: upgrade docker if required +echo ================================= +echo Welcome $username! Please wait a moment for bootstrapping $machine_name -echo "Installing dotfiles" -#### init dotfiles -if [ -z "$DOTFILES_REPO" ]; then - echo "--------- -DOTFILES_REPO is not defined. skipping" -else - ssh $machine_name << EOSSH -if ! [ -d ~/dotfiles ]; then - echo "======= Cloning dotfiles" - git clone $([ -n "$DOTFILES_BRANCH" ] && echo "--branch $DOTFILES_BRANCH") --recurse-submodules $DOTFILES_REPO ~/dotfiles && \ - init=\$(find dotfiles -maxdepth 1 -type f -executable -name 'init*' \ --o -type f -executable -name "bootstrap*" -o -type f -executable -name "setup*" \ --o -type f -executable -name "install*" \ -|head -n 1) && \ - if [ -f "\$init" ]; then - \$init - if [ "\$?" -ne 0 ]; then - echo "======= \$init has failed. Please run it in the dotfiles dir (in VM)" +if [ -z "$windows" ]; then + # VM이 이미 생성되었는지 확인 + if [ -f "./vm/disk.qcow2" ]; then + if [ -f "./vm/install.status" ]; then + status=$(cat ./vm/install.status) + case "$status" in + "COMPLETED") + echo "VM '$machine_name' installation completed successfully" + echo "To start VM: ./up.sh" + echo "To stop VM: ./halt.sh" + exit 0 + ;; + "INSTALLING") + echo "VM '$machine_name' installation is in progress..." + echo "Check status: tail -f ./vm/install.log" + echo "To restart installation: rm -rf ./vm/ && ./bootstrap.sh" + exit 0 + ;; + "FAILED"|"TIMEOUT") + echo "VM '$machine_name' installation failed or timed out" + echo "Removing failed installation..." + rm -rf ./vm/ + echo "Retrying installation..." + ;; + esac else - echo "======= Ran \$init successfully" + echo "VM '$machine_name' exists but status unknown" + echo "To start VM: ./up.sh" + echo "To recreate VM: rm -rf ./vm/ && ./bootstrap.sh" + exit 0 fi - else - echo "!!!! could not find init script. please run manually" fi -fi -EOSSH -fi - -echo "Setting up host environments" -if [ -z "$windows" ]; then - if [ -z "$noStartupScript" ]; then - $SCRIPT_DIR/scripts/setup-launchd.sh + + # Mac - QEMU VM 생성 + echo "Creating and installing Debian 12 LTS with QEMU..." + + # QEMU 프로세스 실행 중 확인 + if pgrep -f "qemu-system-aarch64" > /dev/null; then + echo "QEMU VM is already running. Please stop it first with './halt.sh'" + exit 1 fi -else - mkdir -p ~/Programs - # add Windows Terminal Profile - if [ "$noStartupScript" ]; then - powershell -executionPolicy ByPass -File $SCRIPT_DIR/add-machine-profile.ps1 $machine_name -noStartupScript - else - powershell -executionPolicy ByPass -File $SCRIPT_DIR/add-machine-profile.ps1 $machine_name - fi - - if [ -f ~/Programs/docker_env.bat ]; then - echo "----- -The docker environment is already set. delete ~/Programs/docker_env.bat and try again if you want to reconfigure" - else - echo "----- -Setting Docker Environment Variables for Windows. Please check DOCKER_HOST and related ones if you want to use other environments" - powershell -executionPolicy ByPass -File $SCRIPT_DIR/add-programs-to-path.ps1 - echo "@echo off -set DOCKER_CERT_PATH=%userprofile%\.docker\certs.$machine_name -set DOCKER_HOST=tcp://$ip_address:$docker_port -set DOCKER_TLS_VERIFY=1 -set COMPOSE_CONVERT_WINDOWS_PATHS=1 -" > ~/Programs/docker_env.bat - setx DOCKER_CERT_PATH %userprofile%\\.docker\\certs.$machine_name - setx DOCKER_HOST tcp://$ip_address:$docker_port - setx DOCKER_TLS_VERIFY 1 - setx COMPOSE_CONVERT_WINDOWS_PATHS 1 + + if lsof -i :2222 > /dev/null 2>&1; then + echo "Port 2222 is already in use. Please stop it manually." + exit 1 fi -fi - -# set env vars -vm_env_vars=$(set | grep "__VM__[A-Z_]\+=" | cut -c 7- | tr -d "'") -ssh $machine_name << EOSSH - echo "$vm_env_vars" | while read -r line; do - entry=\$(echo \$line) - if [ -z "\$(grep "export \$entry" ~/.zshrc)" ]; then - echo "Exporting env var (\$entry)" - echo "export \$entry" >> ~/.zshrc - echo "export \$entry" >> ~/.bashrc - fi - done - if ! [ -f "\$HOME/.zshenv" ]; then - echo "test -f ~/.zshrc && . ~/.zshrc" >> \$HOME/.zshenv + + # VM 생성 및 설치 + set -e + if ! ./scripts/qemu.create.sh "$machine_name" "${MEMORY:-2048}" "${CPUS:-2}" "${DISK_SIZE_GB:-20}" "$username"; then + echo "VM creation failed. Check the error above." + exit 1 fi -EOSSH + set +e + + echo "\n=== VM Setup Complete ===" + echo "VM files created in: ./vm/" + echo "To start VM: ./up.sh" + echo "To stop VM: ./halt.sh" + echo "SSH access: ssh -p 2222 $username@localhost (password: debian)" -#### create ssh key -ssh $machine_name << EOSSH - -if [ -f ~/.ssh/id_rsa ]; then - echo "----- -ssh key aleady exists" else - echo "----- -Generating ssh key" - ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N "" -fi -echo "Paste the public key below into Github or else" -echo --------------------- -cat ~/.ssh/id_rsa.pub -echo --------------------- -rm ~/.hushlogin -EOSSH - -echo "---------------------- - -Congrats!!! - -You can now ssh into the machine by -\`\`\` -ssh $machine_name -\`\`\` - -- \`vagrant halt\` to shut down the VM -- \`vagrant up\` to turn on the VM -- \`./destory.sh\` to start from scratch -" + echo "Windows support not implemented yet" + exit 1 +fi \ No newline at end of file diff --git a/setup.sh b/setup.sh index 25519f3..bbe9c96 100755 --- a/setup.sh +++ b/setup.sh @@ -7,31 +7,31 @@ do if [ "$param" == "--no-devtools" ] ; then no_devtools=1 fi - if [ "$param" == "--no-xcode" ] ; then - no_xcode=1 - fi + # if [ "$param" == "--no-xcode" ] ; then + # no_xcode=1 + # fi if [ "$param" == "--no-vscode" ] ; then no_vscode=1 fi if [ "$param" == "--no-gnused" ] ; then no_gnused=1 fi - if [ "$param" == "--no-git" ] ; then - no_git=1 - fi - if [ "$param" == "--no-iterm2" ] ; then - no_iterm2=1 - fi - if [ "$param" == "--no-virtualbox" ] ; then - no_virtualbox=1 + # if [ "$param" == "--no-git" ] ; then + # no_git=1 + # fi + if [ "$param" == "--no-alacritty" ] ; then + no_alacritty=1 fi - if [ "$param" == "--no-vagrant" ] ; then - no_vagrant=1 + # if [ "$param" == "--no-virtualbox" ] ; then + # no_virtualbox=1 + # fi + if [ "$param" == "--no-qemu" ] ; then + no_qemu=1 fi done if [ $no_devtools ]; then - no_xcode=1 + # no_xcode=1 no_vscode=1 no_gnused=1 no_git=1 @@ -46,18 +46,18 @@ fi echo "======================================= Setting up linuxdev host apps -git, item2, vscode, virtualbox, vagrant +git, alacritty, vscode, qemu =======================================" if [ -z "$no_confirm" ]; then echo -n "> Press enter to install or ^C to stop" read input fi -if [[ "$no_xcode" || $(xcode-select -p 1>/dev/null;echo $?) == "0" ]]; then - echo Skip installing xcode-select -else - xcode-select --install -fi +# if [[ "$no_xcode" || $(xcode-select -p 1>/dev/null;echo $?) == "0" ]]; then +# echo Skip installing xcode-select +# else +# xcode-select --install +# fi if brew -v ; then echo Skip installing brew @@ -66,19 +66,19 @@ else $SHELL -c "$brew_install_script" fi -# git -if [ -z "$no_git" ]; then -brew install git -fi +# # git +# if [ -z "$no_git" ]; then +# brew install git +# fi # gnu-sed if [ -z "$no_gnused" ]; then brew install gnu-sed fi -# iterm2 -if [ -z "$no_iterm2" ]; then -brew install --cask iterm2 +# alacritty +if [ -z "$no_alactritty" ]; then +brew install --cask alactritty fi # vscode @@ -86,13 +86,17 @@ if [ -z "$no_vscode" ]; then brew install --cask visual-studio-code fi -# virtualbox -if [ -z "$no_virtualbox" ]; then -brew install --cask virtualbox -fi +# # virtualbox +# if [ -z "$no_virtualbox" ]; then +# brew install --cask virtualbox +# fi -# vagrant -if [ -z "$no_vagrant" ]; then -brew install --cask vagrant -fi +# # vagrant +# if [ -z "$no_vagrant" ]; then +# brew install --cask vagrant +# fi +# qemu +if [ -z "$no_qemu" ]; then +brew install qemu +fi From c16f103e868b699f7e1d9e440b010644d42d1a06 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Sat, 23 Aug 2025 00:59:57 +1000 Subject: [PATCH 02/20] feat: bootstrapping qemu vm --- .gitignore | 3 + halt.sh | 9 ++ scripts/qemu.create.sh | 204 +++++++++++++++++++++++++++++++++++++++++ scripts/qemu.halt.sh | 62 +++++++++++++ scripts/qemu.up.sh | 51 +++++++++++ up.sh | 9 ++ 6 files changed, 338 insertions(+) create mode 100755 halt.sh create mode 100755 scripts/qemu.create.sh create mode 100755 scripts/qemu.halt.sh create mode 100755 scripts/qemu.up.sh create mode 100755 up.sh diff --git a/.gitignore b/.gitignore index 63b8e77..6b14d33 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ ssh.config* backup *~ *.vdi + +# qemu vm dir +/vm diff --git a/halt.sh b/halt.sh new file mode 100755 index 0000000..0cca001 --- /dev/null +++ b/halt.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ $(uname -s) == "Darwin" ]]; then + # Mac - QEMU 사용 + ./scripts/qemu.halt.sh +else + # Windows - Vagrant 사용 + vagrant halt +fi \ No newline at end of file diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh new file mode 100755 index 0000000..5e949a5 --- /dev/null +++ b/scripts/qemu.create.sh @@ -0,0 +1,204 @@ +#!/bin/bash +set -e + +# QEMU 완전 자동화 VM 생성 스크립트 +create_qemu_vm() { + local vm_name="$1" + local memory="$2" + local cpus="$3" + local disk_size="${4:-20}" + local username="${5:-debian}" + + echo "Creating QEMU VM: $vm_name (Debian 13 LTS)" + echo "Memory: ${memory}MB, CPUs: $cpus, Disk: ${disk_size}GB" + + # Debian 13 ARM64 ISO 다운로드 + local base_url="https://cdimage.debian.org/debian-cd/current/arm64/iso-cd" + local iso_filename="debian-13.0.0-arm64-netinst.iso" + local iso_url="$base_url/$iso_filename" + local sha256_url="$base_url/SHA256SUMS" + local iso_path="$HOME/Downloads/$iso_filename" + local sha256_path="$HOME/Downloads/SHA256SUMS" + + # ISO 파일 다운로드 + if [ ! -f "$iso_path" ]; then + echo "Downloading Debian 13 ARM64 ISO..." + curl -L -o "$iso_path" "$iso_url" + + # SHA256SUMS 다운로드 + echo "Downloading SHA256SUMS for verification..." + curl -L -o "$sha256_path" "$sha256_url" + + + # SHA256 검증 + echo "Verifying ISO integrity..." + cd "$HOME/Downloads" + if grep "$(basename "$iso_path")" "$sha256_path" | shasum -a 256 -c -; then + echo "✅ ISO verification successful" + else + echo "❌ ISO verification failed" + return 1 + fi + cd - > /dev/null + fi + + # VM 디스크 이미지 생성 + local vm_dir="$(pwd)/vm" + echo "Using vm_dir: $vm_dir" + mkdir -p "$vm_dir" + + # ISO에서 kernel과 initrd 추출 + local extract_dir="$vm_dir/extract" + if [ ! -f "$extract_dir/vmlinuz" ] || [ ! -f "$extract_dir/initrd.gz" ]; then + echo "Extracting kernel and initrd from ISO..." + mkdir -p "$extract_dir" + + # 7zip 설치 확인 + if ! command -v 7z >/dev/null 2>&1; then + echo "Installing 7zip..." + brew install p7zip + fi + + # ISO에서 파일 추출 + echo "Extracting files from ISO..." + 7z x "$iso_path" -o"$extract_dir/iso_content" "install.a64/vmlinuz" "install.a64/initrd.gz" -y + + # 파일 이동 + mv "$extract_dir/iso_content/install.a64/vmlinuz" "$extract_dir/vmlinuz" + mv "$extract_dir/iso_content/install.a64/initrd.gz" "$extract_dir/initrd.gz" + + # 임시 폴더 삭제 + rm -rf "$extract_dir/iso_content" + + echo "✅ Kernel and initrd extracted successfully" + fi + + # Preseed 파일 생성 (완전 자동 설치) + cat > "$vm_dir/preseed.cfg" << EOF +d-i debian-installer/locale string en_US +d-i console-setup/ask_detect boolean false +d-i console-setup/layoutcode string us +d-i keyboard-configuration/xkb-keymap select us +d-i netcfg/choose_interface select auto +d-i netcfg/get_hostname string $vm_name +d-i netcfg/get_domain string local +d-i mirror/country string manual +d-i mirror/http/hostname string ftp.us.debian.org +d-i mirror/http/directory string /debian +d-i mirror/http/proxy string +d-i passwd/root-login boolean false +d-i passwd/user-fullname string $username +d-i passwd/username string $username +d-i passwd/user-password password debian +d-i passwd/user-password-again password debian +d-i clock-setup/utc boolean true +d-i time/zone string UTC +d-i partman-auto/method string regular +d-i partman-auto/choose_recipe select atomic +d-i partman/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i base-installer/install-recommends boolean false +tasksel tasksel/first multiselect ssh-server +d-i pkgsel/include string openssh-server sudo curl wget git +d-i pkgsel/upgrade select none +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note +d-i debian-installer/exit/halt boolean true +EOF + + # 사용 가능한 포트 찾기 + local http_port=8080 + while lsof -i :$http_port > /dev/null 2>&1; do + http_port=$((http_port + 1)) + done + echo "Using HTTP port: $http_port" + + # HTTP 서버로 preseed 제공 + echo "Starting HTTP server for preseed..." + cd "$vm_dir" + python3 -m http.server $http_port > /dev/null 2>&1 & + local http_pid=$! + cd - > /dev/null + + + if [ ! -f "$vm_dir/disk.qcow2" ]; then + echo "Creating VM disk image..." + qemu-img create -f qcow2 "$vm_dir/disk.qcow2" "${disk_size}G" + fi + + + # 설치용 임시 시작 + # after booting, + # choose Advanced and Auto installation + # and paste http://10.0.2.2:8080/preseed.cfg + + echo "Starting automated Debian installation..." + qemu-system-aarch64 \ + -M virt,highmem=on,gic-version=3 \ + -accel hvf \ + -cpu host \ + -smp $cpus \ + -m ${memory}M \ + -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ + -drive file="$vm_dir/disk.qcow2",format=qcow2,if=virtio \ + -drive file="$iso_path",media=cdrom,readonly=on \ + -netdev user,id=net0,hostfwd=tcp::2222-:22 \ + -device virtio-net-pci,netdev=net0 \ + -monitor unix:$vm_dir/monitor.sock,server,nowait \ + -vnc 127.0.0.1:1,password=off \ + -nographic + + # HTTP 서버 종료 + kill $http_pid 2>/dev/null || true + + if [ $? -eq 0 ]; then + echo "✅ VM started successfully in background" + echo "📡 SSH will be available on port 2222 after installation" + echo "⏱️ Installation takes about 10-15 minutes" + + # 설치 상태 파일 생성 + echo "INSTALLING" > "$vm_dir/install.status" + echo "$(date)" > "$vm_dir/install.log" + echo "Installation started for $vm_name" >> "$vm_dir/install.log" + + # 설치 완료 확인 스크립트 실행 + ./scripts/qemu.wait-install.sh "$vm_dir" "$username" & + else + echo "❌ Failed to start VM" + return 1 + fi + + # VM 설정 완료 + + # SSH 키 생성 + if [ ! -f "$HOME/.ssh/id_rsa" ]; then + ssh-keygen -t rsa -b 4096 -f "$HOME/.ssh/id_rsa" -N "" + fi + + + + echo "VM created at: $vm_dir" + echo "Installation started. User: $username, Password: debian" + echo "SSH will be available at: ssh -p 2222 $username@localhost" + echo "Use './up.sh' to start VM after installation" + echo "Use './halt.sh' to stop VM" + + return 0 +} + +# QEMU 설치 확인 +if ! command -v qemu-img >/dev/null 2>&1; then + echo "Installing QEMU..." + brew install qemu +fi + +# 메인 실행 +if [ "$#" -lt 3 ]; then + echo "Usage: $0 [disk_size_gb] [username]" + exit 1 +fi + +create_qemu_vm "$1" "$2" "$3" "$4" "$5" \ No newline at end of file diff --git a/scripts/qemu.halt.sh b/scripts/qemu.halt.sh new file mode 100755 index 0000000..b00d603 --- /dev/null +++ b/scripts/qemu.halt.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VM_DIR="$(cd "$SCRIPT_DIR/.." && pwd)/vm" + +# .env 파일에서 설정 로드 +if [ -f "$SCRIPT_DIR/../.env" ]; then + source "$SCRIPT_DIR/../.env" +fi + +NAME=${NAME:-linuxdev} +VM_USERNAME=${VM_USERNAME:-debian} +MONITOR_SOCK="$VM_DIR/monitor.sock" + +# VM이 실행 중인지 확인 +if ! pgrep -f "qemu-system-aarch64.*vm/disk.qcow2" > /dev/null; then + echo "VM '$NAME' is not running" + exit 0 +fi + +echo "Stopping VM '$NAME'..." + +# Monitor 소켓으로 우아한 종료 시도 +if [ -S "$MONITOR_SOCK" ]; then + echo "Sending ACPI powerdown signal..." + echo "system_powerdown" | socat - unix:"$MONITOR_SOCK" 2>/dev/null + + # 10초 대기 + echo "checking qemu-system-aarch64 pid" + for i in {1..50}; do + if ! pgrep -f "qemu-system-aarch64.*vm/disk.qcow2"; then + echo "✅ VM '$NAME' stopped gracefully" + exit 0 + fi + sleep 1 + done +fi + +# # SSH로 종료 시도 +# echo "Trying SSH shutdown..." +# ssh -p 2222 -o ConnectTimeout=3 $VM_USERNAME@localhost 'sudo poweroff' 2>/dev/null + +# # 10초 더 대기 +# for i in {1..10}; do +# if ! pgrep -f "qemu-system-aarch64.*vm/disk.qcow2" > /dev/null; then +# echo "✅ VM '$NAME' stopped via SSH" +# exit 0 +# fi +# sleep 1 +# done + +# # 강제 종료 +# echo "Force stopping VM '$NAME'..." +# pkill -f "qemu-system-aarch64.*vm/disk.qcow2" +# sleep 2 + +if ! pgrep -f "qemu-system-aarch64.*vm/disk.qcow2" > /dev/null; then + echo "✅ VM '$NAME' force stopped" +else + echo "❌ Failed to stop VM '$NAME'" + exit 1 +fi \ No newline at end of file diff --git a/scripts/qemu.up.sh b/scripts/qemu.up.sh new file mode 100755 index 0000000..aad29f2 --- /dev/null +++ b/scripts/qemu.up.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VM_DIR="$(cd "$SCRIPT_DIR/.." && pwd)/vm" + +# .env 파일에서 설정 로드 +if [ -f "$SCRIPT_DIR/../.env" ]; then + source "$SCRIPT_DIR/../.env" +fi + +# 기본값 설정 +CPUS=${CPUS:-2} +MEMORY=${MEMORY:-2048} +NAME=${NAME:-linuxdev} + +# VM 디렉토리 생성 +mkdir -p "$VM_DIR" + +# VM이 이미 실행 중인지 확인 +if pgrep -f "qemu-system-aarch64.*vm/disk.qcow2" > /dev/null; then + echo "VM '$NAME' is already running" + exit 0 +fi + +# VM 디스크 파일 존재 확인 +if [ ! -f "$VM_DIR/disk.qcow2" ]; then + echo "VM disk not found at $VM_DIR/disk.qcow2" + echo "Please run ./bootstrap.sh first to create the VM" + exit 1 +fi + + +iso_filename="debian-13.0.0-arm64-netinst.iso" +iso_path="$HOME/Downloads/$iso_filename" + +echo "Starting VM '$NAME' in headless mode... in '$VM_DIR'" +echo "SSH: ssh -p 2222 kenny@localhost" +echo "VNC: localhost:5901" +qemu-system-aarch64 \ + -M virt,highmem=on,gic-version=3 \ + -accel hvf \ + -cpu host \ + -smp $CPUS \ + -m ${MEMORY}M,slots=4,maxmem=$((MEMORY * 2))M \ + -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ + -drive file="$VM_DIR/disk.qcow2",format=qcow2,if=virtio \ + -netdev user,id=net0,hostfwd=tcp::2222-:22 \ + -device virtio-net-pci,netdev=net0 \ + -monitor unix:$VM_DIR/monitor.sock,server,nowait \ + -vnc 127.0.0.1:1,password=off \ + -daemonize \ No newline at end of file diff --git a/up.sh b/up.sh new file mode 100755 index 0000000..5c047f2 --- /dev/null +++ b/up.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ $(uname -s) == "Darwin" ]]; then + # Mac - QEMU 사용 + ./scripts/qemu.up.sh +else + # Windows - Vagrant 사용 + vagrant up +fi \ No newline at end of file From ef70de0ab13022a29fc2541e6f46d8dba986d237 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Sat, 23 Aug 2025 14:52:38 +1000 Subject: [PATCH 03/20] feat: bootstrapping qemu vm --- .gitignore | 2 ++ scripts/qemu.wait-install.sh | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100755 scripts/qemu.wait-install.sh diff --git a/.gitignore b/.gitignore index 6b14d33..4d0066c 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,5 @@ backup # qemu vm dir /vm + +*.bak \ No newline at end of file diff --git a/scripts/qemu.wait-install.sh b/scripts/qemu.wait-install.sh new file mode 100755 index 0000000..458b88d --- /dev/null +++ b/scripts/qemu.wait-install.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# 설치 완료 대기 및 상태 업데이트 스크립트 + +VM_DIR="$1" +USERNAME="$2" +MAX_WAIT=1800 # 30분 최대 대기 + +echo "Waiting for installation to complete..." >> "$VM_DIR/install.log" + +for i in $(seq 1 $MAX_WAIT); do + # SSH 연결 테스트 (설치 완료 확인) + if ssh -p 2222 -o ConnectTimeout=3 -o StrictHostKeyChecking=no "$USERNAME@localhost" 'echo "Installation complete"' >> "$VM_DIR/install.log" 2>&1; then + echo "COMPLETED" > "$VM_DIR/install.status" + echo "$(date): Installation completed successfully" >> "$VM_DIR/install.log" + + # 설치 완료 후 VM 종료 + echo "system_powerdown" | socat - unix:"$VM_DIR/monitor.sock" 2>/dev/null + + echo "✅ Installation completed! VM has been shut down." + echo "Use './up.sh' to start the installed VM" + exit 0 + fi + + # VM이 종료되었는지 확인 (설치 실패 가능성) + if ! pgrep -f "qemu-system-aarch64.*vm/disk.qcow2" > /dev/null; then + echo "FAILED" > "$VM_DIR/install.status" + echo "$(date): VM stopped unexpectedly during installation" >> "$VM_DIR/install.log" + echo "❌ Installation failed - VM stopped unexpectedly" + exit 1 + fi + + # 10초마다 체크 + sleep 10 +done + +# 타임아웃 +echo "TIMEOUT" > "$VM_DIR/install.status" +echo "$(date): Installation timed out after 30 minutes" >> "$VM_DIR/install.log" +echo "⏰ Installation timed out. Please check manually." +exit 1 \ No newline at end of file From cf1ff51a9c453a979681ac4b0e53e7a8fd8124de Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Sun, 24 Aug 2025 15:07:39 +1000 Subject: [PATCH 04/20] fix: add vagrant bootstrap scripts again --- bootstrap.sh | 450 ++++++++++++++++++++++++++++++++++++++++- scripts/qemu.create.sh | 9 +- 2 files changed, 453 insertions(+), 6 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 299791b..ba31d3a 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -23,9 +23,9 @@ source ./.env # get username from env or prompt username=$VM_USERNAME if [ -z "$VM_USERNAME" ]; then - echo -n "> Please enter default vm user name [vagrant]:" + echo -n "> Please enter default vm user name [admin]:" read input - username=${input:-vagrant} + username=${input:-admin} echo "VM_USERNAME=$username">> .env fi @@ -133,4 +133,448 @@ if [ -z "$windows" ]; then else echo "Windows support not implemented yet" exit 1 -fi \ No newline at end of file +fi + +default_user_name=vagrant +if [ -z "$windows" ]; then + default_user_name="admin" +fi +host_directory=/vagrant/ +if [ -z "$windows" ]; then + host_directory=/mnt/host/ +fi + +# create ssh config file +SSH_CONFIG="$SCRIPT_DIR/ssh.config" +if [ -z "$(grep $default_user_name $SSH_CONFIG)" ]; then + vagrant ssh-config >> $SSH_CONFIG +fi + +# create user with UID 1000 + +#### switch default user +ssh="ssh -F $SSH_CONFIG default" +exists=$($ssh id -u $username 2>/dev/null) +admin_uid=$($ssh id -u $default_user_name 2>/dev/null) + +set -e + +if [ "$admin_uid" == "1000" ] && ([ "$exists" != "" ] && [ "$exists" != "1000" ]); then + echo switching is required, remove $username and try again +fi +if [ -z "$admin_uid" ]; then + echo ssh connection looks like failed + exit -1; +fi +$ssh sudo cp -a /home/$default_user_name/.ssh /root/ +$ssh sudo chown -R root:root /root/.ssh + +if [ -z "$(grep root $SSH_CONFIG.user)" ]; then +$sed -e '0,/vagrant/{s/vagrant/'$username'/}' -e '0,/default/{s/default/'$machine_name/'}' $SSH_CONFIG >> $SSH_CONFIG.user +fi + +if [ -z "$(grep root $SSH_CONFIG.root)" ]; then + $sed -e '0,/vagrant/{s/vagrant/root/}' -e '0,/default/{s/default/root/}' $SSH_CONFIG >> $SSH_CONFIG.root +fi + +#### user root +ssh="ssh -F $SSH_CONFIG.root root" + +docker_port=${DOCKER_PORT:-2376} +ip_address=${IP_ADDRESS:-192.168.99.123} +$ssh "touch ~/.hushlogin" + +$ssh << EOSSH +docker -v && exit; + +echo "====> Installing Docker" +docker_version=\$(grep '^_VER_DOCKER=' ${host_directory}.env |tail -1 |cut -d'=' -f2) +echo "Version: \$docker_version" + +apt-get update +apt-get install -y ca-certificates curl +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc +chmod a+r /etc/apt/keyrings/docker.asc + +echo \ + "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ + \$(. /etc/os-release && echo "\$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null +apt-get update + +apt list -a docker-ce + +if [ -z "\$docker_version" ];then + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +else + apt_docker_ver=\$(apt list -a docker-ce |grep -m1 \${docker_version} |cut -d' ' -f2) + echo "apt-get install -y docker-ce=\${apt_docker_ver} docker-ce-cli=\${apt_docker_ver} containerd.io docker-buildx-plugin docker-compose-plugin" + apt-get install -y docker-ce=\${apt_docker_ver} docker-ce-cli=\${apt_docker_ver} containerd.io docker-buildx-plugin docker-compose-plugin +fi +docker -v + +EOSSH + +if [ -z "$exists" ]; then + echo "user $username not found" + $ssh << EOSSH +echo --------------------- +echo "creating $username" +admin_uid=\$(id -u ${default_user_name}) +if [ \$admin_uid == 1000 ]; then + pkill -U 1000 + usermod -u 1002 ${default_user_name} + groupmod -g 1002 ${default_user_name} +fi +chown -R ${default_user_name}:${default_user_name} /home/${default_user_name} +useradd $username -u 1000 --create-home +if ! [ -d "/home/$username/.ssh" ]; then + cp -a /home/${default_user_name}/.ssh /home/$username/ + chown -R $username:$username /home/$username/.ssh +fi +grep $username /etc/passwd +EOSSH + echo --------------------- +fi + +vm_hosts_vars=$(set | grep "__VMHOSTS__[^=]\+=" | cut -c 12-) +$ssh << EOSSH +echo --------------------- Removing ${default_user_name} password +passwd ${default_user_name} --delete > /dev/null +echo --------------------- +echo Adding $username to Sudoer +usermod -aG sudo $username +echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/98_$username +chmod 440 /etc/sudoers.d/98_$username +usermod -aG docker $username + +if [[ "\$(hostname)" =~ ^debian-[0-9]+$ ]]; then + echo found default hostname, changing it to $machine_name + hostname $machine_name + echo $machine_name > /etc/hostname + echo "127.0.0.1 $machine_name" >> /etc/hosts +fi + +if [ $swapfile ]; then + echo Found SWAPFILE config + if ! [ -f "/swapfile" ]; then + echo "----- +Creating swapfile" + dd if=/dev/zero of=/swapfile bs=1M count=1024 oflag=append conv=notrunc + chmod 600 /swapfile + mkswap /swapfile + fi + echo "----- +Adding swapfile" + sudo swapon /swapfile + if [ -z "\$(grep swapfile -w /etc/fstab)" ]; then + echo "/swapfile swap swap defaults 0 0" >> /etc/fstab + fi + mount -a +fi +swapon --show +free -h + +if ! [ -f "/dummy" ]; then + echo "----- +Expanding actual size for ${expand_disk_size}GB" + let "blockSize = $expand_disk_size * 1024" + #fallocate -l ${expand_disk_size}G /dummy + echo DDing \$blockSize x 1M + dd if=/dev/zero of=/dummy bs=1M count=\$blockSize oflag=append conv=notrunc +fi + +if [ -z "\$(crontab -l|grep "${machine_name}.startup.sh")" ]; then + echo "----- +Adding startup script to crontab" + cp ${host_directory}config/vm.docker.disk.sh /root/docker.disk.sh && \ + chmod +x /root/docker.disk.sh && \ + echo "#!/bin/sh +/root/docker.disk.sh" > /root/${machine_name}.startup.sh && \ + chmod +x /root/${machine_name}.startup.sh && \ + crontab -l | { cat; echo "@reboot /root/${machine_name}.startup.sh"; } | crontab - + if ! [ -z "$DOCKER_DISK_SIZE_GB" ]; then + sdb1=\$(fdisk -l /dev/sdb|grep sdb1) + if [ -z "\$sdb1" ]; then + echo "Found an empty disk, make it a docker storage; /dev/sdb1, ${DOCKER_DISK_SIZE_GB}GB" + /root/${machine_name}.startup.sh + fi + fi +else + echo "----- +crontab scripts:" +fi + crontab -l + +# add hosts entry +echo "$vm_hosts_vars" | while read -r line; do + host=\$(echo \$line | cut -d"=" -f 2) + ip=\$(echo \$line | cut -d"=" -f 1 | cut -f1,2,3,4 -d'_' | tr _ ".") + if [ -z "\$(grep "\$ip \$host" /etc/hosts)" ]; then + echo "Adding \"\$ip \$host\" to hosts file" + echo "\$ip \$host" >> /etc/hosts + fi +done + +EOSSH + +$ssh "rm ~/.hushlogin" + +echo --------------------- +mkdir -p ~/.ssh +touch ~/.ssh/config +if [ -z "$(grep -w "Host $machine_name" ~/.ssh/config)" ]; then + echo Adding ssh config for $machine_name + cat $SSH_CONFIG.user >> ~/.ssh/config + if [ -z "$(grep -w "Host $machine_name" $HOME/.ssh/config || echo "")" ]; then + # if $HOME is different to ~ + echo $ssh_config_for_the_machine >> $HOME/.ssh/config + fi +else + echo $machine_name entry found in ~/.ssh/config. Please double check if Port is correct: + grep $machine_name ~/.ssh/config -A10|grep Port +fi + +ssh $machine_name "touch ~/.hushlogin" + +#### user $username +ssh $machine_name << EOSSH + +echo "============================== +Hello from $machine_name, \$(whoami)" +sudo apt remove vim -y +sudo apt update && sudo apt install \ +git \ +zsh \ +vim \ +python3-pip \ +tmux \ +dnsutils \ +pass gnupg2 \ +-y + +if [ "\$?" -eq 0 ]; then +if [ -f ~/.oh-my-zsh/oh-my-zsh.sh ]; then + echo "----- +oh my zsh is aleady installed" +else + echo "----- +Installing oh my zsh...." + wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh + sh install.sh --unattended && \ + rm -f install.sh* && \ + sudo chsh -s /bin/zsh $username +fi +fi + +if [ "\$?" -eq 0 ]; then +if [ -f "/usr/local/bin/docker-compose" ]; then + echo "----- +docker-compose aleady exists" + docker-compose --version +else + echo "----- +Installing docker-compose...." + sudo pip3 install requests --upgrade + dc_version=\${COMPOSE_VERSION:-1.29.2} + dc_version_url=/docker/compose/releases/download/\${dc_version}/docker-compose-\$(uname -s)-\$(uname -m) + if [ -z "\$dc_version_url" ];then + echo "Could not find the docker-compose url, please install manually from \$github_compose_release_url" + else + docker_compose_url=https://github.com\${dc_version_url} + echo Downloading: \$docker_compose_url + sudo wget \$docker_compose_url -O /usr/local/bin/docker-compose -q --show-progress --progress=bar:force + sudo chmod +x /usr/local/bin/docker-compose + docker-compose --version + fi +fi +fi + +if [ "\$?" -eq 0 ]; then +mkdir -p ~/Projects +if [ -d ~/samba ]; then + echo "----- +Samba config is found. skipping to create" +else + echo "----- +Configuring samba" + mkdir -p samba + cp ${host_directory}config/samba/* samba/ + cd samba + docker-compose down + docker-compose up -d + docker cp /etc/passwd samba:/etc/passwd + chmod +x adduser + ./adduser \$USER +fi +fi + +if [ -f "/dummy" ]; then + filesize=\$(stat -c%s "/dummy") + if [ "\$filesize" ] && [ "\$filesize" != "0" ]; then + echo \$filesize was larger than 1, removing /dummy + sudo rm /dummy + sudo touch /dummy + fi +fi +EOSSH + +echo "Creating Docker Certs" + +if [ -d ~/.docker/certs.$machine_name ]; then + echo "-------- +~/.docker/certs.$machine_name already exists, skip creating Docker certs" +else + echo "-------- +Creating Docker certs" + ssh $machine_name ${host_directory}scripts/create_docker_certs.sh + mkdir -p ~/.docker/certs.$machine_name + cp $SCRIPT_DIR/certs/*.pem ~/.docker/certs.$machine_name/ + ssh $machine_name sudo ${host_directory}scripts/config_docker_certs.sh + echo "export DOCKER_CERT_PATH=~/.docker/certs.$machine_name +export DOCKER_HOST=tcp://$ip_address:$docker_port +export DOCKER_TLS_VERIFY=1 +export COMPOSE_CONVERT_WINDOWS_PATHS=1 +" >> ~/.bashrc + touch ~/.bash_profile + if [ -z "$(grep bashrc ~/.bash_profile)" ]; then + echo "test -f ~/.bashrc && source ~/.bashrc" >> ~/.bash_profile + fi +fi + +#### install fonts +mkdir -p $SCRIPT_DIR/data/fonts +touch $SCRIPT_DIR/data/fonts/.download_start_file +if [ "$FONT_URLS" ] || [ "$PATCHED_FONT_URLS" ]; then +echo "Installing fonts" +ssh $machine_name "bash ${host_directory}scripts/download-fonts.sh \"$FONT_URLS\" \"$PATCHED_FONT_URLS\"" +downloaded=$(find $SCRIPT_DIR/data/fonts -maxdepth 1 -newer $SCRIPT_DIR/data/fonts/.download_start_file -type f -name "*.ttf") +if [ "$downloaded" ]; then + if [ "$windows" ]; then + while read file; do + base=$(basename "$file") + font_args="$font_args \"$base\"" + done <<< "$downloaded" + powershell -executionPolicy ByPass -Command "& $(realpath --relative-to=. $SCRIPT_DIR)/scripts/install-fonts.ps1 $font_args" + else + mkdir -p ~/Library/Fonts + while read file; do + cp "$file" ~/Library/Fonts/ + done <<< "$downloaded" + fi +fi +fi + +#### TODO: upgrade docker if required + +echo "Installing dotfiles" +#### init dotfiles +if [ -z "$DOTFILES_REPO" ]; then + echo "--------- +DOTFILES_REPO is not defined. skipping" +else + ssh $machine_name << EOSSH +if ! [ -d ~/dotfiles ]; then + echo "======= Cloning dotfiles" + git clone $([ -n "$DOTFILES_BRANCH" ] && echo "--branch $DOTFILES_BRANCH") --recurse-submodules $DOTFILES_REPO ~/dotfiles && \ + init=\$(find dotfiles -maxdepth 1 -type f -executable -name 'init*' \ +-o -type f -executable -name "bootstrap*" -o -type f -executable -name "setup*" \ +-o -type f -executable -name "install*" \ +|head -n 1) && \ + if [ -f "\$init" ]; then + \$init + if [ "\$?" -ne 0 ]; then + echo "======= \$init has failed. Please run it in the dotfiles dir (in VM)" + else + echo "======= Ran \$init successfully" + fi + else + echo "!!!! could not find init script. please run manually" + fi +fi +EOSSH +fi + +echo "Setting up host environments" +if [ -z "$windows" ]; then + if [ -z "$noStartupScript" ]; then + $SCRIPT_DIR/scripts/setup-launchd.sh + fi +else + mkdir -p ~/Programs + # add Windows Terminal Profile + if [ "$noStartupScript" ]; then + powershell -executionPolicy ByPass -File $SCRIPT_DIR/add-machine-profile.ps1 $machine_name -noStartupScript + else + powershell -executionPolicy ByPass -File $SCRIPT_DIR/add-machine-profile.ps1 $machine_name + fi + + if [ -f ~/Programs/docker_env.bat ]; then + echo "----- +The docker environment is already set. delete ~/Programs/docker_env.bat and try again if you want to reconfigure" + else + echo "----- +Setting Docker Environment Variables for Windows. Please check DOCKER_HOST and related ones if you want to use other environments" + powershell -executionPolicy ByPass -File $SCRIPT_DIR/add-programs-to-path.ps1 + echo "@echo off +set DOCKER_CERT_PATH=%userprofile%\.docker\certs.$machine_name +set DOCKER_HOST=tcp://$ip_address:$docker_port +set DOCKER_TLS_VERIFY=1 +set COMPOSE_CONVERT_WINDOWS_PATHS=1 +" > ~/Programs/docker_env.bat + setx DOCKER_CERT_PATH %userprofile%\\.docker\\certs.$machine_name + setx DOCKER_HOST tcp://$ip_address:$docker_port + setx DOCKER_TLS_VERIFY 1 + setx COMPOSE_CONVERT_WINDOWS_PATHS 1 + fi +fi + +# set env vars +vm_env_vars=$(set | grep "__VM__[A-Z_]\+=" | cut -c 7- | tr -d "'") +ssh $machine_name << EOSSH + echo "$vm_env_vars" | while read -r line; do + entry=\$(echo \$line) + if [ -z "\$(grep "export \$entry" ~/.zshrc)" ]; then + echo "Exporting env var (\$entry)" + echo "export \$entry" >> ~/.zshrc + echo "export \$entry" >> ~/.bashrc + fi + done + if ! [ -f "\$HOME/.zshenv" ]; then + echo "test -f ~/.zshrc && . ~/.zshrc" >> \$HOME/.zshenv + fi +EOSSH + +#### create ssh key +ssh $machine_name << EOSSH + +if [ -f ~/.ssh/id_rsa ]; then + echo "----- +ssh key aleady exists" +else + echo "----- +Generating ssh key" + ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N "" +fi +echo "Paste the public key below into Github or else" +echo --------------------- +cat ~/.ssh/id_rsa.pub +echo --------------------- +rm ~/.hushlogin +EOSSH + +echo "---------------------- + +Congrats!!! + +You can now ssh into the machine by +\`\`\` +ssh $machine_name +\`\`\` + +- \`./status.sh\` to check the VM status +- \`./halt.sh\` to shut down the VM +- \`./up.sh\` to turn on the VM +- \`./destory.sh\` to start from scratch +" diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index 5e949a5..189744d 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -1,5 +1,4 @@ #!/bin/bash -set -e # QEMU 완전 자동화 VM 생성 스크립트 create_qemu_vm() { @@ -7,7 +6,7 @@ create_qemu_vm() { local memory="$2" local cpus="$3" local disk_size="${4:-20}" - local username="${5:-debian}" + local username="${5:-admin}" echo "Creating QEMU VM: $vm_name (Debian 13 LTS)" echo "Memory: ${memory}MB, CPUs: $cpus, Disk: ${disk_size}GB" @@ -189,6 +188,10 @@ EOF return 0 } +# ================================ + +set -e + # QEMU 설치 확인 if ! command -v qemu-img >/dev/null 2>&1; then echo "Installing QEMU..." @@ -201,4 +204,4 @@ if [ "$#" -lt 3 ]; then exit 1 fi -create_qemu_vm "$1" "$2" "$3" "$4" "$5" \ No newline at end of file +create_qemu_vm "$@" From 664f7d3d469972608af8448aaa7a7f1e16d13d6e Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Sun, 24 Aug 2025 21:15:01 +1000 Subject: [PATCH 05/20] fix(init): use prepopulated ssh key for qemu --- bootstrap.sh | 67 ++++++++++++++++++++++++++++++------------ scripts/qemu.create.sh | 38 ++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index ba31d3a..c82d4e1 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -69,7 +69,10 @@ source ./.env echo ================================= echo Welcome $username! Please wait a moment for bootstrapping $machine_name -if [ -z "$windows" ]; then +if [ "$windows" = 1 ]; then + vagrant plugin install vagrant-env + vagrant up +else # VM이 이미 생성되었는지 확인 if [ -f "./vm/disk.qcow2" ]; then if [ -f "./vm/install.status" ]; then @@ -130,24 +133,44 @@ if [ -z "$windows" ]; then echo "To stop VM: ./halt.sh" echo "SSH access: ssh -p 2222 $username@localhost (password: debian)" -else - echo "Windows support not implemented yet" - exit 1 fi -default_user_name=vagrant -if [ -z "$windows" ]; then +# Set platform-specific defaults +if [ "$windows" = 1 ]; then + # Windows/Vagrant defaults + default_user_name="vagrant" + host_directory="/vagrant/" +else + # Mac/QEMU defaults default_user_name="admin" -fi -host_directory=/vagrant/ -if [ -z "$windows" ]; then - host_directory=/mnt/host/ + host_directory="/mnt/host/" + ssh_port="2222" + ssh_host="localhost" fi # create ssh config file SSH_CONFIG="$SCRIPT_DIR/ssh.config" -if [ -z "$(grep $default_user_name $SSH_CONFIG)" ]; then - vagrant ssh-config >> $SSH_CONFIG +if [ "$windows" = 1 ]; then + # Windows/Vagrant: Use vagrant ssh-config + if [ -z "$(grep $default_user_name $SSH_CONFIG)" ]; then + vagrant ssh-config >> $SSH_CONFIG + fi +else + # Mac/QEMU: Create SSH config manually + if [ ! -f "$SSH_CONFIG" ] || [ -z "$(grep $default_user_name $SSH_CONFIG)" ]; then + cat > "$SSH_CONFIG" << EOF +Host default + HostName $ssh_host + User $default_user_name + Port $ssh_port + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + PasswordAuthentication no + IdentityFile $(pwd)/vm/key/id_rsa + IdentitiesOnly yes + LogLevel FATAL +EOF + fi fi # create user with UID 1000 @@ -166,15 +189,20 @@ if [ -z "$admin_uid" ]; then echo ssh connection looks like failed exit -1; fi -$ssh sudo cp -a /home/$default_user_name/.ssh /root/ -$ssh sudo chown -R root:root /root/.ssh + +# Skip SSH key copying for QEMU (already done during installation) +if [ "$windows" = 1 ]; then + $ssh sudo cp -a /home/$default_user_name/.ssh /root/ + $ssh sudo chown -R root:root /root/.ssh +fi + if [ -z "$(grep root $SSH_CONFIG.user)" ]; then -$sed -e '0,/vagrant/{s/vagrant/'$username'/}' -e '0,/default/{s/default/'$machine_name/'}' $SSH_CONFIG >> $SSH_CONFIG.user +$sed -e "0,/$default_user_name/{s/$default_user_name/$username/}" -e '0,/default/{s/default/'$machine_name'/}' $SSH_CONFIG >> $SSH_CONFIG.user fi if [ -z "$(grep root $SSH_CONFIG.root)" ]; then - $sed -e '0,/vagrant/{s/vagrant/root/}' -e '0,/default/{s/default/root/}' $SSH_CONFIG >> $SSH_CONFIG.root + $sed -e "0,/$default_user_name/{s/$default_user_name/root/}" -e '0,/default/{s/default/root/}' $SSH_CONFIG >> $SSH_CONFIG.root fi #### user root @@ -216,7 +244,8 @@ docker -v EOSSH -if [ -z "$exists" ]; then +# Skip user creation for QEMU (already done during installation) +if [ "$windows" = 1 ] && [ -z "$exists" ]; then echo "user $username not found" $ssh << EOSSH echo --------------------- @@ -451,7 +480,7 @@ echo "Installing fonts" ssh $machine_name "bash ${host_directory}scripts/download-fonts.sh \"$FONT_URLS\" \"$PATCHED_FONT_URLS\"" downloaded=$(find $SCRIPT_DIR/data/fonts -maxdepth 1 -newer $SCRIPT_DIR/data/fonts/.download_start_file -type f -name "*.ttf") if [ "$downloaded" ]; then - if [ "$windows" ]; then + if [ "$windows" = 1 ]; then while read file; do base=$(basename "$file") font_args="$font_args \"$base\"" @@ -497,7 +526,7 @@ EOSSH fi echo "Setting up host environments" -if [ -z "$windows" ]; then +if [ "$windows" -ne 1 ]; then if [ -z "$noStartupScript" ]; then $SCRIPT_DIR/scripts/setup-launchd.sh fi diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index 189744d..4bf553f 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -88,8 +88,8 @@ d-i mirror/http/proxy string d-i passwd/root-login boolean false d-i passwd/user-fullname string $username d-i passwd/username string $username -d-i passwd/user-password password debian -d-i passwd/user-password-again password debian +d-i passwd/user-password-crypted password ! +d-i passwd/user-password-again password ! d-i clock-setup/utc boolean true d-i time/zone string UTC d-i partman-auto/method string regular @@ -104,6 +104,7 @@ d-i pkgsel/include string openssh-server sudo curl wget git d-i pkgsel/upgrade select none d-i grub-installer/only_debian boolean true d-i grub-installer/with_other_os boolean true +d-i preseed/late_command string mkdir -p /target/mnt/host; mount -t 9p -o trans=virtio,version=9p2000.L host /target/mnt/host; /target/mnt/host/vm/setup.sh d-i finish-install/reboot_in_progress note d-i debian-installer/exit/halt boolean true EOF @@ -144,6 +145,7 @@ EOF -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ -drive file="$vm_dir/disk.qcow2",format=qcow2,if=virtio \ -drive file="$iso_path",media=cdrom,readonly=on \ + -virtfs local,path="$(pwd)",mount_tag=host,security_model=passthrough,id=host \ -netdev user,id=net0,hostfwd=tcp::2222-:22 \ -device virtio-net-pci,netdev=net0 \ -monitor unix:$vm_dir/monitor.sock,server,nowait \ @@ -177,6 +179,38 @@ EOF ssh-keygen -t rsa -b 4096 -f "$HOME/.ssh/id_rsa" -N "" fi + # VM 전용 키페어 생성 + echo "Creating VM keypair..." + mkdir -p "$vm_dir/key" + if [ ! -f "$vm_dir/key/id_rsa" ]; then + ssh-keygen -t rsa -b 2048 -f "$vm_dir/key/id_rsa" -N "" -C "$username@$vm_name" + echo "✅ VM keypair created" + fi + + # 초기 설정 스크립트 생성 + cat > "$vm_dir/setup.sh" << 'SETUP_EOF' +#!/bin/bash +# Setup user SSH keys +mkdir -p /target/home/REPLACE_USERNAME/.ssh +cp /target/mnt/host/vm/key/id_rsa.pub /target/home/REPLACE_USERNAME/.ssh/authorized_keys +chown 1000:1000 /target/home/REPLACE_USERNAME/.ssh/authorized_keys +chmod 600 /target/home/REPLACE_USERNAME/.ssh/authorized_keys +chmod 700 /target/home/REPLACE_USERNAME/.ssh + +# Setup root SSH keys +mkdir -p /target/root/.ssh +cp /target/mnt/host/vm/key/id_rsa.pub /target/root/.ssh/authorized_keys +chown 0:0 /target/root/.ssh/authorized_keys +chmod 600 /target/root/.ssh/authorized_keys +chmod 700 /target/root/.ssh + +# Setup sudoers +echo 'REPLACE_USERNAME ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/98_REPLACE_USERNAME +chmod 440 /target/etc/sudoers.d/98_REPLACE_USERNAME +SETUP_EOF + sed -i "s/REPLACE_USERNAME/$username/g" "$vm_dir/setup.sh" + chmod +x "$vm_dir/setup.sh" + echo "VM created at: $vm_dir" From 7e301cd4c99d68d0945bd691c6ce4eb9c4bbe4c2 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Sun, 24 Aug 2025 21:41:13 +1000 Subject: [PATCH 06/20] fix(init): australian --- scripts/qemu.create.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index 4bf553f..b1e96fb 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -74,7 +74,7 @@ create_qemu_vm() { # Preseed 파일 생성 (완전 자동 설치) cat > "$vm_dir/preseed.cfg" << EOF -d-i debian-installer/locale string en_US +d-i debian-installer/locale string en_AU d-i console-setup/ask_detect boolean false d-i console-setup/layoutcode string us d-i keyboard-configuration/xkb-keymap select us @@ -82,7 +82,7 @@ d-i netcfg/choose_interface select auto d-i netcfg/get_hostname string $vm_name d-i netcfg/get_domain string local d-i mirror/country string manual -d-i mirror/http/hostname string ftp.us.debian.org +d-i mirror/http/hostname string ftp.au.debian.org d-i mirror/http/directory string /debian d-i mirror/http/proxy string d-i passwd/root-login boolean false @@ -91,7 +91,7 @@ d-i passwd/username string $username d-i passwd/user-password-crypted password ! d-i passwd/user-password-again password ! d-i clock-setup/utc boolean true -d-i time/zone string UTC +d-i time/zone string Australia/Sydney d-i partman-auto/method string regular d-i partman-auto/choose_recipe select atomic d-i partman/confirm_write_new_label boolean true From fb5e5732abca124db98a60e28beb6423d4e31113 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Mon, 25 Aug 2025 01:52:54 +1000 Subject: [PATCH 07/20] fix(qemu): starting --- bootstrap.sh | 13 ++- scripts/qemu.create.sh | 180 ++++++++++++++++++++++++----------------- scripts/qemu.up.sh | 51 ++++++++---- up.sh | 2 +- 4 files changed, 146 insertions(+), 100 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index c82d4e1..9c15255 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -75,10 +75,10 @@ if [ "$windows" = 1 ]; then else # VM이 이미 생성되었는지 확인 if [ -f "./vm/disk.qcow2" ]; then - if [ -f "./vm/install.status" ]; then - status=$(cat ./vm/install.status) + if [ -f "./vm/.status" ]; then + status=$(tail -1 ./vm/.status) case "$status" in - "COMPLETED") + "COMPLETE") echo "VM '$machine_name' installation completed successfully" echo "To start VM: ./up.sh" echo "To stop VM: ./halt.sh" @@ -120,12 +120,9 @@ else fi # VM 생성 및 설치 - set -e - if ! ./scripts/qemu.create.sh "$machine_name" "${MEMORY:-2048}" "${CPUS:-2}" "${DISK_SIZE_GB:-20}" "$username"; then - echo "VM creation failed. Check the error above." - exit 1 - fi set +e + ./scripts/qemu.create.sh "$machine_name" "${MEMORY:-2048}" "${CPUS:-2}" "${DISK_SIZE_GB:-20}" "$username" + set -e echo "\n=== VM Setup Complete ===" echo "VM files created in: ./vm/" diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index b1e96fb..80365ed 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -46,31 +46,31 @@ create_qemu_vm() { echo "Using vm_dir: $vm_dir" mkdir -p "$vm_dir" - # ISO에서 kernel과 initrd 추출 - local extract_dir="$vm_dir/extract" - if [ ! -f "$extract_dir/vmlinuz" ] || [ ! -f "$extract_dir/initrd.gz" ]; then - echo "Extracting kernel and initrd from ISO..." - mkdir -p "$extract_dir" + # # ISO에서 kernel과 initrd 추출 + # local extract_dir="$vm_dir/extract" + # if [ ! -f "$extract_dir/vmlinuz" ] || [ ! -f "$extract_dir/initrd.gz" ]; then + # echo "Extracting kernel and initrd from ISO..." + # mkdir -p "$extract_dir" - # 7zip 설치 확인 - if ! command -v 7z >/dev/null 2>&1; then - echo "Installing 7zip..." - brew install p7zip - fi + # # 7zip 설치 확인 + # if ! command -v 7z >/dev/null 2>&1; then + # echo "Installing 7zip..." + # brew install p7zip + # fi - # ISO에서 파일 추출 - echo "Extracting files from ISO..." - 7z x "$iso_path" -o"$extract_dir/iso_content" "install.a64/vmlinuz" "install.a64/initrd.gz" -y + # # ISO에서 파일 추출 + # echo "Extracting files from ISO..." + # 7z x "$iso_path" -o"$extract_dir/iso_content" "install.a64/vmlinuz" "install.a64/initrd.gz" -y - # 파일 이동 - mv "$extract_dir/iso_content/install.a64/vmlinuz" "$extract_dir/vmlinuz" - mv "$extract_dir/iso_content/install.a64/initrd.gz" "$extract_dir/initrd.gz" + # # 파일 이동 + # mv "$extract_dir/iso_content/install.a64/vmlinuz" "$extract_dir/vmlinuz" + # mv "$extract_dir/iso_content/install.a64/initrd.gz" "$extract_dir/initrd.gz" - # 임시 폴더 삭제 - rm -rf "$extract_dir/iso_content" + # # 임시 폴더 삭제 + # rm -rf "$extract_dir/iso_content" - echo "✅ Kernel and initrd extracted successfully" - fi + # echo "✅ Kernel and initrd extracted successfully" + # fi # Preseed 파일 생성 (완전 자동 설치) cat > "$vm_dir/preseed.cfg" << EOF @@ -104,11 +104,16 @@ d-i pkgsel/include string openssh-server sudo curl wget git d-i pkgsel/upgrade select none d-i grub-installer/only_debian boolean true d-i grub-installer/with_other_os boolean true -d-i preseed/late_command string mkdir -p /target/mnt/host; mount -t 9p -o trans=virtio,version=9p2000.L host /target/mnt/host; /target/mnt/host/vm/setup.sh +d-i preseed/late_command string in-target mkdir -p /mnt/host; in-target mount -t 9p -o trans=virtio,version=9p2000.L host /mnt/host || true; if [ -f /target/mnt/host/vm/setup.sh ]; then /target/mnt/host/vm/setup.sh; else echo 'Setup script not found, skipping'; fi d-i finish-install/reboot_in_progress note d-i debian-installer/exit/halt boolean true EOF + + if [ -f $vm_dir/.status ]; then + vm_installed=$(grep INSTALL_COMPLETE $vm_dir/.status) + fi + if [ -z "$vm_installed" ]; then # 사용 가능한 포트 찾기 local http_port=8080 while lsof -i :$http_port > /dev/null 2>&1; do @@ -128,7 +133,44 @@ EOF echo "Creating VM disk image..." qemu-img create -f qcow2 "$vm_dir/disk.qcow2" "${disk_size}G" fi + + # VM 전용 키페어 생성 (설치 전에 필요) + mkdir -p "$vm_dir/key" + if [ ! -f "$vm_dir/key/id_rsa" ]; then + echo "Creating VM keypair..." + ssh-keygen -t rsa -b 2048 -f "$vm_dir/key/id_rsa" -N "" -C "$username@$vm_name" + echo "✅ VM keypair created" + fi + # 초기 설정 스크립트 생성 (설치 전에 필요) + if [ ! -f "$vm_dir/setup.sh" ]; then + cat > "$vm_dir/setup.sh" << 'SETUP_EOF' +#!/bin/bash +# Setup user SSH keys +mkdir -p /target/home/REPLACE_USERNAME/.ssh +cp /target/mnt/host/vm/key/id_rsa.pub /target/home/REPLACE_USERNAME/.ssh/authorized_keys +chown 1000:1000 /target/home/REPLACE_USERNAME/.ssh/authorized_keys +chmod 600 /target/home/REPLACE_USERNAME/.ssh/authorized_keys +chmod 700 /target/home/REPLACE_USERNAME/.ssh + +# Setup root SSH keys +mkdir -p /target/root/.ssh +cp /target/mnt/host/vm/key/id_rsa.pub /target/root/.ssh/authorized_keys +chown 0:0 /target/root/.ssh/authorized_keys +chmod 600 /target/root/.ssh/authorized_keys +chmod 700 /target/root/.ssh + +# Setup sudoers +echo 'REPLACE_USERNAME ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/98_REPLACE_USERNAME +chmod 440 /target/etc/sudoers.d/98_REPLACE_USERNAME +SETUP_EOF + if [[ $(uname -s) == "Darwin" ]]; then + gsed -i "s/REPLACE_USERNAME/$username/g" "$vm_dir/setup.sh" + else + sed -i "s/REPLACE_USERNAME/$username/g" "$vm_dir/setup.sh" + fi + chmod +x "$vm_dir/setup.sh" + fi # 설치용 임시 시작 # after booting, @@ -136,6 +178,15 @@ EOF # and paste http://10.0.2.2:8080/preseed.cfg echo "Starting automated Debian installation..." + echo "📋 Preseed URL: http://10.0.2.2:$http_port/preseed.cfg" + echo "🔧 Boot options: auto=true priority=critical preseed/url=http://10.0.2.2:$http_port/preseed.cfg" + echo "⚠️ Manual step required: Select 'Advanced options' -> 'Automated install' and enter the preseed URL above" + + # 설치 시작 상태 기록 + echo "INSTALLING" >> "$vm_dir/.status" + echo "$(date) - Installation started" > "$vm_dir/install.log" + echo "Installation started for $vm_name" >> "$vm_dir/install.log" + qemu-system-aarch64 \ -M virt,highmem=on,gic-version=3 \ -accel hvf \ @@ -152,65 +203,36 @@ EOF -vnc 127.0.0.1:1,password=off \ -nographic - # HTTP 서버 종료 + # HTTP 서버 종료 및 설치 완료 기록 kill $http_pid 2>/dev/null || true + echo "✅ VM installation completed" + fi + + # Start the VM + echo "Starting VM..." + ./up.sh - if [ $? -eq 0 ]; then - echo "✅ VM started successfully in background" - echo "📡 SSH will be available on port 2222 after installation" - echo "⏱️ Installation takes about 10-15 minutes" - - # 설치 상태 파일 생성 - echo "INSTALLING" > "$vm_dir/install.status" - echo "$(date)" > "$vm_dir/install.log" - echo "Installation started for $vm_name" >> "$vm_dir/install.log" - - # 설치 완료 확인 스크립트 실행 - ./scripts/qemu.wait-install.sh "$vm_dir" "$username" & - else - echo "❌ Failed to start VM" - return 1 - fi - - # VM 설정 완료 - - # SSH 키 생성 - if [ ! -f "$HOME/.ssh/id_rsa" ]; then - ssh-keygen -t rsa -b 4096 -f "$HOME/.ssh/id_rsa" -N "" - fi + # Wait until SSH server is ready on port 2222 + echo "Waiting for SSH server to be ready..." + for i in {1..60}; do + if nc -z localhost 2222 2>/dev/null; then + echo "✅ SSH server is ready on port 2222" + break + fi + if [ $i -eq 60 ]; then + echo "❌ Timeout waiting for SSH server" + return 1 + fi + sleep 2 + done - # VM 전용 키페어 생성 - echo "Creating VM keypair..." - mkdir -p "$vm_dir/key" - if [ ! -f "$vm_dir/key/id_rsa" ]; then - ssh-keygen -t rsa -b 2048 -f "$vm_dir/key/id_rsa" -N "" -C "$username@$vm_name" - echo "✅ VM keypair created" - fi + # 설치 완료 상태 기록 + echo "INSTALL_COMPLETE" >> "$vm_dir/.status" - # 초기 설정 스크립트 생성 - cat > "$vm_dir/setup.sh" << 'SETUP_EOF' -#!/bin/bash -# Setup user SSH keys -mkdir -p /target/home/REPLACE_USERNAME/.ssh -cp /target/mnt/host/vm/key/id_rsa.pub /target/home/REPLACE_USERNAME/.ssh/authorized_keys -chown 1000:1000 /target/home/REPLACE_USERNAME/.ssh/authorized_keys -chmod 600 /target/home/REPLACE_USERNAME/.ssh/authorized_keys -chmod 700 /target/home/REPLACE_USERNAME/.ssh - -# Setup root SSH keys -mkdir -p /target/root/.ssh -cp /target/mnt/host/vm/key/id_rsa.pub /target/root/.ssh/authorized_keys -chown 0:0 /target/root/.ssh/authorized_keys -chmod 600 /target/root/.ssh/authorized_keys -chmod 700 /target/root/.ssh - -# Setup sudoers -echo 'REPLACE_USERNAME ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/98_REPLACE_USERNAME -chmod 440 /target/etc/sudoers.d/98_REPLACE_USERNAME -SETUP_EOF - sed -i "s/REPLACE_USERNAME/$username/g" "$vm_dir/setup.sh" - chmod +x "$vm_dir/setup.sh" + echo "$(date) - Installation completed" >> "$vm_dir/install.log" + echo "Installation completed for $vm_name" >> "$vm_dir/install.log" + # VM 설정 완료 echo "VM created at: $vm_dir" @@ -232,6 +254,14 @@ if ! command -v qemu-img >/dev/null 2>&1; then brew install qemu fi +# GNU sed 설치 및 설정 (macOS) +if [[ $(uname -s) == "Darwin" ]]; then + if ! command -v gsed >/dev/null 2>&1; then + echo "Installing GNU sed..." + brew install gnu-sed + fi +fi + # 메인 실행 if [ "$#" -lt 3 ]; then echo "Usage: $0 [disk_size_gb] [username]" diff --git a/scripts/qemu.up.sh b/scripts/qemu.up.sh index aad29f2..8691935 100755 --- a/scripts/qemu.up.sh +++ b/scripts/qemu.up.sh @@ -33,19 +33,38 @@ fi iso_filename="debian-13.0.0-arm64-netinst.iso" iso_path="$HOME/Downloads/$iso_filename" -echo "Starting VM '$NAME' in headless mode... in '$VM_DIR'" -echo "SSH: ssh -p 2222 kenny@localhost" -echo "VNC: localhost:5901" -qemu-system-aarch64 \ - -M virt,highmem=on,gic-version=3 \ - -accel hvf \ - -cpu host \ - -smp $CPUS \ - -m ${MEMORY}M,slots=4,maxmem=$((MEMORY * 2))M \ - -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ - -drive file="$VM_DIR/disk.qcow2",format=qcow2,if=virtio \ - -netdev user,id=net0,hostfwd=tcp::2222-:22 \ - -device virtio-net-pci,netdev=net0 \ - -monitor unix:$VM_DIR/monitor.sock,server,nowait \ - -vnc 127.0.0.1:1,password=off \ - -daemonize \ No newline at end of file +# 콘솔 모드 확인 +if [ "$1" = "--console" ] || [ "$1" = "-c" ]; then + echo "Starting VM '$NAME' in console mode... in '$VM_DIR'" + echo "Press Ctrl+A, X to exit console" + qemu-system-aarch64 \ + -M virt,highmem=on,gic-version=3 \ + -accel hvf \ + -cpu host \ + -smp $CPUS \ + -m ${MEMORY}M,slots=4,maxmem=$((MEMORY * 2))M \ + -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ + -drive file="$VM_DIR/disk.qcow2",format=qcow2,if=virtio \ + -netdev user,id=net0,hostfwd=tcp::2222-:22 \ + -device virtio-net-pci,netdev=net0 \ + -monitor unix:$VM_DIR/monitor.sock,server,nowait \ + -nographic +else + echo "Starting VM '$NAME' in headless mode... in '$VM_DIR'" + echo "SSH: ssh -p 2222 kenny@localhost" + echo "VNC: localhost:5901" + echo "Console: ./up.sh --console" + qemu-system-aarch64 \ + -M virt,highmem=on,gic-version=3 \ + -accel hvf \ + -cpu host \ + -smp $CPUS \ + -m ${MEMORY}M,slots=4,maxmem=$((MEMORY * 2))M \ + -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ + -drive file="$VM_DIR/disk.qcow2",format=qcow2,if=virtio \ + -netdev user,id=net0,hostfwd=tcp::2222-:22 \ + -device virtio-net-pci,netdev=net0 \ + -monitor unix:$VM_DIR/monitor.sock,server,nowait \ + -vnc 127.0.0.1:1,password=off \ + -daemonize +fi \ No newline at end of file diff --git a/up.sh b/up.sh index 5c047f2..2088944 100755 --- a/up.sh +++ b/up.sh @@ -2,7 +2,7 @@ if [[ $(uname -s) == "Darwin" ]]; then # Mac - QEMU 사용 - ./scripts/qemu.up.sh + ./scripts/qemu.up.sh "$@" else # Windows - Vagrant 사용 vagrant up From 20eb749d13c13b622fb992dc82b1ccad0f57aee7 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Mon, 25 Aug 2025 01:57:14 +1000 Subject: [PATCH 08/20] fix(qemu): starting --- bootstrap.sh | 6 +++--- scripts/qemu.create.sh | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 9c15255..ae6f89a 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -23,9 +23,9 @@ source ./.env # get username from env or prompt username=$VM_USERNAME if [ -z "$VM_USERNAME" ]; then - echo -n "> Please enter default vm user name [admin]:" + echo -n "> Please enter default vm user name [linuxdev]:" read input - username=${input:-admin} + username=${input:-linuxdev} echo "VM_USERNAME=$username">> .env fi @@ -139,7 +139,7 @@ if [ "$windows" = 1 ]; then host_directory="/vagrant/" else # Mac/QEMU defaults - default_user_name="admin" + default_user_name="linuxdev" host_directory="/mnt/host/" ssh_port="2222" ssh_host="localhost" diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index 80365ed..0755507 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -72,6 +72,7 @@ create_qemu_vm() { # echo "✅ Kernel and initrd extracted successfully" # fi + # for Automated Installation # Preseed 파일 생성 (완전 자동 설치) cat > "$vm_dir/preseed.cfg" << EOF d-i debian-installer/locale string en_AU @@ -88,8 +89,8 @@ d-i mirror/http/proxy string d-i passwd/root-login boolean false d-i passwd/user-fullname string $username d-i passwd/username string $username -d-i passwd/user-password-crypted password ! -d-i passwd/user-password-again password ! +d-i passwd/user-password-crypted password debian +d-i passwd/user-password-again password debian d-i clock-setup/utc boolean true d-i time/zone string Australia/Sydney d-i partman-auto/method string regular From d029b545e5c25dafedac252b4d0bd68b943d0cee Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Mon, 25 Aug 2025 02:01:45 +1000 Subject: [PATCH 09/20] fix(qemu): starting --- scripts/qemu.create.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index 0755507..bdf3c3a 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -6,7 +6,7 @@ create_qemu_vm() { local memory="$2" local cpus="$3" local disk_size="${4:-20}" - local username="${5:-admin}" + local username="${5:-linuxdev}" echo "Creating QEMU VM: $vm_name (Debian 13 LTS)" echo "Memory: ${memory}MB, CPUs: $cpus, Disk: ${disk_size}GB" @@ -72,7 +72,7 @@ create_qemu_vm() { # echo "✅ Kernel and initrd extracted successfully" # fi - # for Automated Installation + # for Automated Install # Preseed 파일 생성 (완전 자동 설치) cat > "$vm_dir/preseed.cfg" << EOF d-i debian-installer/locale string en_AU From a963c8a75356b66a879cd554923f50ad7d4f4081 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Mon, 25 Aug 2025 21:09:03 +1000 Subject: [PATCH 10/20] fix(qemu): set ssh key on bootstrapping --- scripts/qemu.create.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index bdf3c3a..bdc2708 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -72,8 +72,17 @@ create_qemu_vm() { # echo "✅ Kernel and initrd extracted successfully" # fi + # 사용 가능한 포트 찾기 (미리 정의) + local http_port=8080 + while lsof -i :$http_port > /dev/null 2>&1; do + http_port=$((http_port + 1)) + done + # for Automated Install # Preseed 파일 생성 (완전 자동 설치) + # HTTP 포트와 사용자명을 preseed에 삽입하기 위해 임시 변수 사용 + local preseed_late_cmd="wget -O /tmp/id_rsa.pub http://10.0.2.2:$http_port/key/id_rsa.pub && mkdir -p /target/home/$username/.ssh /target/root/.ssh && cp /tmp/id_rsa.pub /target/home/$username/.ssh/authorized_keys && cp /tmp/id_rsa.pub /target/root/.ssh/authorized_keys && chown 1000:1000 /target/home/$username/.ssh/authorized_keys && chmod 600 /target/home/$username/.ssh/authorized_keys && chmod 700 /target/home/$username/.ssh && chmod 600 /target/root/.ssh/authorized_keys && chmod 700 /target/root/.ssh && echo '$username ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/98_$username && chmod 440 /target/etc/sudoers.d/98_$username" + cat > "$vm_dir/preseed.cfg" << EOF d-i debian-installer/locale string en_AU d-i console-setup/ask_detect boolean false @@ -89,7 +98,7 @@ d-i mirror/http/proxy string d-i passwd/root-login boolean false d-i passwd/user-fullname string $username d-i passwd/username string $username -d-i passwd/user-password-crypted password debian +d-i passwd/user-password password debian d-i passwd/user-password-again password debian d-i clock-setup/utc boolean true d-i time/zone string Australia/Sydney @@ -105,7 +114,7 @@ d-i pkgsel/include string openssh-server sudo curl wget git d-i pkgsel/upgrade select none d-i grub-installer/only_debian boolean true d-i grub-installer/with_other_os boolean true -d-i preseed/late_command string in-target mkdir -p /mnt/host; in-target mount -t 9p -o trans=virtio,version=9p2000.L host /mnt/host || true; if [ -f /target/mnt/host/vm/setup.sh ]; then /target/mnt/host/vm/setup.sh; else echo 'Setup script not found, skipping'; fi +d-i preseed/late_command string $preseed_late_cmd d-i finish-install/reboot_in_progress note d-i debian-installer/exit/halt boolean true EOF @@ -115,11 +124,6 @@ EOF vm_installed=$(grep INSTALL_COMPLETE $vm_dir/.status) fi if [ -z "$vm_installed" ]; then - # 사용 가능한 포트 찾기 - local http_port=8080 - while lsof -i :$http_port > /dev/null 2>&1; do - http_port=$((http_port + 1)) - done echo "Using HTTP port: $http_port" # HTTP 서버로 preseed 제공 @@ -197,7 +201,6 @@ SETUP_EOF -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ -drive file="$vm_dir/disk.qcow2",format=qcow2,if=virtio \ -drive file="$iso_path",media=cdrom,readonly=on \ - -virtfs local,path="$(pwd)",mount_tag=host,security_model=passthrough,id=host \ -netdev user,id=net0,hostfwd=tcp::2222-:22 \ -device virtio-net-pci,netdev=net0 \ -monitor unix:$vm_dir/monitor.sock,server,nowait \ From f7b2dedf6acb539b17bb21bbb732d049dd2223f4 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Mon, 25 Aug 2025 21:28:53 +1000 Subject: [PATCH 11/20] fix(qemu): allow root ssh --- scripts/qemu.create.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index bdc2708..2e1027e 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -81,8 +81,19 @@ create_qemu_vm() { # for Automated Install # Preseed 파일 생성 (완전 자동 설치) # HTTP 포트와 사용자명을 preseed에 삽입하기 위해 임시 변수 사용 - local preseed_late_cmd="wget -O /tmp/id_rsa.pub http://10.0.2.2:$http_port/key/id_rsa.pub && mkdir -p /target/home/$username/.ssh /target/root/.ssh && cp /tmp/id_rsa.pub /target/home/$username/.ssh/authorized_keys && cp /tmp/id_rsa.pub /target/root/.ssh/authorized_keys && chown 1000:1000 /target/home/$username/.ssh/authorized_keys && chmod 600 /target/home/$username/.ssh/authorized_keys && chmod 700 /target/home/$username/.ssh && chmod 600 /target/root/.ssh/authorized_keys && chmod 700 /target/root/.ssh && echo '$username ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/98_$username && chmod 440 /target/etc/sudoers.d/98_$username" - + local preseed_late_cmd="wget -O /tmp/id_rsa.pub http://10.0.2.2:$http_port/key/id_rsa.pub && \ + mkdir -p /target/home/$username/.ssh /target/root/.ssh && \ + chmod 700 /target/home/$username/.ssh && \ + chmod 700 /target/root/.ssh && \ + cp /tmp/id_rsa.pub /target/home/$username/.ssh/authorized_keys && \ + chmod 600 /target/home/$username/.ssh/authorized_keys && \ + chown 1000:1000 -R /target/home/$username/.ssh && \ + cp /tmp/id_rsa.pub /target/root/.ssh/authorized_keys && \ + chmod 600 /target/root/.ssh/authorized_keys && \ + echo '$username ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/98_$username && \ + chmod 440 /target/etc/sudoers.d/98_$username && \ + sed -i 's/^#\?PermitRootLogin prohibit-password/PermitRootLogin yes/' /target/etc/ssh/sshd_config" + cat > "$vm_dir/preseed.cfg" << EOF d-i debian-installer/locale string en_AU d-i console-setup/ask_detect boolean false From 9e957dc4db323991110f9c6043a9172f8acf965b Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Mon, 25 Aug 2025 22:04:36 +1000 Subject: [PATCH 12/20] fix(qemu): improve logging --- scripts/qemu.create.sh | 48 +++++++++++++++++++++++------------------- scripts/qemu.up.sh | 8 ++++--- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/scripts/qemu.create.sh b/scripts/qemu.create.sh index 2e1027e..e70758f 100755 --- a/scripts/qemu.create.sh +++ b/scripts/qemu.create.sh @@ -218,43 +218,47 @@ SETUP_EOF -vnc 127.0.0.1:1,password=off \ -nographic + echo "INSTALL_COMPLETE" >> "$vm_dir/.status" + echo "$(date) - Installation completed" >> "$vm_dir/install.log" + # HTTP 서버 종료 및 설치 완료 기록 kill $http_pid 2>/dev/null || true echo "✅ VM installation completed" fi # Start the VM - echo "Starting VM..." - ./up.sh + ./up.sh -q - # Wait until SSH server is ready on port 2222 - echo "Waiting for SSH server to be ready..." - for i in {1..60}; do - if nc -z localhost 2222 2>/dev/null; then - echo "✅ SSH server is ready on port 2222" + # Wait until SSH authentication is ready + echo "Waiting for SSH authentication to be ready..." + for i in {1..10}; do + if timeout 3 ssh -p 2222 -i "$vm_dir/key/id_rsa" -o StrictHostKeyChecking=no -o ConnectTimeout=3 "$username@localhost" "echo ''" 2>/dev/null; then + echo "✅ SSH authentication is ready" break fi - if [ $i -eq 60 ]; then - echo "❌ Timeout waiting for SSH server" + if [ $i -eq 10 ]; then + echo "❌ Timeout waiting for SSH authentication" return 1 fi - sleep 2 + echo "Attempt $i/10: SSH not ready yet, waiting..." + sleep 3 done - - # 설치 완료 상태 기록 - echo "INSTALL_COMPLETE" >> "$vm_dir/.status" - - echo "$(date) - Installation completed" >> "$vm_dir/install.log" - echo "Installation completed for $vm_name" >> "$vm_dir/install.log" + + echo "SSHD_STARTED" >> "$vm_dir/.status" + echo "Installation completed for $vm_name, up and running" >> "$vm_dir/install.log" # VM 설정 완료 - - echo "VM created at: $vm_dir" - echo "Installation started. User: $username, Password: debian" - echo "SSH will be available at: ssh -p 2222 $username@localhost" - echo "Use './up.sh' to start VM after installation" - echo "Use './halt.sh' to stop VM" + echo "✅ VM setup completed successfully!" + echo "🔑 SSH Key Authentication: ssh -p 2222 -i ./vm/key/id_rsa $username@localhost" + echo "🔑 Root Access: ssh -p 2222 -i ./vm/key/id_rsa root@localhost" + echo "📝 Password Login (fallback): ssh -p 2222 $username@localhost (password: debian)" + echo "" + echo "🚀 Next steps:" + echo " - VM is already running and ready to use" + echo " - Continue with: ./bootstrap.sh" + echo " - Stop VM: ./halt.sh" + echo " - Check status: ./status.sh" return 0 } diff --git a/scripts/qemu.up.sh b/scripts/qemu.up.sh index 8691935..8d91ec4 100755 --- a/scripts/qemu.up.sh +++ b/scripts/qemu.up.sh @@ -51,9 +51,11 @@ if [ "$1" = "--console" ] || [ "$1" = "-c" ]; then -nographic else echo "Starting VM '$NAME' in headless mode... in '$VM_DIR'" - echo "SSH: ssh -p 2222 kenny@localhost" - echo "VNC: localhost:5901" - echo "Console: ./up.sh --console" + if [ "$1" != "-q" ]; then + echo "SSH: ssh -p 2222 kenny@localhost" + echo "VNC: localhost:5901" + echo "Console: ./up.sh --console" + fi qemu-system-aarch64 \ -M virt,highmem=on,gic-version=3 \ -accel hvf \ From 0b28816d318442b892d72c9490e8b22d5872a2f5 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Tue, 26 Aug 2025 01:04:02 +1000 Subject: [PATCH 13/20] fix: initiate the vm using linuxdev --- bootstrap.sh | 168 +++++++++++++++++++++------------------------------ 1 file changed, 70 insertions(+), 98 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index ae6f89a..6b5f3c9 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -2,6 +2,23 @@ SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +show_completion_message() { + echo "---------------------- + +Congrats!!! + +You can now ssh into the machine by +\`\`\` +ssh $machine_name +\`\`\` + +- \`./status.sh\` to check the VM status +- \`./halt.sh\` to shut down the VM +- \`./up.sh\` to turn on the VM +- \`./destory.sh\` to start from scratch +" +} + noStartupScript=$(echo ${@} | grep -w '\-\-noStartupScript' >> /dev/null && echo 1 || echo "") echo "bootstrap.sh:" $@ @@ -20,6 +37,9 @@ echo ================================= source ./.env +expand_disk_size=${EXPAND_DISK_GB:-} +swapfile=${SWAPFILE:-} + # get username from env or prompt username=$VM_USERNAME if [ -z "$VM_USERNAME" ]; then @@ -69,21 +89,24 @@ source ./.env echo ================================= echo Welcome $username! Please wait a moment for bootstrapping $machine_name +is_installed=$(grep INSTALL_COMPLETE ./vm/.status) + +if [ -n "$is_installed" ]; then + echo "The VM is already created" >&2 +else + if [ "$windows" = 1 ]; then + echo "INSTALLING" >> ./vm/.status vagrant plugin install vagrant-env - vagrant up + if vagrant up; then + echo "INSTALL_COMPLETE" >> ./vm/.status + fi else # VM이 이미 생성되었는지 확인 if [ -f "./vm/disk.qcow2" ]; then if [ -f "./vm/.status" ]; then status=$(tail -1 ./vm/.status) case "$status" in - "COMPLETE") - echo "VM '$machine_name' installation completed successfully" - echo "To start VM: ./up.sh" - echo "To stop VM: ./halt.sh" - exit 0 - ;; "INSTALLING") echo "VM '$machine_name' installation is in progress..." echo "Check status: tail -f ./vm/install.log" @@ -106,14 +129,12 @@ else fi # Mac - QEMU VM 생성 - echo "Creating and installing Debian 12 LTS with QEMU..." - + echo "Creating and installing Debian LTS with QEMU..." # QEMU 프로세스 실행 중 확인 if pgrep -f "qemu-system-aarch64" > /dev/null; then echo "QEMU VM is already running. Please stop it first with './halt.sh'" exit 1 fi - if lsof -i :2222 > /dev/null 2>&1; then echo "Port 2222 is already in use. Please stop it manually." exit 1 @@ -125,13 +146,10 @@ else set -e echo "\n=== VM Setup Complete ===" - echo "VM files created in: ./vm/" - echo "To start VM: ./up.sh" - echo "To stop VM: ./halt.sh" - echo "SSH access: ssh -p 2222 $username@localhost (password: debian)" - fi +fi # if is_installed + # Set platform-specific defaults if [ "$windows" = 1 ]; then # Windows/Vagrant defaults @@ -154,7 +172,8 @@ if [ "$windows" = 1 ]; then fi else # Mac/QEMU: Create SSH config manually - if [ ! -f "$SSH_CONFIG" ] || [ -z "$(grep $default_user_name $SSH_CONFIG)" ]; then + if [ ! -f "$SSH_CONFIG" ] || [ -z "$(grep "User $default_user_name"ca $SSH_CONFIG)" ]; then + echo Setting User $default_user_name to $SSH_CONFIG cat > "$SSH_CONFIG" << EOF Host default HostName $ssh_host @@ -188,7 +207,7 @@ if [ -z "$admin_uid" ]; then fi # Skip SSH key copying for QEMU (already done during installation) -if [ "$windows" = 1 ]; then +if [ "$windows" = 1 ]; then # vagrant only $ssh sudo cp -a /home/$default_user_name/.ssh /root/ $ssh sudo chown -R root:root /root/.ssh fi @@ -210,39 +229,14 @@ ip_address=${IP_ADDRESS:-192.168.99.123} $ssh "touch ~/.hushlogin" $ssh << EOSSH -docker -v && exit; - -echo "====> Installing Docker" -docker_version=\$(grep '^_VER_DOCKER=' ${host_directory}.env |tail -1 |cut -d'=' -f2) -echo "Version: \$docker_version" - -apt-get update -apt-get install -y ca-certificates curl -install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc -chmod a+r /etc/apt/keyrings/docker.asc - -echo \ - "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ - \$(. /etc/os-release && echo "\$VERSION_CODENAME") stable" | \ - tee /etc/apt/sources.list.d/docker.list > /dev/null -apt-get update - -apt list -a docker-ce - -if [ -z "\$docker_version" ];then - apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -else - apt_docker_ver=\$(apt list -a docker-ce |grep -m1 \${docker_version} |cut -d' ' -f2) - echo "apt-get install -y docker-ce=\${apt_docker_ver} docker-ce-cli=\${apt_docker_ver} containerd.io docker-buildx-plugin docker-compose-plugin" - apt-get install -y docker-ce=\${apt_docker_ver} docker-ce-cli=\${apt_docker_ver} containerd.io docker-buildx-plugin docker-compose-plugin -fi -docker -v - +[ -d dotfiles ] && rm -rf dotfiles || true && \ +git clone -b alt/linuxdev https://github.com/kennyhyun/dotfiles.git dotfiles && \ +PRODUCTION=1 dotfiles/scripts/linux.sh linuxdev && \ +rm -rf dotfiles EOSSH -# Skip user creation for QEMU (already done during installation) -if [ "$windows" = 1 ] && [ -z "$exists" ]; then +# switch default user to $username +if [ "$username" != "$default_user_name" ] && [ -z "$exists" ]; then echo "user $username not found" $ssh << EOSSH echo --------------------- @@ -264,6 +258,7 @@ EOSSH echo --------------------- fi +# initial setup vm_hosts_vars=$(set | grep "__VMHOSTS__[^=]\+=" | cut -c 12-) $ssh << EOSSH echo --------------------- Removing ${default_user_name} password @@ -282,7 +277,7 @@ if [[ "\$(hostname)" =~ ^debian-[0-9]+$ ]]; then echo "127.0.0.1 $machine_name" >> /etc/hosts fi -if [ $swapfile ]; then +if [ -n "$swapfile" ]; then echo Found SWAPFILE config if ! [ -f "/swapfile" ]; then echo "----- @@ -302,7 +297,7 @@ fi swapon --show free -h -if ! [ -f "/dummy" ]; then +if [ -n "$expand_disk_size" ] && ! [ -f "/dummy" ]; then echo "----- Expanding actual size for ${expand_disk_size}GB" let "blockSize = $expand_disk_size * 1024" @@ -311,6 +306,22 @@ Expanding actual size for ${expand_disk_size}GB" dd if=/dev/zero of=/dummy bs=1M count=\$blockSize oflag=append conv=notrunc fi +# add hosts entry +echo "$vm_hosts_vars" | while read -r line; do + host=\$(echo \$line | cut -d"=" -f 2) + ip=\$(echo \$line | cut -d"=" -f 1 | cut -f1,2,3,4 -d'_' | tr _ ".") + if [ -z "\$(grep "\$ip \$host" /etc/hosts)" ]; then + echo "Adding \"\$ip \$host\" to hosts file" + echo "\$ip \$host" >> /etc/hosts + fi +done + +EOSSH + + +if [ "$windows" = 1 ]; then # vagrant only +$ssh << EOSSH + if [ -z "\$(crontab -l|grep "${machine_name}.startup.sh")" ]; then echo "----- Adding startup script to crontab" @@ -333,17 +344,8 @@ crontab scripts:" fi crontab -l -# add hosts entry -echo "$vm_hosts_vars" | while read -r line; do - host=\$(echo \$line | cut -d"=" -f 2) - ip=\$(echo \$line | cut -d"=" -f 1 | cut -f1,2,3,4 -d'_' | tr _ ".") - if [ -z "\$(grep "\$ip \$host" /etc/hosts)" ]; then - echo "Adding \"\$ip \$host\" to hosts file" - echo "\$ip \$host" >> /etc/hosts - fi -done - EOSSH +fi $ssh "rm ~/.hushlogin" @@ -365,6 +367,12 @@ fi ssh $machine_name "touch ~/.hushlogin" #### user $username +if [ "$username" == "$default_user_name" ]; then + echo "username was the default user, stop personalising." + show_completion_message + exit +fi + ssh $machine_name << EOSSH echo "============================== @@ -394,29 +402,6 @@ Installing oh my zsh...." fi fi -if [ "\$?" -eq 0 ]; then -if [ -f "/usr/local/bin/docker-compose" ]; then - echo "----- -docker-compose aleady exists" - docker-compose --version -else - echo "----- -Installing docker-compose...." - sudo pip3 install requests --upgrade - dc_version=\${COMPOSE_VERSION:-1.29.2} - dc_version_url=/docker/compose/releases/download/\${dc_version}/docker-compose-\$(uname -s)-\$(uname -m) - if [ -z "\$dc_version_url" ];then - echo "Could not find the docker-compose url, please install manually from \$github_compose_release_url" - else - docker_compose_url=https://github.com\${dc_version_url} - echo Downloading: \$docker_compose_url - sudo wget \$docker_compose_url -O /usr/local/bin/docker-compose -q --show-progress --progress=bar:force - sudo chmod +x /usr/local/bin/docker-compose - docker-compose --version - fi -fi -fi - if [ "\$?" -eq 0 ]; then mkdir -p ~/Projects if [ -d ~/samba ]; then @@ -503,7 +488,7 @@ else ssh $machine_name << EOSSH if ! [ -d ~/dotfiles ]; then echo "======= Cloning dotfiles" - git clone $([ -n "$DOTFILES_BRANCH" ] && echo "--branch $DOTFILES_BRANCH") --recurse-submodules $DOTFILES_REPO ~/dotfiles && \ + git clone $([ -n "$DOTFILES_BRANCH" ] && echo "-b $DOTFILES_BRANCH") --recurse-submodules $DOTFILES_REPO ~/dotfiles && \ init=\$(find dotfiles -maxdepth 1 -type f -executable -name 'init*' \ -o -type f -executable -name "bootstrap*" -o -type f -executable -name "setup*" \ -o -type f -executable -name "install*" \ @@ -590,17 +575,4 @@ echo --------------------- rm ~/.hushlogin EOSSH -echo "---------------------- - -Congrats!!! - -You can now ssh into the machine by -\`\`\` -ssh $machine_name -\`\`\` - -- \`./status.sh\` to check the VM status -- \`./halt.sh\` to shut down the VM -- \`./up.sh\` to turn on the VM -- \`./destory.sh\` to start from scratch -" +show_completion_message From 651f98692fa432c938848e4e715f8c9bad088d78 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Tue, 26 Aug 2025 01:09:07 +1000 Subject: [PATCH 14/20] fix: microk8s --- bootstrap.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap.sh b/bootstrap.sh index 6b5f3c9..a7f6dfc 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -269,6 +269,7 @@ usermod -aG sudo $username echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/98_$username chmod 440 /etc/sudoers.d/98_$username usermod -aG docker $username +usermod -aG microk8s $username if [[ "\$(hostname)" =~ ^debian-[0-9]+$ ]]; then echo found default hostname, changing it to $machine_name From 9009905e39011ae02e29e3da4152abf025a6f534 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Wed, 27 Aug 2025 23:55:01 +1000 Subject: [PATCH 15/20] feat: add export-split.sh --- scripts/export-split.sh | 199 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100755 scripts/export-split.sh diff --git a/scripts/export-split.sh b/scripts/export-split.sh new file mode 100755 index 0000000..0ff1f98 --- /dev/null +++ b/scripts/export-split.sh @@ -0,0 +1,199 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# 설정 +VM_NAME="${1:-linuxdev}" +EXPORT_FORMAT="qcow2" # qcow2만 지원 +OUTPUT_DIR="$PROJECT_DIR/exports" +VOLUME_SIZE="2000m" # 2GB보다 약간 작게 (안전 마진) + +# 함수들 +check_vm_status() { + echo "Checking VM status..." + if pgrep -f "qemu-system-aarch64" > /dev/null; then + echo "⚠️ VM is running. Shutting down safely..." + "$PROJECT_DIR/halt.sh" + + # VM 종료 대기 + for i in {1..30}; do + if ! pgrep -f "qemu-system-aarch64" > /dev/null; then + echo "✅ VM shutdown complete" + break + fi + if [ $i -eq 30 ]; then + echo "❌ VM shutdown timeout" + exit 1 + fi + sleep 2 + done + else + echo "✅ VM is not running" + fi +} + +export_qcow2_uncompressed() { + local input_file="$PROJECT_DIR/vm/disk.qcow2" + local output_file="$OUTPUT_DIR/${VM_NAME}-$(date +%Y%m%d).qcow2" + + echo "Exporting QCOW2 format (uncompressed)..." + qemu-img convert -f qcow2 -O qcow2 "$input_file" "$output_file" + echo "✅ QCOW2 export complete: $output_file" + echo "$output_file" +} + +create_7zip_volumes() { + local file_path="$1" + local filename="$(basename "$file_path")" + local dir="$(dirname "$file_path")" + local base_name="${filename%.*}" + + echo "Creating 7zip volumes (${VOLUME_SIZE} each)..." + + # 7zip 설치 확인 + if ! command -v 7z >/dev/null 2>&1; then + echo "Installing 7zip..." + if [[ $(uname -s) == "Darwin" ]]; then + brew install p7zip + else + sudo apt-get update && sudo apt-get install -y p7zip-full + fi + fi + + # 7zip으로 볼륨 분할 압축 (최대 압축률) + cd "$dir" + local archive_name="${base_name}" + # 포맷에 따라 다른 아카이브 이름 사용 + if [[ "$filename" == *.img ]]; then + archive_name="${base_name}-raw" + elif [[ "$filename" == *.qcow2 ]]; then + archive_name="${base_name}" + fi + 7z a -t7z -v${VOLUME_SIZE} -mx=9 "${archive_name}.7z" "$filename" + + # 압축 파일 검증 및 단일 볼륨 처리 + echo "Verifying compressed archive..." + if 7z t "${archive_name}.7z.001" > /dev/null 2>&1; then + echo "✅ Archive verification successful" + + # 단일 볼륨인 경우 .001 제거 + if [ ! -f "${archive_name}.7z.002" ]; then + mv "${archive_name}.7z.001" "${archive_name}.7z" + echo "✅ Renamed single volume to ${archive_name}.7z" + fi + + # 검증 성공 시 원본 파일 자동 삭제 + rm "$file_path" + echo "✅ Original file deleted after verification" + else + echo "❌ Archive verification failed - keeping original file" + return 1 + fi + + + + # 체크섬 파일 생성 + echo "Creating checksums..." + if [ -f "${archive_name}.7z" ]; then + # 단일 볼륨 + shasum -a 256 "${archive_name}.7z" > "${archive_name}.sha256" + else + # 다중 볼륨 + for vol_file in ${archive_name}.7z.*; do + if [ -f "$vol_file" ]; then + shasum -a 256 "$vol_file" >> "${archive_name}.sha256" + fi + done + fi + + echo "✅ 7zip archive created:" + + # 단일/다중 볼륨에 따른 변수 설정 + local archive_ext=".7z" + local extract_cmd_suffix="" + local notes_desc_aux="" + if [ -f "${archive_name}.7z.001" ]; then + local archive_ext=".7z.001" + local extract_cmd_suffix=".*" + local notes_desc_aux="split into 2GB volumes." + fi + + ls -lh ${archive_name}${archive_ext} + echo "" + echo "📁 Files to upload:" + echo " - ${archive_name}${archive_ext} (volume file(s))" + echo " - ${archive_name}.sha256 (checksum(s))" + + echo "" + echo "💡 GitHub Release upload commands:" + echo " gh release create v$(date +%Y%m%d) ${archive_name}${archive_ext} ${archive_name}.sha256 \\" + echo " --title 'VM Export $(date +%Y-%m-%d)' \\" + echo " --notes 'VM disk image${notes_desc_aux}. Extract with: 7z x ${archive_name}.7z${extract_cmd_suffix}'" +} + +# 메인 실행 +main() { + echo "🚀 Starting VM export with 7zip splitting..." + echo "VM Name: $VM_NAME" + echo "Format: $EXPORT_FORMAT" + echo "Volume Size: $VOLUME_SIZE" + echo "Output: $OUTPUT_DIR" + + # 출력 디렉토리 생성 + mkdir -p "$OUTPUT_DIR" + + # VM 상태 확인 및 종료 + check_vm_status + + # VM 디스크 파일 존재 확인 + if [ ! -f "$PROJECT_DIR/vm/disk.qcow2" ]; then + echo "❌ VM disk not found: $PROJECT_DIR/vm/disk.qcow2" + exit 1 + fi + + # QCOW2 export + export_qcow2_uncompressed + local exported_file="$OUTPUT_DIR/${VM_NAME}-$(date +%Y%m%d).qcow2" + + # 파일 정보 출력 + echo "" + echo "📊 Export Summary:" + echo " File: $exported_file" + echo " Size: $(du -h "$exported_file" | cut -f1)" + + # 7zip 볼륨 생성 + create_7zip_volumes "$exported_file" + + echo "" + echo "✅ Export and splitting complete!" +} + +# 사용법 출력 +if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + echo "Usage: $0 [VM_NAME] [FORMAT]" + echo "" + echo "Arguments:" + echo " VM_NAME VM name (default: linuxdev)" + echo "" + echo "Features:" + echo " - Exports VM disk in QCOW2 format" + echo " - Splits into 2GB 7zip volumes for GitHub Release" + echo " - Creates checksums for verification" + echo " - Compatible with all virtualization platforms" + echo "" + echo "Examples:" + echo " $0 # Export as QCOW2, split with 7zip" + echo " $0 myvm # Export specific VM" + echo "" + echo "After upload, users can:" + echo " 1. Download all .7z.* files" + echo " 2. Run the extraction script" + echo " 3. Convert to desired format with qemu-img" + exit 0 +fi + +# 메인 실행 +main "$@" \ No newline at end of file From b541adc9dc7f36c452941b86f3a84a2d51a8c810 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Thu, 28 Aug 2025 00:00:01 +1000 Subject: [PATCH 16/20] feat: add export-split.sh --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4d0066c..844770f 100644 --- a/.gitignore +++ b/.gitignore @@ -63,5 +63,7 @@ backup # qemu vm dir /vm +/exports + +*.bak -*.bak \ No newline at end of file From 6e15a4c57079b0452fb90d258d172c3b19ef03f6 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Thu, 28 Aug 2025 00:02:19 +1000 Subject: [PATCH 17/20] feat: add architecture for release --- scripts/export-split.sh | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/scripts/export-split.sh b/scripts/export-split.sh index 0ff1f98..4d9aa31 100755 --- a/scripts/export-split.sh +++ b/scripts/export-split.sh @@ -12,6 +12,32 @@ OUTPUT_DIR="$PROJECT_DIR/exports" VOLUME_SIZE="2000m" # 2GB보다 약간 작게 (안전 마진) # 함수들 +get_vm_architecture() { + local vm_disk="$PROJECT_DIR/vm/disk.qcow2" + if [ -f "$vm_disk" ]; then + # qemu-img info로 아키텍처 정보 추출 시도 + local arch_info=$(qemu-img info "$vm_disk" 2>/dev/null | grep -i "format specific" -A 10 || true) + + # 프로세서 아키텍처 감지 + local host_arch=$(uname -m) + case "$host_arch" in + "arm64"|"aarch64") echo "arm64" ;; + "x86_64"|"amd64") echo "x64" ;; + "i386"|"i686") echo "x86" ;; + *) echo "$host_arch" ;; + esac + else + # VM 디스크가 없으면 호스트 아키텍처 사용 + local host_arch=$(uname -m) + case "$host_arch" in + "arm64"|"aarch64") echo "arm64" ;; + "x86_64"|"amd64") echo "x64" ;; + "i386"|"i686") echo "x86" ;; + *) echo "$host_arch" ;; + esac + fi +} + check_vm_status() { echo "Checking VM status..." if pgrep -f "qemu-system-aarch64" > /dev/null; then @@ -37,7 +63,8 @@ check_vm_status() { export_qcow2_uncompressed() { local input_file="$PROJECT_DIR/vm/disk.qcow2" - local output_file="$OUTPUT_DIR/${VM_NAME}-$(date +%Y%m%d).qcow2" + local arch=$(get_vm_architecture) + local output_file="$OUTPUT_DIR/${VM_NAME}-${arch}-$(date +%Y%m%d).qcow2" echo "Exporting QCOW2 format (uncompressed)..." qemu-img convert -f qcow2 -O qcow2 "$input_file" "$output_file" @@ -156,7 +183,8 @@ main() { # QCOW2 export export_qcow2_uncompressed - local exported_file="$OUTPUT_DIR/${VM_NAME}-$(date +%Y%m%d).qcow2" + local arch=$(get_vm_architecture) + local exported_file="$OUTPUT_DIR/${VM_NAME}-${arch}-$(date +%Y%m%d).qcow2" # 파일 정보 출력 echo "" From 2748aa681179174f903d9f75abbbda0775458b27 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Thu, 28 Aug 2025 00:05:43 +1000 Subject: [PATCH 18/20] feat: add scripts for uploading release --- scripts/export-split.sh | 15 +++++++- scripts/upload-release.sh | 72 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100755 scripts/upload-release.sh diff --git a/scripts/export-split.sh b/scripts/export-split.sh index 4d9aa31..9a88b2f 100755 --- a/scripts/export-split.sh +++ b/scripts/export-split.sh @@ -159,6 +159,17 @@ create_7zip_volumes() { echo " gh release create v$(date +%Y%m%d) ${archive_name}${archive_ext} ${archive_name}.sha256 \\" echo " --title 'VM Export $(date +%Y-%m-%d)' \\" echo " --notes 'VM disk image${notes_desc_aux}. Extract with: 7z x ${archive_name}.7z${extract_cmd_suffix}'" + + # 자동 업로드 옵션 + if [ "$2" = "--upload" ]; then + echo "" + read -p "Create GitHub Release now? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Creating GitHub Release..." + "$SCRIPT_DIR/upload-release.sh" "${archive_name}" + fi + fi } # 메인 실행 @@ -201,10 +212,11 @@ main() { # 사용법 출력 if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then - echo "Usage: $0 [VM_NAME] [FORMAT]" + echo "Usage: $0 [VM_NAME] [--upload]" echo "" echo "Arguments:" echo " VM_NAME VM name (default: linuxdev)" + echo " --upload Create GitHub Release after export" echo "" echo "Features:" echo " - Exports VM disk in QCOW2 format" @@ -215,6 +227,7 @@ if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then echo "Examples:" echo " $0 # Export as QCOW2, split with 7zip" echo " $0 myvm # Export specific VM" + echo " $0 myvm --upload # Export and create GitHub Release" echo "" echo "After upload, users can:" echo " 1. Download all .7z.* files" diff --git a/scripts/upload-release.sh b/scripts/upload-release.sh new file mode 100755 index 0000000..fb8e6c3 --- /dev/null +++ b/scripts/upload-release.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +set -e + +# 사용법 확인 +if [ "$#" -lt 1 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + echo "Usage: $0 [tag] [title]" + echo "" + echo "Arguments:" + echo " archive_name Base name of archive (without extension)" + echo " tag Release tag (default: v$(date +%Y%m%d))" + echo " title Release title (default: VM Export $(date +%Y-%m-%d))" + echo "" + echo "Examples:" + echo " $0 linuxdev-arm64-20250827" + echo " $0 linuxdev-arm64-20250827 v1.0.0 'Stable Release'" + echo "" + echo "Files to upload:" + echo " - .7z (or .7z.*)" + echo " - .sha256" + exit 0 +fi + +ARCHIVE_NAME="$1" +TAG="${2:-v$(date +%Y%m%d)}" +TITLE="${3:-VM Export $(date +%Y-%m-%d)}" + +# exports 디렉토리로 이동 +cd "$(dirname "$0")/../exports" + +# 파일 존재 확인 +if [ ! -f "${ARCHIVE_NAME}.sha256" ]; then + echo "❌ Checksum file not found: ${ARCHIVE_NAME}.sha256" + exit 1 +fi + +# 아카이브 파일 확인 +if [ -f "${ARCHIVE_NAME}.7z" ]; then + ARCHIVE_FILES="${ARCHIVE_NAME}.7z" + EXTRACT_CMD="7z x ${ARCHIVE_NAME}.7z" + NOTES="VM disk image. Extract with: $EXTRACT_CMD" +elif [ -f "${ARCHIVE_NAME}.7z.001" ]; then + ARCHIVE_FILES="${ARCHIVE_NAME}.7z.*" + EXTRACT_CMD="7z x ${ARCHIVE_NAME}.7z.001" + NOTES="VM disk image split into 2GB volumes. Extract with: $EXTRACT_CMD" +else + echo "❌ Archive file not found: ${ARCHIVE_NAME}.7z or ${ARCHIVE_NAME}.7z.001" + exit 1 +fi + +# GitHub CLI 확인 +if ! command -v gh >/dev/null 2>&1; then + echo "❌ GitHub CLI not found. Install with: brew install gh" + exit 1 +fi + +# 파일 목록 출력 +echo "📁 Files to upload:" +ls -lh $ARCHIVE_FILES "${ARCHIVE_NAME}.sha256" + +echo "" +echo "🚀 Creating GitHub Release..." +echo "Tag: $TAG" +echo "Title: $TITLE" + +# GitHub Release 생성 +gh release create "$TAG" $ARCHIVE_FILES "${ARCHIVE_NAME}.sha256" \ + --title "$TITLE" \ + --notes "$NOTES" + +echo "✅ GitHub Release created successfully!" +echo "🔗 View at: https://github.com/$(gh repo view --json owner,name -q '.owner.login + "/" + .name')/releases/tag/$TAG" \ No newline at end of file From e132dfe3b0dac78c03a95074b409417a11b60b8a Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Thu, 28 Aug 2025 01:13:59 +1000 Subject: [PATCH 19/20] fix: use smaller file size for release --- scripts/export-split.sh | 47 +++++++++++++-------------------------- scripts/upload-release.sh | 2 +- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/scripts/export-split.sh b/scripts/export-split.sh index 9a88b2f..159bef5 100755 --- a/scripts/export-split.sh +++ b/scripts/export-split.sh @@ -9,7 +9,7 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")" VM_NAME="${1:-linuxdev}" EXPORT_FORMAT="qcow2" # qcow2만 지원 OUTPUT_DIR="$PROJECT_DIR/exports" -VOLUME_SIZE="2000m" # 2GB보다 약간 작게 (안전 마진) +VOLUME_SIZE="268435456" # 256MB 정확히 (바이트 단위) # 함수들 get_vm_architecture() { @@ -90,7 +90,7 @@ create_7zip_volumes() { fi fi - # 7zip으로 볼륨 분할 압축 (최대 압축률) + # 7zip으로 볼륨 분할 압축 (최대 압축률, 무조건 001 확장자) cd "$dir" local archive_name="${base_name}" # 포맷에 따라 다른 아카이브 이름 사용 @@ -99,19 +99,13 @@ create_7zip_volumes() { elif [[ "$filename" == *.qcow2 ]]; then archive_name="${base_name}" fi - 7z a -t7z -v${VOLUME_SIZE} -mx=9 "${archive_name}.7z" "$filename" + 7z a -t7z -v${VOLUME_SIZE}b -mx=9 "${archive_name}.7z" "$filename" - # 압축 파일 검증 및 단일 볼륨 처리 + # 압축 파일 검증 (무조건 001 파일로 검증) echo "Verifying compressed archive..." if 7z t "${archive_name}.7z.001" > /dev/null 2>&1; then echo "✅ Archive verification successful" - # 단일 볼륨인 경우 .001 제거 - if [ ! -f "${archive_name}.7z.002" ]; then - mv "${archive_name}.7z.001" "${archive_name}.7z" - echo "✅ Renamed single volume to ${archive_name}.7z" - fi - # 검증 성공 시 원본 파일 자동 삭제 rm "$file_path" echo "✅ Original file deleted after verification" @@ -122,31 +116,20 @@ create_7zip_volumes() { - # 체크섬 파일 생성 + # 체크섬 파일 생성 (무조건 001부터 시작하는 볼륨들) echo "Creating checksums..." - if [ -f "${archive_name}.7z" ]; then - # 단일 볼륨 - shasum -a 256 "${archive_name}.7z" > "${archive_name}.sha256" - else - # 다중 볼륨 - for vol_file in ${archive_name}.7z.*; do - if [ -f "$vol_file" ]; then - shasum -a 256 "$vol_file" >> "${archive_name}.sha256" - fi - done - fi + for vol_file in ${archive_name}.7z.*; do + if [ -f "$vol_file" ]; then + shasum -a 256 "$vol_file" >> "${archive_name}.sha256" + fi + done echo "✅ 7zip archive created:" - # 단일/다중 볼륨에 따른 변수 설정 - local archive_ext=".7z" - local extract_cmd_suffix="" - local notes_desc_aux="" - if [ -f "${archive_name}.7z.001" ]; then - local archive_ext=".7z.001" - local extract_cmd_suffix=".*" - local notes_desc_aux="split into 2GB volumes." - fi + # 무조건 001부터 시작하는 볼륨 설정 + local archive_ext=".7z.001" + local extract_cmd_suffix=".*" + local notes_desc_aux="split into 256MB volumes." ls -lh ${archive_name}${archive_ext} echo "" @@ -220,7 +203,7 @@ if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then echo "" echo "Features:" echo " - Exports VM disk in QCOW2 format" - echo " - Splits into 2GB 7zip volumes for GitHub Release" + echo " - Splits into 256MB 7zip volumes for GitHub Release" echo " - Creates checksums for verification" echo " - Compatible with all virtualization platforms" echo "" diff --git a/scripts/upload-release.sh b/scripts/upload-release.sh index fb8e6c3..5ad3f8b 100755 --- a/scripts/upload-release.sh +++ b/scripts/upload-release.sh @@ -42,7 +42,7 @@ if [ -f "${ARCHIVE_NAME}.7z" ]; then elif [ -f "${ARCHIVE_NAME}.7z.001" ]; then ARCHIVE_FILES="${ARCHIVE_NAME}.7z.*" EXTRACT_CMD="7z x ${ARCHIVE_NAME}.7z.001" - NOTES="VM disk image split into 2GB volumes. Extract with: $EXTRACT_CMD" + NOTES="VM disk image split into 256MB volumes. Extract with: $EXTRACT_CMD" else echo "❌ Archive file not found: ${ARCHIVE_NAME}.7z or ${ARCHIVE_NAME}.7z.001" exit 1 From 63a4479e70925905f4e7e0fad37571175f1de329 Mon Sep 17 00:00:00 2001 From: Kenny Yeo Date: Thu, 28 Aug 2025 01:26:24 +1000 Subject: [PATCH 20/20] fix: upload-release message --- scripts/upload-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/upload-release.sh b/scripts/upload-release.sh index 5ad3f8b..05be933 100755 --- a/scripts/upload-release.sh +++ b/scripts/upload-release.sh @@ -69,4 +69,4 @@ gh release create "$TAG" $ARCHIVE_FILES "${ARCHIVE_NAME}.sha256" \ --notes "$NOTES" echo "✅ GitHub Release created successfully!" -echo "🔗 View at: https://github.com/$(gh repo view --json owner,name -q '.owner.login + "/" + .name')/releases/tag/$TAG" \ No newline at end of file +echo "🔗 View release at: gh release view $TAG --web"