Skip to content
Closed
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
69 changes: 69 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Resolve version
id: version
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
version_from_script=$(awk -F= '/^VERSION=/{ gsub(/"/, "", $2); print $2; exit }' install.sh)
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "script_version=$version_from_script" >> "$GITHUB_OUTPUT"
if [[ "$tag" != "v${version_from_script}" ]]; then
echo "Tag $tag does not match install.sh VERSION=$version_from_script" >&2
exit 1
fi

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
notes_file=$(mktemp)
cat > "$notes_file" <<EOF
## Docker-Prep ${TAG}

Pinned consumer note: VM-Setup syncs this release through its
Sync Docker-Prep pin workflow.

Entrypoint: bash install.sh
Ephemeral launch env:
- DOCKER_PREP_EPHEMERAL=1
- DOCKER_PREP_REVISION=<commit sha>
EOF
gh release create "$TAG" \
--title "Docker-Prep ${TAG}" \
--notes-file "$notes_file"

- name: Notify VM-Setup pin sync
env:
GH_TOKEN: ${{ secrets.VM_SETUP_DISPATCH_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
if [[ -z "${GH_TOKEN:-}" ]]; then
echo "VM_SETUP_DISPATCH_TOKEN not configured; skipping repository_dispatch."
exit 0
fi
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
"/repos/Narehood/VM-Setup/dispatches" \
-f event_type='docker-prep-release' \
-f "client_payload[tag]=${TAG}"
30 changes: 30 additions & 0 deletions .github/workflows/shell-security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Shell security checks

on:
push:
pull_request:

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
Comment on lines +14 to +16

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh api repos/actions/checkout/commits/v4 --jq '.sha'

Repository: Narehood/Docker-Prep

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sha="$(gh api repos/actions/checkout/commits/v4 --jq '.sha')"
echo "resolved_v4_sha=$sha"

echo "commit_lookup:"
gh api "repos/actions/checkout/commits/$sha" --jq '.sha'

Repository: Narehood/Docker-Prep

Length of output: 271


Pin actions/checkout to a full commit SHA. v4 is mutable and can move without a repository change, which weakens supply-chain reproducibility in this security-sensitive workflow. Pin it to 34e114876b0b11c390a56381ad16ebd13914f8d5 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/shell-security.yml around lines 14 - 16, Update the
actions/checkout step in the shell-security workflow to pin it to the specified
full commit SHA 34e114876b0b11c390a56381ad16ebd13914f8d5 instead of the mutable
v4 tag, while preserving persist-credentials: false.


- name: Install analysis tools
run: sudo apt-get update && sudo apt-get install -y shellcheck ripgrep

- name: Validate syntax and security policy
run: bash tests/security-checks.sh

- name: Run ShellCheck
run: |
shellcheck --severity=error \
install.sh \
Scripts/*.sh \
tests/*.sh \
tools/*.sh
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.docker-prep-pin-update.md
*.swp
*~
.DS_Store
51 changes: 48 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
Detects your OS, installs the official Docker Engine, creates dedicated users with correct permissions,<br>
and optionally deploys Portainer for immediate container management.

[Features](#-features) • [Quick Start](#-quick-start) • [Capabilities](#-capabilities) • [Notes](#%EF%B8%8F-important-notes)
[Features](#-features) • [Quick Start](#-quick-start) • [Capabilities](#-capabilities) • [VM-Setup](#-vm-setup-integration) • [Notes](#%EF%B8%8F-important-notes)

</div>

Expand Down Expand Up @@ -57,6 +57,7 @@ bash install.sh
| **User Management** | Creates a dedicated Docker user or configures existing users for rootless access |
| **Permission Fix** | Automatically handles group assignments (`usermod -aG docker`) |
| **Portainer Ready** | Option to instantly deploy [Portainer](https://www.portainer.io/) via the script |
| **Checksum Gate** | Verifies `Scripts/*.sh` against `Scripts/.checksums.sha256` before execution |
| **Secure Defaults** | Ensures proper service enabling and user permission handling |

---
Expand All @@ -67,10 +68,53 @@ The script guides you through a simplified menu to perform the following:

| Action | Description |
| :--- | :--- |
| **Install Docker Engine** | Updates repositories, installs dependencies, and sets up the Docker daemon |
| **Install Docker Engine** | Downloads the official installer to a temp file, then runs it (never `curl \| sh`) |
| **User Configuration** | Creates a new user specifically for Docker or adds your current user |
| **Security Groups** | Adds the selected user to the `docker` group for non-root command execution |
| **Portainer Deployment** | Pulls and runs the Portainer CE container on port 9443 |
| **Portainer Deployment** | Pulls and runs Portainer CE pinned to the `lts` image tag on port 9443 |

---

## 🔗 VM-Setup Integration

[VM-Setup](https://github.com/Narehood/VM-Setup) launches Docker-Prep from a pinned revision in a temporary checkout.

| Variable | Purpose |
| :--- | :--- |
| `DOCKER_PREP_EPHEMERAL=1` | Marks the launch as temporary; disables self-update |
| `DOCKER_PREP_REVISION=<sha>` | Displays the pinned commit in the UI |

Example:

```bash
DOCKER_PREP_EPHEMERAL=1 DOCKER_PREP_REVISION=<full-sha> bash ./install.sh
```

VM-Setup syncs its pin when this repository publishes a GitHub Release.

### Publishing a release

1. Bump `VERSION` in `install.sh` (for example `2.4.0`).
2. Commit the change.
3. Tag and push: `git tag v2.4.0 && git push origin v2.4.0`
4. The Release workflow creates the GitHub Release and, when `VM_SETUP_DISPATCH_TOKEN` is configured, notifies VM-Setup via `repository_dispatch` (`docker-prep-release`).

---

## 🔒 Integrity tooling

Regenerate the trusted checksum manifest after changing any file under `Scripts/`:

```bash
bash tools/generate-checksums.sh
```

Local validation (matches CI):

```bash
bash tests/security-checks.sh
shellcheck --severity=error install.sh Scripts/*.sh tests/*.sh tools/*.sh
```

---

Expand All @@ -81,6 +125,7 @@ The script guides you through a simplified menu to perform the following:
| **Root Access** | Script must be run as root or with `sudo` privileges |
| **Re-Login Required** | Log out and back in after adding a user to the Docker group |
| **Portainer** | If installed, accessible at `https://<your-ip>:9443` |
| **Ephemeral launches** | When launched from VM-Setup, update the pin there instead of using menu self-update |

---

Expand Down
4 changes: 4 additions & 0 deletions Scripts/.checksums.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
7765e55bf2bd3e269ac078f7e1a10c013a0c04a6c53282f372746342cd326181 DockerGroup.sh
ff5479ac784e1e93c4fb7094cbba4d75e2188fb7fd10294166f26376052caa68 portainer-install.sh
bd647935533e2354db2ba0c2386ba3ea6c6222feed629c55048bcc0eae872f8a serverSetup.sh
dece1be99a98476bbcebdc745123a265b7bf313c8bf3191fb5ac98c2011fb52b UserCreation.sh
3 changes: 3 additions & 0 deletions Scripts/DockerGroup.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/bash
set -euo pipefail

# Docker Group Management Script
# Adds a user to the docker group for rootless container management
Expand All @@ -7,6 +8,7 @@

# DIRECTORY ANCHOR
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC2034
SCRIPT_PATH="$SCRIPT_DIR/$(basename -- "${BASH_SOURCE[0]}")"

# VISUAL STYLING
Expand Down Expand Up @@ -49,6 +51,7 @@ fi
detect_os() {
OS="unknown"
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
source /etc/os-release
OS="$ID"
elif [[ -f /etc/redhat-release ]]; then
Expand Down
4 changes: 4 additions & 0 deletions Scripts/UserCreation.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/bash
set -euo pipefail

# User Creation Script
# Creates a new user with optional docker group membership
Expand All @@ -7,6 +8,8 @@

# DIRECTORY ANCHOR
SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
# shellcheck disable=SC2034
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"

# VISUAL STYLING
RED='\033[0;31m'
Expand Down Expand Up @@ -48,6 +51,7 @@ fi
detect_os() {
OS="unknown"
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
source /etc/os-release
OS="${ID,,}"
elif [[ -f /etc/redhat-release ]]; then
Expand Down
59 changes: 42 additions & 17 deletions Scripts/portainer-install.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#!/bin/bash
set -euo pipefail

# Portainer CE Installation Script
# Deploys Portainer using the official LTS compose file
# Version: 1.1.0
# Version: 1.2.0
# DESCRIPTION: Install Portainer CE using the official LTS compose file

# DIRECTORY ANCHOR
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
readonly PORTAINER_IMAGE="portainer/portainer-ce:lts"
SUDO=""

# VISUAL STYLING
RED='\033[0;31m'
Expand Down Expand Up @@ -155,31 +159,51 @@ check_existing_portainer() {
return 0
}

# pin_portainer_image rewrites floating Portainer image tags to the pinned LTS tag.
pin_portainer_image() {
local compose_file="$1"

print_info "Pinning Portainer image to ${PORTAINER_IMAGE}..."
if ! $SUDO grep -qE 'image:[[:space:]]*"?portainer/portainer-ce' "$compose_file"; then
print_error "Compose file does not reference official Portainer CE image."
return 1
fi

$SUDO sed -i -E 's|(image:[[:space:]]*"?)portainer/portainer-ce(:[A-Za-z0-9._-]+)?("?)|\1'"${PORTAINER_IMAGE}"'\3|' "$compose_file"
if ! $SUDO grep -qE 'image:[[:space:]]*"?portainer/portainer-ce:lts"?$' "$compose_file"; then
print_error "Failed to pin Portainer image tag."
return 1
fi

print_success "Portainer image pinned."
return 0
}

# validate_compose_file validates a Docker Compose file for Portainer by checking that the file exists and is non-empty, contains a Portainer service and the official `portainer/portainer-ce` image reference, and has valid YAML syntax according to `docker compose config`; returns 0 on success and 1 on failure.
validate_compose_file() {
local compose_file="$1"

print_info "Validating compose file..."

if [ ! -f "$compose_file" ]; then
if [[ ! -f "$compose_file" ]]; then
print_error "Compose file not found."
return 1
fi

if [ ! -s "$compose_file" ]; then
if [[ ! -s "$compose_file" ]]; then
print_error "Compose file is empty."
return 1
fi

# Check for expected Portainer service definition
if ! grep -q "portainer" "$compose_file"; then
if ! $SUDO grep -q "portainer" "$compose_file"; then
print_error "Compose file does not contain expected Portainer service."
return 1
fi

# Check for portainer image reference
if ! grep -qE "portainer/portainer-ce" "$compose_file"; then
print_error "Compose file does not reference official Portainer CE image."
# Check for pinned portainer image reference
if ! $SUDO grep -qE "image:[[:space:]]*\"?portainer/portainer-ce:lts\"?" "$compose_file"; then
print_error "Compose file does not reference pinned Portainer CE LTS image."
return 1
fi

Expand All @@ -202,8 +226,8 @@ deploy_portainer() {
echo ""
print_info "Deploying Portainer CE (LTS)..."

if [ ! -w "/opt" ]; then
if [ "$EUID" -ne 0 ]; then
if [[ ! -w "/opt" ]]; then
if [[ "$EUID" -ne 0 ]]; then
print_warn "Root privileges required to create $compose_dir"
read -rp " Use sudo for directory creation? (Y/n): " use_sudo
use_sudo="${use_sudo:-y}"
Expand All @@ -220,8 +244,7 @@ deploy_portainer() {
fi

print_info "Creating directory: $compose_dir"
$SUDO mkdir -p "$compose_dir"
if [ $? -ne 0 ]; then
if ! $SUDO mkdir -p "$compose_dir"; then
print_error "Failed to create directory."
return 1
fi
Expand All @@ -236,6 +259,11 @@ deploy_portainer() {
return 1
fi

if ! pin_portainer_image "$compose_file"; then
$SUDO rm -f "$compose_file"
return 1
fi

Comment on lines +262 to +266

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve the existing compose file when validation fails.

The download overwrites the live compose path, then this failure path deletes it. Stage the download in a temporary file, pin and validate it, and only then atomically replace portainer-compose.yaml; otherwise a transient upstream change can remove the last known-good configuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Scripts/portainer-install.sh` around lines 262 - 266, Update the installation
flow around pin_portainer_image to download and validate the compose content in
a temporary file first, then atomically replace portainer-compose.yaml only
after validation succeeds. On any download or validation failure, remove only
the temporary file and preserve the existing live compose file.

# Validate the downloaded file
if ! validate_compose_file "$compose_file"; then
print_error "Downloaded compose file failed validation."
Expand All @@ -247,16 +275,13 @@ deploy_portainer() {
print_info "Starting Portainer containers..."
echo ""

cd "$compose_dir" || return 1

if $SUDO docker compose -f "$compose_file" up -d; then
echo ""
print_success "Portainer deployed successfully!"
else
if ! $SUDO docker compose -f "$compose_file" up -d; then
print_error "Failed to deploy Portainer."
return 1
fi

echo ""
print_success "Portainer deployed successfully!"
return 0
}

Expand Down
Loading
Loading