├── .github ├── ISSUE_TEMPLATE │ └── bug_report.yml └── workflows │ ├── cd.yaml │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── assets ├── distrod_wsl_launcher-x86_64.zip └── opt_distrod.tar.gz ├── distrod ├── Cargo.lock ├── Cargo.toml ├── distrod-exec │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── distrod │ ├── Cargo.lock │ ├── Cargo.toml │ ├── resources │ │ ├── distrod_autostart.xml │ │ ├── schedule_autostart_task.ps1 │ │ └── unschedule_autostart_task.ps1 │ ├── src │ │ ├── autostart.rs │ │ ├── main.rs │ │ └── shell_hook.rs │ └── tests │ │ ├── integration_test.rs │ │ └── test_runner.sh ├── distrod_wsl_launcher │ ├── Cargo.lock │ ├── Cargo.toml │ ├── resources │ │ └── .gitkeep │ └── src │ │ ├── main.rs │ │ ├── tar_helper.rs │ │ └── wsl.rs ├── libs │ ├── Cargo.toml │ ├── resources │ │ └── load_per_user_wsl_envs.sh │ ├── src │ │ ├── cli_ui.rs │ │ ├── command_alias.rs │ │ ├── container.rs │ │ ├── container_org_image.rs │ │ ├── distro.rs │ │ ├── distro_image.rs │ │ ├── distrod_config.rs │ │ ├── envfile.rs │ │ ├── lib.rs │ │ ├── local_image.rs │ │ ├── mount_info.rs │ │ ├── multifork.rs │ │ ├── passwd.rs │ │ ├── procfile.rs │ │ ├── systemdunit.rs │ │ ├── template.rs │ │ └── wsl_interop.rs │ └── tests │ │ └── resources │ │ └── systemdunit │ │ ├── Makefile │ │ ├── system │ │ ├── multi-user.target.wants │ │ │ ├── aliased.service │ │ │ ├── multiple_alias1.service │ │ │ ├── multiple_alias2.service │ │ │ ├── multiple_alias3.service │ │ │ ├── multiple_also_unit.service │ │ │ ├── referenced_by_also1.service │ │ │ ├── referenced_by_also2.service │ │ │ ├── referenced_by_also3.service │ │ │ ├── referenced_by_also4.service │ │ │ ├── simple_also_unit.service │ │ │ └── simple_unit.service │ │ ├── multiple_alias.service │ │ ├── multiple_also_unit.service │ │ ├── referenced_by_also1.service │ │ ├── referenced_by_also2.service │ │ ├── referenced_by_also3.service │ │ ├── referenced_by_also4.service │ │ ├── simple_alias.service │ │ ├── simple_also_unit.service │ │ ├── simple_unit.service │ │ ├── systemd-system1.service │ │ └── unrelated.service │ │ ├── unit_dir.tar.gz │ │ └── units │ │ ├── multiple_alias.service │ │ ├── multiple_also_unit.service │ │ ├── referenced_by_also1.service │ │ ├── referenced_by_also2.service │ │ ├── referenced_by_also3.service │ │ ├── referenced_by_also4.service │ │ ├── simple_alias.service │ │ ├── simple_also_unit.service │ │ ├── simple_unit.service │ │ ├── systemd-system1.service │ │ └── unrelated.service ├── misc │ ├── about.hbs │ └── about.toml └── portproxy │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs ├── distrod_packer ├── distrod_packer └── resources │ ├── bin │ ├── adduser │ ├── chsh │ └── useradd │ ├── conf │ ├── distrod.toml │ └── tcp4_ports │ ├── misc │ └── distrod-post-update │ └── run │ ├── systemd │ └── system │ │ ├── portproxy.service │ │ ├── systemd-tmpfiles-clean.service.d │ │ └── exclude_wslg_sockets.conf │ │ └── systemd-tmpfiles-setup.service.d │ │ └── exclude_wslg_sockets.conf │ └── tmpfiles.d │ └── x11.conf ├── docs ├── distrod_demo.gif ├── distrod_shot1.png └── references.md ├── install.sh ├── storeapp ├── .gitignore ├── DistroLauncher-Appx │ ├── Assets │ │ ├── LargeTile.scale-100.png │ │ ├── LargeTile.scale-125.png │ │ ├── LargeTile.scale-150.png │ │ ├── LargeTile.scale-200.png │ │ ├── LargeTile.scale-400.png │ │ ├── SmallTile.scale-100.png │ │ ├── SmallTile.scale-125.png │ │ ├── SmallTile.scale-150.png │ │ ├── SmallTile.scale-200.png │ │ ├── SmallTile.scale-400.png │ │ ├── SplashScreen.scale-100.png │ │ ├── SplashScreen.scale-125.png │ │ ├── SplashScreen.scale-150.png │ │ ├── SplashScreen.scale-200.png │ │ ├── SplashScreen.scale-400.png │ │ ├── Square150x150Logo.scale-100.png │ │ ├── Square150x150Logo.scale-125.png │ │ ├── Square150x150Logo.scale-150.png │ │ ├── Square150x150Logo.scale-200.png │ │ ├── Square150x150Logo.scale-400.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-16.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-256.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-32.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-48.png │ │ ├── Square44x44Logo.scale-100.png │ │ ├── Square44x44Logo.scale-125.png │ │ ├── Square44x44Logo.scale-150.png │ │ ├── Square44x44Logo.scale-200.png │ │ ├── Square44x44Logo.scale-400.png │ │ ├── Square44x44Logo.targetsize-16.png │ │ ├── Square44x44Logo.targetsize-24.png │ │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ │ ├── Square44x44Logo.targetsize-256.png │ │ ├── Square44x44Logo.targetsize-32.png │ │ ├── Square44x44Logo.targetsize-48.png │ │ ├── StoreLogo.scale-100.png │ │ ├── StoreLogo.scale-125.png │ │ ├── StoreLogo.scale-150.png │ │ ├── StoreLogo.scale-200.png │ │ ├── StoreLogo.scale-400.png │ │ ├── Wide310x150Logo.scale-100.png │ │ ├── Wide310x150Logo.scale-125.png │ │ ├── Wide310x150Logo.scale-150.png │ │ ├── Wide310x150Logo.scale-200.png │ │ └── Wide310x150Logo.scale-400.png │ ├── DistroLauncher-Appx.vcxproj │ ├── DistroLauncher-Appx.vcxproj.filters │ └── Distrod.appxmanifest ├── DistroLauncher.sln ├── LICENSE ├── README.md └── build.bat └── windows.mk /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for trying Distrod. Please provide a potentail bug report, following this template. Also, if you haven't starred this project yet, and you like the Distrod project, please star it! 9 | - type: textarea 10 | attributes: 11 | label: Describe the bug 12 | description: A clear and concise description of what the bug is. 13 | validations: 14 | required: true 15 | - type: textarea 16 | attributes: 17 | label: Steps to reproduce 18 | description: Steps to reproduce the behavior 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Expected behavior 24 | description: A clear and concise description of what you expected to happen. 25 | validations: 26 | required: false 27 | - type: input 28 | attributes: 29 | label: Windows version 30 | description: Please share the output of `winver` command on Windows 31 | validations: 32 | required: true 33 | - type: input 34 | attributes: 35 | label: Linux kernel version 36 | description: Please share the output of `uname -a` command on the Distrod distribution. If Distrod is crashing, try `wsl -d Distrod -e uname -a` in a Command Prompt window. 37 | validations: 38 | required: true 39 | - type: input 40 | attributes: 41 | label: Distro 42 | description: Please share your distro version 43 | placeholder: Ubuntu 20.04 44 | validations: 45 | required: true 46 | - type: dropdown 47 | attributes: 48 | label: How did you install that distro? 49 | options: 50 | - Installed by Distrod wizard 51 | - Enabled `distrod` in an existing WSL2 distro 52 | validations: 53 | required: true 54 | - type: textarea 55 | attributes: 56 | label: Logs 57 | description: | 58 | Please share the output of the trace logs, following [the reference](https://github.com/nullpo-head/wsl-distrod/blob/main/docs/references.md#enable-debug-logging-of-distrod). 59 | If you omit this, please provide the concise technical reason why you think the log is not necessary. 60 | validations: 61 | required: true 62 | - type: textarea 63 | attributes: 64 | label: additional comment 65 | validations: 66 | required: false 67 | 68 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | is_prerelease: 7 | description: "Whether this is a pre-release (yes/no)" 8 | required: false 9 | default: "no" 10 | skips_version_bump: 11 | description: "Whether to skip automatic version bump (yes/no)" 12 | required: false 13 | default: "no" 14 | skips_version_commit: 15 | description: "Whether to skip the version commit (yes/no)" 16 | required: false 17 | default: "no" 18 | skips_publish: 19 | description: "Whether to skip publishing the release assets (yes/no)" 20 | required: false 21 | default: "no" 22 | 23 | env: 24 | CARGO_TERM_COLOR: always 25 | 26 | jobs: 27 | publish-release_assets: 28 | name: Publish the release assets 29 | runs-on: ubuntu-latest 30 | needs: [build-distrod_wsl_launcher, bump-version] 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | with: 35 | ref: ${{ needs.bump-version.outputs.tag }} 36 | 37 | - name: Get the arch name 38 | run: | 39 | echo "ARCH_NAME=$(uname -m)" >> $GITHUB_ENV 40 | 41 | - name: Download opt_distrod 42 | uses: actions/download-artifact@v2 43 | with: 44 | name: "opt_distrod-${{ env.ARCH_NAME }}" 45 | path: assets 46 | 47 | - name: Download distrod_wsl_launcher 48 | uses: actions/download-artifact@v2 49 | with: 50 | name: "distrod_wsl_launcher-${{ env.ARCH_NAME }}" 51 | path: "distrod_wsl_launcher-${{ env.ARCH_NAME }}" 52 | 53 | - name: Zip distrod_wsl_launcher 54 | run: | 55 | sudo apt update 56 | sudo apt install -y zip 57 | zip -r "distrod_wsl_launcher-${{ env.ARCH_NAME }}.zip" "distrod_wsl_launcher-${{ env.ARCH_NAME }}" 58 | mv "distrod_wsl_launcher-${{ env.ARCH_NAME }}.zip" assets/ 59 | 60 | - name: Upload Binaries to Release 61 | uses: svenstaro/upload-release-action@v2 62 | if: github.event.inputs.skips_publish != 'yes' 63 | with: 64 | repo_token: ${{ secrets.GITHUB_TOKEN }} 65 | file: assets/* 66 | file_glob: true 67 | tag: ${{ needs.bump-version.outputs.tag }} 68 | overwrite: true 69 | prerelease: ${{ github.event.inputs.is_prerelease == 'yes' }} 70 | release_name: ${{ needs.bump-version.outputs.tag }} 71 | body: ${{ needs.bump-version.outputs.clean_changelog }} 72 | 73 | build-distrod_wsl_launcher: 74 | name: Build Distrod WSL launcher 75 | runs-on: windows-latest 76 | needs: [build-distrod-command, bump-version] 77 | 78 | defaults: 79 | run: 80 | shell: bash 81 | 82 | steps: 83 | - uses: actions/checkout@v2 84 | with: 85 | ref: ${{ needs.bump-version.outputs.tag }} 86 | 87 | - uses: actions/cache@v2 88 | with: 89 | path: | 90 | ~/.cargo/registry 91 | ~/.cargo/git 92 | distrod/target 93 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 94 | 95 | - name: Get the arch name 96 | shell: bash 97 | run: | 98 | echo "ARCH_NAME=$(uname -m)" >> $GITHUB_ENV 99 | 100 | - name: Download the Distrod's rootfs 101 | uses: actions/download-artifact@v2 102 | with: 103 | name: "distrod_root-${{ env.ARCH_NAME }}" 104 | path: rootfs 105 | 106 | - name: Build 107 | run: make -f windows.mk ROOTFS_PATH=rootfs/distrod_root.tar.gz distrod_wsl_launcher 108 | 109 | - name: Upload for the assets 110 | uses: actions/upload-artifact@v2 111 | with: 112 | name: "distrod_wsl_launcher-${{ env.ARCH_NAME }}" 113 | path: distrod/target/release/distrod_wsl_launcher.exe 114 | if-no-files-found: error 115 | 116 | build-distrod-command: 117 | name: Build distrod Linux command 118 | runs-on: ubuntu-latest 119 | 120 | needs: [build-portproxy-exe, bump-version] 121 | 122 | steps: 123 | - uses: actions/checkout@v2 124 | with: 125 | ref: ${{ needs.bump-version.outputs.tag }} 126 | 127 | - uses: actions/cache@v2 128 | with: 129 | path: | 130 | ~/.cargo/registry 131 | ~/.cargo/git 132 | distrod/target 133 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 134 | 135 | - name: Get the arch name 136 | shell: bash 137 | run: | 138 | echo "ARCH_NAME=$(uname -m)" >> $GITHUB_ENV 139 | 140 | - name: Download portproxy.exe 141 | uses: actions/download-artifact@v2 142 | with: 143 | name: "portproxy-${{ env.ARCH_NAME }}" 144 | path: distrod/target/release 145 | 146 | - name: Install dependencies 147 | run: | 148 | sudo apt-get update 149 | sudo apt-get upgrade 150 | sudo apt-get install -y patchelf 151 | sudo apt-get install -y apt-file; sudo apt-file update 152 | cargo install --git https://github.com/EmbarkStudios/cargo-about.git --rev b4d194a734215f55a88191236cd5112ddb198920 153 | 154 | - name: Build the Distrod command 155 | run: make distrod-release 156 | 157 | - name: Build the Distrod rootfs 158 | run: make OUTPUT_ROOTFS_PATH=distrod_root.tar.gz rootfs 159 | 160 | - name: Upload opt_distrod.tar.gz for the assets 161 | uses: actions/upload-artifact@v2 162 | with: 163 | name: "opt_distrod-${{ env.ARCH_NAME }}" 164 | path: opt_distrod.tar.gz 165 | if-no-files-found: error 166 | 167 | - name: Upload distrod_root.tar.gz for the Windows build 168 | uses: actions/upload-artifact@v2 169 | with: 170 | name: "distrod_root-${{ env.ARCH_NAME }}" 171 | path: distrod_root.tar.gz 172 | if-no-files-found: error 173 | 174 | bump-version: 175 | name: Bump the version 176 | runs-on: ubuntu-latest 177 | outputs: 178 | tag: ${{ steps.changelog.outputs.tag }} 179 | clean_changelog: ${{ steps.changelog.outputs.clean_changelog }} 180 | 181 | steps: 182 | - uses: actions/checkout@v2 183 | - name: Conventional Changelog Action 184 | id: changelog 185 | uses: TriPSs/conventional-changelog-action@v3 186 | with: 187 | github-token: ${{ secrets.github_token }} 188 | version-file: ./distrod/distrod/Cargo.toml,./distrod/distrod-exec/Cargo.toml,./distrod/distrod_wsl_launcher/Cargo.toml 189 | version-path: package.version 190 | skip-on-empty: false 191 | git-user-name: "github-actions[bot]" 192 | git-user-email: "41898282+github-actions[bot]@users.noreply.github.com" 193 | release-count: "0" 194 | skip-version-file: ${{ github.event.inputs.skips_version_bump == 'yes' }} 195 | skip-commit: ${{ github.event.inputs.skips_version_commit == 'yes' }} 196 | skip-ci: false 197 | 198 | build-portproxy-exe: 199 | name: Build portproxy.exe 200 | runs-on: windows-latest 201 | 202 | defaults: 203 | run: 204 | shell: bash 205 | 206 | steps: 207 | - uses: actions/checkout@v2 208 | - uses: actions/cache@v2 209 | with: 210 | path: | 211 | ~/.cargo/registry 212 | ~/.cargo/git 213 | distrod/target 214 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 215 | 216 | - name: Get the arch name 217 | shell: bash 218 | run: | 219 | echo "ARCH_NAME=$(uname -m)" >> $GITHUB_ENV 220 | 221 | - name: Build 222 | run: make -f windows.mk portproxy.exe 223 | 224 | - name: Upload portproxy.exe for the Linux build 225 | uses: actions/upload-artifact@v2 226 | with: 227 | name: "portproxy-${{ env.ARCH_NAME }}" 228 | path: distrod/target/release/portproxy.exe 229 | if-no-files-found: error 230 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | unit-test-on-linux: 14 | name: Unit test on Linux 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | distrod/target 25 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 26 | 27 | - name: Run tests 28 | run: | 29 | make unit-test-linux 30 | 31 | integration-test-on-linux: 32 | name: Integration test on Linux 33 | runs-on: ubuntu-latest 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | distro-to-test: 39 | - ubuntu 40 | - debian 41 | - archlinux 42 | - fedora 43 | - centos 44 | - almalinux 45 | - rockylinux 46 | - kali 47 | - mint 48 | - opensuse 49 | - amazonlinux 50 | - oracle 51 | - gentoo 52 | steps: 53 | - uses: actions/checkout@v2 54 | - uses: actions/cache@v2 55 | with: 56 | path: | 57 | ~/.cargo/registry 58 | ~/.cargo/git 59 | distrod/target 60 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 61 | - uses: actions/cache@v2 62 | with: 63 | path: | 64 | ~/.distrod_integration_test/image_cache/**/*.tar.xz 65 | key: ${{ runner.os }}-test-images-v2 66 | 67 | - name: Install dnsutils 68 | run: | 69 | sudo apt-get update 70 | sudo apt-get install -y dnsutils 71 | 72 | - name: Run tests 73 | run: | 74 | export DISTROD_IMAGE_CACHE_DIR="${HOME}/.distrod_integration_test/image_cache" \ 75 | export DISTROD_INSTALL_DIR="${HOME}/.distrod_integration_test/install" \ 76 | export DISTRO_TO_TEST="${{ matrix.distro-to-test }}" 77 | make integration-test-linux 78 | 79 | test-on-windows: 80 | name: CI on Windows 81 | runs-on: ${{ matrix.os }} 82 | 83 | strategy: 84 | matrix: 85 | os: [windows-latest] 86 | 87 | defaults: 88 | run: 89 | shell: bash 90 | 91 | steps: 92 | - uses: actions/checkout@v2 93 | - uses: actions/cache@v2 94 | with: 95 | path: | 96 | ~/.cargo/registry 97 | ~/.cargo/git 98 | distrod/target 99 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 100 | 101 | - name: Run tests 102 | run: | 103 | touch distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz # dummy file 104 | make -f windows.mk test-win 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | ## Ignore Visual Studio temporary files, build results, and 13 | ## files generated by popular Visual Studio add-ons. 14 | ## 15 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 16 | 17 | # User-specific files 18 | *.rsuser 19 | *.suo 20 | *.user 21 | *.userosscache 22 | *.sln.docstates 23 | 24 | # User-specific files (MonoDevelop/Xamarin Studio) 25 | *.userprefs 26 | 27 | # Mono auto generated files 28 | mono_crash.* 29 | 30 | # Build results 31 | [Dd]ebug/ 32 | [Dd]ebugPublic/ 33 | [Rr]elease/ 34 | [Rr]eleases/ 35 | x64/ 36 | x86/ 37 | [Ww][Ii][Nn]32/ 38 | [Aa][Rr][Mm]/ 39 | [Aa][Rr][Mm]64/ 40 | bld/ 41 | [Bb]in/ 42 | [Oo]bj/ 43 | [Ll]og/ 44 | [Ll]ogs/ 45 | 46 | # Visual Studio 2015/2017 cache/options directory 47 | .vs/ 48 | # Uncomment if you have tasks that create the project's static files in wwwroot 49 | #wwwroot/ 50 | 51 | # Visual Studio 2017 auto generated files 52 | Generated\ Files/ 53 | 54 | # MSTest test Results 55 | [Tt]est[Rr]esult*/ 56 | [Bb]uild[Ll]og.* 57 | 58 | # NUnit 59 | *.VisualState.xml 60 | TestResult.xml 61 | nunit-*.xml 62 | 63 | # Build Results of an ATL Project 64 | [Dd]ebugPS/ 65 | [Rr]eleasePS/ 66 | dlldata.c 67 | 68 | # Benchmark Results 69 | BenchmarkDotNet.Artifacts/ 70 | 71 | # .NET Core 72 | project.lock.json 73 | project.fragment.lock.json 74 | artifacts/ 75 | 76 | # ASP.NET Scaffolding 77 | ScaffoldingReadMe.txt 78 | 79 | # StyleCop 80 | StyleCopReport.xml 81 | 82 | # Files built by Visual Studio 83 | *_i.c 84 | *_p.c 85 | *_h.h 86 | *.ilk 87 | *.meta 88 | *.obj 89 | *.iobj 90 | *.pch 91 | *.pdb 92 | *.ipdb 93 | *.pgc 94 | *.pgd 95 | *.rsp 96 | *.sbr 97 | *.tlb 98 | *.tli 99 | *.tlh 100 | *.tmp 101 | *.tmp_proj 102 | *_wpftmp.csproj 103 | *.log 104 | *.tlog 105 | *.vspscc 106 | *.vssscc 107 | .builds 108 | *.pidb 109 | *.svclog 110 | *.scc 111 | 112 | # Chutzpah Test files 113 | _Chutzpah* 114 | 115 | # Visual C++ cache files 116 | ipch/ 117 | *.aps 118 | *.ncb 119 | *.opendb 120 | *.opensdf 121 | *.sdf 122 | *.cachefile 123 | *.VC.db 124 | *.VC.VC.opendb 125 | 126 | # Visual Studio profiler 127 | *.psess 128 | *.vsp 129 | *.vspx 130 | *.sap 131 | 132 | # Visual Studio Trace Files 133 | *.e2e 134 | 135 | # TFS 2012 Local Workspace 136 | $tf/ 137 | 138 | # Guidance Automation Toolkit 139 | *.gpState 140 | 141 | # ReSharper is a .NET coding add-in 142 | _ReSharper*/ 143 | *.[Rr]e[Ss]harper 144 | *.DotSettings.user 145 | 146 | # TeamCity is a build add-in 147 | _TeamCity* 148 | 149 | # DotCover is a Code Coverage Tool 150 | *.dotCover 151 | 152 | # AxoCover is a Code Coverage Tool 153 | .axoCover/* 154 | !.axoCover/settings.json 155 | 156 | # Coverlet is a free, cross platform Code Coverage Tool 157 | coverage*.json 158 | coverage*.xml 159 | coverage*.info 160 | 161 | # Visual Studio code coverage results 162 | *.coverage 163 | *.coveragexml 164 | 165 | # NCrunch 166 | _NCrunch_* 167 | .*crunch*.local.xml 168 | nCrunchTemp_* 169 | 170 | # MightyMoose 171 | *.mm.* 172 | AutoTest.Net/ 173 | 174 | # Web workbench (sass) 175 | .sass-cache/ 176 | 177 | # Installshield output folder 178 | [Ee]xpress/ 179 | 180 | # DocProject is a documentation generator add-in 181 | DocProject/buildhelp/ 182 | DocProject/Help/*.HxT 183 | DocProject/Help/*.HxC 184 | DocProject/Help/*.hhc 185 | DocProject/Help/*.hhk 186 | DocProject/Help/*.hhp 187 | DocProject/Help/Html2 188 | DocProject/Help/html 189 | 190 | # Click-Once directory 191 | publish/ 192 | 193 | # Publish Web Output 194 | *.[Pp]ublish.xml 195 | *.azurePubxml 196 | # Note: Comment the next line if you want to checkin your web deploy settings, 197 | # but database connection strings (with potential passwords) will be unencrypted 198 | *.pubxml 199 | *.publishproj 200 | 201 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 202 | # checkin your Azure Web App publish settings, but sensitive information contained 203 | # in these scripts will be unencrypted 204 | PublishScripts/ 205 | 206 | # NuGet Packages 207 | *.nupkg 208 | # NuGet Symbol Packages 209 | *.snupkg 210 | # The packages folder can be ignored because of Package Restore 211 | **/[Pp]ackages/* 212 | # except build/, which is used as an MSBuild target. 213 | !**/[Pp]ackages/build/ 214 | # Uncomment if necessary however generally it will be regenerated when needed 215 | #!**/[Pp]ackages/repositories.config 216 | # NuGet v3's project.json files produces more ignorable files 217 | *.nuget.props 218 | *.nuget.targets 219 | 220 | # Nuget personal access tokens and Credentials 221 | nuget.config 222 | 223 | # Microsoft Azure Build Output 224 | csx/ 225 | *.build.csdef 226 | 227 | # Microsoft Azure Emulator 228 | ecf/ 229 | rcf/ 230 | 231 | # Windows Store app package directories and files 232 | AppPackages/ 233 | BundleArtifacts/ 234 | Package.StoreAssociation.xml 235 | _pkginfo.txt 236 | *.appx 237 | *.appxbundle 238 | *.appxupload 239 | 240 | # Visual Studio cache files 241 | # files ending in .cache can be ignored 242 | *.[Cc]ache 243 | # but keep track of directories ending in .cache 244 | !?*.[Cc]ache/ 245 | 246 | # Others 247 | ClientBin/ 248 | ~$* 249 | *~ 250 | *.dbmdl 251 | *.dbproj.schemaview 252 | *.jfm 253 | *.pfx 254 | *.publishsettings 255 | orleans.codegen.cs 256 | 257 | # Including strong name files can present a security risk 258 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 259 | #*.snk 260 | 261 | # Since there are multiple workflows, uncomment next line to ignore bower_components 262 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 263 | #bower_components/ 264 | 265 | # RIA/Silverlight projects 266 | Generated_Code/ 267 | 268 | # Backup & report files from converting an old project file 269 | # to a newer Visual Studio version. Backup files are not needed, 270 | # because we have git ;-) 271 | _UpgradeReport_Files/ 272 | Backup*/ 273 | UpgradeLog*.XML 274 | UpgradeLog*.htm 275 | ServiceFabricBackup/ 276 | *.rptproj.bak 277 | 278 | # SQL Server files 279 | *.mdf 280 | *.ldf 281 | *.ndf 282 | 283 | # Business Intelligence projects 284 | *.rdl.data 285 | *.bim.layout 286 | *.bim_*.settings 287 | *.rptproj.rsuser 288 | *- [Bb]ackup.rdl 289 | *- [Bb]ackup ([0-9]).rdl 290 | *- [Bb]ackup ([0-9][0-9]).rdl 291 | 292 | # Microsoft Fakes 293 | FakesAssemblies/ 294 | 295 | # GhostDoc plugin setting file 296 | *.GhostDoc.xml 297 | 298 | # Node.js Tools for Visual Studio 299 | .ntvs_analysis.dat 300 | node_modules/ 301 | 302 | # Visual Studio 6 build log 303 | *.plg 304 | 305 | # Visual Studio 6 workspace options file 306 | *.opt 307 | 308 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 309 | *.vbw 310 | 311 | # Visual Studio LightSwitch build output 312 | **/*.HTMLClient/GeneratedArtifacts 313 | **/*.DesktopClient/GeneratedArtifacts 314 | **/*.DesktopClient/ModelManifest.xml 315 | **/*.Server/GeneratedArtifacts 316 | **/*.Server/ModelManifest.xml 317 | _Pvt_Extensions 318 | 319 | # Paket dependency manager 320 | .paket/paket.exe 321 | paket-files/ 322 | 323 | # FAKE - F# Make 324 | .fake/ 325 | 326 | # CodeRush personal settings 327 | .cr/personal 328 | 329 | # Python Tools for Visual Studio (PTVS) 330 | __pycache__/ 331 | *.pyc 332 | 333 | # Cake - Uncomment if you are using it 334 | # tools/** 335 | # !tools/packages.config 336 | 337 | # Tabs Studio 338 | *.tss 339 | 340 | # Telerik's JustMock configuration file 341 | *.jmconfig 342 | 343 | # BizTalk build output 344 | *.btp.cs 345 | *.btm.cs 346 | *.odx.cs 347 | *.xsd.cs 348 | 349 | # OpenCover UI analysis results 350 | OpenCover/ 351 | 352 | # Azure Stream Analytics local run output 353 | ASALocalRun/ 354 | 355 | # MSBuild Binary and Structured Log 356 | *.binlog 357 | 358 | # NVidia Nsight GPU debugger configuration file 359 | *.nvuser 360 | 361 | # MFractors (Xamarin productivity tool) working folder 362 | .mfractor/ 363 | 364 | # Local History for Visual Studio 365 | .localhistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | .idea/ 399 | *.sln.iml 400 | 401 | 402 | ## Distrod 403 | 404 | rootfs 405 | rootfs.tar.gz 406 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.7](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.6...v0.1.7) (2022-07-07) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * Bind mount `/tmp/.X11-unix` for newer version of WSL2 ([#57](https://github.com/nullpo-head/wsl-distrod/issues/57)) ([921bc3c](https://github.com/nullpo-head/wsl-distrod/commit/921bc3c2262aae4cd4656be800371bbbde5770eb)), closes [#56](https://github.com/nullpo-head/wsl-distrod/issues/56) 7 | 8 | 9 | 10 | ## [0.1.6](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.5...v0.1.6) (2022-07-06) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * ci: Fix version command reported wrong version ([#58](https://github.com/nullpo-head/wsl-distrod/issues/58)) ([fd9bbdc](https://github.com/nullpo-head/wsl-distrod/commit/fd9bbdcce953b2660207aad2b59ec91ab210a621)) 16 | 17 | 18 | 19 | ## [0.1.5](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.4...v0.1.5) (2022-01-17) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * Clean up /etc/environment when distrod is disabled. ([#32](https://github.com/nullpo-head/wsl-distrod/issues/32)) ([fc28178](https://github.com/nullpo-head/wsl-distrod/commit/fc281789900c561eb933d460e6b39483191fbca2)) 25 | * Fix SystemdUnitDisabler followed absolute symbolic links ([#34](https://github.com/nullpo-head/wsl-distrod/issues/34)) ([533ee3f](https://github.com/nullpo-head/wsl-distrod/commit/533ee3f942d31f205755c65f6ca934b2086f66c3)) 26 | * Update hostname in hosts ([#33](https://github.com/nullpo-head/wsl-distrod/issues/33)) ([973f9e9](https://github.com/nullpo-head/wsl-distrod/commit/973f9e9406de7d84e6aa763e7fa7f367c9f282f0)) 27 | 28 | 29 | 30 | ## [0.1.4](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.3...v0.1.4) (2021-11-20) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * Fix mount argument, which caused undocumented behavior ([b59c6f6](https://github.com/nullpo-head/wsl-distrod/commit/b59c6f698e5310e7361402a37fce0c8f9f369f57)) 36 | 37 | 38 | 39 | ## [0.1.3](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.2...v0.1.3) (2021-11-14) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * Fix a tar archive containing files with log path/linkname was not handled correctly (Fix [#6](https://github.com/nullpo-head/wsl-distrod/issues/6)) ([aeb96fd](https://github.com/nullpo-head/wsl-distrod/commit/aeb96fd3fc5a01eac88ca858d3c246ee13cf0e18)) 45 | 46 | 47 | 48 | ## [0.1.2](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.1...v0.1.2) (2021-11-09) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * Fix scheduling task was not working on Windows of non-English locale. (Fix [#4](https://github.com/nullpo-head/wsl-distrod/issues/4)) ([b5ded6f](https://github.com/nullpo-head/wsl-distrod/commit/b5ded6fba0e6d43fd0f719efa9ed7e314dd178d7)) 54 | 55 | 56 | 57 | ## [0.1.1](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre13...v0.1.1) (2021-11-08) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * Fix autostart was failing for light-weight images from linuxcontainers.org ([6e73743](https://github.com/nullpo-head/wsl-distrod/commit/6e73743efa723fbe4e03cc6b1fb2758a5702b531)) 63 | * Fix set_default_user was not working on Win11 with distros with custom names ([765fc9f](https://github.com/nullpo-head/wsl-distrod/commit/765fc9f3bf329350f025b86c41e55a99d6bd6735)) 64 | * Fix WSL's correct resolv.conf was removed during installation on Windows ([3a029e4](https://github.com/nullpo-head/wsl-distrod/commit/3a029e44c3e13bc8910dd33828ddfc659ca53ed2)) 65 | * Network services are now completely disabled for robustness rather than tweaked ([806073b](https://github.com/nullpo-head/wsl-distrod/commit/806073b25453137e2db4127a1c66c6ecbb47a0cf)) 66 | 67 | 68 | 69 | # [0.1.0-pre13](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre12...v0.1.0-pre13) (2021-11-07) 70 | 71 | 72 | 73 | # [0.1.0-pre12](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre11...v0.1.0-pre12) (2021-11-06) 74 | 75 | 76 | 77 | # [0.1.0-pre11](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre10...v0.1.0-pre11) (2021-10-30) 78 | 79 | 80 | 81 | # [0.1.0-pre10](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre9...v0.1.0-pre10) (2021-10-30) 82 | 83 | 84 | ### Reverts 85 | 86 | * Revert "Testing adding a after the first distrod launch" ([78b2bb3](https://github.com/nullpo-head/wsl-distrod/commit/78b2bb326abd95a7d22d67ffc86efe9c1553c3d4)) 87 | 88 | 89 | 90 | # [0.1.0-pre9](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre8...v0.1.0-pre9) (2021-10-28) 91 | 92 | 93 | 94 | # [0.1.0-pre8](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre7...v0.1.0-pre8) (2021-10-27) 95 | 96 | 97 | 98 | # [0.1.0-pre7](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre6...v0.1.0-pre7) (2021-10-27) 99 | 100 | 101 | 102 | # [0.1.0-pre6](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre5...v0.1.0-pre6) (2021-10-26) 103 | 104 | 105 | 106 | # [0.1.0-pre5](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre4...v0.1.0-pre5) (2021-10-23) 107 | 108 | 109 | 110 | # [0.1.0-pre4](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre3...v0.1.0-pre4) (2021-08-22) 111 | 112 | 113 | 114 | # [0.1.0-pre3](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre2...v0.1.0-pre3) (2021-08-17) 115 | 116 | 117 | 118 | # [0.1.0-pre2](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre1...v0.1.0-pre2) (2021-08-16) 119 | 120 | 121 | 122 | # [0.1.0-pre1](https://github.com/nullpo-head/wsl-distrod/compare/v0.1.0-pre0...v0.1.0-pre1) (2021-08-10) 123 | 124 | 125 | 126 | # 0.1.0-pre0 (2021-08-09) 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Takaya Saeki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OUTPUT_ROOTFS_PATH ?= distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz 2 | 3 | build: distrod-release 4 | 5 | rootfs: distrod-bins distrod/target/release/portproxy.exe 6 | ./distrod_packer/distrod_packer ./distrod $(OUTPUT_ROOTFS_PATH) 7 | 8 | distrod-release: distrod-bins distrod/target/release/portproxy.exe 9 | ./distrod_packer/distrod_packer ./distrod opt_distrod.tar.gz --pack-distrod-opt-dir 10 | 11 | distrod-bins: 12 | cd distrod; cargo build --release -p distrod -p distrod-exec -p portproxy 13 | 14 | unit-test-linux: 15 | cd distrod; cargo test --verbose -p libs -p portproxy -p distrod-exec ${TEST_TARGETS} 16 | 17 | integration-test-linux: 18 | cd distrod/distrod/tests; ./test_runner.sh run 19 | 20 | enter-integration-test-env: 21 | @echo Run 'cargo test -p distrod'. 22 | cd distrod/distrod/tests; ./test_runner.sh enter 23 | 24 | ALL_DISTROS_IN_TESTING=ubuntu debian archlinux fedora centos almalinux rockylinux kali mint opensuse amazonlinux oracle gentoo 25 | integration-test-linux-all-distros: 26 | cd distrod/distrod/tests; \ 27 | for distro in $(ALL_DISTROS_IN_TESTING); do \ 28 | DISTRO_TO_TEST=$${distro} ./test_runner.sh run; \ 29 | done 30 | 31 | test-linux: lint unit-test-linux integration-test-linux 32 | 33 | lint: 34 | shellcheck install.sh 35 | 36 | clean: 37 | cd distrod; cargo clean; cargo.exe clean 38 | 39 | ifneq ($(shell uname -a | grep microsoft),) # This is a WSL environment, which means you can run .exe 40 | ROOTFS_PATH = $(OUTPUT_ROOTFS_PATH) 41 | OUTPUT_PORT_PROXY_EXE_PATH = distrod/target/release/portproxy.exe 42 | 43 | $(ROOTFS_PATH): rootfs 44 | include windows.mk 45 | 46 | .PHONY: $(ROOTFS_PATH) 47 | endif 48 | 49 | .PHONY: build rootfs distrod-release distrod-bins lint clean\ 50 | unit-test-linux enter-integration-test-linux integration-test-linux integration-test-linux-all-distros test-linux 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Distrod - WSL2 Distros with Systemd! 2 | 3 | [![CI](https://github.com/nullpo-head/wsl-distrod/actions/workflows/ci.yaml/badge.svg)](https://github.com/nullpo-head/wsl-distrod/actions) 4 | 5 | Distrod is a systemd-based meta-distro for WSL2 that allows you to install Ubuntu, Arch Linux, Gentoo and many other distros 6 | with systemd in a minute, or make your current distro run systemd. 7 | 8 | Distrod also provides built-in auto-start feature and port forwarding service. 9 | This allows you to start systemd-managed services, such as `ssh`, on Windows startup and make it accessible from outside Windows. 10 | 11 | ![Arch, Debian, and Gentoo are running on WSL 2 with systemd running](docs/distrod_shot1.png) 12 | ![Demo gif](docs/distrod_demo.gif) 13 | 14 | With Distrod, you can 15 | 16 | 1. **Run systemd in WSL 2** 17 | You can do the both of the following 18 | 19 | - Install a new distro with systemd running 20 | - Make your current WSL 2 distro run systemd 21 | 22 | 2. **Install any image available from [linuxcontainers.org](https://linuxcontainers.org) as a WSL 2 distro in 1 minute[^1].** 23 | 24 | - The following distros are continuously tested 25 | - Ubuntu, Debian, Arch Linux, Fedora, CentOS, AlmaLinux, 26 | Rocky Linux, Kali Linux, Linux Mint, openSUSE, Amazon Linux, 27 | Oracle Linux, Gentoo Linux 28 | [![CI](https://github.com/nullpo-head/wsl-distrod/actions/workflows/ci.yaml/badge.svg)](https://github.com/nullpo-head/wsl-distrod/actions) (See `Integration test on Linux (distro_name)`) 29 | - Other distros may or may not work 30 | 31 | \* [linuxcontainers.org](https://linuxcontainers.org) is a vendor-neutral project that offers distro images for 32 | containers, which is unrelated to Distrod. LXC/LXD is one of its projects. 33 | Systemd runs in the installed distro, so you can also try LXC/LXD in WSL! 34 | 35 | 3. **Start WSL on Windows Startup.** 36 | This means that you can manage your ssh server and other services with systemd and start them automatically without any hassle! 37 | - Distrod also provides a port proxy service managed by systemd, 38 | allowing you to expose your Linux server to the outside world of Windows easily. 39 | 40 | Feature under development 41 | 42 | 1. **Make your dual-booted physical Linux distro on a separate disk run as a WSL instance.** 43 | 44 | [^1]: as long as your network connection is fast enough :) 45 | 46 | ## Install 47 | 48 | ### Option 1: Install a New Distro. 49 | 50 | 0. Make sure that your default WSL version is 2. 51 | 52 | ```console 53 | > wsl --set-default-version 2 54 | ``` 55 | 56 | 1. Download and unzip [the latest `distrod_wsl_launcher-x86_64.zip` from release](https://github.com/nullpo-head/wsl-distrod/releases/latest/download/distrod_wsl_launcher-x86_64.zip), and double-click the extracted `.exe` file. 57 | 58 | 2. Follow the wizard to install a new distro. 59 | 60 | 3. \[Optional\] To make your distro start on Windows startup, run the following command. 61 | 62 | ```bash 63 | sudo /opt/distrod/bin/distrod enable --start-on-windows-boot 64 | ``` 65 | 66 | You also might want to forward ports of services such as `ssh` to the outside of Windows. 67 | In that case, you can enable the built-in port proxy service provided by Distrod. 68 | 69 | **NOTE**: On Windows 11, `portproxy.service` doesn't work on Windows startup, which should be fixed soon. See [Known bus](docs/references.md#know-bugs). 70 | 71 | ```bash 72 | echo 22 | sudo tee /opt/distrod/conf/tcp4_ports # update the portproxy.service's configuration 73 | sudo systemctl enable --now portproxy.service # enable and start it 74 | ``` 75 | 76 | For more detailed instruction, see [Forward Ports to outside of Windows](docs/references.md#forward-ports-to-outside-of-windows). 77 | 78 | #### See also 79 | 80 | - [Launch WSL 2 on Windows Startup](docs/references.md#launch-wsl-2-on-windows-startup) 81 | - [Forward Ports to outside of Windows](docs/references.md#forward-ports-to-outside-of-windows) 82 | - [Troubleshoot WSL Network Down](docs/references.md#troubleshoot-wsl-network-down) 83 | - [Install and Run Multiple Distros at the same time](docs/references.md#install-and-run-multiple-distros-at-the-same-time) 84 | 85 | ### Option 2: Make your Current Distro Run Systemd 86 | 87 | By this installation, systemd is enabled in your WSL 2 distro. 88 | 89 | 1. Download and run the latest installer script. 90 | 91 | ```bash 92 | curl -L -O "https://raw.githubusercontent.com/nullpo-head/wsl-distrod/main/install.sh" 93 | chmod +x install.sh 94 | sudo ./install.sh install 95 | ``` 96 | 97 | This script installs distrod, but doesn't enable it yet. 98 | 99 | 2. Enable distrod in your distro 100 | 101 | You have two options. 102 | If you want to automatically start your distro on Windows startup, enable distrod by the following command 103 | 104 | ```bash 105 | /opt/distrod/bin/distrod enable --start-on-windows-boot 106 | ``` 107 | 108 | Otherwise, 109 | 110 | ```bash 111 | /opt/distrod/bin/distrod enable 112 | ``` 113 | 114 | You can run `enable` with `--start-on-windows-boot` again if you want to enable autostart later. 115 | 116 | 3. Restart your distro 117 | 118 | Close your WSL's terminal. 119 | Open a new Command Prompt window, and run the following command. 120 | 121 | ```bat 122 | wsl --terminate Distrod 123 | ``` 124 | 125 | After re-opening a new WSL window, your shell runs in a systemd session. 126 | 127 | #### See also 128 | 129 | - [Launch WSL 2 on Windows Startup](docs/references.md#launch-wsl-2-on-windows-startup) 130 | - [Forward Ports to outside of Windows](docs/references.md#forward-ports-to-outside-of-windows) 131 | - [Troubleshoot WSL Network Down](docs/references.md#troubleshoot-wsl-network-down) 132 | - [Open a Shell Session outside the Container for Systemd](docs/references.md#open-a-shell-session-outside-the-container-for-systemd) 133 | - [Disable Systemd / Distrod](docs/references.md#disable-systemd--distrod) 134 | 135 | ## Usage 136 | 137 | If you are using [Windows Terminal](https://github.com/microsoft/terminal), 138 | Windows Terminal will automatically find and register Distrod for you. 139 | Just open the tab named "Distrod". 140 | 141 | If you are using other terminals, please update your terminal settings to launch the Distrod. 142 | For reference, the following command launches a distro by name in WSL 143 | 144 | ```console 145 | > wsl --distribution Distrod 146 | ``` 147 | 148 | ## Update Distrod 149 | 150 | 1. Inside a Distrod session, download and run the latest installer script. 151 | 152 | ```bash 153 | curl -L -O "https://raw.githubusercontent.com/nullpo-head/wsl-distrod/main/install.sh" 154 | chmod +x install.sh 155 | sudo ./install.sh update 156 | ``` 157 | 158 | ## How Distrod Works 159 | 160 | In a nutshell, Distrod is a binary that creates a simple container that runs systemd as an init process, 161 | and starts your WSL sessions within that container. To realize that, Distrod does the following things. 162 | 163 | - Modify the rootfs of the concrete distro you chose so that it is compatible with both WSL and systemd. 164 | - Modify systemd services so that they are compatible with WSL 165 | - Configure networks for WSL 166 | - Put `/opt/distrod/bin/distrod` and other resources in the rootfs. 167 | - Register the Distrod's binary as the login shell 168 | - When Distrod is launched by WSL's init as a login shell, Distrod 169 | 1. Starts systemd in a simple container 170 | 2. Launches your actual shell within that container 171 | 3. Bridges between the systemd sessions and the WSL interop environment. 172 | 173 | ## Other Notes 174 | 175 | 1. Does WSLg works on Distrod? 176 | 177 | Yes! Distrod doesn't prevent anything about WSLg. Distrod sets up sockets for X11 and environment variables properly. 178 | 179 | However, WSLg itself has some quirks even on non-Distrod WSL2 distros. Try many things until a GUI app runs. 180 | For example, to run `xeyes` without failure, I had to run it three times on the non-Distrod official Ubuntu 20.04. 181 | 182 | ### See also 183 | 184 | - [References.md](docs/references.md) 185 | - [Open a Shell Session outside the Container for Systemd](docs/references.md#open-a-shell-session-outside-the-container-for-systemd) 186 | - [Disable Systemd / Distrod](docs/references.md#disable-systemd--distrod) 187 | -------------------------------------------------------------------------------- /assets/distrod_wsl_launcher-x86_64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/assets/distrod_wsl_launcher-x86_64.zip -------------------------------------------------------------------------------- /assets/opt_distrod.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/assets/opt_distrod.tar.gz -------------------------------------------------------------------------------- /distrod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "distrod", 5 | "distrod-exec", 6 | "distrod_wsl_launcher", 7 | "libs", 8 | "portproxy", 9 | ] 10 | -------------------------------------------------------------------------------- /distrod/distrod-exec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "distrod-exec" 3 | version = "0.1.7" 4 | authors = ["Takaya Saeki "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | libs = { path = "../libs" } 11 | log = "0.4" 12 | env_logger = "0.8" 13 | structopt = { version = "0.3" } 14 | anyhow = "1.0" 15 | nix = "0.20.0" 16 | -------------------------------------------------------------------------------- /distrod/distrod-exec/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use libs::cli_ui::LoggerInitializer; 3 | use libs::distro::{self, Distro, DistroLauncher}; 4 | use libs::distrod_config::DistrodConfig; 5 | use libs::multifork::set_noninheritable_sig_ign; 6 | use std::ffi::{CString, OsStr, OsString}; 7 | use std::os::unix::prelude::OsStrExt; 8 | use std::path::Path; 9 | use structopt::StructOpt; 10 | 11 | use libs::passwd::get_real_credential; 12 | 13 | /// Distrod-exec is a small helper command to allow a non-root user to run programs under the systemd container. 14 | /// It implements the subset features of distrod's exec subcommand, but has the setuid bit set. 15 | /// Typically it is run by the main distrod command when distrod is launched as an alias of another command. 16 | #[derive(Debug, StructOpt)] 17 | #[structopt(name = "distrod-exec")] 18 | pub struct Opts { 19 | pub command: OsString, 20 | pub arg0: OsString, 21 | pub args: Vec, 22 | 23 | /// Log level in the env_logger format. Simple levels: trace, debug, info(default), warn, error. 24 | #[structopt(short, long)] 25 | pub log_level: Option, 26 | 27 | /// /dev/kmsg log level in the env_logger format. Simple levels: trace, debug, info, warn, error(default). 28 | #[structopt(short, long)] 29 | pub kmsg_log_level: Option, 30 | } 31 | 32 | fn main() { 33 | let opts = Opts::from_args(); 34 | 35 | init_logger(&opts); 36 | 37 | if let Err(err) = run(opts) { 38 | log::error!("{:?}", err); 39 | } 40 | } 41 | 42 | fn init_logger(opts: &Opts) { 43 | let mut logger_initializer = LoggerInitializer::default(); 44 | let distrod_config = DistrodConfig::get(); 45 | if let Some(log_level) = opts.log_level.as_ref().cloned().or_else(|| { 46 | distrod_config 47 | .as_ref() 48 | .ok() 49 | .and_then(|config| config.distrod.log_level.clone()) 50 | }) { 51 | logger_initializer.with_log_level(log_level); 52 | } 53 | logger_initializer.with_kmsg(true); 54 | if let Some(kmsg_log_level) = opts.kmsg_log_level.as_ref().cloned().or_else(|| { 55 | distrod_config 56 | .ok() 57 | .and_then(|ref config| config.distrod.kmsg_log_level.clone()) 58 | }) { 59 | logger_initializer.with_kmsg_log_level(kmsg_log_level); 60 | } 61 | logger_initializer.init("Distrod".to_owned()); 62 | } 63 | 64 | fn run(opts: Opts) -> Result<()> { 65 | if distro::is_inside_running_distro() { 66 | exec_command(&opts.command, &opts.arg0, &opts.args).with_context(|| "exec_command failed.") 67 | } else { 68 | exec_command_in_distro(&opts.command, &opts.arg0, &opts.args) 69 | .with_context(|| "exec_command_in_distro failed.") 70 | } 71 | } 72 | 73 | fn exec_command(command: P1, arg0: S1, args: &[S2]) -> Result<()> 74 | where 75 | P1: AsRef, 76 | S1: AsRef, 77 | S2: AsRef, 78 | { 79 | log::debug!("distrod-exec: exec_command"); 80 | let cred = get_real_credential().with_context(|| "Failed to get the real credential.")?; 81 | cred.drop_privilege(); 82 | 83 | let path = CString::new(command.as_ref().as_os_str().as_bytes()).with_context(|| { 84 | format!( 85 | "Failed to construct a CString for the alias command.: '{:?}'", 86 | command.as_ref() 87 | ) 88 | })?; 89 | let mut cargs: Vec = vec![CString::new(arg0.as_ref().as_bytes())?]; 90 | cargs.extend(args.iter().map(|arg| { 91 | CString::new(arg.as_ref().as_bytes()) 92 | .expect("CString must be able to be created from non-null bytes.") 93 | })); 94 | nix::unistd::execv(&path, &cargs)?; 95 | std::process::exit(1); 96 | } 97 | 98 | fn exec_command_in_distro(command: P1, arg0: S1, args: &[S2]) -> Result<()> 99 | where 100 | P1: AsRef, 101 | S1: AsRef, 102 | S2: AsRef, 103 | { 104 | log::debug!("distrod-exec: exec_command_in_distro"); 105 | let inner = || -> Result<()> { 106 | let cred = get_real_credential().with_context(|| "Failed to get the real credential.")?; 107 | 108 | let distro = match DistroLauncher::get_running_distro() 109 | .with_context(|| "Failed to get the running distro.")? 110 | { 111 | Some(distro) => distro, 112 | None => launch_distro()?, 113 | }; 114 | 115 | log::debug!("Executing a command in the distro."); 116 | set_noninheritable_sig_ign(); 117 | let mut waiter = distro.exec_command( 118 | command.as_ref(), 119 | args, 120 | Some(std::env::current_dir().with_context(|| "Failed to get the current dir.")?), 121 | Some(arg0.as_ref()), 122 | Some(&cred), 123 | )?; 124 | cred.drop_privilege(); 125 | let status = waiter.wait(); 126 | std::process::exit(status as i32) 127 | }; 128 | 129 | if let Err(e) = inner() { 130 | log::error!("Failed to run the given command in the Systemd container. Fall back to normal WSL2 command execution without using Systemd. {:?}", e); 131 | return exec_command(command, arg0.as_ref(), args); 132 | } 133 | Ok(()) 134 | } 135 | 136 | fn launch_distro() -> Result { 137 | delay_init_launch(); 138 | log::debug!("starting /init from distrod-exec"); 139 | 140 | let mut distro_launcher = 141 | DistroLauncher::new().with_context(|| "Failed to init a DistroLauncher")?; 142 | distro_launcher 143 | .from_default_distro() 144 | .with_context(|| "Failed to get the default distro.")?; 145 | let distro = distro_launcher 146 | .launch() 147 | .with_context(|| "Failed to launch the distro.")?; 148 | Ok(distro) 149 | } 150 | 151 | static DISTROD_EXEC_DELAY_ENV_NAME: &str = "DISTROD_EXEC_INIT_LAUNCH_DELAY"; 152 | 153 | /// On some distros, starting Systemd during WSL's /init being initialized on Windows startup 154 | /// makes /init crash. So launch Systemd after some delay. 155 | fn delay_init_launch() { 156 | let delay_sec_str = match std::env::var(DISTROD_EXEC_DELAY_ENV_NAME) { 157 | Ok(delay_sec_str) => delay_sec_str, 158 | _ => return, 159 | }; 160 | let delay_sec: u32 = match delay_sec_str.parse() { 161 | Ok(delay_sec) => delay_sec, 162 | Err(e) => { 163 | log::warn!( 164 | "[BUG] Invalid {} was given: {:?}. {:?}", 165 | DISTROD_EXEC_DELAY_ENV_NAME, 166 | delay_sec_str, 167 | e 168 | ); 169 | return; 170 | } 171 | }; 172 | 173 | log::debug!( 174 | "Delaying launching init by {}sec. {:?}", 175 | delay_sec, 176 | std::time::Instant::now() 177 | ); 178 | std::thread::sleep(std::time::Duration::from_secs(delay_sec as u64)); 179 | 180 | strip_wslenv_for_distod_exec_delay(); 181 | log::debug!("delay finished {:?}", std::time::Instant::now()); 182 | } 183 | 184 | fn strip_wslenv_for_distod_exec_delay() { 185 | let inner = || -> Result<()> { 186 | let wslenv = std::env::var("WSLENV")?; 187 | if let Some(stripped) = wslenv.strip_suffix(&format!(":{}", DISTROD_EXEC_DELAY_ENV_NAME)) { 188 | std::env::set_var("WSLENV", stripped); 189 | } 190 | Ok(()) 191 | }; 192 | let _ = inner(); 193 | } 194 | -------------------------------------------------------------------------------- /distrod/distrod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "distrod" 3 | version = "0.1.7" 4 | authors = ["Takaya Saeki "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | libs = { path = "../libs" } 11 | structopt = { version = "0.3" } 12 | log = "0.4" 13 | env_logger = "0.8" 14 | anyhow = "1.0" 15 | nix = "0.20.0" 16 | indicatif = "0.16" 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0" 19 | scraper = "0.12" 20 | reqwest = { version = "0.11" } 21 | tokio = { version = "1.10", features = ["rt", "rt-multi-thread", "macros"] } 22 | chrono = "0.4" 23 | xz2 = "0.1" 24 | tar = "0.4.37" 25 | tempfile = "3.0" 26 | regex = "1.0" 27 | 28 | [dev-dependencies] 29 | once_cell = "1.8" 30 | 31 | [dev-dependencies.reqwest] 32 | version = "0.11" 33 | features = ["blocking"] 34 | -------------------------------------------------------------------------------- /distrod/distrod/resources/distrod_autostart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2021-08-02T23:43:02.0467885 5 | {{USER_NAME}} 6 | \{{TASK_NAME}} 7 | 8 | 9 | 10 | true 11 | PT30S 12 | 13 | 14 | 15 | 16 | Password 17 | LeastPrivilege 18 | 19 | 20 | 21 | IgnoreNew 22 | false 23 | false 24 | true 25 | false 26 | false 27 | 28 | true 29 | false 30 | 31 | true 32 | true 33 | false 34 | false 35 | false 36 | PT72H 37 | 7 38 | 39 | 40 | 41 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe 42 | 47 | -Command "do { 48 | $WslLaunchInteractive = @' 49 | [DllImport(\"wslapi.dll\")] 50 | public static extern uint WslLaunchInteractive( 51 | [MarshalAs(UnmanagedType.LPWStr)]string DistributionName, 52 | [MarshalAs(UnmanagedType.LPWStr)]string Command, 53 | [MarshalAs(UnmanagedType.U1)]bool UseCurrentWorkingDirectory, 54 | out uint ExitCode 55 | ); 56 | '@; 57 | 58 | $wslapi = Add-Type -MemberDefinition $WslLaunchInteractive -Name 'WslApi' -Namespace 'Win32' -PassThru; 59 | $exitcode=256; 60 | $Env:DISTROD_EXEC_INIT_LAUNCH_DELAY = \"20\"; 61 | $Env:WSLENV += \":DISTROD_EXEC_INIT_LAUNCH_DELAY\"; 62 | $wslapi::WslLaunchInteractive('{{DISTRO_NAME}}', 'exit', $false, [ref]$exitcode); 63 | } while($false) 64 | " 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /distrod/distrod/resources/schedule_autostart_task.ps1: -------------------------------------------------------------------------------- 1 | While ($true) { 2 | Start-Process C:\Windows\System32\schtasks.exe "/create /ru {{USER_NAME}} /tn {{TASK_NAME}} /xml {{TASK_FILE_WINDOWS_PATH}}" -Verb runas -Wait 3 | If ([bool](schtasks /query /fo list | Select-String -pattern "\s\\{{TASK_NAME}}" -quiet)) { 4 | echo "Enabling autostart has succeeded." 5 | break 6 | } 7 | If ($Host.UI.PromptForChoice("Error", "It seems the task has not been scheduled successfully. You may have typed a wrong password, or you may not have the necessary administrative privileges. Do you want to retry?", ("&Yes", "&No"), 0) -eq 1) { 8 | echo "Enabling autostart has been cancelled." 9 | exit 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /distrod/distrod/resources/unschedule_autostart_task.ps1: -------------------------------------------------------------------------------- 1 | If ([bool](schtasks /query /fo list | Select-String -pattern "\s\\{{TASK_NAME}}" -quiet)) { 2 | Start-Process C:\Windows\System32\schtasks.exe "/delete /tn {{TASK_NAME}} /f" -Verb runas -Wait 3 | echo "Autostart has been unscheduled." 4 | break 5 | } 6 | -------------------------------------------------------------------------------- /distrod/distrod/src/autostart.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, os::unix::prelude::PermissionsExt, path::Path, process::Command}; 2 | 3 | use anyhow::{anyhow, bail, Context, Result}; 4 | use regex::Regex; 5 | use tempfile::NamedTempFile; 6 | 7 | use libs::template::Template; 8 | use libs::wsl_interop; 9 | 10 | pub fn enable_autostart_on_windows_boot(distro_name: &str) -> Result<()> { 11 | let c = wsl_interop::get_wsl_drive_path("C")?.ok_or_else(|| anyhow!("C drive not found."))?; 12 | 13 | let user_name = get_user_name(&c)?; 14 | let (_task_xml, task_xml_win_path) = generate_task_xml(&user_name, distro_name)?; 15 | let sched_ps_cont = generate_schedule_posh_command(&user_name, &task_xml_win_path, distro_name); 16 | 17 | let mut powershell = 18 | Command::new(c.join("Windows/System32/WindowsPowerShell/v1.0/powershell.exe")); 19 | log::trace!("powershell command:\n{}", &sched_ps_cont); 20 | powershell.arg("-Command").arg(sched_ps_cont); 21 | let mut powershell = powershell 22 | .spawn() 23 | .with_context(|| "Failed to execute Powershell.")?; 24 | let status = powershell.wait()?; 25 | if !status.success() { 26 | bail!("Powershell failed. {}", status); 27 | } 28 | Ok(()) 29 | } 30 | 31 | pub fn disable_autostart_on_windows_boot(distro_name: &str) -> Result<()> { 32 | let c = wsl_interop::get_wsl_drive_path("C")?.ok_or_else(|| anyhow!("C drive not found."))?; 33 | 34 | let user_name = get_user_name(&c)?; 35 | let unsched_ps_cont = generate_unschedule_posh_command(&user_name, distro_name); 36 | 37 | let mut powershell = 38 | Command::new(c.join("Windows/System32/WindowsPowerShell/v1.0/powershell.exe")); 39 | log::trace!("powershell command:\n{}", &unsched_ps_cont); 40 | powershell.arg("-Command").arg(unsched_ps_cont); 41 | let mut powershell = powershell 42 | .spawn() 43 | .with_context(|| "Failed to execute Powershell.")?; 44 | let status = powershell.wait()?; 45 | if !status.success() { 46 | bail!("Powershell failed. {}", status); 47 | } 48 | Ok(()) 49 | } 50 | 51 | fn get_user_name(drive_path: &Path) -> Result { 52 | let mut whoami = Command::new(drive_path.join("Windows/System32/whoami.exe")); 53 | let user_name = whoami 54 | .output() 55 | .with_context(|| "Failed to execute whoami.exe.")?; 56 | if !user_name.status.success() { 57 | bail!( 58 | "whoami.exe had an error. command: '{:#?}', stderr: '{}'", 59 | whoami, 60 | String::from_utf8_lossy(&user_name.stderr) 61 | ); 62 | } 63 | Ok(String::from_utf8_lossy(&user_name.stdout) 64 | .trim() 65 | .to_string()) 66 | } 67 | 68 | fn generate_task_xml(user_name: &str, distro_name: &str) -> Result<(NamedTempFile, String)> { 69 | let bytes = include_bytes!("../resources/distrod_autostart.xml"); 70 | let mut task_xml = Template::new(String::from_utf8_lossy(bytes).into_owned()); 71 | task_xml 72 | .assign("USER_NAME", user_name) 73 | .assign("DISTRO_NAME", distro_name) 74 | .assign("TASK_NAME", &format!("StartDistrod_{}", &distro_name)); 75 | let mut task_xml_file = NamedTempFile::new().with_context(|| "Failed to create temp file.")?; 76 | 77 | task_xml_file 78 | .write_all(task_xml.render().as_bytes()) 79 | .with_context(|| "Failed to make the task xml file.")?; 80 | let mut perm = task_xml_file.as_file().metadata()?.permissions(); 81 | perm.set_mode(0o644); 82 | task_xml_file 83 | .as_file_mut() 84 | .set_permissions(perm) 85 | .with_context(|| { 86 | format!( 87 | "Failed to set the permission of {:#?}", 88 | task_xml_file.path() 89 | ) 90 | })?; 91 | 92 | let mut wslpath = Command::new("/bin/wslpath"); 93 | wslpath.args(&["-w".as_ref(), task_xml_file.path().as_os_str()]); 94 | let path_to_task_xml = wslpath.output().with_context(|| { 95 | format!( 96 | "Failed to execute wslpath -w '{}'", 97 | task_xml_file.path().to_string_lossy() 98 | ) 99 | })?; 100 | if !path_to_task_xml.status.success() { 101 | bail!( 102 | "wslpath -w '{}' exited with error. stderr: {}", 103 | &task_xml_file.path().to_string_lossy(), 104 | String::from_utf8_lossy(&path_to_task_xml.stderr) 105 | ); 106 | } 107 | let task_xml_win_path = String::from_utf8_lossy(&path_to_task_xml.stdout) 108 | .trim() 109 | .to_owned(); 110 | 111 | Ok((task_xml_file, task_xml_win_path)) 112 | } 113 | 114 | fn generate_schedule_posh_command( 115 | user_name: &str, 116 | task_file_path: &str, 117 | distro_name: &str, 118 | ) -> String { 119 | let bytes = include_bytes!("../resources/schedule_autostart_task.ps1"); 120 | let mut sched_ps = Template::new(String::from_utf8_lossy(bytes).into_owned()); 121 | sched_ps 122 | .assign("USER_NAME", user_name) 123 | .assign("TASK_FILE_WINDOWS_PATH", task_file_path) 124 | .assign("TASK_NAME", &get_schedule_task_name(user_name, distro_name)); 125 | sched_ps.render() 126 | } 127 | 128 | fn generate_unschedule_posh_command(user_name: &str, distro_name: &str) -> String { 129 | let bytes = include_bytes!("../resources/unschedule_autostart_task.ps1"); 130 | let mut sched_ps = Template::new(String::from_utf8_lossy(bytes).into_owned()); 131 | sched_ps.assign("TASK_NAME", &get_schedule_task_name(user_name, distro_name)); 132 | sched_ps.render() 133 | } 134 | 135 | fn get_schedule_task_name(user_name: &str, distro_name: &str) -> String { 136 | let nonlatin = Regex::new("[^a-zA-Z0-9]").unwrap(); 137 | let user_name = nonlatin.replace_all(user_name, "-"); 138 | format!("StartWSL_{}_for_{}", distro_name, user_name) 139 | } 140 | -------------------------------------------------------------------------------- /distrod/distrod/src/shell_hook.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | io::{BufRead, BufReader, BufWriter, Seek, SeekFrom, Write}, 4 | }; 5 | 6 | use anyhow::{Context, Result}; 7 | 8 | use crate::passwd::{Passwd, PasswdFile}; 9 | use libs::command_alias::CommandAlias; 10 | 11 | pub fn enable_default_shell_hook() -> Result<()> { 12 | let mut shells = HashSet::new(); 13 | let mut passwd_file = PasswdFile::open("/etc/passwd")?; 14 | passwd_file.update(&mut |passwd| { 15 | if CommandAlias::is_alias(passwd.shell) { 16 | return Ok(None); 17 | } 18 | let alias = CommandAlias::open_from_source(passwd.shell, true)? 19 | .expect("an alias should be created."); 20 | let mut new_passwd = Passwd::from_view(passwd); 21 | let shell = alias.get_link_path().to_string_lossy().to_string(); 22 | shells.insert(shell.clone()); 23 | new_passwd.shell = shell; 24 | Ok(Some(new_passwd)) 25 | })?; 26 | if let Err(e) = register_shells_to_system(shells) { 27 | log::warn!("Failed to register shells to system. {}", e); 28 | } 29 | Ok(()) 30 | } 31 | 32 | pub fn disable_default_shell_hook() -> Result<()> { 33 | let mut passwd_file = PasswdFile::open("/etc/passwd")?; 34 | passwd_file.update(&mut |passwd| { 35 | if !CommandAlias::is_alias(passwd.shell) { 36 | return Ok(None); 37 | } 38 | let alias = CommandAlias::open_from_link(passwd.shell)?; 39 | let mut new_passwd = Passwd::from_view(passwd); 40 | new_passwd.shell = alias.get_source_path().to_string_lossy().to_string(); 41 | Ok(Some(new_passwd)) 42 | })?; 43 | Ok(()) 44 | } 45 | 46 | fn register_shells_to_system(mut shell_paths: HashSet) -> Result<()> { 47 | { 48 | let mut open_opts = std::fs::OpenOptions::new(); 49 | open_opts.read(true).write(false).create(false); 50 | let shells = open_opts 51 | .open("/etc/shells") 52 | .with_context(|| "Failed to open /etc/shells")?; 53 | let shells = BufReader::new(shells); 54 | for line in shells.lines() { 55 | let line = line?; 56 | if shell_paths.contains(&line) { 57 | shell_paths.remove(&line); 58 | } 59 | } 60 | } 61 | let mut open_opts = std::fs::OpenOptions::new(); 62 | open_opts.read(true).append(true).create(false); 63 | let shells = open_opts 64 | .open("/etc/shells") 65 | .with_context(|| "Failed to open /etc/shells to write")?; 66 | let mut shells = BufWriter::new(shells); 67 | shells 68 | .seek(SeekFrom::End(0)) 69 | .with_context(|| "Failed to seek to end")?; 70 | for shell_path in shell_paths { 71 | if shell_path.ends_with("/nologin") || shell_path.ends_with("/false") { 72 | continue; 73 | } 74 | shells 75 | .write(format!("{}\n", &shell_path).as_bytes()) 76 | .with_context(|| format!("Failed to write '{}' to /etc/shells.", &shell_path))?; 77 | } 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /distrod/distrod/tests/test_runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | ### 6 | # Because Distrod doesn't implement nested distrod instance running, 7 | # this script runs the integration test in a new mount namespace to 8 | # avoid the problem caused by nesting 9 | ### 10 | 11 | usage() { 12 | cat<<'EOS' 13 | Usage: test_runner.sh COMMAND [options...] 14 | run Run the integration test. 15 | enter Enter the namespaces for testing. 16 | EOS 17 | } 18 | 19 | main () { 20 | if [ "$1" != run ] && [ "$1" != enter ]; then 21 | usage 22 | exit 1 23 | fi 24 | COMMAND="$1" 25 | 26 | if [ "$2" != "--unshared" ]; then 27 | sudo -E unshare -mfp sudo -E -u "$(whoami)" "$0" "$COMMAND" --unshared "$(which cargo)" 28 | EXIT_CODE=$? 29 | teardown_overlayfs_workdir 30 | exit $EXIT_CODE 31 | else 32 | sudo mount -t proc none /proc # Make it see the new PIDs 33 | fi 34 | 35 | ## 36 | # From here, this script runs in the new mount and PID namespace 37 | ## 38 | 39 | if [ -z "$3" ]; then 40 | echo "Error: Internal usage: $0 $COMMAND --unshared path_to_cargo [options...]" >&2 41 | exit 1 42 | fi 43 | CARGO="$3" 44 | 45 | setup_overlayfs_workdir 46 | prepare_for_nested_distrod 47 | mount_opt_distrod 48 | set_pseudo_wsl_envs 49 | NS="itestns" 50 | remove_pseudo_wsl_netns "$NS" # delete netns and interfaces if there is existing ones 51 | create_pseudo_wsl_netns "$NS" 52 | # Use 8.8.8.8 as the name server. Because the cargo runs in the new netns, 53 | # it cannot refer to the host's name server anymore. 54 | set_name_server_to_public_dns 55 | DISTROD_INSTALL_DIR="${DISTROD_INSTALL_DIR:-"$(mktemp -d)"}" 56 | make_rootfs_dir "$DISTROD_INSTALL_DIR" 57 | 58 | # Export environment variables the integration test expects the runner to set 59 | export DISTROD_INSTALL_DIR 60 | export DISTROD_IMAGE_CACHE_DIR="${DISTROD_IMAGE_CACHE_DIR:-"$(dirname "$0")/../../.cache/distrod_integration_test"}" 61 | RELIABLE_CONNECTION_IP_ADDRESS="$(dig +short www.google.com | head -n 1)" 62 | export RELIABLE_CONNECTION_IP_ADDRESS 63 | export DISTRO_TO_TEST="${DISTRO_TO_TEST:-ubuntu}" 64 | echo "Testing ${DISTRO_TO_TEST}" 65 | 66 | # run the tests 67 | set +e 68 | case "$COMMAND" in 69 | run) 70 | # shellcheck disable=SC2086 71 | sudo -E -- ip netns exec "$NS" sudo -E -u "$(whoami)" -- "$CARGO" test --verbose -p distrod ${TEST_TARGETS} 72 | EXIT_CODE=$? 73 | ;; 74 | enter) 75 | sudo -E -- ip netns exec "$NS" sudo -E -u "$(whoami)" -- bash 76 | EXIT_CODE=0 77 | ;; 78 | esac 79 | set -e 80 | 81 | kill_distrod || true 82 | remove_rootfs_dir "$DISTROD_INSTALL_DIR" || true 83 | if [ "$EXIT_CODE" != 0 ]; then 84 | show_iptables_debug_info 85 | fi 86 | remove_pseudo_wsl_netns "$NS" || true 87 | 88 | exit $EXIT_CODE 89 | } 90 | 91 | OVERLAY_TMP_DIR=/tmp/distrod_test 92 | 93 | setup_overlayfs_workdir() { 94 | sudo rm -rf "${OVERLAY_TMP_DIR}" 95 | mkdir -p "${OVERLAY_TMP_DIR}" 96 | } 97 | 98 | teardown_overlayfs_workdir() { 99 | sudo rm -rf "${OVERLAY_TMP_DIR}" 100 | } 101 | 102 | mount_overlay() { 103 | TARGET="$1" 104 | UPPER="${OVERLAY_TMP_DIR}/${TARGET}/upper" 105 | WORK="${OVERLAY_TMP_DIR}/${TARGET}/work" 106 | mkdir -p "${UPPER}" "${WORK}" 107 | sudo mount --bind "${TARGET}" "${TARGET}" 108 | sudo mount -t overlay overlay -o "lowerdir=${TARGET},upperdir=${UPPER},workdir=${WORK}" "${TARGET}" 109 | } 110 | 111 | prepare_for_nested_distrod() { 112 | # Enter a new mount namespace for testing. 113 | # To make distrod think it's not inside another distrod, 114 | # 1. Delete /run/distrod without affecting the running distrod by 115 | # mounting overlay 116 | # 2. Unmount directories under /mnt/distrod_root, which is a condition 117 | # distrod checks 118 | mount_overlay /run 119 | sudo rm -rf /run/distrod 120 | sudo umount /mnt/distrod_root/proc || true # may not exist 121 | } 122 | 123 | mount_opt_distrod() { 124 | # Make the new /opt/distrod to test 125 | mount_overlay /opt 126 | mkdir -p /opt/distrod 127 | sudo cp -R "$(dirname "$0")"/../../../distrod_packer/resources/* /opt/distrod/ 128 | } 129 | 130 | set_pseudo_wsl_envs() { 131 | # Simulate WSL environment variables on non-WSL Linux such as on 132 | # the GitHub action runner. 133 | export WSL_DISTRO_NAME=DUMMY_DISTRO 134 | export WSL_INTEROP=/run/WSL/1_interop 135 | } 136 | 137 | create_pseudo_wsl_netns() { 138 | NS_NAME="$1" 139 | 140 | # set variables such as $INTERFACE_BRIDGE 141 | set_link_name_variables "$NS_NAME" 142 | 143 | # create a ns 144 | sudo ip netns del "$NS_NAME" > /dev/null 2>&1 || true 145 | sudo ip netns add "$NS_NAME" 146 | 147 | # create a veth for the guest as "eth0", which is the name of the inferface on WSL 148 | sudo ip link add name "$INTERFACE_GUEST" type veth peer name "$INTERFACE_GUEST_PEER" 149 | sudo ip link set "$INTERFACE_GUEST" netns "$NS_NAME" 150 | sudo ip netns exec "$NS_NAME" ip link set "$INTERFACE_GUEST" name eth0 151 | INTERFACE_GUEST="eth0" 152 | 153 | # create a bridge 154 | sudo ip link add name "$INTERFACE_BRIDGE" type bridge 155 | sudo ip link set dev "$INTERFACE_GUEST_PEER" master "$INTERFACE_BRIDGE" 156 | 157 | # Set IP addresses 158 | HOST_IP="${SUBNET}.1" 159 | sudo ip addr add "${HOST_IP}/24" dev "$INTERFACE_BRIDGE" 160 | sudo ip netns exec "$NS_NAME" ip addr add "${SUBNET}.2/24" dev "$INTERFACE_GUEST" 161 | 162 | # Link up the guest interfaces 163 | sudo ip netns exec "$NS_NAME" ip link set lo up 164 | sudo ip netns exec "$NS_NAME" ip link set "$INTERFACE_GUEST" up 165 | sudo ip link set "$INTERFACE_GUEST_PEER" up 166 | # Set the default gateway 167 | sudo ip netns exec "$NS_NAME" ip route add default via "$HOST_IP" 168 | 169 | # Link up the bridge 170 | sudo ip link set "$INTERFACE_BRIDGE" up 171 | 172 | # Enable IP forwarding 173 | echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward > /dev/null 174 | 175 | # Forward packets from/to the bridge 176 | sudo iptables -A FORWARD -i "${INTERFACE_BRIDGE}" -j ACCEPT 177 | sudo iptables -A FORWARD -o "${INTERFACE_BRIDGE}" -j ACCEPT 178 | 179 | # Set up a NAT 180 | sudo iptables -t nat -A POSTROUTING -s "${SUBNET}.0/24" -j MASQUERADE 181 | } 182 | 183 | remove_pseudo_wsl_netns() { 184 | NS_NAME="$1" 185 | 186 | # set variables such as $INTERFACE_BRIDGE 187 | set_link_name_variables "$NS_NAME" 188 | 189 | sudo ip netns delete "$NS_NAME" > /dev/null 2>&1 || true 190 | sudo ip link delete "$INTERFACE_BRIDGE" > /dev/null 2>&1 || true 191 | sudo ip link delete "$INTERFACE_GUEST" > /dev/null 2>&1 || true 192 | sudo ip link delete "$INTERFACE_GUEST_PEER" > /dev/null 2>&1 || true 193 | 194 | sudo iptables -D FORWARD -i "${INTERFACE_BRIDGE}" -j ACCEPT > /dev/null 2>&1 || true 195 | sudo iptables -D FORWARD -o "${INTERFACE_BRIDGE}" -j ACCEPT > /dev/null 2>&1 || true 196 | 197 | # Set up a NAT 198 | sudo iptables -t nat -D POSTROUTING -s "${SUBNET}.0/24" -j MASQUERADE > /dev/null 2>&1 || true 199 | } 200 | 201 | show_iptables_debug_info() { 202 | set -x 203 | sudo iptables -t nat -nvL POSTROUTING --line-number 204 | sudo iptables -nvL FORWARD --line-number 205 | set +x 206 | } 207 | 208 | set_link_name_variables() { 209 | NS_NAME="$1" 210 | 211 | SUBNET="192.168.99" 212 | INTERFACE_GUEST="veth-${NS_NAME}" 213 | INTERFACE_GUEST_PEER="br-g${NS_NAME}" 214 | INTERFACE_BRIDGE="${NS_NAME}" 215 | 216 | if [ "${#INTERFACE_GUEST}" -ge 16 ]; then 217 | echo "NS_NAME must be shorter so that INTERFACE_GUEST becomes shorter than 16 characters." >&2 218 | return 1 219 | fi 220 | } 221 | 222 | set_name_server_to_public_dns() { 223 | cat > /tmp/resolv.conf < /dev/null 232 | return $? 233 | } 234 | 235 | make_rootfs_dir() { 236 | mkdir -p "$1" 237 | chmod 755 "$1" 238 | sudo chown root:root "$1" 239 | } 240 | 241 | kill_distrod() { 242 | sudo "$(dirname "$0")"/../../target/debug/distrod stop -9 243 | } 244 | 245 | remove_rootfs_dir() { 246 | if [ "${KEEP_ROOTFS}" = 1 ]; then 247 | echo "Keeping the rootfs at $1" >&2 248 | return 249 | fi 250 | sudo rm -rf "$1" 251 | } 252 | 253 | main "$@" 254 | -------------------------------------------------------------------------------- /distrod/distrod_wsl_launcher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "distrod_wsl_launcher" 3 | version = "0.1.7" 4 | authors = ["Takaya Saeki "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | libs = { path = "../libs" } 11 | structopt = { version = "0.3" } 12 | log = "0.4" 13 | env_logger = "0.8" 14 | strum = { version = "0.20", features = ["derive"] } 15 | anyhow = "1.0" 16 | xz2 = "0.1" 17 | tar = { git = "https://github.com/nullpo-head/tar-rs", branch = "append_link" } 18 | flate2 = "1.0" 19 | indicatif = "0.16" 20 | chrono = "0.4" 21 | scraper = "0.12" 22 | reqwest = { version = "0.11" } 23 | tokio = { version = "1.10", features = ["rt", "rt-multi-thread", "macros"] } 24 | colored = "2" 25 | tempfile = "3" 26 | bytes = "1.0" 27 | regex = "1" 28 | 29 | [dependencies.windows] 30 | version = "0.25.0" 31 | features = [ 32 | "Win32_Foundation", 33 | "Win32_System_SubsystemForLinux", 34 | ] 35 | -------------------------------------------------------------------------------- /distrod/distrod_wsl_launcher/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/distrod/distrod_wsl_launcher/resources/.gitkeep -------------------------------------------------------------------------------- /distrod/distrod_wsl_launcher/src/tar_helper.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::collections::HashSet; 3 | use std::ffi::OsString; 4 | use std::io::{Cursor, Read}; 5 | use std::iter::FromIterator; 6 | use std::path::Path; 7 | 8 | pub fn append_tar_archive( 9 | builder: &mut tar::Builder, 10 | archive: &mut tar::Archive, 11 | exclusion: I, 12 | ) -> Result<()> 13 | where 14 | W: std::io::Write, 15 | R: std::io::Read, 16 | I: IntoIterator, 17 | P: AsRef, 18 | { 19 | let exclusion_set = HashSet::::from_iter( 20 | exclusion 21 | .into_iter() 22 | .flat_map(|path| gen_name_candidates_for_path(path.as_ref())), 23 | ); 24 | 25 | let entries = archive 26 | .entries() 27 | .with_context(|| "Failed to open the archive")?; 28 | 29 | for entry in entries { 30 | let mut entry = entry.with_context(|| "An archive entry is an error.")?; 31 | 32 | let path = entry 33 | .path() 34 | .with_context(|| "Failed to get a path of a tar entry.")? 35 | .as_os_str() 36 | .to_os_string(); 37 | if exclusion_set.contains(path.as_os_str()) { 38 | log::debug!("skipping tar entry: {:?}", &path); 39 | continue; 40 | } 41 | 42 | let mut gnu_header = 43 | to_gnu_header(entry.header()).unwrap_or_else(|| entry.header().clone()); 44 | 45 | if let Some(link_name) = entry 46 | .link_name() 47 | .with_context(|| format!("Failed to get the link_name {:?}", &path))? 48 | { 49 | builder 50 | .append_link(&mut gnu_header, &path, link_name.as_os_str()) 51 | .with_context(|| format!("Failed to append_link {:?}", &path))?; 52 | } else { 53 | let mut data = vec![]; 54 | entry 55 | .read_to_end(&mut data) 56 | .with_context(|| format!("Failed to read the data of an entry: {:?}.", &path))?; 57 | builder 58 | .append_data(&mut gnu_header, &path, Cursor::new(data)) 59 | .with_context(|| format!("Failed to add an entry to an archive. {:?}", &path))?; 60 | } 61 | } 62 | Ok(()) 63 | } 64 | 65 | fn to_gnu_header(header: &tar::Header) -> Option { 66 | if header.as_gnu().is_some() { 67 | return None; 68 | } 69 | if let Some(ustar) = header.as_ustar() { 70 | return Some(convert_ustar_to_gnu_header(ustar)); 71 | } 72 | Some(convert_old_to_gnu_header(header.as_old())) 73 | } 74 | 75 | fn convert_ustar_to_gnu_header(header: &tar::UstarHeader) -> tar::Header { 76 | let mut gnu_header = convert_old_to_gnu_header(header.as_header().as_old()); 77 | let as_gnu = gnu_header 78 | .as_gnu_mut() 79 | .expect("new_gnu should return a GNU header"); 80 | 81 | as_gnu.typeflag = header.typeflag; 82 | as_gnu.uname = header.uname; 83 | as_gnu.gname = header.gname; 84 | as_gnu.dev_major = header.dev_major; 85 | as_gnu.dev_minor = header.dev_minor; 86 | gnu_header 87 | } 88 | 89 | fn convert_old_to_gnu_header(header: &tar::OldHeader) -> tar::Header { 90 | let mut gnu_header = tar::Header::new_gnu(); 91 | let as_gnu = gnu_header 92 | .as_gnu_mut() 93 | .expect("new_gnu should return a GNU header"); 94 | as_gnu.name = header.name; 95 | as_gnu.mode = header.mode; 96 | as_gnu.uid = header.uid; 97 | as_gnu.gid = header.gid; 98 | as_gnu.size = header.size; 99 | as_gnu.mtime = header.mtime; 100 | as_gnu.cksum = header.cksum; 101 | as_gnu.linkname = header.linkname; 102 | gnu_header 103 | } 104 | 105 | fn gen_name_candidates_for_path>(path: P) -> Vec { 106 | // There are several ways to represent an absolute path. 107 | let mut candidates = vec![path.as_ref().as_os_str().to_os_string()]; 108 | let path = path.as_ref(); 109 | if let Ok(alt_path_rel) = path.strip_prefix("/") { 110 | candidates.push(alt_path_rel.as_os_str().to_owned()); 111 | let mut alt_path_dot = OsString::from("."); 112 | alt_path_dot.push(path); 113 | candidates.push(alt_path_dot.to_os_string()); 114 | } 115 | candidates 116 | } 117 | -------------------------------------------------------------------------------- /distrod/distrod_wsl_launcher/src/wsl.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{OsStr, OsString}, 3 | path::Path, 4 | }; 5 | 6 | use anyhow::{anyhow, Context, Result}; 7 | use windows::{ 8 | runtime::IntoParam, 9 | Win32::{Foundation::PWSTR, System::SubsystemForLinux::*}, 10 | }; 11 | 12 | pub unsafe fn is_distribution_registered<'a, Param0: IntoParam<'a, PWSTR>>( 13 | distributionname: Param0, 14 | ) -> bool { 15 | WslIsDistributionRegistered(distributionname).as_bool() 16 | } 17 | 18 | pub unsafe fn register_distribution<'a, Param0, Path0>( 19 | distributionname: Param0, 20 | targzfilename: Path0, 21 | ) -> Result<()> 22 | where 23 | Param0: IntoParam<'a, PWSTR> + std::fmt::Debug, 24 | Path0: AsRef, 25 | { 26 | let err = format!( 27 | "WslRegisterDistribution failed. distro: {:?}, path: {:?}", 28 | &distributionname, 29 | targzfilename.as_ref() 30 | ); 31 | let path = targzfilename.as_ref().as_os_str(); 32 | WslRegisterDistribution(distributionname, path).with_context(|| err) 33 | } 34 | 35 | pub unsafe fn set_distribution_default_user<'a, Param0: IntoParam<'a, PWSTR> + std::fmt::Debug>( 36 | distributionname: Param0, 37 | defaultuid: u32, 38 | ) -> Result<()> { 39 | let err = format!( 40 | "WslConfigureDistribution failed. distribution_name: {:?} uid: {}", 41 | distributionname, defaultuid 42 | ); 43 | let default_distro_flag = WSL_DISTRIBUTION_FLAGS_ENABLE_INTEROP 44 | | WSL_DISTRIBUTION_FLAGS_APPEND_NT_PATH 45 | | WSL_DISTRIBUTION_FLAGS_ENABLE_DRIVE_MOUNTING; 46 | WslConfigureDistribution(distributionname, defaultuid, default_distro_flag).with_context(|| err) 47 | } 48 | 49 | #[derive(Debug)] 50 | pub struct WslCommand { 51 | distribution_name: OsString, 52 | command: Option, 53 | args: Vec, 54 | } 55 | 56 | pub struct WslCommandOutput { 57 | pub status: i32, 58 | pub stdout: Vec, 59 | pub stderr: Vec, 60 | } 61 | 62 | impl WslCommand { 63 | pub fn new, S2: AsRef>( 64 | command: Option, 65 | distribution_name: S2, 66 | ) -> WslCommand { 67 | WslCommand { 68 | distribution_name: distribution_name.as_ref().to_owned(), 69 | command: command.map(|s| s.as_ref().to_owned()), 70 | args: vec![], 71 | } 72 | } 73 | 74 | pub fn arg>(&mut self, arg: S) -> &mut Self { 75 | self.args.push(arg.as_ref().to_owned()); 76 | self 77 | } 78 | 79 | pub fn args(&mut self, args: I) -> &mut Self 80 | where 81 | S: AsRef, 82 | I: IntoIterator, 83 | { 84 | self.args 85 | .extend(args.into_iter().map(|s| s.as_ref().to_owned())); 86 | self 87 | } 88 | 89 | pub fn status(&mut self) -> Result { 90 | // Use wsl command instead of winapi for now, since it seems more robust way to avoid 91 | // strange crash on Windows 11. 92 | let status = self 93 | .gen_command() 94 | .status() 95 | .with_context(|| format!("Failed to invoke status() {:?}", &self))?; 96 | status 97 | .code() 98 | .ok_or_else(|| anyhow!("Failed to get the exit code.")) 99 | } 100 | 101 | pub fn output(&mut self) -> Result { 102 | // Use wsl command instead of winapi for now, since it seems more robust way to avoid 103 | // strange crash on Windows 11. 104 | let output = self 105 | .gen_command() 106 | .output() 107 | .with_context(|| format!("Failed to invoke output() {:?}", &self))?; 108 | let status = output 109 | .status 110 | .code() 111 | .ok_or_else(|| anyhow!("Failed to get the exit code {:?}", &self))?; 112 | let stdout = output.stdout; 113 | let stderr = output.stderr; 114 | Ok(WslCommandOutput { 115 | status, 116 | stdout, 117 | stderr, 118 | }) 119 | } 120 | 121 | fn gen_command(&mut self) -> std::process::Command { 122 | let mut command = std::process::Command::new("wsl"); 123 | command.arg("-d"); 124 | command.arg(&self.distribution_name); 125 | if let Some(ref command_name) = self.command { 126 | command.arg("--"); 127 | command.arg(command_name); 128 | command.args(&self.args); 129 | } 130 | command 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /distrod/libs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libs" 3 | version = "0.1.0" 4 | authors = ["Takaya Saeki "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | async-trait = "0.1.51" 11 | anyhow = "1.0" 12 | chrono = "0.4" 13 | colored = "2" 14 | log = "0.4" 15 | env_logger = "0.8" 16 | scraper = "0.12" 17 | indicatif = "0.16" 18 | reqwest = { version = "0.11" } 19 | glob = "0.3" 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_json = "1.0" 22 | strum = { version = "0.20", features = ["derive"] } 23 | systemd-parser= "0.1" 24 | tracing = "0.1" 25 | tracing-subscriber = { version = "0.3.1", features = ["registry"] } 26 | tracing-log = "0.1" 27 | toml = "0.4" 28 | once_cell = "1.8" 29 | nom = "7.0" 30 | regex = "1.5" 31 | 32 | [dev-dependencies] 33 | tempfile = "3.0" 34 | 35 | [target.'cfg(target_os = "linux")'.dependencies] 36 | passfd = "0.1" 37 | nix = "0.20.0" 38 | procfs = "0.9" 39 | flate2 = "1.0" 40 | tar = "0.4" 41 | 42 | [target.'cfg(target_os = "windows")'.dependencies] 43 | ansi_term = "0.12" 44 | -------------------------------------------------------------------------------- /distrod/libs/resources/load_per_user_wsl_envs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Load additional WSL session environment variables at runtime by sourcing 4 | # a script Distrod creates at runtime. A Linux user who launches Distrod first 5 | # can manipulte the contents of this script, so the script file is per-user one 6 | # to prevent the user from manipulating other user's environment variables. 7 | if [ -e "{{PER_USER_WSL_ENV_INIT_SCRIPT_PATH}}" ]; then 8 | . "{{PER_USER_WSL_ENV_INIT_SCRIPT_PATH}}" 9 | fi 10 | 11 | # If the creator of the script is root, a non-root user loading it is harmless 12 | if [ "$(id -u)" != 0 ] && [ -e "{{ROOT_WSL_ENV_INIT_SCRIPT_PATH}}" ]; then 13 | . "{{ROOT_WSL_ENV_INIT_SCRIPT_PATH}}" 14 | fi 15 | -------------------------------------------------------------------------------- /distrod/libs/src/cli_ui.rs: -------------------------------------------------------------------------------- 1 | use crate::distro_image::{DefaultImageFetcher, DistroImageFetcher, DistroImageList}; 2 | use anyhow::{bail, Context, Result}; 3 | use colored::*; 4 | use std::{ffi::OsString, fmt::Debug, io::Write}; 5 | use tracing::metadata::LevelFilter; 6 | use tracing_subscriber::{fmt::FormatEvent, prelude::*}; 7 | 8 | #[derive(Default, Debug)] 9 | pub struct LoggerInitializer { 10 | logs_kmsg: bool, 11 | log_level: Option, 12 | kmsg_log_level: Option, 13 | } 14 | 15 | impl LoggerInitializer { 16 | pub fn with_kmsg(&mut self, logs_kmsg: bool) -> &mut Self { 17 | self.logs_kmsg = logs_kmsg; 18 | self 19 | } 20 | 21 | pub fn with_log_level(&mut self, log_level: String) -> &mut Self { 22 | self.log_level = Some(log_level); 23 | self 24 | } 25 | 26 | pub fn with_kmsg_log_level(&mut self, log_level: String) -> &mut Self { 27 | self.kmsg_log_level = Some(log_level); 28 | self 29 | } 30 | 31 | pub fn init(self, app_name: String) { 32 | let inner = || -> Result<()> { 33 | let terminal_formatter = TerminalLogFormatter::new(app_name.clone()); 34 | let mut terminal_filter = 35 | tracing_subscriber::filter::Targets::new().with_default(LevelFilter::INFO); 36 | if let Some(target) = self 37 | .log_level 38 | .or_else(|| std::env::var("RUST_LOG").ok()) 39 | .and_then(|level| { 40 | level 41 | .parse() 42 | .map_err(|e| { 43 | eprintln!("Invalid log level format {:?}", e); 44 | e 45 | }) 46 | .ok() 47 | }) 48 | { 49 | terminal_filter = target; 50 | }; 51 | let terminal_fmt_layer = tracing_subscriber::fmt::layer() 52 | .with_target(false) 53 | .event_format(terminal_formatter) 54 | .with_writer(std::io::stderr) 55 | .with_filter(terminal_filter); 56 | 57 | if !self.logs_kmsg { 58 | tracing::subscriber::set_global_default( 59 | tracing_subscriber::registry().with(terminal_fmt_layer), 60 | ) 61 | .with_context(|| "set_global_default failed.")?; 62 | tracing_log::LogTracer::init() 63 | .with_context(|| "Failed to init LogTracer with terminal logger.")?; 64 | return Ok(()); 65 | } 66 | 67 | let kmsg_formatter = KmsgLogFormatter::new(app_name); 68 | let mut kmsg_filter = 69 | tracing_subscriber::filter::Targets::new().with_default(LevelFilter::ERROR); 70 | if let Some(target) = self.kmsg_log_level.and_then(|level| { 71 | level 72 | .parse() 73 | .map_err(|e| { 74 | eprintln!("Invalid kmsg log level format {:?}", e); 75 | e 76 | }) 77 | .ok() 78 | }) { 79 | kmsg_filter = target; 80 | }; 81 | let kmsg_fmt_layer = tracing_subscriber::fmt::layer() 82 | .with_target(false) 83 | .event_format(kmsg_formatter) 84 | .with_writer(|| { 85 | KmsgLogFormatter::get_writer() 86 | .expect("Failed to get writer from TerminalLogFormatter") 87 | }) 88 | .with_filter(kmsg_filter); 89 | 90 | tracing::subscriber::set_global_default( 91 | tracing_subscriber::registry() 92 | .with(terminal_fmt_layer) 93 | .with(kmsg_fmt_layer), 94 | ) 95 | .with_context(|| "set_global_default for kmsg failed.")?; 96 | tracing_log::LogTracer::init().with_context(|| { 97 | "Failed to init LogTracer with terminal logger and kmsg logger." 98 | })?; 99 | 100 | Ok(()) 101 | }; 102 | inner() 103 | .with_context(|| "Failed to init the logger") 104 | .unwrap(); 105 | } 106 | } 107 | 108 | pub fn init_logger(app_name: String, log_level: Option) { 109 | let mut logger_initializer = LoggerInitializer::default(); 110 | if let Some(log_level) = log_level { 111 | logger_initializer.with_log_level(log_level); 112 | } 113 | logger_initializer.init(app_name); 114 | } 115 | 116 | #[derive(Clone, Debug)] 117 | struct TerminalLogFormatter { 118 | app_name: String, 119 | } 120 | 121 | impl TerminalLogFormatter { 122 | fn new(app_name: String) -> TerminalLogFormatter { 123 | #[cfg(target_os = "windows")] 124 | { 125 | if let Err(e) = ansi_term::enable_ansi_support() { 126 | eprintln!("Warn: ansi_term::enable_ansi_support failed. {:?}", e); 127 | } 128 | } 129 | TerminalLogFormatter { app_name } 130 | } 131 | } 132 | 133 | impl FormatEvent for TerminalLogFormatter 134 | where 135 | S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, 136 | N: for<'a> tracing_subscriber::fmt::FormatFields<'a> + 'static, 137 | { 138 | fn format_event( 139 | &self, 140 | ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, 141 | mut writer: tracing_subscriber::fmt::format::Writer<'_>, 142 | event: &tracing::Event<'_>, 143 | ) -> std::fmt::Result { 144 | let level = *event.metadata().level(); 145 | write!( 146 | writer, 147 | "{}{} ", 148 | format!("[{}]", &self.app_name).bright_green(), 149 | match level { 150 | tracing::Level::INFO => "".to_string(), 151 | tracing::Level::ERROR | tracing::Level::WARN => 152 | format!("[{}]", level).red().to_string(), 153 | _ => format!("[{}]", level).bright_green().to_string(), 154 | } 155 | )?; 156 | ctx.field_format().format_fields(writer.by_ref(), event)?; 157 | writeln!(writer)?; 158 | Ok(()) 159 | } 160 | } 161 | 162 | #[derive(Debug)] 163 | struct KmsgLogFormatter { 164 | app_name: String, 165 | } 166 | 167 | impl KmsgLogFormatter { 168 | pub fn new(app_name: String) -> KmsgLogFormatter { 169 | KmsgLogFormatter { app_name } 170 | } 171 | 172 | #[cfg(target_os = "linux")] 173 | fn get_writer() -> Result> { 174 | if nix::unistd::getegid().as_raw() == 0 { 175 | // Rust APIs set CLOEXEC by default 176 | Ok(Box::new( 177 | std::fs::OpenOptions::new() 178 | .append(true) 179 | .create(true) 180 | .open("/dev/kmsg") 181 | .with_context(|| "Failed to open /dev/kmsg")?, 182 | )) 183 | } else { 184 | Ok(Box::new(std::io::sink())) 185 | } 186 | } 187 | 188 | #[cfg(target_os = "windows")] 189 | fn get_writer() -> Result> { 190 | Ok(Box::new(std::io::sink())) 191 | } 192 | } 193 | 194 | impl FormatEvent for KmsgLogFormatter 195 | where 196 | S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, 197 | N: for<'a> tracing_subscriber::fmt::FormatFields<'a> + 'static, 198 | { 199 | fn format_event( 200 | &self, 201 | ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, 202 | mut writer: tracing_subscriber::fmt::format::Writer<'_>, 203 | event: &tracing::Event<'_>, 204 | ) -> std::fmt::Result { 205 | let level = *event.metadata().level(); 206 | write!(writer.by_ref(), "{}: [{}] ", self.app_name, &level)?; 207 | ctx.field_format().format_fields(writer.by_ref(), event)?; 208 | writeln!(writer.by_ref())?; 209 | Ok(()) 210 | } 211 | } 212 | 213 | pub fn choose_from_list(list: DistroImageList) -> Result> { 214 | match list { 215 | DistroImageList::Fetcher(list_item_kind, fetchers, default) => { 216 | if fetchers.is_empty() { 217 | bail!("Empty list of {}.", &list_item_kind); 218 | } 219 | let default = match default { 220 | DefaultImageFetcher::Index(index) => fetchers[index].get_name().to_owned(), 221 | DefaultImageFetcher::Name(name) => name, 222 | }; 223 | for (i, fetcher) in fetchers.iter().enumerate() { 224 | println!("{} {}", format!("[{}]", i + 1).cyan(), fetcher.get_name()); 225 | } 226 | log::info!("Choose {} from the list above.", &list_item_kind); 227 | loop { 228 | log::info!("Type the name or the index of your choice."); 229 | print!("[Default: {}]: ", &default); 230 | let _ = std::io::stdout().flush(); 231 | let mut choice = String::new(); 232 | std::io::stdin() 233 | .read_line(&mut choice) 234 | .with_context(|| "failed to read from the stdin.")?; 235 | choice = choice.trim_end().to_owned(); 236 | if choice.is_empty() { 237 | choice = default.to_owned(); 238 | } 239 | let index = fetchers 240 | .iter() 241 | .position(|fetcher| fetcher.get_name() == choice.as_str()); 242 | if let Some(index) = index { 243 | return Ok(fetchers.into_iter().nth(index).unwrap()); 244 | } 245 | if let Ok(index) = choice.parse::() { 246 | if index <= fetchers.len() && index >= 1 { 247 | return Ok(fetchers.into_iter().nth(index - 1).unwrap()); 248 | } 249 | } 250 | log::info!("{} is off the list.", choice); 251 | } 252 | } 253 | DistroImageList::Image(_) => bail!("Image should not be passed to choose_from_list."), 254 | } 255 | } 256 | 257 | pub fn prompt_path(message: &str, default: Option<&str>) -> Result { 258 | log::info!("{}", message); 259 | print!( 260 | "[Input the path{}]: ", 261 | default 262 | .map(|s| format!(" (Default: '{}')", s)) 263 | .unwrap_or_else(|| "".to_owned()) 264 | ); 265 | let _ = std::io::stdout().flush(); 266 | let mut choice = String::new(); 267 | std::io::stdin() 268 | .read_line(&mut choice) 269 | .with_context(|| "failed to read from the stdin.")?; 270 | choice = choice.trim_end().to_owned(); 271 | Ok(OsString::from(choice)) 272 | } 273 | 274 | pub fn prompt_string(message: &str, target_name: &str, default: Option<&str>) -> Result { 275 | log::info!("{}", message); 276 | print!( 277 | "[Input {}{}]: ", 278 | target_name, 279 | default 280 | .map(|s| format!(" (Default: '{}')", s)) 281 | .unwrap_or_else(|| "".to_owned()) 282 | ); 283 | let _ = std::io::stdout().flush(); 284 | let mut choice = String::new(); 285 | std::io::stdin() 286 | .read_line(&mut choice) 287 | .with_context(|| "failed to read from the stdin.")?; 288 | choice = choice.trim_end().to_owned(); 289 | Ok(choice) 290 | } 291 | 292 | pub fn build_progress_bar(total_size: u64) -> indicatif::ProgressBar { 293 | let bar = indicatif::ProgressBar::new(total_size); 294 | bar.set_style(indicatif::ProgressStyle::default_bar() 295 | .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") 296 | .progress_chars("#>-")); 297 | bar 298 | } 299 | -------------------------------------------------------------------------------- /distrod/libs/src/command_alias.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::{anyhow, Context, Result}; 4 | 5 | use crate::distrod_config; 6 | 7 | pub struct CommandAlias { 8 | source_path: PathBuf, 9 | link_path: PathBuf, 10 | } 11 | 12 | impl CommandAlias { 13 | pub fn is_alias>(path: P) -> bool { 14 | path.as_ref().starts_with(distrod_config::get_alias_dir()) 15 | } 16 | 17 | pub fn open_from_source>( 18 | source: P, 19 | creates: bool, 20 | ) -> Result> { 21 | let link_path = Path::new(distrod_config::get_alias_dir()).join( 22 | source.as_ref().strip_prefix("/").with_context(|| { 23 | format!( 24 | "The given path is not an absolute path: {:?}", 25 | source.as_ref() 26 | ) 27 | })?, 28 | ); 29 | if !link_path.exists() { 30 | if creates { 31 | let link_path_dir = link_path 32 | .parent() 33 | .ok_or_else(|| anyhow!("Failed to get the parent of '{:?}'", &link_path))?; 34 | if !link_path_dir.exists() { 35 | std::fs::create_dir_all(link_path_dir)?; 36 | } 37 | let distrod_path = std::env::current_exe() 38 | .with_context(|| anyhow!("Failed to get the current_exe."))?; 39 | std::fs::hard_link(&distrod_path, &link_path).with_context(|| { 40 | format!("Failed to create a new hard link at {:?}", &link_path) 41 | })?; 42 | } else { 43 | return Ok(None); 44 | } 45 | } 46 | Ok(Some(CommandAlias { 47 | source_path: source.as_ref().to_owned(), 48 | link_path, 49 | })) 50 | } 51 | 52 | pub fn open_from_link>(link_path: P) -> Result { 53 | let source_path = link_path 54 | .as_ref() 55 | .strip_prefix(distrod_config::get_alias_dir()) 56 | .with_context(|| { 57 | format!( 58 | "The given link does not exist in the alias directory.: '{:?}'", 59 | link_path.as_ref() 60 | ) 61 | })? 62 | .to_owned(); 63 | let source_path = Path::new("/").join(source_path); 64 | // Please note that we do not check if the source path exists, because 65 | // user may have deleted it. 66 | Ok(CommandAlias { 67 | source_path, 68 | link_path: link_path.as_ref().to_owned(), 69 | }) 70 | } 71 | 72 | pub fn get_source_path(&self) -> &Path { 73 | &self.source_path 74 | } 75 | 76 | pub fn get_link_path(&self) -> &Path { 77 | &self.link_path 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /distrod/libs/src/container_org_image.rs: -------------------------------------------------------------------------------- 1 | use crate::distro_image::{ 2 | DefaultImageFetcher, DistroImage, DistroImageFetcher, DistroImageFile, DistroImageList, 3 | ListChooseFn, 4 | }; 5 | use anyhow::{anyhow, bail, Context, Result}; 6 | use async_trait::async_trait; 7 | use chrono::NaiveDateTime; 8 | 9 | static LINUX_CONTAINERS_ORG_BASE: &str = "https://images.linuxcontainers.org/"; 10 | 11 | pub async fn fetch_container_org_image(choose_from_list: ListChooseFn<'_>) -> Result { 12 | let mut distro_image_list = Box::new(ContainerOrgImageList {}) as Box; 13 | loop { 14 | let fetched_image_list = distro_image_list.fetch().await?; 15 | match fetched_image_list { 16 | DistroImageList::Fetcher(_, _, _) => { 17 | distro_image_list = choose_from_list(fetched_image_list)?; 18 | } 19 | DistroImageList::Image(image) => { 20 | return Ok(image); 21 | } 22 | } 23 | } 24 | } 25 | 26 | #[derive(Default)] 27 | pub struct ContainerOrgImageList; 28 | 29 | #[async_trait] 30 | impl DistroImageFetcher for ContainerOrgImageList { 31 | fn get_name(&self) -> &str { 32 | "Download an image from linuxcontainers.org" 33 | } 34 | 35 | async fn fetch(&self) -> Result { 36 | let distros: Vec<_> = fetch_apache_file_list("images/") 37 | .await 38 | .map(|links| { 39 | links 40 | .into_iter() 41 | .map(|link| { 42 | Box::new(ContainerOrgDistroVersionList { 43 | name: link.name, 44 | version_list_url: format!("images/{}", link.url), 45 | }) as Box 46 | }) 47 | .collect() 48 | }) 49 | .with_context(|| { 50 | "Failed to parse the distro image list of the linuxcontainer.org image server." 51 | })?; 52 | 53 | Ok(DistroImageList::Fetcher( 54 | "a linuxcontainers.org image".to_owned(), 55 | distros, 56 | DefaultImageFetcher::Name("ubuntu".to_owned()), 57 | )) 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct ContainerOrgDistroVersionList { 63 | name: String, 64 | version_list_url: String, 65 | } 66 | 67 | #[async_trait] 68 | impl DistroImageFetcher for ContainerOrgDistroVersionList { 69 | fn get_name(&self) -> &str { 70 | self.name.as_str() 71 | } 72 | 73 | async fn fetch(&self) -> Result { 74 | let mut links = fetch_apache_file_list(&self.version_list_url) 75 | .await 76 | .with_context(|| "Failed to parse the version list.")?; 77 | links.sort_by(|a, b| a.last_modified.cmp(&b.last_modified)); 78 | let versions: Vec<_> = links 79 | .into_iter() 80 | .map(|link| { 81 | Box::new(ContainerOrgDistroVersion { 82 | distro_name: self.name.clone(), 83 | version_name: link.name, 84 | platform_list_url: format!("{}{}", self.version_list_url, link.url), 85 | }) as Box 86 | }) 87 | .collect(); 88 | let default = match self.get_name() { 89 | "ubuntu" => DefaultImageFetcher::Name("focal".to_owned()), 90 | "debian" => DefaultImageFetcher::Name("bullseye".to_owned()), 91 | _ => DefaultImageFetcher::Index(versions.len() - 1), 92 | }; 93 | Ok(DistroImageList::Fetcher( 94 | "a version".to_owned(), 95 | versions, 96 | default, 97 | )) 98 | } 99 | } 100 | 101 | #[derive(Debug)] 102 | pub struct ContainerOrgDistroVersion { 103 | distro_name: String, 104 | version_name: String, 105 | platform_list_url: String, 106 | } 107 | 108 | #[async_trait] 109 | impl DistroImageFetcher for ContainerOrgDistroVersion { 110 | fn get_name(&self) -> &str { 111 | self.version_name.as_str() 112 | } 113 | 114 | async fn fetch(&self) -> Result { 115 | let variant = match self.distro_name.as_str() { 116 | "gentoo" => "amd64/systemd", 117 | _ => "amd64/default", 118 | }; 119 | let mut dates = fetch_apache_file_list(&format!("{}{}", &self.platform_list_url, variant)) 120 | .await 121 | .with_context(|| { 122 | format!( 123 | "Failed to get the image for {}. Perhaps '{}{}' is not found?", 124 | variant, &self.platform_list_url, variant 125 | ) 126 | })?; 127 | dates.sort_by(|a, b| b.last_modified.cmp(&a.last_modified)); 128 | let latest = &dates[0]; 129 | let rootfs_url = format!( 130 | "{}{}{}/{}rootfs.tar.xz", 131 | &LINUX_CONTAINERS_ORG_BASE, &self.platform_list_url, variant, latest.url 132 | ); 133 | Ok(DistroImageList::Image(DistroImage { 134 | name: format!("{}-{}", &self.distro_name, &self.version_name), 135 | image: DistroImageFile::Url(rootfs_url), 136 | })) 137 | } 138 | } 139 | 140 | async fn fetch_apache_file_list(relative_url: &str) -> Result> { 141 | let url = LINUX_CONTAINERS_ORG_BASE.to_owned() + relative_url; 142 | let date_selector = 143 | scraper::Selector::parse("body > table > tbody > tr > td:nth-child(3)").unwrap(); 144 | let a_link_selector = 145 | scraper::Selector::parse("body > table > tbody > tr > td:nth-child(2) > a").unwrap(); 146 | log::info!("Fetching from linuxcontainers.org..."); 147 | let apache_file_list_body = reqwest::get(&url) 148 | .await 149 | .with_context(|| format!("Failed to fetch {}", &url))? 150 | .text() 151 | .await 152 | .with_context(|| format!("Failed to get the text of {}", &url))?; 153 | let doc = scraper::Html::parse_document(&apache_file_list_body); 154 | let dates: Vec<_> = doc.select(&date_selector).collect(); 155 | let a_links: Vec<_> = doc.select(&a_link_selector).collect(); 156 | let links = a_links 157 | .iter() 158 | .skip(1) 159 | .enumerate() 160 | .map(|(i, a)| { 161 | let name = a 162 | .text() 163 | .next() 164 | .ok_or_else(|| anyhow!(format!("No text in a tag: {:#?}.", &a)))?; 165 | let name = name.trim_end_matches('/').to_owned(); 166 | let url = a 167 | .value() 168 | .attr("href") 169 | .ok_or_else(|| anyhow!(format!("a tag has No href. {:#?}", &a)))? 170 | .to_owned(); 171 | let url = url.trim_start_matches("./").to_owned(); 172 | Ok(FileOnApache { 173 | name, 174 | url, 175 | last_modified: NaiveDateTime::parse_from_str( 176 | dates[i + 1] 177 | .text() 178 | .next() 179 | .ok_or_else(|| { 180 | anyhow!(format!("Last modified time is not found. Tag: {:#?}.", &a)) 181 | })? 182 | .trim_end(), 183 | "%Y-%m-%d %H:%M", 184 | ) 185 | .with_context(|| { 186 | format!( 187 | "Failed to parse the date time.: {:#?}", 188 | dates[i + 1].text().next() 189 | ) 190 | })?, 191 | }) 192 | }) 193 | .collect::>>() 194 | .with_context(|| "Failed to parse the Apache file list page. Maybe the page is updated?")?; 195 | if links.is_empty() { 196 | bail!("{:?} is not available", &relative_url); 197 | } 198 | Ok(links) 199 | } 200 | 201 | #[derive(Debug)] 202 | struct FileOnApache { 203 | name: String, 204 | url: String, 205 | last_modified: NaiveDateTime, 206 | } 207 | -------------------------------------------------------------------------------- /distrod/libs/src/distro_image.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | 3 | use anyhow::{Context, Result}; 4 | use async_trait::async_trait; 5 | 6 | pub type ListChooseFn<'a> = 7 | &'a (dyn Fn(DistroImageList) -> Result> + Send + Sync); 8 | pub type PromptPath<'a> = &'a (dyn Fn(&str, Option<&str>) -> Result + Send + Sync); 9 | 10 | #[async_trait] 11 | pub trait DistroImageFetcher { 12 | fn get_name(&self) -> &str; 13 | async fn fetch(&self) -> Result; 14 | } 15 | 16 | pub enum DistroImageList { 17 | Fetcher( 18 | String, 19 | Vec>, 20 | DefaultImageFetcher, 21 | ), 22 | Image(DistroImage), 23 | } 24 | 25 | #[derive(Debug)] 26 | pub enum DefaultImageFetcher { 27 | Index(usize), 28 | Name(String), 29 | } 30 | 31 | #[derive(Debug)] 32 | pub struct DistroImage { 33 | pub name: String, 34 | pub image: DistroImageFile, 35 | } 36 | 37 | #[derive(Debug)] 38 | pub enum DistroImageFile { 39 | Local(OsString), 40 | Url(String), 41 | } 42 | 43 | pub type DistroImageFetcherGen = Box Result> + Sync>; 44 | 45 | pub async fn fetch_image( 46 | fetchers: Vec, 47 | choose_from_list: ListChooseFn<'_>, 48 | default_index: usize, 49 | ) -> Result { 50 | let mut distro_image_list = Box::new(DistroImageFetchersList { 51 | fetchers, 52 | default_index, 53 | }) as Box; 54 | loop { 55 | let fetched_image_list = distro_image_list.fetch().await?; 56 | match fetched_image_list { 57 | DistroImageList::Fetcher(_, _, _) => { 58 | distro_image_list = choose_from_list(fetched_image_list)?; 59 | } 60 | DistroImageList::Image(image) => { 61 | return Ok(image); 62 | } 63 | } 64 | } 65 | } 66 | 67 | struct DistroImageFetchersList { 68 | fetchers: Vec, 69 | default_index: usize, 70 | } 71 | 72 | #[async_trait] 73 | impl DistroImageFetcher for DistroImageFetchersList { 74 | fn get_name(&self) -> &str { 75 | "Image candidates" 76 | } 77 | 78 | async fn fetch(&self) -> Result { 79 | let fetchers: Result>> = self.fetchers.iter().map(|f| f()).collect(); 80 | Ok(DistroImageList::Fetcher( 81 | "the way to get a distro image".to_owned(), 82 | fetchers?, 83 | DefaultImageFetcher::Index(self.default_index), 84 | )) 85 | } 86 | } 87 | 88 | pub async fn download_file_with_progress( 89 | url: &str, 90 | progress_bar_builder: F, 91 | out: &mut W, 92 | ) -> Result<()> 93 | where 94 | F: FnOnce(u64) -> indicatif::ProgressBar, 95 | W: std::io::Write, 96 | { 97 | let client = reqwest::Client::builder().build()?; 98 | let mut response = client 99 | .get(url) 100 | .send() 101 | .await 102 | .with_context(|| format!("Failed to download {}.", &url))?; 103 | let total_size = response 104 | .content_length() 105 | .with_context(|| format!("Failed to get the content length of {}.", &url))?; 106 | 107 | let progress_bar = progress_bar_builder(total_size); 108 | let mut downloaded_size = 0; 109 | 110 | while let Some(bytes) = response.chunk().await? { 111 | out.write_all(&bytes)?; 112 | downloaded_size = std::cmp::min(downloaded_size + bytes.len(), total_size as usize); 113 | progress_bar.set_position(downloaded_size as u64); 114 | } 115 | 116 | progress_bar.finish(); 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /distrod/libs/src/distrod_config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use once_cell::sync::Lazy; 3 | #[cfg(target_os = "linux")] 4 | use std::io::Read; 5 | use std::io::{BufWriter, Write}; 6 | #[cfg(target_os = "linux")] 7 | use std::os::linux::fs::MetadataExt; 8 | use std::sync::{Arc, RwLock}; 9 | use std::{ 10 | fs::File, 11 | path::{Path, PathBuf}, 12 | }; 13 | 14 | use serde::{Deserialize, Serialize}; 15 | 16 | #[derive(Serialize, Deserialize, Clone, Debug)] 17 | pub struct DistrodConfig { 18 | pub distrod: DistrodGlobalConfig, 19 | } 20 | 21 | #[derive(Serialize, Deserialize, Clone, Debug)] 22 | pub struct DistrodGlobalConfig { 23 | pub default_distro_image: PathBuf, 24 | pub distro_images_dir: PathBuf, 25 | pub log_level: Option, 26 | pub kmsg_log_level: Option, 27 | } 28 | 29 | static DISTROD_ROOT_DIR: &str = "/opt/distrod"; 30 | 31 | static DISTROD_CONFIG: Lazy>>> = Lazy::new(|| { 32 | Ok(RwLock::new(Arc::new(read_distrod_config().with_context( 33 | || "Failed to read the Distrod config file.", 34 | )?))) 35 | }); 36 | 37 | impl DistrodConfig { 38 | pub fn get() -> Result> { 39 | match DISTROD_CONFIG.as_ref() { 40 | Ok(cfg) => { 41 | let r = cfg.read(); 42 | if let Err(e) = r { 43 | bail!("Failed to acquire the read lock of the config. {:?}", e); 44 | } 45 | Ok(r.unwrap().clone()) 46 | } 47 | Err(e) => bail!("Failed to get the Distrod config. {:?}", e), 48 | } 49 | } 50 | 51 | pub fn update(self) -> Result<()> { 52 | write_distrod_config(&self).with_context(|| "Failed to save the new config.")?; 53 | match DISTROD_CONFIG.as_ref() { 54 | Ok(cfg) => { 55 | let w = cfg.write(); 56 | if let Err(e) = w { 57 | bail!("Failed to acquire the write lock of the config. {:?}", e); 58 | } 59 | *w.unwrap() = Arc::new(self); 60 | Ok(()) 61 | } 62 | Err(e) => bail!("Failed to get the Distrod config. {:?}", e), 63 | } 64 | } 65 | } 66 | 67 | static DISTROD_ALIAS_DIR: Lazy = Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "alias")); 68 | 69 | /// The directory where the alias commands are stored. 70 | pub fn get_alias_dir() -> &'static str { 71 | DISTROD_ALIAS_DIR.as_str() 72 | } 73 | 74 | static DISTROD_BIN_DIR: Lazy = Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "bin")); 75 | 76 | /// The path to the distrod binary. 77 | pub fn get_distrod_bin_dir_path() -> &'static str { 78 | DISTROD_BIN_DIR.as_str() 79 | } 80 | 81 | static DISTROD_BIN_PATH: Lazy = 82 | Lazy::new(|| format!("{}/{}", DISTROD_BIN_DIR.as_str(), "distrod")); 83 | 84 | /// The path to the distrod binary. 85 | pub fn get_distrod_bin_path() -> &'static str { 86 | DISTROD_BIN_PATH.as_str() 87 | } 88 | 89 | static DISTROD_EXEC_BIN_PATH: Lazy = 90 | Lazy::new(|| format!("{}/{}", DISTROD_BIN_DIR.as_str(), "distrod-exec")); 91 | 92 | /// The path to the distrod-exec binary. 93 | pub fn get_distrod_exec_bin_path() -> &'static str { 94 | DISTROD_EXEC_BIN_PATH.as_str() 95 | } 96 | 97 | static DISTROD_RUN_OVERLAY_DIR_PAH: Lazy = 98 | Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "run")); 99 | 100 | /// The path to the directory where the static files to be located on /run directory 101 | pub fn get_distrod_run_overlay_dir() -> &'static str { 102 | DISTROD_RUN_OVERLAY_DIR_PAH.as_str() 103 | } 104 | 105 | static DISTROD_CONF_DIR_PAH: Lazy = 106 | Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "conf")); 107 | 108 | /// The path to the directory where the configuration files are stored. 109 | /// Configurations files are modified by users. Thus, the files stroed 110 | /// in this directory should not be overwritten by the update and should be 111 | /// backwork-compatible. 112 | pub fn get_distrod_conf_dir() -> &'static str { 113 | DISTROD_CONF_DIR_PAH.as_str() 114 | } 115 | 116 | #[cfg(target_os = "linux")] 117 | fn read_distrod_config() -> Result { 118 | let config_path = Path::new(&*DISTROD_CONF_DIR_PAH).join("distrod.toml"); 119 | let mut config_file = File::open(&config_path).with_context(|| { 120 | format!( 121 | "Failed to open the distrod config file: '{:?}'.", 122 | &config_path 123 | ) 124 | })?; 125 | let metadata = config_file 126 | .metadata() 127 | .with_context(|| "Failed to get the permision of the metadata of the config.")?; 128 | if metadata.st_uid() != 0 || metadata.st_gid() != 0 { 129 | bail!("The distrod config file is not owned by root."); 130 | } 131 | 132 | let mut config_cont = String::new(); 133 | config_file.read_to_string(&mut config_cont)?; 134 | 135 | toml::from_str(&config_cont).with_context(|| { 136 | format!( 137 | "Failed to parse the config file. Invalid format? '{:?}'.", 138 | &config_path 139 | ) 140 | }) 141 | } 142 | 143 | // This should be defined in Windows as well to make it compilable. 144 | #[cfg(target_os = "windows")] 145 | fn read_distrod_config() -> Result { 146 | bail!("read_distrod_config function should not be called on Windows side."); 147 | } 148 | 149 | fn write_distrod_config(config: &DistrodConfig) -> Result<()> { 150 | let config_path = Path::new(&*DISTROD_CONF_DIR_PAH).join("distrod.toml"); 151 | let mut config_file = BufWriter::new(File::create(&config_path).with_context(|| { 152 | format!( 153 | "Failed to open the distrod config file: '{:?}'.", 154 | &config_path 155 | ) 156 | })?); 157 | 158 | config_file 159 | .write_all(&toml::to_vec(config).with_context(|| "Failed to serialize the new config.")?) 160 | .with_context(|| format!("Failed to write the config to '{:?}'.", config_path))?; 161 | Ok(()) 162 | } 163 | -------------------------------------------------------------------------------- /distrod/libs/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cli_ui; 2 | pub mod container_org_image; 3 | pub mod distro_image; 4 | pub mod distrod_config; 5 | pub mod local_image; 6 | 7 | #[cfg(target_os = "linux")] 8 | pub mod command_alias; 9 | #[cfg(target_os = "linux")] 10 | pub mod container; 11 | #[cfg(target_os = "linux")] 12 | pub mod distro; 13 | #[cfg(target_os = "linux")] 14 | pub mod envfile; 15 | #[cfg(target_os = "linux")] 16 | pub mod mount_info; 17 | #[cfg(target_os = "linux")] 18 | pub mod multifork; 19 | #[cfg(target_os = "linux")] 20 | pub mod passwd; 21 | #[cfg(target_os = "linux")] 22 | pub mod procfile; 23 | #[cfg(target_os = "linux")] 24 | pub mod systemdunit; 25 | #[cfg(target_os = "linux")] 26 | pub mod wsl_interop; 27 | 28 | #[cfg(target_os = "linux")] 29 | pub mod template; 30 | -------------------------------------------------------------------------------- /distrod/libs/src/local_image.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use crate::distro_image::{ 5 | DistroImage, DistroImageFetcher, DistroImageFile, DistroImageList, PromptPath, 6 | }; 7 | use anyhow::Result; 8 | 9 | pub struct LocalDistroImage { 10 | prompt_path: PromptPath<'static>, 11 | } 12 | 13 | impl LocalDistroImage { 14 | pub fn new(prompt_path: PromptPath<'static>) -> LocalDistroImage { 15 | LocalDistroImage { prompt_path } 16 | } 17 | } 18 | 19 | #[async_trait] 20 | impl DistroImageFetcher for LocalDistroImage { 21 | fn get_name(&self) -> &str { 22 | "Use a local tar.xz file" 23 | } 24 | 25 | async fn fetch(&self) -> Result { 26 | let mut path; 27 | loop { 28 | path = (self.prompt_path)("Please input the path to your .tar.xz image file.", None)?; 29 | if !path.to_string_lossy().ends_with(".tar.xz") { 30 | log::error!("The path must end with '.tar.xz'"); 31 | continue; 32 | } 33 | if !Path::new(&path).exists() { 34 | log::error!("The path does not exist."); 35 | continue; 36 | } 37 | break; 38 | } 39 | let path_buf = PathBuf::from(&path); 40 | Ok(DistroImageList::Image(DistroImage { 41 | name: path_buf 42 | .file_stem() 43 | .expect("File name exists") 44 | .to_string_lossy() 45 | .into_owned(), 46 | image: DistroImageFile::Local(path), 47 | })) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /distrod/libs/src/mount_info.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{BufRead, BufReader}, 4 | path::PathBuf, 5 | }; 6 | 7 | use anyhow::{Context, Result}; 8 | 9 | pub struct MountEntry { 10 | pub source: String, 11 | pub path: PathBuf, 12 | pub fstype: String, 13 | pub attributes: String, 14 | } 15 | 16 | pub fn get_mount_entries() -> Result> { 17 | let mounts = File::open("/proc/mounts").with_context(|| "Failed to open '/proc/mounts'")?; 18 | let reader = BufReader::new(mounts); 19 | 20 | let mut mount_entries = vec![]; 21 | for (_, line) in reader.lines().enumerate() { 22 | let line = line?; 23 | let row: Vec<&str> = line.split(' ').take(4).collect(); 24 | let (source, path, fstype, attributes) = ( 25 | row[0].to_owned(), 26 | row[1].to_owned(), 27 | row[2].to_owned(), 28 | row[3].to_owned(), 29 | ); 30 | mount_entries.push(MountEntry { 31 | source, 32 | path: PathBuf::from(path), 33 | fstype, 34 | attributes, 35 | }); 36 | } 37 | 38 | Ok(mount_entries) 39 | } 40 | -------------------------------------------------------------------------------- /distrod/libs/src/multifork.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use nix::fcntl::OFlag; 3 | use nix::libc::c_int; 4 | use nix::sys::signal; 5 | use std::convert::From; 6 | use std::fs::File; 7 | use std::io::{Read, Write}; 8 | use std::ops::Deref; 9 | use std::os::unix::io::FromRawFd; 10 | use std::os::unix::prelude::CommandExt; 11 | use std::process::Command; 12 | 13 | pub struct CommandByMultiFork<'a> { 14 | command: Command, 15 | pre_second_fork: Option Result<()> + 'a>>, 16 | proxy_process: Option, 17 | does_triple_fork: bool, 18 | } 19 | 20 | impl<'a> CommandByMultiFork<'a> { 21 | pub fn new(command: Command) -> CommandByMultiFork<'a> { 22 | CommandByMultiFork { 23 | command, 24 | pre_second_fork: None, 25 | proxy_process: None, 26 | does_triple_fork: false, 27 | } 28 | } 29 | 30 | pub fn do_triple_fork(&mut self, does_triple_fork: bool) -> &mut CommandByMultiFork<'a> { 31 | self.does_triple_fork = does_triple_fork; 32 | self 33 | } 34 | 35 | // Define proxy function to allow it to be called before pre_second_fork for readability. 36 | pub unsafe fn pre_exec(&mut self, f: F) -> &mut CommandByMultiFork<'a> 37 | where 38 | F: FnMut() -> std::io::Result<()> + Send + Sync + 'static, 39 | { 40 | self.command.pre_exec(f); 41 | self 42 | } 43 | 44 | pub fn pre_second_fork(&mut self, f: F) -> &mut CommandByMultiFork<'a> 45 | where 46 | F: FnMut() -> Result<()> + 'a, 47 | { 48 | self.pre_second_fork = Some(Box::new(f)); 49 | self 50 | } 51 | 52 | pub fn insert_waiter_proxy(&mut self) -> Result { 53 | let (proxy, waiter) = 54 | ProxyProcess::make_pair().with_context(|| "Failed to make a proxy process.")?; 55 | self.proxy_process = Some(proxy); 56 | Ok(waiter) 57 | } 58 | 59 | pub fn spawn(mut self) -> Result<()> { 60 | if unsafe { nix::unistd::fork().with_context(|| "The first fork failed")? }.is_child() { 61 | let inner = || -> Result<()> { 62 | if let Some(ref mut f) = self.pre_second_fork { 63 | f().with_context(|| "Pre_second_fork failed.")?; 64 | } 65 | if self.does_triple_fork 66 | && unsafe { 67 | nix::unistd::fork() 68 | .with_context(|| "The third fork failed.")? 69 | .is_parent() 70 | } 71 | { 72 | log::debug!("The parent of the second of three forks exits."); 73 | std::process::exit(0); 74 | } 75 | log::debug!("Spawning the command or the waiter."); 76 | match self.proxy_process { 77 | None => { 78 | self.command 79 | .spawn() 80 | .with_context(|| "Failed to spawn the command.")?; 81 | } 82 | Some(proxy_process) => { 83 | log::debug!("Spawning the waiter."); 84 | proxy_process 85 | .spawn(&mut self.command) 86 | .with_context(|| "Failed to spawn the command.")?; 87 | } 88 | }; 89 | Ok(()) 90 | }; 91 | if let Err(err) = inner() { 92 | log::error!("{:?}", err); 93 | } 94 | std::process::exit(0); 95 | } 96 | self.proxy_process = None; // Drop the proxy process in the parent process and drop the writer pipe. 97 | Ok(()) 98 | } 99 | } 100 | 101 | impl<'a> Deref for CommandByMultiFork<'a> { 102 | type Target = Command; 103 | 104 | fn deref(&self) -> &Self::Target { 105 | &self.command 106 | } 107 | } 108 | 109 | impl<'a> From for CommandByMultiFork<'a> { 110 | fn from(command: Command) -> Self { 111 | CommandByMultiFork { 112 | command, 113 | pre_second_fork: None, 114 | proxy_process: None, 115 | does_triple_fork: false, 116 | } 117 | } 118 | } 119 | 120 | pub struct Waiter { 121 | pipe_for_exitcode: File, 122 | } 123 | 124 | impl Waiter { 125 | pub fn wait(&mut self) -> u32 { 126 | let mut exit_code = vec![137]; // The exit code for SIGKILL 127 | let res = self 128 | .pipe_for_exitcode 129 | .read_exact(&mut exit_code) 130 | .with_context(|| "Failed to read the exit code from the pipe."); 131 | if res.is_err() { 132 | log::debug!( 133 | "The pipe for wait has been closed. Possibly the proxy process has been killed by SIGKILL." 134 | ); 135 | } 136 | exit_code[0] as u32 137 | } 138 | } 139 | 140 | pub struct ProxyProcess { 141 | pipe_for_exitcode: File, 142 | } 143 | 144 | impl ProxyProcess { 145 | pub fn make_pair() -> Result<(ProxyProcess, Waiter)> { 146 | let (waiter_pipe_host, waiter_pipe_child) = 147 | nix::unistd::pipe2(OFlag::O_CLOEXEC).with_context(|| "Failed to make a pipe.")?; 148 | unsafe { 149 | Ok(( 150 | ProxyProcess { 151 | pipe_for_exitcode: File::from_raw_fd(waiter_pipe_child), 152 | }, 153 | Waiter { 154 | pipe_for_exitcode: File::from_raw_fd(waiter_pipe_host), 155 | }, 156 | )) 157 | } 158 | } 159 | 160 | pub fn spawn(mut self, command: &mut Command) -> Result<()> { 161 | if unsafe { nix::unistd::fork().with_context(|| "The proxy_process's fork failed")? } 162 | .is_child() 163 | { 164 | set_noninheritable_sig_ign(); 165 | let mut child = command 166 | .spawn() 167 | .with_context(|| "Failed to run a command.")?; 168 | let status = child 169 | .wait() 170 | .with_context(|| "Failed to wait wthe command.")?; 171 | let exit_code = status 172 | .code() 173 | .ok_or_else(|| anyhow!("status.code() is None unexpectedly."))? 174 | as u8; 175 | let exit_code = vec![exit_code]; 176 | if let Err(e) = self.pipe_for_exitcode.write_all(&exit_code) { 177 | log::debug!("Failed to write the exit code to the pipe. {}", e); 178 | } 179 | std::process::exit(0); 180 | } 181 | Ok(()) 182 | } 183 | } 184 | 185 | pub fn set_noninheritable_sig_ign() { 186 | for signal in signal::Signal::iterator() { 187 | // Ignore signals by a function instead of SIG_IGN so that the child doesn't inherit it. 188 | if let Err(e) = unsafe { signal::signal(signal, signal::SigHandler::Handler(do_nothing)) } { 189 | if signal != signal::Signal::SIGSYS { 190 | log::debug!("Failed to ignore signal {:?}", e); 191 | } 192 | } 193 | } 194 | } 195 | 196 | extern "C" fn do_nothing(_sig: c_int) {} 197 | 198 | #[cfg(test)] 199 | mod tests { 200 | use super::*; 201 | 202 | #[test] 203 | fn test_insert_proxy() { 204 | let mut command = Command::new("/bin/bash"); 205 | command.args(&["-c", "sleep 1; exit 42"]); 206 | let mut doublefork = CommandByMultiFork::new(command); 207 | let mut waiter = doublefork.insert_waiter_proxy().unwrap(); 208 | let _ = doublefork.spawn().unwrap(); 209 | let exit_code = waiter.wait(); 210 | assert_eq!(42, exit_code); 211 | } 212 | 213 | #[test] 214 | fn test_inserted_proxy_ignore_signal() { 215 | let mut command = Command::new("/bin/bash"); 216 | command.args(&[ 217 | "-c", 218 | "trap '' SIGINT; kill -SIGINT $PPID; sleep 1; exit 42;", 219 | ]); 220 | let mut doublefork = CommandByMultiFork::new(command); 221 | let mut waiter = doublefork.insert_waiter_proxy().unwrap(); 222 | let _ = doublefork.spawn().unwrap(); 223 | let exit_code = waiter.wait(); 224 | assert_eq!(42, exit_code); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /distrod/libs/src/procfile.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Context, Result}; 2 | use nix::fcntl::OFlag; 3 | use std::fs::File; 4 | use std::io::{Read, Seek, SeekFrom}; 5 | use std::os::unix::io::{AsRawFd, FromRawFd}; 6 | 7 | #[derive(Debug)] 8 | pub struct ProcFile { 9 | dir: File, 10 | } 11 | 12 | impl ProcFile { 13 | pub fn current_proc() -> Result { 14 | let procfile = 15 | ProcFile::from_proc_dir("self").with_context(|| "Failed to open /proc/self.")?; 16 | procfile.ok_or_else(|| anyhow!("/proc/self doesn't exist.")) 17 | } 18 | 19 | #[allow(dead_code)] 20 | pub fn from_pid(pid: u32) -> Result> { 21 | ProcFile::from_proc_dir(pid.to_string().as_str()) 22 | } 23 | 24 | pub fn is_live(&mut self) -> bool { 25 | self.pid().is_ok() 26 | } 27 | 28 | pub unsafe fn from_raw_fd(pidfd: i32) -> ProcFile { 29 | ProcFile { 30 | dir: File::from_raw_fd(pidfd), 31 | } 32 | } 33 | 34 | pub fn as_raw_fd(&self) -> i32 { 35 | self.dir.as_raw_fd() 36 | } 37 | 38 | pub fn pid(&mut self) -> Result { 39 | let statfd = nix::fcntl::openat( 40 | self.dir.as_raw_fd(), 41 | "stat", 42 | OFlag::O_RDONLY | OFlag::O_CLOEXEC, 43 | nix::sys::stat::Mode::empty(), 44 | ) 45 | .with_context(|| "Failed to open /proc/self/stat")?; 46 | if statfd < 0 { 47 | bail!("The process doesn't exist"); 48 | } 49 | let mut stat = unsafe { File::from_raw_fd(statfd) }; 50 | let mut stat_cont = String::new(); 51 | stat.read_to_string(&mut stat_cont)?; 52 | stat.seek(SeekFrom::Start(0))?; 53 | let pid = stat_cont 54 | .split(' ') 55 | .next() // 0: PID 56 | .ok_or_else(|| anyhow!("Failed to read pid from the stat file."))? 57 | .parse() 58 | .with_context(|| "Failed to parse the pid.")?; 59 | Ok(pid) 60 | } 61 | 62 | pub fn open_file_at(&self, name: &str) -> Result { 63 | let nsdir_fd = nix::fcntl::openat( 64 | self.dir.as_raw_fd(), 65 | name, 66 | OFlag::O_RDONLY, 67 | nix::sys::stat::Mode::empty(), 68 | ) 69 | .with_context(|| format!("Failed to open {}", name))?; 70 | Ok(unsafe { File::from_raw_fd(nsdir_fd) }) 71 | } 72 | 73 | fn from_proc_dir(proc_dir: &str) -> Result> { 74 | let piddirfd = nix::fcntl::open( 75 | format!("/proc/{}", proc_dir).as_str(), 76 | OFlag::O_RDONLY | OFlag::O_CLOEXEC, 77 | nix::sys::stat::Mode::empty(), 78 | ); 79 | if let Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) = piddirfd { 80 | return Ok(None); 81 | } 82 | Ok(Some(ProcFile { 83 | dir: unsafe { 84 | File::from_raw_fd( 85 | piddirfd.with_context(|| format!("Failed to open /proc/{}", proc_dir))?, 86 | ) 87 | }, 88 | })) 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | use std::process::Command; 96 | 97 | #[test] 98 | fn test_current_proc() { 99 | assert!(ProcFile::current_proc().is_ok()); 100 | } 101 | 102 | #[test] 103 | fn test_current_pid() { 104 | let mut current = ProcFile::current_proc().unwrap(); 105 | assert_eq!(std::process::id(), current.pid().unwrap()); 106 | } 107 | 108 | #[test] 109 | fn test_child_pid() { 110 | let mut child = Command::new("/bin/sleep"); 111 | child.args(&["2"]); 112 | let child = child.spawn().unwrap(); 113 | let mut child_procfile = ProcFile::from_pid(child.id()).unwrap().unwrap(); 114 | assert_eq!(child.id(), child_procfile.pid().unwrap()); 115 | } 116 | 117 | #[test] 118 | fn test_proc_liveness() { 119 | let mut child = Command::new("/bin/sleep"); 120 | child.args(&["2"]); 121 | let mut child = child.spawn().unwrap(); 122 | let mut child_procfile = ProcFile::from_pid(child.id()).unwrap().unwrap(); 123 | assert!(child_procfile.is_live()); 124 | let _ = child.wait(); 125 | assert!(!child_procfile.is_live()); 126 | assert!(ProcFile::from_pid(child.id()).unwrap().is_none()); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /distrod/libs/src/template.rs: -------------------------------------------------------------------------------- 1 | pub struct Template { 2 | cont: String, 3 | } 4 | 5 | impl Template { 6 | pub fn new(cont: String) -> Self { 7 | Template { cont } 8 | } 9 | 10 | pub fn assign(&mut self, name: &str, val: &str) -> &mut Self { 11 | self.cont = self.cont.replace(&format!("{{{{{}}}}}", name), val); 12 | self 13 | } 14 | 15 | pub fn render(&self) -> String { 16 | self.cont.clone() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /distrod/libs/src/wsl_interop.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | ffi::OsString, 4 | iter::FromIterator, 5 | path::PathBuf, 6 | }; 7 | 8 | use anyhow::{anyhow, bail, Context, Result}; 9 | use procfs::process; 10 | 11 | use crate::{envfile::PathVariable, mount_info::get_mount_entries}; 12 | 13 | pub fn get_wsl_drive_path(drive_letter: &str) -> Result> { 14 | let entries = get_mount_entries().with_context(|| "Failed to get the mount entries.")?; 15 | Ok(entries.into_iter().find_map(|e| { 16 | if e.fstype != "9p" { 17 | return None; 18 | } 19 | // Windows 10 20 | if e.source 21 | .starts_with(&format!("{}:\\", drive_letter.to_uppercase())) 22 | { 23 | return Some(e.path); 24 | } 25 | // Windows 11 26 | if e.source == "drvfs" 27 | && e.attributes 28 | .contains(&format!("path={}:\\", drive_letter.to_uppercase())) 29 | { 30 | return Some(e.path); 31 | } 32 | None 33 | })) 34 | } 35 | 36 | fn get_wsl_drive_mount_point() -> Result> { 37 | let c_drive = get_wsl_drive_path("c") 38 | .with_context(|| "Failed to get the path where C drive is mounted.")?; 39 | if c_drive.is_none() { 40 | return Ok(None); 41 | } 42 | let c_drive = c_drive.unwrap(); 43 | Ok(Some( 44 | c_drive 45 | .parent() 46 | .map_or_else(|| PathBuf::from("/"), |p| p.to_owned()), 47 | )) 48 | } 49 | 50 | pub fn get_distro_name() -> Result { 51 | let envs = collect_wsl_env_vars().with_context(|| "Failed to collect wsl envs.")?; 52 | Ok(envs 53 | .get(&OsString::from("WSL_DISTRO_NAME")) 54 | .ok_or_else(|| anyhow!("Failed to get distro name."))? 55 | .to_string_lossy() 56 | .to_string()) 57 | } 58 | 59 | pub fn collect_wsl_env_vars() -> Result> { 60 | let wsl_env_names = HashSet::::from_iter(get_wsl_interop_env_names().into_iter()); 61 | 62 | // Try to get them from the current process first. 63 | // Note that the environment variables may be modified internally, which we should collect. 64 | let wsl_envs: HashMap<_, _> = wsl_env_names 65 | .iter() 66 | .flat_map(|var| std::env::var_os(var).map(|val| (var.clone(), val))) 67 | .collect(); 68 | if !wsl_envs.is_empty() { 69 | return Ok(wsl_envs); 70 | } 71 | 72 | // The WSL env vars may not be set if the process is launched by sudo. 73 | // Traverse the parent process until we find the WSL env vars in that case. 74 | let mut proc = process::Process::myself() 75 | .with_context(|| "Failed to get Process struct for the current process")?; 76 | loop { 77 | let env = proc.environ()?; 78 | let wsl_envs: HashMap<_, _> = env 79 | .into_iter() 80 | .filter(|(name, _)| wsl_env_names.contains(name)) 81 | .collect(); 82 | if !wsl_envs.is_empty() { 83 | return Ok(wsl_envs); 84 | } 85 | 86 | if proc.pid == 1 { 87 | break; 88 | } 89 | let stat = proc 90 | .stat() 91 | .with_context(|| format!("Failed to get Process::Stat struct of {}", proc.pid))?; 92 | proc = process::Process::new(stat.ppid).with_context(|| { 93 | format!( 94 | "Failed to get Process struct for the parent process {}", 95 | stat.ppid 96 | ) 97 | })?; 98 | } 99 | 100 | bail!("Couldn't find WSL envs"); 101 | } 102 | 103 | fn get_wsl_interop_env_names() -> Vec { 104 | ["WSL_INTEROP", "WSLENV", "WSL_DISTRO_NAME"] 105 | .iter() 106 | .map(OsString::from) 107 | .collect() 108 | } 109 | 110 | pub fn collect_wsl_paths() -> Result> { 111 | let wsl_mount_point = 112 | get_wsl_drive_mount_point().with_context(|| "Failed to get the WSL drive mount point.")?; 113 | if wsl_mount_point.is_none() { 114 | return Ok(vec![]); 115 | } 116 | let wsl_mount_point = wsl_mount_point.unwrap(); 117 | let wsl_mount_point = wsl_mount_point.to_string_lossy(); 118 | 119 | let path = std::env::var("PATH")?; 120 | let path = PathVariable::parse(&path); 121 | let wsl_paths = path 122 | .iter() 123 | .filter(|path| path.starts_with(wsl_mount_point.as_ref())) 124 | .map(|p| p.to_owned()) 125 | .collect(); 126 | Ok(wsl_paths) 127 | } 128 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/Makefile: -------------------------------------------------------------------------------- 1 | tar: 2 | tar czf unit_dir.tar.gz system units 3 | 4 | .PHONY: tar 5 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/aliased.service: -------------------------------------------------------------------------------- 1 | ../../units/simple_alias.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/multiple_alias1.service: -------------------------------------------------------------------------------- 1 | ../../units/multiple_alias.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/multiple_alias2.service: -------------------------------------------------------------------------------- 1 | ../../units/multiple_alias.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/multiple_alias3.service: -------------------------------------------------------------------------------- 1 | ../../units/multiple_alias.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/multiple_also_unit.service: -------------------------------------------------------------------------------- 1 | ../../units/multiple_also_unit.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/referenced_by_also1.service: -------------------------------------------------------------------------------- 1 | ../../units/referenced_by_also1.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/referenced_by_also2.service: -------------------------------------------------------------------------------- 1 | ../../units/referenced_by_also2.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/referenced_by_also3.service: -------------------------------------------------------------------------------- 1 | ../../units/referenced_by_also3.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/referenced_by_also4.service: -------------------------------------------------------------------------------- 1 | ../../units/referenced_by_also4.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/simple_also_unit.service: -------------------------------------------------------------------------------- 1 | ../../units/simple_also_unit.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multi-user.target.wants/simple_unit.service: -------------------------------------------------------------------------------- 1 | ../../units/simple_unit.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multiple_alias.service: -------------------------------------------------------------------------------- 1 | ../units/multiple_alias.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/multiple_also_unit.service: -------------------------------------------------------------------------------- 1 | ../units/multiple_also_unit.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/referenced_by_also1.service: -------------------------------------------------------------------------------- 1 | ../units/referenced_by_also1.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/referenced_by_also2.service: -------------------------------------------------------------------------------- 1 | ../units/referenced_by_also2.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/referenced_by_also3.service: -------------------------------------------------------------------------------- 1 | ../units/referenced_by_also3.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/referenced_by_also4.service: -------------------------------------------------------------------------------- 1 | ../units/referenced_by_also4.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/simple_alias.service: -------------------------------------------------------------------------------- 1 | ../units/simple_alias.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/simple_also_unit.service: -------------------------------------------------------------------------------- 1 | ../units/simple_also_unit.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/simple_unit.service: -------------------------------------------------------------------------------- 1 | ../units/simple_unit.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/systemd-system1.service: -------------------------------------------------------------------------------- 1 | ../units/systemd-system1.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/system/unrelated.service: -------------------------------------------------------------------------------- 1 | ../units/unrelated.service -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/unit_dir.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/distrod/libs/tests/resources/systemdunit/unit_dir.tar.gz -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/multiple_alias.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | Alias=multiple_alias1.service 12 | Alias=multiple_alias2.service multiple_alias3.service 13 | Other=unrelated.service 14 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/multiple_also_unit.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | Also=referenced_by_also2.service referenced_by_also3.service 12 | Also=referenced_by_also4.service 13 | Other=unrelated.service 14 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/referenced_by_also1.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/referenced_by_also2.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/referenced_by_also3.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/referenced_by_also4.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/simple_alias.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | Alias=aliased.service 12 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/simple_also_unit.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | Also=referenced_by_also1.service 12 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/simple_unit.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/systemd-system1.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/libs/tests/resources/systemdunit/units/unrelated.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=unit 3 | Wants=network.target 4 | 5 | [Service] 6 | ExecStart=/bin/exit 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /distrod/misc/about.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | 37 | 38 | 39 |
40 |
41 |

Third Party Licenses

42 |

This page lists the licenses of the projects used in Distrod.

43 |
44 | 45 |

Overview of licenses:

46 |
    47 | {{#each overview}} 48 |
  • {{name}} ({{count}})
  • 49 | {{/each}} 50 |
51 | 52 |

All license text:

53 |
    54 | {{#each licenses}} 55 |
  • 56 |

    {{name}}

    57 |

    Used by:

    58 | 63 |
    {{text}}
    64 |
  • 65 | {{/each}} 66 |
67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /distrod/misc/about.toml: -------------------------------------------------------------------------------- 1 | accepted = ['Apache-2.0', 'MIT', 'ISC', 'BSD-3-Clause', 'MPL-2.0'] -------------------------------------------------------------------------------- /distrod/portproxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "portproxy" 3 | version = "0.1.0" 4 | authors = ["Takaya Saeki "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | libs = { path = "../libs" } 9 | anyhow = "1" 10 | tokio = { version = "1", features = ["full"] } 11 | # Use my fork until https://github.com/TeXitoi/structopt/issues/490 is resolved 12 | structopt = { git = "https://github.com/nullpo-head/structopt.git" } 13 | log = "0.4" 14 | env_logger = "0.8" 15 | strum = { version = "0.20", features = ["derive"] } 16 | 17 | [target.'cfg(target_os = "linux")'.dependencies] 18 | nix = "0.20.0" 19 | -------------------------------------------------------------------------------- /distrod/portproxy/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use libs::cli_ui::init_logger; 3 | use structopt::StructOpt; 4 | use strum::{EnumString, EnumVariantNames}; 5 | use tokio::io::AsyncWriteExt; 6 | use tokio::io::{self, BufReader}; 7 | use tokio::net::{TcpListener, TcpStream}; 8 | 9 | #[derive(Debug, StructOpt)] 10 | #[structopt(name = "portproxy", rename_all = "kebab")] 11 | pub struct Opts { 12 | /// Log level in the env_logger format. Simple levels: trace, debug, info(default), warn, error. 13 | #[structopt(short, long)] 14 | pub log_level: Option, 15 | #[structopt(subcommand)] 16 | pub command: Subcommand, 17 | } 18 | 19 | #[derive(Debug, StructOpt)] 20 | pub enum Subcommand { 21 | Proxy(ProxyOpts), 22 | Show(ShowOpts), 23 | } 24 | 25 | #[derive(Debug, StructOpt)] 26 | #[structopt(rename_all = "kebab")] 27 | pub struct ProxyOpts { 28 | pub dest_addr: String, 29 | #[structopt(short, long)] 30 | pub tcp4: Vec, 31 | } 32 | 33 | #[derive(Debug, StructOpt)] 34 | #[structopt(rename_all = "kebab")] 35 | pub struct ShowOpts { 36 | pub show: ShowItem, 37 | } 38 | 39 | #[derive(Clone, Debug, EnumString, EnumVariantNames)] 40 | #[strum(serialize_all = "kebab-case")] 41 | pub enum ShowItem { 42 | Ipv4(String), 43 | } 44 | 45 | #[tokio::main] 46 | async fn main() -> Result<()> { 47 | let opts = Opts::from_args(); 48 | init_logger("PortProxy".to_owned(), opts.log_level.clone()); 49 | 50 | if let Err(e) = run(opts).await { 51 | log::error!("{:?}", e); 52 | std::process::exit(1); 53 | } 54 | 55 | Ok(()) 56 | } 57 | 58 | async fn run(opts: Opts) -> Result<()> { 59 | match opts.command { 60 | Subcommand::Proxy(proxy_opts) => run_proxy(proxy_opts).await, 61 | Subcommand::Show(show_opts) => run_show(show_opts)?, 62 | }; 63 | log::trace!("Exiting run."); 64 | Ok(()) 65 | } 66 | 67 | #[cfg(target_os = "linux")] 68 | fn run_show(_opts: ShowOpts) -> Result<()> { 69 | use anyhow::anyhow; 70 | use nix::sys::socket::{InetAddr, SockAddr}; 71 | 72 | let mut addrs = nix::ifaddrs::getifaddrs()?; 73 | let eth0_addr = addrs 74 | .find_map(|iaddr| { 75 | if iaddr.interface_name != "eth0" { 76 | return None; 77 | } 78 | match iaddr.address { 79 | Some(SockAddr::Inet(addr @ InetAddr::V4(_))) => Some(addr.ip()), 80 | _ => None, 81 | } 82 | }) 83 | .ok_or_else(|| anyhow!("'eth0' is not found."))?; 84 | log::trace!("eth0 addr is '{}'", eth0_addr); 85 | print!("{}", eth0_addr); 86 | Ok(()) 87 | } 88 | 89 | #[cfg(target_os = "windows")] 90 | fn run_show(_opts: ShowOpts) -> Result<()> { 91 | use anyhow::bail; 92 | 93 | bail!("Show command is not implemented on Windows."); 94 | } 95 | 96 | async fn run_proxy(opts: ProxyOpts) { 97 | let mut handles = vec![]; 98 | for tcp_port in opts.tcp4 { 99 | if tcp_port == 0 { 100 | log::info!("Skipping port 0"); 101 | continue; 102 | } 103 | let dest_addr = format!("{}:{}", &opts.dest_addr, tcp_port); 104 | handles.push(tokio::spawn(async move { 105 | if let Err(e) = proxy_tcp_port(tcp_port, dest_addr).await { 106 | log::error!("{:?}", e); 107 | } 108 | })); 109 | } 110 | for handle in handles { 111 | let _ = handle.await; 112 | } 113 | } 114 | 115 | async fn proxy_tcp_port(port: u16, dest_addr: String) -> Result<()> { 116 | let listen_addr = format!("0.0.0.0:{}", port); 117 | let listener = TcpListener::bind(&listen_addr) 118 | .await 119 | .with_context(|| format!("Failed to bind {}.", &listen_addr))?; 120 | println!("Forwarding {} to {}", &listen_addr, &dest_addr); 121 | loop { 122 | let (stream, _) = listener 123 | .accept() 124 | .await 125 | .with_context(|| format!("Failed to accept on the port {}.", port))?; 126 | let dest = dest_addr.clone(); 127 | tokio::spawn(async move { 128 | if let Err(e) = proxy_tcp_stream(stream, dest).await { 129 | log::error!("{:?}", e); 130 | } 131 | }); 132 | } 133 | } 134 | 135 | async fn proxy_tcp_stream(mut client: TcpStream, upstream_addr: String) -> Result<()> { 136 | let buf_size = 1 << 16; 137 | 138 | let mut upstream = TcpStream::connect(upstream_addr) 139 | .await 140 | .with_context(|| "Failed to connect to the upstream.")?; 141 | 142 | let (client_read, mut client_write) = client.split(); 143 | let (upstream_read, mut upstream_write) = upstream.split(); 144 | 145 | let client_to_upstream = async { 146 | let mut buf_read = BufReader::with_capacity(buf_size, client_read); 147 | io::copy_buf(&mut buf_read, &mut upstream_write) 148 | .await 149 | .with_context(|| "Copy to the upstream failed.")?; 150 | upstream_write 151 | .shutdown() 152 | .await 153 | .with_context(|| "Shutting down the client_to_upsteam failed.")?; 154 | Ok::<(), anyhow::Error>(()) 155 | }; 156 | 157 | let upstream_to_client = async { 158 | let mut buf_read = BufReader::with_capacity(buf_size, upstream_read); 159 | io::copy(&mut buf_read, &mut client_write) 160 | .await 161 | .with_context(|| "Copy to the client failed.")?; 162 | client_write 163 | .shutdown() 164 | .await 165 | .with_context(|| "Shutting down the upstream_to_client failed.")?; 166 | Ok(()) 167 | }; 168 | 169 | tokio::try_join!(client_to_upstream, upstream_to_client)?; 170 | 171 | Ok(()) 172 | } 173 | -------------------------------------------------------------------------------- /distrod_packer/resources/bin/adduser: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ORIG="$(PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin command -v adduser)" 4 | if [ -t 0 ]; then 5 | echo "[Distrod] Run 'sudo /opt/distrod/bin/distrod enable' after this command succeeds." >&2 6 | echo "[Distrod] It's necessary for Systemd to work as the pseudo init process." >&2 7 | fi 8 | "$ORIG" "$@" 9 | -------------------------------------------------------------------------------- /distrod_packer/resources/bin/chsh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ORIG="$(PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin command -v chsh)" 4 | if [ -t 0 ]; then 5 | echo "[Distrod] Run 'sudo /opt/distrod/bin/distrod enable' after this command succeeds." >&2 6 | echo "[Distrod] It's necessary for Systemd to work as the pseudo init process." >&2 7 | fi 8 | "$ORIG" "$@" 9 | -------------------------------------------------------------------------------- /distrod_packer/resources/bin/useradd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ORIG="$(PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin command -v useradd)" 4 | if [ -t 0 ]; then 5 | echo "[Distrod] Run 'sudo /opt/distrod/bin/distrod enable' after this command succeeds." >&2 6 | echo "[Distrod] It's necessary for Systemd to work as the pseudo init process." >&2 7 | fi 8 | "$ORIG" "$@" 9 | -------------------------------------------------------------------------------- /distrod_packer/resources/conf/distrod.toml: -------------------------------------------------------------------------------- 1 | [distrod] 2 | default_distro_image = "/" 3 | distro_images_dir = "/var/lib/distrod" 4 | -------------------------------------------------------------------------------- /distrod_packer/resources/conf/tcp4_ports: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /distrod_packer/resources/misc/distrod-post-update: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | /opt/distrod/bin/distrod enable 6 | -------------------------------------------------------------------------------- /distrod_packer/resources/run/systemd/system/portproxy.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Distrod port exposure service 3 | After=network-online.target 4 | Wants=network-online.target systemd-networkd-wait-online.service 5 | 6 | [Service] 7 | Restart=on-failure 8 | RestartSec=15 9 | 10 | # TODO: On Windows 11, starting an exe located at WSL's path on Windows startup hangs up. Fix it. 11 | ExecStart=/bin/sh -c '/opt/distrod/bin/portproxy.exe proxy $(/opt/distrod/bin/portproxy show ipv4) -t $(cat /opt/distrod/conf/tcp4_ports)' 12 | # WSL_INTEROP and other variables should be set by systemd even without sourcing /etc/environment, 13 | # but if a user enable this just after they updated systemd (apt-upgrade or pacman -Syu), then 14 | # systemd will forget those variables due to restart. So, source /etc/environment just in case. 15 | EnvironmentFile=/etc/environment 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /distrod_packer/resources/run/systemd/system/systemd-tmpfiles-clean.service.d/exclude_wslg_sockets.conf: -------------------------------------------------------------------------------- 1 | # This file is part of Distrod. 2 | # 3 | # Do not edit this file directly. This file can be overwritten when Distrod is updated. 4 | # Instead, create /etc/systemd/system/systemd-tmpfiles-clean.service.d/exclude_wslg_sockets.conf, 5 | # systemd will load it. 6 | 7 | # avoid `/tmp/.X11-unix` to be cleaned up by systemd 8 | [Service] 9 | ExecStart= 10 | ExecStart=systemd-tmpfiles --clean --exclude-prefix=/tmp/.X11-unix -------------------------------------------------------------------------------- /distrod_packer/resources/run/systemd/system/systemd-tmpfiles-setup.service.d/exclude_wslg_sockets.conf: -------------------------------------------------------------------------------- 1 | # This file is part of Distrod. 2 | # 3 | # Do not edit this file directly. This file can be overwritten when Distrod is updated. 4 | # Instead, create /etc/systemd/system/systemd-tmpfiles-setup.service.d/exclude_wslg_sockets.conf, 5 | # systemd will load it. 6 | 7 | # avoid `/tmp/.X11-unix` to be cleaned up by systemd 8 | [Service] 9 | ExecStart= 10 | ExecStart=systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev --exclude-prefix=/tmp/.X11-unix -------------------------------------------------------------------------------- /distrod_packer/resources/run/tmpfiles.d/x11.conf: -------------------------------------------------------------------------------- 1 | # This file is part of Distrod. 2 | # 3 | # Do not edit this file directly. This file can be overwritten when Distrod 4 | # is updated. Instead, create /etc/tmpfiles.d/x11.conf. Systemd will load it. 5 | 6 | # Create a symlink to the socket of WSLg after /tmp is cleaned up 7 | L+ /tmp/.X11-unix - - - - /mnt/wslg/.X11-unix 8 | -------------------------------------------------------------------------------- /docs/distrod_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/docs/distrod_demo.gif -------------------------------------------------------------------------------- /docs/distrod_shot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/docs/distrod_shot1.png -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ## Troubleshoot WSL Network Down 4 | 5 | Distrod should modify systemd-based systems so that it doesn't break the WSL network, 6 | but possibly you may encounter the situation where Distrod cannot connect to the Internet, 7 | or perhaps your entire WSL network go down. 8 | Don't be panicked. It can be fixed easily :) 9 | 10 | ### Why did the entire WSL network go down, not just Distrod's? 11 | 12 | Sometimes your entire WSL network may go down, not just Distrod's. 13 | This is because in WSL2, all distros share the same network interfaces. 14 | Actually, each distro of WSL2 is not running on an isolated VM, but on a container that WSL's `/init` creates. 15 | And they share the same network namespaces, thus the entire network is down if some distro breaks its network. 16 | 17 | ### Solution 1: Use Tested Distro with Distrod 18 | 19 | 1. Fix the network first. 20 | 21 | Broken network affects other distros as well, so let's anyway fix it first. 22 | Just restarting your WSL2 solves the problem. 23 | 24 | Run 25 | 26 | ```console 27 | > wsl --shutdown 28 | ``` 29 | 30 | and restart it by starting your NON-Distrod distro. If you start Distrod, it will break the network again. 31 | 32 | 2. Un-install Distrod 33 | 34 | ```console 35 | > wsl --unregister Distrod 36 | ``` 37 | 38 | 3. Install a distro which [README.md](../README.md) says is continuously tested. 39 | 40 | Follow the instruction on README.md. The tested distros will not break the network. 41 | 42 | ### Solution 2: Debug What Systemd Service is Breaking your Network 43 | 44 | If you're familiar with Linux system and Systemd, you will be able to find which systemd service is 45 | breaking the WSL network, and disable it by `# systemctl disable SERVICE_NAME`. 46 | 47 | 1. Find the service which broke the network. 48 | 49 | Basically, when a WSL2 distro starts, 50 | WSL will initialize `eth0` and assign an IP address and the default gateway to it. 51 | Then, WSL also updates `/etc/resolv.conf` with its own DNS server. 52 | On the other hand, WSL doesn't provide any DHCP server. 53 | 54 | So, maybe some systemd unit broke 55 | 56 | 1. `eth0`'s IP / the default gateway by DHCP or 57 | 2. `/etc/resolv.conf` 58 | 59 | Find that from the log from `journalctl` 60 | 61 | ```bash 62 | sudo journalctl -b 63 | ``` 64 | 65 | 2. Disable the service 66 | 67 | ```bash 68 | sudo systemctl disable SERVICE 69 | ``` 70 | 71 | 3. Restart WSL 72 | 73 | ```console 74 | > wsl --shutdown 75 | ``` 76 | 77 | 4. Report the issue to Distrod! :) 78 | 79 | ### Solution 3: Disable Distrod 80 | 81 | 1. Disable Distrod 82 | 83 | See [Disable Systemd / Distrod](#disable-systemd--distrod) 84 | 85 | 2. Restart WSL 86 | 87 | ```console 88 | > wsl --shutdown 89 | ``` 90 | 91 | Even after disabling Distrod, you can continue to use your distro as a regular WSL2 distro. 92 | 93 | ## Launch WSL 2 on Windows Startup 94 | 95 | Please run `enable` command with `--start-on-windows-boot` option. 96 | It's ok to run `enable` command multiple times, even if you have already enabled distrod. 97 | 98 | ```bash 99 | sudo /opt/distrod/bin/distrod enable --start-on-windows-boot 100 | ``` 101 | 102 | This command registers a Windows task that launches a Distrod distro in WSL2 on Windows startup. 103 | Although the UAC dialog appears when you run `enable --start-on-windows-boot`, 104 | the registered Windows task will run without Windows admin privilege. 105 | 106 | Distrod will start automatically when Windows starts, regardless of whether you are logged in or not. 107 | 108 | **NOTE**: Distrod runs on Windows startup with a 30 second delay. 109 | You can check if the auto-start succeeded by Windows' Task Scheduler. 110 | 111 | See also: 112 | 113 | - [Enable Debug Logging of Distrod](#enable-debug-logging-of-distrod) 114 | 115 | ## Stop Launching WSL 2 on Windows Startup 116 | 117 | Open Windows "Task Scheduler" app, and remove the task named as `StartDistrod_YOUR_DISTRO_NAME_for_YOUR_USER_NAME`. 118 | 119 | ## Forward Ports to outside of Windows 120 | 121 | As of October 2021, WSL 2 doesn't forward ports to outside of Windows. 122 | This has plagued many users who want to expose their ssh servers to the outside of Windows, 123 | and there was no easy solution to this problem. 124 | 125 | Distrod provides a very simple port-forwarding solution, using systemd. 126 | Distrod has the built-in `portproxy.service`. Enable it by `systemctl`. 127 | 128 | **NOTE**: On Windows 11, exe files on WSL FS doesn't work on Windows startup. See [Known bus](#known-bugs). 129 | 130 | 1. Configure the port numbers to forward 131 | 132 | Write the port numbers in `/opt/distrod/conf/tcp4_ports`, separated by spaces. 133 | 134 | ```console 135 | $ cat /opt/distrod/conf/tcp4_ports 136 | 0 137 | $ echo 22 80 443 | sudo tee /opt/distrod/conf/tcp4_ports 138 | $ cat /opt/distrod/conf/tcp4_ports 139 | 22 80 443 140 | ``` 141 | 142 | 2. Enable and start `portproxy.service` 143 | 144 | ```console 145 | $ sudo systemctl enable --now portproxy.service 146 | Created symlink /etc/systemd/system/multi-user.target.wants/portproxy.service → /run/systemd/system/portproxy.service 147 | $ sudo systemctl status portproxy.service 148 | ● portproxy.service - Distrod port exposure service 149 | Loaded: loaded (/run/systemd/system/portproxy.service; enabled; vendor preset: disabled) 150 | Active: active (running) since Sat 2021-10-30 21:55:13 JST; 2s ago 151 | Main PID: 271 (portproxy.exe) 152 | Tasks: 1 (limit: 61620) 153 | Memory: 2.8M 154 | CGroup: /system.slice/portproxy.service 155 | └─271 /tools/init /opt/distrod/bin/portproxy.exe proxy 172.29.231.165 -t 22 80 443 156 | 157 | Oct 30 21:55:13 machine systemd[1]: Started Distrod port exposure service. 158 | Oct 30 21:55:13 machine sh[271]: Forwarding 0.0.0.0:22 to 172.29.231.165:22 159 | Oct 30 21:55:13 machine sh[271]: Forwarding 0.0.0.0:443 to 172.29.231.165:443 160 | Oct 30 21:55:13 machine sh[271]: Forwarding 0.0.0.0:80 to 172.29.231.165:80 161 | ``` 162 | 163 | Now you should be able to access your services from outside of Windows. 164 | 165 | ## Install and Run Multiple Distros at the same time 166 | 167 | You can install multiple distros by `distrod_wsl_launcher.exe`. 168 | Please run it with `-d new_distro_name` option. 169 | 170 | ```console 171 | > distrod_wsl_launcher -d new_distrod 172 | ``` 173 | 174 | ## Disable Systemd / Distrod 175 | 176 | By disabling Distrod, systemd will not run anymore. 177 | You can continue to use your WSL instance as a regular WSL distro. 178 | 179 | ```bash 180 | sudo /opt/distrod/bin/distrod disable 181 | ``` 182 | 183 | If you also want to completely remove distrod, just delete `/opt/distrod`. 184 | 185 | **For users of versions prior to 1.5** 186 | 187 | In addition, clean up the WSL related variables written in `/etc/environment`. 188 | Remove `WSLENV`, `WSL_DISTRO_NAME`, and `WSL_INTEROP`. You can remove `/opt/distrod/bin` from `PATH` as well. 189 | Prior to version 1.5, Distrod did not clean up these variables. 190 | This prevented `.exe` files from being launched from a `sudo` or `ssh` session. 191 | 192 | ## Open a Shell Session outside the Container for Systemd 193 | 194 | Basically, Distrod works by 195 | 196 | 1. Register Distrod as the login shell in Linux 197 | 2. When Distrod is launched by WSL's init as a login shell, 198 | 1. Distrod starts systemd in a simple container 199 | 2. Launch your actual shell within that container 200 | 201 | You can escape from this container for debug or other purposes. 202 | Usually, `wsl` command runs every command given to it via the default shell, 203 | that is, Distrod in this case. However, with `-e` option, it runs a command 204 | without a shell. So, launch the shell by the following command to escape from 205 | the Distrod's container. 206 | 207 | ```console 208 | > wsl -d Distrod -e /bin/bash 209 | ``` 210 | 211 | ## Run Distrod as a Standalone One-shot Command 212 | 213 | In this usage, distrod works just like genie or subsystemd. 214 | 215 | Before starting it, enabling Distrod once is recommended. 216 | Otherwise, it's likely that the network interfaces of the whole WSL system will get broken 217 | by the default systemd network configuration, which is often incompatible with WSL. 218 | 219 | ```bash 220 | sudo /opt/distrod/bin/distrod enable 221 | sudo /opt/distrod/bin/distrod disable 222 | ``` 223 | 224 | Then, you can start a systemd session by the following command. 225 | **WARNING:** systemd will clean up files under `/tmp` 226 | 227 | ```bash 228 | sudo /opt/distrod/bin/distrod start 229 | ``` 230 | 231 | You can get inside the container by 232 | 233 | ```bash 234 | sudo /opt/distrod/bin/distrod exec -u $(whoami) -- /bin/bash 235 | ``` 236 | 237 | ## Enable Debug Logging of Distrod 238 | 239 | Edit the Distrod's configuration file and set the debug level. 240 | 241 | Add the following lines to `/opt/distrod/conf/distrod.toml`. 242 | 243 | ```toml 244 | log_level = "trace" 245 | ``` 246 | 247 | With this setting, you will see debug messages in a terminal from Distrod when you open a WSL session. 248 | The message displayed will be different when you start a WSL session for the first time after Windows starts 249 | and when you start a WSL session after that. 250 | 251 | In some cases, such as when Distrod starts automatically when Windows starts, it may be difficult to see the messages in the terminal. You can enable logging to `/dev/kmsg`. 252 | 253 | ```toml 254 | kmsg_log_level = "trace" 255 | ``` 256 | 257 | You can see the log by the following command. 258 | 259 | ```bash 260 | sudo journalctl -b -k -t Distrod 261 | ``` 262 | 263 | If your system is not running systemd for some reason, then you can check the log by 264 | 265 | ```bash 266 | sudo grep 'Distrod:' /dev/kmsg 267 | ``` 268 | 269 | ## Know Bugs 270 | 271 | - Starting the port forwarding service on Windows startup doesn't work on Windows 11, 272 | because Windows 11 has a bug that it cannot run exe files on WSL's FS on Windows startup. 273 | Workaround will be implemented soon. 274 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | LATEST_RELEASE_URL="https://github.com/nullpo-head/wsl-distrod/releases/latest/download/opt_distrod.tar.gz" 6 | 7 | HELP_STR="Usage: $0 8 | 9 | command: 10 | - install 11 | - update 12 | - uninstall" 13 | 14 | help () { 15 | echo "$HELP_STR" 16 | } 17 | 18 | error_help () { 19 | error "$HELP_STR" 20 | } 21 | 22 | install () { 23 | mkdir /opt/distrod || error "Failed to create /opt/distrod." 24 | cd /opt/distrod || error "Could not change directory to /opt/distrod" 25 | get_release_file 26 | tar xvf opt_distrod.tar.gz 27 | rm opt_distrod.tar.gz 28 | echo "Installation is complete!" 29 | } 30 | 31 | uninstall () { 32 | if grep -o systemd /proc/1/status > /dev/null; then 33 | error "This uninstall command cannot run inside a running Distrod distro." 34 | error "To uninstall it, do the following first." 35 | error "1. /opt/distrod/bin/distrod disable # Stop systemd from starting as init" 36 | error "2. wsl.exe --shutdown # Terminate WSL2" 37 | error "After that, Systemd will not run as the init and you can run uninstall." 38 | exit 1 39 | fi 40 | rm -rf /opt/distrod 41 | echo "Distrod has been uninstalled!" 42 | } 43 | 44 | update () { 45 | cd /opt/distrod || error "Could not change directory to /opt/distrod" 46 | get_release_file 47 | EXCLUDE="" 48 | for FILE in /opt/distrod/conf/*; do 49 | FILE=${FILE#/opt/distrod/} 50 | if printf "%s" "$FILE" | grep -E ' '; then 51 | error "Found a file with a name containing spaces. Please remove it. Aborting update command." 52 | exit 1 53 | fi 54 | EXCLUDE="$EXCLUDE --exclude $FILE" 55 | done 56 | # shellcheck disable=SC2086 57 | tar xvf opt_distrod.tar.gz $EXCLUDE 58 | echo "Ruuning post-update actions..." 59 | POST_UPDATE="/opt/distrod/misc/distrod-post-update" 60 | if [ -e "${POST_UPDATE}" ]; then 61 | "${POST_UPDATE}" 62 | fi 63 | echo "Distrod has been updated!" 64 | } 65 | 66 | get_release_file() { 67 | if [ -n "$RELEASE_FILE" ]; then 68 | if [ "$(realpath "$RELEASE_FILE")" != "$(realpath opt_distrod.tar.gz)" ]; then 69 | cp "$RELEASE_FILE" opt_distrod.tar.gz 70 | fi 71 | else 72 | curl -L -O "${LATEST_RELEASE_URL}" 73 | fi 74 | } 75 | 76 | error () { 77 | echo "$@" >&2 78 | } 79 | 80 | 81 | if [ -z "$1" ]; then 82 | error_help 83 | exit 1 84 | fi 85 | 86 | if [ "$(whoami)" != "root" ]; then 87 | error "You must be root to run this script, please use sudo ./install.sh" 88 | exit 1 89 | fi 90 | 91 | COMMAND= 92 | while [ -n "$1" ]; do 93 | case "$1" in 94 | -h|--help) 95 | echo "$HELP_STR" 96 | exit 0 97 | ;; 98 | install) 99 | COMMAND=install 100 | shift 101 | ;; 102 | uninstall) 103 | COMMAND=uninstall 104 | shift 105 | ;; 106 | update) 107 | COMMAND=update 108 | shift 109 | ;; 110 | -r|--release-file) 111 | RELEASE_FILE="$(realpath "$2")" 112 | shift 2 113 | ;; 114 | -*) 115 | error "Error: Unknown flag $1" 116 | exit 1 117 | ;; 118 | *) # preserve positional arguments 119 | error "Error: Unknown command $1" 120 | exit 1 121 | ;; 122 | esac 123 | done 124 | 125 | "$COMMAND" 126 | -------------------------------------------------------------------------------- /storeapp/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | 290 | 291 | 292 | DistroLauncher/MSG*.bin 293 | DistroLauncher/messages.rc 294 | DistroLauncher/messages.h 295 | *.lib 296 | *.dll 297 | *.exe 298 | *.tar.gz -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/LargeTile.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SmallTile.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/SplashScreen.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square150x150Logo.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-16.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-256.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-32.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.altform-unplated_targetsize-48.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-16.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-24.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-256.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-32.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Square44x44Logo.targetsize-48.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/StoreLogo.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-100.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-125.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-150.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullpo-head/wsl-distrod/c3181f4e49566a1d92988b71487716f0dceddd1e/storeapp/DistroLauncher-Appx/Assets/Wide310x150Logo.scale-400.png -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/DistroLauncher-Appx.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {f63472f9-d0a0-412e-aa3d-a4e822970486} 5 | DistroLauncher_Appx 6 | en-US 7 | 14.0 8 | true 9 | Windows Store 10 | 10.0.16299.0 11 | 10.0.16215.0 12 | 10.0 13 | distrod 14 | DistroLauncher-Appx 15 | True 16 | 17 | 18 | 19 | 20 | Debug 21 | ARM64 22 | 23 | 24 | Debug 25 | x64 26 | 27 | 28 | Release 29 | ARM64 30 | 31 | 32 | Release 33 | x64 34 | 35 | 36 | 37 | Application 38 | true 39 | v142 40 | 41 | 42 | Application 43 | true 44 | v142 45 | true 46 | 47 | 48 | Application 49 | false 50 | true 51 | v142 52 | true 53 | 54 | 55 | Application 56 | false 57 | true 58 | v142 59 | true 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | True 79 | Always 80 | 675A5337F69CDA6EB8616E4FA4210493F94202B0 81 | False 82 | x64|arm64 83 | 1 84 | OnApplicationRun 85 | True 86 | DistroLauncher-Appx_TemporaryKey.pfx 87 | 88 | 89 | 90 | false 91 | 92 | 93 | 94 | 95 | 96 | Designer 97 | 98 | 99 | 100 | 101 | 102 | 103 | true 104 | 105 | 106 | 107 | 108 | true 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | true 149 | 150 | 151 | 152 | 153 | true 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | Document 168 | false 169 | Copy $(SolutionDir)\..\distrod\target\release\distrod_wsl_launcher.exe into $(SolutionDir)\$(platform)\$(Configuration)\$(ProjectName)\$(targetname).exe 170 | copy $(SolutionDir)\..\distrod\target\release\distrod_wsl_launcher.exe $(SolutionDir)\$(platform)\$(Configuration)\$(ProjectName)\$(targetname).exe 171 | $(targetname).exe 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/DistroLauncher-Appx.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | f63472f9-d0a0-412e-aa3d-a4e822970486 6 | 7 | 8 | 3cd39ea5-301d-4878-9d47-b5dbb30f6c95 9 | bmp;fbx;gif;jpg;jpeg;tga;tiff;tif;png 10 | 11 | 12 | 13 | 14 | Assets 15 | 16 | 17 | Assets 18 | 19 | 20 | Assets 21 | 22 | 23 | Assets 24 | 25 | 26 | Assets 27 | 28 | 29 | Assets 30 | 31 | 32 | Assets 33 | 34 | 35 | Assets 36 | 37 | 38 | Assets 39 | 40 | 41 | Assets 42 | 43 | 44 | Assets 45 | 46 | 47 | Assets 48 | 49 | 50 | Assets 51 | 52 | 53 | Assets 54 | 55 | 56 | Assets 57 | 58 | 59 | Assets 60 | 61 | 62 | Assets 63 | 64 | 65 | Assets 66 | 67 | 68 | Assets 69 | 70 | 71 | Assets 72 | 73 | 74 | Assets 75 | 76 | 77 | Assets 78 | 79 | 80 | Assets 81 | 82 | 83 | Assets 84 | 85 | 86 | Assets 87 | 88 | 89 | Assets 90 | 91 | 92 | Assets 93 | 94 | 95 | Assets 96 | 97 | 98 | Assets 99 | 100 | 101 | Assets 102 | 103 | 104 | Assets 105 | 106 | 107 | Assets 108 | 109 | 110 | Assets 111 | 112 | 113 | Assets 114 | 115 | 116 | Assets 117 | 118 | 119 | Assets 120 | 121 | 122 | Assets 123 | 124 | 125 | Assets 126 | 127 | 128 | Assets 129 | 130 | 131 | Assets 132 | 133 | 134 | Assets 135 | 136 | 137 | Assets 138 | 139 | 140 | Assets 141 | 142 | 143 | Assets 144 | 145 | 146 | Assets 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /storeapp/DistroLauncher-Appx/Distrod.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Distrod 7 | Windows Console Dev Team 8 | Assets\StoreLogo.png 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /storeapp/DistroLauncher.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31424.327 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DistroLauncher-Appx", "DistroLauncher-Appx\DistroLauncher-Appx.vcxproj", "{F63472F9-D0A0-412E-AA3D-A4E822970486}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM64 = Debug|ARM64 11 | Debug|x64 = Debug|x64 12 | Release|ARM64 = Release|ARM64 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Debug|ARM64.ActiveCfg = Debug|ARM64 17 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Debug|ARM64.Build.0 = Debug|ARM64 18 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Debug|ARM64.Deploy.0 = Debug|ARM64 19 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Debug|x64.ActiveCfg = Debug|x64 20 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Debug|x64.Build.0 = Debug|x64 21 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Debug|x64.Deploy.0 = Debug|x64 22 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Release|ARM64.ActiveCfg = Release|ARM64 23 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Release|ARM64.Build.0 = Release|ARM64 24 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Release|ARM64.Deploy.0 = Release|ARM64 25 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Release|x64.ActiveCfg = Release|x64 26 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Release|x64.Build.0 = Release|x64 27 | {F63472F9-D0A0-412E-AA3D-A4E822970486}.Release|x64.Deploy.0 = Release|x64 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(ExtensibilityGlobals) = postSolution 33 | SolutionGuid = {1CBEEAE2-D963-465A-A538-A30F8D615037} 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /storeapp/LICENSE: -------------------------------------------------------------------------------- 1 | Some of the files under this directory consist of contributions by the following 2 | contributors under the following licenses. The copyright of the original lines of 3 | the original files belong to the original contributors. 4 | 5 | --- 6 | 7 | MIT License 8 | 9 | Copyright (c) Microsoft Corporation. All rights reserved. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE 28 | -------------------------------------------------------------------------------- /storeapp/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Add path to MSBuild Binaries 4 | set MSBUILD=() 5 | if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" ( 6 | set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" 7 | goto :FOUND_MSBUILD 8 | ) 9 | if exist "%ProgramFiles%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" ( 10 | set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" 11 | goto :FOUND_MSBUILD 12 | ) 13 | if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" ( 14 | set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" 15 | goto :FOUND_MSBUILD 16 | ) 17 | if exist "%ProgramFiles%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" ( 18 | set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" 19 | goto :FOUND_MSBUILD 20 | ) 21 | if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" ( 22 | set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" 23 | goto :FOUND_MSBUILD 24 | ) 25 | if exist "%ProgramFiles%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" ( 26 | set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" 27 | goto :FOUND_MSBUILD 28 | ) 29 | if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe" ( 30 | set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe" 31 | goto :FOUND_MSBUILD 32 | ) 33 | if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe" ( 34 | set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe" 35 | goto :FOUND_MSBUILD 36 | ) 37 | if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" ( 38 | set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" 39 | goto :FOUND_MSBUILD 40 | ) 41 | if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin" ( 42 | set MSBUILD="%ProgramFiles(x86)%\MSBuild\14.0\bin\msbuild.exe" 43 | goto :FOUND_MSBUILD 44 | ) 45 | if exist "%ProgramFiles%\MSBuild\14.0\bin" ( 46 | set MSBUILD="%ProgramFiles%\MSBuild\14.0\bin\msbuild.exe" 47 | goto :FOUND_MSBUILD 48 | ) 49 | 50 | if %MSBUILD%==() ( 51 | echo "I couldn't find MSBuild on your PC. Make sure it's installed somewhere, and if it's not in the above if statements (in build.bat), add it." 52 | goto :EXIT 53 | ) 54 | :FOUND_MSBUILD 55 | set _MSBUILD_TARGET=Build 56 | set _MSBUILD_CONFIG=Debug 57 | 58 | :ARGS_LOOP 59 | if (%1) == () goto :POST_ARGS_LOOP 60 | if (%1) == (clean) ( 61 | set _MSBUILD_TARGET=Clean,Build 62 | ) 63 | if (%1) == (rel) ( 64 | set _MSBUILD_CONFIG=Release 65 | ) 66 | shift 67 | goto :ARGS_LOOP 68 | 69 | :POST_ARGS_LOOP 70 | %MSBUILD% %~dp0\DistroLauncher.sln /t:%_MSBUILD_TARGET% /m /nr:true /p:Configuration=%_MSBUILD_CONFIG%;Platform=x64;AppxBundle=Always;AppxBundlePlatforms="x64" 71 | 72 | if (%ERRORLEVEL%) == (0) ( 73 | echo. 74 | echo Created appx in %~dp0x64\%_MSBUILD_CONFIG%\DistroLauncher-Appx\ 75 | echo. 76 | ) 77 | 78 | :EXIT 79 | -------------------------------------------------------------------------------- /windows.mk: -------------------------------------------------------------------------------- 1 | ROOTFS_PATH ?= distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz 2 | OUTPUT_PORT_PROXY_EXE_PATH ?= distrod/target/release/portproxy.exe 3 | 4 | build: distro_launcher/x64/distrod_wsl_launcher.exe 5 | 6 | distro_launcher: distro_launcher/x64/Release/DistroLauncher-Appx/DistroLauncher-Appx_1.0.0.0_x64.appx 7 | 8 | distro_launcher/x64/Release/DistroLauncher-Appx/DistroLauncher-Appx_1.0.0.0_x64.appx: distro_launcher/x64/distrod_wsl_launcher.exe 9 | cd distro_launcher; cmd.exe /C "build.bat rel" 10 | 11 | distro_launcher/x64/distrod_wsl_launcher.exe: distrod_wsl_launcher 12 | 13 | distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz: $(ROOTFS_PATH) 14 | if [ "$$(realpath "$(ROOTFS_PATH)" )" != "$$(realpath distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz)" ]; then \ 15 | cp $(ROOTFS_PATH) $@; \ 16 | fi 17 | 18 | distrod_wsl_launcher: distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz 19 | cd distrod; cargo.exe build --release -p distrod_wsl_launcher 20 | 21 | distrod/target/release/portproxy.exe: portproxy.exe 22 | portproxy.exe: 23 | cd distrod; cargo.exe build --release -p portproxy 24 | if [ "$$(realpath "$(OUTPUT_PORT_PROXY_EXE_PATH)" )" != "$$(realpath ./distrod/target/release/portproxy.exe)" ]; then \ 25 | cp target/release/port_proxy.exe $(OUTPUT_PORT_PROXY_EXE_PATH); \ 26 | fi 27 | 28 | test-win: distrod/distrod_wsl_launcher/resources/distrod_root.tar.gz 29 | cd distrod; cargo test --verbose -p libs -p portproxy -p distrod_wsl_launcher 30 | 31 | .PHONY: build distro_launcher distrod_wsl_launcher portproxy.exe test 32 | --------------------------------------------------------------------------------