Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .config/cspell/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"globalconfig",
"hasha",
"mktemp",
"libc",
"libicu",
"msbuild",
"NOLOGO",
Expand All @@ -27,6 +28,13 @@
"unrs",
"muxer",
"tarball",
"optout"
"optout",
"cpython",
"dsse",
"sigstore",
"slsa",
"uvx",
"ndjson",
"venv"
]
}
5 changes: 4 additions & 1 deletion .devcontainer/features/src/user-init/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

Creates `remoteUser` with UID 1000.

Each user gets its own subtree under the four volume-mounted XDG roots:
Each user gets its own subtree under the five volume-mounted roots:

| Variable | Directory |
| ----------------- | --------------------------------------- |
| `XDG_CONFIG_HOME` | `/etc/devcontainer-config/<user>` |
| `XDG_CACHE_HOME` | `/var/cache/devcontainer-config/<user>` |
| `XDG_DATA_HOME` | `/usr/share/devcontainer-config/<user>` |
| `XDG_STATE_HOME` | `/var/lib/devcontainer-config/<user>` |
| `XDG_BIN_HOME` | `/usr/bin/devcontainer-config/<user>` |

`XDG_BIN_HOME` is prepended to `PATH`.
5 changes: 4 additions & 1 deletion .devcontainer/features/src/user-init/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ Rename user with UID 1000 to remoteUser and setup XDG base directories.

Creates `remoteUser` with UID 1000.

Each user gets its own subtree under the four volume-mounted XDG roots:
Each user gets its own subtree under the five volume-mounted roots:

| Variable | Directory |
| ----------------- | --------------------------------------- |
| `XDG_CONFIG_HOME` | `/etc/devcontainer-config/<user>` |
| `XDG_CACHE_HOME` | `/var/cache/devcontainer-config/<user>` |
| `XDG_DATA_HOME` | `/usr/share/devcontainer-config/<user>` |
| `XDG_STATE_HOME` | `/var/lib/devcontainer-config/<user>` |
| `XDG_BIN_HOME` | `/usr/bin/devcontainer-config/<user>` |

`XDG_BIN_HOME` is prepended to `PATH`.

---

Expand Down
32 changes: 26 additions & 6 deletions .devcontainer/features/src/user-init/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
{
"id": "user-init",
"version": "4.0.0",
"version": "4.1.0",
"name": "Initialize User",
"description": "Rename user with UID 1000 to remoteUser and setup XDG base directories.",
"keywords": ["initialize", "user", "username", "uid", "common-utils"],
"installsAfter": ["ghcr.io/devcontainers/features/common-utils"],
"containerEnv": {
"XDG_DATA_DIRS": "/usr/local/share:/usr/share",
"XDG_CONFIG_DIRS": "/etc/xdg"
},
"mounts": [
{ "type": "volume", "source": "XDG_CONFIG_HOME-${devcontainerId}", "target": "/etc/devcontainer-config" },
{ "type": "volume", "source": "XDG_CACHE_HOME-${devcontainerId}", "target": "/var/cache/devcontainer-config" },
{ "type": "volume", "source": "XDG_DATA_HOME-${devcontainerId}", "target": "/usr/share/devcontainer-config" },
{ "type": "volume", "source": "XDG_STATE_HOME-${devcontainerId}", "target": "/var/lib/devcontainer-config" }
{
"type": "volume",
"source": "XDG_CONFIG_HOME-${devcontainerId}",
"target": "/etc/devcontainer-config"
},
{
"type": "volume",
"source": "XDG_CACHE_HOME-${devcontainerId}",
"target": "/var/cache/devcontainer-config"
},
{
"type": "volume",
"source": "XDG_DATA_HOME-${devcontainerId}",
"target": "/usr/share/devcontainer-config"
},
{
"type": "volume",
"source": "XDG_STATE_HOME-${devcontainerId}",
"target": "/var/lib/devcontainer-config"
},
{
"type": "volume",
"source": "XDG_BIN_HOME-${devcontainerId}",
"target": "/usr/bin/devcontainer-config"
}
]
}
64 changes: 40 additions & 24 deletions .devcontainer/features/src/user-init/install.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
#!/bin/sh
set -e
set -eu

# spell-checker:ignore usermod NOPASSWD
# spell-checker:ignore groupmod NOPASSWD usermod

OLD_USER=$(id --user --name 1000)
OLD_GROUP=$(id --group --name "${OLD_USER}")
HOME=/home/${_REMOTE_USER}
usermod --login "${_REMOTE_USER}" --home "${HOME}" --move-home "${OLD_USER}"
groupmod --new-name "${_REMOTE_USER}" "${OLD_GROUP}"
old_user=$(id --user --name 1000)
old_group=$(id --group --name "${old_user}")
new_user=${_REMOTE_USER}
home=/home/${new_user}

rm --force "/etc/sudoers.d/${OLD_USER}"
echo "${_REMOTE_USER} ALL=(root) NOPASSWD:ALL" > "/etc/sudoers.d/${_REMOTE_USER}"
chmod ug=r,o= "/etc/sudoers.d/${_REMOTE_USER}"
usermod --login "${new_user}" --home "${home}" --move-home "${old_user}"
groupmod --new-name "${new_user}" "${old_group}"

# Ideally these folders would be in ${HOME}, but there's no way to mount volumes base on remoteUser.
rm --force "/etc/sudoers.d/${old_user}"
printf '%s ALL=(root) NOPASSWD:ALL\n' "${new_user}" > "/etc/sudoers.d/${new_user}"
chmod ug=r,o= "/etc/sudoers.d/${new_user}"

# Ideally these folders would be in ${HOME}, but there's no way to mount volumes based on remoteUser.
# See https://github.com/devcontainers/spec/issues/220
XDG_CONFIG_HOME_ROOT=/etc/devcontainer-config
XDG_CACHE_HOME_ROOT=/var/cache/devcontainer-config
XDG_DATA_HOME_ROOT=/usr/share/devcontainer-config
XDG_STATE_HOME_ROOT=/var/lib/devcontainer-config
mkdir --parents "${XDG_CONFIG_HOME_ROOT}" "${XDG_CACHE_HOME_ROOT}" "${XDG_DATA_HOME_ROOT}" "${XDG_STATE_HOME_ROOT}"
chmod -R a+rwx "${XDG_CONFIG_HOME_ROOT}" "${XDG_CACHE_HOME_ROOT}" "${XDG_DATA_HOME_ROOT}" "${XDG_STATE_HOME_ROOT}"
xdg_config_home_root=/etc/devcontainer-config
xdg_cache_home_root=/var/cache/devcontainer-config
xdg_data_home_root=/usr/share/devcontainer-config
xdg_state_home_root=/var/lib/devcontainer-config
xdg_bin_home_root=/usr/bin/devcontainer-config
mkdir --parents \
"${xdg_config_home_root}" \
"${xdg_cache_home_root}" \
"${xdg_data_home_root}" \
"${xdg_state_home_root}" \
"${xdg_bin_home_root}"
chmod --recursive a+rwx \
"${xdg_config_home_root}" \
"${xdg_cache_home_root}" \
"${xdg_data_home_root}" \
"${xdg_state_home_root}" \
"${xdg_bin_home_root}"

cat > /etc/profile.d/00-xdg.sh << EOF
XDG_CONFIG_HOME="${XDG_CONFIG_HOME_ROOT}/\$(id -un)"
XDG_CACHE_HOME="${XDG_CACHE_HOME_ROOT}/\$(id -un)"
XDG_DATA_HOME="${XDG_DATA_HOME_ROOT}/\$(id -un)"
XDG_STATE_HOME="${XDG_STATE_HOME_ROOT}/\$(id -un)"
export XDG_CONFIG_HOME XDG_CACHE_HOME XDG_DATA_HOME XDG_STATE_HOME
mkdir -p "\$XDG_CONFIG_HOME" "\$XDG_CACHE_HOME" "\$XDG_DATA_HOME" "\$XDG_STATE_HOME"
XDG_CONFIG_HOME="${xdg_config_home_root}/\$(id -un)"
XDG_CACHE_HOME="${xdg_cache_home_root}/\$(id -un)"
XDG_DATA_HOME="${xdg_data_home_root}/\$(id -un)"
XDG_STATE_HOME="${xdg_state_home_root}/\$(id -un)"
XDG_BIN_HOME="${xdg_bin_home_root}/\$(id -un)"
export XDG_CONFIG_HOME XDG_CACHE_HOME XDG_DATA_HOME XDG_STATE_HOME XDG_BIN_HOME
mkdir -p "\$XDG_CONFIG_HOME" "\$XDG_CACHE_HOME" "\$XDG_DATA_HOME" "\$XDG_STATE_HOME" "\$XDG_BIN_HOME"
PATH="\$XDG_BIN_HOME:\$PATH"
export PATH
EOF
chmod 0644 /etc/profile.d/00-xdg.sh

chown --recursive "${_REMOTE_USER}:${_REMOTE_USER}" "${HOME}"
chown --recursive "${new_user}:${new_user}" "${home}"
22 changes: 22 additions & 0 deletions .devcontainer/features/test/user-init/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { execFileSync } from "node:child_process";
import { R_OK, W_OK, X_OK } from "node:constants";
import { access, stat } from "node:fs/promises";
import { userInfo } from "node:os";

import { expect, test } from "vitest";

const binDirectoryOf = (username: string): string => `/usr/bin/devcontainer-config/${username}`;

test("remote user home", async () => {
const { username, uid, gid, homedir } = userInfo();

Expand All @@ -29,3 +32,22 @@ test("XDG base directories", async () => {
await access(directory, R_OK | W_OK | X_OK);
}
});

test("XDG bin directory", async () => {
const { username } = userInfo();
const bin = binDirectoryOf(username);

expect(process.env.XDG_BIN_HOME).toBe(bin);
expect(process.env.PATH?.split(":")).toContain(bin);
await access(bin, R_OK | W_OK | X_OK);
});

test("XDG bin directory is prepended to PATH", () => {
const { username } = userInfo();
const bin = binDirectoryOf(username);
const path = execFileSync("/bin/sh", ["-c", '. /etc/profile.d/00-xdg.sh; printf "%s" "$PATH"'], {
encoding: "utf8",
env: { ...process.env, PATH: "/usr/bin:/bin" },
});
expect(path).toBe(`${bin}:/usr/bin:/bin`);
});
24 changes: 12 additions & 12 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ github.token }}

- name: Pack the install binary
- name: Pack the Feature binary
uses: devcontainers/ci@v0.3
with:
imageName: ${{ matrix.arm64 && format('ghcr.io/{0}/devcontainer', github.repository) || '' }}
Expand All @@ -80,15 +80,15 @@ jobs:
refFilterForPush: ${{ matrix.arm64 && 'refs/heads/main' || '' }}
useNativeRunner: ${{ matrix.arm64 }}
platform: ${{ matrix.arm64 && 'linux/arm64' || '' }}
runCmd: pnpm tsx scripts/tasks/features/dotnet/packInstall.ts --rid "$RID" --out .ci/install
runCmd: pnpm tsx scripts/tasks/features/dotnet/packFeature.ts --rid "$RID" --out .ci/feature
env: |
RID=${{ matrix.rid }}

- name: Upload the install binary
- name: Upload the Feature archive
uses: actions/upload-artifact@v7
with:
name: install-${{ matrix.rid }}
path: .ci/install/${{ matrix.rid }}/
name: feature-${{ matrix.rid }}
path: .ci/feature/${{ matrix.rid }}.tar

image:
needs:
Expand All @@ -110,24 +110,24 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ github.token }}

- name: Download the x64 install binary
- name: Download the x64 Feature archive
uses: actions/download-artifact@v7
with:
name: install-linux-x64
path: .ci/install/linux-x64
name: feature-linux-x64
path: .ci/feature

- name: Download the arm64 install binary
- name: Download the arm64 Feature archive
uses: actions/download-artifact@v7
with:
name: install-linux-arm64
path: .ci/install/linux-arm64
name: feature-linux-arm64
path: .ci/feature

- name: Publish the image
uses: devcontainers/ci@v0.3
with:
cacheFrom: ghcr.io/${{ github.repository }}/devcontainer
push: never
runCmd: pnpm tsx scripts/tasks/features/dotnet/publishImage.ts --component ${{ matrix.component }} --version ${{ matrix.version }} --channel "$CHANNEL" --install-dir .ci/install
runCmd: pnpm tsx scripts/tasks/features/dotnet/publishImage.ts --component ${{ matrix.component }} --version ${{ matrix.version }} --channel "$CHANNEL" --install-dir .ci/feature
env: |
CHANNEL=${{ github.head_ref || github.ref_name }}
GH_TOKEN=${{ github.token }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/renovate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
feature: [dotnet, ripgrep]
feature: [dotnet, ripgrep, uv]
steps:
- name: Checkout (GitHub)
uses: actions/checkout@v7
Expand Down
Loading
Loading