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
5 changes: 2 additions & 3 deletions infrastructure/host-os/installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,7 @@ q
run "sudo swapon /$latest_trimmed_parition"
echo "Update /etc/fstab for the new partition line"
run "sudo sed -i '$ d' /etc/fstab"
run "sudo chmod 766 /etc/fstab"
echo "/dev/disk/by-uuid/6443e3b1-12bc-41d0-83d8-e5c25477b5a0 none swap sw 0 " >> /etc/fstab
run "echo '/dev/disk/by-uuid/6443e3b1-12bc-41d0-83d8-e5c25477b5a0 none swap sw 0 ' | sudo tee -a /etc/fstab > /dev/null"
run "sudo chmod 644 /etc/fstab"
fi
fi
Expand Down Expand Up @@ -455,7 +454,7 @@ function InternalConfigSetup() {
run "echo 'set enable-bracketed-paste off' >> /etc/inputrc"
fi
run "echo 'sys_olvtelemetry ALL=(ALL) NOPASSWD: /usr/sbin/biosdecode, /usr/sbin/dmidecode, /usr/sbin/ownership, /usr/sbin/vpddecode' > /etc/sudoers.d/user-sudo"
run "echo 'user ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/user-sudo"
run "echo 'user ALL=(ALL) ALL' >> /etc/sudoers.d/user-sudo"
run "chmod 440 /etc/sudoers.d/user-sudo"
run "sed -i 's/.*AutomaticLoginEnable =.*/AutomaticLoginEnable = true/g' /etc/gdm3/custom.conf"
run "sed -i 's/.*AutomaticLogin = user1/AutomaticLogin = user/g' /etc/gdm3/custom.conf"
Expand Down
74 changes: 68 additions & 6 deletions infrastructure/installation-scripts/download-resources.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,37 @@ check_dep() {
command -v "$1" &>/dev/null || { echo "ERROR: '$1' is required but not found. Install it and retry."; exit 1; }
}

verify_sha256_hex() {
# $1 = file path, $2 = expected 64-hex sha256
local file="$1" expected="$2" got
[[ "${#expected}" -eq 64 ]] || { echo "[ERROR] verify_sha256_hex: invalid expected sha length for $(basename "$file")"; return 2; }
got="$(sha256sum "$file" | awk '{print $1}')"
if [[ "$got" != "$expected" ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the curly brackets for the variables to avoid shellcheck errors?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoted & unambiguous. So not needed.

echo "[ERROR] sha256 mismatch: $(basename "$file")"
echo " expected: $expected"
echo " got : $got"
return 1
fi
echo "[OK] sha256 verified: $(basename "$file")"
}

verify_sha256_from_sumfile() {
# $1 = file path, $2 = sha256sum-format file, $3 = optional upstream name
# (defaults to basename $1)
local file="$1" sumfile="$2" name="${3:-$(basename "$1")}" expected got
[[ -f "$sumfile" ]] || { echo "[ERROR] sumfile not found: $sumfile"; return 1; }
expected="$(awk -v n="$name" '$2 == n || $2 == "*"n {print $1; exit}' "$sumfile")"
[[ -n "$expected" ]] || { echo "[ERROR] no entry for $name in $(basename "$sumfile")"; return 1; }
got="$(sha256sum "$file" | awk '{print $1}')"
if [[ "$got" != "$expected" ]]; then
echo "[ERROR] sha256 mismatch: $name"
echo " expected: $expected"
echo " got : $got"
return 1
fi
echo "[OK] sha256 verified: $name"
}

# ------------------------------------------------------------------------------
# Preflight
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -87,35 +118,47 @@ echo ""
# K3s
# ==============================================================================
K3S_BASE_URL="https://github.com/k3s-io/k3s/releases/download/${K3S_VERSION}"
info "Downloading K3s checksums..."
curl -fL "${K3S_BASE_URL}/sha256sum-${K3S_ARCH}.txt" \
-o "${RESOURCES_DIR}/k3s/sha256sum-${K3S_ARCH}.txt"
K3S_SUMS="${RESOURCES_DIR}/k3s/sha256sum-${K3S_ARCH}.txt"
success "sha256sum-${K3S_ARCH}.txt saved"

info "Downloading K3s binary..."
curl -fL "${K3S_BASE_URL}/${K3S_BINARY}" \
-o "${RESOURCES_DIR}/k3s/k3s"
verify_sha256_from_sumfile "${RESOURCES_DIR}/k3s/k3s" "${K3S_SUMS}" "${K3S_BINARY}"
chmod +x "${RESOURCES_DIR}/k3s/k3s"
success "k3s binary saved"

info "Downloading K3s airgap images (this may take several minutes)..."
# Try the newer .tar.zst format first; fall back to .tar.gz
if curl -fL "${K3S_BASE_URL}/k3s-airgap-images-${K3S_ARCH}.tar.zst" \
-o "${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.zst" 2>/dev/null; then
verify_sha256_from_sumfile \
"${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.zst" "${K3S_SUMS}"
success "k3s-airgap-images-${K3S_ARCH}.tar.zst saved"
else
warn ".tar.zst not found, falling back to .tar.gz"
curl -fL "${K3S_BASE_URL}/k3s-airgap-images-${K3S_ARCH}.tar.gz" \
-o "${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.gz"
verify_sha256_from_sumfile \
"${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.gz" "${K3S_SUMS}"
success "k3s-airgap-images-${K3S_ARCH}.tar.gz saved"
fi

info "Downloading K3s install script..."
curl -fL "https://get.k3s.io" -o "${RESOURCES_DIR}/k3s/install.sh"
K3S_INSTALL_URL="https://raw.githubusercontent.com/k3s-io/k3s/${K3S_VERSION}/install.sh"
curl -fL "${K3S_INSTALL_URL}" -o "${RESOURCES_DIR}/k3s/install.sh"
if [[ -n "${K3S_INSTALL_SH_SHA256:-}" ]]; then
verify_sha256_hex "${RESOURCES_DIR}/k3s/install.sh" "${K3S_INSTALL_SH_SHA256}"
else
warn "K3S_INSTALL_SH_SHA256 not set — install.sh content is not checksum-verified."
warn " Pin it for reproducibility: K3S_INSTALL_SH_SHA256=<sha> ./download-resources.sh"
fi
chmod +x "${RESOURCES_DIR}/k3s/install.sh"
success "install.sh saved"

info "Downloading K3s checksums..."
curl -fL "${K3S_BASE_URL}/sha256sum-${K3S_ARCH}.txt" \
-o "${RESOURCES_DIR}/k3s/sha256sum-${K3S_ARCH}.txt"
success "sha256sum-${K3S_ARCH}.txt saved"

# Store the version so install scripts can report it
echo "${K3S_VERSION}" > "${RESOURCES_DIR}/k3s/VERSION"

Expand All @@ -128,12 +171,26 @@ DOCKER_STATIC_URL="https://download.docker.com/linux/static/stable/${DOCKER_ARCH
info "Downloading Docker static binaries..."
curl -fL "${DOCKER_STATIC_URL}" \
-o "${RESOURCES_DIR}/docker/docker-${DOCKER_VERSION}.tgz"

if [[ -n "${DOCKER_SHA256:-}" ]]; then
verify_sha256_hex "${RESOURCES_DIR}/docker/docker-${DOCKER_VERSION}.tgz" "${DOCKER_SHA256}"
else
warn "DOCKER_SHA256 not set — docker-${DOCKER_VERSION}.tgz is not checksum-verified."
warn " Pin it for reproducibility: DOCKER_SHA256=<sha> ./download-resources.sh"
fi
success "docker-${DOCKER_VERSION}.tgz saved"

info "Downloading Docker Compose plugin (${COMPOSE_VERSION})..."
COMPOSE_URL="https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-${COMPOSE_ARCH}"
curl -fL "${COMPOSE_URL}" \
-o "${RESOURCES_DIR}/docker/docker-compose"

curl -fL "${COMPOSE_URL}.sha256" \
-o "${RESOURCES_DIR}/docker/docker-compose.sha256"
verify_sha256_from_sumfile \
"${RESOURCES_DIR}/docker/docker-compose" \
"${RESOURCES_DIR}/docker/docker-compose.sha256" \
"docker-compose-linux-${COMPOSE_ARCH}"
chmod +x "${RESOURCES_DIR}/docker/docker-compose"
success "docker-compose saved"

Expand All @@ -158,6 +215,11 @@ curl -fL "${HELM_BASE_URL}/${HELM_TARBALL}.sha256sum" \
-o "${RESOURCES_DIR}/helm/${HELM_TARBALL}.sha256sum"
success "${HELM_TARBALL}.sha256sum saved"

verify_sha256_from_sumfile \
"${RESOURCES_DIR}/helm/${HELM_TARBALL}" \
"${RESOURCES_DIR}/helm/${HELM_TARBALL}.sha256sum" \
"${HELM_TARBALL}"

# Store the version
echo "${HELM_VERSION}" > "${RESOURCES_DIR}/helm/VERSION"

Expand Down
21 changes: 9 additions & 12 deletions infrastructure/installation-scripts/install-helm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,18 @@ echo "======================================================================"
echo ""

# ==============================================================================
# 1. Verify checksum (if available)
# 1. Verify checksum (missing treated as a hard error so a tampered bundle that
# strips the .sha256sum cannot bypass verification.)
# ==============================================================================
CHECKSUM_FILE="${HELM_TARBALL}.sha256sum"
if [[ -f "${CHECKSUM_FILE}" ]]; then
info "Verifying checksum ..."
# sha256sum file contains an absolute path; rebuild it relative to RESOURCES_DIR
EXPECTED_SUM="$(awk '{print $1}' "${CHECKSUM_FILE}")"
ACTUAL_SUM="$(sha256sum "${HELM_TARBALL}" | awk '{print $1}')"
if [[ "${EXPECTED_SUM}" != "${ACTUAL_SUM}" ]]; then
die "Checksum mismatch for $(basename "${HELM_TARBALL}")!\n Expected: ${EXPECTED_SUM}\n Got : ${ACTUAL_SUM}\nDelete the file and re-run download-resources.sh."
fi
success "Checksum verified"
else
echo "[WARN] No checksum file found — skipping verification."
[[ -f "${CHECKSUM_FILE}" ]] || die "Checksum file not found: $(basename "${CHECKSUM_FILE}")\nRefusing to install without verification. Re-run download-resources.sh to repopulate the bundle."
info "Verifying checksum ..."
EXPECTED_SUM="$(awk '{print $1}' "${CHECKSUM_FILE}")"
ACTUAL_SUM="$(sha256sum "${HELM_TARBALL}" | awk '{print $1}')"
if [[ "${EXPECTED_SUM}" != "${ACTUAL_SUM}" ]]; then
die "Checksum mismatch for $(basename "${HELM_TARBALL}")!\n Expected: ${EXPECTED_SUM}\n Got : ${ACTUAL_SUM}\nDelete the file and re-run download-resources.sh."
fi
success "Checksum verified"

# ==============================================================================
# 2. Extract Helm binary
Expand Down
41 changes: 26 additions & 15 deletions infrastructure/installation-scripts/install-k3s.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,30 @@ if [[ "${STAGED}" -eq 0 ]]; then
fi

# ==============================================================================
# 3. Ensure config directory exists
# 3. Ensure config directory exists (root-only)
# ==============================================================================
mkdir -p "${K3S_CONFIG_DIR}"
install -d -m 0700 -o root -g root "${K3S_CONFIG_DIR}"

# Copy a user-supplied config if provided
# Copy a user-supplied config if provided. Permissions hardened to 0600 root:root
# because the config may contain `token:` or other secrets.
if [[ -n "${K3S_CONFIG_FILE:-}" && -f "${K3S_CONFIG_FILE}" ]]; then
info "Copying config file ${K3S_CONFIG_FILE}${K3S_CONFIG_DIR}/config.yaml"
cp "${K3S_CONFIG_FILE}" "${K3S_CONFIG_DIR}/config.yaml"
install -m 0600 -o root -g root "${K3S_CONFIG_FILE}" "${K3S_CONFIG_DIR}/config.yaml"
fi

# Persist server URL and join token to a permission-hardened drop-in.
if [[ "${K3S_MODE}" == "agent" ]]; then
install -d -m 0700 -o root -g root "${K3S_CONFIG_DIR}/config.yaml.d"
AGENT_DROPIN="${K3S_CONFIG_DIR}/config.yaml.d/00-agent.yaml"
( umask 077 && cat > "${AGENT_DROPIN}" <<EOF
server: ${K3S_URL}
token: ${K3S_TOKEN}
EOF
)
chmod 0600 "${AGENT_DROPIN}"
chown root:root "${AGENT_DROPIN}"
# Drop the token from this script's env
unset K3S_TOKEN
fi

# ==============================================================================
Expand All @@ -127,17 +143,12 @@ fi
info "Running K3s installer (offline mode) ..."
echo ""

if [[ "${K3S_MODE}" == "agent" ]]; then
INSTALL_K3S_SKIP_DOWNLOAD=true \
INSTALL_K3S_BIN_DIR="${K3S_INSTALL_DIR}" \
K3S_URL="${K3S_URL}" \
K3S_TOKEN="${K3S_TOKEN}" \
bash "${RESOURCES_DIR}/install.sh" agent
else
INSTALL_K3S_SKIP_DOWNLOAD=true \
INSTALL_K3S_BIN_DIR="${K3S_INSTALL_DIR}" \
bash "${RESOURCES_DIR}/install.sh"
fi
INSTALL_ARGS=()
[[ "${K3S_MODE}" == "agent" ]] && INSTALL_ARGS+=(agent)

INSTALL_K3S_SKIP_DOWNLOAD=true \
INSTALL_K3S_BIN_DIR="${K3S_INSTALL_DIR}" \
bash "${RESOURCES_DIR}/install.sh" "${INSTALL_ARGS[@]}"

# ==============================================================================
# 5. Post-install guidance
Expand Down
72 changes: 47 additions & 25 deletions infrastructure/installation-scripts/kubernetes-provision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ echo "=== kubernetes-provision.sh: start ==="
. /etc/environment 2>/dev/null || true
# Export so child processes (curl, helm, kubectl, etc.) inherit them
export http_proxy https_proxy HTTP_PROXY HTTPS_PROXY no_proxy NO_PROXY 2>/dev/null || true
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

K3S_KUBECONFIG=/etc/rancher/k3s/k3s.yaml
k() { KUBECONFIG="$K3S_KUBECONFIG" kubectl "$@"; }

# ── Disable docker — kubernetes node does not run docker ──────────────────
systemctl disable docker 2>/dev/null || true
Expand All @@ -43,8 +45,10 @@ systemctl enable --now k3s
# ── Configure k3s proxy for containerd (if proxy variables are set) ───────
if [ -n "${HTTP_PROXY:-}" ] || [ -n "${HTTPS_PROXY:-}" ]; then
echo "Configuring k3s proxy settings..."
mkdir -p /etc/systemd/system/k3s.service.d
cat > /etc/systemd/system/k3s.service.d/http-proxy.conf <<EOF
install -d -m 0755 /etc/systemd/system/k3s.service.d
# Drop-in may contain proxy URLs with embedded credentials. Restrict to root.
DROPIN=/etc/systemd/system/k3s.service.d/http-proxy.conf
( umask 077 && cat > "$DROPIN" <<EOF
[Service]
Environment="HTTP_PROXY=${HTTP_PROXY}"
Environment="HTTPS_PROXY=${HTTPS_PROXY}"
Expand All @@ -53,6 +57,9 @@ Environment="CONTAINERD_HTTP_PROXY=${HTTP_PROXY}"
Environment="CONTAINERD_HTTPS_PROXY=${HTTPS_PROXY}"
Environment="CONTAINERD_NO_PROXY=${NO_PROXY}"
EOF
)
chmod 0600 "$DROPIN"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curly brackets for variables.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoted & unambiguous. So not needed.

chown root:root "$DROPIN"
systemctl daemon-reload
systemctl restart k3s
echo "K3s proxy configuration applied"
Expand Down Expand Up @@ -81,35 +88,50 @@ fi
# ── Wait for k3s API to be ready (up to 5 minutes) ───────────────────────
echo "Waiting for K3s API server to be ready..."
for i in $(seq 1 60); do
kubectl get nodes --no-headers 2>/dev/null | grep -q ' Ready' && break
k get nodes --no-headers 2>/dev/null | grep -q ' Ready' && break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is k can be used instead of kubectl ? Did we set this alias in environment file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sleep 5
done
if ! kubectl get nodes >/dev/null 2>&1; then
if ! k get nodes >/dev/null 2>&1; then
echo "ERROR: K3s API not ready after 5 minutes — aborting plugin setup"
exit 1
fi
echo "K3s nodes:"
kubectl get nodes
k get nodes

# ── Install Helm if not already present ───────────────────────────────────
INSTALL_SCRIPTS="/opt/edge/scripts"
HELM_VERSION="v3.17.2"
if ! command -v helm >/dev/null 2>&1; then
if [ -f "${INSTALL_SCRIPTS}/install-helm.sh" ] && ls "${INSTALL_SCRIPTS}"/helm-*-linux-*.tar.gz >/dev/null 2>&1; then
echo "Installing Helm from local resources..."
if [ -f "${INSTALL_SCRIPTS}/install-helm.sh" ] && ls "${INSTALL_SCRIPTS}"/resources/helm/helm-*-linux-*.tar.gz >/dev/null 2>&1; then
echo "Installing Helm from local airgap bundle..."
bash "${INSTALL_SCRIPTS}/install-helm.sh"
else
# Local helm tarball not bundled (resources/helm/ not present in hook OS).
# Attempt internet install only if reachable within 10s; skip otherwise.
echo "Local Helm resources not found — testing internet connectivity..."
if curl -fsSL --connect-timeout 10 --max-time 10 \
https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 \
-o /tmp/get-helm-3 2>/dev/null; then
bash /tmp/get-helm-3 || true
rm -f /tmp/get-helm-3
echo "Local Helm bundle not found"
case "$(uname -m)" in
x86_64) HELM_ARCH="amd64" ;;
aarch64) HELM_ARCH="arm64" ;;
armv7l) HELM_ARCH="arm" ;;
*) HELM_ARCH="" ;;
esac

if [ -z "${HELM_ARCH}" ]; then
echo "ERROR: Unsupported architecture $(uname -m) — cannot install Helm"
else
echo "WARNING: Helm internet install skipped (endpoint unreachable)."
echo " Install Helm manually after first boot:"
echo " curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash"
HELM_TARBALL="helm-${HELM_VERSION}-linux-${HELM_ARCH}.tar.gz"
HELM_TMP="$(mktemp -d)"
( umask 077 && cd "${HELM_TMP}" && \
for i in 1 2 3; do
curl -fsSL --max-time 120 --retry 3 "https://get.helm.sh/${HELM_TARBALL}" -o "${HELM_TARBALL}" && \
curl -fsSL --max-time 30 --retry 3 "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o "${HELM_TARBALL}.sha256sum" && break
echo " helm download attempt $i failed, retrying..."
sleep 5
done && \
sha256sum -c "${HELM_TARBALL}.sha256sum" && \
tar -xzf "${HELM_TARBALL}" && \
install -m 0755 "linux-${HELM_ARCH}/helm" /usr/local/bin/helm
) && echo "Helm ${HELM_VERSION} installed: $(helm version --short 2>/dev/null)" \
|| echo "WARNING: Helm install failed (download or checksum verification error)"
rm -rf "${HELM_TMP}"
fi
fi
else
Expand All @@ -122,15 +144,15 @@ fi
# applying manifests directly (k3s pulls images at runtime).
apply_manifests_directly() {
# Manifests are at /opt/edge/scripts/ (flat layout from hook OS)
[ -f "${INSTALL_SCRIPTS}/nfd.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/nfd.yaml" && sleep 15 || true
[ -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" || true
[ -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" || true
[ -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" || true
[ -f "${INSTALL_SCRIPTS}/nfd.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/nfd.yaml" && sleep 15 || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the k alias set in environment file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" || true
[ -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" || true
[ -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" || true
}

if [ -f "${INSTALL_SCRIPTS}/install-intel-device-plugins.sh" ]; then
echo "Running install-intel-device-plugins.sh..."
bash "${INSTALL_SCRIPTS}/install-intel-device-plugins.sh" || {
KUBECONFIG="$K3S_KUBECONFIG" bash "${INSTALL_SCRIPTS}/install-intel-device-plugins.sh" || {
echo "WARNING: install-intel-device-plugins.sh failed (likely missing pre-pulled images) — applying manifests directly"
apply_manifests_directly
}
Expand All @@ -140,7 +162,7 @@ else
fi

echo "=== Pod status after plugin installation ==="
kubectl get pods -A
k get pods -A

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to reconfirm on k

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# ── SR-IOV Configuration (Optional) ───────────────────────────────────────
# Set up SR-IOV virtual functions if enabled in config-file
Expand Down
Loading