diff --git a/.editorconfig b/.editorconfig index f5df4e8..403a682 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,9 +8,13 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[windows.xml] +[{windows.xml,windows-*.xml}] end_of_line = crlf +[windows-frpc.xml] +charset = utf-16-le +indent_size = 2 + [*.{bat,cmd,ps1}] end_of_line = crlf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8660461..891b9bb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,10 +7,13 @@ assignees: '' --- -原来的系统: -要安装的系统: -遇到的问题:截图或者登录 SSH 获取日志 /reinstall.log +原来的系统 (Original system): -Original system: -System to be installed: -Issues encountered: Screenshot or log in via SSH to get the logs from /reinstall.log +要安装的系统 (System to be installed): + +遇到的问题 (Issue): + + diff --git a/.github/workflows/run_reinstall.yml b/.github/workflows/run_reinstall.yml index 143ed6e..c09b51d 100644 --- a/.github/workflows/run_reinstall.yml +++ b/.github/workflows/run_reinstall.yml @@ -8,7 +8,6 @@ jobs: run: name: 运行主程序 strategy: - fail-fast: false matrix: os: [ubuntu-latest, windows-latest] include: @@ -22,18 +21,19 @@ jobs: git config --global core.autocrlf false - uses: actions/checkout@v4 - run: | - ${{ matrix.command }} centos - ${{ matrix.command }} almalinux 8 - ${{ matrix.command }} rocky 9 - ${{ matrix.command }} fedora + # ${{ matrix.command }} centos + ${{ matrix.command }} almalinux + # ${{ matrix.command }} rocky + # ${{ matrix.command }} fedora + # ${{ matrix.command }} oracle ${{ matrix.command }} ubuntu ${{ matrix.command }} debian ${{ matrix.command }} debian --ci - ${{ matrix.command }} kali - ${{ matrix.command }} alpine - ${{ matrix.command }} opensuse - ${{ matrix.command }} arch - ${{ matrix.command }} gentoo + # ${{ matrix.command }} kali + # ${{ matrix.command }} alpine + # ${{ matrix.command }} opensuse + # ${{ matrix.command }} arch + # ${{ matrix.command }} gentoo ${{ matrix.command }} netboot.xyz ${{ matrix.command }} dd --img=https://download.opensuse.org/tumbleweed/appliances/openSUSE-MicroOS.x86_64-SelfInstall.raw.xz diff --git a/.github/workflows/sync_to_gitlab.yml b/.github/workflows/sync_to_cnb.yml similarity index 55% rename from .github/workflows/sync_to_gitlab.yml rename to .github/workflows/sync_to_cnb.yml index eb35139..1369e4c 100644 --- a/.github/workflows/sync_to_gitlab.yml +++ b/.github/workflows/sync_to_cnb.yml @@ -1,10 +1,10 @@ -name: 同步到 Gitlab +name: 同步到 CNB on: workflow_dispatch: push: jobs: run: - name: 同步到 Gitlab + name: 同步到 CNB runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -12,7 +12,7 @@ jobs: fetch-depth: 0 - uses: yesolutions/mirror-action@master with: - REMOTE: https://gitlab.com/bin456789/reinstall.git - GIT_USERNAME: username - GIT_PASSWORD: ${{ secrets.GITLAB_TOKEN }} + REMOTE: https://cnb.cool/bin456789/reinstall.git + GIT_USERNAME: cnb + GIT_PASSWORD: ${{ secrets.CNB_TOKEN }} PUSH_ALL_REFS: false diff --git a/.github/workflows/sync_to_gitlabcn.yml b/.github/workflows/sync_to_gitlabcn.yml deleted file mode 100644 index 3f05944..0000000 --- a/.github/workflows/sync_to_gitlabcn.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: 同步到 Gitlab CN -on: - workflow_dispatch: - push: -jobs: - run: - name: 同步到 Gitlab CN - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: yesolutions/mirror-action@master - with: - REMOTE: https://jihulab.com/bin456789/reinstall.git - GIT_USERNAME: username - GIT_PASSWORD: ${{ secrets.JIHULAB_TOKEN }} - PUSH_ALL_REFS: false diff --git a/README.en.md b/README.en.md index eb85fcc..62f990f 100644 --- a/README.en.md +++ b/README.en.md @@ -4,26 +4,40 @@ [![Codacy](https://img.shields.io/codacy/grade/dc679a17751448628fe6d8ac35e26eed?logo=Codacy&label=Codacy&style=flat-square)](https://app.codacy.com/gh/bin456789/reinstall/dashboard) [![CodeFactor](https://img.shields.io/codefactor/grade/github/bin456789/reinstall?logo=CodeFactor&logoColor=white&label=CodeFactor&style=flat-square)](https://www.codefactor.io/repository/github/bin456789/reinstall) -[![Lines of Code](https://tokei.rs/b1/github/bin456789/reinstall?category=code&label=Lines%20of%20Code&style=flat-square)](https://github.com/XAMPPRocky/tokei) -[![Telegram Group](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-square&logo=telegram&logoColor=white)](https://t.me/reinstall_os) -[![Github Sponsors](https://img.shields.io/badge/sponsor-30363D?style=flat-square&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/bin456789) - +[![Lines of Code](https://tokei.rs/b1/github/bin456789/reinstall?category=code&label=Lines%20of%20Code&style=flat-square)](https://github.com/XAMPPRocky/tokei_rs) -One-Click Script to Reinstall System [中文](README.md) +One-Click system reinstallation script for VPS [中文](README.md) -[![Sponsors](https://raw.githubusercontent.com/bin456789/sponsors/refs/heads/master/sponsors.svg)](https://github.com/sponsors/bin456789) +## Introduction -## Highlights - -- One-click Linux installation: Supports 19 common distributions. -- One-click Windows installation: Uses the official ISO for installation instead of custom images. The script can automatically ~~retrieves the ISO link~~ and installs common drivers like `Virtio`. -- Supports installation in any direction, i.e., `Linux to Linux`, `Linux to Windows`, `Windows to Windows`, `Windows to Linux` -- No need to input IP parameters; automatically recognizes dynamic and static IPs, supports `/32`, `/128`, `gateway outside subnet`, `IPv6 only`, `dual NIC` +- One-click reinstallation to Linux: Supports 19 common distributions. +- One-click reinstallation to Windows: Uses the official original ISO instead of custom images. The script can automatically fetch the ISO link and installs public cloud drivers like `VirtIO`. +- Supports reinstallation in any direction, i.e., `Linux to Linux`, `Linux to Windows`, `Windows to Windows`, `Windows to Linux` +- Automatically configures IP and intelligently sets it as static or dynamic. Supports `/32`, `/128`, `gateway outside subnet`, `IPv6 only`, `IPv4/IPv6 on different NIC` - Specially optimized for low-spec servers, requires less memory than the official netboot - Uses partition table ID to identify hard drives throughout the process, ensuring no wrong disk is written - Supports BIOS and EFI boot, and ARM Server - No homemades image included, all resources are obtained in real-time from mirror sites +If this helped you, you can buy me a milk tea. +[![Donate](https://img.shields.io/badge/Donate-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/bin456789) + +[![Sponsors](https://raw.githubusercontent.com/bin456789/sponsors/refs/heads/master/sponsors.svg)](https://github.com/sponsors/bin456789) + +### Feedback + +[![GitHub Issues](https://img.shields.io/badge/GitHub-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/bin456789/reinstall/issues) +[![Telegram Group](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/reinstall_os) + +## Quick Start + +- [Download](#download-current-system-is--linux) +- [Feature 1. One-click reinstallation to Linux](#feature-1-install--linux) +- [Feature 2. One-click DD Raw image to hard disk](#feature-2-dd-raw-image-to-hard-disk) +- [Feature 3. One-click reboot to Alpine Live OS in-memory system](#feature-3-reboot-to--alpine-live-os-ram-os) +- [Feature 4. One-click reboot to netboot.xyz](#feature-4-reboot-to--netbootxyz) +- [Feature 5. One-click reinstallation to Windows](#feature-5-install--windows-iso) + ## System Requirements The original system can be any system listed in the table. @@ -32,22 +46,22 @@ The system requirements for the target system are as follows: | System | Version | Memory | Disk | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | --------- | ---------------- | -| Alpine | 3.18, 3.19, 3.20, 3.21 | 256 MB | 1 GB | -| Debian | 9, 10, 11, 12 | 256 MB | 1 ~ 1.5 GB ^ | +| Alpine | 3.20, 3.21, 3.22, 3.23 | 256 MB | 1 GB | +| Debian | 9, 10, 11, 12, 13 | 256 MB | 1 ~ 1.5 GB ^ | | Kali | Rolling | 256 MB | 1 ~ 1.5 GB ^ | -| Ubuntu | 16.04 LTS - 24.04 LTS, 24.10 | 512 MB \* | 2 GB | +| Ubuntu | 16.04 LTS - 24.04 LTS, 25.10 | 512 MB \* | 2 GB | | Anolis | 7, 8, 23 | 512 MB \* | 5 GB | -| RHEL   AlmaLinux   Rocky   Oracle | 8, 9 | 512 MB \* | 5 GB | +| RHEL   AlmaLinux   Rocky   Oracle | 8, 9, 10 | 512 MB \* | 5 GB | | OpenCloudOS | 8, 9, Stream 23 | 512 MB \* | 5 GB | -| CentOS | 9, 10 | 512 MB \* | 5 GB | -| Fedora | 40, 41 | 512 MB \* | 5 GB | -| openEuler | 20.03 LTS - 24.03 LTS, 24.09 | 512 MB \* | 5 GB | -| openSUSE | 15.6, Tumbleweed (Rolling) | 512 MB \* | 5 GB | -| NixOS | 24.11 | 512 MB | 5 GB | +| CentOS Stream | 9, 10 | 512 MB \* | 5 GB | +| Fedora | 42, 43 | 512 MB \* | 5 GB | +| openEuler | 20.03 LTS - 24.03 LTS, 25.09 | 512 MB \* | 5 GB | +| openSUSE | Leap 15.6, 16.0, Tumbleweed (Rolling) | 512 MB \* | 5 GB | +| NixOS | 25.11 | 512 MB | 5 GB | | Arch | Rolling | 512 MB | 5 GB | | Gentoo | Rolling | 512 MB | 5 GB | -| AOSC OS | Rolling | 512 MB | 5 GB | -| fnOS | Beta | 512 MB | 8 GB | +| AOSC OS | Rolling | 512 MB | 5 GB | +| fnOS | 1 | 512 MB | 8 GB | | Windows (DD) | Any | 512 MB | Depends on image | | Windows (ISO) | Vista, 7, 8.x (Server 2008 - 2012 R2) | 512 MB | 25 GB | | Windows (ISO) | 10, 11 (Server 2016 - 2025) | 1 GB | 25 GB | @@ -57,6 +71,13 @@ The system requirements for the target system are as follows: ^ Indicates requiring either 256 MB memory + 1.5 GB disk, or 512 MB memory + 1 GB disk > [!WARNING] +> +> In theory it also supports dedicated servers and PCs +> +> but if you can use IPMI or a USB drive, this script is not recommended. + +> [!WARNING] +> > ❌ This script does not support OpenVZ or LXC virtual machines. > > Please use instead. @@ -66,13 +87,13 @@ The system requirements for the target system are as follows: For server outside China: ```bash -curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O reinstall.sh $_ +curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O ${_##*/} $_ ``` For server inside China: ```bash -curl -O https://gitlab.com/bin456789/reinstall/-/raw/main/reinstall.sh || wget -O reinstall.sh $_ +curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O ${_##*/} $_ ``` ## Download (Current system is Windows) @@ -105,7 +126,7 @@ certutil -urlcache -f -split https://raw.githubusercontent.com/bin456789/reinsta For server inside China: ```batch -certutil -urlcache -f -split https://gitlab.com/bin456789/reinstall/-/raw/main/reinstall.bat +certutil -urlcache -f -split https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.bat ``` ## Usage @@ -118,7 +139,13 @@ certutil -urlcache -f -split https://gitlab.com/bin456789/reinstall/-/raw/main/r ### Feature 1: Install Linux -- The username is `root` with a default password of `123@@@`. +> [!CAUTION] +> +> This feature will erase **the entire hard disk** of the current system (including other partitions)! +> +> Data is priceless — please think twice before proceeding! + +- Username `root`. The script prompts for a password. If left blank, a random one is generated. - When installing the latest version, the version number does not need to be specified. - Maximizes disk space usage: no boot partition (except for Fedora) and no swap partition. - Automatically selects different optimized kernels based on machine type, such as `Cloud` or `HWE` kernels. @@ -127,38 +154,43 @@ certutil -urlcache -f -split https://gitlab.com/bin456789/reinstall/-/raw/main/r ```bash bash reinstall.sh anolis 7|8|23 + rocky 8|9|10 + oracle 8|9|10 + almalinux 8|9|10 opencloudos 8|9|23 - rocky 8|9 - redhat 8|9 --img="http://xxx.com/xxx.qcow2" - oracle 8|9 - almalinux 8|9 centos 9|10 - fedora 40|41 - nixos 24.11 - debian 9|10|11|12 - opensuse 15.6|tumbleweed - alpine 3.18|3.19|3.20|3.21 - openeuler 20.03|22.03|24.03|24.09 - ubuntu 16.04|18.04|20.04|22.04|24.04|24.10 [--minimal] + fnos 1 + nixos 25.11 + fedora 42|43 + debian 9|10|11|12|13 + alpine 3.20|3.21|3.22|3.23 + opensuse 15.6|16.0|tumbleweed + openeuler 20.03|22.03|24.03|25.09 + ubuntu 16.04|18.04|20.04|22.04|24.04|25.10 [--minimal] kali arch gentoo aosc - fnos + redhat --img="http://access.cdn.redhat.com/xxx.qcow2" ``` #### Optional Parameters - `--password PASSWORD` Set the password +- `--ssh-key KEY` Set up SSH login public key, [formatted as follows](#--ssh-key). When using public key, password is empty. - `--ssh-port PORT` Change the SSH port (for log observation during installation and for the new system) -- `--web-port PORT` Change the Web port (for log observation during installation) -- `--hold 2` Prevent reboot after installation completes, allowing SSH login to modify system content; the system is mounted at `/os` (this feature is not supported on Debian/Kali). +- `--web-port PORT` Change the Web port (for log observation during installation only) +- `--frpc-toml PATH` Add frpc for intranet tunneling. Parameter can be local filepath or HTTP URL +- `--hold 1` Reboot only into install environment, without running installer, only for SSH connect to test network connection. +- `--hold 2` Prevent reboot after installation completes, allowing SSH login to modify system content; the system is mounted at `/target` for Debian/Kali and `/os` for other distros. > [!TIP] -> When installing Debian/Kali, x86 architectures can monitor the installation progress through VNC in the background, while ARM architectures can use the serial console. > -> When installing other systems, can monitor the progress through various methods (SSH, HTTP 80 port, VNC in the background, serial console). ->
Even if errors occur during the installation process, you can still install to Alpine via SSH by running `/trans.sh alpine` +> Can monitor the progress through various methods (SSH, HTTP 80 port, VNC from server provider, serial console). +> +> Even if errors occur during the installation process, SSH is available for manual recovery. +> +> If the target system is not Debian/Kali, run `/trans.sh alpine` can automatically recover to Alpine Linux.
@@ -192,9 +224,15 @@ bash reinstall.sh ubuntu --installer
-### Feature 2: DD +### Feature 2: DD RAW image to hard disk -- Supports `raw` and `vhd` image formats (either uncompressed or compressed as `.gz`, `.xz`, `.zst`, `.tar`, `.tar.gz`, `.tar.xz`, `.tar.zst`). +> [!CAUTION] +> +> This feature will erase **the entire hard disk** of the current system (including other partitions)! +> +> Data is priceless — please think twice before proceeding! + +- Supports `raw` and fixed-size `vhd` image formats. Either uncompressed or compressed as `.gz`, `.xz`, `.zst`, `.tar`, `.tar.gz`, `.tar.xz`, `.tar.zst`. - When deploy a Windows image, the system disk will be automatically expanded, and machines with a static IP will have their IP configured, and may take a few minutes after the first boot for the configuration to take effect. - When deploy a Linux image, will **NOT** modify any contents of the image. @@ -202,37 +240,55 @@ bash reinstall.sh ubuntu --installer bash reinstall.sh dd --img "https://example.com/xxx.xz" ``` -#### Optional parameters +#### Optional Parameters -- `--allow-ping` Allow ping responses (DD Windows only) +- `--allow-ping` Configure Windows Firewall to Allow Ping Responses (DD Windows only) - `--rdp-port PORT` Change RDP port (DD Windows only) - `--ssh-port PORT` Change SSH port (for log observation during installation) - `--web-port PORT` Change Web port (for log observation during installation) -- `--hold 2` Prevent reboot after the DD process finishes, allowing SSH login to modify system content. The Windows system will be mounted at `/os`, but Linux systems will **NOT** be automatically mounted. +- `--frpc-toml PATH` Add frpc for intranet tunneling (DD Windows only). Parameter can be local filepath or HTTP URL +- `--hold 1` Reboot only into install environment, without running installer, only for SSH connect to test network connection. +- `--hold 2` Prevent reboot after the DD process finishes. For SSH login to modify system content. The Windows system will be mounted at `/os`, but Linux systems will **NOT** be automatically mounted. > [!TIP] -> Can monitor the progress through various methods (SSH, HTTP 80 port, VNC in the background, serial console). ->
Even if errors occur during the installation process, you can still install to Alpine via SSH by running `/trans.sh alpine` +> +> Can monitor the progress through various methods (SSH, HTTP 80 port, VNC from server provider, serial console). +> +> Even if errors occur during the installation process, SSH is available for manual recovery. +> +> Or Run `/trans.sh alpine` to automatically recover to Alpine Linux. ### Feature 3: Reboot to Alpine Live OS (RAM OS) -- You can use SSH to backup/restore disk, manually perform DD operations, modify partitions, and manually install Alpine, Arch, Gentoo, and other systems. -- Username `root`, Default password `123@@@` -- If manual operations do not damage the original system, rebooting will return to the original system. +- You can use SSH to backup/restore disk, manually perform DD operations, partition modifications, manual Alpine installation, and other operations. +- Username `root`. The script prompts for a password. If left blank, a random one is generated. + +> [!TIP] +> +> Although the script being run is `reinstall`, this feature **does not** delete any data or perform an automatic reinstallation; manual user operation is required. + +> If the user does not damage the original system during manual operation, rebooting will return to the original system. ```bash -bash reinstall.sh alpine --hold=1 +bash reinstall.sh alpine --hold 1 ``` -#### Optional parameters +#### Optional Parameters - `--password PASSWORD` Set password - `--ssh-port PORT` Change SSH port +- `--ssh-key KEY` Set up SSH login public key, [formatted as follows](#--ssh-key). When using public key, password is empty. +- `--frpc-toml PATH` Add frpc for intranet tunneling. Parameter can be local filepath or HTTP URL ### Feature 4: Reboot to netboot.xyz - Can manually install [more systems](https://github.com/netbootxyz/netboot.xyz?tab=readme-ov-file#what-operating-systems-are-currently-available-on-netbootxyz) using vendor backend VNC. -- If manual operations do not damage the original system, rebooting will return to the original system. + +> [!TIP] +> +> Although the script being run is `reinstall`, this feature **does not** delete any data or perform an automatic reinstallation; manual user operation is required. + +> If the user does not damage the original system during manual operation, rebooting will return to the original system. ```bash bash reinstall.sh netboot.xyz @@ -244,10 +300,17 @@ bash reinstall.sh netboot.xyz ![Windows Installation](https://github.com/bin456789/reinstall/assets/7548515/07c1aea2-1ce3-4967-904f-aaf9d6eec3f7) -- Username `administrator`, Default password `123@@@` +> [!CAUTION] +> +> This feature will erase **the entire hard disk** of the current system (including other partitions)! +> +> Data is priceless — please think twice before proceeding! + +- Username `administrator`. The script prompts for a password. If left blank, a random one is generated. - If remote login fails, try using the username `.\administrator`. - The machine with a static IP will automatically configure the IP. It may take a few minutes to take effect on the first boot. -- Supports all languages. +- Supports ISO images in any language. +- Supports bypassing Windows 11 hardware requirements. #### Supported Systems @@ -256,12 +319,9 @@ bash reinstall.sh netboot.xyz - Windows Server Essentials \* - Windows Server (Semi) Annual Channel \* - Hyper-V Server \* - - Azure Stack HCI \* + - Azure Local (Azure Stack HCI) \* -#### ~~Method 1: Let the Script Automatically Search for ISO~~ - -> [!CAUTION] -> The ISO repository currently prohibits direct link downloads, so this method is no longer effective. +#### Method 1: Let the Script Automatically Search for ISO - The script will search for ISOs from , a site that collects official ISOs. - Systems marked with \* do not support automatic ISO searching. @@ -325,7 +385,7 @@ zh-tw ```bash bash reinstall.sh windows \ - --image-name "Windows 11 Enterprise LTSC 2024" \ + --image-name "Windows 11 Enterprise LTSC 2024 Evaluation" \ --iso "https://go.microsoft.com/fwlink/?linkid=2289029" ``` @@ -350,7 +410,6 @@ bash reinstall.sh windows \ - - - Evaluation - - - - - @@ -364,30 +423,58 @@ bash reinstall.sh windows \ -#### Optional parameters +#### Optional Parameters - `--password PASSWORD` Set Password -- `--allow-ping` Configures the Windows firewall to allow ping requests +- `--allow-ping` Configure Windows Firewall to Allow Ping Responses - `--rdp-port PORT` Change RDP port -- `--ssh-port PORT` Change SSH port (for log observation during installation) -- `--web-port PORT` Change Web port (for log observation during installation) +- `--ssh-port PORT` Change SSH port (for log observation during installation only) +- `--web-port PORT` Change Web port (for log observation during installation only) - `--add-driver INF_OR_DIR` Add additional driver, specifying .inf path, or the folder contains .inf file. - - The driver must be downloaded locally first. + - The driver must be downloaded to current system first. - This parameter can be set multiple times to add different driver. -- `--hold 2` Allow SSH connections for modifying the disk content before rebooting into the official Windows installation program, with the disk mounted at `/os`. +- `--frpc-toml PATH` Add frpc for intranet tunneling. Parameter can be local filepath or HTTP URL +- `--hold 1` Reboot only into install environment, without running installer, only for SSH connect to test network connection. +- `--hold 2` Allow SSH connections for modifying `boot.wim`, `install.wim` or other contents before rebooting into the official Windows installation program, with the disk mounted at `/os`. #### The following drivers will automatic download and install as needed, without the need for manual addition -- Virtio ([Virtio](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/), [Alibaba Cloud](https://www.alibabacloud.com/help/ecs/user-guide/update-red-hat-virtio-drivers-of-windows-instances)) -- XEN ([XEN](https://xenproject.org/resources/downloads/), [Citrix](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Upgrading_PV_drivers.html#win2008-citrix-upgrade), [AWS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/xen-drivers-overview.html)) -- AWS ([ENA Network Adapter](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ena-driver-releases-windows.html), [NVMe Storage Controller](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-driver-version-history.html)) -- GCP ([gVNIC Network Adapter](https://cloud.google.com/compute/docs/networking/using-gvnic), [GGA Display Adapter](https://cloud.google.com/compute/docs/instances/enable-instance-virtual-display)) -- Azure ([MANA Network Adapter](https://learn.microsoft.com/azure/virtual-network/accelerated-networking-mana-windows)) -- Intel ([VMD Storage Controller](https://www.intel.com/content/www/us/en/download/720755/intel-rapid-storage-technology-driver-installation-software-with-intel-optane-memory-11th-up-to-13th-gen-platforms.html)) +- VirtIO ([Community][virtio-virtio], [Alibaba Cloud][virtio-aliyun], [Tencent Cloud][virtio-qcloud], [GCP][virtio-gcp]) +- XEN ([~~Community~~][xen-xen] (unsigned), [Citrix][xen-citrix], [AWS][xen-aws]) +- AWS ([ENA Network Adapter][aws-ena], [NVME Storage Controller][aws-nvme]) +- GCP ([gVNIC Network Adapter][gcp-gvnic], [GGA Display Adapter][gcp-gga]) +- Azure ([MANA Network Adapter][azure-mana]) +- Intel ([VMD Storage Controller][intel-vmd], Network Adapter: [7][intel-nic-7], [8][intel-nic-8], [8.1][intel-nic-8.1], [10][intel-nic-10], [11][intel-nic-11], [2008 R2][intel-nic-2008-r2], [2012][intel-nic-2012], [2012 R2][intel-nic-2012-r2], [2016][intel-nic-2016], [2019][intel-nic-2019], [2022][intel-nic-2022], [2025][intel-nic-2025]) + +[virtio-virtio]: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/ +[virtio-aliyun]: https://www.alibabacloud.com/help/ecs/user-guide/install-the-virtio-driver-1 +[virtio-qcloud]: https://cloud.tencent.com/document/product/213/17815#b84b2032-752c-43c4-a509-73530b8f82ff +[virtio-gcp]: https://console.cloud.google.com/storage/browser/gce-windows-drivers-public +[xen-xen]: https://xenproject.org/resources/downloads/ +[xen-aws]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/xen-drivers-overview.html +[xen-citrix]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Upgrading_PV_drivers.html#win2008-citrix-upgrade +[aws-ena]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ena-driver-releases-windows.html +[aws-nvme]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-driver-version-history.html +[gcp-gvnic]: https://cloud.google.com/compute/docs/networking/using-gvnic +[gcp-gga]: https://cloud.google.com/compute/docs/instances/enable-instance-virtual-display +[azure-mana]: https://learn.microsoft.com/azure/virtual-network/accelerated-networking-mana-windows +[intel-vmd]: https://www.intel.com/content/www/us/en/download/849936/intel-rapid-storage-technology-driver-installation-software-with-intel-optane-memory-12th-to-15th-gen-platforms.html +[intel-nic-7]: https://www.intel.com/content/www/us/en/download/15590/intel-network-adapter-driver-for-windows-7-final-release.html +[intel-nic-8]: https://web.archive.org/web/20250501043104/https://www.intel.com/content/www/us/en/download/16765/intel-network-adapter-driver-for-windows-8-final-release.html +[intel-nic-8.1]: https://www.intel.com/content/www/us/en/download/17479/intel-network-adapter-driver-for-windows-8-1.html +[intel-nic-10]: https://www.intel.com/content/www/us/en/download/18293/intel-network-adapter-driver-for-windows-10.html +[intel-nic-11]: https://www.intel.com/content/www/us/en/download/727998/intel-network-adapter-driver-for-microsoft-windows-11.html +[intel-nic-2008-r2]: https://web.archive.org/web/20250501002542/https://www.intel.com/content/www/us/en/download/15591/intel-network-adapter-driver-for-windows-server-2008-r2-final-release.html +[intel-nic-2012]: https://www.intel.com/content/www/us/en/download/16789/intel-network-adapter-driver-for-windows-server-2012.html +[intel-nic-2012-r2]: https://www.intel.com/content/www/us/en/download/17480/intel-network-adapter-driver-for-windows-server-2012-r2.html +[intel-nic-2016]: https://www.intel.com/content/www/us/en/download/18737/intel-network-adapter-driver-for-windows-server-2016.html +[intel-nic-2019]: https://www.intel.com/content/www/us/en/download/19372/intel-network-adapter-driver-for-windows-server-2019.html +[intel-nic-2022]: https://www.intel.com/content/www/us/en/download/706171/intel-network-adapter-driver-for-windows-server-2022.html +[intel-nic-2025]: https://www.intel.com/content/www/us/en/download/838943/intel-network-adapter-driver-for-windows-server-2025.html #### How to Specify the Image Name `--image-name` -Typically, an ISO will contain multiple system versions, such as Home Edition and Professional Edition. The image name `--image-name` is used to specify the version to be installed, and it is case-insensitive when entered. +An ISO usually contains multiple system editions, such as Home and Pro. Therefore, you need to use `--image-name` to specify the system edition (image name) to install, case-insensitive. You can use tools like DISM, DISM++, or Wimlib to query the image names included in the ISO. @@ -410,18 +497,32 @@ Open File menu > Open Image File, select the iso to be installed to get the imag > Vista (Server 2008) and 32-bit systems may lack drivers. > [!WARNING] -> For EFI machines without CSM enabled, Windows 7 (Server 2008 R2) cannot be installed. > -> Hyper-V (Azure) requires selecting the appropriate VM generation: +> For Windows 7 (Server 2008 R2) installation: +> +> 1. EFI-boot machines must enable CSM. +> +> 2. On Hyper-V (Azure), select Generation 1 VM. > [!WARNING] +> > In the Chinese version of Windows 10 LTSC 2021 ISO `zh-cn_windows_10_enterprise_ltsc_2021_x64_dvd_033b7312.iso`, the `wsappx` process may indefinitely consume CPU resources. > > The solution is to update the system patches or manually install the `VCLibs` library . +> [!WARNING] +> +> When installing Windows ISOs released in `May 2022` or later on GCP, the system may repeatedly reboot during the Windows installation (PE) stage. You can resolve this issue using one of the following two methods: +> +> 1. Add the `--force-boot-mode bios` parameter. The script will install Windows in `BIOS boot + MBR partition table` mode. +> +> (Optional) After installation, you can convert it to `EFI boot + GPT partition table` using the command `MBR2GPT /convert /allowFullOS`. +> +> 2. Create a custom RAW image and install it via DD. + #### Considerations for Installing Windows on ARM -Most ARM machines support installing Windows 11 24H2. +Most ARM machines support installing latest Windows 11. During the installation process, you might encounter a black screen, and the serial console may display `ConvertPages: failed to find range`, but neither issue affects the installation. @@ -448,10 +549,33 @@ Log in to the server using Remote Desktop, open Device Manager, locate the graph -## Discussion +## Parameter Format -[![GitHub Issues](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/bin456789/reinstall/issues) -[![Telegram Group](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/reinstall_os) +### --ssh-key + +- `--ssh-key "ssh-rsa ..."` +- `--ssh-key "ssh-ed25519 ..."` +- `--ssh-key "ecdsa-sha2-nistp256/384/521 ..."` +- `--ssh-key http://path/to/public_key` +- `--ssh-key github:your_username` +- `--ssh-key gitlab:your_username` +- `--ssh-key /path/to/public_key` +- `--ssh-key C:\path\to\public_key` + +## How to Use an Old Version + +According to the Law of Bug Conservation, fixing old bugs often introduces new ones. + +If a new bug occurs, try using an older version to see if it works. + +Go to and find the old version’s `commit_id` on the right side. + +```bash +commit_id=xxxxxxx +curl -O https://raw.githubusercontent.com/bin456789/reinstall/$commit_id/reinstall.sh || wget -O ${_##*/} $_ +sed -i "/^confhome.*main$/s/main/$commit_id/" reinstall.sh +bash reinstall.sh ... +``` ## How to Modify the Script for Your Own @@ -461,8 +585,6 @@ Log in to the server using Remote Desktop, open Device Manager, locate the graph ## Thanks -[![Github Sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/bin456789) - Thanks to the following businesses for providing free servers. [![Oracle Cloud](https://github.com/bin456789/reinstall/assets/7548515/8b430ed4-8344-4f96-b4da-c2bda031cc90)](https://www.oracle.com/cloud/) diff --git a/README.md b/README.md index 89e83d1..436e5cd 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,40 @@ [![Codacy](https://img.shields.io/codacy/grade/dc679a17751448628fe6d8ac35e26eed?logo=Codacy&label=Codacy&style=flat-square)](https://app.codacy.com/gh/bin456789/reinstall/dashboard) [![CodeFactor](https://img.shields.io/codefactor/grade/github/bin456789/reinstall?logo=CodeFactor&logoColor=white&label=CodeFactor&style=flat-square)](https://www.codefactor.io/repository/github/bin456789/reinstall) -[![Lines of Code](https://tokei.rs/b1/github/bin456789/reinstall?category=code&label=Lines%20of%20Code&style=flat-square)](https://github.com/XAMPPRocky/tokei) -[![Telegram Group](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-square&logo=telegram&logoColor=white)](https://t.me/reinstall_os) -[![Github Sponsors](https://img.shields.io/badge/sponsor-30363D?style=flat-square&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/bin456789) - +[![Lines of Code](https://tokei.rs/b1/github/bin456789/reinstall?category=code&label=Lines%20of%20Code&style=flat-square)](https://github.com/XAMPPRocky/tokei_rs) -一键重装脚本 [English](README.en.md) +一键 VPS 系统重装脚本 [English](README.en.md) -[![Sponsors](https://raw.githubusercontent.com/bin456789/sponsors/refs/heads/master/sponsors.svg)](https://github.com/sponsors/bin456789) +## 介绍 -## 亮点 - -- 一键安装 Linux,支持 19 种常见发行版 -- 一键安装 Windows,使用官方 ISO 安装而非自制镜像,~~脚本会自动获取 ISO 链接~~、自动安装 Virtio 等常见驱动 +- 一键重装到 Linux,支持 19 种常见发行版 +- 一键重装到 Windows,使用官方原版 ISO 而非自制镜像,脚本支持自动查找 ISO 链接、自动安装 `VirtIO` 等公有云驱动 - 支持任意方向重装,即 `Linux to Linux`、`Linux to Windows`、`Windows to Windows`、`Windows to Linux` -- 无需填写 IP 参数,自动识别动静态,支持 `/32`、`/128`、`网关不在子网范围内`、`纯 IPv6`、`双网卡` +- 自动设置 IP,智能设置动静态,支持 `/32`、`/128`、`网关不在子网范围内`、`纯 IPv6`、`IPv4/IPv6 在不同的网卡` - 专门适配低配小鸡,比官方 netboot 需要更少的内存 - 全程用分区表 ID 识别硬盘,确保不会写错硬盘 - 支持 BIOS、EFI 引导,支持 ARM 服务器 - 不含自制包,所有资源均实时从镜像源获得 +如果帮到你,可以请我喝奶茶。 +[![Donate](https://img.shields.io/badge/Donate-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/bin456789) + +[![Sponsors](https://raw.githubusercontent.com/bin456789/sponsors/refs/heads/master/sponsors.svg)](https://github.com/sponsors/bin456789) + +### 反馈 + +[![GitHub Issues](https://img.shields.io/badge/GitHub-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/bin456789/reinstall/issues) +[![Telegram Group](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/reinstall_os) + +## 快速开始 + +- [下载](#下载当前系统是--linux) +- [功能 1. 一键重装到 Linux](#功能-1-安装--linux) +- [功能 2. 一键 DD Raw 镜像到硬盘](#功能-2-dd-raw-镜像到硬盘) +- [功能 3. 一键引导到 Alpine Live OS 内存系统](#功能-3-重启到--alpine-live-os内存系统) +- [功能 4. 一键引导到 netboot.xyz](#功能-4-重启到--netbootxyz) +- [功能 5. 一键重装到 Windows](#功能-5-安装--windows-iso) + ## 系统要求 原系统可以是表格中的任意系统 @@ -32,22 +46,22 @@ | 系统 | 版本 | 内存 | 硬盘 | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | --------- | ------------ | -| Alpine | 3.18, 3.19, 3.20, 3.21 | 256 MB | 1 GB | -| Debian | 9, 10, 11, 12 | 256 MB | 1 ~ 1.5 GB ^ | +| Alpine | 3.20, 3.21, 3.22, 3.23 | 256 MB | 1 GB | +| Debian | 9, 10, 11, 12, 13 | 256 MB | 1 ~ 1.5 GB ^ | | Kali | 滚动 | 256 MB | 1 ~ 1.5 GB ^ | -| Ubuntu | 16.04 LTS - 24.04 LTS, 24.10 | 512 MB \* | 2 GB | +| Ubuntu | 16.04 LTS - 24.04 LTS, 25.10 | 512 MB \* | 2 GB | | Anolis | 7, 8, 23 | 512 MB \* | 5 GB | -| RHEL   AlmaLinux   Rocky   Oracle | 8, 9 | 512 MB \* | 5 GB | +| RHEL   AlmaLinux   Rocky   Oracle | 8, 9, 10 | 512 MB \* | 5 GB | | OpenCloudOS | 8, 9, Stream 23 | 512 MB \* | 5 GB | -| CentOS | 9, 10 | 512 MB \* | 5 GB | -| Fedora | 40, 41 | 512 MB \* | 5 GB | -| openEuler | 20.03 LTS - 24.03 LTS, 24.09 | 512 MB \* | 5 GB | -| openSUSE | 15.6, Tumbleweed (滚动) | 512 MB \* | 5 GB | -| NixOS | 24.11 | 512 MB | 5 GB | +| CentOS Stream | 9, 10 | 512 MB \* | 5 GB | +| Fedora | 42, 43 | 512 MB \* | 5 GB | +| openEuler | 20.03 LTS - 24.03 LTS, 25.09 | 512 MB \* | 5 GB | +| openSUSE | Leap 15.6, 16.0, Tumbleweed (滚动) | 512 MB \* | 5 GB | +| NixOS | 25.11 | 512 MB | 5 GB | | Arch | 滚动 | 512 MB | 5 GB | | Gentoo | 滚动 | 512 MB | 5 GB | -| 安同 OS | 滚动 | 512 MB | 5 GB | -| 飞牛 fnOS | 公测 | 512 MB | 8 GB | +| 安同 OS | 滚动 | 512 MB | 5 GB | +| 飞牛 fnOS | 1 | 512 MB | 8 GB | | Windows (DD) | 任何 | 512 MB | 取决于镜像 | | Windows (ISO) | Vista, 7, 8.x (Server 2008 - 2012 R2) | 512 MB | 25 GB | | Windows (ISO) | 10, 11 (Server 2016 - 2025) | 1 GB | 25 GB | @@ -57,6 +71,13 @@ ^ 表示需要 256 MB 内存 + 1.5 GB 硬盘,或 512 MB 内存 + 1 GB 硬盘 > [!WARNING] +> +> 本脚本理论上支持独服和 PC +> +> 但如果能使用 IPMI 或 U 盘,则不建议使用本脚本 + +> [!WARNING] +> > ❌ 本脚本不支持 OpenVZ、LXC 虚拟机 > > 请改用 @@ -66,13 +87,13 @@ 国外服务器: ```bash -curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O reinstall.sh $_ +curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O ${_##*/} $_ ``` 国内服务器: ```bash -curl -O https://gitlab.com/bin456789/reinstall/-/raw/main/reinstall.sh || wget -O reinstall.sh $_ +curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O ${_##*/} $_ ``` ## 下载(当前系统是 Windows) @@ -105,7 +126,7 @@ certutil -urlcache -f -split https://raw.githubusercontent.com/bin456789/reinsta 国内服务器: ```batch -certutil -urlcache -f -split https://gitlab.com/bin456789/reinstall/-/raw/main/reinstall.bat +certutil -urlcache -f -split https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.bat ``` ## 使用 @@ -114,11 +135,17 @@ certutil -urlcache -f -split https://gitlab.com/bin456789/reinstall/-/raw/main/r - Linux 下运行 `bash reinstall.sh ...` - Windows 下先运行 `cmd`,再运行 `reinstall.bat ...` - - 如果参数中的链接包含特殊字符,要用 `""` 将链接包起来,不能用 `''` + - 如果参数中的链接包含特殊字符,要用 `""` 将链接包裹起来,不能用 `''` ### 功能 1: 安装 Linux -- 用户名 `root` 默认密码 `123@@@` +> [!CAUTION] +> +> 此功能会清除当前系统**整个硬盘**的全部数据(包含其它分区)! +> +> 数据无价,请三思而后行! + +- 用户名为 `root`,脚本会提示输入密码,不输入则使用随机密码 - 安装最新版可不输入版本号 - 最大化利用磁盘空间:不含 boot 分区(Fedora 例外),不含 swap 分区 - 自动根据机器类型选择不同的优化内核,例如 `Cloud`、`HWE` 内核 @@ -127,38 +154,43 @@ certutil -urlcache -f -split https://gitlab.com/bin456789/reinstall/-/raw/main/r ```bash bash reinstall.sh anolis 7|8|23 + rocky 8|9|10 + oracle 8|9|10 + almalinux 8|9|10 opencloudos 8|9|23 - rocky 8|9 - redhat 8|9 --img="http://xxx.com/xxx.qcow2" - oracle 8|9 - almalinux 8|9 centos 9|10 - fedora 40|41 - nixos 24.11 - debian 9|10|11|12 - opensuse 15.6|tumbleweed - alpine 3.18|3.19|3.20|3.21 - openeuler 20.03|22.03|24.03|24.09 - ubuntu 16.04|18.04|20.04|22.04|24.04|24.10 [--minimal] + fnos 1 + nixos 25.11 + fedora 42|43 + debian 9|10|11|12|13 + alpine 3.20|3.21|3.22|3.23 + opensuse 15.6|16.0|tumbleweed + openeuler 20.03|22.03|24.03|25.09 + ubuntu 16.04|18.04|20.04|22.04|24.04|25.10 [--minimal] kali arch gentoo aosc - fnos + redhat --img="http://access.cdn.redhat.com/xxx.qcow2" ``` #### 可选参数 - `--password PASSWORD` 设置密码 -- `--ssh-port PORT` 修改 SSH 端口(安装期间观察日志用,也用于新系统) +- `--ssh-key KEY` 设置 SSH 登录公钥,[格式如下](#--ssh-key)。当使用公钥时,密码为空 +- `--ssh-port PORT` 修改 SSH 端口(安装期间观察日志用,也作用于新系统) - `--web-port PORT` 修改 Web 端口(安装期间观察日志用) -- `--hold 2` 安装结束后不重启,此时可以 SSH 登录修改系统内容,系统挂载在 `/os` (此功能不支持 Debian/Kali) +- `--frpc-toml PATH` 添加 frpc 内网穿透,参数填本地路径或 HTTP 链接 +- `--hold 1` 仅重启到安装环境,不运行安装,用于 SSH 登录验证网络连通性 +- `--hold 2` 安装结束后不重启,用于 SSH 登录修改系统内容,Debian/Kali 会挂载在 `/target`,其它系统会挂载在 `/os` > [!TIP] -> 安装 Debian/Kali 时,x86 可通过后台 VNC 查看安装进度,ARM 可通过串行控制台查看安装进度。 > -> 安装其它系统时,可通过多种方式(SSH、HTTP 80 端口、后台 VNC、串行控制台)查看安装进度。 ->
即使安装过程出错,也能通过 SSH 运行 `/trans.sh alpine` 安装到 Alpine。 +> 可通过多种方式(SSH、HTTP 80 端口、商家后台 VNC、串行控制台)查看安装进度。 +> +> 即使安装过程出错,也能连接 SSH 手动救砖。 +> +> 目标系统非 Debian/Kali 时,可以运行 `/trans.sh alpine` 自动救砖成 Alpine 系统。
@@ -192,9 +224,15 @@ bash reinstall.sh ubuntu --installer
-### 功能 2: DD +### 功能 2: DD RAW 镜像到硬盘 -- 支持 `raw` `vhd` 格式的镜像(未压缩,或者压缩成 `.gz` `.xz` `.zst` `.tar` `.tar.gz` `.tar.xz` `.tar.zst`) +> [!CAUTION] +> +> 此功能会清除当前系统**整个硬盘**的全部数据(包含其它分区)! +> +> 数据无价,请三思而后行! + +- 支持 `raw` 和固定大小的 `vhd` 镜像。未压缩或者压缩成 `.gz` `.xz` `.zst` `.tar` `.tar.gz` `.tar.xz` `.tar.zst` - DD Windows 镜像时,会自动扩展系统盘,静态 IP 的机器会配置好 IP,可能首次开机几分钟后才生效 - DD Linux 镜像时,**不会**修改镜像的任何内容 @@ -204,35 +242,53 @@ bash reinstall.sh dd --img "https://example.com/xxx.xz" #### 可选参数 -- `--allow-ping` 允许被 Ping (仅限 DD Windows) +- `--allow-ping` 设置 Windows 防火墙允许被 Ping (仅限 DD Windows) - `--rdp-port PORT` 修改 RDP 端口 (仅限 DD Windows) - `--ssh-port PORT` 修改 SSH 端口(安装期间观察日志用) - `--web-port PORT` 修改 Web 端口(安装期间观察日志用) -- `--hold 2` DD 结束后不重启,此时可以 SSH 登录修改系统内容,Windows 系统会挂载在 `/os`,Linux 系统**不会**自动挂载 +- `--frpc-toml PATH` 添加 frpc 内网穿透(仅限 DD Windows),参数填本地路径或 HTTP 链接 +- `--hold 1` 仅重启到安装环境,不运行安装,用于 SSH 登录验证网络连通性 +- `--hold 2` DD 结束后不重启,用于 SSH 登录修改系统内容,Windows 系统会挂载在 `/os`,Linux 系统**不会**自动挂载 > [!TIP] -> 可通过多种方式(SSH、HTTP 80 端口、后台 VNC、串行控制台)查看安装进度。 ->
即使安装过程出错,也能通过 SSH 运行 `/trans.sh alpine` 安装到 Alpine。 +> +> 可通过多种方式(SSH、HTTP 80 端口、商家后台 VNC、串行控制台)查看安装进度。 +> +> 即使安装过程出错,也能连接 SSH 手动救砖 +> +> 也可以运行 `/trans.sh alpine` 自动救砖成 Alpine 系统。 ### 功能 3: 重启到 Alpine Live OS(内存系统) -- 可用 ssh 连接,进行备份/恢复硬盘、手动 DD、修改分区、手动安装 Alpine/Arch/Gentoo 等操作 -- 用户名 `root` 默认密码 `123@@@` -- 如果手动操作没有破坏原系统,再次重启将回到原系统 +- 可用 ssh 连接,进行备份/恢复硬盘、手动 DD、修改分区、手动安装 Alpine 等操作 +- 用户名为 `root`,脚本会提示输入密码,不输入则使用随机密码 + +> [!TIP] +> +> 虽然运行的脚本叫 `reinstall`,但是此功能**不会**删除任何数据和进行自动重装,而是要用户手动操作 +> +> 如果用户手动操作没有破坏原系统,再次重启将回到原系统 ```bash -bash reinstall.sh alpine --hold=1 +bash reinstall.sh alpine --hold 1 ``` #### 可选参数 - `--password PASSWORD` 设置密码 - `--ssh-port PORT` 修改 SSH 端口 +- `--ssh-key KEY` 设置 SSH 登录公钥,[格式如下](#--ssh-key)。当使用公钥时,密码为空 +- `--frpc-toml PATH` 添加 frpc 内网穿透,参数填本地路径或 HTTP 链接 ### 功能 4: 重启到 netboot.xyz - 可使用商家后台 VNC 手动安装 [更多系统](https://github.com/netbootxyz/netboot.xyz?tab=readme-ov-file#what-operating-systems-are-currently-available-on-netbootxyz) -- 如果手动操作没有破坏原系统,再次重启将回到原系统 + +> [!TIP] +> +> 虽然运行的脚本叫 `reinstall`,但是此功能**不会**删除任何数据和进行自动重装,而是要用户手动操作 +> +> 如果用户手动操作没有破坏原系统,再次重启将回到原系统 ```bash bash reinstall.sh netboot.xyz @@ -244,10 +300,17 @@ bash reinstall.sh netboot.xyz ![Windows 安装界面](https://github.com/bin456789/reinstall/assets/7548515/07c1aea2-1ce3-4967-904f-aaf9d6eec3f7) -- 用户名 `administrator` 默认密码 `123@@@` +> [!CAUTION] +> +> 此功能会清除当前系统**整个硬盘**的全部数据(包含其它分区)! +> +> 数据无价,请三思而后行! + +- 用户名为 `administrator`,脚本会提示输入密码,不输入则使用随机密码 - 如果远程登录失败,可以尝试使用用户名 `.\administrator` - 静态机器会自动配置好 IP,可能首次开机几分钟后才生效 -- 支持所有语言 +- 支持任意语言的 ISO +- 支持绕过 Windows 11 硬件限制 #### 支持的系统 @@ -256,12 +319,9 @@ bash reinstall.sh netboot.xyz - Windows Server Essentials \* - Windows Server (Semi) Annual Channel \* - Hyper-V Server \* - - Azure Stack HCI \* + - Azure Local (Azure Stack HCI) \* -#### ~~方法 1: 让脚本自动查找 ISO~~ - -> [!CAUTION] -> 目前该 ISO 仓库禁止了直链下载,因此该方法已失效 +#### 方法 1: 让脚本自动查找 ISO - 脚本会从 查找 ISO,该网站专门提供官方 ISO 下载 - 上面带 \* 的系统不支持自动查找 ISO @@ -325,7 +385,7 @@ zh-tw ```bash bash reinstall.sh windows \ - --image-name "Windows 11 Enterprise LTSC 2024" \ + --image-name "Windows 11 Enterprise LTSC 2024 Evaluation" \ --iso "https://go.microsoft.com/fwlink/?linkid=2289029" ``` @@ -350,7 +410,6 @@ bash reinstall.sh windows \ - - - 评估版 - - - - - @@ -369,25 +428,53 @@ bash reinstall.sh windows \ - `--password PASSWORD` 设置密码 - `--allow-ping` 设置 Windows 防火墙允许被 Ping - `--rdp-port PORT` 更改 RDP 端口 -- `--ssh-port PORT` 修改 SSH 端口(安装期间观察日志用) -- `--web-port PORT` 修改 Web 端口(安装期间观察日志用) +- `--ssh-port PORT` 修改 SSH 端口(仅安装期间观察日志用) +- `--web-port PORT` 修改 Web 端口(仅安装期间观察日志用) - `--add-driver INF_OR_DIR` 添加额外驱动,填写 .inf 路径,或者 .inf 所在的文件夹 - - 需先下载驱动到本地 + - 需先下载驱动到当前系统 - 可多次设置该参数以添加不同的驱动 -- `--hold 2` 在进入 Windows 官方安装程序之前,可以 SSH 登录修改硬盘内容,硬盘挂载在 `/os` +- `--frpc-toml PATH` 添加 frpc 内网穿透,参数填本地路径或 HTTP 链接 +- `--hold 1` 仅重启到安装环境,不运行安装,用于 SSH 登录验证网络连通性 +- `--hold 2` 用于在进入 Windows 官方安装程序之前,SSH 登录修改 `boot.wim`、`install.wim` 或者其它内容,硬盘挂载在 `/os` #### 以下驱动会自动按需下载安装,无需手动添加 -- Virtio ([Virtio](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/)、[阿里云](https://www.alibabacloud.com/help/ecs/user-guide/update-red-hat-virtio-drivers-of-windows-instances)) -- XEN ([XEN](https://xenproject.org/resources/downloads/)、[Citrix](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Upgrading_PV_drivers.html#win2008-citrix-upgrade)、[AWS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/xen-drivers-overview.html)) -- AWS ([ENA 网卡](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ena-driver-releases-windows.html)、[NVME 存储控制器](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-driver-version-history.html)) -- GCP ([gVNIC 网卡](https://cloud.google.com/compute/docs/networking/using-gvnic)、[GGA 显卡](https://cloud.google.com/compute/docs/instances/enable-instance-virtual-display)) -- Azure ([MANA 网卡](https://learn.microsoft.com/azure/virtual-network/accelerated-networking-mana-windows)) -- Intel ([VMD 存储控制器](https://www.intel.com/content/www/us/en/download/720755/intel-rapid-storage-technology-driver-installation-software-with-intel-optane-memory-11th-up-to-13th-gen-platforms.html)) +- VirtIO ([社区版][virtio-virtio], [阿里云][virtio-aliyun], [腾讯云][virtio-qcloud], [GCP][virtio-gcp]) +- XEN ([~~社区版~~][xen-xen] (未签名), [Citrix][xen-citrix], [AWS][xen-aws]) +- AWS ([ENA 网卡][aws-ena], [NVME 存储控制器][aws-nvme]) +- GCP ([gVNIC 网卡][gcp-gvnic], [GGA 显卡][gcp-gga]) +- Azure ([MANA 网卡][azure-mana]) +- Intel ([VMD 存储控制器][intel-vmd], 网卡: [7][intel-nic-7], [8][intel-nic-8], [8.1][intel-nic-8.1], [10][intel-nic-10], [11][intel-nic-11], [2008 R2][intel-nic-2008-r2], [2012][intel-nic-2012], [2012 R2][intel-nic-2012-r2], [2016][intel-nic-2016], [2019][intel-nic-2019], [2022][intel-nic-2022], [2025][intel-nic-2025]) + +[virtio-virtio]: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/ +[virtio-aliyun]: https://www.alibabacloud.com/help/ecs/user-guide/install-the-virtio-driver-1 +[virtio-qcloud]: https://cloud.tencent.com/document/product/213/17815#b84b2032-752c-43c4-a509-73530b8f82ff +[virtio-gcp]: https://console.cloud.google.com/storage/browser/gce-windows-drivers-public +[xen-xen]: https://xenproject.org/resources/downloads/ +[xen-aws]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/xen-drivers-overview.html +[xen-citrix]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Upgrading_PV_drivers.html#win2008-citrix-upgrade +[aws-ena]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ena-driver-releases-windows.html +[aws-nvme]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-driver-version-history.html +[gcp-gvnic]: https://cloud.google.com/compute/docs/networking/using-gvnic +[gcp-gga]: https://cloud.google.com/compute/docs/instances/enable-instance-virtual-display +[azure-mana]: https://learn.microsoft.com/azure/virtual-network/accelerated-networking-mana-windows +[intel-vmd]: https://www.intel.com/content/www/us/en/download/849936/intel-rapid-storage-technology-driver-installation-software-with-intel-optane-memory-12th-to-15th-gen-platforms.html +[intel-nic-7]: https://www.intel.com/content/www/us/en/download/15590/intel-network-adapter-driver-for-windows-7-final-release.html +[intel-nic-8]: https://web.archive.org/web/20250501043104/https://www.intel.com/content/www/us/en/download/16765/intel-network-adapter-driver-for-windows-8-final-release.html +[intel-nic-8.1]: https://www.intel.com/content/www/us/en/download/17479/intel-network-adapter-driver-for-windows-8-1.html +[intel-nic-10]: https://www.intel.com/content/www/us/en/download/18293/intel-network-adapter-driver-for-windows-10.html +[intel-nic-11]: https://www.intel.com/content/www/us/en/download/727998/intel-network-adapter-driver-for-microsoft-windows-11.html +[intel-nic-2008-r2]: https://web.archive.org/web/20250501002542/https://www.intel.com/content/www/us/en/download/15591/intel-network-adapter-driver-for-windows-server-2008-r2-final-release.html +[intel-nic-2012]: https://www.intel.com/content/www/us/en/download/16789/intel-network-adapter-driver-for-windows-server-2012.html +[intel-nic-2012-r2]: https://www.intel.com/content/www/us/en/download/17480/intel-network-adapter-driver-for-windows-server-2012-r2.html +[intel-nic-2016]: https://www.intel.com/content/www/us/en/download/18737/intel-network-adapter-driver-for-windows-server-2016.html +[intel-nic-2019]: https://www.intel.com/content/www/us/en/download/19372/intel-network-adapter-driver-for-windows-server-2019.html +[intel-nic-2022]: https://www.intel.com/content/www/us/en/download/706171/intel-network-adapter-driver-for-windows-server-2022.html +[intel-nic-2025]: https://www.intel.com/content/www/us/en/download/838943/intel-network-adapter-driver-for-windows-server-2025.html #### 如何填写映像名称 `--image-name` -通常一个 ISO 会包含多个系统版本,例如家庭版、专业版。映像名称 `--image-name` 就是用来指定要安装的版本,填写时不区分大小写 +一个 ISO 通常包含多个系统版本,例如家庭版、专业版。因此需要用 `--image-name` 指定要安装的系统版本(映像名称),不区分大小写 可以用 DISM、DISM++、Wimlib 等工具查询 ISO 包含的映像名称 @@ -410,18 +497,32 @@ Windows Server 2025 SERVERDATACENTER > Vista (Server 2008) 和 32 位系统可能会缺少驱动 > [!WARNING] -> 未开启 CSM 的 EFI 机器,无法安装 Windows 7 (Server 2008 R2) > -> Hyper-V (Azure) 需选择合适的虚拟机代系 +> 安装 Windows 7 (Server 2008 R2) 时 +> +> 1. EFI 引导的机器要开启 CSM +> +> 2. Hyper-V (Azure) 需选择第 1 代虚拟机 > [!WARNING] +> > Windows 10 LTSC 2021 中文版镜像 `zh-cn_windows_10_enterprise_ltsc_2021_x64_dvd_033b7312.iso` 的 `wsappx` 进程会长期占用 CPU > > 解决方法是更新系统补丁,或者手动安装 `VCLibs` 库 +> [!WARNING] +> +> 在 GCP 上安装 `2022年5月` 和之后发布的 Windows ISO,在引导 Windows 安装界面 (PE) 时会不断反复重启。解决方法如下,二选一 +> +> 1. 添加 `--force-boot-mode bios` 参数,脚本将以 `BIOS 引导 + MBR 分区表` 方式安装 Windows +> +> (可选) 安装完成后用 `MBR2GPT /convert /allowFullOS` 命令转为 `EFI 引导 + GPT 分区表` +> +> 2. 自制 RAW 镜像并通过 DD 安装 + #### ARM 安装 Windows 的注意事项 -大部分 ARM 机器都支持安装 Windows 11 24H2 +大部分 ARM 机器都支持安装最新版 Windows 11 安装过程可能会黑屏,串行控制台可能会显示 `ConvertPages: failed to find range`,均不影响正常安装 @@ -448,10 +549,33 @@ Windows Server 2025 SERVERDATACENTER -## 讨论 +## 参数格式 -[![GitHub Issues](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/bin456789/reinstall/issues) -[![Telegram Group](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/reinstall_os) +### --ssh-key + +- `--ssh-key "ssh-rsa ..."` +- `--ssh-key "ssh-ed25519 ..."` +- `--ssh-key "ecdsa-sha2-nistp256/384/521 ..."` +- `--ssh-key http://path/to/public_key` +- `--ssh-key github:your_username` +- `--ssh-key gitlab:your_username` +- `--ssh-key /path/to/public_key` +- `--ssh-key C:\path\to\public_key` + +## 如何使用旧版本 + +根据 Bug 守恒定律,修复旧 Bug 的同时会引入新的 Bug + +如果遇到新的 Bug,可以试下旧版本是否正常 + +从 右侧找到旧版本的 `commit_id` + +```bash +commit_id=xxxxxxx +curl -O https://raw.githubusercontent.com/bin456789/reinstall/$commit_id/reinstall.sh || wget -O ${_##*/} $_ +sed -i "/^confhome.*main$/s/main/$commit_id/" reinstall.sh +bash reinstall.sh ... +``` ## 如何修改脚本自用 @@ -461,8 +585,6 @@ Windows Server 2025 SERVERDATACENTER ## 感谢 -[![Github Sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/bin456789) - 感谢以下商家提供白嫖机器 [![Oracle Cloud](https://github.com/bin456789/reinstall/assets/7548515/8b430ed4-8344-4f96-b4da-c2bda031cc90)](https://www.oracle.com/cloud/) diff --git a/debian.cfg b/debian.cfg index f61a330..d24aa63 100644 --- a/debian.cfg +++ b/debian.cfg @@ -2,14 +2,14 @@ # shellcheck disable=SC1091,SC2148 # https://www.debian.org/releases/stable/amd64/apbs04.zh-cn.html # https://www.debian.org/releases/stable/example-preseed.txt -# https://preseed.debian.net/debian-preseed/bookworm/amd64-main-full.txt +# https://preseed.debian.net/debian-preseed/trixie/amd64-main-full.txt # 需要留意 kali initrd 自带的 /preseed.cfg # 下面这行语句无效,因为本行后面有反斜杠,前面有空格(安装器认为不算注释)\ -d-i debian-installer/locale string en_US +d-i debian-installer/locale string en_US.UTF-8 # B.4.1. 本地化 -d-i debian-installer/locale string en_US +d-i debian-installer/locale string en_US.UTF-8 d-i keyboard-configuration/xkb-keymap select us # B.4.2. 网络设置 @@ -25,9 +25,10 @@ d-i mirror/country string manual # B.4.5. 帐号设置 d-i passwd/make-user boolean false -# 单纯为了跳过设置,实际上是在 partman/early_command 里设置密码,preseed/early_command 无法设置密码 -d-i passwd/root-password password '' -d-i passwd/root-password-again password '' +# 注意如果用 ssh key 后面还要删除密码 +# d-i passwd/root-password password '' +# d-i passwd/root-password-again password '' +# d-i passwd/root-password-crypted password '' # kali 需要下面这行,否则会提示输入用户名 d-i passwd/root-login boolean true @@ -62,6 +63,9 @@ d-i partman-efi/non_efi_system boolean true # 选择 true 就一直死循环 d-i partman-basicfilesystems/no_swap boolean false +# 分区大小计算 +# https://salsa.debian.org/installer-team/partman-base/-/blob/master/lib/base.sh + # 最小值 膨胀权重 最大值 # https://salsa.debian.org/installer-team/partman-auto/-/blob/master/recipes/atomic?ref_type=heads # https://salsa.debian.org/installer-team/partman-auto/-/blob/master/recipes-amd64-efi/atomic?ref_type=heads @@ -72,6 +76,7 @@ d-i partman-auto/expert_recipe_efi string efi :: \ 1 1 -1 $default_filesystem \ method{ format } format{ } use_filesystem{ } $default_filesystem{ } mountpoint{ / } . +# 大于 2T 会自动用 gpt # shellcheck disable=SC1083,SC2086,SC2154 d-i partman-auto/expert_recipe_bios string bios :: \ 1 1 1 free \ @@ -95,29 +100,130 @@ d-i pkgsel/upgrade select none # B.4.11. 安装 bootloader # 添加 bootx64.efi -d-i grub2/force_efi_extra_removable boolean true +d-i grub-installer/force-efi-extra-removable boolean true # B.4.12. 完成安装 -d-i finish-install/reboot_in_progress note +# 由下面的 hold 2 设置 +# d-i finish-install/reboot_in_progress note # B.4.13. 预置其他的软件包 # 其他设置 # d-i anna/standard_modules boolean false # d-i anna/choose_modules string network-console -# d-i network-console/password password 123@@@ -# d-i network-console/password-again password 123@@@ +# d-i network-console/password password '' +# d-i network-console/password-again password '' # B.5.1. 安装过程中运行用户命令 # 注意所有命令都会合并成一行命令 # 最后的 true; \ 没什么用,只是让 vscode 代码高亮不报错误 +# debian 11+ 才有 websocketd + # 有 /cdrom/simple-cdd 才安装 simple-cdd-profiles # 不然安装时 control 脚本会报错: # Loading simple-cdd-profiles failed for unknown reasons + +# 未下载的组件,无法用 debconf-set,需要用 debconf-set-selections + +# https://salsa.debian.org/installer-team/network-console/-/blob/master/debian/network-console.postinst?ref_type=heads +# https://salsa.debian.org/installer-team/user-setup/-/blob/master/user-setup-apply?ref_type=heads + +# 此时还没有配置源,anna-install 会在配置完源后再安装 d-i preseed/early_command string true; \ - if [ -d /cdrom/simple-cdd ]; then anna-install simple-cdd-profiles; fi + for str in $(grep -wo "extra_[^ ]*" /proc/cmdline | sed 's/^extra_//'); do eval "$str"; done; \ + + di(){ \ + echo "d-i $*" >/tmp/selections.cfg; \ + echo "d-i $*" >>/tmp/selections.cfg.all; \ + debconf-set-selections /tmp/selections.cfg; \ + rm -f /tmp/selections.cfg; \ + }; \ + + run_as_service_with_screen() { \ + if ! [ -f /etc/screenrc.bak ]; then \ + cp /etc/screenrc /etc/screenrc.bak; \ + fi; \ + true >/etc/screenrc; \ + screen sh -c 'while true; do pidof ${1##*/} || "$@"; sleep 5; done' _ "$@"; \ + cp -f /etc/screenrc.bak /etc/screenrc; \ + }; \ + + if [ "$hold" = 1 ]; then \ + di auto-install/enable boolean false; \ + di debconf/priority select low; \ + di partman/early_command string; \ + else \ + { \ + echo 'Reinstalling...'; \ + echo 'Option 1. View logs:'; \ + echo ' tail -fn+1 /var/log/syslog'; \ + echo 'Option 2. Attach to the installer:'; \ + echo ' TERM=screen screen -xp1'; \ + } >>/etc/motd; \ + mem=$(grep ^MemTotal: /proc/meminfo | { read -r _ y _; echo "$((y / 1024))"; }); \ + if command -v websocketd && [ "$mem" -ge 400 ]; then \ + for _ in {1..10}; do \ + if wget "$confhome/logviewer.html" -O /tmp/index.html; then \ + break; \ + fi; \ + sleep 5; \ + done; \ + if [ -z "$web_port" ]; then \ + web_port=80; \ + fi; \ + run_as_service_with_screen websocketd --port 80 --loglevel=fatal --staticdir=/tmp \ + sh -c "tail -fn+0 /var/log/syslog | tr '\r' '\n' | grep -Fiv -e password -e token" ; \ + fi; \ + fi; \ + + if ! [ "$hold" = 2 ]; then \ + di finish-install/reboot_in_progress note; \ + fi; \ + + if [ -s /configs/ssh_keys ]; then \ + di passwd/root-password-crypted password "''"; \ + else \ + di passwd/root-password-crypted password "$(cat /configs/password-linux-sha512)"; \ + fi; \ + + mkdir -p /etc/ssh; \ + true >/etc/ssh/sshd_config; \ + if [ -s /configs/ssh_keys ]; then \ + (umask 077; mkdir -p /.ssh; cat /configs/ssh_keys >/.ssh/authorized_keys); \ + else \ + echo "PermitRootLogin yes" >>/etc/ssh/sshd_config; \ + fi; \ + if [ -n "$ssh_port" ] && ! [ "$ssh_port" = 22 ]; then \ + echo "Port $ssh_port" >>/etc/ssh/sshd_config; \ + fi; \ + grep -qs ^root: /etc/shadow || echo "root:$(cat /configs/password-linux-sha512):1:0:99999:7:::" >>/etc/shadow; \ + grep -qs ^nogroup: /etc/group || echo "nogroup:*:65534:" >>/etc/group; \ + grep -qs ^sshd: /etc/passwd || echo "sshd:*:100:65534::/run/sshd:/bin/false" >>/etc/passwd; \ + mkdir -p /run/sshd; \ + chmod 0755 /run/sshd; \ + ssh-keygen -A; \ + run_as_service_with_screen /usr/sbin/sshd -D; \ + + if [ -s /configs/frpc.toml ]; then \ + url=$(sh /get-frpc-url.sh linux); \ + mkdir -p /usr/local/bin; \ + mkdir -p /usr/local/etc/frpc; \ + for _ in {1..10}; do \ + if wget -O- "$url" | tar xz "*/frpc" -O >/usr/local/bin/frpc; then \ + break; \ + fi; \ + sleep 5; \ + done; \ + chmod a+x /usr/local/bin/frpc; \ + cp /configs/frpc.toml /usr/local/etc/frpc/; \ + run_as_service_with_screen /usr/local/bin/frpc -c /usr/local/etc/frpc/frpc.toml; \ + fi; \ + + if [ -d /cdrom/simple-cdd ]; then \ + anna-install simple-cdd-profiles; \ + fi # debian 11 initrd 没有 xargs awk # debian 12 initrd 没有 xargs @@ -131,10 +237,11 @@ d-i partman/early_command string true; \ true >$postinst; \ swapfile=/target/swapfile; \ - mem=$(grep ^MemTotal: /proc/meminfo | { read -r _ y _; echo "$y"; }); \ - mem=$((mem / 1024)); \ + mem=$(grep ^MemTotal: /proc/meminfo | { read -r _ y _; echo "$((y / 1024))"; }); \ swap_size=$((512 - mem)); \ - [ $swap_size -gt 0 ] && echo "fallocate -l ${swap_size}M $swapfile; mkswap $swapfile; swapon $swapfile" >>$postinst; \ + if [ $swap_size -gt 0 ]; then \ + echo "fallocate -l ${swap_size}M $swapfile; mkswap $swapfile; swapon $swapfile" >>$postinst; \ + fi; \ echo "swapoff -a; rm -f $swapfile" >/usr/lib/finish-install.d/95swapoff; \ chmod a+x /usr/lib/finish-install.d/95swapoff; \ @@ -151,18 +258,22 @@ d-i partman/early_command string true; \ eths=$(cd /dev/netconf/ && ls); \ - sh /can_use_cloud_kernel.sh "$xda" $eths || debconf-set base-installer/kernel/image "$(debconf-get base-installer/kernel/image | sed 's/-cloud//')"; \ + if ! sh /can_use_cloud_kernel.sh "$xda" $eths; then \ + debconf-set base-installer/kernel/image "$(debconf-get base-installer/kernel/image | sed 's/-cloud//')"; \ + fi; \ - [ -d /sys/firmware/efi ] && debconf-set partman-auto/expert_recipe "$(debconf-get partman-auto/expert_recipe_efi)"; \ - [ -d /sys/firmware/efi ] || debconf-set partman-auto/expert_recipe "$(debconf-get partman-auto/expert_recipe_bios)"; \ - - debconf-set passwd/root-password-crypted "$(cat /configs/password-linux-sha512)"; \ + if [ -d /sys/firmware/efi ]; then \ + debconf-set partman-auto/expert_recipe "$(debconf-get partman-auto/expert_recipe_efi)"; \ + else \ + debconf-set partman-auto/expert_recipe "$(debconf-get partman-auto/expert_recipe_bios)"; \ + fi; \ true >/bin/os-prober # kali ssh 默认关闭 # 另一种方法处理 cloudcone # if [ "$link_grub_dir" = 1 ]; then mkdir /target/boot/grub2; echo 'chainloader (hd0)+1' >/target/boot/grub2/grub.cfg; fi; \ +# debian 9 tar 不支持 --strip-components d-i preseed/late_command string true; \ for str in $(grep -wo "extra_[^ ]*" /proc/cmdline | sed 's/^extra_//'); do eval "$str"; done; \ @@ -172,14 +283,29 @@ d-i preseed/late_command string true; \ in-target systemctl enable ssh; \ - echo "PermitRootLogin yes" >/target/etc/ssh/sshd_config.d/01-permitrootlogin.conf || \ - echo "PermitRootLogin yes" >>/target/etc/ssh/sshd_config; \ + if [ -s /configs/ssh_keys ]; then \ + (umask 077; mkdir -p /target/root/.ssh; cat /configs/ssh_keys >/target/root/.ssh/authorized_keys); \ + in-target passwd -d root; \ + else \ + echo "PermitRootLogin yes" >/target/etc/ssh/sshd_config.d/01-permitrootlogin.conf || \ + echo "PermitRootLogin yes" >>/target/etc/ssh/sshd_config; \ + fi; \ if [ -n "$ssh_port" ] && ! [ "$ssh_port" = 22 ]; then \ echo "Port $ssh_port" >/target/etc/ssh/sshd_config.d/01-change-ssh-port.conf || \ echo "Port $ssh_port" >>/target/etc/ssh/sshd_config; \ fi; \ + if [ -s /configs/frpc.toml ]; then \ + mkdir -p /target/usr/local/bin; \ + mkdir -p /target/usr/local/etc/frpc; \ + cp /usr/local/bin/frpc /target/usr/local/bin/; \ + cp /usr/local/etc/frpc/frpc.toml /target/usr/local/etc/frpc/; \ + chmod a+x /target/usr/local/bin/frpc; \ + cp /frpc.service /target/etc/systemd/system/; \ + in-target systemctl enable frpc; \ + fi; \ + cp /fix-eth-name.sh /target/; \ cp /fix-eth-name.service /target/etc/systemd/system/; \ in-target systemctl enable fix-eth-name diff --git a/fix-eth-name.service b/fix-eth-name.service index 1380c73..39a4cbd 100644 --- a/fix-eth-name.service +++ b/fix-eth-name.service @@ -22,6 +22,7 @@ ExecStart=/usr/bin/env bash /fix-eth-name.sh ExecStart=/usr/bin/env rm -f /fix-eth-name.sh ExecStart=/usr/bin/env rm -f /etc/systemd/system/fix-eth-name.service ExecStart=/usr/bin/env rm -f /etc/systemd/system/multi-user.target.wants/fix-eth-name.service +ExecStart=/usr/bin/env rm -f /lib/systemd/system-preset/01-fix-eth-name.preset ExecStart=/usr/bin/env rm -f /usr/lib/systemd/system-preset/01-fix-eth-name.preset [Install] diff --git a/fix-eth-name.sh b/fix-eth-name.sh index 09e3b36..959579b 100644 --- a/fix-eth-name.sh +++ b/fix-eth-name.sh @@ -7,6 +7,13 @@ set -eE # openeuler 需等待 udev 将网卡名从 eth0 改为 enp3s0 sleep 10 +# 不知道有没有用 +if command -v udevadm >/dev/null; then + # udevadm trigger + udevadm settle +elif command -v mdev >/dev/null; then + mdev -sf +fi # 本脚本在首次进入新系统后运行 # 将 trans 阶段生成的网络配置中的网卡名(eth0) 改为正确的网卡名,也适用于以下情况 @@ -41,20 +48,39 @@ retry() { # 用 systemd-analyze plot >a.svg 发现 sys-subsystem-net-devices-enp3s0.device 也是出现在 NetworkManager 之后 # 因此需要等待网卡出现 get_ethx_by_mac() { - mac=$(echo "$1" | to_lower) - retry 10 _get_ethx_by_mac "$mac" + retry 10 _get_ethx_by_mac "$@" } _get_ethx_by_mac() { + mac=$(echo "$1" | to_lower) + + flag=$2 + if [ -z "$flag" ]; then + flag=master + fi + if true; then - # 过滤 azure vf (带 master ethx) - ip -o link | grep -i "$mac" | grep -v master | awk '{print $2}' | cut -d: -f1 | grep . - return + if [ "$flag" = master ]; then + # master + # 过滤 azure vf (带 master ethx) + ip -o link | grep -i "$mac" | grep -v master | awk '{print $2}' | cut -d: -f1 | grep . + else + # slave + # 带 master ethx + ip -o link | grep -i "$mac" | grep -w master | awk '{print $2}' | cut -d: -f1 | grep . + fi else for i in $(cd /sys/class/net && echo *); do if [ "$(cat "/sys/class/net/$i/address")" = "$mac" ]; then - echo "$i" - return + if [ $(($(cat "/sys/class/net/$i/flags") & 0x800)) -ne 0 ]; then + fact_flag=slave + else + fact_flag=master + fi + if [ "$flag" = "$fact_flag" ]; then + echo "$i" + return + fi fi done return 1 @@ -131,6 +157,23 @@ fix_network_manager() { # 更改文件名 mv "$file" "$proper_file" + + # NM 不会自动忽略 Azure 的 slave 网卡,需手动设置 + # azure 文档中的方法不够通用,只适合 azure + # https://learn.microsoft.com/zh-cn/azure/virtual-network/accelerated-networking-overview + + # 我们采用红帽的方法 + # https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/configuring-networkmanager-to-ignore-certain-devices_configuring-and-managing-networking + if slave_ethx=$(get_ethx_by_mac "$mac" slave); then + cat >"/etc/NetworkManager/conf.d/99-$slave_ethx-unmanaged.conf" <&1 | grep Location: + # Location: https://github.com/fatedier/frp/releases/tag/v0.62.0 + # Location: https://github.com/fatedier/frp/releases/tag/v0.62.0 [following] # 原版 wget 多了这行 + + wget --spider -S https://github.com/fatedier/frp/releases/latest 2>&1 | + grep -m1 '^ Location:' | sed 's,.*/tag/v,,' + fi + fi + ) + + if [ -z "$version" ]; then + echo 'cannot find version' + return 1 + fi + + suffix=$( + case "$os_type" in + linux) echo tar.gz ;; + windows) echo zip ;; + esac + ) + + mirror=$( + # nju 没有 win7 用的旧版 + # github 不支持 ipv6 + # daocloud 加速不支持 ipv6 + # jsdelivr 不支持 github releases 文件 + if is_ipv6_only; then + if is_need_old_version; then + echo 'NOT_SUPPORT' + return 1 + else + echo https://mirrors.nju.edu.cn/github-release/fatedier/frp + fi + else + if is_in_china; then + if is_need_old_version; then + echo https://files.m.daocloud.io/github.com/fatedier/frp/releases/download + else + echo https://mirrors.nju.edu.cn/github-release/fatedier/frp + fi + else + echo https://github.com/fatedier/frp/releases/download + fi + fi + ) + + arch=$( + case "$(uname -m)" in + x86_64) echo amd64 ;; + aarch64) echo arm64 ;; + esac + ) + + filename=frp_${version}_${os_type}_${arch}.$suffix + + echo "${mirror}/v${version}/${filename}" +} + +get_frpc_url "$@" diff --git a/initrd-network.sh b/initrd-network.sh index f4b5a0e..e3b21d7 100644 --- a/initrd-network.sh +++ b/initrd-network.sh @@ -1,6 +1,6 @@ #!/bin/ash # shellcheck shell=dash -# alpine / debian initrd 共用此脚本 +# alpine/debian initrd 共用此脚本 # accept_ra 接收 RA + 自动配置网关 # autoconf 自动配置地址,依赖 accept_ra @@ -42,9 +42,9 @@ get_ethx() { # 2: eth0: mtu 1500 qdisc mq state UP qlen 1000\ link/ether 60:45:bd:21:8a:51 brd ff:ff:ff:ff:ff:ff # 3: eth1: mtu 1500 qdisc mq master eth0 state UP qlen 1000\ link/ether 60:45:bd:21:8a:51 brd ff:ff:ff if false; then - ip -o link | grep -i "$mac_addr" | grep -v master | awk '{print $2}' | cut -d: -f1 + ip -o link | grep -i "$mac_addr" | grep -v master | awk '{print $2}' | cut -d: -f1 | grep . else - ip -o link | grep -i "$mac_addr" | grep -v master | cut -d' ' -f2 | cut -d: -f1 + ip -o link | grep -i "$mac_addr" | grep -v master | cut -d' ' -f2 | cut -d: -f1 | grep . fi } @@ -240,11 +240,17 @@ test_connect() { test_internet() { for i in $(seq 5); do echo "Testing Internet Connection. Test $i... " - if is_need_test_ipv4 && test_connect "$(get_first_ipv4_addr | remove_netmask)" "$ipv4_dns1" >/dev/null 2>&1; then + if is_need_test_ipv4 && + current_ipv4_addr="$(get_first_ipv4_addr | remove_netmask)" && + { test_connect "$current_ipv4_addr" "$ipv4_dns1" || + test_connect "$current_ipv4_addr" "$ipv4_dns2"; } >/dev/null 2>&1; then echo "IPv4 has internet." ipv4_has_internet=true fi - if is_need_test_ipv6 && test_connect "$(get_first_ipv6_addr | remove_netmask)" "$ipv6_dns1" >/dev/null 2>&1; then + if is_need_test_ipv6 && + current_ipv6_addr="$(get_first_ipv6_addr | remove_netmask)" && + { test_connect "$current_ipv6_addr" "$ipv6_dns1" || + test_connect "$current_ipv6_addr" "$ipv6_dns2"; } >/dev/null 2>&1; then echo "IPv6 has internet." ipv6_has_internet=true fi @@ -258,8 +264,11 @@ test_internet() { flush_ipv4_config() { ip -4 addr flush scope global dev "$ethx" ip -4 route flush dev "$ethx" + # DHCP 获取的 IP 不是重装前的 IP 时,一并删除 DHCP 获取的 DNS,以防 DNS 无效 + sed -i "/\./d" /etc/resolv.conf } +should_disable_dhcpv4=false should_disable_accept_ra=false should_disable_autoconf=false @@ -272,9 +281,17 @@ flush_ipv6_config() { fi ip -6 addr flush scope global dev "$ethx" ip -6 route flush dev "$ethx" + # DHCP 获取的 IP 不是重装前的 IP 时,一并删除 DHCP 获取的 DNS,以防 DNS 无效 + sed -i "/:/d" /etc/resolv.conf } -ethx=$(get_ethx) +for i in $(seq 20); do + if ethx=$(get_ethx); then + break + fi + sleep 1 +done + if [ -z "$ethx" ]; then echo "Not found network card: $mac_addr" exit @@ -282,6 +299,9 @@ fi echo "Configuring $ethx ($mac_addr)..." +# 不开启 lo 则 frp 无法连接 127.0.0.1 22 +ip link set dev lo up + # 开启 ethx ip link set dev "$ethx" up sleep 1 @@ -325,8 +345,9 @@ EOF db_progress INFO netcfg/link_detect_progress else # alpine - # h3c 移动云电脑使用 udhcpc 会重复提示 sending select,无法获得 ipv6,因此使用 dhcpcd - method=dhcpcd + # h3c 移动云电脑使用 udhcpc 会重复提示 sending select,无法获得 ipv6 + # dhcpcd 会配置租约时间,过期会移除 IP,但我们的没有在后台运行 dhcpcd ,因此用 udhcpc + method=udhcpc case "$method" in udhcpc) @@ -355,6 +376,10 @@ else sleep $DNS_FILE_TIMEOUT # 需要等待写入 dns dhcpcd -x "$ethx" # 终止 fi + # autoconf 和 accept_ra 会被 dhcpcd 自动关闭,因此需要重新打开 + # 如果没重新打开,重新运行 dhcpcd 命令依然可以正常生成 slaac 地址和路由 + sysctl -w "net.ipv6.conf.$ethx.autoconf=1" + sysctl -w "net.ipv6.conf.$ethx.accept_ra=1" ;; esac fi @@ -373,12 +398,7 @@ done # 由于还没设置静态ip,所以有条目表示有动态地址 is_have_ipv4_addr && dhcpv4=true || dhcpv4=false is_have_ipv6_addr && dhcpv6_or_slaac=true || dhcpv6_or_slaac=false -if is_have_ipv6_gateway; then - ra_has_gateway=true - ipv6_gateway_from_ra=$(get_ipv6_gateway) -else - ra_has_gateway=false -fi +is_have_ipv6_gateway && ra_has_gateway=true || ra_has_gateway=false # 如果自动获取的 IP 不是重装前的,则改成静态,使用之前的 IP # 只比较 IP,不比较掩码/网关,因为 @@ -387,13 +407,12 @@ fi if $dhcpv4 && [ -n "$ipv4_addr" ] && [ -n "$ipv4_gateway" ] && ! [ "$(echo "$ipv4_addr" | cut -d/ -f1)" = "$(get_first_ipv4_addr | cut -d/ -f1)" ]; then echo "IPv4 address obtained from DHCP is different from old system." - dhcpv4=false + should_disable_dhcpv4=true flush_ipv4_config fi if $dhcpv6_or_slaac && [ -n "$ipv6_addr" ] && [ -n "$ipv6_gateway" ] && ! [ "$(echo "$ipv6_addr" | cut -d/ -f1)" = "$(get_first_ipv6_addr | cut -d/ -f1)" ]; then echo "IPv6 address obtained from SLAAC/DHCPv6 is different from old system." - dhcpv6_or_slaac=false should_disable_accept_ra=true should_disable_autoconf=true flush_ipv6_config @@ -413,18 +432,19 @@ test_internet # IP 不同的情况在前面已经改成静态了 if ! $ipv4_has_internet && $dhcpv4 && [ -n "$ipv4_addr" ] && [ -n "$ipv4_gateway" ] && - ! { [ "$ipv4_addr" = "$(get_first_ipv4_addr)" ] || [ "$ipv4_gateway" = "$(get_first_ipv4_gateway)" ]; }; then + ! { [ "$ipv4_addr" = "$(get_first_ipv4_addr)" ] && [ "$ipv4_gateway" = "$(get_first_ipv4_gateway)" ]; }; then echo "IPv4 netmask/gateway obtained from DHCP is different from old system." - dhcpv4=false + should_disable_dhcpv4=true flush_ipv4_config add_missing_ipv4_config test_internet fi +# 有可能是静态 IPv6 但能从 RA 获取到网关,因此加上 || $ra_has_gateway if ! $ipv6_has_internet && - $dhcpv6_or_slaac && [ -n "$ipv6_addr" ] && [ -n "$ipv6_gateway" ] && - ! { [ "$ipv6_addr" = "$(get_first_ipv6_addr)" ] || [ "$ipv6_gateway" = "$(get_first_ipv6_gateway)" ]; }; then + { $dhcpv6_or_slaac || $ra_has_gateway; } && + [ -n "$ipv6_addr" ] && [ -n "$ipv6_gateway" ] && + ! { [ "$ipv6_addr" = "$(get_first_ipv6_addr)" ] && [ "$ipv6_gateway" = "$(get_first_ipv6_gateway)" ]; }; then echo "IPv6 netmask/gateway obtained from SLAAC/DHCPv6 is different from old system." - dhcpv6_or_slaac=false should_disable_accept_ra=true should_disable_autoconf=true flush_ipv6_config @@ -432,31 +452,41 @@ if ! $ipv6_has_internet && test_internet fi -# 如果是静态地址(包括动态无法上网而改成静态的),但是 RA 有网关且和正确的网关不同,要关闭 RA,避免自动设置网关 -# TODO: 测试 RA 给的网关和静态设置的网关的优先级 -if $ipv6_has_internet && ! $dhcpv6_or_slaac && $ra_has_gateway && - ! [ "$(get_first_ipv6_gateway)" = "$ipv6_gateway_from_ra" ]; then - echo "Ignore IPv6 gateway from RA." - should_disable_accept_ra=true -fi - # 要删除不联网协议的ip,因为 # 1 甲骨文云管理面板添加ipv6地址然后取消 # 依然会分配ipv6地址,但ipv6没网络 # 此时alpine只会用ipv6下载apk,而不用会ipv4下载 -# 2 有ipv4地址但没有ipv4网关的情况(vultr),aria2会用ipv4下载 -if $ipv4_has_internet && ! $ipv6_has_internet; then - flush_ipv6_config -elif ! $ipv4_has_internet && $ipv6_has_internet; then +# 2 有ipv4地址但没有ipv4网关的情况(vultr $2.5 ipv6 only),aria2会用ipv4下载 + +# 假设 ipv4 ipv6 在不同网卡,ipv4 能上网但 ipv6 不能上网,这时也要删除 ipv6 +# 不能用 ipv4_has_internet && ! ipv6_has_internet 判断,因为它判断的是同一个网卡 +if ! $ipv4_has_internet; then + if $dhcpv4; then + should_disable_dhcpv4=true + fi flush_ipv4_config fi +if ! $ipv6_has_internet; then + # 防止删除 IPv6 后再次通过 SLAAC 获得 + # 不用判断 || $ra_has_gateway ,因为没有 IPv6 地址但有 IPv6 网关时,不会出现下载问题 + if $dhcpv6_or_slaac; then + should_disable_accept_ra=true + should_disable_autoconf=true + fi + flush_ipv6_config +fi # 如果联网了,但没获取到默认 DNS,则添加我们的 DNS -if $ipv4_has_internet && ! { [ -e /etc/resolv.conf ] && is_have_ipv4_dns; }; then + +# 有一种情况是,多网卡,且能上网的网卡先完成了这个脚本,不能上网的网卡后完成 +# 无法上网的网卡通过 flush_ipv4_config 删除了不能上网的 IP 和 dns +# (原计划是删除无法上网的网卡 dhcp4 获取的 dns,但实际上无法区分) +# 因此这里直接添加 dns,不判断是否联网 +if ! is_have_ipv4_dns; then echo "nameserver $ipv4_dns1" >>/etc/resolv.conf echo "nameserver $ipv4_dns2" >>/etc/resolv.conf fi -if $ipv6_has_internet && ! { [ -e /etc/resolv.conf ] && is_have_ipv6_dns; }; then +if ! is_have_ipv6_dns; then echo "nameserver $ipv6_dns1" >>/etc/resolv.conf echo "nameserver $ipv6_dns2" >>/etc/resolv.conf fi @@ -466,6 +496,7 @@ netconf="/dev/netconf/$ethx" mkdir -p "$netconf" $dhcpv4 && echo 1 >"$netconf/dhcpv4" || echo 0 >"$netconf/dhcpv4" $dhcpv6_or_slaac && echo 1 >"$netconf/dhcpv6_or_slaac" || echo 0 >"$netconf/dhcpv6_or_slaac" +$should_disable_dhcpv4 && echo 1 >"$netconf/should_disable_dhcpv4" || echo 0 >"$netconf/should_disable_dhcpv4" $should_disable_accept_ra && echo 1 >"$netconf/should_disable_accept_ra" || echo 0 >"$netconf/should_disable_accept_ra" $should_disable_autoconf && echo 1 >"$netconf/should_disable_autoconf" || echo 0 >"$netconf/should_disable_autoconf" $is_in_china && echo 1 >"$netconf/is_in_china" || echo 0 >"$netconf/is_in_china" diff --git a/logviewer.html b/logviewer.html index 05c1cea..edd6065 100644 --- a/logviewer.html +++ b/logviewer.html @@ -58,7 +58,7 @@ diff --git a/reinstall.bat b/reinstall.bat index af00357..aa69843 100644 --- a/reinstall.bat +++ b/reinstall.bat @@ -3,7 +3,7 @@ mode con cp select=437 >nul setlocal EnableDelayedExpansion set confhome=https://raw.githubusercontent.com/bin456789/reinstall/main -set confhome_cn=https://gitlab.com/bin456789/reinstall/-/raw/main +set confhome_cn=https://cnb.cool/bin456789/reinstall/-/git/raw/main rem set confhome_cn=https://www.ghproxy.cc/https://raw.githubusercontent.com/bin456789/reinstall/main set pkgs=curl,cpio,p7zip,dos2unix,jq,xz,gzip,zstd,openssl,bind-utils,libiconv,binutils @@ -30,15 +30,25 @@ if errorlevel 1 ( rem 有时 %tmp% 带会话 id,且文件夹不存在 rem https://learn.microsoft.com/troubleshoot/windows-server/shell-experience/temp-folder-with-logon-session-id-deleted -if not exist %tmp% ( - md %tmp% +rem if not exist %tmp% ( +rem md %tmp% +rem ) + +rem 下载 geoip +if not exist geoip ( + rem www.cloudflare.com/dash.cloudflare.com 国内访问的是美国服务器,而且部分地区被墙 + call :download http://www.qualcomm.cn/cdn-cgi/trace %~dp0geoip || goto :download_failed +) + +rem 判断是否有 loc= +findstr /c:"loc=" geoip >nul +if errorlevel 1 ( + echo Invalid geoip file + del geoip + exit /b 1 ) rem 检查是否国内 -if not exist geoip ( - rem www.cloudflare.com/dash.cloudflare.com 国内访问的是美国服务器,而且部分地区被墙 - call :download http://www.visa.cn/cdn-cgi/trace %~dp0geoip || goto :download_failed -) findstr /c:"loc=CN" geoip >nul if not errorlevel 1 ( rem mirrors.tuna.tsinghua.edu.cn 会强制跳转 https @@ -60,37 +70,49 @@ if not errorlevel 1 ( call :check_cygwin_installed || ( rem win10 arm 支持运行 x86 软件 rem win11 arm 支持运行 x86 和 x86_64 软件 - rem wmic os get osarchitecture 显示中文 - rem wmic ComputerSystem get SystemType 显示英文 - rem SystemType rem windows 11 24h2 没有 wmic + rem wmic os get osarchitecture 显示中文,即使设置了 mode con cp select=437 + rem wmic ComputerSystem get SystemType 显示英文 + rem for /f "tokens=*" %%a in ('wmic ComputerSystem get SystemType ^| find /i "based"') do ( + rem set "SystemType=%%a" + rem ) + rem 有的系统精简了 powershell - where wmic >nul 2>&1 - if not errorlevel 1 ( - for /f "tokens=*" %%a in ('wmic ComputerSystem get SystemType ^| find /i "based"') do ( - set "SystemType=%%a" - ) - ) else ( - for /f "delims=" %%a in ('powershell -NoLogo -NoProfile -NonInteractive -Command "(Get-WmiObject win32_computersystem).SystemType"') do ( - set "SystemType=%%a" - ) + rem for /f "delims=" %%a in ('powershell -NoLogo -NoProfile -NonInteractive -Command "(Get-WmiObject win32_computersystem).SystemType"') do ( + rem set "SystemType=%%a" + rem ) + + rem SystemArch + for /f "tokens=3" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PROCESSOR_ARCHITECTURE') do ( + set SystemArch=%%a ) + rem 也可以用 PROCESSOR_ARCHITEW6432 和 PROCESSOR_ARCHITECTURE 判断 + rem ARM64 win11 PROCESSOR_ARCHITEW6432 PROCESSOR_ARCHITECTURE + rem 原生cmd 未定义 ARM64 + rem 32位cmd ARM64 x86 + + rem if defined PROCESSOR_ARCHITEW6432 ( + rem set "SystemArch=%PROCESSOR_ARCHITEW6432%" + rem ) else ( + rem set "SystemArch=%PROCESSOR_ARCHITECTURE%" + rem ) + rem BuildNumber for /f "tokens=3" %%a in ('reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v CurrentBuildNumber') do ( - set /a BuildNumber=%%a + set /a BuildNumber=%%a ) set CygwinEOL=1 - echo !SystemType! | find "ARM" > nul + echo !SystemArch! | find "ARM" > nul if not errorlevel 1 ( if !BuildNumber! GEQ 22000 ( set CygwinEOL=0 ) ) else ( - echo !SystemType! | find "x64" > nul + echo !SystemArch! | find "AMD64" > nul if not errorlevel 1 ( if !BuildNumber! GEQ 9600 ( set CygwinEOL=0 @@ -109,11 +131,26 @@ call :check_cygwin_installed || ( set dir=/sourceware/cygwin ) + rem daocloud 加速有 90 天缓存,且不支持 IPv6 + rem https://github.com/DaoCloud/public-binary-files-mirror + rem 无法用查询字符串强制刷新缓存 + rem https://files.m.daocloud.io/www.cloudflare.com/cdn-cgi/trace?a=1 + rem https://files.m.daocloud.io/www.cloudflare.com/cdn-cgi/trace?b=2 + rem 也就无法用 https://www.cygwin.com/setup-x86_64.exe?xxx=20250101 强制每天刷新缓存 + rem 下载 Cygwin if not exist setup-!CygwinArch!.exe ( call :download http://www.cygwin.com/setup-!CygwinArch!.exe %~dp0setup-!CygwinArch!.exe || goto :download_failed ) + rem 少于 1M 视为无效 + rem 有的 IP 被官网拉黑,无法下载 exe,下载得到 html + for %%A in (setup-!CygwinArch!.exe) do if %%~zA LSS 1048576 ( + echo Invalid Cgywin installer + del setup-!CygwinArch!.exe + exit /b 1 + ) + rem 安装 Cygwin set site=!mirror!!dir! start /wait setup-!CygwinArch!.exe ^ @@ -126,11 +163,8 @@ call :check_cygwin_installed || ( --packages %pkgs% rem 检查 Cygwin 是否成功安装 - if errorlevel 1 ( - goto :install_cygwin_failed - ) else ( - call :check_cygwin_installed || goto :install_cygwin_failed - ) + if errorlevel 1 goto :install_cygwin_failed + call :check_cygwin_installed || goto :install_cygwin_failed ) rem 在c盘根目录下执行 cygpath -ua . 会得到 /cygdrive/c,因此末尾要有 / @@ -159,23 +193,28 @@ rem 或者添加 export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/ %SystemDrive%\cygwin\bin\bash %thisdir%reinstall.sh %* exit /b - - - - -:download rem bits 要求有 Content-Length 才能下载 +rem cloudflare 的 cdn-cgi/trace 没有 Content-Length rem 据说如果网络设为“按流量计费” bits 也无法下载 rem https://learn.microsoft.com/en-us/windows/win32/bits/http-requirements-for-bits-downloads +rem bitsadmin /transfer "%~3" /priority foreground %~1 %~2 + +:download rem certutil 会被 windows Defender 报毒 rem windows server 2019 要用第二条 certutil 命令 -echo Download: %~1 %~2 +echo Downloading: %~1 %~2 del /q "%~2" 2>nul if exist "%~2" (echo Cannot delete %~2 & exit /b 1) -if not exist "%~2" certutil -urlcache -f -split "%~1" "%~2" >nul -if not exist "%~2" certutil -urlcache -split "%~1" "%~2" >nul -if not exist "%~2" exit /b 1 -exit /b + +certutil -urlcache -f -split "%~1" "%~2" >nul +if not errorlevel 1 if exist "%~2" exit /b 0 + +certutil -urlcache -split "%~1" "%~2" >nul +if not errorlevel 1 if exist "%~2" exit /b 0 + +rem 下载失败时删除文件,防止下载了一部分导致下次运行时跳过了下载 +del /q "%~2" 2>nul +exit /b 1 :download_with_curl rem 加 --insecure 防止以下错误 diff --git a/reinstall.sh b/reinstall.sh index 0e8ac69..5f3243a 100644 --- a/reinstall.sh +++ b/reinstall.sh @@ -4,14 +4,11 @@ set -eE confhome=https://raw.githubusercontent.com/bin456789/reinstall/main -confhome_cn=https://gitlab.com/bin456789/reinstall/-/raw/main +confhome_cn=https://cnb.cool/bin456789/reinstall/-/git/raw/main # confhome_cn=https://www.ghproxy.cc/https://raw.githubusercontent.com/bin456789/reinstall/main -# 默认密码 -DEFAULT_PASSWORD=123@@@ - # 用于判断 reinstall.sh 和 trans.sh 是否兼容 -SCRIPT_VERSION=4BACD833-A585-23BA-6CBB-9AA4E08E0003 +SCRIPT_VERSION=4BACD833-A585-23BA-6CBB-9AA4E08E0004 # 记录要用到的 windows 程序,运行时输出删除 \r WINDOWS_EXES='cmd powershell wmic reg diskpart netsh bcdedit mountvol' @@ -25,6 +22,22 @@ export LC_ALL=C # 不要漏了最后的 $PATH,否则会找不到 windows 系统程序例如 diskpart export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH +# 如果不是 bash 的话,继续执行会有语法错误,因此在这里判断是否 bash +if [ -z "$BASH" ]; then + if [ -f /etc/alpine-release ]; then + if ! apk add bash; then + echo "Error while install bash." >&2 + exit 1 + fi + fi + if command -v bash >/dev/null; then + exec bash "$0" "$@" + else + echo "Please run this script with bash." >&2 + exit 1 + fi +fi + # 记录日志,过滤含有 password 的行 exec > >(tee >(grep -iv password >>/reinstall.log)) 2>&1 THIS_SCRIPT=$(readlink -f "$0") @@ -47,32 +60,39 @@ usage_and_exit() { cat <&2 + echo_color_text '\e[32m' "$msg" >&2 } warn() { - echo_color_text '\e[33m' "Warning: $*" >&2 + local msg + if [ "$1" = false ]; then + shift + msg=$* + else + msg="Warning: $*" + fi + echo_color_text '\e[33m' "$msg" >&2 } error() { @@ -113,12 +139,35 @@ error_and_exit() { exit 1 } +show_dd_password_tips() { + warn false " +This password is only used for SSH access to view logs during the installation. +Password of the image will NOT modify. + +密码仅用于安装过程中通过 SSH 查看日志。 +镜像的密码不会被修改。 +" +} + +show_url_in_args() { + while [ $# -gt 0 ]; do + case "$1" in + [Hh][Tt][Tt][Pp][Ss]://* | [Hh][Tt][Tt][Pp]://* | [Mm][Aa][Gg][Nn][Ee][Tt]:*) echo "$1" ;; + esac + shift + done +} + curl() { + is_have_cmd curl || install_pkg curl + + # 显示 url + show_url_in_args "$@" >&2 + # 添加 -f, --fail,不然 404 退出码也为0 # 32位 cygwin 已停止更新,证书可能有问题,先添加 --insecure # centos 7 curl 不支持 --retry-connrefused --retry-all-errors # 因此手动 retry - grep -o 'http[^ ]*' <<<"$@" >&2 for i in $(seq 5); do if command curl --insecure --connect-timeout 10 -f "$@"; then return @@ -145,16 +194,16 @@ is_in_china() { if [ -z "$_loc" ]; then # www.cloudflare.com/dash.cloudflare.com 国内访问的是美国服务器,而且部分地区被墙 - # 备用 www.bose.cn - # 备用 www.qualcomm.cn + # 没有ipv6 www.visa.cn + # 没有ipv6 www.bose.cn + # 没有ipv6 www.garmin.com.cn # 备用 www.prologis.cn - # 备用 www.garmin.com.cn # 备用 www.autodesk.com.cn # 备用 www.keysight.com.cn - _loc=$(curl -L http://www.visa.cn/cdn-cgi/trace | grep '^loc=' | cut -d= -f2) - if [ -z "$_loc" ]; then + if ! _loc=$(curl -L http://www.qualcomm.cn/cdn-cgi/trace | grep '^loc=' | cut -d= -f2 | grep .); then error_and_exit "Can not get location." fi + echo "Location: $_loc" >&2 fi [ "$_loc" = CN ] } @@ -196,13 +245,25 @@ get_os_part() { awk '($2=="/") { print $1 }' /proc/mounts } +umount_all() { + # windows defender 打开时,cygwin 运行 mount 很慢,但 cat /proc/mounts 很快 + if mount_lists=$(mount | grep -w "on $1" | awk '{print $3}' | grep .); then + # alpine 没有 -R + if umount --help 2>&1 | grep -wq -- '-R'; then + umount -R "$1" + else + echo "$mount_lists" | tac | xargs -n1 umount + fi + fi +} + cp_to_btrfs_root() { mount_dir=$tmp/reinstall-btrfs-root if ! grep -q $mount_dir /proc/mounts; then mkdir -p $mount_dir mount "$(get_os_part)" $mount_dir -t btrfs -o subvol=/ fi - cp -rf "$@" $tmp/reinstall-btrfs-root + cp -rf "$@" "$mount_dir" } is_host_has_ipv4_and_ipv6() { @@ -253,11 +314,21 @@ get_function_content() { } insert_into_file() { - file=$1 - location=$2 - regex_to_find=$3 + local file=$1 + local location=$2 + local regex_to_find=$3 + shift 3 - line_num=$(grep -E -n "$regex_to_find" "$file" | cut -d: -f1) + if ! [ -f "$file" ]; then + error_and_exit "File not found: $file" + fi + + # 默认 grep -E + if [ $# -eq 0 ]; then + set -- -E + fi + + line_num=$(grep "$@" -n "$regex_to_find" "$file" | cut -d: -f1) found_count=$(echo "$line_num" | wc -l) if [ ! "$found_count" -eq 1 ]; then @@ -339,7 +410,7 @@ test_url_real() { real_type=$(file_enhanced $tmp_file) echo "File type: $real_type" - # debian 9 ubuntu 16.04 可能会将 iso 识别成 raw + # debian 9 ubuntu 16.04-20.04 可能会将 iso 识别成 raw for type in $expect_types $([ "$expect_types" = iso ] && echo raw); do if [[ ."$real_type" = *."$type" ]]; then # 如果要设置变量 @@ -484,15 +555,14 @@ run_with_del_cr() { } run_with_del_cr_template() { - # 调用链:wmic() -> run_with_del_cr(wmic) -> _wmic() -> command wmic - if command -v _$exe >/dev/null; then + if get_function _$exe >/dev/null; then run_with_del_cr _$exe "$@" else run_with_del_cr command $exe "$@" fi } -_wmic() { +wmic() { if is_have_cmd wmic; then # 如果参数没有 GET,添加 GET,防止以下报错 # wmic memorychip /format:list @@ -623,7 +693,7 @@ is_absolute_path() { [[ "$1" = /* ]] } -assert_cpu_supports_x86_64_v3() { +is_cpu_supports_x86_64_v3() { # 用 ld.so/cpuid/coreinfo.exe 更准确 # centos 7 /usr/lib64/ld-linux-x86-64.so.2 没有 --help # alpine gcompat /lib/ld-linux-x86-64.so.2 没有 --help @@ -640,11 +710,17 @@ assert_cpu_supports_x86_64_v3() { for flag in $need_flags; do if ! grep -qw $flag <<<"$had_flags"; then - error_and_exit "Could not install $distro $releasever because the CPU does not support x86-64-v3." + return 1 fi done } +assert_cpu_supports_x86_64_v3() { + if ! is_cpu_supports_x86_64_v3; then + error_and_exit "Could not install $distro $releasever because the CPU does not support x86-64-v3." + fi +} + # sr-latn-rs 到 sr-latn en_us() { echo "$lang" | awk -F- '{print $1"-"$2}' @@ -781,30 +857,22 @@ is_have_arm_version() { case "$version" in 10) case "$edition" in - pro | education | enterprise | 'pro education' | 'pro for workstations') return ;; + home | 'home single language' | pro | education | enterprise | 'pro education' | 'pro for workstations') return ;; 'iot enterprise') return ;; + # arm ltsc 只有 2021 有 iso 'enterprise ltsc 2021' | 'iot enterprise ltsc 2021') return ;; esac ;; - 11) - case "$edition" in - pro | education | enterprise | 'pro education' | 'pro for workstations') return ;; - 'iot enterprise' | 'iot enterprise subscription') return ;; - 'enterprise ltsc 2024' | 'iot enterprise ltsc 2024' | 'iot enterprise ltsc 2024 subscription') return ;; - esac - ;; + 11) return ;; esac return 1 } find_windows_iso() { parse_windows_image_name || error_and_exit "--image-name wrong: $image_name" - if ! [ "$version" = 8.1 ] && [ -z "$edition" ]; then + if ! { [ "$version" = 8 ] || [ "$version" = 8.1 ]; } && [ -z "$edition" ]; then error_and_exit "Edition is not set." fi - if [ "$basearch" = 'aarch64' ] && ! is_have_arm_version; then - error_and_exit "No ARM iso for this Windows Version." - fi if [ -z "$lang" ]; then lang=en-us @@ -818,11 +886,10 @@ find_windows_iso() { aarch64) arch_win=arm64 ;; esac - get_windows_iso_links get_windows_iso_link } -get_windows_iso_links() { +get_windows_iso_link() { get_label_msdn() { if [ -n "$server" ]; then case "$version" in @@ -834,6 +901,7 @@ get_windows_iso_links() { serverdatacenter | serverdatacentercore) echo _ ;; esac ;; + # massgrave 不提供 2012 下载 '2012 r2' | \ 2016 | 2019 | 2022 | 2025) case "$edition" in @@ -851,33 +919,42 @@ get_windows_iso_links() { x86) echo _ ;; esac ;; - homebasic | homepremium | business | ultimate) echo _ ;; - enterprise) echo enterprise ;; + homebasic | homepremium | ultimate) echo _ ;; + business | enterprise) echo "$edition" ;; esac ;; 7) case "$edition" in starter) case "$arch_win" in - x86) echo ultimate ;; + x86) echo starter ;; esac ;; - professional) echo professional ;; - homebasic | homepremium | ultimate) echo ultimate ;; - enterprise) echo enterprise ;; + homebasic) + case "$arch_win" in + x86) echo "home basic" ;; + esac + ;; + homepremium) echo "home premium" ;; + professional | enterprise | ultimate) echo "$edition" ;; esac ;; - 8.1) + 8 | 8.1) case "$edition" in - '') ;; # massgrave 不提供 windows 8.1 家庭版链接 - pro) echo pro ;; - enterprise) echo enterprise ;; + '') echo _ ;; # windows 8.x core + pro | enterprise) echo "$edition" ;; esac ;; 10) case "$edition" in home | 'home single language') echo consumer ;; - pro | education | enterprise | 'pro education' | 'pro for workstations') echo business ;; + pro | enterprise) echo business ;; + education | 'pro education' | 'pro for workstations') + case "$arch_win" in + arm64) echo consumer ;; + x64) echo business ;; # iso 更小 + esac + ;; # iot 'iot enterprise') echo 'iot enterprise' ;; # iot ltsc @@ -894,13 +971,23 @@ get_windows_iso_links() { esac ;; 11) + # arm business iso 都没有 education, pro education, pro for workstations + # 即使它的名字包含 EDU + # SW_DVD9_Win_Pro_10_22H2.31_Arm64_English_Pro_Ent_EDU_N_MLF_X24-05074.ISO + # en-us_windows_11_business_editions_version_25h2_arm64_dvd_8afc9b39.iso case "$edition" in home | 'home single language') echo consumer ;; - pro | education | enterprise | 'pro education' | 'pro for workstations') echo business ;; + pro | enterprise) echo business ;; + education | 'pro education' | 'pro for workstations') + case "$arch_win" in + arm64) echo consumer ;; + x64) echo business ;; # iso 更小 + esac + ;; # iot 'iot enterprise' | 'iot enterprise subscription') echo 'iot enterprise' ;; # iot ltsc - 'iot enterprise ltsc 2024' | 'iot enterprise ltsc 2024 subscription') echo 'iot enterprise ltsc 2024' ;; + 'iot enterprise ltsc 2024' | 'iot enterprise subscription ltsc 2024') echo 'iot enterprise ltsc 2024' ;; # ltsc 'enterprise ltsc 2024') # arm64 的 enterprise ltsc 2024 要下载 iot enterprise ltsc 2024 iso @@ -925,6 +1012,21 @@ get_windows_iso_links() { esac } + # msdl 没有每月发布的 iso + # msdl 只有 consumer 版本,因此里面的 pro 版本不是 vl 版 + # 8.1 没有每月发布的 iso,因此优先从 msdl 下载 + # win10 22h2 arm 有每月发布的 iso,因此不从 msdl 下载 + # win10/11 ltsc 没有每月发布的 iso,但是 msdl 没有 ltsc 版本 + get_label_msdl() { + case "$version" in + 8.1) + case "$edition" in + '' | pro) echo _ ;; + esac + ;; + esac + } + get_page() { if [ "$arch_win" = arm64 ]; then echo arm @@ -934,7 +1036,7 @@ get_windows_iso_links() { echo server else case "$version" in - vista | 7 | 8.1 | 10 | 11) + vista | 7 | 8 | 8.1 | 10 | 11) echo "$version" ;; esac @@ -947,34 +1049,57 @@ get_windows_iso_links() { # 部分 bash 不支持 $() 里面嵌套case,所以定义成函数 label_msdn=$(get_label_msdn) + label_msdl=$(get_label_msdl) label_vlsc=$(get_label_vlsc) page=$(get_page) - page_url=https://massgrave.dev/windows_${page}_links + if [ "$page" = vista ]; then + page_url=https://massgrave.dev/windows_vista__links + elif [ "$page" = server ]; then + page_url=https://massgrave.dev/windows-server-links + else + page_url=https://massgrave.dev/windows_${page}_links + fi info "Find windows iso" echo "Version: $version" echo "Edition: $edition" echo "Label msdn: $label_msdn" + echo "Label msdl: $label_msdl" echo "Label vlsc: $label_vlsc" echo "List: $page_url" echo - if [ -z "$page" ] || { [ -z "$label_msdn" ] && [ -z "$label_vlsc" ]; }; then - error_and_exit "Not support find this iso. Check if --image-name is wrong. If not, set --iso manually." + # 先判断是否能自动查找该版本 + # 再判断是否支持 arm + # 这样可以在输入错误 Edition 时例如 windows 11 enterprise ltsc 2021 + # 显示名称错误,而不是显示该版本不支持 arm + + if [ -z "$page" ] || { [ -z "$label_msdn" ] && [ -z "$label_msdl" ] && [ -z "$label_vlsc" ]; }; then + error_and_exit "Not support find this iso. Check if --image-name is wrong. Or set --iso manually." fi - curl -L "$page_url" | grep -ioP 'https://.*?.(iso|img)' >$tmp/win.list + if [ "$basearch" = aarch64 ] && ! is_have_arm_version; then + error_and_exit "No ARM iso for this Windows Version or Edition." + fi - # 如果不是 ltsc ,应该先去除 ltsc 链接,否则最终链接有 ltsc 的 - # 例如查找 windows 10 iot enterprise,会得到 - # en-us_windows_10_iot_enterprise_ltsc_2021_arm64_dvd_e8d4fc46.iso - # en-us_windows_10_iot_enterprise_version_22h2_arm64_dvd_39566b6b.iso - # sed -Ei 和 sed -iE 是不同的 - if is_ltsc; then - sed -Ei '/ltsc|ltsb/!d' $tmp/win.list + if [ -n "$label_msdl" ]; then + iso=$(curl -L "$page_url" | grep -ioP 'https://[^ ]+?#[0-9]+' | head -1 | grep .) else - sed -Ei '/ltsc|ltsb/d' $tmp/win.list + curl -L "$page_url" | grep -ioP 'https://[^ ]+?.(iso|img)' >$tmp/win.list + + # 如果不是 ltsc ,应该先去除 ltsc 链接,否则最终链接有 ltsc 的 + # 例如查找 windows 10 iot enterprise,会得到 + # en-us_windows_10_iot_enterprise_ltsc_2021_arm64_dvd_e8d4fc46.iso + # en-us_windows_10_iot_enterprise_version_22h2_arm64_dvd_39566b6b.iso + # sed -Ei 和 sed -iE 是不同的 + if is_ltsc; then + sed -Ei '/ltsc|ltsb/!d' $tmp/win.list + else + sed -Ei '/ltsc|ltsb/d' $tmp/win.list + fi + + get_windows_iso_link_inner fi } @@ -983,7 +1108,7 @@ get_shortest_line() { awk '(NR == 1 || length($0) < length(shortest)) { shortest = $0 } END { print shortest }' } -get_windows_iso_link() { +get_windows_iso_link_inner() { regexs=() # msdn @@ -1043,12 +1168,6 @@ setos() { setos_alpine() { is_virt && flavour=virt || flavour=lts - # alpine aarch64 3.16/3.17 virt 没有直连链接 - if [ "$basearch" = aarch64 ] && - { [ "$releasever" = 3.16 ] || [ "$releasever" = 3.17 ]; }; then - flavour=lts - fi - # 不要用https 因为甲骨文云arm initramfs阶段不会从硬件同步时钟,导致访问https出错 if is_in_china; then mirror=http://mirror.nju.edu.cn/alpine/v$releasever @@ -1066,6 +1185,10 @@ setos() { [ "$releasever" -le 10 ] } + if [ "$releasever" -le 9 ] && [ "$basearch" = aarch64 ]; then + error_and_exit "Debian $releasever ELTS does not support aarch64." + fi + # 用此标记要是否 elts, 用于安装后修改 elts/etls-cn 源 # shellcheck disable=SC2034 is_debian_elts && elts=1 || elts=0 @@ -1075,6 +1198,9 @@ setos() { 10) codename=buster ;; 11) codename=bullseye ;; 12) codename=bookworm ;; + 13) codename=trixie ;; + 14) codename=forky ;; + 15) codename=duke ;; esac if ! is_use_cloud_image && is_debian_elts && is_in_china; then @@ -1181,6 +1307,7 @@ Continue? eval ${step}_vmlinuz=$mirror/linux eval ${step}_initrd=$mirror/initrd.gz eval ${step}_ks=$confhome/debian.cfg + eval ${step}_deb_mirror=$hostname/kali eval ${step}_udeb_mirror=$hostname/kali eval ${step}_codename=$codename eval ${step}_kernel=linux-image$flavour-$basearch_alt @@ -1195,7 +1322,7 @@ Continue? 20.04) codename=focal ;; 22.04) codename=jammy ;; 24.04) codename=noble ;; - 24.10) codename=oracular ;; # non-lts + 25.10) codename=questing ;; # non-lts esac if is_use_cloud_image; then @@ -1212,17 +1339,11 @@ Continue? ci_mirror=https://cloud-images.ubuntu.com fi - # 22.04 和以下没有 minimal aarch64 镜像 + # 以下版本有 minimal 镜像 + # amd64 所有 + # arm64 24.04 和以上 is_have_minimal_image() { - [ "$basearch_alt" = amd64 ] || [ "$releasever" = 24.04 ] - } - - is_should_use_minimal_cloud_image() { - if [ "$minimal" = 1 ] && ! is_have_minimal_image; then - echo "Fallback to normal cloud image." - return 1 - fi - [ "$minimal" = 1 ] + [ "$basearch_alt" = amd64 ] || [ "${releasever%.*}" -ge 24 ] } get_suffix() { @@ -1235,10 +1356,14 @@ Continue? fi } - if is_should_use_minimal_cloud_image; then + if [ "$minimal" = 1 ]; then + if ! is_have_minimal_image; then + error_and_exit "Minimal cloud image is not available for $releasever $basearch_alt." + fi eval ${step}_img="$ci_mirror/minimal/releases/$codename/release/ubuntu-$releasever-minimal-cloudimg-$basearch_alt$(get_suffix).img" else - eval ${step}_img="$ci_mirror/releases/$releasever/release/ubuntu-$releasever-server-cloudimg-$basearch_alt$(get_suffix).img" + # 用 codename 而不是 releasever,可减少一次跳转 + eval ${step}_img="$ci_mirror/releases/$codename/release/ubuntu-$releasever-server-cloudimg-$basearch_alt$(get_suffix).img" fi else # 传统安装 @@ -1255,7 +1380,7 @@ Continue? fi # iso - filename=$(curl -L $mirror | grep -oP "ubuntu-$releasever.*?-live-server-$basearch_alt.iso" | + filename=$(curl -L $mirror/ | grep -oP "ubuntu-$releasever.*?-live-server-$basearch_alt.iso" | sort -uV | tail -1 | grep .) iso=$mirror/$filename # 在 ubuntu 20.04 上,file 命令检测 ubuntu 22.04 iso 结果是 DOS/MBR boot sector @@ -1342,8 +1467,12 @@ Continue? } setos_opensuse() { - # aria2 有 mata4 问题 # https://download.opensuse.org/ + # curl 会跳转到最近的镜像源,但可能会被镜像源 block + # aria2 会跳转使用 metalink + + # https://downloadcontent.opensuse.org # 德国 + # https://downloadcontentcdn.opensuse.org # fastly cdn # 很多国内源缺少 aarch64 tumbleweed appliances # https://download.opensuse.org/ports/aarch64/tumbleweed/appliances/ @@ -1353,7 +1482,7 @@ Continue? if is_in_china; then mirror=https://mirror.nju.edu.cn/opensuse else - mirror=https://ftp.gwdg.de/pub/opensuse + mirror=https://downloadcontentcdn.opensuse.org fi if [ "$releasever" = tumbleweed ]; then @@ -1365,12 +1494,20 @@ Continue? fi file=openSUSE-Tumbleweed-Minimal-VM.$basearch-Cloud.qcow2 else - # 常规版本 + # leap dir=distribution/leap/$releasever/appliances - file=openSUSE-Leap-$releasever-Minimal-VM.$basearch-Cloud.qcow2 - fi + case "$releasever" in + 15.6) file=openSUSE-Leap-$releasever-Minimal-VM.$basearch-Cloud.qcow2 ;; + 16.0) file=Leap-$releasever-Minimal-VM.$basearch-Cloud.qcow2 ;; + # 16.0) file=Leap-$releasever-Minimal-VM.$basearch-kvm$(if [ "$basearch" = x86_64 ]; then echo '-and-xen'; fi).qcow2 ;; + esac - # 有专门的kvm镜像,openSUSE-Leap-15.5-Minimal-VM.x86_64-kvm-and-xen.qcow2,但里面没有cloud-init + # https://src.opensuse.org/openSUSE/Leap-Images/src/branch/leap-16.0/kiwi-templates-Minimal/Minimal.kiwi + # https://build.opensuse.org/projects/Virtualization:Appliances:Images:openSUSE-Leap-15.6/packages/kiwi-templates-Minimal/files/Minimal.kiwi + # https://build.opensuse.org/projects/Virtualization:Appliances:Images:openSUSE-Tumbleweed/packages/kiwi-templates-Minimal/files/Minimal.kiwi + # 有专门的kvm镜像,openSUSE-Leap-15.5-Minimal-VM.x86_64-kvm-and-xen.qcow2,里面没有cloud-init + # file=openSUSE-Leap-15.5-Minimal-VM.x86_64-kvm-and-xen.qcow2 + fi eval ${step}_img=$mirror/$dir/$file } @@ -1390,25 +1527,27 @@ Continue? if [[ "$iso" = magnet:* ]]; then : # 不测试磁力链接 else - iso_tested=false + # 需要用户输入 massgrave.dev 直链 + if grep -Eiq '\.massgrave\.dev/.*\.(iso|img)$' <<<"$iso" || + grep -Eiq '\.gravesoft\.dev/#[0-9]+$' <<<"$iso"; then + info "Set Direct link" + # MobaXterm 不支持 + # printf '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' - # 获取 massgrave.dev 直链 - if grep -Eiq '\.massgrave\.dev/.*\.(iso|img)' <<<"$iso"; then - # 如果已经是 iso 直链则跳过下面的 iso 测试 - if test_url_grace "$iso" iso; then - iso_tested=true - else - msg="Could not find direct link for $iso" - if ! iso=$(grep -oE 'https?.*\.iso[^"]*' $tmp/img-test | sed 's/&/\&/g' | grep .); then - error_and_exit "$msg" - fi + # MobaXterm 不显示为超链接 + # info false "请在浏览器中打开 $iso 获取直链并粘贴到这里。" + # info false "Please open $iso in browser to get the direct link and paste it here." + + echo "请在浏览器中打开 $iso 获取直链并粘贴到这里。" + echo "Please open $iso in browser to get the direct link and paste it here." + IFS= read -r -p "Direct Link: " iso + if [ -z "$iso" ]; then + error_and_exit "ISO Link is empty." fi fi # 测试是否是 iso - if ! $iso_tested; then - test_url "$iso" iso - fi + test_url "$iso" iso # 判断 iso 架构是否兼容 # https://gitlab.com/libosinfo/osinfo-db/-/tree/main/data/os/microsoft.com?ref_type=heads @@ -1501,7 +1640,17 @@ Continue with DD? fi done - iso=$(curl -L https://fnnas.com/ | grep -o 'https://[^"]*\.iso' | head -1) + iso=$(curl -L https://fnnas.com/ | grep -o 'https://[^"]*\.iso' | head -1 | grep .) + + # curl 7.82.0+ + # curl -L --json '{"url":"'$iso'"}' https://www.fnnas.com/api/download-sign + + iso=$(curl -L \ + -d '{"url":"'$iso'"}' \ + -H 'Content-Type: application/json' \ + https://www.fnnas.com/api/download-sign | + grep -o 'https://[^"]*') + test_url "$iso" iso eval "${step}_iso='$iso'" } @@ -1523,28 +1672,35 @@ Continue with DD? } setos_centos_almalinux_rocky_fedora() { - # el 10 需要 x86-64-v3 + # el 10 需要 x86-64-v3,除了 almalinux if [ "$basearch" = x86_64 ] && - { [ "$distro" = centos ] || [ "$distro" = almalinux ] || [ "$distro" = rocky ]; } && + { [ "$distro" = centos ] || [ "$distro" = rocky ]; } && [ "$releasever" -ge 10 ]; then assert_cpu_supports_x86_64_v3 fi + elarch=$basearch + if [ "$basearch" = x86_64 ] && + [ "$distro" = almalinux ] && [ "$releasever" -ge 10 ] && + ! is_cpu_supports_x86_64_v3; then + elarch=x86_64_v2 + fi + if is_use_cloud_image; then # ci if is_in_china; then case $distro in centos) ci_mirror="https://mirror.nju.edu.cn/centos-cloud/centos" ;; - almalinux) ci_mirror="https://mirror.nju.edu.cn/almalinux/$releasever/cloud/$basearch/images" ;; - rocky) ci_mirror="https://mirror.nju.edu.cn/rocky/$releasever/images/$basearch" ;; - fedora) ci_mirror="https://mirror.nju.edu.cn/fedora/releases/$releasever/Cloud/$basearch/images" ;; + almalinux) ci_mirror="https://mirror.nju.edu.cn/almalinux/$releasever/cloud/$elarch/images" ;; + rocky) ci_mirror="https://mirror.nju.edu.cn/rocky/$releasever/images/$elarch" ;; + fedora) ci_mirror="https://mirror.nju.edu.cn/fedora/releases/$releasever/Cloud/$elarch/images" ;; esac else case $distro in centos) ci_mirror="https://cloud.centos.org/centos" ;; - almalinux) ci_mirror="https://repo.almalinux.org/almalinux/$releasever/cloud/$basearch/images" ;; - rocky) ci_mirror="https://download.rockylinux.org/pub/rocky/$releasever/images/$basearch" ;; - fedora) ci_mirror="https://dl.fedoraproject.org/pub/fedora/linux/releases/$releasever/Cloud/$basearch/images" ;; + almalinux) ci_mirror="https://repo.almalinux.org/almalinux/$releasever/cloud/$elarch/images" ;; + rocky) ci_mirror="https://download.rockylinux.org/pub/rocky/$releasever/images/$elarch" ;; + fedora) ci_mirror="https://d2lzkl7pfhq30w.cloudfront.net/pub/fedora/linux/releases/$releasever/Cloud/$elarch/images" ;; esac fi case $distro in @@ -1553,22 +1709,25 @@ Continue with DD? 7) # CentOS-7-aarch64-GenericCloud.qcow2c 是旧版本 ver=-2211 - ci_image=$ci_mirror/$releasever/images/CentOS-$releasever-$basearch-GenericCloud$ver.qcow2c + ci_image=$ci_mirror/$releasever/images/CentOS-$releasever-$elarch-GenericCloud$ver.qcow2c ;; *) # 有 bios 和 efi 镜像 # https://cloud.centos.org/centos/10-stream/x86_64/images/CentOS-Stream-GenericCloud-10-latest.x86_64.qcow2 # https://cloud.centos.org/centos/10-stream/x86_64/images/CentOS-Stream-GenericCloud-x86_64-10-latest.x86_64.qcow2 - [ "$basearch" = x86_64 ] && - ci_image=$ci_mirror/$releasever-stream/$basearch/images/CentOS-Stream-GenericCloud-x86_64-$releasever-latest.$basearch.qcow2 || - ci_image=$ci_mirror/$releasever-stream/$basearch/images/CentOS-Stream-GenericCloud-$releasever-latest.$basearch.qcow2 + [ "$elarch" = x86_64 ] && + ci_image=$ci_mirror/$releasever-stream/$elarch/images/CentOS-Stream-GenericCloud-x86_64-$releasever-latest.$elarch.qcow2 || + ci_image=$ci_mirror/$releasever-stream/$elarch/images/CentOS-Stream-GenericCloud-$releasever-latest.$elarch.qcow2 ;; esac ;; - almalinux) ci_image=$ci_mirror/AlmaLinux-$releasever-GenericCloud-latest.$basearch.qcow2 ;; - rocky) ci_image=$ci_mirror/Rocky-$releasever-GenericCloud-Base.latest.$basearch.qcow2 ;; + almalinux) ci_image=$ci_mirror/AlmaLinux-$releasever-GenericCloud-latest.$elarch.qcow2 ;; + rocky) ci_image=$ci_mirror/Rocky-$releasever-GenericCloud-Base.latest.$elarch.qcow2 ;; fedora) - filename=$(curl -L $ci_mirror | grep -oP "Fedora-Cloud-Base-Generic.*?.qcow2" | + # 不加 / 会跳转到 https://dl.fedoraproject.org,纯 ipv6 无法访问 + # curl -L -6 https://d2lzkl7pfhq30w.cloudfront.net/pub/fedora/linux/releases/42/Cloud/x86_64/images + # curl -L -6 https://d2lzkl7pfhq30w.cloudfront.net/pub/fedora/linux/releases/42/Cloud/x86_64/images/ + filename=$(curl -L $ci_mirror/ | grep -oP "Fedora-Cloud-Base-Generic.*?.qcow2" | sort -uV | tail -1 | grep .) ci_image=$ci_mirror/$filename ;; @@ -1578,14 +1737,14 @@ Continue with DD? else # 传统安装 case $distro in - centos) mirrorlist="https://mirrors.centos.org/mirrorlist?repo=centos-baseos-$releasever-stream&arch=$basearch" ;; + centos) mirrorlist="https://mirrors.centos.org/mirrorlist?repo=centos-baseos-$releasever-stream&arch=$elarch" ;; almalinux) mirrorlist="https://mirrors.almalinux.org/mirrorlist/$releasever/baseos" ;; - rocky) mirrorlist="https://mirrors.rockylinux.org/mirrorlist?arch=$basearch&repo=BaseOS-$releasever" ;; - fedora) mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?arch=$basearch&repo=fedora-$releasever" ;; + rocky) mirrorlist="https://mirrors.rockylinux.org/mirrorlist?arch=$elarch&repo=BaseOS-$releasever" ;; + fedora) mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?arch=$elarch&repo=fedora-$releasever" ;; esac - # rocky/centos9 需要删除第一行注释, almalinux 需要替换$basearch - for cur_mirror in $(curl -L $mirrorlist | sed "/^#/d" | sed "s,\$basearch,$basearch,"); do + # rocky/centos9 需要删除第一行注释, almalinux 需要替换链接里面的 $basearch + for cur_mirror in $(curl -L $mirrorlist | sed "/^#/d" | sed "s,\$basearch,$elarch,"); do host=$(get_host_by_url $cur_mirror) if is_host_has_ipv4_and_ipv6 $host && test_url_grace ${cur_mirror}images/pxeboot/vmlinuz; then @@ -1634,13 +1793,11 @@ Continue with DD? } setos_redhat() { - # el 10 需要 x86-64-v3 - if [ "$basearch" = x86_64 ] && [ "$releasever" -ge 10 ]; then - assert_cpu_supports_x86_64_v3 - fi - if is_use_cloud_image; then - # ci + # el 10 需要 x86-64-v3 + if [ "$basearch" = x86_64 ] && [[ "$img" = *rhel-10* ]]; then + assert_cpu_supports_x86_64_v3 + fi eval "${step}_img='$img'" else : @@ -1658,7 +1815,12 @@ Continue with DD? if is_use_cloud_image; then # ci - dir=$releasever/images/$basearch + if [ "$releasever" -eq 9 ]; then + dir=$releasever/images/qcow2/$basearch + else + dir=$releasever/images/$basearch + fi + file=$(curl -L $mirror/$dir/ | grep -oP 'OpenCloudOS.*?\.qcow2' | sort -uV | tail -1 | grep .) eval ${step}_img=$mirror/$dir/$file @@ -1715,7 +1877,7 @@ Continue with DD? # 集中测试云镜像格式 if is_use_cloud_image && [ "$step" = finalos ]; then # shellcheck disable=SC2154 - test_url $finalos_img 'qemu qemu.gzip qemu.xz qemu.zstd' finalos_img_type + test_url $finalos_img 'qemu qemu.gzip qemu.xz qemu.zstd raw.xz' finalos_img_type fi } @@ -1737,6 +1899,11 @@ is_distro_like_debian() { [ "$_distro" = debian ] || [ "$_distro" = kali ] } +get_latest_distro_releasever() { + get_function_content verify_os_name | + grep -wo "$1 [^'\"]*" | awk -F'|' '{print $NF}' +} + # 检查是否为正确的系统名 verify_os_name() { if [ -z "$*" ]; then @@ -1748,22 +1915,22 @@ verify_os_name() { 'centos 7|9|10' \ 'anolis 7|8|23' \ 'opencloudos 8|9|23' \ - 'almalinux 8|9' \ - 'rocky 8|9' \ - 'redhat 8|9' \ - 'oracle 8|9' \ - 'fedora 40|41' \ - 'nixos 24.11' \ - 'debian 9|10|11|12' \ - 'opensuse 15.6|tumbleweed' \ - 'alpine 3.18|3.19|3.20|3.21' \ - 'openeuler 20.03|22.03|24.03|24.09' \ - 'ubuntu 16.04|18.04|20.04|22.04|24.04|24.10' \ + 'almalinux 8|9|10' \ + 'rocky 8|9|10' \ + 'oracle 8|9|10' \ + 'fnos 1' \ + 'fedora 42|43' \ + 'nixos 25.11' \ + 'debian 9|10|11|12|13' \ + 'opensuse 15.6|16.0|tumbleweed' \ + 'alpine 3.20|3.21|3.22|3.23' \ + 'openeuler 20.03|22.03|24.03|25.09' \ + 'ubuntu 16.04|18.04|20.04|22.04|24.04|25.10' \ + 'redhat' \ 'kali' \ 'arch' \ 'gentoo' \ 'aosc' \ - 'fnos' \ 'windows' \ 'dd' \ 'netboot.xyz'; do @@ -1790,6 +1957,10 @@ verify_os_args() { redhat) [ -n "$img" ] || error_and_exit "redhat need --img" ;; windows) [ -n "$image_name" ] || error_and_exit "Install Windows need --image-name." ;; esac + + case "$distro" in + netboot.xyz | windows) [ -z "$ssh_keys" ] || error_and_exit "not support ssh key for $distro" ;; + esac } get_cmd_path() { @@ -1813,8 +1984,7 @@ install_pkg() { # 因为可能装了多种包管理器 if [ -f /etc/os-release ]; then # shellcheck source=/dev/null - . /etc/os-release - for id in $ID $ID_LIKE; do + for id in $({ . /etc/os-release && echo $ID $ID_LIKE; }); do # https://github.com/chef/os_release case "$id" in fedora | centos | rhel) is_have_cmd dnf && pkg_mgr=dnf || pkg_mgr=yum ;; @@ -2015,6 +2185,10 @@ install_pkg() { done >&2 } +is_valid_ram_size() { + is_digit "$1" && [ "$1" -gt 0 ] +} + check_ram() { ram_standard=$( case "$distro" in @@ -2042,7 +2216,7 @@ check_ram() { ) if is_in_windows; then - ram_size=$(wmic memorychip get capacity | awk -F= '{sum+=$2} END {print sum/1024/1024}') + ram_size=$(wmic memorychip get capacity | awk -F= '{sum+=$2} END {if(sum>0) print sum/1024/1024}') else # lsmem最准确但 centos7 arm 和 alpine 不能用,debian 9 util-linux 没有 lsmem # arm 24g dmidecode 显示少了128m @@ -2051,12 +2225,12 @@ check_ram() { install_pkg lsmem ram_size=$(lsmem -b 2>/dev/null | grep 'Total online memory:' | awk '{ print $NF/1024/1024 }') - if [ -z $ram_size ]; then + if ! is_valid_ram_size "$ram_size"; then install_pkg dmidecode - ram_size=$(dmidecode -t 17 | grep "Size.*[GM]B" | awk '{if ($3=="GB") s+=$2*1024; else s+=$2} END {print s}') + ram_size=$(dmidecode -t 17 | grep "Size.*[GM]B" | awk '{if ($3=="GB") s+=$2*1024; else s+=$2} END {if(s>0) print s}') fi - if [ -z $ram_size ]; then + if ! is_valid_ram_size "$ram_size"; then install_pkg lshw # 不能忽略 -i,alpine 显示的是 System memory ram_str=$(lshw -c memory -short | grep -i 'System Memory' | awk '{print $3}') @@ -2066,12 +2240,13 @@ check_ram() { fi # 用于兜底,不太准确 - if [ -z $ram_size ]; then - ram_size=$(free -m | grep ^Mem: | awk '{print $2}') - ram_size=$((ram_size + 64 + 4)) + # cygwin 要装 procps-ng 才有 free 命令 + if ! is_valid_ram_size "$ram_size"; then + ram_size_k=$(grep '^MemTotal:' /proc/meminfo | awk '{print $2}') + ram_size=$((ram_size_k / 1024 + 64 + 4)) fi - if [ -z $ram_size ] || [ $ram_size -le 0 ]; then + if ! is_valid_ram_size "$ram_size"; then error_and_exit "Could not detect RAM size." fi @@ -2156,13 +2331,19 @@ to_lower() { } del_cr() { - sed 's/\r//g' + # wmic/reg 换行符是 \r\r\n + # wmic nicconfig where InterfaceIndex=$id get MACAddress,IPAddress,IPSubnet,DefaultIPGateway | hexdump -c + sed -E 's/\r+$//' } del_empty_lines() { sed '/^[[:space:]]*$/d' } +del_comment_lines() { + sed '/^[[:space:]]*#/d' +} + trim() { # sed -E -e 's/^[[:space:]]+//' -e 's/[[:space:]]+$//' sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' @@ -2170,16 +2351,23 @@ trim() { prompt_password() { info "prompt password" + warn false "Leave blank to use a random password." + warn false "不填写则使用随机密码" while true; do - IFS= read -r -p "Password [$DEFAULT_PASSWORD]: " password - IFS= read -r -p "Retype password [$DEFAULT_PASSWORD]: " password_confirm - password=${password:-$DEFAULT_PASSWORD} - password_confirm=${password_confirm:-$DEFAULT_PASSWORD} - if [ -z "$password" ]; then - error "Passwords is empty. Try again." - elif [ "$password" != "$password_confirm" ]; then - error "Passwords don't match. Try again." + IFS= read -r -p "Password: " password + if [ -n "$password" ]; then + IFS= read -r -p "Retype password: " password_confirm + if [ "$password" = "$password_confirm" ]; then + break + else + error "Passwords don't match. Try again." + fi else + # 特殊字符列表 + # https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994562(v=ws.11) + # 有的机器运行 centos 7 ,用 /dev/random 产生 16 位密码,开启了 rngd 也要 5 秒,关闭了 rngd 则长期阻塞 + chars=\''A-Za-z0-9~!@#$%^&*_=+`|(){}[]:;"<>,.?/-' + password=$(tr -dc "$chars" /dev/null - rm -rf $dir - mkdir -p $dir + # 再次运行时,有可能 mount 了 btrfs root,因此先要 umount_all + # 但目前不需要 mount ,因此用不到 + # umount_all "$dir" + rm -rf "$dir" + mkdir -p "$dir" } mod_initrd_debian_kali() { @@ -2957,6 +3156,14 @@ mod_initrd_debian_kali() { sed -Ei 's,&&( onlink=),||\1,' etc/udhcpc/default.script # hack 2 + # 强制使用 screen + # shellcheck disable=SC1003,SC2016 + { + echo 'if false && : \' | insert_into_file lib/debian-installer.d/S70menu before 'if [ -x "$bterm" ]' -F + echo 'if true || : \' | insert_into_file lib/debian-installer.d/S70menu before 'if [ -x "$screen_bin" -a' -F + } + + # hack 3 # 修改 /var/lib/dpkg/info/netcfg.postinst 运行我们的脚本 netcfg() { #!/bin/sh @@ -2968,18 +3175,24 @@ mod_initrd_debian_kali() { # 运行 trans.sh,保存配置 db_progress INFO base-installer/progress/netcfg - sh /trans.sh + # 添加 || exit ,可以在 debian installer 不兼容 /trans.sh 语法时强制报错 + # exit 不带参数,返回值为 || 前面命令的返回值 + sh /trans.sh || exit db_progress STEP 1 + db_progress STOP } - # 直接覆盖 net-retriever,方便调试 - # curl -Lo /usr/lib/debian-installer/retriever/net-retriever $confhome/net-retriever - postinst=var/lib/dpkg/info/netcfg.postinst get_function_content netcfg >$postinst get_ip_conf_cmd | insert_into_file $postinst after ": get_ip_conf_cmd" # cat $postinst + # hack 4 + # 修改 udeb 依赖 + + # 直接覆盖 net-retriever,方便调试 + # curl -Lo /usr/lib/debian-installer/retriever/net-retriever $confhome/net-retriever + change_priority() { while IFS= read -r line; do if [[ "$line" = Package:* ]]; then @@ -3025,6 +3238,7 @@ partman-xfs rescue-check wpasupplicant-udeb lilo-installer +systemd-boot-installer nic-modules-$kver-di nic-pcmcia-modules-$kver-di nic-usb-modules-$kver-di @@ -3062,32 +3276,44 @@ EOF # 云内核没有 sata 模块,也没有内嵌,有一个 CONFIG_SATA_HOST=y,libata-$(CONFIG_SATA_HOST) += libata-sata.o # scsi-modules 默认安装(改成可选),包含 nvme.ko(+) 和各种虚拟化驱动(+) - download_and_extract_udeb() { - package=$1 - extract_dir=$2 + download_and_extract_deb() { + local type=$1 + local package=$2 + local extract_dir=$3 - # 获取 udeb 列表 - udeb_list=$tmp/udeb_list - if ! [ -f $udeb_list ]; then - # shellcheck disable=SC2154 - curl -L http://$nextos_udeb_mirror/dists/$nextos_codename/main/debian-installer/binary-$basearch_alt/Packages.gz | - zcat | grep 'Filename:' | awk '{print $2}' >$udeb_list + # shellcheck disable=SC2154 + case "$type" in + deb) + local mirror=$nextos_deb_mirror + local url=http://$mirror/dists/$nextos_codename/main/binary-$basearch_alt/Packages.gz + ;; + udeb) + local mirror=$nextos_udeb_mirror + local url=http://$mirror/dists/$nextos_codename/main/debian-installer/binary-$basearch_alt/Packages.gz + ;; + esac + + # 获取 deb/udeb 列表 + deb_list=$tmp/${type}_list + if ! [ -f $deb_list ]; then + curl -L "$url" | zcat | grep 'Filename:' | awk '{print $2}' >$deb_list fi - # 下载 udeb - curl -Lo $tmp/tmp.udeb http://$nextos_udeb_mirror/"$(grep /$package $udeb_list)" + # 下载 deb/udeb + deb_path=$(grep -F "/${package}_" "$deb_list") + curl -Lo $tmp/tmp.deb http://$mirror/"$deb_path" if false; then # 使用 dpkg # cygwin 没有 dpkg install_pkg dpkg - dpkg -x $tmp/tmp.udeb $extract_dir + dpkg -x $tmp/tmp.deb $extract_dir else # 使用 ar tar xz # cygwin 需安装 binutils # centos7 ar 不支持 --output install_pkg ar tar xz - (cd $tmp && ar x $tmp/tmp.udeb) + (cd $tmp && ar x $tmp/tmp.deb) tar xf $tmp/data.tar.xz -C $extract_dir fi } @@ -3100,11 +3326,15 @@ EOF curl -LO "$confhome/fix-eth-name.sh" curl -LO "$confhome/fix-eth-name.service" - # 最近 kali initrd 删除了原版 wget + # 有段时间 kali initrd 删除了原版 wget # 但 initrd 的 busybox wget 又不支持 https # 因此改成在这里下载 curl -LO "$confhome/get-xda.sh" curl -LO "$confhome/ttys.sh" + if [ -n "$frpc_config" ]; then + curl -LO "$confhome/get-frpc-url.sh" + curl -LO "$confhome/frpc.service" + fi # 可以节省一点内存? echo 'export DEBCONF_DROP_TRANSLATIONS=1' | @@ -3127,12 +3357,27 @@ EOF curl -Lo usr/share/keyrings/debian-archive-keyring.gpg https://deb.freexian.com/extended-lts/archive-key.gpg fi + # 提前下载 sshd + # 以便在配置下载源之前就可以启动 sshd + mkdir_clear $tmp/sshd + download_and_extract_deb udeb openssh-server-udeb $tmp/sshd + cp -r $tmp/sshd/* . + # 提前下载 fdisk # 因为 fdisk-udeb 包含 fdisk 和 sfdisk,提前下载可减少占用 mkdir_clear $tmp/fdisk - download_and_extract_udeb fdisk-udeb $tmp/fdisk + download_and_extract_deb udeb fdisk-udeb $tmp/fdisk cp -f $tmp/fdisk/usr/sbin/fdisk usr/sbin/ + # 下载 websocketd + # debian 11+ 才有 websocketd + if [ "$distro" = kali ] || + { [ "$distro" = debian ] && [ "$releasever" -ge 11 ]; }; then + mkdir_clear $tmp/websocketd + download_and_extract_deb deb websocketd $tmp/websocketd + cp -f $tmp/websocketd/usr/bin/websocketd usr/bin/ + fi + # >256M 或者当前系统是 windows if [ $ram_size -gt 256 ] || is_in_windows; then sed -i '/^pata-modules/d' $net_retriever @@ -3163,7 +3408,7 @@ EOF # 但反查也找不到 curl https://deb.debian.org/debian/dists/bookworm/main/Contents-udeb-amd64.gz | zcat | grep xen if [ -n "$extra_drivers" ]; then mkdir_clear $tmp/scsi - download_and_extract_udeb scsi-modules-$kver-di $tmp/scsi + download_and_extract_deb udeb scsi-modules-$kver-di $tmp/scsi relative_drivers_dir=lib/modules/$kver/kernel/drivers udeb_drivers_dir=$tmp/scsi/$relative_drivers_dir @@ -3399,15 +3644,13 @@ EOF # grep -E -A5 'configure_ip\(\)' init # hack 4 运行 trans.start - # exec /bin/busybox switch_root $switch_root_opts $sysroot $chart_init "$KOPT_init" $KOPT_init_args # 3.17 - # exec switch_root $switch_root_opts $sysroot $chart_init "$KOPT_init" $KOPT_init_args # 3.18 # 1. alpine arm initramfs 时间问题 要添加 --no-check-certificate # 2. aws t4g arm 如果没设置console=ttyx,在initramfs里面wget https会出现bad header错误,chroot后正常 # Connecting to raw.githubusercontent.com (185.199.108.133:443) # 60C0BB2FFAFF0000:error:0A00009C:SSL routines:ssl3_get_record:http request:ssl/record/ssl3_record.c:345: # ssl_client: SSL_connect # wget: bad header line: � - insert_into_file init before '^exec (/bin/busybox )?switch_root' <\$sysroot/etc/local.d/trans.start # wget --no-check-certificate -O \$sysroot/etc/local.d/trans.start $confhome/trans.sh @@ -3471,7 +3714,14 @@ This script is outdated, please download reinstall.sh again. # 保存配置 mkdir -p $initrd_dir/configs - save_password $initrd_dir/configs + if [ -n "$ssh_keys" ]; then + cat <<<"$ssh_keys" >$initrd_dir/configs/ssh_keys + else + save_password $initrd_dir/configs + fi + if [ -n "$frpc_config" ]; then + cat "$frpc_config" >$initrd_dir/configs/frpc.toml + fi if is_distro_like_debian $nextos_distro; then mod_initrd_debian_kali @@ -3494,6 +3744,11 @@ This script is outdated, please download reinstall.sh again. remove_useless_initrd_files fi + if [ "$hold" = 0 ]; then + info 'hold 0' + read -r -p 'Press Enter to continue...' + fi + # 重建 # 注意要用 cpio -H newc 不要用 cpio -c ,不同版本的 -c 作用不一样,很坑 # -c Use the old portable (ASCII) archive format @@ -3521,14 +3776,15 @@ remove_useless_initrd_files() { for item in *; do case "$item" in # 甲骨文 arm 用自定义镜像支持设为 mlx5 vf 网卡,且不是 azure 那样显示两个网卡 - amazon | google | mellanox) ;; + # https://debian.pkgs.org/13/debian-main-amd64/linux-image-6.12.43+deb13-cloud-amd64_6.12.43-1_amd64.deb.html + amazon | google | mellanox | realtek | pensando) ;; intel) ( cd "$item" for sub_item in *; do case "$sub_item" in # 有 e100.ko e1000文件夹 e1000e文件夹 - e100* | lib* | *vf) ;; + e100* | lib* | *vf | idpf) ;; *) rm -rf $sub_item ;; esac done @@ -3572,7 +3828,17 @@ remove_useless_initrd_files() { du -sh . } +get_unix_path() { + if is_in_windows; then + # 输入的路径是 / 开头也没问题 + cygpath -u "$1" + else + printf '%s' "$1" + fi +} + # 脚本入口 + if mount | grep -q 'tmpfs on / type tmpfs'; then error_and_exit "Can't run this script in Live OS." fi @@ -3593,6 +3859,12 @@ if is_in_windows; then # 为 windows 程序输出删除 cr for exe in $WINDOWS_EXES; do + # 如果我们覆写了 wmic(),则先将 wmic() 重命名为 _wmic() + if get_function $exe >/dev/null 2>&1; then + eval "_$(get_function $exe)" + fi + # 使用以下方法重新生成 wmic() + # 调用链:wmic() -> run_with_del_cr(wmic) -> _wmic() -> command wmic eval "$exe(){ $(get_function_content run_with_del_cr_template | sed "s/\$exe/$exe/g") }" done fi @@ -3610,7 +3882,7 @@ else fi long_opts= -for o in ci installer debug minimal allow-ping force-cn \ +for o in ci installer debug minimal allow-ping force-cn help \ add-driver: \ hold: sleep: \ iso: \ @@ -3620,30 +3892,40 @@ for o in ci installer debug minimal allow-ping force-cn \ lang: \ passwd: password: \ ssh-port: \ + ssh-key: public-key: \ rdp-port: \ web-port: http-port: \ allow-ping: \ commit: \ - force: \ + frpc-conf: frpc-config: frpc-toml: \ + force-boot-mode: \ force-old-windows-setup:; do [ -n "$long_opts" ] && long_opts+=, long_opts+=$o done # 整理参数 -if ! opts=$(getopt -n $0 -o "" --long "$long_opts" -- "$@"); then +if ! opts=$(getopt -n $0 -o "h,x" --long "$long_opts" -- "$@"); then exit fi +# /tmp 挂载在内存的话,可能不够空间 +# 处理 --frpc--toml 时会下载文件,因此在处理参数前就创建临时目录 +tmp=/reinstall-tmp +mkdir_clear "$tmp" + eval set -- "$opts" # shellcheck disable=SC2034 while true; do case "$1" in + -h | --help) + usage_and_exit + ;; --commit) commit=$2 shift 2 ;; - --debug) + -x | --debug) set -x shift ;; @@ -3671,22 +3953,114 @@ while true; do shift ;; --hold | --sleep) - if ! { [ "$2" = 1 ] || [ "$2" = 2 ]; }; then + if ! { [ "$2" = 0 ] || [ "$2" = 1 ] || [ "$2" = 2 ]; }; then error_and_exit "Invalid $1 value: $2" fi hold=$2 shift 2 ;; - --force) + --frpc-conf | --frpc-config | --frpc-toml) + [ -n "$2" ] || error_and_exit "Need value for $1" + + case "$(to_lower <<<"$2")" in + http://* | https://*) + frpc_config_url=$2 + frpc_config=$tmp/frpc_config + if ! curl -L "$frpc_config_url" -o "$frpc_config"; then + error_and_exit "Can't get frpc config from $frpc_config_url" + fi + ;; + *) + # windows 路径转换 + if ! { frpc_config=$(get_unix_path "$2") && [ -f "$frpc_config" ]; }; then + error_and_exit "File not exists: $2" + fi + ;; + esac + + # 转为绝对路径 + frpc_config=$(readlink -f "$frpc_config") + + shift 2 + ;; + --force-boot-mode) if ! { [ "$2" = bios ] || [ "$2" = efi ]; }; then error_and_exit "Invalid $1 value: $2" fi - force=$2 + force_boot_mode=$2 shift 2 ;; --passwd | --password) [ -n "$2" ] || error_and_exit "Need value for $1" password=$2 + shift 2 + ;; + --ssh-key | --public-key) + ssh_key_error_and_exit() { + error "$1" + cat <$target_cfg # 原系统为 openeuler 云镜像,需要添加 --unrestricted,否则要输入密码 - del_empty_lines <&2 + echo "Run '/trans.sh alpine' to install Alpine Linux instead." >&2 exit 1 } @@ -53,10 +55,12 @@ trap_err() { line_no=$1 ret_no=$2 - error "Line $line_no return $ret_no" - if [ -f "/trans.sh" ]; then - sed -n "$line_no"p /trans.sh - fi + error_and_exit "$( + echo "Line $line_no return $ret_no" + if [ -f "/trans.sh" ]; then + sed -n "$line_no"p /trans.sh + fi + )" } is_run_from_locald() { @@ -83,11 +87,20 @@ apk() { retry 5 command apk "$@" >&2 } +show_url_in_args() { + while [ $# -gt 0 ]; do + case "$1" in + [Hh][Tt][Tt][Pp][Ss]://* | [Hh][Tt][Tt][Pp]://* | [Mm][Aa][Gg][Nn][Ee][Tt]:*) echo "$1" ;; + esac + shift + done +} + # 在没有设置 set +o pipefail 的情况下,限制下载大小: # retry 5 command wget | head -c 1048576 会触发 retry,下载 5 次 # command wget "$@" --tries=5 | head -c 1048576 不会触发 wget 自带的 retry,只下载 1 次 wget() { - echo "$@" | grep -o 'http[^ ]*' >&2 + show_url_in_args "$@" >&2 if command wget 2>&1 | grep -q BusyBox; then # busybox wget 没有重试功能 # 好像默认永不超时 @@ -161,24 +174,9 @@ download() { # 有ipv4地址无ipv4网关的情况下,aria2可能会用ipv4下载,而不是ipv6 # axel 在 lightsail 上会占用大量cpu - # aria2 下载 fedora 官方镜像链接会将meta4文件下载下来,而且占用了指定文件名,造成重命名失效。而且无法指定目录 # https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-kvm-and-xen.qcow2 # https://aria2.github.io/manual/en/html/aria2c.html#cmdoption-o - # 构造 aria2 参数 - save= - # 文件夹 - if [[ "$path" = '/*' ]]; then - save="$save -d /" - fi - # 文件名 - if [ -n "$path" ]; then - case "$(get_url_type "$url")" in - http) save="$save -o $path" ;; - bt) save="$save -O 1=$path" ;; - esac - fi - # 阿里云源限速,而且检测 user-agent 禁止 axel/aria2 下载 # aria2 默认 --max-tries 5 @@ -205,7 +203,25 @@ download() { url=$torrent fi - aria2c $save "$url" + # intel 禁止了 aria2 下载驱动 + # intel 禁止了 wget 下载网页内容 + # 腾讯云 virtio 驱动也禁止了 aria2 下载 + + # -o 设置 http 下载文件名 + # -O 设置 bt 首个文件的文件名 + aria2c "$url" \ + -d "$(dirname "$path")" \ + -o "$(basename "$path")" \ + -O "1=$(basename "$path")" \ + -U curl/7.54.1 + + # opensuse 官方镜像支持 metalink + # aira2 无法重命名用 metalink 下载的文件 + # 需用以下方法重命名 + if head -c 1024 "$path" | grep -Fq 'urn:ietf:params:xml:ns:metalink'; then + real_file=$(tr -d '\n' <"$path" | sed -E 's|.*/dev/null) && [ -n "$xda" ]; then @@ -436,7 +452,7 @@ EOF } umount_all() { - dirs="/mnt /os /iso /wim /installer /nbd /nbd-boot /nbd-efi /root /nix" + dirs="/mnt /os /iso /wim /installer /nbd /nbd-boot /nbd-efi /nbd-test /root /nix" regex=$(echo "$dirs" | sed 's, ,|,g') if mounts=$(mount | grep -Ew "on $regex" | awk '{print $3}' | tac); then for mount in $mounts; do @@ -538,6 +554,7 @@ set_config() { printf '%s' "$2" >"/configs/$1" } +# ubuntu 安装版、el/ol 安装版不使用该密码 get_password_linux_sha512() { get_config password-linux-sha512 } @@ -546,7 +563,6 @@ get_password_windows_administrator_base64() { get_config password-windows-administrator-base64 } -# debian 安装版、ubuntu 安装版、el/ol 安装版不使用该密码 get_password_plaintext() { get_config password-plaintext } @@ -599,7 +615,7 @@ get_netconf_to() { eval "$1='$res'" } -is_ipv4_has_internet() { +is_any_ipv4_has_internet() { grep -q 1 /dev/netconf/*/ipv4_has_internet } @@ -610,12 +626,20 @@ is_in_china() { # 有 dhcpv4 不等于有网关,例如 vultr 纯 ipv6 # 没有 dhcpv4 不等于是静态ip,可能是没有 ip is_dhcpv4() { + if ! is_ipv4_has_internet || should_disable_dhcpv4; then + return 1 + fi + get_netconf_to dhcpv4 # shellcheck disable=SC2154 [ "$dhcpv4" = 1 ] } is_staticv4() { + if ! is_ipv4_has_internet; then + return 1 + fi + if ! is_dhcpv4; then get_netconf_to ipv4_addr get_netconf_to ipv4_gateway @@ -627,6 +651,10 @@ is_staticv4() { } is_staticv6() { + if ! is_ipv6_has_internet; then + return 1 + fi + if ! is_slaac && ! is_dhcpv6; then get_netconf_to ipv6_addr get_netconf_to ipv6_gateway @@ -643,6 +671,24 @@ is_dhcpv6_or_slaac() { [ "$dhcpv6_or_slaac" = 1 ] } +is_ipv4_has_internet() { + get_netconf_to ipv4_has_internet + # shellcheck disable=SC2154 + [ "$ipv4_has_internet" = 1 ] +} + +is_ipv6_has_internet() { + get_netconf_to ipv6_has_internet + # shellcheck disable=SC2154 + [ "$ipv6_has_internet" = 1 ] +} + +should_disable_dhcpv4() { + get_netconf_to should_disable_dhcpv4 + # shellcheck disable=SC2154 + [ "$should_disable_dhcpv4" = 1 ] +} + should_disable_accept_ra() { get_netconf_to should_disable_accept_ra # shellcheck disable=SC2154 @@ -658,7 +704,12 @@ should_disable_autoconf() { is_slaac() { # 如果是静态(包括自动获取到 IP 但无法联网而切换成静态)直接返回 1,不考虑 ra # 防止部分机器slaac/dhcpv6获取的ip/网关无法上网 - if ! is_dhcpv6_or_slaac; then + + # 有可能 ra 的 dhcpv6/slaac 是打开的,但实测无法获取到 ipv6 地址 + # is_dhcpv6_or_slaac 是实测结果,因此如果实测不通过,也返回 1 + + # 不要判断 is_staticv6,因为这会导致死循环 + if ! is_ipv6_has_internet || ! is_dhcpv6_or_slaac || should_disable_accept_ra || should_disable_autoconf; then return 1 fi get_netconf_to slaac @@ -669,7 +720,12 @@ is_slaac() { is_dhcpv6() { # 如果是静态(包括自动获取到 IP 但无法联网而切换成静态)直接返回 1,不考虑 ra # 防止部分机器slaac/dhcpv6获取的ip/网关无法上网 - if ! is_dhcpv6_or_slaac; then + + # 有可能 ra 的 dhcpv6/slaac 是打开的,但实测无法获取到 ipv6 地址 + # is_dhcpv6_or_slaac 是实测结果,因此如果实测不通过,也返回 1 + + # 不要判断 is_staticv6,因为这会导致死循环 + if ! is_ipv6_has_internet || ! is_dhcpv6_or_slaac || should_disable_accept_ra || should_disable_autoconf; then return 1 fi get_netconf_to dhcpv6 @@ -702,32 +758,39 @@ is_have_rdnss() { [ -n "$rdnss" ] } +# dd 完检测到镜像是 windows 时会改写此方法 is_windows() { - for dir in /os /wim; do - [ -d $dir/Windows/System32 ] && return 0 - done - return 1 + [ "$distro" = windows ] } # 15063 或之后才支持 rdnss is_windows_support_rdnss() { - apk add pev - for dir in /os /wim; do - dll=$dir/Windows/System32/kernel32.dll - if [ -f $dll ]; then - build_ver="$(peres -v $dll | grep 'Product Version:' | cut -d. -f3)" - echo "Windows Build Version: $build_ver" - apk del pev - [ "$build_ver" -ge 15063 ] && return 0 || return 1 - fi - done - error_and_exit "Not found kernel32.dll" + [ "$build_ver" -ge 15063 ] +} + +get_windows_version_from_windows_drive() { + local os_dir=$1 + + apk add hivex pev + ntoskrnl_exe=$(find_file_ignore_case $os_dir/Windows/System32/ntoskrnl.exe) + hive=$(find_file_ignore_case $os_dir/Windows/System32/config/SOFTWARE) + IFS=. read -r nt_ver_major nt_ver_minor _ rev_ver _ \ + < <(peres -v "$ntoskrnl_exe" | grep 'Product Version:' | awk '{print $NF}') + nt_ver="$nt_ver_major.$nt_ver_minor" + # win10 22h2 19045 的 exe/dll 版本还是 19041 的,因此要从注册表获取 + build_ver=$(hivexget $hive 'Microsoft\Windows NT\CurrentVersion' CurrentBuildNumber) + echo "Version: $nt_ver_major.$nt_ver_minor.$build_ver.$rev_ver" >&2 + apk del hivex pev } is_elts() { [ -n "$elts" ] && [ "$elts" = 1 ] } +is_need_set_ssh_keys() { + [ -s /configs/ssh_keys ] +} + is_need_change_ssh_port() { [ -n "$ssh_port" ] && ! [ "$ssh_port" = 22 ] } @@ -755,9 +818,9 @@ get_current_dns() { # debian 11 initrd 没有 xargs awk # debian 12 initrd 没有 xargs if false; then - grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | grep -F "$mark" + grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | grep -F "$mark" | cut -d '%' -f1 else - grep '^nameserver' /etc/resolv.conf | cut -d' ' -f2 | grep -F "$mark" + grep '^nameserver' /etc/resolv.conf | cut -d' ' -f2 | grep -F "$mark" | cut -d '%' -f1 fi } @@ -770,7 +833,11 @@ to_lower() { } del_cr() { - sed 's/\r//g' + sed 's/\r$//' +} + +del_comment_lines() { + sed '/^[[:space:]]*#/d' } del_empty_lines() { @@ -812,8 +879,9 @@ del_invalid_efi_entry() { done < <(efibootmgr | grep 'HD(.*,GPT,') } +# reinstall.sh 有同名方法 grep_efi_index() { - awk -F '*' '{print $1}' | sed 's/Boot//' + awk '{print $1}' | sed -e 's/Boot//' -e 's/\*//' } # 某些机器可能不会回落到 bootx64.efi @@ -878,11 +946,15 @@ unix2dos() { } insert_into_file() { - file=$1 - location=$2 - regex_to_find=$3 + local file=$1 + local location=$2 + local regex_to_find=$3 shift 3 + if ! [ -f "$file" ]; then + error_and_exit "File not found: $file" + fi + # 默认 grep -E if [ $# -eq 0 ]; then set -- -E @@ -1007,7 +1079,16 @@ EOF echo "iface $ethx inet6 auto" >>$conf_file elif is_dhcpv6; then - echo "iface $ethx inet6 dhcp" >>$conf_file + # debian 13 使用 ifupdown + dhcpcd-base + # inet/inet6 都配置成 dhcp 时,重启后 dhcpv4 会丢失 + # 手动 systemctl restart networking 后正常 + # 删除 dhcpcd-base 安装 isc-dhcp-client(类似 debian 12 升级到 13),轮到 dhcpv6 丢失 + if { [ "$distro" = debian ] && [ "$releasever" -ge 13 ]; } || + [ "$distro" = kali ]; then + echo "iface $ethx inet6 auto" >>$conf_file + else + echo "iface $ethx inet6 dhcp" >>$conf_file + fi elif is_staticv6; then get_netconf_to ipv6_addr @@ -1347,6 +1428,18 @@ install_alpine() { # 删除 setup-disk 时自动安装的包 apk del e2fsprogs dosfstools efibootmgr grub* + # 如果没有挂载 /proc + + # 1. chroot /os setup-keymap us us 会报错 + # grep: /proc/filesystems: No such file or directory + + # 2. 安装固件微码会触发 grub-probe,如果没挂载会报错 + # Executing grub-2.12-r5.trigger + # /usr/sbin/grub-probe: error: failed to get canonical path of `/dev/vda1'. + # ERROR: grub-2.12-r5.trigger: script exited with error 1 + + mount_pseudo_fs /os + # 安装到硬盘后才安装各种应用 # 避免占用 Live OS 内存 @@ -1376,7 +1469,14 @@ install_alpine() { # 安装其他部件 chroot /os setup-keymap us us chroot /os setup-timezone -i Asia/Shanghai - chroot /os setup-ntp chrony || true + # 3.21 默认是 chrony + # 3.22 默认是 busybox ntp + printf '\n' | chroot /os setup-ntp || true + + # 设置公钥 + if is_need_set_ssh_keys; then + set_ssh_keys_and_del_password /os + fi # 下载 fix-eth-name download "$confhome/fix-eth-name.sh" /os/fix-eth-name.sh @@ -1384,12 +1484,14 @@ install_alpine() { chmod +x /os/etc/init.d/fix-eth-name chroot /os rc-update add fix-eth-name boot - # 安装固件微码会触发 grub-probe - # 如果没挂载会报错 - # Executing grub-2.12-r5.trigger - # /usr/sbin/grub-probe: error: failed to get canonical path of `/dev/vda1'. - # ERROR: grub-2.12-r5.trigger: script exited with error 1 - mount_pseudo_fs /os + # 安装 frpc + if [ -s /configs/frpc.toml ]; then + chroot /os apk add frp + # chroot rc-update add 默认添加到 sysinit + # 但不加 chroot 默认添加到 default + chroot /os rc-update add frpc boot + cp /configs/frpc.toml /os/etc/frp/frpc.toml + fi # setup-disk 会自动选择固件,但不包括微码? # https://github.com/alpinelinux/alpine-conf/blob/3.18.1/setup-disk.in#L421 @@ -1463,7 +1565,8 @@ install_nixos() { show_nixos_config() { echo - cat -n /os/etc/nixos/configuration.nix + # 过滤 frp auth.token + cat -n /os/etc/nixos/configuration.nix | grep -Fv 'auth.token' echo cat -n /os/etc/nixos/hardware-configuration.nix echo @@ -1526,13 +1629,38 @@ install_nixos() { done fi - if is_in_china; then - sh=https://mirror.nju.edu.cn/nix/latest/install + # 备用方案 + # 1. 从 https://mirror.nju.edu.cn/nix-channels/nixos-25.11/nixexprs.tar.xz 获取 + # https://github.com/NixOS/nixpkgs/blob/nixos-25.11/pkgs/tools/package-management/nix/default.nix + # https://github.com/NixOS/nixpkgs/blob/nixos-25.11/nixos/modules/installer/tools/nix-fallback-paths.nix + # 2. 安装最新版 nix,添加 nixos channel 后获取 + # nix eval -f '' --raw 'nixVersions.stable.version' --extra-experimental-features nix-command + + if true; then + # nix 版本号使用目标系统里面的 + download $mirror/nixos-$releasever/store-paths.xz /os/store-paths.xz + apk add xz + nix_ver=$(xz -dc "$os_dir/usr/lib/systemd/system-preset/01-fix-eth-name.preset" + + # 可能是 /usr/lib/systemd/system-preset/ 或者 /lib/systemd/system-preset/ + if [ -d "$os_dir/usr/lib/systemd/system-preset" ]; then + echo "enable $service_name.service" >"$os_dir/usr/lib/systemd/system-preset/01-$service_name.preset" + else + echo "enable $service_name.service" >"$os_dir/lib/systemd/system-preset/01-$service_name.preset" + fi +} + +add_fix_eth_name_systemd_service() { + local os_dir=$1 + + # 无需执行 systemctl daemon-reload + # 因为 chroot 下执行会提示 Running in chroot, ignoring command 'daemon-reload' + download "$confhome/fix-eth-name.sh" "$os_dir/fix-eth-name.sh" + add_systemd_service "$os_dir" fix-eth-name +} + +get_frpc_url() { + wget "$confhome/get-frpc-url.sh" -O- | sh -s "$@" +} + +add_frpc_systemd_service_if_need() { + local os_dir=$1 + + if [ -s /configs/frpc.toml ]; then + mkdir -p "$os_dir/usr/local/bin" + mkdir -p "$os_dir/usr/local/etc/frpc" + + # 下载 frpc + # 注意下载的 frpc owner 不是 root:root + frpc_url=$(get_frpc_url linux) + basename=$(echo "$frpc_url" | awk -F/ '{print $NF}' | sed 's/\.tar\.gz//') + download "$frpc_url" "$os_dir/frpc.tar.gz" + # busybox tar 不支持 wildcard + # tar: */frpc: not found in archive + tar xzf "$os_dir/frpc.tar.gz" "$basename/frpc" -O >"$os_dir/usr/local/bin/frpc" + rm -f "$os_dir/frpc.tar.gz" + chmod a+x "$os_dir/usr/local/bin/frpc" + + # frpc conf + cp /configs/frpc.toml "$os_dir/usr/local/etc/frpc/frpc.toml" + + # 添加服务 + add_systemd_service "$os_dir" frpc + fi } basic_init() { @@ -1722,19 +1922,26 @@ basic_init() { done fi - allow_root_password_login $os_dir - allow_password_login $os_dir if is_need_change_ssh_port; then change_ssh_port $os_dir $ssh_port fi - # 修改密码 - change_root_password $os_dir + # 公钥/密码 + if is_need_set_ssh_keys; then + set_ssh_keys_and_del_password $os_dir + else + change_root_password $os_dir + allow_root_password_login $os_dir + allow_password_login $os_dir + fi # 下载 fix-eth-name.service # 即使开了 net.ifnames=0 也需要 # 因为 alpine live 和目标系统的网卡顺序可能不同 add_fix_eth_name_systemd_service $os_dir + + # frpc + add_frpc_systemd_service_if_need $os_dir } install_arch_gentoo_aosc() { @@ -1908,7 +2115,7 @@ EOF git_uri=https://mirror.nju.edu.cn/git/gentoo-portage.git else # github 不支持 ipv6 - is_ipv4_has_internet && git_uri=https://github.com/gentoo-mirror/gentoo.git || + is_any_ipv4_has_internet && git_uri=https://github.com/gentoo-mirror/gentoo.git || git_uri=https://anongit.gentoo.org/git/repo/gentoo.git fi @@ -1976,11 +2183,6 @@ EOF # preset-all 后多了很多服务,内存占用多了几十M chroot $os_dir systemctl preset-all fi - # 此时不能用 - # chroot $os_dir timedatectl set-timezone Asia/Shanghai - chroot $os_dir systemd-firstboot --force --timezone=Asia/Shanghai - # gentoo 不会自动创建 machine-id - clear_machine_id $os_dir # 网络配置 case "$network_app" in @@ -1995,6 +2197,8 @@ EOF cat -n net.cfg # 正常应该是 -D gentoo,但 alpine 的 cloud-init 包缺少 gentoo 配置 cloud-init devel net-convert -p net.cfg -k yaml -d out -D alpine -O networkd + + # 注意名字是 10-cloud-init-eth*.network,fix-eth-name.sh 会此文件名查找配置文件 cp out/etc/systemd/network/10-cloud-init-eth*.network $os_dir/etc/systemd/network/ # 删除网卡名匹配 @@ -2022,21 +2226,10 @@ EOF ;; esac - # 修正网卡名 - add_fix_eth_name_systemd_service $os_dir - # arch gentoo 网络配置是用 alpine cloud-init 生成的 # cloud-init 版本够新,因此无需修复 onlink 网关 - # ssh - chroot $os_dir systemctl enable sshd - allow_root_password_login $os_dir - if is_need_change_ssh_port; then - change_ssh_port $os_dir $ssh_port - fi - - # 修改密码 - change_root_password $os_dir + basic_init $os_dir # ntp 用 systemd 自带的 # TODO: vm agent + 随机数生成器 @@ -2099,8 +2292,8 @@ aria2c() { apk add coreutils fi - # 指定 bt 种子时没有链接,因此忽略错误 - echo "$@" | grep -o '(http|https|magnet):[^ ]*' || true + # 显示 url + show_url_in_args "$@" >&2 # 下载 tracker # 在 sub shell 里面无法保存变量,因此写入到文件 @@ -2237,6 +2430,10 @@ get_disk_logic_sector_size() { blockdev --getss "$1" } +is_4kn() { + [ "$(blockdev --getss "$1")" = 4096 ] +} + is_xda_gt_2t() { disk_size=$(get_disk_size /dev/$xda) disk_2t=$((2 * 1024 * 1024 * 1024 * 1024)) @@ -2261,8 +2458,8 @@ create_part() { # shellcheck disable=SC2154 if [ "$distro" = windows ]; then if ! size_bytes=$(get_link_file_size "$iso"); then - # 默认值,最大的iso 23h2 假设 7g - size_bytes=$((7 * 1024 * 1024 * 1024)) + # 默认值,目前最大的 iso 小于 8g + size_bytes=$((8 * 1024 * 1024 * 1024)) fi # 按iso容量计算分区大小 @@ -2335,7 +2532,7 @@ create_part() { # 向下取整 MiB # gpt 最后 33 个扇区是备份分区表,不可用 - # parted 会忽略最后不足 1MiB 的部分 + # parted 结束位置填 100% 时也会忽略最后不足 1MiB 的部分,我们模仿它 max_can_use_m=$((total_sector_count_except_backup_gpt * sector_size / 1024 / 1024)) echo "expect_m: $expect_m" @@ -2559,14 +2756,6 @@ mount_pseudo_fs() { fi } -get_yq_name() { - if grep -q '3\.1[6789]' /etc/alpine-release; then - echo yq - else - echo yq-go - fi -} - create_cloud_init_network_config() { ci_file=$1 recognize_static6=${2:-true} @@ -2578,7 +2767,7 @@ create_cloud_init_network_config() { mkdir -p "$(dirname "$ci_file")" touch "$ci_file" - apk add "$(get_yq_name)" + apk add yq-go need_set_dns4=false need_set_dns6=false @@ -2659,10 +2848,10 @@ create_cloud_init_network_config() { \"address\": \"$ipv6_addr\", \"gateway\": \"$ipv6_gateway\" } " $ci_file - # 无法设置 autoconf = false ? - if should_disable_accept_ra; then - yq -i ".network.config[$config_id].accept-ra = false" $ci_file - fi + fi + # 无法设置 autoconf = false ? + if should_disable_accept_ra; then + yq -i ".network.config[$config_id].accept-ra = false" $ci_file fi # 有 ipv6 但需设置 dns 的情况 @@ -2692,7 +2881,7 @@ create_cloud_init_network_config() { yq -i "del(.network.config[$config_id] | select(has(\"address\") | not))" $ci_file fi - apk del "$(get_yq_name)" + apk del yq-go # 查看文件 info "Cloud-init network config" @@ -2749,6 +2938,28 @@ EOF create_cloud_init_network_config "$ci_file" "$recognize_static6" "$recognize_ipv6_types" } +get_image_state() { + local os_dir=$1 + local image_state= + + # 如果 dd 镜像精简了 State.ini,则从注册表获取 + if state_ini=$(find_file_ignore_case $os_dir/Windows/Setup/State/State.ini); then + image_state=$(grep -i '^ImageState=' $state_ini | cut -d= -f2 | tr -d '\r') + fi + if [ -z "$image_state" ]; then + apk add hivex + hive=$(find_file_ignore_case $os_dir/Windows/System32/config/SOFTWARE) + image_state=$(hivexget $hive '\Microsoft\Windows\CurrentVersion\Setup\State' ImageState) + apk del hivex + fi + + if [ -n "$image_state" ]; then + echo "$image_state" + else + error_and_exit "Cannot get ImageState." + fi +} + modify_windows() { os_dir=$1 info "Modify Windows" @@ -2758,9 +2969,10 @@ modify_windows() { # https://learn.microsoft.com/windows-hardware/manufacture/desktop/add-a-custom-script-to-windows-setup # 判断用 SetupComplete 还是组策略 - state_ini=$os_dir/Windows/Setup/State/State.ini - cat $state_ini - if grep -q IMAGE_STATE_COMPLETE $state_ini; then + image_state=$(get_image_state "$os_dir") + echo "ImageState: $image_state" + + if [ "$image_state" = IMAGE_STATE_COMPLETE ]; then use_gpo=true else use_gpo=false @@ -2792,11 +3004,32 @@ modify_windows() { bats="$bats windows-set-netconf-$ethx.bat" done + # 5 frp + if [ -s /configs/frpc.toml ]; then + # 好像 win7 无法运行 frpc,暂时不管 + windows_arch=$(get_windows_arch_from_windows_drive "$os_dir" | to_lower) + if [ "$windows_arch" = amd64 ] || [ "$windows_arch" = arm64 ]; then + mkdir -p "$os_dir/frpc/" + url=$(get_frpc_url windows "$nt_ver") + download "$url" $os_dir/frpc/frpc.zip + # -j 去除文件夹 + # -C 筛选文件时不区分大小写,但 busybox zip 不支持 + unzip -o -j "$os_dir/frpc/frpc.zip" '*/frpc.exe' -d "$os_dir/frpc/" + rm -f "$os_dir/frpc/frpc.zip" + cp -f /configs/frpc.toml "$os_dir/frpc/frpc.toml" + download "$confhome/windows-frpc.xml" "$os_dir/frpc/frpc.xml" + download "$confhome/windows-frpc.bat" "$os_dir/frpc/frpc.bat" + bats="$bats frpc\frpc.bat" + else + warn "$windows_arch Not Support frpc" + fi + fi + if $use_gpo; then # 使用组策略 - gpt_ini=$os_dir/Windows/System32/GroupPolicy/gpt.ini - scripts_ini=$os_dir/Windows/System32/GroupPolicy/Machine/Scripts/scripts.ini + scripts_ini=$(get_path_in_correct_case $os_dir/Windows/System32/GroupPolicy/Machine/Scripts/scripts.ini) mkdir -p "$(dirname $scripts_ini)" + gpt_ini=$(get_path_in_correct_case $os_dir/Windows/System32/GroupPolicy/gpt.ini) # 备份 ini for file in $gpt_ini $scripts_ini; do @@ -2843,7 +3076,7 @@ EOF download $confhome/windows-del-gpo.bat $os_dir/windows-del-gpo.bat else # 使用 SetupComplete - setup_complete=$os_dir/Windows/Setup/Scripts/SetupComplete.cmd + setup_complete=$(get_path_in_correct_case $os_dir/Windows/Setup/Scripts/SetupComplete.cmd) mkdir -p "$(dirname $setup_complete)" # 添加到 C:\Setup\Scripts\SetupComplete.cmd 最前面 @@ -2862,6 +3095,9 @@ EOF # cat 可以保留权限 cat $setup_complete_mod >$setup_complete + + # 查看最终内容 + cat -n $setup_complete fi } @@ -2990,22 +3226,60 @@ chroot_systemctl_disable() { done } -disable_cloud_init() { +remove_or_disable_cloud_init() { os_dir=$1 - info "Disable Cloud-Init" - # 两种方法都可以 - - if [ -d $os_dir/etc/cloud ]; then - touch $os_dir/etc/cloud/cloud-init.disabled + if ! is_have_cmd_on_disk $os_dir cloud-init; then + return fi - for name in cloud-init-local cloud-init cloud-config cloud-final; do - for type in service socket; do + info "Remove or Disable Cloud-Init" + + # ubuntu-server-minimal ubuntu-cloud-minimal 都包含 cloud-init + # 用 iso 安装的 ubuntu 也有 cloud-init + # 因此不删除 ubuntu 的 cloud-init,而是禁用它 + + # iso 安装首次启动是通过 /etc/cloud/cloud.cfg.d/99-installer.cfg 初始化系统,包括: + # 1. 创建普通用户和密码,添加 ssh 登录公钥 + # 2. 创建 /etc/cloud/cloud-init.disabled + + if grep -iq ubuntu $os_dir/etc/os-release; then + # 模仿 iso 安装的 ubuntu,只创建 cloud-init.disabled,不禁用服务 + touch $os_dir/etc/cloud/cloud-init.disabled + else + # systemctl is-enabled cloud-init-hotplugd.service 状态是 static + # disable 会出现一堆提示信息,也无法 disable + for unit in $( + chroot $os_dir systemctl list-unit-files | + grep -E '^(cloud-init|cloud-init-.*|cloud-config|cloud-final)\.(service|socket)' | grep enabled | awk '{print $1}' + ); do # 服务不存在时会报错 - chroot $os_dir systemctl disable "$name.$type" 2>/dev/null || true + if chroot $os_dir systemctl -q is-enabled "$unit"; then + chroot $os_dir systemctl disable "$unit" + fi done - done + + for pkg_mgr in dnf yum zypper apt-get; do + if is_have_cmd_on_disk $os_dir $pkg_mgr; then + case $pkg_mgr in + dnf | yum) + chroot $os_dir $pkg_mgr remove -y cloud-init + rm -f $os_dir/etc/cloud/cloud.cfg.rpmsave + ;; + zypper) + # 加上 -u 才会删除依赖 + chroot $os_dir zypper remove -y -u cloud-init cloud-init-config-suse + ;; + apt-get) + # ubuntu 25.04 开始有 cloud-init-base + chroot_apt_remove $os_dir cloud-init cloud-init-base + chroot_apt_autoremove $os_dir + ;; + esac + break + fi + done + fi } disable_jeos_firstboot() { @@ -3021,6 +3295,9 @@ disable_jeos_firstboot() { # 服务不存在时会报错 chroot $os_dir systemctl disable "$name.service" 2>/dev/null || true done + + # 可选 + # chroot $os_dir zypper remove -y -u jeos-firstboot } create_network_manager_config() { @@ -3092,19 +3369,36 @@ EOF # 1. 禁用 selinux kdump # 2. 添加微码+固件 if [ -f $os_dir/etc/redhat-release ]; then + # 防止删除 cloud-init / 安装 firmware 时不够内存 + create_swap_if_ram_less_than 2048 $os_dir/swapfile + find_and_mount /boot find_and_mount /boot/efi mount_pseudo_fs $os_dir cp_resolv_conf $os_dir - disable_cloud_init $os_dir - # 可以直接用 alpine 的 cloud-init 生成 Network Manager 配置 create_cloud_init_network_config /net.cfg create_network_manager_config /net.cfg "$os_dir" rm /net.cfg - disable_selinux_kdump $os_dir + # TODO: fedora 43 eol 后删除 + # 删除 cloud-init 会删除依赖包 netcat + # 但是删除 netcat 时会报错 + # 因此保留 netcat 包 + # >>> Running %preun scriptlet: netcat-0:1.229-3.fc43.x86_64 + # >>> Error in %preun scriptlet: netcat-0:1.229-3.fc43.x86_64 + # >>> Scriptlet output: + # >>> failed to create admindir: No such file or directory + # >>> [RPM] %preun(netcat-1.229-3.fc43.x86_64) scriptlet failed, exit status 2 + # >>> [RPM] netcat-1.229-3.fc43.x86_64: erase failed + if [ "$distro" = fedora ] && [ "$releasever" = 43 ]; then + chroot $os_dir dnf mark user netcat -y + fi + remove_or_disable_cloud_init $os_dir + + disable_selinux $os_dir + disable_kdump $os_dir if fw_pkgs=$(get_ucode_firmware_pkgs) && [ -n "$fw_pkgs" ]; then is_have_cmd_on_disk $os_dir dnf && mgr=dnf || mgr=yum @@ -3128,7 +3422,7 @@ EOF find_and_mount /boot find_and_mount /boot/efi - disable_cloud_init $os_dir + remove_or_disable_cloud_init $os_dir # 获取当前开启的 Components, 后面要用 if [ -f $os_dir/etc/apt/sources.list.d/debian.sources ]; then @@ -3284,7 +3578,7 @@ EOF fi # opensuse - # 1. kernel-default-base 缺少 nvme 驱动,换成 kernel-default + # 1. kernel-default-base 缺少 nvme gve mlx5 mana 驱动,换成 kernel-default # 2. 添加微码+固件 # https://documentation.suse.com/smart/virtualization-cloud/html/minimal-vm/index.html if grep -q opensuse $os_dir/etc/os-release; then @@ -3294,17 +3588,24 @@ EOF find_and_mount /boot find_and_mount /boot/efi - disable_cloud_init $os_dir disable_jeos_firstboot $os_dir - # opensuse leap - if grep opensuse-leap $os_dir/etc/os-release; then + # 禁用 selinux + disable_selinux $os_dir + # opensuse leap 15.6 用 wicked + # opensuse leap 16.0 / tumbleweed 用 NetworkManager + if chroot $os_dir rpm -qi wicked; then # sysconfig ifcfg create_cloud_init_network_config $os_dir/net.cfg chroot $os_dir cloud-init devel net-convert \ -p /net.cfg -k yaml -d out -D opensuse -O sysconfig + # 删除 + # Created by cloud-init on instance boot automatically, do not edit. + # + sed -i '/^#/d' "$os_dir/out/etc/sysconfig/network/ifcfg-eth"* + for ethx in $(get_eths); do # 1. 修复甲骨文云重启后 ipv6 丢失 # https://github.com/openSUSE/wicked/issues/1058 @@ -3336,11 +3637,8 @@ EOF # 清理 rm -rf $os_dir/net.cfg $os_dir/out - fi - # opensuse tumbleweed - # network-manager - if grep opensuse-tumbleweed $os_dir/etc/os-release; then + else # 如果使用 cloud-init 则需要 touch NetworkManager.conf # 更新到 cloud-init 24.1 后删除 # touch $os_dir/etc/NetworkManager/NetworkManager.conf @@ -3351,39 +3649,57 @@ EOF rm /net.cfg fi - # 不能同时装 kernel-default-base 和 kernel-default - chroot $os_dir zypper remove -y kernel-default-base + # 选择新内核 + # 只有 leap 有 kernel-azure + if grep -iq leap $os_dir/etc/os-release && [ "$(get_cloud_vendor)" = azure ]; then + target_kernel='kernel-azure' + else + target_kernel='kernel-default' + fi + + # rpm -qi 不支持通配符 + origin_kernel=$(chroot $os_dir rpm -qa 'kernel-*' --qf '%{NAME}\n' | grep -v firmware) + if ! [ "$(echo "$origin_kernel" | wc -l)" -eq 1 ]; then + error_and_exit "Unexpected kernel installed: $origin_kernel" + fi + + # 16.0 能同时装 kernel-default-base 和 kernel-default + # tw 不能同时装 kernel-default-base 和 kernel-default + # 因此需要添加 --force-resolution 自动删除 kernel-default-base + if ! [ "$origin_kernel" = "$target_kernel" ]; then + # x86 必须设置一个密码,否则报错,arm 没有这个问题 + # Failed to get root password hash + # Failed to import /etc/uefi/certs/76B6A6A0.crt + # warning: %post(kernel-default-5.14.21-150500.55.83.1.x86_64) scriptlet failed, exit status 255 + need_password_workaround=false + if grep -q '^root:[:!*]' $os_dir/etc/shadow; then + need_password_workaround=true + fi + + if $need_password_workaround; then + echo "root:$(mkpasswd '')" | chroot $os_dir chpasswd -e + fi + # 安装新内核 + chroot $os_dir zypper install -y --force-resolution $target_kernel + # 删除旧内核 + if chroot $os_dir rpm -q $origin_kernel; then + chroot $os_dir zypper remove -y --force-resolution $origin_kernel + fi + if $need_password_workaround; then + chroot $os_dir passwd -d root + fi + fi # 固件+微码 if fw_pkgs=$(get_ucode_firmware_pkgs) && [ -n "$fw_pkgs" ]; then chroot $os_dir zypper install -y $fw_pkgs fi - # 选择新内核 - # 只有 leap 有 kernel-azure - if grep -q opensuse-leap $os_dir/etc/os-release && [ "$(get_cloud_vendor)" = azure ]; then - kernel='kernel-azure' - else - kernel='kernel-default' - fi - - # x86 必须设置一个密码,否则报错,arm 没有这个问题 - # Failed to get root password hash - # Failed to import /etc/uefi/certs/76B6A6A0.crt - # warning: %post(kernel-default-5.14.21-150500.55.83.1.x86_64) scriptlet failed, exit status 255 - if grep -q '^root:[:!*]' $os_dir/etc/shadow; then - echo "root:$(mkpasswd '')" | chroot $os_dir chpasswd -e - chroot $os_dir zypper install -y $kernel - chroot $os_dir passwd -d root - else - chroot $os_dir zypper install -y $kernel - fi + # 最后才删除 cloud-init + # 因为生成 sysconfig 网络配置要用目标系统的 cloud-init + remove_or_disable_cloud_init $os_dir restore_resolv_conf $os_dir - - # 删除 swap - swapoff -a - rm -f $os_dir/swapfile fi # arch 云镜像 @@ -3407,7 +3723,7 @@ EOF # 在这里修改密码,而不是用cloud-init,因为我们的默认密码太弱 is_password_plaintext && sed -i 's/enforce=everyone/enforce=none/' $os_dir/etc/security/passwdqc.conf - echo "root:$(get_password_linux_sha512)" | chroot $os_dir chpasswd -e + change_root_password $os_dir is_password_plaintext && sed -i 's/enforce=none/enforce=everyone/' $os_dir/etc/security/passwdqc.conf # 下载仓库,选择 profile @@ -3451,6 +3767,10 @@ EOF if [ -f "$ci_file" ]; then cat -n "$ci_file" fi + + # 删除 swap + swapoff -a + rm -f $os_dir/swapfile } modify_os_on_disk() { @@ -3483,15 +3803,31 @@ modify_os_on_disk() { # find /mnt/c -iname windows -type d -maxdepth 1 # find: /mnt/c/pagefile.sys: Permission denied # find: /mnt/c/swapfile.sys: Permission denied - # shellcheck disable=SC2010 - if ls -d /os/*/ | grep -i '/windows/' 2>/dev/null; then + # shellcheck disable=SC1090 + # find_file_ignore_case 也在这个文件里面 + . <(wget -O- $confhome/windows-driver-utils.sh) + if find_file_ignore_case /os/Windows/System32/ntoskrnl.exe >/dev/null 2>&1; then + # 其他地方会用到 + is_windows() { true; } # 重新挂载为读写、忽略大小写 umount /os - mount -t ntfs3 -o nocase /dev/$part /os - # 有休眠文件时无法挂载成读写,提醒用户并退出脚本 - if mount | grep ' /os ' | grep -wq ro; then - error_and_exit "Can't mount windows partition /dev/$part as rw." + if ! { mount -t ntfs3 -o nocase,rw /dev/$part /os && + mount | grep -w 'on /os type' | grep -wq rw; }; then + # 显示警告 + warn "Can't normally mount windows partition /dev/$part as rw." + dmesg | grep -F "ntfs3($part):" || true + # 有可能 fallback 挂载成 ro, 因此先取消挂载 + if mount | grep -wq 'on /os type'; then + umount /os + fi + # 尝试修复并强制挂载 + apk add ntfs-3g-progs + ntfsfix /dev/$part + apk del ntfs-3g-progs + mount -t ntfs3 -o nocase,rw,force /dev/$part /os fi + # 获取版本号,其他地方会用到 + get_windows_version_from_windows_drive /os modify_windows /os return fi @@ -3528,6 +3864,10 @@ create_swap() { swapfile=$2 if ! grep $swapfile /proc/swaps; then + # 用兼容 btrfs 的方式创建 swapfile + truncate -s 0 $swapfile + # 如果分区不支持 chattr +C 会显示错误但返回值是 0 + chattr +C $swapfile 2>/dev/null fallocate -l ${swapsize}M $swapfile chmod 0600 $swapfile mkswap $swapfile @@ -3535,6 +3875,21 @@ create_swap() { fi } +set_ssh_keys_and_del_password() { + os_dir=$1 + info 'set ssh keys' + + # 添加公钥 + ( + umask 077 + mkdir -p $os_dir/root/.ssh + cat /configs/ssh_keys >$os_dir/root/.ssh/authorized_keys + ) + + # 删除密码 + chroot $os_dir passwd -d root +} + # 除了 alpine 都会用到 change_ssh_conf() { os_dir=$1 @@ -3542,20 +3897,24 @@ change_ssh_conf() { value=$3 sub_conf=$4 - # arch 没有 /etc/ssh/sshd_config.d/ 文件夹 - # opensuse tumbleweed 没有 /etc/ssh/sshd_config - # 有 /etc/ssh/sshd_config.d/ 文件夹 - # 有 /usr/etc/ssh/sshd_config - if { grep -q 'Include.*/etc/ssh/sshd_config.d' $os_dir/etc/ssh/sshd_config || - grep -q '^Include.*/etc/ssh/sshd_config.d/' $os_dir/usr/etc/ssh/sshd_config; } 2>/dev/null; then + if line="^$key .*" && grep -Exq "$line" $os_dir/etc/ssh/sshd_config 2>/dev/null; then + # 如果 sshd_config 存在此 key(非注释状态),则替换 + sed -Ei "s/$line/$key $value/" $os_dir/etc/ssh/sshd_config + elif include_line='^Include.*/etc/ssh/sshd_config.d' && + # arch 没有 /etc/ssh/sshd_config.d/ 文件夹 + # opensuse tumbleweed 没有 /etc/ssh/sshd_config + # 有 /etc/ssh/sshd_config.d/ 文件夹 + # 有 /usr/etc/ssh/sshd_config + { grep -q "$include_line" $os_dir/etc/ssh/sshd_config || + grep -q "$include_line" $os_dir/usr/etc/ssh/sshd_config; } 2>/dev/null; then mkdir -p $os_dir/etc/ssh/sshd_config.d/ echo "$key $value" >"$os_dir/etc/ssh/sshd_config.d/$sub_conf" else - # 如果 sshd_config 存在此 key,则替换 + # 如果 sshd_config 存在此 key (无论是否已注释),则替换,包括删除注释 # 否则追加 - line="^#?$key .*" + line="^[# ]*$key .*" if grep -Exq "$line" $os_dir/etc/ssh/sshd_config; then - sed -Eiq "s/$line/$key $value/" $os_dir/etc/ssh/sshd_config + sed -Ei "s/$line/$key $value/" $os_dir/etc/ssh/sshd_config else echo "$key $value" >>$os_dir/etc/ssh/sshd_config fi @@ -3564,17 +3923,25 @@ change_ssh_conf() { allow_password_login() { os_dir=$1 - change_ssh_conf "$os_dir" PasswordAuthentication yes 02-PasswordAuthenticaton.conf + change_ssh_conf "$os_dir" PasswordAuthentication yes 01-PasswordAuthentication.conf } -# arch gentoo 常规安装用 allow_root_password_login() { os_dir=$1 - change_ssh_conf "$os_dir" PermitRootLogin yes 01-permitrootlogin.conf + # opensuse 16/tumbleweed 安装 openssh-server-config-rootlogin + # 会生成 /usr/etc/ssh/sshd_config.d/50-permit-root-login.conf + # 但是如果用户删除了此文件,包有更新的话,可能会重新创建这个文件? + # 因此先不用这个方法 + if false && [ -f $os_dir/etc/os-release ] && + grep -iq opensuse $os_dir/etc/os-release && + ! grep -iq 15.6 $os_dir/etc/os-release; then + chroot $os_dir zypper install -y openssh-server-config-rootlogin + else + change_ssh_conf "$os_dir" PermitRootLogin yes 01-permitrootlogin.conf + fi } -# arch gentoo 常规安装用 change_ssh_port() { os_dir=$1 ssh_port=$2 @@ -3629,10 +3996,9 @@ change_root_password() { fi } -disable_selinux_kdump() { +disable_selinux() { os_dir=$1 - # selinux # https://access.redhat.com/solutions/3176 # centos7 也建议将 selinux 开关写在 cmdline # grep selinux=0 /usr/lib/dracut/modules.d/98selinux/selinux-loadpolicy.sh @@ -3640,9 +4006,28 @@ disable_selinux_kdump() { if [ -f $os_dir/etc/selinux/config ]; then sed -i 's/^SELINUX=enforcing/SELINUX=disabled/g' $os_dir/etc/selinux/config fi - chroot $os_dir grubby --update-kernel ALL --args selinux=0 - # kdump + # opensuse 没有安装 grubby + if is_have_cmd_on_disk $os_dir grubby; then + # grubby 只处理 GRUB_CMDLINE_LINUX,不会处理 GRUB_CMDLINE_LINUX_DEFAULT + # rocky 的 GRUB_CMDLINE_LINUX_DEFAULT 有 crashkernel=auto + chroot $os_dir grubby --update-kernel ALL --args selinux=0 + + # el7 上面那条 grubby 命令不能设置 /etc/default/grub + sed -i 's/selinux=1/selinux=0/' $os_dir/etc/default/grub + else + # 有可能没有 selinux 参数,但现在的镜像没有这个问题 + # sed -Ei 's/[[:space:]]?(security|selinux|enforcing)=[^ ]*//g' $os_dir/etc/default/grub + sed -i 's/selinux=1/selinux=0/' $os_dir/etc/default/grub + + # 如果需要用 snapshot 可以用 transactional-update grub.cfg + chroot $os_dir grub2-mkconfig -o /boot/grub2/grub.cfg + fi +} + +disable_kdump() { + os_dir=$1 + # grubby 只处理 GRUB_CMDLINE_LINUX,不会处理 GRUB_CMDLINE_LINUX_DEFAULT # rocky 的 GRUB_CMDLINE_LINUX_DEFAULT 有 crashkernel=auto @@ -3792,16 +4177,21 @@ chroot_dnf() { fi } -chroot_apt_install() { +chroot_apt_update() { os_dir=$1 - shift current_hash=$(cat $os_dir/etc/apt/sources.list $os_dir/etc/apt/sources.list.d/*.sources 2>/dev/null | md5sum) if ! [ "$saved_hash" = "$current_hash" ]; then chroot $os_dir apt-get update saved_hash="$current_hash" fi +} +chroot_apt_install() { + os_dir=$1 + shift + + chroot_apt_update $os_dir DEBIAN_FRONTEND=noninteractive chroot $os_dir apt-get install -y "$@" } @@ -3809,17 +4199,23 @@ chroot_apt_remove() { os_dir=$1 shift + # minimal 镜像 删除 grub-pc 时会安装 grub-efi-amd64 + # 因此需要先更新索引 + chroot_apt_update $os_dir + # 不能用 apt remove --purge -y xxx yyy # 因为如果索引里没有其中一个,会报错,另一个也不会删除 - # 因此需要分开删除 - for package in "$@"; do + local pkgs= + for pkg in "$@"; do # apt list 会提示 WARNING: apt does not have a stable CLI interface. Use with caution in scripts. # 但又不能用 apt-get list - if chroot $os_dir apt list --installed "$package" | grep -q installed; then - # 删除 resolvconf 时会弹出建议重启,因此添加 noninteractive - DEBIAN_FRONTEND=noninteractive chroot $os_dir apt-get remove --purge -y "$package" + if chroot $os_dir apt list --installed "$pkg" | grep -q installed; then + pkgs="$pkgs $pkg" fi done + + # 删除 resolvconf 时会弹出建议重启,因此添加 noninteractive + DEBIAN_FRONTEND=noninteractive chroot $os_dir apt-get remove --purge --allow-remove-essential -y $pkgs } chroot_apt_autoremove() { @@ -3954,8 +4350,11 @@ install_fnos() { # chroot $os_dir update-initramfs -u # 更改密码 - # chroot $os_dir passwd -d root - echo "root:$(get_password_linux_sha512)" | chroot $os_dir chpasswd -e + if is_need_set_ssh_keys; then + set_ssh_keys_and_del_password $os_dir + else + change_root_password $os_dir + fi # ssh root 登录,测试用 if false; then @@ -4000,6 +4399,9 @@ install_fnos() { # 修正网卡名 add_fix_eth_name_systemd_service $os_dir + + # frpc + add_frpc_systemd_service_if_need $os_dir } install_qcow_by_copy() { @@ -4019,7 +4421,8 @@ install_qcow_by_copy() { del_default_user /os # selinux kdump - disable_selinux_kdump /os + disable_selinux /os + disable_kdump /os # el7 删除 machine-id 后不会自动重建 clear_machine_id /os @@ -4039,7 +4442,7 @@ install_qcow_by_copy() { fi # el7 yum 可能会使用 ipv6,即使没有 ipv6 网络 - if [ "$(cat /dev/netconf/eth*/ipv6_has_internet | sort -u)" = 0 ]; then + if [ "$(cat /dev/netconf/*/ipv6_has_internet | sort -u)" = 0 ]; then echo 'ip_resolve=4' >>/os/etc/yum.conf fi @@ -4113,12 +4516,36 @@ install_qcow_by_copy() { # 安装引导 if is_efi; then # 只有centos 和 oracle x86_64 镜像没有efi,其他系统镜像已经从efi分区复制了文件 - if [ -z "$efi_part" ]; then - remove_grub_conflict_files - # openeuler 自带 grub2-efi-ia32,此时安装 grub2-efi 提示已经安装了 grub2-efi-ia32,不会继续安装 grub2-efi-x64 - [ "$(uname -m)" = x86_64 ] && arch=x64 || arch=aa64 + # openeuler 自带 grub2-efi-ia32,此时安装 grub2-efi 提示已经安装了 grub2-efi-ia32,不会继续安装 grub2-efi-x64 + + # 假设极端情况,qcow2 制作时,安装 grub2-efi-x64 时没有挂载 efi 分区,那么 efi 文件会在系统分区下 + # 但我们复制系统分区时挂载了 /boot/efi,因此 efi 文件会正确地复制到 efi 分区 + # 因此无需判断 qcow2 的 efi 是否是独立分区 + + # rhel 镜像没有源,直接 yum install 安装可能会报错 + # 因此如果已经安装了要用的包就不再运行 yum install + need_install=false + need_remove_grub_conflict_files=false + + [ "$(uname -m)" = x86_64 ] && arch=x64 || arch=aa64 + if ! chroot $os_dir rpm -qi grub2-efi-$arch; then + need_install=true + need_remove_grub_conflict_files=true + elif ! chroot $os_dir rpm -qi shim-$arch || ! chroot $os_dir rpm -qi efibootmgr; then + need_install=true + fi + + if $need_install; then + if $need_remove_grub_conflict_files; then + remove_grub_conflict_files + fi chroot_dnf install efibootmgr grub2-efi-$arch shim-$arch fi + # openeuler arm 25.09 云镜像里面的 grubaa64.efi 是用于 mbr 分区表,$root 是 hd0,msdos1 + # 因此要重新下载 $root 是 hd0,gpt1 的 grubaa64.efi + if $need_reinstall_grub_efi; then + chroot_dnf reinstall grub2-efi-$arch + fi else # bios remove_grub_conflict_files @@ -4161,8 +4588,12 @@ EOF fi # 主 grub.cfg - # --update-bls-cmdline - chroot /os/ grub2-mkconfig -o "$grub_o_cfg" + if ls /os/boot/loader/entries/*.conf >/dev/null 2>&1 && + chroot /os/ grub2-mkconfig --help | grep -q update-bls-cmdline; then + chroot /os/ grub2-mkconfig -o "$grub_o_cfg" --update-bls-cmdline + else + chroot /os/ grub2-mkconfig -o "$grub_o_cfg" + fi # 网络配置 # el7/8 sysconfig @@ -4223,13 +4654,13 @@ EOF # 清理 rm -rf $os_dir/net.cfg $os_dir/out + # 删除 # Created by cloud-init on instance boot automatically, do not edit. # 修正网络配置问题并显示文件 - sed -i '/^IPV[46]_FAILURE_FATAL=/d' $os_dir/etc/sysconfig/network-scripts/ifcfg-* + sed -i -e '/^IPV[46]_FAILURE_FATAL=/d' -e '/^#/d' $os_dir/etc/sysconfig/network-scripts/ifcfg-* for file in "$os_dir/etc/sysconfig/network-scripts/ifcfg-"*; do if grep -q '^DHCPV6C=yes' "$file"; then sed -i '/^IPV6_AUTOCONF=no/d' "$file" fi - cat -n "$file" done else @@ -4284,6 +4715,24 @@ configfile \$prefix/grub.cfg EOF fi + # 避免 do-release-upgrade 时自动执行 dpkg-reconfigure grub-xx 但是 efi/biosgrub 分区不存在而导致报错 + # shellcheck disable=SC2046 + chroot_apt_remove $os_dir $(is_efi && echo 'grub-pc' || echo 'grub-efi*' 'shim*') + chroot_apt_autoremove $os_dir + + # 安装 mbr + if ! is_efi; then + if false; then + # debconf-show grub-pc + # 每次开机硬盘名字可能不一样,但是 debian netboot 安装后也是设置了 grub-pc/install_devices + echo grub-pc grub-pc/install_devices multiselect /dev/$xda | chroot $os_dir debconf-set-selections # 22.04 + echo grub-pc grub-pc/cloud_style_installation boolean true | chroot $os_dir debconf-set-selections # 24.04 + chroot $os_dir dpkg-reconfigure -f noninteractive grub-pc + else + chroot $os_dir grub-install /dev/$xda + fi + fi + # 自带内核: # 常规版本 generic # minimal 20.04/22.04 kvm # 后台 vnc 无显示 @@ -4319,6 +4768,9 @@ EOF # 网络配置 # 18.04+ netplan if is_have_cmd_on_disk $os_dir netplan; then + # 避免删除 cloud-init 后,minimal 镜像的 netplan.io 被 autoremove + chroot $os_dir apt-mark manual netplan.io + # 生成 cloud-init 网络配置 create_cloud_init_network_config $os_dir/net.cfg @@ -4343,6 +4795,9 @@ EOF rm -rf $os_dir/net.cfg fi else + # 避免删除 cloud-init 后 ifupdown 被 autoremove + chroot $os_dir apt-mark manual ifupdown + # 16.04 镜像用 ifupdown/networking 管理网络 # 要安装 resolveconf,不然 /etc/resolv.conf 为空 chroot_apt_install $os_dir resolvconf @@ -4360,11 +4815,6 @@ EOF fi fi - # 安装 bios 引导 - if ! is_efi; then - chroot $os_dir grub-install /dev/$xda - fi - # 更改 efi 目录的 grub.cfg 写死的 fsuuid # 因为 24.04 fsuuid 对应 boot 分区 efi_grub_cfg=$os_dir/boot/efi/EFI/ubuntu/grub.cfg @@ -4442,25 +4892,76 @@ EOF lvchange -ay "$vg" fi - # TODO: 系统分区应该是最后一个分区 - # 选择最大分区 - os_part=$(lsblk /dev/nbd0p* --sort SIZE -no NAME,FSTYPE | grep -E 'ext4|xfs' | tail -1 | awk '{print $1}') - efi_part=$(lsblk /dev/nbd0p* --sort SIZE -no NAME,PARTTYPE | grep -i "$EFI_UUID" | awk '{print $1}') - # 排除前两个,再选择最大分区 - # almalinux9 boot 分区的类型不是规定的 uuid - # openeuler boot 分区是 fat 格式 - boot_part=$(lsblk /dev/nbd0p* --sort SIZE -no NAME,FSTYPE | grep -E 'ext4|xfs|fat' | awk '{print $1}' | - grep -vx "$os_part" | grep -vx "$efi_part" | tail -1 | awk '{print $1}') + mount_nouuid() { + part_fstype= + for arg in "$@"; do + case "$arg" in + /dev/*) + part_fstype=$(lsblk -no FSTYPE "$arg") + break + ;; + esac + done - if $is_lvm_image; then - os_part="mapper/$os_part" + case "$part_fstype" in + xfs) mount -o nouuid "$@" ;; + *) mount "$@" ;; + esac + } + + # 可以直接选择最后一个分区为系统分区? + # almalinux9 boot 分区的类型不是规定的 uuid + # openeuler boot 分区是 vfat 格式 + # openeuler arm 25.09 是 mbr 分区表, efi boot 是同一个分区,vfat 格式 + + info "qcow2 Partitions check" + + # 检测分区表类型 + partition_table_format=$(get_partition_table_format /dev/nbd0) + need_reinstall_grub_efi=false + if is_efi && [ "$partition_table_format" = "msdos" ]; then + need_reinstall_grub_efi=true fi + # 通过检测文件判断是什么分区 + os_part='' boot_part='' efi_part='' + mkdir -p /nbd-test + for part in $(lsblk /dev/nbd0p* --sort SIZE -no NAME,FSTYPE | + grep -E ' (ext4|xfs|fat|vfat)$' | awk '{print $1}' | tac); do + mapper_part=$part + if $is_lvm_image && [ -e /dev/mapper/$part ]; then + mapper_part=mapper/$part + fi + + if mount_nouuid -o ro /dev/$mapper_part /nbd-test; then + if { ls /nbd-test/etc/os-release || ls /nbd-test/*/etc/os-release; } 2>/dev/null; then + os_part=$mapper_part + fi + # shellcheck disable=SC2010 + # 当 boot 作为独立分区时,vmlinuz 等文件在根目录 + # 当 boot 不是独立分区时,vmlinuz 等文件在 /boot 目录 + if ls /nbd-test/ /nbd-test/boot/ 2>/dev/null | grep -Ei '^(vmlinuz|initrd|initramfs)'; then + boot_part=$mapper_part + fi + # mbr + efi 引导 ,分区表没有 esp guid + # 因此需要用 efi 文件判断是否 efi 分区 + # efi 文件可能在 efi 目录的子目录,子目录层数不定 + if find /nbd-test/ -type f -ipath '/nbd-test/EFI/*.efi' 2>/dev/null | grep .; then + efi_part=$mapper_part + fi + umount /nbd-test + fi + done + info "qcow2 Partitions" lsblk -f /dev/nbd0 -o +PARTTYPE + # 显示 OS/EFI/Boot 文件在哪个分区 + echo "---" + echo "Table: $partition_table_format" echo "Part OS: $os_part" echo "Part EFI: $efi_part" echo "Part Boot: $boot_part" + echo "---" # 分区寻找方式 # 系统/分区 cmdline:root fstab:efi @@ -4468,25 +4969,16 @@ EOF # ubuntu PARTUUID LABEL=UEFI # 其他el/ol UUID UUID - # read -r os_part_uuid os_part_label < <(lsblk /dev/$os_part -no UUID,LABEL) - os_part_uuid=$(lsblk /dev/$os_part -no UUID) - os_part_label=$(lsblk /dev/$os_part -no LABEL) - os_part_fstype=$(lsblk /dev/$os_part -no FSTYPE) + IFS=, read -r os_part_uuid os_part_label os_part_fstype \ + < <(lsblk /dev/$os_part -rno UUID,LABEL,FSTYPE | tr ' ' ,) if [ -n "$efi_part" ]; then - efi_part_uuid=$(lsblk /dev/$efi_part -no UUID) - efi_part_label=$(lsblk /dev/$efi_part -no LABEL) + IFS=, read -r efi_part_uuid efi_part_label \ + < <(lsblk /dev/$efi_part -rno UUID,LABEL | tr ' ' ,) fi mkdir -p /nbd /nbd-boot /nbd-efi - mount_nouuid() { - case "$os_part_fstype" in - ext4) mount "$@" ;; - xfs) mount -o nouuid "$@" ;; - esac - } - # 使用目标系统的格式化程序 # centos8 如果用alpine格式化xfs,grub2-mkconfig和grub2里面都无法识别xfs分区 mount_nouuid /dev/$os_part /nbd/ @@ -4522,16 +5014,17 @@ EOF cp -a /nbd/* /os/ umount /nbd/ - # 复制boot分区,如果有 - if [ -n "$boot_part" ]; then + # 复制独立的boot分区,如果有 + if [ -n "$boot_part" ] && ! [ "$boot_part" = "$os_part" ]; then echo Copying boot partition... mount_nouuid -o ro /dev/$boot_part /nbd-boot/ cp -a /nbd-boot/* /os/boot/ umount /nbd-boot/ fi - # 复制efi分区,如果有 - if [ -n "$efi_part" ]; then + # 复制独立的efi分区,如果有 + # 如果 efi 和 boot 是同一个分区,则复制 boot 分区时已经复制了 efi 分区的文件 + if [ -n "$efi_part" ] && ! [ "$efi_part" = "$os_part" ] && ! [ "$efi_part" = "$boot_part" ]; then echo Copying efi partition... mount -o ro /dev/$efi_part /nbd-efi/ cp -a /nbd-efi/* /os/boot/efi/ @@ -4555,11 +5048,11 @@ EOF umount /os/ umount /installer/ - # 如果镜像有efi分区,复制其uuid + # 如果镜像有独立的efi分区(包括efi+boot在同一个分区),复制其uuid # 如果有相同uuid的fat分区,则无法挂载 # 所以要先复制efi分区,断开nbd再复制uuid # 复制uuid前要取消挂载硬盘 efi 分区 - if is_efi && [ -n "$efi_part_uuid" ]; then + if is_efi && [ -n "$efi_part_uuid" ] && ! [ "$efi_part" = "$os_part" ]; then info "Copy efi partition uuid" apk add mtools mlabel -N "$(echo $efi_part_uuid | sed 's/-//')" -i /dev/$xda*1 ::$efi_part_label @@ -4593,9 +5086,12 @@ EOF esac # 基本配置 - disable_cloud_init /os basic_init /os + # 最后才删除 cloud-init + # 因为生成 netplan/sysconfig 网络配置要用目标系统的 cloud-init + remove_or_disable_cloud_init /os + # 删除 swapfile swapoff -a rm -f /os/swapfile @@ -4692,6 +5188,7 @@ dd_qcow() { # 将前1M从内存 dd 到硬盘 umount /installer/ dd if=/first-1M of=/dev/$xda + rm -f /first-1M # gpt 分区表开头记录了备份分区表的位置 # 如果 qcow2 虚拟容量 大于 实际硬盘容量 @@ -4759,6 +5256,7 @@ fix_gpt_backup_partition_table_by_sgdisk() { # 适用于 DD 后修复 gpt 备份分区表 fix_gpt_backup_partition_table_by_parted() { + apk add parted parted /dev/$xda -f -s print update_part } @@ -4993,7 +5491,7 @@ get_server_name_by_build_ver() { echo 2012 elif [ "$build_ver" -ge 7600 ]; then echo 2008 r2 - elif [ "$build_ver" -ge 6000 ]; then + elif [ "$build_ver" -ge 6001 ]; then echo 2008 else error_and_exit "Unknown Build Version: $build_ver" @@ -5027,6 +5525,8 @@ get_cloud_vendor() { echo huawei elif is_dmi_contains 'Alibaba Cloud'; then echo aliyun + elif is_dmi_contains 'Tencent Cloud'; then + echo qcloud fi } @@ -5040,12 +5540,69 @@ is_absolute_path() { [[ "$1" = "/*" ]] } +# 注意使用方法是 list=$(list_add "$list" "$item_to_add") +list_add() { + local list=$1 + local item_to_add=$2 + if [ -n "$list" ]; then + echo "$list" + fi + echo "$item_to_add" +} + +is_list_has() { + local list=$1 + local item=$2 + echo "$list" | grep -qFx "$item" +} + +# hivexget 是 shell 脚本,开头是 #!/bin/bash +# 但 alpine 没安装 bash,直接运行 hivexget 会报错 +hivexget() { + ash "$(which hivexget)" "$@" +} + +get_windows_type_from_windows_drive() { + local os_dir=$1 + + apk add hivex + software_hive=$(find_file_ignore_case $os_dir/Windows/System32/config/SOFTWARE) + system_hive=$(find_file_ignore_case $os_dir/Windows/System32/config/SYSTEM) + installation_type=$(hivexget $software_hive '\Microsoft\Windows NT\CurrentVersion' InstallationType || true) + product_type=$(hivexget $system_hive '\ControlSet001\Control\ProductOptions' ProductType || true) + apk del hivex + + # 根据 win11 multi-session 的情况 + # InstallationType 比 ProductType 准确 + + # Vista wim 和注册表都没有 InstallationType + case "$installation_type" in + Client | Embedded) echo client ;; + Server | 'Server Core') echo server ;; + *) case "$product_type" in + WinNT) echo client ;; + ServerNT) echo server ;; + *) error_and_exit "Unknown Windows Type" ;; + esac ;; + esac +} + +get_windows_arch_from_windows_drive() { + local os_dir=$1 + + apk add hivex + hive=$(find_file_ignore_case $os_dir/Windows/System32/config/SYSTEM) + # 没有 CurrentControlSet + hivexget $hive 'ControlSet001\Control\Session Manager\Environment' PROCESSOR_ARCHITECTURE + apk del hivex +} + install_windows() { get_wim_prop() { wim=$1 property=$2 - wiminfo "$wim" | grep -i "^$property:" | cut -d: -f2- | xargs + wiminfo "$wim" | grep -i "^$property:" | cut -d: -f2- | trim } get_image_prop() { @@ -5053,20 +5610,39 @@ install_windows() { index=$2 property=$3 - wiminfo "$wim" "$index" | grep -i "^$property:" | cut -d: -f2- | xargs + wiminfo "$wim" "$index" | grep -i "^$property:" | cut -d: -f2- | trim } info "Process windows iso" + mkdir -p /iso /wim + + # find_file_ignore_case 也在这个文件里面 + # shellcheck disable=SC1090 + . <(wget -O- $confhome/windows-driver-utils.sh) apk add wimlib download $iso /os/windows.iso - mkdir -p /iso mount -o ro /os/windows.iso /iso + sources_boot_wim=$( + cd /iso + find_file_ignore_case sources/boot.wim 2>/dev/null || + error_and_exit "can't find boot.wim" + ) + + # 一般镜像是 install.wim + # en_server_install_disc_windows_home_server_2011_x64_dvd_658487.iso 是 Install.wim + source_install_wim=$( + cd /iso + { find_file_ignore_case sources/install.wim || + find_file_ignore_case sources/install.esd; } 2>/dev/null || + error_and_exit "can't find install.wim or install.esd" + ) + # 防止用了不兼容架构的 iso - boot_index=$(get_wim_prop /iso/sources/boot.wim 'Boot Index') - arch_wim=$(get_image_prop /iso/sources/boot.wim "$boot_index" 'Architecture' | to_lower) + boot_index=$(get_wim_prop "/iso/$sources_boot_wim" 'Boot Index') + arch_wim=$(get_image_prop "/iso/$sources_boot_wim" "$boot_index" 'Architecture' | to_lower) if ! { { [ "$(uname -m)" = "x86_64" ] && [ "$arch_wim" = x86_64 ]; } || { [ "$(uname -m)" = "x86_64" ] && [ "$arch_wim" = x86 ]; } || @@ -5080,17 +5656,12 @@ install_windows() { error_and_exit "EFI machine can't install 32-bit Windows." fi - if [ -e /iso/sources/install.esd ]; then - iso_install_wim=/iso/sources/install.esd - install_wim=/os/installer/sources/install.esd - else - iso_install_wim=/iso/sources/install.wim - install_wim=/os/installer/sources/install.wim - fi + iso_install_wim=/iso/$source_install_wim + install_wim=/os/installer/$source_install_wim # 匹配映像版本 # 需要整行匹配,因为要区分 Windows 10 Pro 和 Windows 10 Pro for Workstations - image_count=$(wiminfo $iso_install_wim | grep "^Image Count:" | cut -d: -f2 | xargs) + image_count=$(wiminfo $iso_install_wim | grep "^Image Count:" | cut -d: -f2 | trim) all_image_names=$(wiminfo $iso_install_wim | grep ^Name: | sed 's/^Name: *//') info "Images Count: $image_count" echo "$all_image_names" @@ -5099,12 +5670,14 @@ install_windows() { if [ "$image_count" = 1 ]; then # 只有一个版本就用那个版本 image_name=$all_image_names + image_index=1 else while true; do # 匹配成功 # 改成正确的大小写 if matched_image_name=$(echo "$all_image_names" | grep -ix "$image_name"); then image_name=$matched_image_name + image_index=$(wiminfo "$iso_install_wim" "$image_name" | grep 'Index:' | awk '{print $NF}') break fi @@ -5125,12 +5698,13 @@ install_windows() { fi get_selected_image_prop() { - get_image_prop "$iso_install_wim" "$image_name" "$1" + get_image_prop "$iso_install_wim" "$image_index" "$1" } # 多会话的信息来自注册表,因为没有官方 iso # Installation Type: + # https://github.com/search?q=InstallationType+Client+Embedded+Server+Core&type=code # - Client (普通 windows) # - Server (windows server 带桌面体验) # - Server Core (windows server 不带桌面体验) @@ -5148,28 +5722,56 @@ install_windows() { # Product Suite: # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/exinit/productsuite.htm - # - Terminal Server (普通 windows) - # - Enterprise (windows server 带桌面体验) - # - Enterprise (windows server 不带桌面体验) - # - Terminal Server (WES7 / Thin PC) - # - ? (windows 10/11 enterprise 多会话) + # - Terminal Server (普通 windows) + # - Enterprise (windows server 2025 带桌面体验) + # - Enterprise (windows server 2025 不带桌面体验) + # - Terminal Server (windows server 2012 R2 评估板 带桌面体验,注册表也是这个值) + # - Terminal Server (windows server 2022 R2 评估板 不带桌面体验,注册表也是这个值) + # - Terminal Server (WES7 / Thin PC) + # - ? (windows 10/11 enterprise 多会话) # 用内核版本号筛选驱动 # 使得可以安装 Hyper-V Server / Azure Stack HCI 等 Windows Server 变种 - nt_ver=$(get_selected_image_prop "Major Version").$(get_selected_image_prop "Minor Version") - build_ver=$(get_selected_image_prop "Build") - product_suite=$(get_selected_image_prop "Product Suite") + # 7601.24214.180801-1700.win7sp1_ldr_escrow_CLIENT_ULTIMATE_x64FRE_en-us.iso wim 没有 Installation Type + # Vista wim 和 注册表 都没有 InstallationType + if false; then + nt_ver=$(get_selected_image_prop "Major Version").$(get_selected_image_prop "Minor Version") + build_ver=$(get_selected_image_prop "Build") + installation_type=$(get_selected_image_prop "Installation Type") + fi - case "$product_suite" in - 'Terminal Server') - windows_type=client - product_ver=$(get_client_name_by_build_ver "$build_ver") - ;; - *) - windows_type=server - product_ver=$(get_server_name_by_build_ver "$build_ver") - ;; - esac + # 挂载 install.wim,检查 + # 1. 是否自带 sac 组件 + # 2. 是否自带 nvme 驱动 + # 3. 是否支持 sha256 + # 4. Installation Type + wimmount "$iso_install_wim" "$image_index" /wim/ + get_windows_version_from_windows_drive /wim + windows_type=$(get_windows_type_from_windows_drive /wim) + { + find_file_ignore_case /wim/Windows/System32/sacsess.exe && has_sac=true || has_sac=false + find_file_ignore_case /wim/Windows/INF/stornvme.inf && has_stornvme=true || has_stornvme=false + } >/dev/null 2>&1 + wimunmount /wim/ + + # https://www.hummingheads.co.jp/press/info-certificates.html + # https://support.microsoft.com/kb/KB3033929 + # https://support.microsoft.com/kb/KB4474419 + # Windows Vista SP2 ldr_escrow 6.0.6003 + KB4474419 + # Windows 7 SP1 6.1.7601 + KB3033929 + support_sha256=false + if is_nt_ver_ge 6.2 || + { [ "$nt_ver" = 6.1 ] && [ "$build_ver" -ge 7601 ] && [ "$rev_ver" -ge 18741 ]; } || + { [ "$nt_ver" = 6.0 ] && [ "$build_ver" -ge 6003 ] && [ "$rev_ver" -ge 20555 ]; }; then + support_sha256=true + fi + + product_ver=$( + case "$windows_type" in + client) get_client_name_by_build_ver "$build_ver" ;; + server) get_server_name_by_build_ver "$build_ver" ;; + esac + ) info "Selected image info" echo "Image Name: $image_name" @@ -5177,6 +5779,10 @@ install_windows() { echo "Windows Type: $windows_type" echo "NT Version: $nt_ver" echo "Build Version: $build_ver" + echo "-------------------------" + echo "Has SAC: $has_sac" + echo "Has StorNVMe: $has_stornvme" + echo "Support SHA256: $support_sha256" echo # 复制 boot.wim 到 /os,用于临时编辑 @@ -5184,7 +5790,7 @@ install_windows() { # 自定义 boot.wim 链接 download "$boot_wim" /os/boot.wim else - cp /iso/sources/boot.wim /os/boot.wim + cp /iso/$sources_boot_wim /os/boot.wim fi # efi 启动目录为 efi 分区 @@ -5198,15 +5804,16 @@ install_windows() { # 复制启动相关的文件 # efi 额外复制efi目录 echo 'Copying boot files...' - cp -r /iso/boot* $boot_dir + cp -r "$(get_path_in_correct_case /iso/boot)"* $boot_dir if is_efi; then echo 'Copying efi files...' - cp -r /iso/efi/ $boot_dir + cp -r "$(get_path_in_correct_case /iso/efi)" $boot_dir fi # 复制iso全部文件(除了boot.wim)到installer分区 echo 'Copying installer files...' if false; then + # 还需忽略大小写 rsync -rv \ --exclude=/sources/boot.wim \ --exclude=/sources/install.wim \ @@ -5216,9 +5823,9 @@ install_windows() { ( cd /iso find . -type f \ - -not -name boot.wim \ - -not -name install.wim \ - -not -name install.esd \ + -not -iname boot.wim \ + -not -iname install.wim \ + -not -iname install.esd \ -exec cp -r --parents {} /os/installer/ \; ) fi @@ -5228,7 +5835,7 @@ install_windows() { # (意义不大,因为已经删除了 boot.wim 用来创建虚拟内存,vista 除外) # 缺点: 如果 install.wim 只有一个镜像,则只能缩小 10M+ if false; then - time wimexport --threads "$(get_build_threads 512)" "$iso_install_wim" "$image_name" "$install_wim" + time wimexport --threads "$(get_build_threads 512)" "$iso_install_wim" "$image_index" "$install_wim" info "install.wim size" echo "Original: $(get_filesize_mb "$iso_install_wim")" echo "Optimized: $(get_filesize_mb "$install_wim")" @@ -5241,8 +5848,9 @@ install_windows() { # 用注册表无法绕过 # https://github.com/pbatard/rufus/issues/1990 # https://learn.microsoft.com/windows/iot/iot-enterprise/Hardware/System_Requirements + # win11 旧版本安装程序(24h2之前)无法用 setup.exe /product server 跳过 cpu 核数限制,因此在xml里解除限制 if [ "$product_ver" = "11" ] && [ "$(nproc)" -le 1 ]; then - wiminfo "$install_wim" "$image_name" --image-property WINDOWS/INSTALLATIONTYPE=Server + wiminfo "$install_wim" "$image_index" --image-property WINDOWS/INSTALLATIONTYPE=Server fi # 变量名 使用场景 @@ -5271,16 +5879,16 @@ install_windows() { ;; esac + # win7 drvload 可以加载 sha256 签名的驱动 + # 但系统安装完重启报错 windows cannot verify the digital signature for this file + # 需要按 F8 禁用驱动签名 + add_drivers() { info "Add drivers" drv=/os/drivers mkdir -p "$drv" # 驱动下载临时文件夹 - # 下载脚本 - # shellcheck disable=SC1090 - . <(wget -O- $confhome/windows-driver-utils.sh) - # 这里有坑 # $(get_cloud_vendor) 调用了 cache_dmi_and_virt # 但是 $(get_cloud_vendor) 运行在 subshell 里面 @@ -5293,9 +5901,26 @@ install_windows() { if is_virt_contains virtio; then if [ "$vendor" = aliyun ] && is_nt_ver_ge 6.1 && [ "$arch_wim" = x86_64 ]; then add_driver_aliyun_virtio - # 未测试是否需要专用驱动 + elif [ "$vendor" = qcloud ] && is_nt_ver_ge 6.1 && [ "$arch_wim" = x86_64 ]; then + add_driver_qcloud_virtio + # 未测试是否需要专用驱动 elif false && [ "$vendor" = huawei ] && is_nt_ver_ge 6.0 && { [ "$arch_wim" = x86 ] || [ "$arch_wim" = x86_64 ]; }; then add_driver_huawei_virtio + + # gcp 官方驱动不全,需要用公版补全 + # 官方 windows server 模板没有 viorng 设备,但 linux 模板有 + elif [ "$vendor" = gcp ] && is_nt_ver_ge 6.1 && [ "$arch_wim" = x86 ] && $support_sha256; then + add_driver_gcp_virtio + add_driver_generic_virtio \( -iname viorng.inf -or -iname pvpanic.inf \) + + elif [ "$vendor" = gcp ] && is_nt_ver_ge 6.1 && [ "$arch_wim" = x86_64 ] && $support_sha256; then + add_driver_gcp_virtio + add_driver_generic_virtio -iname viorng.inf + + elif [ "$vendor" = gcp ] && [ "$nt_ver" = 6.1 ] && [ "$arch_wim" = x86_64 ] && ! $support_sha256; then + add_driver_gcp_virtio_win6_1_sha1_x64 + add_driver_generic_virtio \( -iname viorng.inf -or -iname balloon.inf \) + else # 兜底 add_driver_generic_virtio @@ -5313,10 +5938,11 @@ install_windows() { fi # vmd - # 改进: 像检测 virtio 那样直接从 /sys 检测设备 - # inf 有要求 19041 或以上 - if [ "$build_ver" -ge 19041 ] && [ "$arch_wim" = x86_64 ] && - is_lspci_contains 'Volume Management Device'; then + # RST v17 不支持 vmd + # RST v18 inf 要求 15063 或以上 + # RST v19 inf 要求 15063 或以上 + # RST v20 inf 要求 19041 或以上 + if [ -d /sys/module/vmd ] && [ "$build_ver" -ge 15063 ] && [ "$arch_wim" = x86_64 ]; then add_driver_vmd fi @@ -5340,10 +5966,174 @@ install_windows() { ;; esac + # intel 网卡驱动 + # 官网没有提供 vista/2008 驱动 + # win7 驱动 inf/ndis 不支持 vista/2008 + if is_nt_ver_ge 6.1 && { [ "$arch_wim" = x86 ] || [ "$arch_wim" = x86_64 ]; } && + grep -iq 8086 /sys/class/net/e*/device/vendor; then + add_driver_intel_nic + fi + # 自定义驱动 add_driver_custom } + add_driver_intel_nic() { + info "Add drivers: Intel NIC" + + arch_intel=$( + case "$arch_wim" in + x86) echo 32 ;; + x86_64) echo x64 ;; + esac + ) + + url=$( + case "$product_ver" in + '7' | '2008 r2') + # 现在官网只有 25.0 + # 25.0 比 24.5 只更新了 ProSet 软件,驱动相同 + # 25.0 有部分文件是 sha256 签名 + # 24.3 全部文件是 sha1 签名 + # https://web.archive.org/web/20250405130938/https://www.intel.com/content/www/us/en/download/15590/29323/intel-network-adapter-driver-for-windows-7-final-release.html + echo https://downloadmirror.intel.com/18713/eng/prowin${arch_intel}legacy.exe + ;; + '8' | '8.1') + # 之前有 Intel® Network Adapter Driver for Windows 8* - Final Release ,版本 22.7.1 + # 但已被删除,原因不明 + # https://web.archive.org/web/20250501043104/https://www.intel.com/content/www/us/en/download/16765/intel-network-adapter-driver-for-windows-8-final-release.html + # 27.8 有 NDIS63 文件夹,意味着支持 Windows 8 + # 27.8 相比 22.7.1,可能有些老设备不支持了,但我们不管了 + echo https://downloadmirror.intel.com/764813/Wired_driver_27.8_${arch_intel}.zip + ;; + '2012' | '2012 r2') + echo https://downloadmirror.intel.com/772074/Wired_driver_28.0_${arch_intel}.zip + ;; + # 2016 2019 2022 2025 win10 win11 + *) case "${arch_intel}" in + 32) + echo https://downloadmirror.intel.com/849483/Wired_driver_30.0.1_${arch_intel}.zip + ;; + x64) + # intel 禁止了 wget 下载网页 + wget -U curl/7.54.1 https://www.intel.com/content/www/us/en/download/727998.html -O- | + grep -Eio -m1 "\"https://.+/(Wired_driver|prowin).*${arch_intel}(legacy)?\.(zip|exe)\"" | tr -d '"' | grep . + ;; + esac ;; + esac + ) + + # 注意 intel 禁止了 aria2 下载 + download "$url" $drv/intel.zip + + # inf 可能是 UTF-16 LE?因此用 rg 搜索 + # 用 busybox unzip 解压 win10 驱动时,路径和文件名会粘在一起 + # 但解压 28.0 驱动时,依然会出现这个问题 + # 因此需要 convert_backslashes + apk add unzip ripgrep + + # https://superuser.com/questions/1382839/zip-files-expand-with-backslashes-on-linux-no-subdirectories + convert_backslashes() { + for file in "$1"/*\\*; do + if [ -f "$file" ]; then + target="${file//\\//}" + mkdir -p "${target%/*}" + mv -v "$file" "$target" + fi + done + } + + # win7 驱动是 .exe 解压不会报错 + # win10 驱动是 .zip 解压反而会报错,目测 zip 文件有问题 + # 在 windows 下解压 win8 的驱动会提示 checksum 错误 + unzip -o -d $drv/intel/ $drv/intel.zip || true + convert_backslashes $drv/intel + + is_have_inf_in_intel_dir() { + find $drv/intel -ipath "*/*.inf" | grep . >/dev/null + } + + # Wired_driver_28.0_x64.zip 需要二次解压 + if ! is_have_inf_in_intel_dir; then + unzip -o -d $drv/intel/ $drv/intel/Wired_driver_*.exe || true + convert_backslashes $drv/intel + fi + + # 由于上面使用了 || true,因此确认下解压后是否有 inf 文件 + if ! is_have_inf_in_intel_dir; then + error_and_exit "No .inf file found in intel driver package" + fi + + # Vista RTM 版本号是 6000 NDIS 6.0 + # 2008 RTM 版本号是 6001 NDIS 6.1 + + # 找出驱动文件夹对应的最低系统版本 + # 1. 驱动可能限制 windows client/server,但我们不区分 + # 如果装不了也没关系。如果能装但不加载,用户也可以在硬件管理器强制加载驱动 + # 2. 官网写着 win10 驱动要求 RS5 1809,但是驱动包里有 NDIS65 文件夹,也就是支持 10240 + # 3. 有可能 NDIS65 文件夹实际要求 NDIS 6.51?但是先不管 + # https://learn.microsoft.com/en-us/windows-hardware/drivers/network/overview-of-ndis-versions + min_support_map=$(cat </dev/null) + device=$(cat "$d/device" 2>/dev/null) + if [ "$vendor" = "0x8086" ] && [ "$device" = "0x9a0b" ]; then + is_gen11=true + break + fi + done + + if ! $is_gen11 && [ "$build_ver" -ge 19041 ]; then + # RST v20 + local page=https://www.intel.com/content/www/us/en/download/849936.html + elif [ "$build_ver" -ge 15063 ]; then + # RST v19 + local page=https://www.intel.com/content/www/us/en/download/849933.html + else + error_and_exit "can't find suitable vmd driver" + fi + local url + url=$(wget -U curl/7.54.1 "$page" -O- | + grep -Eio -m1 "\"https://.+/SetupRST\.exe\"" | tr -d '"' | grep .) + + # 注意 intel 禁止了 aria2 下载 + download $url $drv/SetupRST.exe apk add 7zip - download https://downloadmirror.intel.com/820815/SetupRST.exe $drv/SetupRST.exe 7z x $drv/SetupRST.exe -o$drv/SetupRST -i!.text 7z x $drv/SetupRST/.text -o$drv/vmd + apk del 7zip cp_drivers $drv/vmd } @@ -5720,6 +6666,8 @@ install_windows() { locale=$(get_selected_image_prop 'Default Language') use_default_rdp_port=$(is_need_change_rdp_port && echo false || echo true) password_base64=$(get_password_windows_administrator_base64) + # 7601.24214.180801-1700.win7sp1_ldr_escrow_CLIENT_ULTIMATE_x64FRE_en-us.iso Image Name 为空 + # 将 xml Image Name 的值设为空可以正常安装 sed -i \ -e "s|%arch%|$arch|" \ -e "s|%image_name%|$image_name|" \ @@ -5746,15 +6694,15 @@ install_windows() { # 评估版 iso ei.cfg 有 EVAL 字样,填空白 key 报错 Windows Cannot find Microsoft software license terms # key - if [[ "$image_name" = 'Windows Vista*' ]]; then - # vista 需密钥,密钥可与 edition 不一致 - # TODO: 改成从网页获取? + if [ "$product_ver" = vista ]; then + # vista 无人值守安装需要密钥,密钥可与 edition 不一致 # https://learn.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys - key=VKK3X-68KWM-X2YGT-QR4M6-4BWMV + # 从镜像获取默认密钥 + setup_cfg=$(get_path_in_correct_case /os/installer/sources/inf/setup.cfg) + key=$(del_cr <"$setup_cfg" | grep -Eix 'Value=([A-Z0-9]{5}-){4}[A-Z0-9]{5}' | cut -d= -f2 | grep .) sed -i "s/%key%/$key/" /tmp/autounattend.xml else - # shellcheck disable=SC2010 - if ls -d /os/installer/sources/* | grep -iq ei.cfg; then + if [ -f "$(get_path_in_correct_case /os/installer/sources/ei.cfg)" ]; then # 镜像有 ei.cfg,删除 key 字段 sed -i "/%key%/d" /tmp/autounattend.xml else @@ -5763,23 +6711,18 @@ install_windows() { fi fi - mkdir -p /wim - - # 挂载 install.wim,检查是否有 sac 组件 - wimmount "$install_wim" "$image_name" /wim/ - [ -f /wim/Windows/System32/sacsess.exe ] && has_sac=true || has_sac=false - wimunmount /wim/ - # 挂载 boot.wim info "mount boot.wim" wimmountrw /os/boot.wim "$boot_index" /wim/ + # 防止重复 + copyed_infs= cp_drivers() { if [ "$1" = custom ]; then shift - dst="/wim/custom_drivers" + dst=$(get_path_in_correct_case "/wim/custom_drivers") else - dst=/wim/drivers + dst=$(get_path_in_correct_case "/wim/drivers") fi src=$1 @@ -5788,70 +6731,92 @@ install_windows() { # -not -iname "*.pdb" \ # -not -iname "dpinst.exe" \ - find $src -type f -iname "*.inf" "$@" | while read -r inf; do - parse_inf_and_cp_driever "$inf" "$dst" "$arch" false - done + # 这里需要在 while 里面变更 $copyed_infs,因此不能用 find | while + while read -r inf; do + if ! is_list_has "$copyed_infs" "$inf"; then + parse_inf_and_cp_driever "$inf" "$dst" "$arch" false + copyed_infs=$(list_add "$copyed_infs" "$inf") + fi + done < <(find $src -type f -iname "*.inf" "$@") } # 添加驱动 add_drivers # win7 要添加 bootx64.efi 到 efi 目录 - [ $arch = amd64 ] && boot_efi=bootx64.efi || boot_efi=bootaa64.efi - if is_efi && [ ! -e /os/boot/efi/efi/boot/$boot_efi ]; then - mkdir -p /os/boot/efi/efi/boot/ - cp /wim/Windows/Boot/EFI/bootmgfw.efi /os/boot/efi/efi/boot/$boot_efi + if is_efi; then + [ $arch = amd64 ] && boot_efi=bootx64.efi || boot_efi=bootaa64.efi + + local src dst + dst=$(get_path_in_correct_case /os/boot/efi/EFI/boot/$boot_efi) + if ! [ -f $dst ]; then + mkdir -p "$(dirname $dst)" + src=$(get_path_in_correct_case /wim/Windows/Boot/EFI/bootmgfw.efi) + cp "$src" "$dst" + fi fi # 复制应答文件 # 移除注释,否则 windows-setup.bat 重新生成的 autounattend.xml 有问题 + wim_autounattend_xml=$(get_path_in_correct_case /wim/autounattend.xml) + wim_windows_xml=$(get_path_in_correct_case /wim/windows.xml) + wim_setup_exe=$(get_path_in_correct_case /wim/setup.exe) + apk add xmlstarlet - xmlstarlet ed -d '//comment()' /tmp/autounattend.xml >/wim/autounattend.xml - unix2dos /wim/autounattend.xml + xmlstarlet ed -d '//comment()' /tmp/autounattend.xml >$wim_autounattend_xml + unix2dos $wim_autounattend_xml info "autounattend.xml" # 查看最终文件,并屏蔽密码 - xmlstarlet ed -d '//*[name()="AdministratorPassword" or name()="Password"]' /wim/autounattend.xml | cat -n + xmlstarlet ed -d '//*[name()="AdministratorPassword" or name()="Password"]' $wim_autounattend_xml | cat -n apk del xmlstarlet # 避免无参数运行 setup.exe 时自动安装 - mv /wim/autounattend.xml /wim/windows.xml + mv $wim_autounattend_xml $wim_windows_xml # 复制安装脚本 # https://slightlyovercomplicated.com/2016/11/07/windows-pe-startup-sequence-explained/ # https://learn.microsoft.com/previous-versions/windows/it-pro/windows-vista/cc721977(v=ws.10) - mv /wim/setup.exe /wim/setup.exe.disabled + mv $wim_setup_exe $wim_setup_exe.disabled # 如果有重复的 Windows/System32 文件夹,会提示找不到 winload.exe 无法引导 # win7 win10 boot.wim 是 Windows/System32,install.wim 是 Windows/System32 # win2016 boot.wim 是 windows/system32,install.wim 是 Windows/System32 # wimmount 无法挂载成忽略大小写 - # shellcheck disable=SC2010 - system32_dir=$(ls -d /wim/*/*32 | grep -i windows/system32) - download $confhome/windows-setup.bat $system32_dir/startnet.cmd + + startnet_cmd=$(get_path_in_correct_case /wim/Windows/System32/startnet.cmd) + winpeshl_ini=$(get_path_in_correct_case /wim/Windows/System32/winpeshl.ini) + + download $confhome/windows-setup.bat $startnet_cmd # dism 手动释放镜像时用 - # sed -i "s|@image_name@|$image_name|" $system32_dir/startnet.cmd + # sed -i "s|@image_name@|$image_name|" "$startnet.cmd" # shellcheck disable=SC2154 if [ "$force_old_windows_setup" = 1 ]; then - sed -i 's/ForceOldSetup=0/ForceOldSetup=1/i' $system32_dir/startnet.cmd + sed -i 's/ForceOldSetup=0/ForceOldSetup=1/i' $startnet_cmd fi # 有 SAC 组件时,启用 EMS if $has_sac; then - sed -i 's/EnableEMS=0/EnableEMS=1/i' $system32_dir/startnet.cmd + sed -i 's/EnableEMS=0/EnableEMS=1/i' $startnet_cmd + fi + + # 4kn EFI 分区最少要 260M + # https://learn.microsoft.com/windows-hardware/manufacture/desktop/hard-drives-and-partitions + if is_4kn /dev/$xda; then + sed -i 's/is4kn=0/is4kn=1/i' $startnet_cmd fi # Windows Thin PC 有 Windows\System32\winpeshl.ini # [LaunchApps] # %SYSTEMDRIVE%\windows\system32\drvload.exe, %SYSTEMDRIVE%\windows\inf\sdbus.inf # %SYSTEMDRIVE%\setup.exe - if [ -f $system32_dir/winpeshl.ini ]; then + if [ -f "$winpeshl_ini" ]; then info "mod winpeshl.ini" # https://learn.microsoft.com/previous-versions/windows/it-pro/windows-vista/cc721977(v=ws.10) # 两种方法都可以,第一种是原版命令 - sed -i 's|setup.exe|windows\\system32\\cmd.exe, "/k %SYSTEMROOT%\\system32\\startnet.cmd"|i' $system32_dir/winpeshl.ini - # sed -i 's|setup.exe|windows\\system32\\startnet.cmd|i' $system32_dir/winpeshl.ini - cat -n $system32_dir/winpeshl.ini + sed -i 's|setup.exe|windows\\system32\\cmd.exe, "/k %SYSTEMROOT%\\system32\\startnet.cmd"|i' "$winpeshl_ini" + # sed -i 's|setup.exe|windows\\system32\\startnet.cmd|i' "$winpeshl_ini" + cat -n "$winpeshl_ini" fi # 提交修改 boot.wim @@ -5863,7 +6828,6 @@ install_windows() { # wimoptimize /os/boot.wim # 优化 boot.wim 并复制到正确的位置 - mkdir -p $boot_dir/sources/ if is_nt_ver_ge 6.1; then # win7 或以上删除 boot.wim 镜像 1 不会报错 # 因为 win7 winre 镜像在 install.wim Windows\System32\Recovery\winRE.wim @@ -5876,17 +6840,18 @@ install_windows() { # vista install.wim 没有 Windows\System32\Recovery\winRE.wim images=all fi - wimexport --boot /os/boot.wim "$images" $boot_dir/sources/boot.wim + mkdir -p "$(get_path_in_correct_case "$(dirname $boot_dir/$sources_boot_wim)")" + wimexport --boot /os/boot.wim "$images" $boot_dir/$sources_boot_wim info "boot.wim size" - echo "Original: $(get_filesize_mb /iso/sources/boot.wim)" + echo "Original: $(get_filesize_mb /iso/$sources_boot_wim)" echo "Added Drivers: $(get_filesize_mb /os/boot.wim)" - echo "Optimized: $(get_filesize_mb "$boot_dir/sources/boot.wim")" + echo "Optimized: $(get_filesize_mb "$boot_dir/$sources_boot_wim")" echo # vista 安装时需要 boot.wim,原因见上面 if [ "$nt_ver" = 6.0 ] && - ! [ -e /os/installer/sources/boot.wim ]; then - cp $boot_dir/sources/boot.wim /os/installer/sources/boot.wim + ! [ -e /os/installer/$sources_boot_wim ]; then + cp $boot_dir/$sources_boot_wim /os/installer/$sources_boot_wim fi # windows 7 没有 invoke-webrequest @@ -5894,7 +6859,7 @@ install_windows() { # 所以复制 resize.bat 到 install.wim if true; then info "mount install.wim" - wimmountrw $install_wim "$image_name" /wim/ + wimmountrw $install_wim "$image_index" /wim/ if false; then # 使用 autounattend.xml # win7 在此阶段找不到网卡 @@ -5991,7 +6956,8 @@ sync_time() { ;; esac - hwclock -w + # 重启时 alpine 会自动写入到硬件时钟,因此这里跳过 + # hwclock -w } is_ubuntu_lts() { @@ -6284,6 +7250,8 @@ elif [ "$1" = "alpine" ]; then distro=alpine # 后面的步骤很多都会用到这个,例如分区布局 cloud_image=0 +elif [ -n "$1" ]; then + error_and_exit "unknown option $1" fi # 无参数运行部分 @@ -6299,13 +7267,32 @@ mount / -o remount,size=100% # 4. 允许同步失败,因为不是关键步骤 sync_time || true -# 设置密码,安装并打开 ssh -echo "root:$(get_password_linux_sha512)" | chpasswd -e +# 安装 ssh 并更改端口 apk add openssh if is_need_change_ssh_port; then change_ssh_port / $ssh_port fi -printf '\nyes' | setup-sshd + +# 设置密码,添加开机启动 + 开启 ssh 服务 +if is_need_set_ssh_keys; then + set_ssh_keys_and_del_password / + printf '\n' | setup-sshd +else + change_root_password / + printf '\nyes' | setup-sshd +fi + +# 设置 frpc +# 并防止重复运行 +if [ -s /configs/frpc.toml ] && ! pidof frpc >/dev/null; then + info 'run frpc' + add_community_repo + apk add frp + while true; do + frpc -c /configs/frpc.toml || true + sleep 5 + done & +fi # shellcheck disable=SC2154 if [ "$hold" = 1 ]; then diff --git a/windows-driver-utils.sh b/windows-driver-utils.sh index 7d8501b..30ba377 100644 --- a/windows-driver-utils.sh +++ b/windows-driver-utils.sh @@ -1,14 +1,42 @@ #!/bin/ash # shellcheck shell=dash -# shellcheck disable=SC3001,SC3010 +# shellcheck disable=SC3001,SC3003,SC3010 # reinstall.sh / trans.sh 共用此文件 +# grep 无法处理 UTF-16LE 编码的 inf,有以下几种解决方法 +# 1. 使用 ripgrep (rg) 或者 ugrep,但 cygwin 没有 +# 2. grep -a a.b.c.d +# 3. iconv -f UTF-16 -t UTF-8 + del_inf_comment() { sed 's/;.*//' } simply_inf() { - del_cr | del_inf_comment | trim | del_empty_lines + convert_file_to_utf8 "$1" | del_cr | del_inf_comment | trim | del_empty_lines +} + +convert_file_to_utf8() { + # ash 用 * 比较字符串有问题 + # [[ $'\xEF\xBB' = $'\xEF\xBB*' ]] && echo 1 + + # UTF-16LE without BOM 要处理吗? windows 支持这种编码的 inf? + + # UTF-16LE with BOM + if [ "$(head -c2 "$1")" = $'\xFF\xFE' ]; then + # -f UTF-16LE -t UTF-8 会添加 UTF-8 BOM + iconv -f UTF-16 -t UTF-8 "$1" + + # UTF-8 with BOM + elif [ "$(head -c3 "$1")" = $'\xEF\xBB\xBF' ]; then + # busybox sed 不支持 + # sed '1s/^\xEF\xBB\xBF//' "$1" + tail -c +4 "$1" + + # 其它 + else + cat "$1" + fi } simply_inf_word() { @@ -33,7 +61,7 @@ list_files_from_inf() { local mix_x86_x86_64=$3 # 所有字段不区分大小写 - inf_txts=$(simply_inf <"$inf" | to_lower) + inf_txts=$(simply_inf "$inf" | to_lower) is_match_section() { local section=$1 @@ -79,6 +107,19 @@ list_files_from_inf() { # 0. 检测 inf 是否适合当前架构 # 目前没有对比版本号 + ############################################## + # 注意这种情况, NTamd64.6.0 为空,表示不支持 6.0 + + # [Manufacturer] + # %V_INTEL% = Intel, NTamd64.6.0, NTamd64.6.1.1 + + # [Intel.NTamd64.6.0] + # ; Empty section. + + # 如果后期改成不从 Manufacturer 获取支持的架构,而是从[]获取支持的架构,注意这种情况 + # [Intel.NTamd64.10.0.1..22000] + ############################################## + # 例子1 # [Manufacturer] # %Amazon% = AWSNVME, NTamd64, NTARM64 @@ -163,12 +204,12 @@ list_files_from_inf() { # 注意可能有空格和引号 if $in_section; then + local num dir num=$(echo "$line" | awk -F= '{print $1}' | simply_inf_word) dir=$(echo "$line" | awk -F, '{print $4}' | simply_inf_word) # 每行一条记录 if [ -n "$SourceDisksNames" ]; then - SourceDisksNames="$SourceDisksNames -" + SourceDisksNames=$SourceDisksNames$'\n' fi SourceDisksNames="$SourceDisksNames$num:$dir" fi @@ -198,7 +239,11 @@ list_files_from_inf() { done < <(echo "$inf_txts") } -find_file_ignore_case() { +# windows 安装驱动时,只会安装相同架构的驱动文件到系统,即使 inf 里有列出其它架构的驱动 +# 因此 DISM 导出驱动时,也就没有包含其它架构的驱动文件 + +# 用于尽可能匹配路径大小写 +get_path_in_correct_case() { # 同时支持参数和管道 local path path=$({ if [ -n "$1" ]; then echo "$1"; else cat; fi; }) @@ -208,42 +253,66 @@ find_file_ignore_case() { # shellcheck disable=SC2046 set -- $(echo "$path" | grep -o '[^/]*') ( - # windows 安装驱动时,只会安装相同架构的驱动文件到系统,即使 inf 里有列出其它架构的驱动 - # 因此导出驱动时,也就不会包含其它架构的驱动文件 - # 因此这里只警告,不中断脚本 - local output= if is_absolute_path "$path"; then cd / output=/ fi + stop_find=false + while [ $# -gt 0 ]; do local part=$1 + local tmp # shellcheck disable=SC2010 - if part=$(ls -1 | grep -Fix "$part"); then - # 大于 1 表示当前 part 是目录 - if [ $# -gt 1 ]; then - if cd "$part"; then - output="$output$part/" - else - warn "Can't cd $path" - return 1 + if ! $stop_find; then + if tmp=$(ls -1 | grep -Fix "$part"); then + part=$tmp + # 大于 1 表示当前 part 是目录 + if [ $# -gt 1 ]; then + if ! cd "$part" 2>/dev/null; then + warn "Can't cd $path" + stop_find=true + fi fi else - # 最后 part - output="$output$part" + stop_find=true fi + fi + + if [ $# -gt 1 ]; then + output="$output$part/" else - warn "Can't find $path" >&2 - return 1 + # 最后 part + output="$output$part" fi shift done + echo "$output" ) } +is_file_or_link() { + # -e / -f 坏软连接,返回 false + # -L 坏软连接,返回 true + [ -f "$1" ] || [ -L "$1" ] +} + +find_file_ignore_case() { + # 同时支持参数和管道 + local path + path=$({ if [ -n "$1" ]; then echo "$1"; else cat; fi; }) + + path=$(get_path_in_correct_case "$path") + if is_file_or_link "$path"; then + echo "$path" + else + warn "Can't find $path" >&2 + return 1 + fi +} + parse_inf_and_cp_driever() { local inf=$1 local dst=$2 @@ -276,3 +345,22 @@ parse_inf_and_cp_driever() { fi fi } + +get_sys_dir_for_eth() { + ( + cd "$(readlink -f "/sys/class/net/$1")" || error_and_exit "Can't cd to $1" + while ! [ "$(pwd)" = / ]; do + # DRIVER=virtio-pci + # PCI_CLASS=20000 # 2 开头表示网络设备 + # PCI_ID=1AF4:1041 + # PCI_SUBSYS_ID=1AF4:1100 + # PCI_SLOT_NAME=0000:03:00.0 + if [ -f uevent ] && grep -q 'PCI_CLASS=2' uevent && grep -q 'PCI_ID' uevent; then + pwd + return + fi + cd .. + done + return 1 + ) +} diff --git a/windows-frpc.bat b/windows-frpc.bat new file mode 100644 index 0000000..c0b66ec --- /dev/null +++ b/windows-frpc.bat @@ -0,0 +1,39 @@ +@echo off +mode con cp select=437 >nul + +rem Windows Deferder 会误报,因此要添加白名单 +powershell -ExecutionPolicy Bypass -Command "Add-MpPreference -ExclusionPath '%SystemDrive%\frpc\frpc.exe'" + +rem 启用日志 +rem wevtutil set-log Microsoft-Windows-TaskScheduler/Operational /enabled:true + +rem 创建计划任务并立即运行 +schtasks /Create /TN "frpc" /XML "%SystemDrive%\frpc\frpc.xml" +schtasks /Run /TN "frpc" +del "%SystemDrive%\frpc\frpc.xml" + +rem win10+ 在用户首次登录后,用 LocalService 用户运行的计划任务才会生效 +rem 即使手动重启,计划任务也没有运行 + +rem 如果 10 秒内有 frpc 进程,则代表计划任务已经生效,不需要首次登录 +rem 如果 10 秒后也没有 frpc 进程,则需要临时改用 SYSTEM 用户运行计划任务 +for /L %%i in (1,1,10) do ( + timeout 1 + tasklist /FI "IMAGENAME eq frpc.exe" | find /I "frpc.exe" && ( + goto :end + ) +) + +rem 临时改用 SYSTEM 用户运行计划任务 +schtasks /Change /TN frpc /RU S-1-5-18 +schtasks /Run /TN frpc + +rem 用户登录后改回用 LocalService 运行 +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /f ^ + /v FrpcRunAsLocalService ^ + /t REG_SZ ^ + /d "schtasks /Change /TN frpc /RU S-1-5-19" + +:end +rem 删除此脚本 +del "%~f0" diff --git a/windows-frpc.xml b/windows-frpc.xml new file mode 100644 index 0000000..d8bb0c0 Binary files /dev/null and b/windows-frpc.xml differ diff --git a/windows-set-netconf.bat b/windows-set-netconf.bat index c4d5fff..2ee680c 100644 --- a/windows-set-netconf.bat +++ b/windows-set-netconf.bat @@ -12,56 +12,90 @@ rem set ipv6_dns2=::2 @echo off mode con cp select=437 >nul -setlocal EnableDelayedExpansion rem 禁用 IPv6 地址标识符的随机化,防止 IPv6 和后台面板不一致 netsh interface ipv6 set global randomizeidentifiers=disabled rem 检查是否定义了 MAC 地址 -if defined mac_addr ( - for /f %%a in ('wmic nic where "MACAddress='%mac_addr%'" get InterfaceIndex ^| findstr [0-9]') do set id=%%a - if defined id ( - rem 配置静态 IPv4 地址和网关 - if defined ipv4_addr if defined ipv4_gateway ( - rem gwmetric 默认值为 1,自动跃点需设为 0 - netsh interface ipv4 set address !id! static !ipv4_addr! gateway=!ipv4_gateway! gwmetric=0 - ) +if not defined mac_addr goto :del - rem 配置静态 IPv4 DNS 服务器 - for %%i in (1, 2) do ( - if defined ipv4_dns%%i ( - netsh interface ipv4 add | findstr "dnsservers" - if ErrorLevel 1 ( - rem vista - netsh interface ipv4 add dnsserver !id! !ipv4_dns%%i! %%i - ) else ( - rem win7 - netsh interface ipv4 add dnsservers !id! !ipv4_dns%%i! %%i no - ) +rem vista 没有自带 powershell +rem win11 24h2 安装后有 wmic,但是过一段时间会自动删除,因此有的 dd 镜像没有 wmic +if exist "%windir%\system32\wbem\wmic.exe" ( + rem wmic 换行符是 \r\r\n + rem 虽然这里用了 findstr 全字匹配 ,但是结尾还是有 \r + for /f "tokens=2 delims==" %%a in ( + 'wmic nic where "MACAddress='%mac_addr%'" get InterfaceIndex /format:list ^| findstr "^InterfaceIndex=[0-9][0-9]*$"' + ) do set id=%%a +) + +if not defined id ( + for /f %%a in ('powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass ^ + -Command "(Get-WmiObject Win32_NetworkAdapter | Where-Object { $_.MACAddress -eq '%mac_addr%' }).InterfaceIndex" ^| findstr "^[0-9][0-9]*$"' + ) do set id=%%a +) + +if not defined id ( + for /f %%a in ('powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass ^ + -Command "(Get-CimInstance Win32_NetworkAdapter | Where-Object { $_.MACAddress -eq '%mac_addr%' }).InterfaceIndex" ^| findstr "^[0-9][0-9]*$"' + ) do set id=%%a +) + +if defined id ( + rem 配置静态 IPv4 地址和网关 + if defined ipv4_addr if defined ipv4_gateway ( + rem 如果使用了 setlocal EnableDelayedExpansion + rem netsh interface ipv4 set address !id! static %ipv4_addr% gateway=%ipv4_gateway% gwmetric=0 + rem !id! 变量最后有 \r 会导致语句不正确 + rem %id% 变量则没有这个问题 + + rem gwmetric 默认值为 1,自动跃点需设为 0 + netsh interface ipv4 set address %id% static %ipv4_addr% gateway=%ipv4_gateway% gwmetric=0 + ) + + rem 配置静态 IPv4 DNS 服务器 + for %%i in (1, 2) do ( + if defined ipv4_dns%%i ( + netsh interface ipv4 add | findstr "dnsservers" >nul + if ErrorLevel 1 ( + rem vista + setlocal EnableDelayedExpansion + netsh interface ipv4 add dnsserver %id% !ipv4_dns%%i! %%i + endlocal + ) else ( + rem win7 + setlocal EnableDelayedExpansion + netsh interface ipv4 add dnsservers %id% !ipv4_dns%%i! %%i no + endlocal ) ) + ) - rem 配置 IPv6 地址和网关 - if defined ipv6_addr if defined ipv6_gateway ( - netsh interface ipv6 set address !id! !ipv6_addr! - netsh interface ipv6 add route prefix=::/0 !id! !ipv6_gateway! - ) + rem 配置 IPv6 地址和网关 + if defined ipv6_addr if defined ipv6_gateway ( + netsh interface ipv6 set address %id% %ipv6_addr% + netsh interface ipv6 add route prefix=::/0 %id% %ipv6_gateway% + ) - rem 配置 IPv6 DNS 服务器 - for %%i in (1, 2) do ( - if defined ipv6_dns%%i ( - netsh interface ipv6 add | findstr "dnsservers" - if ErrorLevel 1 ( - rem vista - netsh interface ipv6 add dnsserver !id! !ipv6_dns%%i! %%i - ) else ( - rem win7 - netsh interface ipv6 add dnsservers !id! !ipv6_dns%%i! %%i no - ) + rem 配置 IPv6 DNS 服务器 + for %%i in (1, 2) do ( + if defined ipv6_dns%%i ( + netsh interface ipv6 add | findstr "dnsservers" >nul + if ErrorLevel 1 ( + rem vista + setlocal EnableDelayedExpansion + netsh interface ipv6 add dnsserver %id% !ipv6_dns%%i! %%i + endlocal + ) else ( + rem win7 + setlocal EnableDelayedExpansion + netsh interface ipv6 add dnsservers %id% !ipv6_dns%%i! %%i no + endlocal ) ) ) ) +:del rem 删除此脚本 del "%~f0" diff --git a/windows-setup.bat b/windows-setup.bat index f001b4f..a71e8a0 100644 --- a/windows-setup.bat +++ b/windows-setup.bat @@ -19,7 +19,7 @@ rem echo a | find "a" rem 使用高性能模式 rem https://learn.microsoft.com/windows-hardware/manufacture/desktop/capture-and-apply-windows-using-a-single-wim rem win8 pe 没有 powercfg -call powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 2>nul +powercfg /s SCHEME_MIN 2>nul rem 安装 SCSI 驱动 if exist X:\drivers\ ( @@ -93,6 +93,14 @@ for /f "tokens=3" %%a in (X:\disk.txt) do ( ) del X:\disk.txt +rem 这个变量会被 trans.sh 修改 +set is4kn=0 +if "%is4kn%"=="1" ( + set EFISize=260 +) else ( + set EFISize=100 +) + rem 重新分区/格式化 (if "%BootType%"=="efi" ( echo select disk %DiskIndex% @@ -104,7 +112,7 @@ rem 重新分区/格式化 echo select part 3 echo delete part override - echo create part efi size=100 + echo create part efi size=%EFISize% echo format fs=fat32 quick echo create part msr size=16 diff --git a/windows.xml b/windows.xml index cfc2754..c6faada 100644 --- a/windows.xml +++ b/windows.xml @@ -64,24 +64,31 @@ - + https://support.huaweicloud.com/usermanual-ims/zh-cn_topic_0047501112.html + powercfg /aliases 能显示 GUID/名称 的对应关系,vista 也能使用名称 --> 1 - powercfg /setacvalueindex 381b4222-f694-41f0-9685-ff5bb260df2e 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0 + powercfg /setacvalueindex SCHEME_BALANCED SUB_VIDEO VIDEOIDLE 0 2 - powercfg /setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0 + powercfg /setacvalueindex SCHEME_MIN SUB_VIDEO VIDEOIDLE 0 3 - powercfg /setacvalueindex a1841308-3541-4fab-bc81-f71556f20b4a 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0 + powercfg /setacvalueindex SCHEME_MAX SUB_VIDEO VIDEOIDLE 0 + + + + 4 + powercfg /setactive SCHEME_MIN - 4 + 5 @@ -91,7 +98,7 @@ - 5 + 6 reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager /v ShippedWithReserves /t REG_DWORD /d 0 /f