Merge ~cnihelton/ubuntu/+source/wsl-setup:lp-2122047-new-upstream-version into ubuntu/+source/wsl-setup:ubuntu/devel

Proposed by Carlos Nihelton
Status: Merged
Merged at revision: ecf8d011e9cc2e09421859238d286dfd329650a2
Proposed branch: ~cnihelton/ubuntu/+source/wsl-setup:lp-2122047-new-upstream-version
Merge into: ubuntu/+source/wsl-setup:ubuntu/devel
Diff against target: 1517 lines (+1226/-100)
20 files modified
.github/actions/get-wsl-image/action.yaml (+118/-0)
.github/actions/validate-wsl/action.yaml (+113/-0)
.github/dependabot.yaml (+18/-0)
.github/workflows/integration.yaml (+328/-0)
.github/workflows/shellcheck.yaml (+27/-0)
.shellcheckrc (+1/-0)
CONTRIBUTING.md (+102/-0)
SECURITY.md (+53/-0)
debian/changelog (+12/-0)
debian/control (+1/-0)
debian/install (+2/-1)
debian/rules (+0/-1)
dev/null (+0/-7)
test/basic-assertions.sh (+30/-0)
test/e2e/setup.expect (+113/-0)
test/systemd-assertions.sh (+33/-0)
test/ubuntu-insights-assertions.sh (+33/-0)
ubuntu-insights.sh (+143/-0)
wait-for-cloud-init (+2/-2)
wsl-setup (+97/-89)
Reviewer Review Type Date Requested Status
Nick Rosbrook (community) Approve
git-ubuntu import Pending
Review via email: mp+497725@code.launchpad.net

Commit message

wsl-setup (0.6.1) resolute; urgency=medium

  [ Kat Kuo]
  * Added ubuntu-insights integration
  * Added integration tests in upstream CI

  [Carlos Nihelton]
  * Case-fold the Windows username before sanitization (LP: #2122047)
  * Remove the systemd-timesyncd.service override that would enable it in WSL

Description of the change

This imports the new upstream version of the wsl-setup package to Resolute Racoon, fixing the bug LP #2122047 and improving the package quality by fixing some lintian warnings.
A considerable size in the diff is due new CI workflows being added for enhanced automated testing of this package in the upstream repository CI (GitHub). Those tests are run in upstream CI at pull requests, but not during package building time because most of them needs to run on Windows.

Given the fact this is a native package, uploading this branch would be equivalent in terms of contents to just checking out the repository at the tag 0.6.0, creating the source package and uploading it. The commit IDs wouldn't match though because this branch is created by turning the diff between ubuntu/devel and upstream/tags/0.6.1 into patches applied with `git am`, thus shown as new and linear commits.

To post a comment you must log in.
Revision history for this message
Nick Rosbrook (enr0n) wrote :

This is a bit awkward of a PR, because it wsl-setup is native, and in it's actual git repo this is already merged... so there is not much to "review" at this point.

If you just want the git-ubuntu history to be aligned with upstream, fair enough. Otherwise, there's not too much reason to make git-ubuntu PRs for this package in the future.

In any case, sponsoring.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.github/actions/get-wsl-image/action.yaml b/.github/actions/get-wsl-image/action.yaml
2new file mode 100644
3index 0000000..b93fd29
4--- /dev/null
5+++ b/.github/actions/get-wsl-image/action.yaml
6@@ -0,0 +1,118 @@
7+# This action gets the latest WSL image, leveraging both the persistent behavior of our runners, and the GitHub cache
8+name: Get WSL Image
9+description: Get the latest WSL image, leveraging both the persistent behavior of our runners, and the GitHub cache
10+
11+inputs:
12+ base_url:
13+ description: "The base URL to download the WSL image from"
14+ required: false
15+ default: "https://cdimages.ubuntu.com/ubuntu-wsl/daily-live/current/"
16+ arch:
17+ description: "The architecture of the WSL image to download (amd64 or arm64)"
18+ required: false
19+ default: "amd64"
20+ output_path:
21+ description: "The path to save the downloaded WSL image to"
22+ required: false
23+ default: "daily-image.wsl"
24+outputs:
25+ cache-key:
26+ description: "The cache key for the WSL image"
27+ value: ${{ steps.compute-cache-key.outputs.cache-key }}
28+ image-name:
29+ description: "The name of the image we found"
30+ value: ${{ steps.compute-cache-key.outputs.image-name }}
31+
32+runs:
33+ using: "composite"
34+ steps:
35+ - name: Compute image cache key
36+ id: compute-cache-key
37+ shell: bash
38+ run: |
39+ set -euo pipefail
40+
41+ shafile="${{ runner.temp }}/SHA256SUMS"
42+ uri="${{ inputs.base_url }}/SHA256SUMS"
43+ if ! curl --fail -o "$shafile" "${uri}"; then
44+ echo "::error:: Failed to download the checksum file from $uri."
45+ exit 1
46+ fi
47+
48+ # Look for a line matching the pattern: <sha> *<name>-wsl-<arch>.wsl
49+ arch="${{ inputs.arch }}"
50+ line=$(grep -E "^[a-f0-9]+[[:space:]]+\*.*-wsl-${arch}\.wsl$" "$shafile")
51+ if [ -z "$line" ]; then
52+ echo "::error:: No WSL image found for architecture '$arch' in the $shafile file."
53+ exit 2
54+ fi
55+
56+ # Extract the SHA (first field) and image name (second field without the *)
57+ sha=$(echo "$line" | awk '{print $1}')
58+ image_name=$(echo "$line" | awk '{print $2}' | sed 's/^\*//')
59+
60+ echo "cache-key=$sha" >> "$GITHUB_OUTPUT"
61+ echo "image-name=$image_name" >> "$GITHUB_OUTPUT"
62+
63+ - name: Validate image
64+ id: check-locally
65+ shell: bash
66+ run: |
67+ set -euo pipefail
68+
69+ expected_sha="${{ steps.compute-cache-key.outputs.cache-key }}"
70+ output_path="${{ inputs.output_path }}"
71+
72+ if [ -r "$output_path" ]; then
73+ echo "Found local image at $output_path"
74+
75+ # Compare the SHAs
76+ if (echo "$expected_sha $output_path" | sha256sum -c); then
77+ echo "✅ Local image matches expected SHA. Using local file."
78+ echo "is-valid=true" >> "$GITHUB_OUTPUT"
79+ else
80+ echo "❌ Local image SHA does not match expected SHA. Will need to download."
81+ rm "$output_path"
82+ echo "is-valid=false" >> "$GITHUB_OUTPUT"
83+ fi
84+ else
85+ echo "No local image found at $output_path"
86+ echo "is-valid=false" >> "$GITHUB_OUTPUT"
87+ fi
88+
89+ - name: Restore image cache
90+ id: restore-cache
91+ if: ${{ steps.check-locally.outputs.is-valid != 'true' }}
92+ uses: actions/cache/restore@v4
93+ with:
94+ path: ${{ inputs.output_path }}
95+ key: ${{ steps.compute-cache-key.outputs.cache-key }}
96+ enableCrossOsArchive: true
97+
98+ - name: Download image
99+ if: ${{ steps.check-locally.outputs.is-valid != 'true' && steps.restore-cache.outputs.cache-hit != 'true' }}
100+ shell: bash
101+ run: |
102+ set -euo pipefail
103+
104+ expected_sha="${{ steps.compute-cache-key.outputs.cache-key }}"
105+ output_path="${{ inputs.output_path }}"
106+
107+ uri="${{ inputs.base_url }}/${{ steps.compute-cache-key.outputs.image-name }}"
108+ if ! curl --fail -o "$output_path" "$uri"; then
109+ echo "::error:: Failed to download the WSL image from $uri"
110+ exit 1
111+ fi
112+
113+ if ! (echo "$expected_sha $output_path" | sha256sum -c); then
114+ echo "::error:: Downloaded WSL image SHA does not match expected SHA."
115+ exit 2
116+ fi
117+
118+ - name: Save image to cache
119+ if: ${{ steps.restore-cache.outputs.cache-hit != 'true' }}
120+ uses: actions/cache/save@v4
121+ with:
122+ path: ${{ inputs.output_path }}
123+ key: ${{ steps.restore-cache.outputs.cache-primary-key }}
124+ enableCrossOsArchive: true
125diff --git a/.github/actions/validate-wsl/action.yaml b/.github/actions/validate-wsl/action.yaml
126new file mode 100644
127index 0000000..ac24cb8
128--- /dev/null
129+++ b/.github/actions/validate-wsl/action.yaml
130@@ -0,0 +1,113 @@
131+# This action validates a newly set up WSL instance by running a set of basic assertions. Interop must be enabled.
132+name: Validate WSL Setup
133+description: Validate a newly set up WSL instance by running a set of basic assertions.
134+
135+inputs:
136+ instance:
137+ description: "The name of the WSL instance to validate"
138+ required: true
139+ working-dir:
140+ description: "The working directory to checkout scripts to inside the WSL instance"
141+ required: true
142+ expected-user:
143+ description: "The expected default user in the WSL instance"
144+ required: true
145+ expected-internal-consent-state:
146+ description: "The expected default Ubuntu Insights internal consent state (true, false)"
147+ required: true
148+ expected-registry-consent-state:
149+ description: "The expected Ubuntu Insights registry consent state. If skip, asserts that the registry key doesn't exist."
150+ required: false
151+ default: "skip"
152+ skip-systemd-assertions:
153+ description: "Whether to skip systemd specific tests"
154+ required: false
155+ default: "false"
156+ skip-font-assertions:
157+ description: "Whether to skip the font installation test"
158+ required: false
159+ default: "false"
160+
161+runs:
162+ using: "composite"
163+ steps:
164+ - name: Terminate WSL instance
165+ shell: powershell
166+ run: |
167+ wsl --terminate ${{ inputs.instance }}
168+
169+ - name: Checkout scripts into WSL
170+ uses: ubuntu/WSL/.github/actions/wsl-checkout@main
171+ with:
172+ distro: ${{ inputs.instance }}
173+ working-dir: ${{ inputs.working-dir }}
174+
175+ - name: Run basic assertions
176+ uses: ubuntu/WSL/.github/actions/wsl-bash@main
177+ with:
178+ distro: ${{ inputs.instance }}
179+ working-dir: ${{ inputs.working-dir }}
180+ exec: |
181+ source test/basic-assertions.sh "${{ inputs.expected-user }}"
182+
183+ - name: Run font installation assertions
184+ if: ${{ inputs.skip-font-assertions == 'false' }}
185+ shell: powershell
186+ run: |
187+ if (!(Test-Path -Path "${env:LocalAppData}\Microsoft\Windows\Fonts\Ubuntu*.ttf")) {
188+ Write-Error "Ubuntu font was not installed as expected"
189+ Exit 1
190+ }
191+ if (!(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts\" -Name "Ubuntu*" )) {
192+ Write-Error "Ubuntu font was not registered"
193+ Exit 2
194+ }
195+
196+ - name: Run systemd assertions
197+ if: ${{ inputs.skip-systemd-assertions == 'false' }}
198+ uses: ubuntu/WSL/.github/actions/wsl-bash@main
199+ with:
200+ distro: ${{ inputs.instance }}
201+ working-dir: ${{ inputs.working-dir }}
202+ exec: |
203+ source test/systemd-assertions.sh
204+
205+ - name: Run internal insights consent assertions
206+ uses: ubuntu/WSL/.github/actions/wsl-bash@main
207+ with:
208+ distro: ${{ inputs.instance }}
209+ working-dir: ${{ inputs.working-dir }}
210+ exec: |
211+ source test/ubuntu-insights-assertions.sh "${{ inputs.expected-internal-consent-state }}"
212+
213+ - name: Run registry insights consent assertions
214+ shell: powershell
215+ run: |
216+ $registry_key_path = "HKCU:\Software\Canonical\Ubuntu\"
217+ $registry_value_name = "UbuntuInsightsConsent"
218+
219+ if ("${{ inputs.expected-registry-consent-state }}" -eq "skip") {
220+ # Expect the registry key to not exist
221+ try {
222+ $reg = Get-ItemProperty -Path $registry_key_path -Name $registry_value_name -ErrorAction Stop
223+ Write-Error "Ubuntu Insights registry key was found but was not expected"
224+ Exit 1
225+ } catch {
226+ # Key does not exist as expected
227+ Exit 0
228+ }
229+ }
230+
231+ try {
232+ $reg = Get-ItemProperty -Path $registry_key_path -Name $registry_value_name -ErrorAction Stop
233+ } catch {
234+ Write-Error "Ubuntu Insights registry key not found"
235+ Exit 1
236+ }
237+
238+ $actual = $reg.UbuntuInsightsConsent
239+ $expected = "${{ inputs.expected-registry-consent-state }}"
240+ if (-not ("$actual".Trim() -ieq "$expected".Trim())) {
241+ Write-Error "Ubuntu Insights registry value mismatch. Expected '$expected' but found '$actual'"
242+ Exit 2
243+ }
244diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
245new file mode 100644
246index 0000000..1ee0310
247--- /dev/null
248+++ b/.github/dependabot.yaml
249@@ -0,0 +1,18 @@
250+version: 2
251+updates:
252+ # Infrastructure
253+ ## GitHub Actions
254+ - package-ecosystem: "github-actions"
255+ # Workflow files stored in the
256+ # default location of `.github/workflows`
257+ directory: "/"
258+ schedule:
259+ interval: "weekly"
260+ day: "thursday"
261+ time: "09:00"
262+ groups:
263+ gh-actions:
264+ #applies-to: version-updates
265+ patterns: ["*"]
266+ commit-message:
267+ prefix: "deps(ci)"
268diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
269new file mode 100644
270index 0000000..9bfd6c3
271--- /dev/null
272+++ b/.github/workflows/integration.yaml
273@@ -0,0 +1,328 @@
274+name: Integration tests
275+on:
276+ pull_request:
277+ workflow_dispatch:
278+ schedule:
279+ - cron: "4 3 * * 2" # Tuesdays 03:04 am.
280+ push:
281+ branches: ["main"]
282+env:
283+ instance: Ubuntu-${{ github.run_id }}
284+ profile_script: /etc/profile.d/99-quit-wsl.sh
285+ instance_working_dir: ~/wsl-setup-${{ github.run_id }}
286+ insights_registry_key_path: "HKCU:\\Software\\Canonical\\Ubuntu"
287+ insights_registry_value_name: "UbuntuInsightsConsent"
288+
289+jobs:
290+ build-deb:
291+ name: Build wsl-setup debian package
292+ runs-on: ubuntu-latest
293+ steps:
294+ - name: Check out repository
295+ uses: actions/checkout@v6
296+ - name: Build deb
297+ uses: canonical/desktop-engineering/gh-actions/common/build-debian@main
298+ with:
299+ token: ${{ secrets.GITHUB_TOKEN }}
300+ docker-image: ubuntu:devel
301+
302+ cache-img:
303+ name: Cache the daily-live image
304+ runs-on: ubuntu-latest
305+ steps:
306+ - name: Check out repository
307+ uses: actions/checkout@v6
308+ - name: Get WSL Image
309+ uses: ./.github/actions/get-wsl-image
310+ with:
311+ output_path: images.wsl
312+
313+ cloud-init-test:
314+ needs: [build-deb, cache-img]
315+ name: "Cloud-init tests"
316+ runs-on: windows-2025 # WSL and winget are preinstalled and working
317+ strategy:
318+ fail-fast: false
319+ matrix:
320+ ubuntu-insights-flag: ["0", "1", "12", "skip"] # False, True, Invalid, Not set
321+ ubuntu-insights-state: ["false", "true", "null", "skip"]
322+ exclude:
323+ # Skip combinations that would result in an interactive prompt
324+ - ubuntu-insights-flag: "skip"
325+ ubuntu-insights-state: "null"
326+ - ubuntu-insights-flag: "12"
327+ ubuntu-insights-state: "null"
328+ - ubuntu-insights-flag: "skip"
329+ ubuntu-insights-state: "skip"
330+ - ubuntu-insights-flag: "12"
331+ ubuntu-insights-state: "skip"
332+
333+ steps:
334+ - name: Check out repository
335+ uses: actions/checkout@v6
336+
337+ - name: Download artifacts
338+ uses: actions/download-artifact@v7
339+ with:
340+ path: ci-artifacts
341+ # name: is left blank so that all artifacts are downloaded, thus we don't couple on
342+ # whatever name was chosen in the build-debian action.
343+
344+ - name: Get WSL image
345+ uses: ./.github/actions/get-wsl-image
346+ with:
347+ output_path: images.wsl
348+
349+ - name: Prepare Ubuntu Insights consent registry key
350+ if: matrix.ubuntu-insights-flag != 'skip'
351+ shell: powershell
352+ run: |
353+ New-Item -Path "${{ env.insights_registry_key_path }}" -Force
354+ New-ItemProperty -Path "${{ env.insights_registry_key_path }}" -Name "${{ env.insights_registry_value_name }}" -Value "${{ matrix.ubuntu-insights-flag }}" -PropertyType DWord -Force
355+
356+ - name: Prepare WSL instance and cloud-init
357+ shell: powershell
358+ env:
359+ WSL_UTF8: "1" # Recommended otherwise it's hard to read wsl output on Github
360+ # Just to skip the interactive session
361+ cloudinit: |
362+ #cloud-config
363+ users:
364+ - name: u
365+ gecos: Ubuntu User
366+ groups: [adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev]
367+ sudo: ALL=(ALL) NOPASSWD:ALL
368+ shell: /bin/bash
369+ packages: [hello]
370+ write_files:
371+ - path: /etc/wsl.conf
372+ append: true
373+ content: |
374+ [user]
375+ default=u
376+ run: |
377+ if ("${{ matrix.ubuntu-insights-state }}" -ne "skip") {
378+ $env:cloudinit += "`- path: /home/u/.config/ubuntu-insights/wsl_setup-consent.toml`n owner: u:u`n permissions: '0755'`n defer: true`n content: |`n consent_state = ${{ matrix.ubuntu-insights-state }}"
379+ }
380+
381+ # No launch so we can install the deb before running the OOBE
382+ wsl --install --no-launch --from-file "images.wsl" --name "${{ env.instance }}"
383+ wsl -d "${{ env.instance }}" -u root -- ls -l "./ci-artifacts/wsl-setup_*/"
384+ wsl -d "${{ env.instance }}" -u root -- dpkg -i "./ci-artifacts/wsl-setup_*/wsl-setup_*.deb"
385+
386+ # In case cloud-init fails we don't get stuck on endless prompting.
387+ wsl -d "${{ env.instance }}" -u root -- adduser --quiet --gecos '' --disabled-password ubuntu
388+ wsl -d "${{ env.instance }}" -u root -- usermod ubuntu -aG "adm,cdrom,sudo,dip,plugdev"
389+ wsl -d "${{ env.instance }}" -u root -- cloud-init status --wait
390+ wsl -d "${{ env.instance }}" -u root -- bash -ec "rm /etc/cloud/cloud-init.disabled || true"
391+ wsl -d "${{ env.instance }}" -u root -- bash -ec "echo -e '#!/bin/bash\necho Exiting because the profile says so\nexit 0' > ${{ env.profile_script }}"
392+ wsl -d "${{ env.instance }}" -u root -- chmod '0777' ${{ env.profile_script }}
393+ wsl -d "${{ env.instance }}" -u root -- cloud-init clean --logs
394+ wsl --shutdown
395+
396+ # Write the cloud-init user-data contents.
397+ $cloudinitdir = New-Item -ItemType "Directory" -Path "${env:UserProfile}\.cloud-init\" -Force
398+ # TODO: Investigate why UTF-8 BOM seems to break cloud-init.
399+ $filePath="${cloudinitdir}\${{ env.instance }}.user-data"
400+ $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
401+ [System.IO.File]::WriteAllLines($filePath, $env:cloudinit, $utf8NoBomEncoding)
402+ Write-Output "Testing wsl-setup with cloud-init user data at ${cloudinitdir} :"
403+ Get-Content "${cloudinitdir}\${{ env.instance }}.user-data"
404+
405+ - name: Test initial setup
406+ timeout-minutes: 10
407+ shell: powershell
408+ run: |
409+ wsl -d "${{ env.instance }}" # Will run the wsl-setup script.
410+
411+ wsl -d "${{ env.instance }}" -u root -- bash -ec "rm ${{ env.profile_script }} || true"
412+ wsl -d "${{ env.instance }}" -u root -- bash -ec "cat /var/log/cloud-init.log || true"
413+ wsl --terminate ${{ env.instance }}
414+
415+ - name: Determine expected consent state
416+ id: expected-consent
417+ shell: powershell
418+ run: |
419+ $state = "${{ matrix.ubuntu-insights-state }}"
420+ $flag = "${{ matrix.ubuntu-insights-flag }}"
421+
422+ if ($state -eq "true" -or $state -eq "false") {
423+ $expected = $state
424+ } elseif ($flag -eq "1") {
425+ $expected = "true"
426+ } elseif ($flag -eq "0") {
427+ $expected = "false"
428+ } else {
429+ Write-Error "Internal test setup error: invalid state='$state' and flag='$flag'. Expected state to be 'true' or 'false', or flag to be '0' or '1'."
430+ Exit 1
431+ }
432+
433+ Add-Content -Path $env:GITHUB_OUTPUT -Value "expected=$expected"
434+
435+ - name: Validate instance state
436+ uses: ./.github/actions/validate-wsl
437+ with:
438+ instance: ${{ env.instance }}
439+ working-dir: ${{ env.instance_working_dir }}
440+ expected-user: u
441+ expected-internal-consent-state: ${{ steps.expected-consent.outputs.expected }}
442+ expected-registry-consent-state: ${{ matrix.ubuntu-insights-flag }}
443+
444+ - name: Run hello package
445+ uses: ubuntu/WSL/.github/actions/wsl-bash@main
446+ with:
447+ distro: ${{ env.instance }}
448+ working-dir: ${{ env.instance_working_dir }}
449+ exec: |
450+ if ! hello; then
451+ echo "::error:: Failed to execute the hello program that should have been installed by cloud-init"
452+ exit 1
453+ fi
454+
455+ - name: Clean up # Probably not necessary since we're leveraging ephemeral GH's runners.
456+ if: always()
457+ shell: powershell
458+ run: |
459+ wsl --unregister ${{ env.instance }}
460+
461+ interactive-test:
462+ needs: [build-deb, cache-img]
463+ name: "Interactive Expect tests"
464+ runs-on: windows-2025 # WSL is preinstalled and working
465+ env:
466+ username: test-ubuntu-user
467+ password: TestPass123!
468+ strategy:
469+ fail-fast: false
470+ matrix:
471+ include:
472+ # Basic interactive consent
473+ - consent: "default"
474+ consent-registry-flag: "skip"
475+ config-state: "skip"
476+ expected-internal: "true"
477+ expected-registry: "1"
478+ - consent: "yes"
479+ consent-registry-flag: "skip"
480+ config-state: "skip"
481+ expected-internal: "true"
482+ expected-registry: "1"
483+ - consent: "no"
484+ consent-registry-flag: "skip"
485+ config-state: "skip"
486+ expected-internal: "false"
487+ expected-registry: "0"
488+ # Handle null states
489+ - consent: "yes"
490+ consent-registry-flag: "123"
491+ config-state: "null"
492+ expected-internal: "true"
493+ expected-registry: "1"
494+ # Config state takes precedence over registry flag
495+ - consent: "skip"
496+ consent-registry-flag: "1"
497+ config-state: "false"
498+ expected-internal: "false"
499+ expected-registry: "1"
500+ - consent: "skip"
501+ consent-registry-flag: "0"
502+ config-state: "true"
503+ expected-internal: "true"
504+ expected-registry: "0"
505+ # Registry flag takes precedence over interactive prompt
506+ - consent: "skip"
507+ consent-registry-flag: "1"
508+ config-state: "skip"
509+ expected-internal: "true"
510+ expected-registry: "1"
511+ - consent: "skip"
512+ consent-registry-flag: "0"
513+ config-state: "null"
514+ expected-internal: "false"
515+ expected-registry: "0"
516+
517+ steps:
518+ - name: Check out repository
519+ uses: actions/checkout@v6
520+
521+ - name: Download artifacts
522+ uses: actions/download-artifact@v7
523+ with:
524+ path: ci-artifacts
525+
526+ - name: Get WSL image
527+ uses: ./.github/actions/get-wsl-image
528+ with:
529+ output_path: images.wsl
530+
531+ - name: Prepare Ubuntu Insights consent registry key
532+ if: matrix.consent-registry-flag != 'skip'
533+ shell: powershell
534+ run: |
535+ New-Item -Path "${{ env.insights_registry_key_path }}" -Force
536+ New-ItemProperty -Path "${{ env.insights_registry_key_path }}" -Name "${{ env.insights_registry_value_name }}" -Value "${{ matrix.consent-registry-flag }}" -PropertyType DWord -Force
537+
538+ - name: Install WSL instance
539+ shell: powershell
540+ env:
541+ WSL_UTF8: "1"
542+ run: |
543+ wsl --install --no-launch --from-file "images.wsl" --name "${{ env.instance }}"
544+
545+ - name: Install wsl-setup and test dependencies
546+ shell: powershell
547+ env:
548+ WSL_UTF8: "1"
549+ run: |
550+ # Install the deb package
551+ wsl -d "${{ env.instance }}" -u root -- dpkg -i "./ci-artifacts/wsl-setup_*/wsl-setup_*.deb"
552+ # Install expect
553+ wsl -d "${{ env.instance }}" -u root -- apt-get update
554+ wsl -d "${{ env.instance }}" -u root -- apt-get install -y expect
555+
556+ - name: Configure consent file
557+ if: matrix.config-state != 'skip'
558+ shell: powershell
559+ env:
560+ WSL_UTF8: "1"
561+ run: |
562+ $configContent = "consent_state = ${{ matrix.config-state }}"
563+ wsl -d "${{ env.instance }}" -u root -- bash -c "mkdir -p /etc/skel/.config/ubuntu-insights"
564+ wsl -d "${{ env.instance }}" -u root -- bash -c "echo '$configContent' > /etc/skel/.config/ubuntu-insights/wsl_setup-consent.toml"
565+
566+ - name: Reset WSL instance state
567+ shell: powershell
568+ env:
569+ WSL_UTF8: "1"
570+ run: |
571+ wsl -d "${{ env.instance }}" -u root -- cloud-init status --wait
572+ wsl -d "${{ env.instance }}" -u root -- bash -ec "rm /etc/cloud/cloud-init.disabled || true"
573+ wsl -d "${{ env.instance }}" -u root -- cloud-init clean --logs
574+ wsl --shutdown
575+
576+ - name: Run expect test
577+ shell: powershell
578+ env:
579+ WSL_UTF8: "1"
580+ timeout-minutes: 10
581+ run: |
582+ # Copy expect script to instance
583+ wsl -d "${{ env.instance }}" -u root -- cp "test/e2e/setup.expect" "/tmp/setup.expect"
584+
585+ # Run the expect script
586+ wsl -d "${{ env.instance }}" -u root -- expect /tmp/setup.expect "${{ env.username }}" "${{ env.password }}" "${{ matrix.consent }}"
587+
588+ - name: Validate instance state
589+ uses: ./.github/actions/validate-wsl
590+ with:
591+ instance: ${{ env.instance }}
592+ working-dir: ${{ env.instance_working_dir }}
593+ expected-user: ${{ env.username }}
594+ expected-internal-consent-state: ${{ matrix.expected-internal }}
595+ expected-registry-consent-state: ${{ matrix.expected-registry }}
596+
597+ - name: Clean up # Probably not necessary since we're leveraging ephemeral GH's runners.
598+ if: always()
599+ shell: powershell
600+ run: |
601+ wsl --unregister ${{ env.instance }}
602diff --git a/.github/workflows/shellcheck.yaml b/.github/workflows/shellcheck.yaml
603new file mode 100644
604index 0000000..77cf70a
605--- /dev/null
606+++ b/.github/workflows/shellcheck.yaml
607@@ -0,0 +1,27 @@
608+name: ShellCheck QA
609+
610+on:
611+ push:
612+ branches: [main]
613+ pull_request:
614+permissions: {}
615+
616+env:
617+ EXTENSIONLESS_SCRIPTS: "wsl-setup wait-for-cloud-init ubuntu-insights"
618+
619+jobs:
620+ shellcheck:
621+ name: Run ShellCheck
622+ runs-on: ubuntu-24.04
623+ steps:
624+ - name: Checkout repository
625+ uses: actions/checkout@v6
626+
627+ - name: Find and Run ShellCheck
628+ run: |
629+ exec shellcheck -f gcc $(
630+ find . \
631+ \( -name '*.sh' -o -name '*.bash' -o -name '*.zsh' \
632+ $(for f in $EXTENSIONLESS_SCRIPTS; do echo -o -name "$f"; done) \) \
633+ -type f
634+ )
635diff --git a/.shellcheckrc b/.shellcheckrc
636new file mode 100644
637index 0000000..8226afb
638--- /dev/null
639+++ b/.shellcheckrc
640@@ -0,0 +1 @@
641+external-sources=true
642diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
643new file mode 100644
644index 0000000..67c33a0
645--- /dev/null
646+++ b/CONTRIBUTING.md
647@@ -0,0 +1,102 @@
648+# Contributing to `wsl-setup`
649+
650+A big welcome and thank you for considering contributing to `wsl-setup` and Ubuntu! It’s people like you that make it a reality for users in our community.
651+
652+Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing this project. In return, we will reciprocate that respect by addressing your issue, assessing changes, and helping you finalize your pull requests.
653+
654+These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
655+
656+## Quick links
657+
658+* [Code of conduct](#code-of-conduct)
659+* [Getting started](#getting-started)
660+* [Issues](#issues)
661+* [Pull requests](#pull-requests)
662+* [Contributing to the code](#contributing-to-the-code)
663+* [Contributor license agreement](#contributor-license-agreement)
664+* [Getting help](#getting-help)
665+
666+## Code of conduct
667+
668+We take our community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct](https://ubuntu.com/community/code-of-conduct).
669+
670+## Getting started
671+
672+Contributions are made to this project via issues and pull requests (PRs). A few general guidelines that cover both:
673+
674+* To report security vulnerabilities, please use the advisories page of the repository and not a public bug report. Please use [GitHub security advisories](https://github.com/ubuntu/wsl-setup/security/advisories/new) [launchpad private bugs](https://bugs.launchpad.net/ubuntu/+source/wsl-setup/+filebug) which is monitored by our security team. On an Ubuntu machine, it’s best to use `ubuntu-bug wsl-setup` to collect relevant information.
675+* Search for existing issues and PRs on this repository before creating your own.
676+* We work hard to makes sure issues are handled in a timely manner but, depending on the impact, it could take a while to investigate the root cause. A friendly ping in the comment thread to the submitter or a contributor can help draw attention if your issue is blocking.
677+* If you've never contributed before, see [this Ubuntu community page](https://ubuntu.com/community/docs/contribute) for resources and tips on how to get started.
678+
679+### Issues
680+
681+Issues should be used to report problems with the software, request a new feature, or to discuss potential changes before a PR is created. When you create a new issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate.
682+
683+If you find an issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter.
684+
685+### Pull requests
686+
687+PRs to our project are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should:
688+
689+* Only fix/add the functionality in question **OR** address wide-spread whitespace/style issues, not both.
690+* Add unit or integration tests for fixed or changed functionality.
691+* Address a single concern in the least number of changed lines as possible.
692+* Include documentation in the repo or on our [docs site](https://documentation.ubuntu.com/wsl).
693+* Be accompanied by a complete pull request template (loaded automatically when a PR is created).
694+
695+For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open an issue to discuss your proposal first. This is not required but can save time creating and reviewing changes.
696+
697+In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr)
698+
699+1. Fork the repository to your own GitHub account
700+1. Clone the project to your machine
701+1. Create a branch locally with a succinct but descriptive name
702+1. Commit changes to the branch
703+1. Following any formatting and testing guidelines specific to this repo
704+1. Push changes to your fork
705+1. Open a PR in our repository and follow the PR template so that we can efficiently review the changes.
706+
707+> PRs will trigger unit and integration tests with and without race detection, linting and formatting validations, static and security checks, freshness of generated files verification. All the tests must pass before merging in main branch.
708+
709+Once merged to the main branch, `po` files and any documentation change will be automatically updated. Those are thus not necessary in the pull request itself to minimize diff review.
710+
711+## Contributing to the code
712+
713+### Building and running
714+
715+This project is packaged as a Debian package.
716+
717+To build from source, run the following command from the root folder of the repository.
718+
719+```bash
720+debuild
721+```
722+
723+The output will be available in the parent directory.
724+
725+Please see the [Ubuntu Packaging Guide](https://canonical-ubuntu-packaging-guide.readthedocs-hosted.com) for more details.
726+
727+### About the test suite
728+
729+The project includes a comprehensive test suite made of unit and integration tests. All the tests must pass before the review is considered. If you have troubles with the test suite, feel free to mention it on your PR description.
730+
731+Some of our tests utilize [expect](https://linux.die.net/man/1/expect) and supplementary shell scripts. Please see `test/` as well as the workflows and actions present in `.github/`.
732+
733+The test suite must pass before merging the PR to our main branch. Any new feature, change or fix must be covered by corresponding tests.
734+
735+### Code style
736+
737+This project utilizes [ShellCheck](https://github.com/koalaman/shellcheck) for static analysis of shell scripts.
738+
739+## Contributor license agreement
740+
741+It is required to sign the [Contributor License Agreement](https://ubuntu.com/legal/contributors) in order to contribute to this project.
742+
743+An automated test is executed on PRs to check if it has been accepted.
744+
745+This project is covered by [GPL-3.0](LICENSE).
746+
747+## Getting help
748+
749+Join us in the [Ubuntu Community](https://discourse.ubuntu.com/c/project/wsl/27) and post your question there with a descriptive tag.
750diff --git a/SECURITY.md b/SECURITY.md
751new file mode 100644
752index 0000000..e54ba42
753--- /dev/null
754+++ b/SECURITY.md
755@@ -0,0 +1,53 @@
756+# Security policy
757+
758+## Supported versions
759+
760+`wsl-setup` is distributed via the Ubuntu archive as the [`wsl-setup` package](https://launchpad.net/ubuntu/+source/wsl-setup).
761+
762+Currently, we provide security updates for supported LTS releases of Ubuntu on WSL.
763+
764+If you are unsure of the Ubuntu version you are using, please run the following command in a
765+WSL terminal running your Ubuntu distro:
766+
767+```bash
768+lsb_release -a
769+```
770+
771+## Reporting a vulnerability
772+
773+If you discover a security vulnerability within this repository, we encourage
774+responsible disclosure. Please report any security issues to help us keep
775+`wsl-setup` and Ubuntu on WSL secure for everyone.
776+
777+### Private vulnerability reporting
778+
779+The most straightforward way to report a security vulnerability is through
780+[GitHub](https://github.com/ubuntu/wsl-setup/security/advisories/new). For detailed
781+instructions, please review the
782+[Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
783+documentation. This method enables you to communicate vulnerabilities directly
784+and confidentially with the `wsl-setup` maintainers.
785+
786+The project's admins will be notified of the issue and will work with you to
787+determine whether the issue qualifies as a security issue and, if so, in which
788+component. We will then handle finding a fix, getting a CVE assigned and
789+coordinating the release of the fix to the various Linux distributions.
790+
791+The [Ubuntu Security disclosure and embargo policy](https://ubuntu.com/security/disclosure-policy)
792+contains more information about what you can expect when you contact us, and what we expect from you.
793+
794+#### Steps to report a vulnerability on GitHub
795+
796+1. Go to the [Security Advisories Page](https://github.com/ubuntu/wsl-setup/security/advisories) of the `wsl-setup` repository.
797+2. Click "Report a Vulnerability"
798+3. Provide detailed information about the vulnerability, including steps to reproduce, affected versions, and potential impact.
799+
800+## Security resources
801+
802+* [Canonical's Security Site](https://ubuntu.com/security)
803+* [Ubuntu Security disclosure and embargo policy](https://ubuntu.com/security/disclosure-policy)
804+* [Ubuntu Security Notices](https://ubuntu.com/security/notices)
805+* [Ubuntu on WSL documentation](https://documentation.ubuntu.com/wsl/en/latest/)
806+
807+If you have any questions regarding security vulnerabilities, please reach out
808+to the maintainers through the aforementioned channels.
809diff --git a/debian/changelog b/debian/changelog
810index 628d1d5..b807dd6 100644
811--- a/debian/changelog
812+++ b/debian/changelog
813@@ -1,3 +1,15 @@
814+wsl-setup (0.6.1) resolute; urgency=medium
815+
816+ [ Kat Kuo]
817+ * Added ubuntu-insights integration
818+ * Added integration tests in upstream CI
819+
820+ [Carlos Nihelton]
821+ * Case-fold the Windows username before sanitization (LP: #2122047)
822+ * Remove the systemd-timesyncd.service override that would enable it in WSL
823+
824+ -- Kat Kuo <kat.kuo@canonical.com> Tue, 25 Nov 2025 19:56:56 -0500
825+
826 wsl-setup (0.5.10) questing; urgency=medium
827
828 * Fix Ubuntu on WSL install fails on non-ASCII usernames (LP: #2118617)
829diff --git a/debian/control b/debian/control
830index e20198d..d54931f 100644
831--- a/debian/control
832+++ b/debian/control
833@@ -19,5 +19,6 @@ Depends: adduser,
834 systemd,
835 ${shlibs:Depends},
836 ${misc:Depends},
837+Recommends: ubuntu-insights,
838 Description: ${source:Synopsis}
839 ${source:Extended-Description}
840diff --git a/debian/install b/debian/install
841index 8490e53..d27180f 100644
842--- a/debian/install
843+++ b/debian/install
844@@ -1,6 +1,7 @@
845 cloud/ etc/
846 update-motd.d/99-wsl etc/update-motd.d/
847-systemd/ lib/
848+systemd/ usr/lib/
849+ubuntu-insights.sh usr/lib/wsl/
850 wsl-setup usr/lib/wsl/
851 wait-for-cloud-init usr/lib/wsl/
852 wsl/ usr/share/
853diff --git a/debian/rules b/debian/rules
854index 80b3fe1..ed58acc 100755
855--- a/debian/rules
856+++ b/debian/rules
857@@ -3,4 +3,3 @@
858
859 %:
860 dh $@
861-
862diff --git a/systemd/system/systemd-timesyncd.service.d/wsl.conf b/systemd/system/systemd-timesyncd.service.d/wsl.conf
863deleted file mode 100644
864index bcb2e53..0000000
865--- a/systemd/system/systemd-timesyncd.service.d/wsl.conf
866+++ /dev/null
867@@ -1,7 +0,0 @@
868-# Enable timesyncd on WSL machines
869-# so WSL clock is synced on resume from suspend of the host.
870-
871-[Unit]
872-ConditionVirtualization=
873-ConditionVirtualization=|!container
874-ConditionVirtualization=|wsl
875diff --git a/test/basic-assertions.sh b/test/basic-assertions.sh
876new file mode 100755
877index 0000000..69fc1cb
878--- /dev/null
879+++ b/test/basic-assertions.sh
880@@ -0,0 +1,30 @@
881+#!/bin/bash
882+# This is a set of basic assertions to verify a WSL instance is correctly set up.
883+# This should run inside a WSL instance or machine prepared for testing purposes.
884+# It requires installing the wsl-setup Debian package to assert on its results.
885+# The expected default user can be passed as the first argument.
886+
887+EXPECTED_USER=${1:-u}
888+
889+brandingdir="/usr/share/wsl"
890+if [[ ! -r "${brandingdir}/ubuntu.ico" ]]; then
891+ echo "::error:: Missing Ubuntu icon in the $brandingdir directory."
892+ ls "${brandingdir}"
893+ exit 1
894+fi
895+
896+if [[ ! -r "${brandingdir}/terminal-profile.json" ]]; then
897+ echo "::error:: Missing terminal profile fragment in the $brandingdir directory."
898+ ls "${brandingdir}"
899+ exit 2
900+fi
901+
902+if [[ $(id -u) == 0 ]]; then
903+ echo "::error:: Default user shouldn't be root"
904+ exit 3
905+fi
906+
907+if [[ $(whoami) != "$EXPECTED_USER" ]]; then
908+ echo "::error:: Default user doesn't match expected user '$EXPECTED_USER'."
909+ exit 4
910+fi
911diff --git a/test/e2e/setup.expect b/test/e2e/setup.expect
912new file mode 100644
913index 0000000..76ff5cc
914--- /dev/null
915+++ b/test/e2e/setup.expect
916@@ -0,0 +1,113 @@
917+#!/usr/bin/expect -f
918+
919+set timeout 240
920+
921+set username [lindex $argv 0]
922+set password [lindex $argv 1]
923+set consent [lindex $argv 2]
924+
925+if {$username == ""} {
926+ puts "Error: Username argument is required."
927+ exit 1
928+}
929+
930+if {$password == ""} {
931+ puts "Error: Password argument is required."
932+ exit 1
933+}
934+
935+if {$consent == ""} {
936+ puts "Error: Consent argument is required (yes, no, skip, default)."
937+ exit 1
938+}
939+
940+# Spawn the setup script
941+spawn /usr/lib/wsl/wsl-setup
942+
943+# Handle Username
944+expect {
945+ "Create a default Unix user account:" {
946+ # Send Ctrl-U to clear the pre-filled default username
947+ send "\025"
948+ send "$username\r"
949+ }
950+ timeout {
951+ puts "Error: Timeout waiting for username prompt."
952+ exit 1
953+ }
954+}
955+
956+# Handle Password
957+expect {
958+ "New password:" {
959+ send "$password\r"
960+ }
961+ timeout {
962+ puts "Error: Timeout waiting for password prompt."
963+ exit 1
964+ }
965+}
966+
967+expect {
968+ "Retype new password:" {
969+ send "$password\r"
970+ }
971+ timeout {
972+ puts "Error: Timeout waiting for retype password prompt."
973+ exit 1
974+ }
975+}
976+
977+# Handle Consent
978+if {$consent == "skip"} {
979+ # If we expect to skip, we should NOT see the consent prompt.
980+ expect {
981+ "Would you like to opt-in to platform metrics collection (Y/n)? To see an example of the data collected, enter 'e'." {
982+ puts "Error: Consent prompt appeared but should have been skipped."
983+ exit 1
984+ }
985+ eof {
986+ # Script finished successfully
987+ }
988+ timeout {
989+ puts "Error: Timeout waiting for EOF (skip scenario)."
990+ exit 1
991+ }
992+ }
993+} else {
994+ expect {
995+ "\\\[Y/n/e\\\]: " {
996+ if {$consent == "default"} {
997+ send "\r"
998+ } else {
999+ # Send Ctrl-U to clear the pre-filled default consent value
1000+ send "\025"
1001+
1002+ if {$consent == "yes"} {
1003+ send "y\r"
1004+ } elseif {$consent == "no"} {
1005+ send "n\r"
1006+ } else {
1007+ puts "Error: Invalid consent value '$consent'. Use yes, no, skip, or default."
1008+ exit 1
1009+ }
1010+ }
1011+ }
1012+ timeout {
1013+ puts "Error: Timeout waiting for consent prompt."
1014+ exit 1
1015+ }
1016+ eof {
1017+ puts "Error: Unexpected EOF while waiting for consent prompt."
1018+ exit 1
1019+ }
1020+ }
1021+ expect eof
1022+}
1023+
1024+# Check exit status
1025+lassign [wait] pid spawnid os_error_flag value
1026+if {$value != 0} {
1027+ puts "Error: wsl-setup failed with exit code $value"
1028+ exit $value
1029+}
1030diff --git a/test/systemd-assertions.sh b/test/systemd-assertions.sh
1031new file mode 100755
1032index 0000000..8827d27
1033--- /dev/null
1034+++ b/test/systemd-assertions.sh
1035@@ -0,0 +1,33 @@
1036+#!/bin/bash
1037+# This is a set of basic assertions to verify systemd specific conditions in a newly set up WSL instance.
1038+# This should run inside a WSL instance or machine prepared for testing purposes.
1039+# It requires installing the wsl-setup Debian package to assert on its results.
1040+
1041+if [[ $(LANG=C systemctl is-system-running) != "running" ]]; then
1042+ systemctl --failed
1043+ exit 1
1044+fi
1045+
1046+if [[ $(LANG=C systemctl is-active multipathd.service) != "inactive" ]]; then
1047+ echo "::error:: Unit multipathd.service should have been disabled by the multipathd.service.d/container.conf override"
1048+ systemctl status multipathd.service
1049+ exit 2
1050+fi
1051+
1052+# It's been a while since WSL kernel implemented a patch specifically for time sync with Hyper-V.
1053+# With that NTS clients inside WSL instances can cause more trouble if they don't sync with the same
1054+# source as Windows does. So we're no longer overriding this systemd unit, let be the defaults.
1055+# Let's not worry about chrony just yet.
1056+nts_unit="systemd-timesyncd.service"
1057+if systemctl is-enabled "${nts_unit}"; then
1058+ if [[ $(LANG=C systemctl is-active "${nts_unit}") != "inactive" ]]; then
1059+ echo "::error:: Unit ${nts_unit} should be disabled by default."
1060+ systemctl status ${nts_unit}
1061+ exit 3
1062+ fi
1063+fi
1064+
1065+if [[ ! -r "/etc/cloud/cloud-init.disabled" ]]; then
1066+ echo "::error:: Missing cloud-init.disabled marker file"
1067+ exit 4
1068+fi
1069diff --git a/test/ubuntu-insights-assertions.sh b/test/ubuntu-insights-assertions.sh
1070new file mode 100755
1071index 0000000..f1c6bef
1072--- /dev/null
1073+++ b/test/ubuntu-insights-assertions.sh
1074@@ -0,0 +1,33 @@
1075+#!/bin/bash
1076+# This is a set of basic assertions partially validating Ubuntu Insights consent setup in WSL.
1077+# This should run inside a WSL instance or machine prepared for testing purposes.
1078+# It requires installing the wsl-setup Debian package to assert on its results.
1079+# The expected consent state can be passed as the first argument: true or false.
1080+
1081+EXPECTED_CONSENT=${1}
1082+
1083+if [[ "$EXPECTED_CONSENT" != "true" && "$EXPECTED_CONSENT" != "false" ]]; then
1084+ echo "::error:: Expected first argument to be 'true' or 'false', got '$EXPECTED_CONSENT'"
1085+ exit 1
1086+fi
1087+
1088+if ! ubuntu-insights consent wsl_setup | grep -q "wsl_setup: $EXPECTED_CONSENT"; then
1089+ echo "::error:: Ubuntu-Insights Consent state assertion: Expected 'wsl_setup: $EXPECTED_CONSENT'"
1090+ exit 1
1091+fi
1092+
1093+# Verify that a report was created for the wsl_setup source.
1094+# Use max int32 for period (2147483647) to ensure we cover the timestamp of the existing report.
1095+OUTPUT=$(ubuntu-insights collect --period 2147483647 --dry-run wsl_setup <(echo "{}") 2>&1 >/dev/null)
1096+EXIT_CODE=$?
1097+
1098+if [ $EXIT_CODE -eq 0 ]; then
1099+ echo "::error:: Expected non-zero exit code from ubuntu-insights collect, got 0."
1100+ exit 1
1101+fi
1102+
1103+if ! echo "$OUTPUT" | grep -q "report already exists for this period"; then
1104+ echo "::error:: Expected 'report already exists for this period' error from ubuntu-insights collect."
1105+ echo "Output was: $OUTPUT"
1106+ exit 1
1107+fi
1108diff --git a/ubuntu-insights.sh b/ubuntu-insights.sh
1109new file mode 100755
1110index 0000000..0d1a5c8
1111--- /dev/null
1112+++ b/ubuntu-insights.sh
1113@@ -0,0 +1,143 @@
1114+#!/bin/bash
1115+# Manages Ubuntu Insights consent during WSL setup.
1116+# It prioritizes any existing local consent settings, then the Windows registry.
1117+# If no consent is found, it prompts the user for consent.
1118+set -euo pipefail
1119+
1120+sources=("linux" "wsl_setup" "ubuntu_release_upgrader")
1121+
1122+registry_key_path="HKCU:\Software\Canonical\Ubuntu"
1123+registry_value_name="UbuntuInsightsConsent"
1124+
1125+# List of regular users
1126+readarray -t users < <(getent passwd | grep -Ev '/nologin|/false|/sync' | awk -F: '$3 >= 1000 { print $1 }')
1127+
1128+function ask_question() {
1129+ # Only ask if we are in an interactive terminal
1130+ if [ ! -t 0 ]; then
1131+ return
1132+ fi
1133+
1134+ local choice_val=""
1135+ local view_choice=""
1136+ while true; do
1137+ echo "Would you like to opt-in to platform metrics collection (Y/n)? To see an example of the data collected, enter 'e'."
1138+ read -rep "[Y/n/e]: " -i "y" view_choice
1139+
1140+ if [[ $view_choice =~ ^[Ee]$ ]]; then
1141+ ubuntu-insights collect -df 2>/dev/null
1142+ continue
1143+ elif [[ $view_choice =~ ^[Yy]$ ]]; then
1144+ choice_val=1
1145+ break
1146+ elif [[ $view_choice =~ ^[Nn]$ ]]; then
1147+ choice_val=0
1148+ break
1149+ else
1150+ echo "Invalid input (Y/n/e)."
1151+ fi
1152+ done
1153+
1154+ apply_consent "$choice_val"
1155+ set_consent_registry "$choice_val" >/dev/null || true
1156+}
1157+
1158+function apply_consent() {
1159+ local consent="$1"
1160+
1161+ for user in "${users[@]}"; do
1162+ for source in "${sources[@]}"; do
1163+ # shellcheck disable=SC2016 # Intentional due to how we pass parameters to a su command
1164+ su "$user" -c 'ubuntu-insights consent "$0" -s="$1" > /dev/null' -- "$source" "$([[ $consent -eq 1 ]] && echo true || echo false)"
1165+ done
1166+ done
1167+}
1168+
1169+function read_consent_registry() {
1170+ local consent_value=""
1171+ consent_value=$(powershell.exe -NoProfile -Command "& {
1172+ param(\$Path, \$Name)
1173+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
1174+ try {
1175+ return (Get-ItemProperty -Path \$Path -Name \$Name -ErrorAction Stop).\$Name
1176+ }
1177+ catch {
1178+ return \"\"
1179+ }
1180+ }" -Path "${registry_key_path}" -Name "${registry_value_name}" 2>/dev/null) || true
1181+ # strip control chars like \r and \n
1182+ echo "${consent_value//[[:cntrl:]]/}"
1183+}
1184+
1185+function set_consent_registry() {
1186+ local consent="$1"
1187+ powershell.exe -NoProfile -Command "& {
1188+ param(\$Path, \$Name, \$Consent)
1189+ if (-not (Test-Path -Path \$Path)) {
1190+ New-Item -Path \$Path -Force | Out-Null
1191+ }
1192+ New-ItemProperty -Path \$Path -Name \$Name -Value \$Consent -PropertyType DWord -Force
1193+ }" -Path "${registry_key_path}" -Name "${registry_value_name}" -Consent "${consent}" 2>/dev/null || true
1194+}
1195+
1196+function check_local_consent() {
1197+ # If any of the users has the wsl_setup consent set, we consider that consent is set locally
1198+ for user in "${users[@]}"; do
1199+ # Check wsl_setup source, if exit code is 0, consent is set
1200+ if su "$user" -c 'ubuntu-insights consent wsl_setup' >/dev/null 2>&1; then
1201+ return 0
1202+ fi
1203+ done
1204+ return 1
1205+}
1206+
1207+function collect() {
1208+ # Collect insights and upload in background for all users
1209+ for user in "${users[@]}"; do
1210+ if su "$user" -c 'echo "{}" | ubuntu-insights collect wsl_setup /dev/stdin -f > /dev/null 2>&1'; then
1211+ su "$user" -c 'nohup ubuntu-insights upload wsl_setup -rf > /dev/null 2>&1 &'
1212+ fi
1213+ done
1214+}
1215+
1216+# Check if ubuntu-insights is installed
1217+if ! command -v ubuntu-insights >/dev/null 2>&1; then
1218+ # shellcheck disable=SC2317 # For when not run in a function
1219+ return 0 2>/dev/null || exit 0
1220+fi
1221+
1222+# Skip if no users found
1223+if [ "${#users[@]}" -eq 0 ]; then
1224+ # shellcheck disable=SC2317 # For when not run in a function
1225+ return 0 2>/dev/null || exit 0
1226+fi
1227+
1228+# Check if consent is already set locally
1229+if check_local_consent; then
1230+ collect
1231+ # shellcheck disable=SC2317 # For when not run in a function
1232+ return 0 2>/dev/null || exit 0
1233+fi
1234+
1235+# Check if we have a stored consent value in the Windows registry.
1236+consent_value=$(read_consent_registry)
1237+if [[ "$consent_value" =~ ^[01]$ ]]; then
1238+ apply_consent "$consent_value"
1239+ collect
1240+ # shellcheck disable=SC2317 # For when not run in a function
1241+ return 0 2>/dev/null || exit 0
1242+fi
1243+
1244+# Failed to read consent from the Windows registry, ask the user.
1245+echo "Help improve Ubuntu!
1246+
1247+You can share anonymous data with the Ubuntu development team so we can improve your experience.
1248+If you agree, we will collect and report anonymous hardware and system information.
1249+This information can't be used to identify a single machine.
1250+For legal details, please visit: https://ubuntu.com/legal/systems-information-notice
1251+
1252+We will save your answer to Windows and will only ask you once.
1253+"
1254+ask_question
1255+
1256+collect
1257diff --git a/wait-for-cloud-init b/wait-for-cloud-init
1258index 96f9821..1f3ee40 100644
1259--- a/wait-for-cloud-init
1260+++ b/wait-for-cloud-init
1261@@ -7,6 +7,6 @@
1262 set -euo pipefail
1263
1264 if status=$(LANG=C systemctl is-system-running 2>/dev/null) || [ "${status}" != "offline" ] && systemctl is-enabled --quiet cloud-init-local.service 2>/dev/null; then
1265- cloud-init status --wait > /dev/null 2>&1 || true
1266- touch /etc/cloud/cloud-init.disabled || true
1267+ cloud-init status --wait >/dev/null 2>&1 || true
1268+ touch /etc/cloud/cloud-init.disabled || true
1269 fi
1270diff --git a/wsl-setup b/wsl-setup
1271index a8f698f..bdb712b 100755
1272--- a/wsl-setup
1273+++ b/wsl-setup
1274@@ -3,122 +3,127 @@ set -euo pipefail
1275
1276 # command_not_found_handle is a noop function that prevents printing error messages if WSL interop is disabled.
1277 function command_not_found_handle() {
1278- :
1279+ :
1280 }
1281
1282 # get_first_interactive_uid returns first interactive non system user uid with uid >=1000.
1283 function get_first_interactive_uid() {
1284- getent passwd | egrep -v '/nologin|/false|/sync' | sort -t: -k3,3n | awk -F: '$3 >= 1000 { print $3; exit }'
1285+ getent passwd | grep -E -v '/nologin|/false|/sync' | sort -t: -k3,3n | awk -F: '$3 >= 1000 { print $3; exit }'
1286 }
1287
1288 # create_regular_user prompts user for a username and assign default WSL permissions.
1289 # First argument is the prefilled username.
1290 function create_regular_user() {
1291- local default_username="${1}"
1292-
1293- local valid_username_regex='^[a-z_][a-z0-9_-]*$'
1294- local DEFAULT_GROUPS='adm,cdrom,sudo,dip,plugdev'
1295-
1296- # Filter the prefilled username to remove invalid characters.
1297- default_username=$(echo "${default_username}" | sed 's/[^a-z0-9_-]//g')
1298- # It should start with a character or _.
1299- default_username=$(echo "${default_username}" | sed 's/^[^a-z_]//')
1300-
1301- # Ensure a valid username
1302- while true; do
1303- # Prefill the prompt with the Windows username.
1304- read -e -p "Create a default Unix user account: " -i "${default_username}" username
1305-
1306- # Validate the username.
1307- if [[ ! "${username}" =~ ${valid_username_regex} ]]; then
1308- echo "Invalid username. A valid username must start with a lowercase letter or underscore, and can contain lowercase letters, digits, underscores, and dashes."
1309- continue
1310- fi
1311-
1312- # Create the user and change its default groups.
1313- if ! /usr/sbin/adduser --quiet --gecos '' "${username}"; then
1314- echo "Failed to create user '${username}'. Please choose a different name."
1315- continue
1316- fi
1317-
1318- if ! /usr/sbin/usermod "${username}" -aG "${DEFAULT_GROUPS}"; then
1319- echo "Failed to add '${username}' to default groups. Attempting cleanup."
1320- /usr/sbin/deluser --quiet "${username}"
1321- continue
1322- fi
1323-
1324- break
1325- done
1326+ local default_username="${1}"
1327+
1328+ local valid_username_regex='^[a-z_][a-z0-9_-]*$'
1329+ local DEFAULT_GROUPS='adm,cdrom,sudo,dip,plugdev'
1330+
1331+ # Filter the prefilled username to remove invalid characters.
1332+ # Remove all characters except a-z, 0-9, _ and -
1333+ default_username="${default_username//[^a-z0-9_-]/}"
1334+ # Remove leading character if not a-z or _
1335+ default_username="${default_username#[!a-z_]}"
1336+
1337+ # Ensure a valid username
1338+ while true; do
1339+ # Prefill the prompt with the Windows username.
1340+ read -er -p "Create a default Unix user account: " -i "${default_username}" username
1341+
1342+ # Validate the username.
1343+ if [[ ! "${username}" =~ ${valid_username_regex} ]]; then
1344+ echo "Invalid username. A valid username must start with a lowercase letter or underscore, and can contain lowercase letters, digits, underscores, and dashes."
1345+ continue
1346+ fi
1347+
1348+ # Create the user and change its default groups.
1349+ if ! /usr/sbin/adduser --quiet --gecos '' "${username}"; then
1350+ echo "Failed to create user '${username}'. Please choose a different name."
1351+ continue
1352+ fi
1353+
1354+ if ! /usr/sbin/usermod "${username}" -aG "${DEFAULT_GROUPS}"; then
1355+ echo "Failed to add '${username}' to default groups. Attempting cleanup."
1356+ /usr/sbin/deluser --quiet "${username}"
1357+ continue
1358+ fi
1359+
1360+ break
1361+ done
1362 }
1363
1364 # set_user_as_default sets the given username as the default user in the wsl.conf configuration.
1365 # It will only set it if there is no existing default under the [user] section.
1366 function set_user_as_default() {
1367- local username="${1}"
1368+ local username="${1}"
1369
1370- local wsl_conf="/etc/wsl.conf"
1371- touch "${wsl_conf}"
1372+ local wsl_conf="/etc/wsl.conf"
1373+ touch "${wsl_conf}"
1374
1375- # Append [user] section with default if they don't exist.
1376- if ! grep -q "^\[user\]" "${wsl_conf}"; then
1377- echo -e "\n[user]\ndefault=${username}" >> "${wsl_conf}"
1378- return
1379- fi
1380+ # Append [user] section with default if they don't exist.
1381+ if ! grep -q "^\[user\]" "${wsl_conf}"; then
1382+ echo -e "\n[user]\ndefault=${username}" >>"${wsl_conf}"
1383+ return
1384+ fi
1385
1386- # If default is missing from the user section, append it to it.
1387- if ! sed -n '/^\[user\]/,/^\[/{/^\s*default\s*=/p}' "${wsl_conf}" | grep -q .; then
1388- sed -i '/^\[user\]/a\default='"${username}" "${wsl_conf}"
1389- fi
1390+ # If default is missing from the user section, append it to it.
1391+ if ! sed -n '/^\[user\]/,/^\[/{/^\s*default\s*=/p}' "${wsl_conf}" | grep -q .; then
1392+ sed -i '/^\[user\]/a\default='"${username}" "${wsl_conf}"
1393+ fi
1394 }
1395
1396 # powershell_env outputs the contents of PowerShell.exe environment variables $Env:<ARG>
1397 # encoded in UTF-8.
1398 function powershell_env() {
1399- local var="$1"
1400- if [ "$#" != 1 ]; then
1401- echo "powershell_env: expected 1 argument, got $# ."
1402- return 1
1403- fi
1404-
1405- local ret=$(powershell.exe -NoProfile -Command '& {
1406+ local var="$1"
1407+ if [ "$#" != 1 ]; then
1408+ echo "powershell_env: expected 1 argument, got $# ."
1409+ return 1
1410+ fi
1411+
1412+ local ret
1413+ # shellcheck disable=SC2016 # Intentional due to how we use powershell
1414+ ret=$(powershell.exe -NoProfile -Command '& {
1415 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
1416 $Env:'"${var}"'}') 2>/dev/null || true
1417- # strip control chars like \r and \n
1418- ret="${ret%%[[:cntrl:]]}"
1419- echo "$ret"
1420+ # strip control chars like \r and \n
1421+ ret="${ret%%[[:cntrl:]]}"
1422+ echo "$ret"
1423 }
1424
1425 # install_ubuntu_font copies the Ubuntu font into Windows filesystem and register it for the current Windows user.
1426 function install_ubuntu_font() {
1427- local local_app_data=$(powershell_env "LocalAppData")
1428- if [ -z "${local_app_data}" ]; then
1429- return
1430- fi
1431-
1432- local_app_data=$(wslpath -au "${local_app_data}") 2>/dev/null || true
1433- local fonts_dir="${local_app_data}/Microsoft/Windows/Fonts"
1434- local font="UbuntuMono[wght].ttf"
1435- mkdir -p "${fonts_dir}" 2>/dev/null
1436- if [ -f "${fonts_dir}/${font}" ]; then
1437- return
1438- fi
1439-
1440- cp "/usr/share/fonts/truetype/ubuntu/${font}" "${fonts_dir}" 2>/dev/null || true
1441-
1442- # Register the font for the current user.
1443- local dst=$(wslpath -aw "${fonts_dir}/${font}") 2>/dev/null || true
1444- powershell.exe -NoProfile -Command '& {
1445+ local local_app_data
1446+ local_app_data=$(powershell_env "LocalAppData")
1447+ if [ -z "${local_app_data}" ]; then
1448+ return
1449+ fi
1450+
1451+ local_app_data=$(wslpath -au "${local_app_data}") 2>/dev/null || true
1452+ local fonts_dir="${local_app_data}/Microsoft/Windows/Fonts"
1453+ local font="UbuntuMono[wght].ttf"
1454+ mkdir -p "${fonts_dir}" 2>/dev/null
1455+ if [ -f "${fonts_dir}/${font}" ]; then
1456+ return
1457+ fi
1458+
1459+ cp "/usr/share/fonts/truetype/ubuntu/${font}" "${fonts_dir}" 2>/dev/null || true
1460+
1461+ # Register the font for the current user.
1462+ local dst
1463+ dst=$(wslpath -aw "${fonts_dir}/${font}") 2>/dev/null || true
1464+ # shellcheck disable=SC2016 # Intentional due to how we use powershell
1465+ powershell.exe -NoProfile -Command '& {
1466 $null = New-Item -Path "HKCU:\Software\Microsoft\Windows NT\CurrentVersion" -Name "Fonts" 2>$null;
1467 $null = New-ItemProperty -Name "Ubuntu Mono (TrueType)" -Path "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -PropertyType string -Value "'"${dst}"'" 2>$null;
1468- }' || true
1469+ }' >/dev/null || true
1470 }
1471
1472-
1473 echo "Provisioning the new WSL instance $WSL_DISTRO_NAME"
1474 echo "This might take a while..."
1475
1476 # Read the Windows user name.
1477-win_username=$(powershell_env "UserName")
1478+win_username=$(powershell_env "UserName.ToLower()")
1479 # replace any potential whitespaces with underscores.
1480 win_username="${win_username// /_}"
1481
1482@@ -126,23 +131,26 @@ install_ubuntu_font
1483
1484 # Wait for cloud-init to finish if systemd and its service is enabled
1485 # by running the script located at the same dir as this one.
1486-this_dir=$(dirname "$(realpath $0)")
1487+this_dir=$(dirname "$(realpath "$0")")
1488 source "${this_dir}/wait-for-cloud-init"
1489
1490 # Check if there is a pre-provisioned users (pre-baked on the rootfs or created by cloud-init).
1491 user_id=$(get_first_interactive_uid)
1492
1493 # If we don’t have a non system user, let’s create it.
1494-if [ -z "${user_id}" ] ; then
1495- create_regular_user "${win_username}"
1496-
1497- user_id=$(get_first_interactive_uid)
1498- if [ -z "${user_id}" ] ; then
1499- echo 'Failed to create a regular user account'
1500- exit 1
1501- fi
1502+if [ -z "${user_id}" ]; then
1503+ create_regular_user "${win_username}"
1504+
1505+ user_id=$(get_first_interactive_uid)
1506+ if [ -z "${user_id}" ]; then
1507+ echo 'Failed to create a regular user account'
1508+ exit 1
1509+ fi
1510 fi
1511
1512 # Set the newly created user as the WSL default.
1513 username=$(id -un "${user_id}")
1514 set_user_as_default "${username}"
1515+
1516+# Start Ubuntu Insights consent script
1517+"${this_dir}/ubuntu-insights.sh"

Subscribers

People subscribed via source and target branches