diff --git a/Moodle/PHP/agets.sh b/Moodle/PHP/agets.sh new file mode 100644 index 0000000..b7add0f --- /dev/null +++ b/Moodle/PHP/agets.sh @@ -0,0 +1,5 @@ +aria2c -x 16 --content-disposition-default-utf8=true --check-certificate=false -i list.txt \ + --continue=true \ + --check-integrity=true \ + --max-concurrent-downloads=5 \ + --log=aria2.log --log-level=notice diff --git a/Moodle/PHP/batch_run.sh b/Moodle/PHP/batch_run.sh new file mode 100644 index 0000000..bdf9fa5 --- /dev/null +++ b/Moodle/PHP/batch_run.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# Kiểm tra có đối số không +if [ -z "$1" ] || [ -z "$2" ]; then + echo "Usage: $(basename "$0") " + exit 1 +fi +siteid=$1 +courseid=$2 + +# Lưu thư mục hiện tại (nơi chạy script, không phải nơi đặt script) +BASEDIR="$(pwd)" + +for dir in */ ; do + # Bỏ dấu '/' cuối + dirname="${dir%/}" + + # Cắt phần số trước dấu '.' và trim khoảng trắng + index=$(echo "$dirname" | cut -d'.' -f1 | tr -d '[:space:]') + + # Bỏ số 0 đầu nếu có + index_nozero=$(echo "$index" | sed 's/^0*//') + + # Nếu chuỗi rỗng (trường hợp '0') + if [ -z "$index_nozero" ]; then + index_nozero=0 + fi + + # Kiểm tra là số hợp lệ + if [[ "$index_nozero" =~ ^[0-9]+$ ]]; then + # Tính i-1 + i_minus_1=$((index_nozero - 1)) + + echo "===> Đang xử lý thư mục: $dirname (index: $index_nozero, i-1: $i_minus_1)" + + # Chuyển vào thư mục + cd "$dirname" || { echo "Không thể vào thư mục $dirname"; exit 1; } + + # Gọi online.sh với courseid từ dòng lệnh và i-1 + + case "$siteid" in + 1) + online.sh "$courseid" "$i_minus_1" + ;; + 2) + elearning.sh "$courseid" "$i_minus_1" + ;; + 3) + english.sh "$courseid" "$i_minus_1" + ;; + *) + echo "Không hỗ trợ siteid=$siteid" + exit 1 + ;; +esac + + # Quay lại thư mục gốc + cd "$BASEDIR" + else + echo "Bỏ qua $dirname (không bắt đầu bằng số)" + fi +done diff --git a/Moodle/PHP/create_course.sh b/Moodle/PHP/create_course.sh new file mode 100644 index 0000000..ba62438 --- /dev/null +++ b/Moodle/PHP/create_course.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lấy thư mục hiện tại nơi người dùng chạy lệnh +CURRENT_DIR="$(pwd)" + +# Gọi PHP script, truyền tham số đầu vào và thư mục hiện tại +/usr/local/lsws/lsphp82/bin/php /usr/bin/create_moodle_course.php "$@" "$CURRENT_DIR" diff --git a/Moodle/PHP/create_moodle_course.php b/Moodle/PHP/create_moodle_course.php new file mode 100644 index 0000000..e75e058 --- /dev/null +++ b/Moodle/PHP/create_moodle_course.php @@ -0,0 +1,131 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); +require_once($CFG->dirroot . '/course/externallib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if (count($argv) < 4) { + echo "❗Cách dùng: create_course \n"; + echo " Ví dụ : create_course 3 topics\n"; + exit(1); +} + +$categoryid = intval($argv[2]); +$format = $argv[3]; +$cwd = $argv[4]; // Thư mục làm việc thực tế + +$coursefullname = basename($cwd); +$shortname = preg_replace('/\s+/', '', $coursefullname); + +echo "📂 Thư mục hiện tại: $cwd\n"; +echo "📘 Tên khóa học: $coursefullname\n"; +echo "🔤 Shortname: $shortname\n"; +echo "📁 Category ID: $categoryid\n"; +echo "🧱 Course Format: $format\n"; + +// ✅ Lấy danh sách thư mục con để đếm số topic +$topics = array_filter(glob($cwd . '/*'), 'is_dir'); +$numsections = count($topics); +echo "🧩 Số topic được tạo: $numsections\n"; + +// ✅ Kiểm tra shortname đã tồn tại chưa +if ($DB->record_exists('course', ['shortname' => $shortname])) { + echo "⚠️ Shortname '$shortname' đã tồn tại. Dừng lại để tránh trùng.\n"; + exit(1); +} + +// ✅ Tạo khóa học +$course = new stdClass(); +$course->fullname = $coursefullname; +$course->shortname = $shortname; +$course->category = $categoryid; +$course->format = $format; +$course->numsections = $numsections; +$course->summary = $coursefullname; +$course->summaryformat = FORMAT_HTML; +$course->visible = 1; + +$newcourse = create_course($course); +echo "✅ Đã tạo khóa học thành công với ID: {$newcourse->id}\n"; + +// ✅ Đặt tên cho từng topic/section +foreach ($topics as $dir) { + $dirname = basename($dir); + $index = 0; + + if (preg_match('/^(\d+)/', $dirname, $matches)) { + $index = intval($matches[1]) - 1; + } + + $topicname = preg_replace('/^\d+\.\s*/', '', $dirname); + + $section = $DB->get_record('course_sections', [ + 'course' => $newcourse->id, + 'section' => $index + ]); + + if ($section) { + $section->name = $topicname; + $DB->update_record('course_sections', $section); + echo "🔁 Đổi tên topic [$index] thành: $topicname\n"; + } else { + $section = course_create_section($newcourse->id, $index); + $section->name = $topicname; + $section->summary = ''; + $section->summaryformat = FORMAT_HTML; + $DB->update_record('course_sections', $section); + echo "🆕 Tạo topic mới [$index]: $topicname\n"; + } +} + +// ✅ Ghi course ID vào file .env +$envFile = $cwd . '/.env'; +$courseIdLine = "COURSEID={$newcourse->id}"; + +if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + $found = false; + foreach ($lines as &$line) { + if (str_starts_with(trim($line), 'COURSEID=')) { + $line = $courseIdLine; + $found = true; + break; + } + } + if (!$found) { + $lines[] = $courseIdLine; + } + file_put_contents($envFile, implode(PHP_EOL, $lines) . PHP_EOL); + echo "📄 Đã cập nhật COURSEID vào .env\n"; +} else { + file_put_contents($envFile, $courseIdLine . PHP_EOL); + echo "📄 Đã tạo file .env với COURSEID={$newcourse->id}\n"; +} diff --git a/Moodle/PHP/createcourse.php b/Moodle/PHP/createcourse.php new file mode 100644 index 0000000..ee14abc --- /dev/null +++ b/Moodle/PHP/createcourse.php @@ -0,0 +1,114 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); +require_once($CFG->dirroot . '/course/externallib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if (count($argv) < 4) { + echo "❗Cách dùng: create_course_list \n"; + echo " Ví dụ : create_course_list 3 topics\n"; + exit(1); +} + +$categoryid = intval($argv[1]); +$format = $argv[2]; +$cwd = $argv[3]; // Thư mục đang thao tác (nơi chứa list.txt) + +// 🧾 Đọc tên khóa học từ thư mục +$coursefullname = basename($cwd); +$shortname = preg_replace('/\s+/', '', $coursefullname); + +echo "📂 Thư mục hiện tại: $cwd\n"; +echo "📘 Tên khóa học: $coursefullname\n"; +echo "🔤 Shortname: $shortname\n"; +echo "📁 Category ID: $categoryid\n"; +echo "🧱 Course Format: $format\n"; + +// 📄 Đọc danh sách topic từ file list.txt +$listfile = $cwd . '/list.txt'; + +if (!file_exists($listfile)) { + echo "❌ Không tìm thấy file list.txt trong thư mục: $cwd\n"; + exit(1); +} + +$lines = file($listfile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); +$numsections = count($lines); +echo "🧩 Số topic từ list.txt: $numsections\n"; + +// ✅ Kiểm tra shortname đã tồn tại chưa +if ($DB->record_exists('course', ['shortname' => $shortname])) { + echo "⚠️ Shortname '$shortname' đã tồn tại. Dừng lại để tránh trùng.\n"; + exit(1); +} + +// ✅ Tạo khóa học +$course = new stdClass(); +$course->fullname = $coursefullname; +$course->shortname = $shortname; +$course->category = $categoryid; +$course->format = $format; +$course->numsections = $numsections; +$course->summary = $coursefullname; +$course->summaryformat = FORMAT_HTML; +$course->visible = 1; + +$newcourse = create_course($course); +echo "✅ Đã tạo khóa học thành công với ID: {$newcourse->id}\n"; + +// ✅ Tạo và đặt tên cho từng topic từ danh sách +foreach ($lines as $i => $topicname) { + $index = $i; // Bắt đầu từ 0 + + $section = $DB->get_record('course_sections', [ + 'course' => $newcourse->id, + 'section' => $index + ]); + + if ($section) { + $section->name = trim($topicname); + $DB->update_record('course_sections', $section); + echo "🔁 Đặt tên topic [$index] thành: $topicname\n"; + } else { + $section = course_create_section($newcourse->id, $index); + $section->name = trim($topicname); + $section->summary = ''; + $section->summaryformat = FORMAT_HTML; + $DB->update_record('course_sections', $section); + echo "🆕 Tạo topic [$index]: $topicname\n"; + } +} + +// ✅ Ghi course ID vào file .env +$envFile = $cwd . '/.env'; +$courseIdLine = "COURSEID={$newcourse->id}"; + +if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + $found = false; + foreach ($lines as &$line) { + if (str_starts_with(trim($line), 'COURSEID=')) { + $line = $courseIdLine; + $found = true; + break; + } + } + if (!$found) { + $lines[] = $courseIdLine; + } + file_put_contents($envFile, implode(PHP_EOL, $lines) . PHP_EOL); + echo "📄 Đã cập nhật COURSEID vào .env\n"; +} else { + file_put_contents($envFile, $courseIdLine . PHP_EOL); + echo "📄 Đã tạo file .env với COURSEID={$newcourse->id}\n"; +} diff --git a/Moodle/PHP/createcourse.sh b/Moodle/PHP/createcourse.sh new file mode 100644 index 0000000..342aae2 --- /dev/null +++ b/Moodle/PHP/createcourse.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lấy thư mục hiện tại nơi người dùng chạy lệnh +CURRENT_DIR="$(pwd)" + +# Gọi PHP script, truyền tham số đầu vào và thư mục hiện tại +php /usr/bin/createcourse.php "$@" "$CURRENT_DIR" diff --git a/Moodle/PHP/delete_course.php b/Moodle/PHP/delete_course.php new file mode 100644 index 0000000..49c855e --- /dev/null +++ b/Moodle/PHP/delete_course.php @@ -0,0 +1,42 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if ($argc < 2) { + echo "❗ Cách dùng: delete_course \n"; + echo " Ví dụ : delete_course 123\n"; + exit(1); +} + +$courseid = intval($argv[1]); + +if (!$course = $DB->get_record('course', ['id' => $courseid])) { + echo "⚠️ Không tìm thấy khóa học với ID: $courseid\n"; + exit(1); +} + +if ($course->id == SITEID) { + echo "❌ Không thể xóa site course (ID = SITEID).\n"; + exit(1); +} + +echo "⚠️ Bạn sắp xóa khóa học: [{$course->id}] {$course->fullname}\n"; + +// ✅ Thực hiện xóa +try { + delete_course($course, false); // false = không tái sắp xếp category + echo "✅ Đã xóa khóa học thành công.\n"; +} catch (Exception $e) { + echo "❌ Lỗi khi xóa khóa học: " . $e->getMessage() . "\n"; + exit(1); +} diff --git a/Moodle/PHP/ebatch_run.sh b/Moodle/PHP/ebatch_run.sh new file mode 100644 index 0000000..2077e0f --- /dev/null +++ b/Moodle/PHP/ebatch_run.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# Kiểm tra có đối số không +if [ -z "$1" ]; then + echo "Usage: $(basename "$0") " + exit 1 +fi + +courseid="$1" + +# Lưu thư mục hiện tại (nơi chạy script, không phải nơi đặt script) +BASEDIR="$(pwd)" + +for dir in */ ; do + # Bỏ dấu '/' cuối + dirname="${dir%/}" + + # Cắt phần số trước dấu '.' và trim khoảng trắng + index=$(echo "$dirname" | cut -d'.' -f1 | tr -d '[:space:]') + + # Bỏ số 0 đầu nếu có + index_nozero=$(echo "$index" | sed 's/^0*//') + + # Nếu chuỗi rỗng (trường hợp '0') + if [ -z "$index_nozero" ]; then + index_nozero=0 + fi + + # Kiểm tra là số hợp lệ + if [[ "$index_nozero" =~ ^[0-9]+$ ]]; then + # Tính i-1 + i_minus_1=$((index_nozero - 1)) + + echo "===> Đang xử lý thư mục: $dirname (index: $index_nozero, i-1: $i_minus_1)" + + # Chuyển vào thư mục + cd "$dirname" || { echo "Không thể vào thư mục $dirname"; exit 1; } + + # Gọi online.sh với courseid từ dòng lệnh và i-1 + elearning.sh "$courseid" "$i_minus_1" + + # Quay lại thư mục gốc + cd "$BASEDIR" + else + echo "Bỏ qua $dirname (không bắt đầu bằng số)" + fi +done diff --git a/Moodle/PHP/ecreate_course.sh b/Moodle/PHP/ecreate_course.sh new file mode 100644 index 0000000..c558a53 --- /dev/null +++ b/Moodle/PHP/ecreate_course.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lấy thư mục hiện tại nơi người dùng chạy lệnh +CURRENT_DIR="$(pwd)" + +# Gọi PHP script, truyền tham số đầu vào và thư mục hiện tại +php /usr/bin/ecreate_moodle_course.php "$@" "$CURRENT_DIR" diff --git a/Moodle/PHP/ecreate_moodle_course.php b/Moodle/PHP/ecreate_moodle_course.php new file mode 100644 index 0000000..71d6301 --- /dev/null +++ b/Moodle/PHP/ecreate_moodle_course.php @@ -0,0 +1,113 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); +require_once($CFG->dirroot . '/course/externallib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if (count($argv) < 4) { + echo "❗Cách dùng: create_course \n"; + echo " Ví dụ : create_course 3 topics\n"; + exit(1); +} + +$categoryid = intval($argv[1]); +$format = $argv[2]; +$cwd = $argv[3]; // Thư mục làm việc thực tế + +$coursefullname = basename($cwd); +$shortname = preg_replace('/\s+/', '', $coursefullname); + +echo "📂 Thư mục hiện tại: $cwd\n"; +echo "📘 Tên khóa học: $coursefullname\n"; +echo "🔤 Shortname: $shortname\n"; +echo "📁 Category ID: $categoryid\n"; +echo "🧱 Course Format: $format\n"; + +// ✅ Lấy danh sách thư mục con để đếm số topic +$topics = array_filter(glob($cwd . '/*'), 'is_dir'); +$numsections = count($topics); +echo "🧩 Số topic được tạo: $numsections\n"; + +// ✅ Kiểm tra shortname đã tồn tại chưa +if ($DB->record_exists('course', ['shortname' => $shortname])) { + echo "⚠️ Shortname '$shortname' đã tồn tại. Dừng lại để tránh trùng.\n"; + exit(1); +} + +// ✅ Tạo khóa học +$course = new stdClass(); +$course->fullname = $coursefullname; +$course->shortname = $shortname; +$course->category = $categoryid; +$course->format = $format; +$course->numsections = $numsections; +$course->summary = $coursefullname; +$course->summaryformat = FORMAT_HTML; +$course->visible = 1; + +$newcourse = create_course($course); +echo "✅ Đã tạo khóa học thành công với ID: {$newcourse->id}\n"; + +// ✅ Đặt tên cho từng topic/section +foreach ($topics as $dir) { + $dirname = basename($dir); + $index = 0; + + if (preg_match('/^(\d+)/', $dirname, $matches)) { + $index = intval($matches[1]) - 1; + } + + $topicname = preg_replace('/^\d+\.\s*/', '', $dirname); + + $section = $DB->get_record('course_sections', [ + 'course' => $newcourse->id, + 'section' => $index + ]); + + if ($section) { + $section->name = $topicname; + $DB->update_record('course_sections', $section); + echo "🔁 Đổi tên topic [$index] thành: $topicname\n"; + } else { + $section = course_create_section($newcourse->id, $index); + $section->name = $topicname; + $section->summary = ''; + $section->summaryformat = FORMAT_HTML; + $DB->update_record('course_sections', $section); + echo "🆕 Tạo topic mới [$index]: $topicname\n"; + } +} + +// ✅ Ghi course ID vào file .env +$envFile = $cwd . '/.env'; +$courseIdLine = "COURSEID={$newcourse->id}"; + +if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + $found = false; + foreach ($lines as &$line) { + if (str_starts_with(trim($line), 'COURSEID=')) { + $line = $courseIdLine; + $found = true; + break; + } + } + if (!$found) { + $lines[] = $courseIdLine; + } + file_put_contents($envFile, implode(PHP_EOL, $lines) . PHP_EOL); + echo "📄 Đã cập nhật COURSEID vào .env\n"; +} else { + file_put_contents($envFile, $courseIdLine . PHP_EOL); + echo "📄 Đã tạo file .env với COURSEID={$newcourse->id}\n"; +} diff --git a/Moodle/PHP/edelete_course.php b/Moodle/PHP/edelete_course.php new file mode 100644 index 0000000..ab82f3e --- /dev/null +++ b/Moodle/PHP/edelete_course.php @@ -0,0 +1,42 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if ($argc < 2) { + echo "❗ Cách dùng: delete_course \n"; + echo " Ví dụ : delete_course 123\n"; + exit(1); +} + +$courseid = intval($argv[1]); + +if (!$course = $DB->get_record('course', ['id' => $courseid])) { + echo "⚠️ Không tìm thấy khóa học với ID: $courseid\n"; + exit(1); +} + +if ($course->id == SITEID) { + echo "❌ Không thể xóa site course (ID = SITEID).\n"; + exit(1); +} + +echo "⚠️ Bạn sắp xóa khóa học: [{$course->id}] {$course->fullname}\n"; + +// ✅ Thực hiện xóa +try { + delete_course($course, false); // false = không tái sắp xếp category + echo "✅ Đã xóa khóa học thành công.\n"; +} catch (Exception $e) { + echo "❌ Lỗi khi xóa khóa học: " . $e->getMessage() . "\n"; + exit(1); +} diff --git a/Moodle/PHP/elearning.sh b/Moodle/PHP/elearning.sh new file mode 100644 index 0000000..0c377d8 --- /dev/null +++ b/Moodle/PHP/elearning.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Kiểm tra số lượng tham số đầu vào +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Gán tham số đầu vào vào biến +COURSEID=$1 +TOPICID=$2 + +# Lấy đường dẫn thư mục hiện tại +CURRENT_DIR=$(pwd) + +# Chạy lệnh PHP với các tham số, sử dụng mdl.media từ thư mục hiện tại +/usr/local/lsws/lsphp82/bin/php /home/elearning.huph.edu.vn/public_html/elearning/page.php "$CURRENT_DIR/mdl.media" "$COURSEID" "$TOPICID" + +# Hiển thị thông báo hoàn tất +echo "Command executed with mdl.media from $CURRENT_DIR, courseid=$COURSEID, and topicid=$TOPICID" diff --git a/Moodle/PHP/enbatch_run.sh b/Moodle/PHP/enbatch_run.sh new file mode 100644 index 0000000..499d441 --- /dev/null +++ b/Moodle/PHP/enbatch_run.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# Kiểm tra có đối số không +if [ -z "$1" ]; then + echo "Usage: $(basename "$0") " + exit 1 +fi + +courseid="$1" + +# Lưu thư mục hiện tại (nơi chạy script, không phải nơi đặt script) +BASEDIR="$(pwd)" + +for dir in */ ; do + # Bỏ dấu '/' cuối + dirname="${dir%/}" + + # Cắt phần số trước dấu '.' và trim khoảng trắng + index=$(echo "$dirname" | cut -d'.' -f1 | tr -d '[:space:]') + + # Bỏ số 0 đầu nếu có + index_nozero=$(echo "$index" | sed 's/^0*//') + + # Nếu chuỗi rỗng (trường hợp '0') + if [ -z "$index_nozero" ]; then + index_nozero=0 + fi + + # Kiểm tra là số hợp lệ + if [[ "$index_nozero" =~ ^[0-9]+$ ]]; then + # Tính i-1 + i_minus_1=$((index_nozero - 1)) + + echo "===> Đang xử lý thư mục: $dirname (index: $index_nozero, i-1: $i_minus_1)" + + # Chuyển vào thư mục + cd "$dirname" || { echo "Không thể vào thư mục $dirname"; exit 1; } + + # Gọi online.sh với courseid từ dòng lệnh và i-1 + english.sh "$courseid" "$i_minus_1" + + # Quay lại thư mục gốc + cd "$BASEDIR" + else + echo "Bỏ qua $dirname (không bắt đầu bằng số)" + fi +done diff --git a/Moodle/PHP/encreate_course.sh b/Moodle/PHP/encreate_course.sh new file mode 100644 index 0000000..3f5bed6 --- /dev/null +++ b/Moodle/PHP/encreate_course.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lấy thư mục hiện tại nơi người dùng chạy lệnh +CURRENT_DIR="$(pwd)" + +# Gọi PHP script, truyền tham số đầu vào và thư mục hiện tại +php /usr/bin/encreate_moodle_course.php "$@" "$CURRENT_DIR" diff --git a/Moodle/PHP/encreate_moodle_course.php b/Moodle/PHP/encreate_moodle_course.php new file mode 100644 index 0000000..f80f6c5 --- /dev/null +++ b/Moodle/PHP/encreate_moodle_course.php @@ -0,0 +1,113 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); +require_once($CFG->dirroot . '/course/externallib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if (count($argv) < 4) { + echo "❗Cách dùng: create_course \n"; + echo " Ví dụ : create_course 3 topics\n"; + exit(1); +} + +$categoryid = intval($argv[1]); +$format = $argv[2]; +$cwd = $argv[3]; // Thư mục làm việc thực tế + +$coursefullname = basename($cwd); +$shortname = preg_replace('/\s+/', '', $coursefullname); + +echo "📂 Thư mục hiện tại: $cwd\n"; +echo "📘 Tên khóa học: $coursefullname\n"; +echo "🔤 Shortname: $shortname\n"; +echo "📁 Category ID: $categoryid\n"; +echo "🧱 Course Format: $format\n"; + +// ✅ Lấy danh sách thư mục con để đếm số topic +$topics = array_filter(glob($cwd . '/*'), 'is_dir'); +$numsections = count($topics); +echo "🧩 Số topic được tạo: $numsections\n"; + +// ✅ Kiểm tra shortname đã tồn tại chưa +if ($DB->record_exists('course', ['shortname' => $shortname])) { + echo "⚠️ Shortname '$shortname' đã tồn tại. Dừng lại để tránh trùng.\n"; + exit(1); +} + +// ✅ Tạo khóa học +$course = new stdClass(); +$course->fullname = $coursefullname; +$course->shortname = $shortname; +$course->category = $categoryid; +$course->format = $format; +$course->numsections = $numsections; +$course->summary = $coursefullname; +$course->summaryformat = FORMAT_HTML; +$course->visible = 1; + +$newcourse = create_course($course); +echo "✅ Đã tạo khóa học thành công với ID: {$newcourse->id}\n"; + +// ✅ Đặt tên cho từng topic/section +foreach ($topics as $dir) { + $dirname = basename($dir); + $index = 0; + + if (preg_match('/^(\d+)/', $dirname, $matches)) { + $index = intval($matches[1]) - 1; + } + + $topicname = preg_replace('/^\d+\.\s*/', '', $dirname); + + $section = $DB->get_record('course_sections', [ + 'course' => $newcourse->id, + 'section' => $index + ]); + + if ($section) { + $section->name = $topicname; + $DB->update_record('course_sections', $section); + echo "🔁 Đổi tên topic [$index] thành: $topicname\n"; + } else { + $section = course_create_section($newcourse->id, $index); + $section->name = $topicname; + $section->summary = ''; + $section->summaryformat = FORMAT_HTML; + $DB->update_record('course_sections', $section); + echo "🆕 Tạo topic mới [$index]: $topicname\n"; + } +} + +// ✅ Ghi course ID vào file .env +$envFile = $cwd . '/.env'; +$courseIdLine = "COURSEID={$newcourse->id}"; + +if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + $found = false; + foreach ($lines as &$line) { + if (str_starts_with(trim($line), 'COURSEID=')) { + $line = $courseIdLine; + $found = true; + break; + } + } + if (!$found) { + $lines[] = $courseIdLine; + } + file_put_contents($envFile, implode(PHP_EOL, $lines) . PHP_EOL); + echo "📄 Đã cập nhật COURSEID vào .env\n"; +} else { + file_put_contents($envFile, $courseIdLine . PHP_EOL); + echo "📄 Đã tạo file .env với COURSEID={$newcourse->id}\n"; +} diff --git a/Moodle/PHP/encreatecourse.php b/Moodle/PHP/encreatecourse.php new file mode 100644 index 0000000..1f0de96 --- /dev/null +++ b/Moodle/PHP/encreatecourse.php @@ -0,0 +1,114 @@ +#!/usr/bin/env php +libdir . '/clilib.php'); +require_once($CFG->dirroot . '/course/lib.php'); +require_once($CFG->dirroot . '/course/externallib.php'); + +// 🛠 Nhận tham số dòng lệnh +global $argv; + +if (count($argv) < 4) { + echo "❗Cách dùng: create_course_list \n"; + echo " Ví dụ : create_course_list 3 topics\n"; + exit(1); +} + +$categoryid = intval($argv[1]); +$format = $argv[2]; +$cwd = $argv[3]; // Thư mục đang thao tác (nơi chứa list.txt) + +// 🧾 Đọc tên khóa học từ thư mục +$coursefullname = basename($cwd); +$shortname = preg_replace('/\s+/', '', $coursefullname); + +echo "📂 Thư mục hiện tại: $cwd\n"; +echo "📘 Tên khóa học: $coursefullname\n"; +echo "🔤 Shortname: $shortname\n"; +echo "📁 Category ID: $categoryid\n"; +echo "🧱 Course Format: $format\n"; + +// 📄 Đọc danh sách topic từ file list.txt +$listfile = $cwd . '/list.txt'; + +if (!file_exists($listfile)) { + echo "❌ Không tìm thấy file list.txt trong thư mục: $cwd\n"; + exit(1); +} + +$lines = file($listfile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); +$numsections = count($lines); +echo "🧩 Số topic từ list.txt: $numsections\n"; + +// ✅ Kiểm tra shortname đã tồn tại chưa +if ($DB->record_exists('course', ['shortname' => $shortname])) { + echo "⚠️ Shortname '$shortname' đã tồn tại. Dừng lại để tránh trùng.\n"; + exit(1); +} + +// ✅ Tạo khóa học +$course = new stdClass(); +$course->fullname = $coursefullname; +$course->shortname = $shortname; +$course->category = $categoryid; +$course->format = $format; +$course->numsections = $numsections; +$course->summary = $coursefullname; +$course->summaryformat = FORMAT_HTML; +$course->visible = 1; + +$newcourse = create_course($course); +echo "✅ Đã tạo khóa học thành công với ID: {$newcourse->id}\n"; + +// ✅ Tạo và đặt tên cho từng topic từ danh sách +foreach ($lines as $i => $topicname) { + $index = $i; // Bắt đầu từ 0 + + $section = $DB->get_record('course_sections', [ + 'course' => $newcourse->id, + 'section' => $index + ]); + + if ($section) { + $section->name = trim($topicname); + $DB->update_record('course_sections', $section); + echo "🔁 Đặt tên topic [$index] thành: $topicname\n"; + } else { + $section = course_create_section($newcourse->id, $index); + $section->name = trim($topicname); + $section->summary = ''; + $section->summaryformat = FORMAT_HTML; + $DB->update_record('course_sections', $section); + echo "🆕 Tạo topic [$index]: $topicname\n"; + } +} + +// ✅ Ghi course ID vào file .env +$envFile = $cwd . '/.env'; +$courseIdLine = "COURSEID={$newcourse->id}"; + +if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + $found = false; + foreach ($lines as &$line) { + if (str_starts_with(trim($line), 'COURSEID=')) { + $line = $courseIdLine; + $found = true; + break; + } + } + if (!$found) { + $lines[] = $courseIdLine; + } + file_put_contents($envFile, implode(PHP_EOL, $lines) . PHP_EOL); + echo "📄 Đã cập nhật COURSEID vào .env\n"; +} else { + file_put_contents($envFile, $courseIdLine . PHP_EOL); + echo "📄 Đã tạo file .env với COURSEID={$newcourse->id}\n"; +} diff --git a/Moodle/PHP/encreatecourse.sh b/Moodle/PHP/encreatecourse.sh new file mode 100644 index 0000000..770b73c --- /dev/null +++ b/Moodle/PHP/encreatecourse.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lấy thư mục hiện tại nơi người dùng chạy lệnh +CURRENT_DIR="$(pwd)" + +# Gọi PHP script, truyền tham số đầu vào và thư mục hiện tại +php /usr/bin/encreatecourse.php "$@" "$CURRENT_DIR" diff --git a/Moodle/PHP/english.sh b/Moodle/PHP/english.sh new file mode 100644 index 0000000..503a7c1 --- /dev/null +++ b/Moodle/PHP/english.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Kiểm tra số lượng tham số đầu vào +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Gán tham số đầu vào vào biến +COURSEID=$1 +TOPICID=$2 + +# Lấy đường dẫn thư mục hiện tại +CURRENT_DIR=$(pwd) + +# Chạy lệnh PHP với các tham số, sử dụng mdl.media từ thư mục hiện tại +/usr/local/lsws/lsphp82/bin/php /home/english.huph.edu.vn/public_html/english/page.php "$CURRENT_DIR/mdl.media" "$COURSEID" "$TOPICID" + +# Hiển thị thông báo hoàn tất +echo "Command executed with mdl.media from $CURRENT_DIR, courseid=$COURSEID, and topicid=$TOPICID" diff --git a/Moodle/PHP/genmedia.sh b/Moodle/PHP/genmedia.sh new file mode 100644 index 0000000..542504a --- /dev/null +++ b/Moodle/PHP/genmedia.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Thư mục gốc của khóa học +COURSE_FOLDER="$(pwd)" + +# Tìm tất cả các thư mục con +mapfile -t DIRS < <(find "$COURSE_FOLDER" -type d) + +for dir in "${DIRS[@]}"; do + relative_path="${dir#*/vod/}" + OUTPUT_JSON="$dir/mdl.media" + + # Xóa tệp mdl.media cũ nếu tồn tại + rm -f "$OUTPUT_JSON" + + # Tìm và sắp xếp các file .mp3 và .mp4 theo thứ tự tự nhiên + mapfile -d '' -t FILES < <(find "$dir" -maxdepth 1 -type f \( -name '*.mp3' -o -name '*.mp4' \) -print0 | sort -z -V) + + for file in "${FILES[@]}"; do + filename=$(basename "$file") + vid="$relative_path/$filename" + echo "[stream=$vid]" >> "$OUTPUT_JSON" + done + + echo "File mdl.media created successfully in $dir." +done diff --git a/Moodle/PHP/genmedia.v.1.sh b/Moodle/PHP/genmedia.v.1.sh new file mode 100644 index 0000000..86d3e33 --- /dev/null +++ b/Moodle/PHP/genmedia.v.1.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Thư mục gốc của khóa học +COURSE_FOLDER="$(pwd)" + +# Hàm xử lý tạo mdl.media cho mỗi thư mục +process_dir() { + local dir="$1" + local relative_path="${dir#*/vod/}" + + # Đường dẫn đến tệp mdl.media trong thư mục + OUTPUT_JSON="$dir/mdl.media" + + # Xóa tệp mdl.media cũ nếu tồn tại + rm -f "$OUTPUT_JSON" + + # Tìm và sắp xếp các file .mp4 theo thứ tự tự nhiên (hỗ trợ cả số có hoặc không có leading zeros) + find "$dir" -maxdepth 1 -type f -name '*.mp4' -print0 | sort -z -V | while IFS= read -r -d '' file; do + if [ -f "$file" ]; then + filename=$(basename "$file") + vid="$relative_path/$filename" + echo "[stream=$vid]" >> "$OUTPUT_JSON" + fi + done + + echo "File mdl.media created successfully in $dir." +} + +# Duyệt qua thư mục gốc và tất cả các thư mục con, gọi hàm xử lý +find "$COURSE_FOLDER" -type d | while IFS= read -r dir; do + process_dir "$dir" +done diff --git a/Moodle/PHP/genmp3.sh b/Moodle/PHP/genmp3.sh new file mode 100644 index 0000000..e6f3d63 --- /dev/null +++ b/Moodle/PHP/genmp3.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Thư mục gốc của khóa học +COURSE_FOLDER="$(pwd)" + +# Hàm xử lý tạo mdl.media cho mỗi thư mục +process_dir() { + local dir="$1" + local relative_path="${dir#*/vod/}" + + # Đường dẫn đến tệp mdl.media trong thư mục + OUTPUT_JSON="$dir/mdl.media" + + # Xóa tệp mdl.media cũ nếu tồn tại + rm -f "$OUTPUT_JSON" + + # Tìm và sắp xếp các file .mp3 theo thứ tự tự nhiên (hỗ trợ cả số có hoặc không có leading zeros) + find "$dir" -maxdepth 1 -type f -name '*.mp3' -print0 | sort -z -V | while IFS= read -r -d '' file; do + if [ -f "$file" ]; then + filename=$(basename "$file") + vid="$relative_path/$filename" + echo "[stream=$vid]" >> "$OUTPUT_JSON" + fi + done + + echo "File mdl.media created successfully in $dir." +} + +# Duyệt qua thư mục gốc và tất cả các thư mục con, gọi hàm xử lý +find "$COURSE_FOLDER" -type d | while IFS= read -r dir; do + process_dir "$dir" +done diff --git a/Moodle/PHP/gets.sh b/Moodle/PHP/gets.sh new file mode 100644 index 0000000..7192dbb --- /dev/null +++ b/Moodle/PHP/gets.sh @@ -0,0 +1,2 @@ +wget --no-check-certificate --content-disposition --max-redirect=10 --user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36" --referer="https://en.git.ir/" -i list.txt + diff --git a/Moodle/PHP/gettext.sh b/Moodle/PHP/gettext.sh new file mode 100644 index 0000000..342ca47 --- /dev/null +++ b/Moodle/PHP/gettext.sh @@ -0,0 +1,135 @@ +#! /bin/sh +# +# Copyright (C) 2003, 2005-2007, 2011, 2018-2020 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# + +# Find a way to echo strings without interpreting backslash. +if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then + echo='echo' +else + if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then + echo='printf %s\n' + else + echo_func () { + cat < +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law." + echo "Written by" "Bruno Haible" + } + if test $# = 1; then + case "$1" in + --help | --hel | --he | --h ) + func_usage; exit 0 ;; + --version | --versio | --versi | --vers | --ver | --ve | --v ) + func_version; exit 0 ;; + esac + fi + func_usage 1>&2 + exit 1 + ;; + esac +fi + +# eval_gettext MSGID +# looks up the translation of MSGID and substitutes shell variables in the +# result. +eval_gettext () { + gettext "$1" | (export PATH `envsubst --variables "$1"`; envsubst "$1") +} + +# eval_ngettext MSGID MSGID-PLURAL COUNT +# looks up the translation of MSGID / MSGID-PLURAL for COUNT and substitutes +# shell variables in the result. +eval_ngettext () { + ngettext "$1" "$2" "$3" | (export PATH `envsubst --variables "$1 $2"`; envsubst "$1 $2") +} + +# eval_pgettext MSGCTXT MSGID +# looks up the translation of MSGID in the context MSGCTXT and substitutes +# shell variables in the result. +eval_pgettext () { + gettext --context="$1" "$2" | (export PATH `envsubst --variables "$2"`; envsubst "$2") +} + +# eval_npgettext MSGCTXT MSGID MSGID-PLURAL COUNT +# looks up the translation of MSGID / MSGID-PLURAL for COUNT in the context +# MSGCTXT and substitutes shell variables in the result. +eval_npgettext () { + ngettext --context="$1" "$2" "$3" "$4" | (export PATH `envsubst --variables "$2 $3"`; envsubst "$2 $3") +} + +# Note: This use of envsubst is much safer than using the shell built-in 'eval' +# would be. +# 1) The security problem with Chinese translations that happen to use a +# character such as \xe0\x60 is avoided. +# 2) The security problem with malevolent translators who put in command lists +# like "$(...)" or "`...`" is avoided. +# 3) The translations can only refer to shell variables that are already +# mentioned in MSGID or MSGID-PLURAL. +# +# Note: "export PATH" above is a dummy; this is for the case when +# `envsubst --variables ...` returns nothing. +# +# Note: In eval_ngettext above, "$1 $2" means a string whose variables set is +# the union of the variables set of "$1" and "$2". +# +# Note: The minimal use of backquote above ensures that trailing newlines are +# not dropped, not from the gettext invocation and not from the value of any +# shell variable. +# +# Note: Field splitting on the `envsubst --variables ...` result is desired, +# since envsubst outputs the variables, separated by newlines. Pathname +# wildcard expansion or tilde expansion has no effect here, since the words +# output by "envsubst --variables ..." consist solely of alphanumeric +# characters and underscore. diff --git a/Moodle/PHP/hsphnline.sh b/Moodle/PHP/hsphnline.sh new file mode 100644 index 0000000..ee61e7c --- /dev/null +++ b/Moodle/PHP/hsphnline.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Kiểm tra số lượng tham số đầu vào +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Gán tham số đầu vào vào biến +COURSEID=$1 +TOPICID=$2 + +# Lấy đường dẫn thư mục hiện tại +CURRENT_DIR=$(pwd) + +# Chạy lệnh PHP với các tham số, sử dụng mdl.media từ thư mục hiện tại +php /home/online.linkvn.vn/public_html/online/objpage.php "$CURRENT_DIR/mdl.media" "$COURSEID" "$TOPICID" + +# Hiển thị thông báo hoàn tất +echo "Command executed with mdl.media from $CURRENT_DIR, courseid=$COURSEID, and topicid=$TOPICID" diff --git a/Moodle/PHP/hsphonline.sh b/Moodle/PHP/hsphonline.sh new file mode 100644 index 0000000..d6e6c9c --- /dev/null +++ b/Moodle/PHP/hsphonline.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Kiểm tra số lượng tham số đầu vào +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Gán tham số đầu vào vào biến +COURSEID=$1 +TOPICID=$2 + +# Lấy đường dẫn thư mục hiện tại +CURRENT_DIR=$(pwd) + +# Chạy lệnh PHP với các tham số, sử dụng mdl.media từ thư mục hiện tại +php /home/online.linkvn.vn/public_html/online/page.php "$CURRENT_DIR/mdl.media" "$COURSEID" "$TOPICID" + +# Hiển thị thông báo hoàn tất +echo "Command executed with mdl.media from $CURRENT_DIR, courseid=$COURSEID, and topicid=$TOPICID" diff --git a/Moodle/move_pages_to_topics.php b/Moodle/PHP/move_pages_to_topics.php similarity index 100% rename from Moodle/move_pages_to_topics.php rename to Moodle/PHP/move_pages_to_topics.php diff --git a/Moodle/PHP/objonline.sh b/Moodle/PHP/objonline.sh new file mode 100644 index 0000000..ee61e7c --- /dev/null +++ b/Moodle/PHP/objonline.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Kiểm tra số lượng tham số đầu vào +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Gán tham số đầu vào vào biến +COURSEID=$1 +TOPICID=$2 + +# Lấy đường dẫn thư mục hiện tại +CURRENT_DIR=$(pwd) + +# Chạy lệnh PHP với các tham số, sử dụng mdl.media từ thư mục hiện tại +php /home/online.linkvn.vn/public_html/online/objpage.php "$CURRENT_DIR/mdl.media" "$COURSEID" "$TOPICID" + +# Hiển thị thông báo hoàn tất +echo "Command executed with mdl.media from $CURRENT_DIR, courseid=$COURSEID, and topicid=$TOPICID" diff --git a/Moodle/PHP/online.sh b/Moodle/PHP/online.sh new file mode 100644 index 0000000..5c64e4f --- /dev/null +++ b/Moodle/PHP/online.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Kiểm tra số lượng tham số đầu vào +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Gán tham số đầu vào vào biến + +COURSEID=$1 +TOPICID=$2 + +# Lấy đường dẫn thư mục hiện tại +CURRENT_DIR=$(pwd) + +# Chạy lệnh PHP với các tham số, sử dụng mdl.media từ thư mục hiện tại +/usr/local/lsws/lsphp82/bin/php /home/online.huph.edu.vn/public_html/online/page.php "$CURRENT_DIR/mdl.media" "$COURSEID" "$TOPICID" + +# Hiển thị thông báo hoàn tất +echo "Command executed with mdl.media from $CURRENT_DIR, courseid=$COURSEID, and topicid=$TOPICID" diff --git a/Moodle/PHP/removeblank.sh b/Moodle/PHP/removeblank.sh new file mode 100644 index 0000000..d179808 --- /dev/null +++ b/Moodle/PHP/removeblank.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +BASE_DIR="." + +find "$BASE_DIR" -depth | while IFS= read -r path; do + current_name=$(basename "$path") + parent_dir=$(dirname "$path") + + # Loại bỏ ký tự đặc biệt: # ! ' [ ] @ bằng tr + tr -d + new_name=$(echo "$current_name" | tr -d "#!'\[\]@") + + # Loại bỏ khoảng trắng thừa (chỉ giữ 1 dấu cách, loại bỏ cuối) + new_name=$(echo "$new_name" | tr -s ' ' | sed 's/ *$//') + + # Nếu tên thay đổi thì đổi tên + if [[ "$current_name" != "$new_name" ]]; then + mv -- "$path" "$parent_dir/$new_name" + echo "Đã đổi tên: '$path' → '$parent_dir/$new_name'" + fi +done + +echo "✅ Hoàn thành." diff --git a/Moodle/PHP/rename_topics.sh b/Moodle/PHP/rename_topics.sh new file mode 100644 index 0000000..59302c5 --- /dev/null +++ b/Moodle/PHP/rename_topics.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Lấy thư mục hiện hành nơi người dùng đang gọi lệnh +TARGET_DIR="$(pwd)" + +# Đổi tên các thư mục con cấp 1 theo mẫu: 1 - ABC → 1. ABC +find "$TARGET_DIR" -mindepth 1 -maxdepth 1 -type d -regextype posix-extended -regex '.*/[0-9]+ - .+' | while read dir; do + base=$(basename "$dir") + newname=$(echo "$base" | sed -E 's/^([0-9]+) - (.+)$/\1. \2/') + if [ "$base" != "$newname" ]; then + echo "🔁 Đổi tên: $base → $newname" + mv "$TARGET_DIR/$base" "$TARGET_DIR/$newname" + fi +done diff --git a/Moodle/PHP/rescan-scsi-bus.sh b/Moodle/PHP/rescan-scsi-bus.sh new file mode 100644 index 0000000..89d6422 --- /dev/null +++ b/Moodle/PHP/rescan-scsi-bus.sh @@ -0,0 +1,1401 @@ +#!/bin/bash +# Script to rescan SCSI bus, using the scsi add-single-device mechanism. +# (c) 1998--2010 Kurt Garloff , GNU GPL v2 or v3 +# (c) 2006--2018 Hannes Reinecke, GNU GPL v2 or later +# $Id: rescan-scsi-bus.sh,v 1.57 2012/03/31 14:08:48 garloff Exp $ + +VERSION="20180615" +SCAN_WILD_CARD=4294967295 + +setcolor () +{ + red="\e[0;31m" + green="\e[0;32m" + yellow="\e[0;33m" + bold="\e[0;1m" + norm="\e[0;0m" +} + +unsetcolor () +{ + red=""; green="" + yellow=""; norm="" +} + +echo_debug() +{ + if [ "$debug" -eq 1 ] ; then + echo "$1" + fi +} + +# Output some text and return cursor to previous position +# (only works for simple strings) +# Stores length of string in LN and returns it +print_and_scroll_back () +{ + STRG="$1" + LN=${#STRG} + BK="" + declare -i cntr=0 + while [ $cntr -lt "$LN" ] ; do BK="$BK\e[D"; let cntr+=1; done + echo -en "$STRG$BK" + return "$LN" +} + +# Overwrite a text of length $LN with whitespace +white_out () +{ + BK=""; WH="" + declare -i cntr=0 + while [ $cntr -lt "$LN" ] ; do BK="$BK\e[D"; WH="$WH "; let cntr+=1; done + echo -en "$WH$BK" +} + +# Return hosts. sysfs must be mounted +findhosts_26 () +{ + hosts= + for hostdir in /sys/class/scsi_host/host* ; do + [ -e "$hostdir" ] || continue + hostno=${hostdir#/sys/class/scsi_host/host} + if [ -f "$hostdir/isp_name" ] ; then + hostname="qla2xxx" + elif [ -f "$hostdir/lpfc_drvr_version" ] ; then + hostname="lpfc" + else + hostname=$(cat "$hostdir/proc_name") + fi + hosts="$hosts $hostno" + echo_debug "Host adapter $hostno ($hostname) found." + done + if [ -z "$hosts" ] ; then + echo "No SCSI host adapters found in sysfs" + exit 1; + fi + # Not necessary just use double quotes around variable to preserve new lines + #hosts=$(echo $hosts | tr ' ' '\n') +} + +# Return hosts. /proc/scsi/HOSTADAPTER/? must exist +findhosts () +{ + hosts= + for driverdir in /proc/scsi/*; do + driver=${driverdir#/proc/scsi/} + if [ "$driver" = scsi ] || [ "$driver" = sg ] || [ "$driver" = dummy ] || [ "$driver" = device_info ] ; then continue; fi + for hostdir in $driverdir/*; do + name=${hostdir#/proc/scsi/*/} + if [ "$name" = add_map ] || [ "$name" = map ] || [ "$name" = mod_parm ] ; then continue; fi + num=$name + driverinfo=$driver + if [ -r "$hostdir/status" ] ; then + num=$(printf '%d\n' "$(sed -n 's/SCSI host number://p' "$hostdir/status")") + driverinfo="$driver:$name" + fi + hosts="$hosts $num" + echo "Host adapter $num ($driverinfo) found." + done + done +} + +printtype () +{ + local type=$1 + + case "$type" in + 0) echo "Direct-Access" ;; + 1) echo "Sequential-Access" ;; + 2) echo "Printer" ;; + 3) echo "Processor" ;; + 4) echo "WORM" ;; + 5) echo "CD-ROM" ;; + 6) echo "Scanner" ;; + 7) echo "Optical-Device" ;; + 8) echo "Medium-Changer" ;; + 9) echo "Communications" ;; + 10) echo "Unknown" ;; + 11) echo "Unknown" ;; + 12) echo "RAID" ;; + 13) echo "Enclosure" ;; + 14) echo "Direct-Access-RBC" ;; + *) echo "Unknown" ;; + esac +} + +print02i() +{ + if [ "$1" = "*" ] ; then + echo "00" + else + printf "%02i" "$1" + fi +} + +# Get /proc/scsi/scsi info for device $host:$channel:$id:$lun +# Optional parameter: Number of lines after first (default = 2), +# result in SCSISTR, return code 1 means empty. +procscsiscsi () +{ + if [ -z "$1" ] ; then + LN=2 + else + LN=$1 + fi + CHANNEL=$(print02i "$channel") + ID=$(print02i "$id") + LUN=$(print02i "$lun") + if [ -d /sys/class/scsi_device ]; then + SCSIPATH="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}" + if [ -d "$SCSIPATH" ] ; then + SCSISTR="Host: scsi${host} Channel: $CHANNEL Id: $ID Lun: $LUN" + if [ "$LN" -gt 0 ] ; then + IVEND=$(cat "${SCSIPATH}/device/vendor") + IPROD=$(cat "${SCSIPATH}/device/model") + IPREV=$(cat "${SCSIPATH}/device/rev") + SCSIDEV=$(printf ' Vendor: %-08s Model: %-16s Rev: %-4s' "$IVEND" "$IPROD" "$IPREV") + SCSISTR="$SCSISTR +$SCSIDEV" + fi + if [ "$LN" -gt 1 ] ; then + ILVL=$(cat "${SCSIPATH}/device/scsi_level") + type=$(cat "${SCSIPATH}/device/type") + ITYPE=$(printtype "$type") + SCSITMP=$(printf ' Type: %-17s ANSI SCSI revision: %02d' "$ITYPE" "$((ILVL - 1))") + SCSISTR="$SCSISTR +$SCSITMP" + fi + else + return 1 + fi + else + grepstr="scsi$host Channel: $CHANNEL Id: $ID Lun: $LUN" + SCSISTR=$(grep -A "$LN" -e "$grepstr" /proc/scsi/scsi) + fi + if [ -z "$SCSISTR" ] ; then + return 1 + else + return 0 + fi +} + +# Find sg device with 2.6 sysfs support +sgdevice26 () +{ + local gendev + + gendev=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/generic + if [ -e "$gendev" ] ; then + SGDEV=$(basename "$(readlink "$gendev")") + else + for SGDEV in /sys/class/scsi_generic/sg*; do + DEV=$(readlink "$SGDEV/device") + if [ "${DEV##*/}" = "$host:$channel:$id:$lun" ] ; then + SGDEV=$(basename "$SGDEV"); return + fi + done + SGDEV="" + fi +} + +# Find sg device with 2.4 report-devs extensions +sgdevice24 () +{ + if procscsiscsi 3; then + SGDEV=$(echo "$SCSISTR" | grep 'Attached drivers:' | sed 's/^ *Attached drivers: \(sg[0-9]*\).*/\1/') + fi +} + +# Find sg device that belongs to SCSI device $host $channel $id $lun +# and return in SGDEV +sgdevice () +{ + SGDEV= + if [ -d /sys/class/scsi_device ] ; then + sgdevice26 + else + DRV=$(grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null) + repdevstat=$((1-$?)) + if [ $repdevstat = 0 ]; then + echo "scsi report-devs 1" >/proc/scsi/scsi + DRV=$(grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null) + [ $? -eq 1 ] && return + fi + if ! echo "$DRV" | grep -q 'drivers: sg'; then + modprobe sg + fi + sgdevice24 + if [ $repdevstat = 0 ]; then + echo "scsi report-devs 0" >/proc/scsi/scsi + fi + fi +} + +# Whether or not the RMB (removable) bit has been set in the INQUIRY response. +# Uses ${host}, ${channel}, ${id} and ${lun}. Assumes that sg_device() has +# already been called. How to test this function: copy/paste this function +# in a shell and run +# (cd /sys/class/scsi_device && for d in *; do set ${d//:/ }; echo -n "$d $( "; SGDEV=bsg/$d host=$1 channel=$2 id=$3 lun=$4 is_removable; done) +is_removable () +{ + local b p + + p=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/inquiry + # Extract the second byte of the INQUIRY response and check bit 7 (mask 0x80). + b=$(hexdump -n1 -e '/1 "%02X"' "$p" 2>/dev/null) + if [ -n "$b" ]; then + echo $(((0x$b & 0x80) != 0)) + else + sg_inq /dev/$SGDEV 2>/dev/null | sed -n 's/^.*RMB=\([0-9]*\).*$/\1/p' + fi +} + +# Test if SCSI device is still responding to commands +# Return values: +# 0 device is present +# 1 device has changed +# 2 device has been removed +testonline () +{ + local ctr RC RMB + + : testonline + ctr=0 + RC=0 + # Set default values + IPTYPE=31 + IPQUAL=3 + [ ! -x /usr/bin/sg_turs ] && return 0 + sgdevice + [ -z "$SGDEV" ] && return 0 + sg_turs /dev/$SGDEV >/dev/null 2>&1 + RC=$? + + # Handle in progress of becoming ready and unit attention + while [ $RC = 2 -o $RC = 6 ] && [ $ctr -le 30 ] ; do + if [ $RC = 2 ] && [ "$RMB" != "1" ] ; then + echo -n "." + let LN+=1 + sleep 1 + else + sleep 0.02 + fi + let ctr+=1 + sg_turs /dev/$SGDEV >/dev/null 2>&1 + RC=$? + # Check for removable device; TEST UNIT READY obviously will + # fail for a removable device with no medium + RMB=$(is_removable) + print_and_scroll_back "$host:$channel:$id:$lun $SGDEV ($RMB) " + [ $RC = 2 ] && [ "$RMB" = "1" ] && break + done + if [ $ctr != 0 ] ; then + white_out + fi + # echo -e "\e[A\e[A\e[A${yellow}Test existence of $SGDEV = $RC ${norm} \n\n\n" + [ $RC = 1 ] && return $RC + # Reset RC (might be !=0 for passive paths) + RC=0 + # OK, device online, compare INQUIRY string + INQ=$(sg_inq "$sg_len_arg" /dev/$SGDEV 2>/dev/null) + if [ -z "$INQ" ] ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}INQUIRY failed${norm} \n\n\n" + return 2 + fi + IVEND=$(echo "$INQ" | grep 'Vendor identification:' | sed 's/^[^:]*: \(.*\)$/\1/') + IPROD=$(echo "$INQ" | grep 'Product identification:' | sed 's/^[^:]*: \(.*\)$/\1/') + IPREV=$(echo "$INQ" | grep 'Product revision level:' | sed 's/^[^:]*: \(.*\)$/\1/') + STR=$(printf " Vendor: %-08s Model: %-16s Rev: %-4s" "$IVEND" "$IPROD" "$IPREV") + IPTYPE=$(echo "$INQ" | sed -n 's/.* Device_type=\([0-9]*\) .*/\1/p') + if [ -z "$IPTYPE" ]; then + IPTYPE=$(echo "$INQ" | sed -n 's/.* PDT=\([0-9]*\) .*/\1/p') + fi + IPQUAL=$(echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) Device.*/\1/p') + if [ -z "$IPQUAL" ] ; then + IPQUAL=$(echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) PDT.*/\1/p') + fi + if [ "$IPQUAL" != 0 ] ; then + [ -z "$IPQUAL" ] && IPQUAL=3 + [ -z "$IPTYPE" ] && IPTYPE=31 + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}LU not available (PQual $IPQUAL)${norm} \n\n\n" + return 2 + fi + + TYPE=$(printtype $IPTYPE) + if ! procscsiscsi ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV removed.\n\n\n" + return 2 + fi + TMPSTR=$(echo "$SCSISTR" | grep 'Vendor:') + if [ "$ignore_rev" -eq 0 ] ; then + if [ "$TMPSTR" != "$STR" ]; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n" + return 1 + fi + else + # Ignore disk revision change + local old_str_no_rev= + local new_str_no_rev= + + old_str_no_rev=${TMPSTR%Rev:*} + new_str_no_rev=${STR%Rev:*} + if [ "$old_str_no_rev" != "$new_str_no_rev" ]; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n" + return 1 + fi + fi + TMPSTR=$(echo "$SCSISTR" | sed -n 's/.*Type: *\(.*\) *ANSI.*/\1/p' | sed 's/ *$//g') + if [ "$TMPSTR" != "$TYPE" ] ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${TMPSTR} \nto: $TYPE ${norm} \n\n\n" + return 1 + fi + return $RC +} + +# Test if SCSI device $host $channel $id $lun exists +# Outputs description from /proc/scsi/scsi (unless arg passed) +# Returns SCSISTR (empty if no dev) +testexist () +{ + : testexist + SCSISTR= + if procscsiscsi && [ -z "$1" ] ; then + echo "$SCSISTR" | head -n1 + echo "$SCSISTR" | tail -n2 | pr -o4 -l1 + fi +} + +# Returns the list of existing channels per host +chanlist () +{ + local hcil + local cil + local chan + local tmpchan + + for dev in /sys/class/scsi_device/${host}:* ; do + [ -d "$dev" ] || continue; + hcil=${dev##*/} + cil=${hcil#*:} + chan=${cil%%:*} + for tmpchan in $channelsearch ; do + if [ "$chan" -eq "$tmpchan" ] ; then + chan= + fi + done + if [ -n "$chan" ] ; then + channelsearch="$channelsearch $chan" + fi + done + if [ -z "$channelsearch" ] ; then + channelsearch="0" + fi +} + +# Returns the list of existing targets per host +idlist () +{ + local tmpid + local newid + local oldid + + oldlist=$(find /sys/class/scsi_device -name "${host}:${channel}:*" -printf "%f\n") + # Rescan LUN 0 to check if we found new targets + echo "${channel} - -" > "/sys/class/scsi_host/host${host}/scan" + newlist=$(find /sys/class/scsi_device -name "${host}:${channel}:*" -printf "%f\n") + for newid in $newlist ; do + oldid=$newid + for tmpid in $oldlist ; do + if [ "$newid" = "$tmpid" ] ; then + oldid= + break + fi + done + if [ -n "$oldid" ] ; then + if [ -d /sys/class/scsi_device/$oldid ] ; then + hcil=${oldid} + printf "\r${green}NEW: %s" "$norm" + testexist + if [ "$SCSISTR" ] ; then + incrfound "$hcil" + fi + fi + fi + done + idsearch=$(find /sys/bus/scsi/devices -name "target${host}:${channel}:*" -printf "%f\n" | cut -f 3 -d :) +} + +# Returns the list of existing LUNs from device $host $channel $id $lun +# and returns list to stdout +getluns() +{ + sgdevice + [ -z "$SGDEV" ] && return 1 + if [ ! -x /usr/bin/sg_luns ] ; then + echo 0 + return 1 + fi + LLUN=$(sg_luns /dev/$SGDEV 2>/dev/null | sed -n 's/ \(.*\)/\1/p') + # Added -z $LLUN condition because $? gets the RC from sed, not sg_luns + if [ $? -ne 0 ] || [ -z "$LLUN" ] ; then + echo 0 + return 1 + fi + for lun in $LLUN ; do + # Swap LUN number + l0=0x$lun + l1=$(( (l0 >> 48) & 0xffff )) + l2=$(( (l0 >> 32) & 0xffff )) + l3=$(( (l0 >> 16) & 0xffff )) + l4=$(( l0 & 0xffff )) + l0=$(( ( ( (l4 * 0xffff) + l3 ) * 0xffff + l2 ) * 0xffff + l1 )) + printf "%u\n" $l0 + done + return 0 +} + +# Wait for udev to settle (create device nodes etc.) +udevadm_settle() +{ + local tmo=60 + if [ -x /bin/udevadm ] ; then + print_and_scroll_back " Calling udevadm settle (can take a while) " + # Loop for up to 60 seconds if sd devices still are settling.. + # This allows us to continue if udev events are stuck on multipaths in recovery mode + while [ $tmo -gt 0 ] ; do + if ! /bin/udevadm settle --timeout=1 | egrep -q sd[a-z]+ ; then + break; + fi + let tmo=$tmo-1 + done + white_out + elif [ -x /sbin/udevsettle ] ; then + print_and_scroll_back " Calling udevsettle (can take a while) " + /sbin/udevsettle + white_out + else + sleep 0.02 + fi +} + +# Perform scan on a single lun $host $channel $id $lun +dolunscan() +{ + local remappedlun0= + local devpath + SCSISTR= + devnr="$host $channel $id $lun" + echo -e " Scanning for device $devnr ... " + printf "${yellow}OLD: %s" "$norm" + testexist + # Device exists: Test whether it's still online + # (testonline returns 2 if it's gone and 1 if it has changed) + devpath="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device" + if [ "$SCSISTR" ] ; then + testonline + RC=$? + # Well known lun transition case. Only for Direct-Access devs (type 0) + # If block directory exists && and PQUAL != 0, we unmapped lun0 and just have a well-known lun + # If block directory doesn't exist && PQUAL == 0, we mapped a real lun0 + if [ "$lun" -eq 0 ] && [ $IPTYPE -eq 0 ] ; then + if [ $RC = 2 ] ; then + if [ -e "$devpath" ] ; then + if [ -d "$devpath/block" ] ; then + remappedlun0=2 # Transition from real lun 0 to well-known + else + RC=0 # Set this so the system leaves the existing well known lun alone. This is a lun 0 with no block directory + fi + fi + elif [ $RC = 0 ] && [ $IPTYPE -eq 0 ] ; then + if [ -e "$devpath" ] ; then + if [ ! -d "$devpath/block" ] ; then + remappedlun0=1 # Transition from well-known to real lun 0 + fi + fi + fi + fi + fi + + # Special case: lun 0 just got added (for reportlunscan), + # so make sure we correctly treat it as new + if [ "$lun" = "0" ] && [ "$1" = "1" ] && [ -z "$remappedlun0" ] ; then + SCSISTR="" + printf "\r\e[A\e[A\e[A" + fi + + : f "$remove" s $SCSISTR + if [ "$remove" ] && [ "$SCSISTR" -o "$remappedlun0" = "1" ] ; then + if [ $RC != 0 ] || [ ! -z "$forceremove" ] || [ -n "$remappedlun0" ] ; then + if [ "$remappedlun0" != "1" ] ; then + echo -en "\r\e[A\e[A\e[A${red}REM: " + echo "$SCSISTR" | head -n1 + echo -e "${norm}\e[B\e[B" + fi + if [ -e "$devpath" ] ; then + # have to preemptively do this so we can figure out the mpath device + # Don't do this if we're deleting a well known lun to replace it + if [ "$remappedlun0" != "1" ] ; then + incrrmvd "$host:$channel:$id:$lun" + fi + echo 1 > "$devpath/delete" + sleep 0.02 + else + echo "scsi remove-single-device $devnr" > /proc/scsi/scsi + if [ $RC -eq 1 ] || [ "$lun" -eq 0 ] ; then + # Try readding, should fail if device is gone + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + fi + fi + if [ $RC = 0 ] || [ "$forcerescan" ] ; then + if [ -e "$devpath" ] ; then + echo 1 > "$devpath/rescan" + fi + fi + printf "\r\e[A\e[A\e[A${yellow}OLD: %s" "$norm" + testexist + if [ -z "$SCSISTR" ] && [ $RC != 1 ] && [ "$remappedlun0" != "1" ] ; then + printf "\r${red}DEL: %s\r\n\n" "$norm" + # In the event we're replacing with a well known node, we need to let it continue, to create the replacement node + [ "$remappedlun0" != "2" ] && return 2 + fi + fi + if [ -z "$SCSISTR" ] || [ -n "$remappedlun0" ] ; then + if [ "$remappedlun0" != "2" ] ; then + # Device does not exist, try to add + printf "\r${green}NEW: %s" "$norm" + fi + if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then + echo "$channel $id $lun" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null + else + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + testexist + if [ -z "$SCSISTR" ] ; then + # Device not present + printf "\r\e[A"; + # Optimization: if lun==0, stop here (only if in non-remove mode) + if [ "$lun" = 0 ] && [ -z "$remove" ] && [ "$optscan" = 1 ] ; then + return 1; + fi + else + if [ "$remappedlun0" != "2" ] ; then + incrfound "$host:$channel:$id:$lun" + fi + fi + fi + return 0; +} + +# Perform report lun scan on $host $channel $id using REPORT_LUNS +doreportlun() +{ + lun=0 + SCSISTR= + devnr="$host $channel $id $lun" + echo -en " Scanning for device $devnr ...\r" + lun0added= + #printf "${yellow}OLD: %s" "$norm" + # Phase one: If LUN0 does not exist, try to add + testexist -q + if [ -z "$SCSISTR" ] ; then + # Device does not exist, try to add + #printf "\r${green}NEW: %s" "$norm" + if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then + echo "$channel $id $lun" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null + udevadm_settle + else + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + testexist -q + if [ -n "$SCSISTR" ] ; then + lun0added=1 + #testonline + else + # Device not present + # return + # Find alternative LUN to send getluns to + for dev in /sys/class/scsi_device/${host}:${channel}:${id}:*; do + [ -d "$dev" ] || continue + lun=${dev##*:} + break + done + fi + fi + targetluns=$(getluns) + REPLUNSTAT=$? + lunremove= + #echo "getluns reports " $targetluns + olddev=$(find /sys/class/scsi_device/ -name "$host:$channel:$id:*" 2>/dev/null | sort -t: -k4 -n) + oldtargets="$targetluns" + # OK -- if we don't have a LUN to send a REPORT_LUNS to, we could + # fall back to wildcard scanning. Same thing if the device does not + # support REPORT_LUNS + # TODO: We might be better off to ALWAYS use wildcard scanning if + # it works + if [ "$REPLUNSTAT" = "1" ] ; then + if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then + echo "$channel $id -" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null + udevadm_settle + else + echo "scsi add-single-device $host $channel $id $SCAN_WILD_CARD" > /proc/scsi/scsi + fi + targetluns=$(find /sys/class/scsi_device/ -name "$host:$channel:$id:*" -printf "%f\n" | cut -d : -f 4) + let found+=$(echo "$targetluns" | wc -l) + let found-=$(echo "$olddev" | wc -l) + fi + [ -z "$targetluns" ] && targetluns="$oldtargets" + # Check existing luns + for dev in $olddev; do + [ -d "$dev" ] || continue + lun=${dev##*:} + newsearch= + inlist= + # OK, is existing $lun (still) in reported list + for tmplun in $targetluns; do + if [ "$tmplun" = "$lun" ] ; then + inlist=1 + dolunscan $lun0added + [ $? -eq 1 ] && break + else + newsearch="$newsearch $tmplun" + fi + done + # OK, we have now done a lunscan on $lun and + # $newsearch is the old $targetluns without $lun + if [ -z "$inlist" ]; then + # Stale lun + lunremove="$lunremove $lun" + fi + # $lun removed from $lunsearch + targetluns=${newsearch# } + done + # Add new ones and check stale ones + for lun in $targetluns $lunremove; do + dolunscan $lun0added + [ $? -eq 1 ] && break + done +} + +# Perform search (scan $host) +dosearch () +{ + if [ -z "$channelsearch" ] ; then + chanlist + fi + for channel in $channelsearch; do + if [ -z "$idsearch" ] ; then + if [ -z "$lunsearch" ] ; then + idlist + else + idsearch=$(find /sys/bus/scsi/devices -name "target${host}:${channel}:*" -printf "%f\n" | cut -f 3 -d :) + fi + fi + for id in $idsearch; do + if [ -z "$lunsearch" ] ; then + doreportlun + else + for lun in $lunsearch; do + dolunscan + [ $? -eq 1 ] && break + done + fi + done + done +} + +expandlist () +{ + list=$1 + result="" + first=${list%%,*} + rest=${list#*,} + while [ ! -z "$first" ] ; do + beg=${first%%-*}; + if [ "$beg" = "$first" ] ; then + result="$result $beg"; + else + end=${first#*-} + result="$result $(seq $beg $end)" + fi + [ "$rest" = "$first" ] && rest="" + first=${rest%%,*} + rest=${rest#*,} + done + echo "$result" +} + +searchexisting() +{ + local tmpch; + local tmpid + local match=0 + local targets= + + targets=$(find /sys/bus/scsi/devices -name "target${host}:*" -printf "%f\n" | cut -d : -f 2-3) + # Nothing came back on this host, so we should skip it + [ -z "$targets" ] && return + + local target=; + for target in $targets ; do + channel=${target%:*} + id=${target#*:} + if [ -n "$channelsearch" ] ; then + for tmpch in $channelsearch ; do + [ $tmpch -eq "$channel" ] && match=1 + done + else + match=1 + fi + + [ $match -eq 0 ] && continue + match=0 + + if [ "$filter_ids" -eq 1 ] ; then + for tmpid in $idsearch ; do + if [ "$tmpid" = "$id" ] ; then + match=1 + fi + done + else + match=1 + fi + + [ $match -eq 0 ] && continue + + if [ -z "$lunsearch" ] ; then + doreportlun + else + for lun in $lunsearch ; do + dolunscan + [ $? -eq 1 ] && break + done + fi + done +} + +# Go through all of the existing devices and figure out any that have been remapped +findremapped() +{ + local hctl=; + local devs= + local sddev= + local id_serial= + local id_serial_old= + local remapped= + mpaths="" + local tmpfile= + + tmpfile=$(mktemp /tmp/rescan-scsi-bus.XXXXXXXX 2> /dev/null) + if [ -z "$tmpfile" ] ; then + tmpfile="/tmp/rescan-scsi-bus.$$" + rm -f $tmpfile + fi + + # Get all of the ID_SERIAL attributes, after finding their sd node + devs=$(ls /sys/class/scsi_device/) + for hctl in $devs ; do + if [ -d "/sys/class/scsi_device/$hctl/device/block" ] ; then + sddev=$(ls "/sys/class/scsi_device/$hctl/device/block") + id_serial_old=$(udevadm info -q all -n "$sddev" | grep "ID_SERIAL=" | cut -d"=" -f2) + [ -z "$id_serial_old" ] && id_serial_old="none" + echo "$hctl $sddev $id_serial_old" >> $tmpfile + fi + done + + # Trigger udev to update the info + echo -n "Triggering udev to update device information... " + /bin/udevadm trigger + udevadm_settle 2>&1 /dev/null + echo "Done" + + # See what changed and reload the respective multipath device if applicable + while read -r hctl sddev id_serial_old ; do + remapped=0 + id_serial=$(udevadm info -q all -n "$sddev" | grep "ID_SERIAL=" | cut -d"=" -f2) + [ -z "$id_serial" ] && id_serial="none" + if [ "$id_serial_old" != "$id_serial" ] ; then + remapped=1 + fi + # If udev events updated the disks already, but the multipath device isn't update + # check for old devices to make sure we found remapped luns + if [ -n "$mp_enable" ] && [ $remapped -eq 0 ]; then + findmultipath "$sddev" $id_serial + if [ $? -eq 1 ] ; then + remapped=1 + fi + fi + + # if uuid is 1, it's unmapped, so we don't want to treat it as a remap + # if remapped flag is 0, just skip the rest of the logic + if [ "$id_serial" = "1" ] || [ $remapped -eq 0 ] ; then + continue + fi + printf "${yellow}REMAPPED: %s" "$norm" + host=$(echo "$hctl" | cut -d":" -f1) + channel=$(echo "$hctl" | cut -d":" -f2) + id=$(echo "$hctl" | cut -d":" -f3) + lun=$(echo "$hctl" | cut -d":" -f4) + procscsiscsi + echo "$SCSISTR" + incrchgd "$hctl" + done < $tmpfile + rm -f $tmpfile + + if [ -n "$mp_enable" ] && [ -n "$mpaths" ] ; then + echo "Updating multipath device mappings" + flushmpaths + $MULTIPATH | grep "create:" 2> /dev/null + fi +} + +incrfound() +{ + local hctl="$1" + if [ -n "$hctl" ] ; then + let found+=1 + FOUNDDEVS="$FOUNDDEVS\t[$hctl]\n" + else + return + fi +} + +incrchgd() +{ + local hctl="$1" + if [ -n "$hctl" ] ; then + if ! echo "$CHGDEVS" | grep -q "\[$hctl\]"; then + let updated+=1 + CHGDEVS="$CHGDEVS\t[$hctl]\n" + fi + else + return + fi + + if [ -n "$mp_enable" ] ; then + local sdev + + sdev=$(findsddev "$hctl") + if [ -n "$sdev" ] ; then + findmultipath "$sdev" + fi + fi +} + +incrrmvd() +{ + local hctl="$1" + if [ -n "$hctl" ] ; then + let rmvd+=1; + RMVDDEVS="$RMVDDEVS\t[$hctl]\n" + else + return + fi + + if [ -n "$mp_enable" ] ; then + local sdev + + sdev=$(findsddev "$hctl") + if [ -n "$sdev" ] ; then + findmultipath "$sdev" + fi + fi +} + +findsddev() +{ + local hctl="$1" + local sddev= + local blkpath + + blkpath="/sys/class/scsi_device/$hctl/device/block" + if [ -e "$blkpath" ] ; then + sddev=$(ls "$blkpath") + echo "$sddev" + fi +} + +addmpathtolist() +{ + local mp="$1" + local mp2= + + for mp2 in $mpaths ; do + # The multipath device is already in the list + if [ "$mp2" = "$mp" ] ; then + return + fi + done + mpaths="$mpaths $mp" +} + +findmultipath() +{ + local dev="$1" + local find_mismatch="$2" + local mp= + local mp2= + local found_dup=0 + local maj_min= + + # Need a sdev, and executable multipath and dmsetup command here + if [ -z "$dev" ] || [ ! -x "$DMSETUP" ] || [ ! -x "$MULTIPATH" ] ; then + return 1 + fi + + maj_min=$(cat "/sys/block/$dev/dev") + for mp in $($DMSETUP ls --target=multipath | cut -f 1) ; do + [ "$mp" = "No" ] && break; + if "$DMSETUP" status "$mp" | grep -q " $maj_min "; then + # With two arguments, look up current uuid from sysfs + # if it doesn't match what was passed, this multipath + # device is not updated, so this is a remapped LUN + if [ -n "$find_mismatch" ] ; then + mp2=$($MULTIPATH -l "$mp" | egrep -o "dm-[0-9]+") + mp2=$(cut -f2 -d- "/sys/block/$mp2/dm/uuid") + if [ "$find_mismatch" != "$mp2" ] ; then + addmpathtolist "$mp" + found_dup=1 + fi + continue + fi + # Normal mode: Find the first multipath with the sdev + # and add it to the list + addmpathtolist "$mp" + return + fi + done + + # Return 1 to signal that a duplicate was found to the calling function + if [ $found_dup -eq 1 ] ; then + return 1 + else + return 0 + fi +} + +reloadmpaths() +{ + local mpath + if [ ! -x "$MULTIPATH" ] ; then + echo "no -x multipath" + return + fi + + # Pass 1 as the argument to reload all mpaths + if [ "$1" = "1" ] ; then + echo "Reloading all multipath devices" + $MULTIPATH -r > /dev/null 2>&1 + return + fi + + # Reload the multipath devices + for mpath in $mpaths ; do + echo -n "Reloading multipath device $mpath... " + if $MULTIPATH -r "$mpath" > /dev/null 2>&1 ; then + echo "Done" + else + echo "Fail" + fi + done +} + +resizempaths() +{ + local mpath + + for mpath in $mpaths ; do + echo -n "Resizing multipath map $mpath ..." + multipathd -k"resize map $mpath" + let updated+=1 + done +} + +flushmpaths() +{ + local mpath + local remove="" + local i + local flush_retries=5 + + if [ -n "$1" ] ; then + for mpath in $($DMSETUP ls --target=multipath | cut -f 1) ; do + [ "$mpath" = "No" ] && break + num=$($DMSETUP status "$mpath" | awk 'BEGIN{RS=" ";active=0}/[0-9]+:[0-9]+/{dev=1}/A/{if (dev == 1) active++; dev=0} END{ print active }') + if [ "$num" -eq 0 ] ; then + remove="$remove $mpath" + fi + done + else + remove="$mpaths" + fi + + for mpath in $remove ; do + i=0 + echo -n "Flushing multipath device $mpath... " + while [ $i -lt $flush_retries ] ; do + $DMSETUP message "$mpath" 0 fail_if_no_path > /dev/null 2>&1 + if $MULTIPATH -f "$mpath" > /dev/null 2>&1 ; then + echo "Done ($i retries)" + break + elif [ $i -eq $flush_retries ] ; then + echo "Fail" + fi + sleep 0.02 + let i=$i+1 + done + done +} + + +# Find resized luns +findresized() +{ + local devs= + local size= + local new_size= + local sysfs_path= + local sddev= + local i= + local m= + local mpathsize= + declare -a mpathsizes + + if [ -z "$lunsearch" ] ; then + devs=$(ls /sys/class/scsi_device/) + else + for lun in $lunsearch ; do + devs="$devs $(cd /sys/class/scsi_device/ && ls -d *:${lun})" + done + fi + + for hctl in $devs ; do + sysfs_path="/sys/class/scsi_device/$hctl/device" + if [ -d "$sysfs_path/block" ] ; then + sddev=$(ls "$sysfs_path/block") + size=$(cat "$sysfs_path/block/$sddev/size") + + echo 1 > "$sysfs_path/rescan" + new_size=$(cat "$sysfs_path/block/$sddev/size") + + if [ "$size" != "$new_size" ] && [ "$size" != "0" ] && [ "$new_size" != "0" ] ; then + printf "${yellow}RESIZED: %s" "$norm" + host=$(echo "$hctl" | cut -d":" -f1) + channel=$(echo "$hctl" | cut -d":" -f2) + id=$(echo "$hctl" | cut -d":" -f3) + lun=$(echo "$hctl" | cut -d":" -f4) + + procscsiscsi + echo "$SCSISTR" + incrchgd "$hctl" + fi + fi + done + + if [ -n "$mp_enable" ] && [ -n "$mpaths" ] ; then + i=0 + for m in $mpaths ; do + mpathsizes[$i]="$($MULTIPATH -l "$m" | egrep -o [0-9]+.[0-9]+[KMGT])" + let i=$i+1 + done + resizempaths + i=0 + for m in $mpaths ; do + mpathsize="$($MULTIPATH -l "$m" | egrep -o [0-9\.]+[KMGT])" + echo "$m ${mpathsizes[$i]} => $mpathsize" + let i=$i+1 + done + fi +} + +FOUNDDEVS="" +CHGDEVS="" +RMVDDEVS="" + +# main +if [ "@$1" = @--help ] || [ "@$1" = @-h ] || [ "@$1" = "@-?" ] ; then + echo "Usage: rescan-scsi-bus.sh [options] [host [host ...]]" + echo "Options:" + echo " -a scan all targets, not just currently existing [default: disabled]" + echo " -c enables scanning of channels 0 1 [default: 0 / all detected ones]" + echo " -d enable debug [default: 0]" + echo " -f flush failed multipath devices [default: disabled]" + echo " -h help: print this usage message then exit" + echo " -i issue a FibreChannel LIP reset [default: disabled]" + echo " -I SECS issue a FibreChannel LIP reset and wait for SECS seconds [default: disabled]" + echo " -l activates scanning for LUNs 0--7 [default: 0]" + echo " -L NUM activates scanning for LUNs 0--NUM [default: 0]" + echo " -m update multipath devices [default: disabled]" + echo " -r enables removing of devices [default: disabled]" + echo " -s look for resized disks and reload associated multipath devices, if applicable" + echo " -u look for existing disks that have been remapped" + echo " -V print version date then exit" + echo " -w scan for target device IDs 0--15 [default: 0--7]" + echo "--alltargets: same as -a" + echo "--attachpq3: Tell kernel to attach sg to LUN 0 that reports PQ=3" + echo "--channels=LIST: Scan only channel(s) in LIST" + echo "--color: use coloured prefixes OLD/NEW/DEL" + echo "--flush: same as -f" + echo "--forceremove: Remove stale devices (DANGEROUS)" + echo "--forcerescan: Remove and readd existing devices (DANGEROUS)" + echo "--help: print this usage message then exit" + echo "--hosts=LIST: Scan only host(s) in LIST" + echo "--ids=LIST: Scan only target ID(s) in LIST" + echo "--ignore-rev: Ignore the revision change" + echo "--issue-lip: same as -i" + echo "--issue-lip-wait=SECS: same as -I" + echo "--largelun: Tell kernel to support LUNs > 7 even on SCSI2 devs" + echo "--luns=LIST: Scan only lun(s) in LIST" + echo "--multipath: same as -m" + echo "--nooptscan: don't stop looking for LUNs if 0 is not found" + echo "--remove: same as -r" + echo "--reportlun2: Tell kernel to try REPORT_LUN even on SCSI2 devices" + echo "--resize: same as -s" + echo "--sparselun: Tell kernel to support sparse LUN numbering" + echo "--sync/nosync: Issue a sync / no sync [default: sync if remove]" + echo "--update: same as -u" + echo "--version: same as -V" + echo "--wide: same as -w" + echo "" + echo "Host numbers may thus be specified either directly on cmd line (deprecated)" + echo "or with the --hosts=LIST parameter (recommended)." + echo "LIST: A[-B][,C[-D]]... is a comma separated list of single values and ranges" + echo "(No spaces allowed.)" + exit 0 +fi + +if [ "@$1" = @--version ] || [ "@$1" = @-V ] ; then + echo ${VERSION} + exit 0 +fi + +if [ ! -d /sys/class/scsi_host/ ] && [ ! -d /proc/scsi/ ] ; then + echo "Error: SCSI subsystem not active" + exit 1 +fi + +# Make sure sg is there +modprobe sg >/dev/null 2>&1 + +if [ -x /usr/bin/sg_inq ] ; then + sg_version=$(sg_inq -V 2>&1 | cut -d " " -f 3) + if [ -n "$sg_version" ] ; then + sg_ver_maj=${sg_version:0:1} + sg_version=${sg_version##?.} + let sg_version+=$((100 * sg_ver_maj)) + fi + sg_version=${sg_version##0.} + #echo "\"$sg_version\"" + if [ -z "$sg_version" ] || [ "$sg_version" -lt 70 ] ; then + sg_len_arg="-36" + else + sg_len_arg="--len=36" + fi +else + echo "WARN: /usr/bin/sg_inq not present -- please install sg3_utils" + echo " or rescan-scsi-bus.sh might not fully work." +fi + +# defaults +unsetcolor +debug=0 +lunsearch= +opt_idsearch=$(seq 0 7) +filter_ids=0 +opt_channelsearch= +remove= +updated=0 +update=0 +resize=0 +forceremove= +optscan=1 +sync=1 +existing_targets=1 +mp_enable= +lipreset=-1 +declare -i scan_flags=0 +ignore_rev=0 + +# Scan options +opt="$1" +while [ ! -z "$opt" ] && [ -z "${opt##-*}" ] ; do + opt=${opt#-} + case "$opt" in + a) existing_targets=;; #Scan ALL targets when specified + c) opt_channelsearch="0 1" ;; + d) debug=1 ;; + f) flush=1 ;; + i) lipreset=0 ;; + I) shift; lipreset=$opt ;; + l) lunsearch=$(seq 0 7) ;; + L) lunsearch=$(seq 0 "$2"); shift ;; + m) mp_enable=1 ;; + r) remove=1 ;; + s) resize=1; mp_enable=1 ;; + u) update=1 ;; + w) opt_idsearch=$(seq 0 15) ;; + -alltargets) existing_targets=;; + -attachpq3) scan_flags=$((scan_flags|0x1000000)) ;; + -channels=*) arg=${opt#-channels=};opt_channelsearch=$(expandlist "$arg") ;; + -color) setcolor ;; + -flush) flush=1 ;; + -forceremove) remove=1; forceremove=1 ;; + -forcerescan) remove=1; forcerescan=1 ;; + -hosts=*) arg=${opt#-hosts=}; hosts=$(expandlist "$arg") ;; + -ids=*) arg=${opt#-ids=}; opt_idsearch=$(expandlist "$arg") ; filter_ids=1;; + -ignore-rev) ignore_rev=1;; + -issue-lip) lipreset=0 ;; + -issue-lip-wait) lipreset=${opt#-issue-lip-wait=};; + -largelun) scan_flags=$((scan_flags|0x200)) ;; + -luns=*) arg=${opt#-luns=}; lunsearch=$(expandlist "$arg") ;; + -multipath) mp_enable=1 ;; + -nooptscan) optscan=0 ;; + -nosync) sync=0 ;; + -remove) remove=1 ;; + -reportlun2) scan_flags=$((scan_flags|0x20000)) ;; + -resize) resize=1;; + -sparselun) scan_flags=$((scan_flags|0x40)) ;; + -sync) sync=2 ;; + -update) update=1;; + -wide) opt_idsearch=$(seq 0 15) ;; + *) echo "Unknown option -$opt !" ;; + esac + shift + opt="$1" +done + +if [ -z "$hosts" ] ; then + if [ -d /sys/class/scsi_host ] ; then + findhosts_26 + else + findhosts + fi +fi + +if [ -d /sys/class/scsi_host ] && [ ! -w /sys/class/scsi_host ]; then + echo "You need to run scsi-rescan-bus.sh as root" + exit 2 +fi +[ "$sync" = 1 ] && [ "$remove" = 1 ] && sync=2 +if [ "$sync" = 2 ] ; then + echo "Syncing file systems" + sync +fi +if [ -w /sys/module/scsi_mod/parameters/default_dev_flags ] && [ $scan_flags != 0 ] ; then + OLD_SCANFLAGS=$(cat /sys/module/scsi_mod/parameters/default_dev_flags) + NEW_SCANFLAGS=$((OLD_SCANFLAGS|scan_flags)) + if [ "$OLD_SCANFLAGS" != "$NEW_SCANFLAGS" ] ; then + echo -n "Temporarily setting kernel scanning flags from " + printf "0x%08x to 0x%08x\n" "$OLD_SCANFLAGS" "$NEW_SCANFLAGS" + echo $NEW_SCANFLAGS > /sys/module/scsi_mod/parameters/default_dev_flags + else + unset OLD_SCANFLAGS + fi +fi +DMSETUP=$(which dmsetup) +[ -z "$DMSETUP" ] && flush= && mp_enable= +MULTIPATH=$(which multipath) +[ -z "$MULTIPATH" ] && flush= && mp_enable= + +echo -n "Scanning SCSI subsystem for new devices" +[ -z "$flush" ] || echo -n ", flush failed multipath devices," +[ -z "$remove" ] || echo -n " and remove devices that have disappeared" +echo +declare -i found=0 +declare -i updated=0 +declare -i rmvd=0 + +if [ -n "$flush" ] ; then + if [ -x "$MULTIPATH" ] ; then + flushmpaths 1 + fi +fi + +# Update existing mappings +if [ $update -eq 1 ] ; then + echo "Searching for remapped LUNs" + findremapped + # If you've changed the mapping, there's a chance it's a different size + mpaths="" + findresized +# Search for resized LUNs +elif [ $resize -eq 1 ] ; then + echo "Searching for resized LUNs" + findresized +# Normal rescan mode +else + for host in $hosts; do + echo -n "Scanning host $host " + if [ -e "/sys/class/fc_host/host$host" ] ; then + # It's pointless to do a target scan on FC + issue_lip=/sys/class/fc_host/host$host/issue_lip + if [ -e "$issue_lip" ] && [ "$lipreset" -ge 0 ] ; then + echo 1 > "$issue_lip" 2> /dev/null; + udevadm_settle + [ "$lipreset" -gt 0 ] && sleep "$lipreset" + fi + channelsearch= + idsearch= + else + channelsearch=$opt_channelsearch + idsearch=$opt_idsearch + fi + [ -n "$channelsearch" ] && echo -n "channels $channelsearch " + echo -n "for " + if [ -n "$idsearch" ] ; then + echo -n " SCSI target IDs $idsearch" + else + echo -n " all SCSI target IDs" + fi + if [ -n "$lunsearch" ] ; then + echo ", LUNs $lunsearch" + else + echo ", all LUNs" + fi + + if [ -n "$existing_targets" ] ; then + searchexisting + else + dosearch + fi + done + if [ -n "$OLD_SCANFLAGS" ] ; then + echo "$OLD_SCANFLAGS" > /sys/module/scsi_mod/parameters/default_dev_flags + fi +fi + +let rmvd_found=$rmvd+$found +if [ -n "$mp_enable" ] && [ $rmvd_found -gt 0 ] ; then + echo "Attempting to update multipath devices..." + if [ $rmvd -gt 0 ] ; then + udevadm_settle + echo "Removing multipath mappings for removed devices if all paths are now failed... " + flushmpaths 1 + fi + if [ $found -gt 0 ] ; then + /bin/udevadm trigger --sysname-match=sd* + udevadm_settle + if [ -x "$MULTIPATH" ] ; then + echo "Trying to discover new multipath mappings for newly discovered devices... " + $MULTIPATH | grep "create:" 2> /dev/null + fi + fi +fi + +echo "$found new or changed device(s) found. " +if [ ! -z "$FOUNDDEVS" ] ; then + printf "%s" "$FOUNDDEVS" +fi +echo "$updated remapped or resized device(s) found." +if [ ! -z "$CHGDEVS" ] ; then + printf "%s" "$CHGDEVS" +fi +echo "$rmvd device(s) removed. " +if [ ! -z "$RMVDDEVS" ] ; then + printf "%s" "$RMVDDEVS" +fi + +# Local Variables: +# sh-basic-offset: 2 +# End: + diff --git a/Moodle/Tools/agets.sh b/Moodle/Tools/agets.sh new file mode 100644 index 0000000..5d58d69 --- /dev/null +++ b/Moodle/Tools/agets.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +INPUT_LIST="list.txt" +TEMP_LIST="list_to_download.txt" +MIN_VALID_SIZE=1024 # Kích thước tối thiểu (bytes) để coi là file hợp lệ + +# 🔍 Kiểm tra aria2c đã cài chưa +if ! command -v aria2c &> /dev/null; then + echo "❌ Lỗi: aria2c chưa được cài. Vui lòng cài aria2 trước." + exit 1 +fi + +# 📂 Kiểm tra file list.txt có tồn tại không +if [ ! -f "$INPUT_LIST" ]; then + echo "❌ Không tìm thấy file $INPUT_LIST" + exit 1 +fi + +# 📝 Tạo danh sách tạm các URL cần tải +> "$TEMP_LIST" + +while read -r url; do + # 👉 Bỏ qua dòng trống hoặc comment + [[ -z "$url" || "$url" == \#* ]] && continue + + # 🎯 Lấy tên file bỏ phần query string + filename=$(basename "${url%%\?*}") + + # 📦 Kiểm tra trạng thái file + if [ ! -f "$filename" ]; then + echo "$url" >> "$TEMP_LIST" + echo "⬇️ Thiếu file: $filename" + elif [ -f "$filename.aria2" ]; then + echo "$url" >> "$TEMP_LIST" + echo "🔄 Đang tải dở: $filename" + elif [ "$(stat -c%s "$filename")" -lt "$MIN_VALID_SIZE" ]; then + echo "$url" >> "$TEMP_LIST" + echo "⚠️ File nhỏ bất thường (<$MIN_VALID_SIZE bytes), sẽ tải lại: $filename" + else + echo "✅ Đã có đầy đủ: $filename" + fi +done < "$INPUT_LIST" + +# 🚫 Nếu không còn gì để tải +if [ ! -s "$TEMP_LIST" ]; then + echo "🎉 Tất cả file đã được tải đầy đủ." + rm -f "$TEMP_LIST" + exit 0 +fi + +# 🚀 Bắt đầu tải bằng aria2c +aria2c \ + -i "$TEMP_LIST" \ + --dir=. \ + --max-connection-per-server=16 \ + --split=16 \ + --min-split-size=1M \ + --max-concurrent-downloads=10 \ + --continue=true \ + --remove-control-file=true \ + --auto-file-renaming=false \ + --file-allocation=none \ + --summary-interval=0 \ + --console-log-level=warn \ + --log="aria2_download.log" \ + --log-level=notice + +# ✅ Hoàn tất +echo "✅ Hoàn tất tải các file chưa đầy đủ." +rm -f "$TEMP_LIST" diff --git a/Moodle/Tools/dockerd-rootless-setuptool.sh b/Moodle/Tools/dockerd-rootless-setuptool.sh new file mode 100644 index 0000000..c804a8c --- /dev/null +++ b/Moodle/Tools/dockerd-rootless-setuptool.sh @@ -0,0 +1,557 @@ +#!/bin/sh +# dockerd-rootless-setuptool.sh: setup tool for dockerd-rootless.sh +# Needs to be executed as a non-root user. +# +# Typical usage: dockerd-rootless-setuptool.sh install --force +# +# Documentation: https://docs.docker.com/go/rootless/ +set -eu + +# utility functions +INFO() { + /bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m $@" +} + +WARNING() { + /bin/echo >&2 -e "\e[101m\e[97m[WARNING]\e[49m\e[39m $@" +} + +ERROR() { + /bin/echo >&2 -e "\e[101m\e[97m[ERROR]\e[49m\e[39m $@" +} + +# constants +DOCKERD_ROOTLESS_SH="dockerd-rootless.sh" +SYSTEMD_UNIT="docker.service" +CLI_CONTEXT="rootless" + +# CLI opt: --force +OPT_FORCE="" +# CLI opt: --skip-iptables +OPT_SKIP_IPTABLES="" + +# global vars +ARG0="$0" +DOCKERD_ROOTLESS_SH_FLAGS="" +BIN="" +SYSTEMD="" +CFG_DIR="" +XDG_RUNTIME_DIR_CREATED="" +USERNAME="" +USERNAME_ESCAPED="" + +# run checks and also initialize global vars +init() { + # OS verification: Linux only + case "$(uname)" in + Linux) ;; + + *) + ERROR "Rootless Docker cannot be installed on $(uname)" + exit 1 + ;; + esac + + # User verification: deny running as root + if [ "$(id -u)" = "0" ]; then + ERROR "Refusing to install rootless Docker as the root user" + exit 1 + fi + + # set BIN + if ! BIN="$(command -v "$DOCKERD_ROOTLESS_SH" 2> /dev/null)"; then + ERROR "$DOCKERD_ROOTLESS_SH needs to be present under \$PATH" + exit 1 + fi + BIN=$(dirname "$BIN") + + # set SYSTEMD + if systemctl --user show-environment > /dev/null 2>&1; then + SYSTEMD=1 + fi + + # HOME verification + if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then + ERROR "HOME needs to be set" + exit 1 + fi + if [ ! -w "$HOME" ]; then + ERROR "HOME needs to be writable" + exit 1 + fi + + # Set USERNAME from `id -un` and potentially protect backslash + # for windbind/samba domain users + USERNAME=$(id -un) + USERNAME_ESCAPED=$(echo $USERNAME | sed 's/\\/\\\\/g') + + # set CFG_DIR + CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}" + + # Existing rootful docker verification + if [ -w /var/run/docker.sock ] && [ -z "$OPT_FORCE" ]; then + ERROR "Aborting because rootful Docker (/var/run/docker.sock) is running and accessible. Set --force to ignore." + exit 1 + fi + + # Validate XDG_RUNTIME_DIR and set XDG_RUNTIME_DIR_CREATED + if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -w "$XDG_RUNTIME_DIR" ]; then + if [ -n "$SYSTEMD" ]; then + ERROR "Aborting because systemd was detected but XDG_RUNTIME_DIR (\"$XDG_RUNTIME_DIR\") is not set, does not exist, or is not writable" + ERROR "Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:" + ERROR "- try again by first running with root privileges 'loginctl enable-linger ' where is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user '" + ERROR "- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)" + exit 1 + fi + export XDG_RUNTIME_DIR="$HOME/.docker/run" + mkdir -p -m 700 "$XDG_RUNTIME_DIR" + XDG_RUNTIME_DIR_CREATED=1 + fi + + instructions="" + # instruction: uidmap dependency check + if ! command -v newuidmap > /dev/null 2>&1; then + if command -v apt-get > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install newuidmap & newgidmap binaries + apt-get install -y uidmap + EOI + ) + elif command -v dnf > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install newuidmap & newgidmap binaries + dnf install -y shadow-utils + EOI + ) + elif command -v yum > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install newuidmap & newgidmap binaries + yum install -y shadow-utils + EOI + ) + else + ERROR "newuidmap binary not found. Please install with a package manager." + exit 1 + fi + fi + + # instruction: iptables dependency check + faced_iptables_error="" + # Many OSs now use iptables-nft by default so, check for module nf_tables by default. But, + # if "iptables --version" worked and reported "legacy", check for module ip_tables instead. + iptables_module="nf_tables" + iptables_command=$(PATH=$PATH:/sbin:/usr/sbin command -v iptables 2> /dev/null) || : + if [ -n "$iptables_command" ]; then + iptables_version=$($iptables_command --version 2> /dev/null) || : + case $iptables_version in + *legacy*) iptables_module="ip_tables" ;; + esac + else + faced_iptables_error=1 + if [ -z "$OPT_SKIP_IPTABLES" ]; then + if command -v apt-get > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install iptables + apt-get install -y iptables + EOI + ) + elif command -v dnf > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install iptables + dnf install -y iptables + EOI + ) + elif command -v yum > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install iptables + yum install -y iptables + EOI + ) + else + ERROR "iptables binary not found. Please install with a package manager." + exit 1 + fi + fi + fi + + # instruction: ip_tables module dependency check + if ! grep -q $iptables_module /proc/modules 2> /dev/null && ! grep -q $iptables_module /lib/modules/$(uname -r)/modules.builtin 2> /dev/null; then + faced_iptables_error=1 + if [ -z "$OPT_SKIP_IPTABLES" ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Load $iptables_module module + modprobe $iptables_module + EOI + ) + fi + fi + + # set DOCKERD_ROOTLESS_SH_FLAGS + if [ -n "$faced_iptables_error" ] && [ -n "$OPT_SKIP_IPTABLES" ]; then + DOCKERD_ROOTLESS_SH_FLAGS="${DOCKERD_ROOTLESS_SH_FLAGS} --iptables=false" + fi + + # instruction: Debian and Arch require setting unprivileged_userns_clone + if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then + if [ "1" != "$(cat /proc/sys/kernel/unprivileged_userns_clone)" ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Set kernel.unprivileged_userns_clone + cat < /etc/sysctl.d/50-rootless.conf + kernel.unprivileged_userns_clone = 1 + EOT + sysctl --system + EOI + ) + fi + fi + + # instruction: RHEL/CentOS 7 requires setting max_user_namespaces + if [ -f /proc/sys/user/max_user_namespaces ]; then + if [ "0" = "$(cat /proc/sys/user/max_user_namespaces)" ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Set user.max_user_namespaces + cat < /etc/sysctl.d/51-rootless.conf + user.max_user_namespaces = 28633 + EOT + sysctl --system + EOI + ) + fi + fi + + # instructions: validate subuid for current user + if command -v "getsubids" > /dev/null 2>&1; then + getsubids "$USERNAME" > /dev/null 2>&1 || getsubids "$(id -u)" > /dev/null 2>&1 + else + grep -q "^$USERNAME_ESCAPED:\|^$(id -u):" /etc/subuid 2> /dev/null + fi + if [ $? -ne 0 ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Add subuid entry for ${USERNAME} + echo "${USERNAME}:100000:65536" >> /etc/subuid + EOI + ) + fi + + # instructions: validate subgid for current user + if command -v "getsubids" > /dev/null 2>&1; then + getsubids -g "$USERNAME" > /dev/null 2>&1 || getsubids -g "$(id -u)" > /dev/null 2>&1 + else + grep -q "^$USERNAME_ESCAPED:\|^$(id -u):" /etc/subgid 2> /dev/null + fi + if [ $? -ne 0 ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Add subgid entry for ${USERNAME} + echo "${USERNAME}:100000:65536" >> /etc/subgid + EOI + ) + fi + + # fail with instructions if requirements are not satisfied. + if [ -n "$instructions" ]; then + ERROR "Missing system requirements. Run the following commands to" + ERROR "install the requirements and run this tool again." + if [ -n "$faced_iptables_error" ] && [ -z "$OPT_SKIP_IPTABLES" ]; then + ERROR "Alternatively iptables checks can be disabled with --skip-iptables ." + fi + echo + echo "########## BEGIN ##########" + echo "sudo sh -eux < "${unit_file}" + [Unit] + Description=Docker Application Container Engine (Rootless) + Documentation=https://docs.docker.com/go/rootless/ + Requires=dbus.socket + + [Service] + Environment=PATH=$BIN:/sbin:/usr/sbin:$PATH + ExecStart=$BIN/dockerd-rootless.sh $DOCKERD_ROOTLESS_SH_FLAGS + ExecReload=/bin/kill -s HUP \$MAINPID + TimeoutSec=0 + RestartSec=2 + Restart=always + StartLimitBurst=3 + StartLimitInterval=60s + LimitNOFILE=infinity + LimitNPROC=infinity + LimitCORE=infinity + TasksMax=infinity + Delegate=yes + Type=notify + NotifyAccess=all + KillMode=mixed + + [Install] + WantedBy=default.target + EOT + systemctl --user daemon-reload + fi + if ! systemctl --user --no-pager status "${SYSTEMD_UNIT}" > /dev/null 2>&1; then + INFO "starting systemd service ${SYSTEMD_UNIT}" + ( + set -x + if ! systemctl --user start "${SYSTEMD_UNIT}"; then + set +x + show_systemd_error + exit 1 + fi + sleep 3 + ) + fi + ( + set -x + if ! systemctl --user --no-pager --full status "${SYSTEMD_UNIT}"; then + set +x + show_systemd_error + exit 1 + fi + DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" $BIN/docker version + systemctl --user enable "${SYSTEMD_UNIT}" + ) + INFO "Installed ${SYSTEMD_UNIT} successfully." + INFO "To control ${SYSTEMD_UNIT}, run: \`systemctl --user (start|stop|restart) ${SYSTEMD_UNIT}\`" + INFO "To run ${SYSTEMD_UNIT} on system startup, run: \`sudo loginctl enable-linger ${USERNAME}\`" + echo +} + +# install (non-systemd) +install_nonsystemd() { + INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be started manually:" + echo + echo "PATH=$BIN:/sbin:/usr/sbin:\$PATH ${DOCKERD_ROOTLESS_SH} ${DOCKERD_ROOTLESS_SH_FLAGS}" + echo +} + +cli_ctx_exists() { + name="$1" + "${BIN}/docker" --context=default context inspect -f "{{.Name}}" "${name}" > /dev/null 2>&1 +} + +cli_ctx_create() { + name="$1" + host="$2" + description="$3" + "${BIN}/docker" --context=default context create "${name}" --docker "host=${host}" --description "${description}" > /dev/null +} + +cli_ctx_use() { + name="$1" + "${BIN}/docker" --context=default context use "${name}" > /dev/null +} + +cli_ctx_rm() { + name="$1" + "${BIN}/docker" --context=default context rm -f "${name}" > /dev/null +} + +# CLI subcommand: "install" +cmd_entrypoint_install() { + init + # Most requirements are already checked in init(), except the smoke test below for RootlessKit. + # https://github.com/docker/docker-install/issues/417 + + # check RootlessKit functionality. RootlessKit will print hints if something is still unsatisfied. + # (e.g., `kernel.apparmor_restrict_unprivileged_userns` constraint) + if ! rootlesskit true; then + if [ -z "$OPT_FORCE" ]; then + ERROR "RootlessKit failed, see the error messages and https://rootlesscontaine.rs/getting-started/common/ . Set --force to ignore." + exit 1 + else + WARNING "RootlessKit failed, see the error messages and https://rootlesscontaine.rs/getting-started/common/ ." + fi + fi + + if [ -z "$SYSTEMD" ]; then + install_nonsystemd + else + install_systemd + fi + + if cli_ctx_exists "${CLI_CONTEXT}"; then + INFO "CLI context \"${CLI_CONTEXT}\" already exists" + else + INFO "Creating CLI context \"${CLI_CONTEXT}\"" + cli_ctx_create "${CLI_CONTEXT}" "unix://${XDG_RUNTIME_DIR}/docker.sock" "Rootless mode" + fi + + INFO "Using CLI context \"${CLI_CONTEXT}\"" + cli_ctx_use "${CLI_CONTEXT}" + + echo + INFO "Make sure the following environment variable(s) are set (or add them to ~/.bashrc):" + if [ -n "$XDG_RUNTIME_DIR_CREATED" ]; then + echo "# WARNING: systemd not found. You have to remove XDG_RUNTIME_DIR manually on every logout." + echo "export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}" + fi + echo "export PATH=${BIN}:\$PATH" + echo + INFO "Some applications may require the following environment variable too:" + echo "export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock" + echo + +} + +# CLI subcommand: "uninstall" +cmd_entrypoint_uninstall() { + init + # requirements are already checked in init() + if [ -z "$SYSTEMD" ]; then + INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be stopped manually:" + else + unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}" + ( + set -x + systemctl --user stop "${SYSTEMD_UNIT}" + ) || : + ( + set -x + systemctl --user disable "${SYSTEMD_UNIT}" + ) || : + rm -f "${unit_file}" + INFO "Uninstalled ${SYSTEMD_UNIT}" + fi + + if cli_ctx_exists "${CLI_CONTEXT}"; then + cli_ctx_rm "${CLI_CONTEXT}" + INFO "Deleted CLI context \"${CLI_CONTEXT}\"" + fi + unset DOCKER_HOST + unset DOCKER_CONTEXT + cli_ctx_use "default" + INFO 'Configured CLI to use the "default" context.' + INFO + INFO 'Make sure to unset or update the environment PATH, DOCKER_HOST, and DOCKER_CONTEXT environment variables if you have added them to `~/.bashrc`.' + INFO "This uninstallation tool does NOT remove Docker binaries and data." + INFO "To remove data, run: \`$BIN/rootlesskit rm -rf $HOME/.local/share/docker\`" +} + +# text for --help +usage() { + echo "Usage: ${ARG0} [OPTIONS] COMMAND" + echo + echo "A setup tool for Rootless Docker (${DOCKERD_ROOTLESS_SH})." + echo + echo "Documentation: https://docs.docker.com/go/rootless/" + echo + echo "Options:" + echo " -f, --force Ignore rootful Docker (/var/run/docker.sock)" + echo " --skip-iptables Ignore missing iptables" + echo + echo "Commands:" + echo " check Check prerequisites" + echo " nsenter Enter into RootlessKit namespaces (mostly for debugging)" + echo " install Install systemd unit (if systemd is available) and show how to manage the service" + echo " uninstall Uninstall systemd unit" +} + +# parse CLI args +if ! args="$(getopt -o hf --long help,force,skip-iptables -n "$ARG0" -- "$@")"; then + usage + exit 1 +fi +eval set -- "$args" +while [ "$#" -gt 0 ]; do + arg="$1" + shift + case "$arg" in + -h | --help) + usage + exit 0 + ;; + -f | --force) + OPT_FORCE=1 + ;; + --skip-iptables) + OPT_SKIP_IPTABLES=1 + ;; + --) + break + ;; + *) + # XXX this means we missed something in our "getopt" arguments above! + ERROR "Scripting error, unknown argument '$arg' when parsing script arguments." + exit 1 + ;; + esac +done + +command="${1:-}" +if [ -z "$command" ]; then + ERROR "No command was specified. Run with --help to see the usage. Maybe you want to run \`$ARG0 install\`?" + exit 1 +fi + +if ! command -v "cmd_entrypoint_${command}" > /dev/null 2>&1; then + ERROR "Unknown command: ${command}. Run with --help to see the usage." + exit 1 +fi + +# main +"cmd_entrypoint_${command}" "$@" diff --git a/Moodle/Tools/dockerd-rootless.sh b/Moodle/Tools/dockerd-rootless.sh new file mode 100644 index 0000000..f798cd0 --- /dev/null +++ b/Moodle/Tools/dockerd-rootless.sh @@ -0,0 +1,203 @@ +#!/bin/sh +# dockerd-rootless.sh executes dockerd in rootless mode. +# +# Usage: dockerd-rootless.sh [DOCKERD_OPTIONS] +# +# External dependencies: +# * newuidmap and newgidmap needs to be installed. +# * /etc/subuid and /etc/subgid needs to be configured for the current user. +# * Either one of slirp4netns (>= v0.4.0), VPNKit, lxc-user-nic needs to be installed. +# +# Recognized environment variables: +# * DOCKERD_ROOTLESS_ROOTLESSKIT_STATE_DIR=DIR: the rootlesskit state dir. Defaults to "$XDG_RUNTIME_DIR/dockerd-rootless". +# * DOCKERD_ROOTLESS_ROOTLESSKIT_NET=(slirp4netns|vpnkit|pasta|lxc-user-nic): the rootlesskit network driver. Defaults to "slirp4netns" if slirp4netns (>= v0.4.0) is installed. Otherwise defaults to "vpnkit". +# * DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=NUM: the MTU value for the rootlesskit network driver. Defaults to 65520 for slirp4netns, 1500 for other drivers. +# * DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=(builtin|slirp4netns|implicit): the rootlesskit port driver. Defaults to "builtin". +# * DOCKERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX=(auto|true|false): whether to protect slirp4netns with a dedicated mount namespace. Defaults to "auto". +# * DOCKERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP=(auto|true|false): whether to protect slirp4netns with seccomp. Defaults to "auto". +# * DOCKERD_ROOTLESS_ROOTLESSKIT_DISABLE_HOST_LOOPBACK=(true|false): prohibit connections to 127.0.0.1 on the host (including via 10.0.2.2, in the case of slirp4netns). Defaults to "true". + +# To apply an environment variable via systemd, create ~/.config/systemd/user/docker.service.d/override.conf as follows, +# and run `systemctl --user daemon-reload && systemctl --user restart docker`: +# --- BEGIN --- +# [Service] +# Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta" +# Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit" +# --- END --- + +# Guide to choose the network driver and the port driver: +# +# Network driver | Port driver | Net throughput | Port throughput | Src IP | No SUID | Note +# ---------------|----------------|----------------|-----------------|--------|---------|--------------------------------------------------------- +# slirp4netns | builtin | Slow | Fast ✅ | ❌ | ✅ | Default in typical setup +# vpnkit | builtin | Slow | Fast ✅ | ❌ | ✅ | Default when slirp4netns is not installed +# slirp4netns | slirp4netns | Slow | Slow | ✅ | ✅ | +# pasta | implicit | Slow | Fast ✅ | ✅ | ✅ | Experimental; Needs recent version of pasta (2023_12_04) +# lxc-user-nic | builtin | Fast ✅ | Fast ✅ | ❌ | ❌ | Experimental +# (bypass4netns) | (bypass4netns) | Fast ✅ | Fast ✅ | ✅ | ✅ | (Not integrated to RootlessKit) + +# See the documentation for the further information: https://docs.docker.com/go/rootless/ + +set -e -x +case "$1" in + "check" | "install" | "uninstall") + echo "Did you mean 'dockerd-rootless-setuptool.sh $@' ?" + exit 1 + ;; +esac +if ! [ -w "$XDG_RUNTIME_DIR" ]; then + echo "XDG_RUNTIME_DIR needs to be set and writable" + exit 1 +fi +if ! [ -d "$HOME" ]; then + echo "HOME needs to be set and exist." + exit 1 +fi + +mount_directory() { + if [ -z "$_DOCKERD_ROOTLESS_CHILD" ]; then + echo "mount_directory should be called from the child context. Otherwise data loss is at risk" >&2 + exit 1 + fi + + DIRECTORY="$1" + if [ ! -d "$DIRECTORY" ]; then + return + fi + + # Bind mount directory: this makes this directory visible to + # Dockerd, even if it is originally a symlink, given Dockerd does + # not always follow symlinks. Some directories might also be + # "copied-up", meaning that they will also be writable on the child + # namespace; this will be the case only if they are provided as + # --copy-up to the rootlesskit. + DIRECTORY_REALPATH=$(realpath "$DIRECTORY") + MOUNT_OPTIONS="${2:---bind}" + rm -rf "$DIRECTORY" + mkdir -p "$DIRECTORY" + mount $MOUNT_OPTIONS "$DIRECTORY_REALPATH" "$DIRECTORY" +} + +rootlesskit="" +for f in docker-rootlesskit rootlesskit; do + if command -v $f > /dev/null 2>&1; then + rootlesskit=$f + break + fi +done +if [ -z "$rootlesskit" ]; then + echo "rootlesskit needs to be installed" + exit 1 +fi + +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_STATE_DIR:=$XDG_RUNTIME_DIR/dockerd-rootless}" +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_NET:=}" +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_MTU:=}" +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER:=builtin}" +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX:=auto}" +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP:=auto}" +: "${DOCKERD_ROOTLESS_ROOTLESSKIT_DISABLE_HOST_LOOPBACK:=}" +net=$DOCKERD_ROOTLESS_ROOTLESSKIT_NET +mtu=$DOCKERD_ROOTLESS_ROOTLESSKIT_MTU +if [ -z "$net" ]; then + if command -v slirp4netns > /dev/null 2>&1; then + # If --netns-type is present in --help, slirp4netns is >= v0.4.0. + if slirp4netns --help | grep -qw -- --netns-type; then + net=slirp4netns + if [ -z "$mtu" ]; then + mtu=65520 + fi + else + echo "slirp4netns found but seems older than v0.4.0. Falling back to VPNKit." + fi + fi + if [ -z "$net" ]; then + if command -v vpnkit > /dev/null 2>&1; then + net=vpnkit + else + echo "Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed" + exit 1 + fi + fi +fi +if [ -z "$mtu" ]; then + mtu=1500 +fi + +host_loopback="--disable-host-loopback" +if [ "$DOCKERD_ROOTLESS_ROOTLESSKIT_DISABLE_HOST_LOOPBACK" = "false" ]; then + host_loopback="" +fi + +dockerd="${DOCKERD:-dockerd}" + +if [ -z "$_DOCKERD_ROOTLESS_CHILD" ]; then + _DOCKERD_ROOTLESS_CHILD=1 + export _DOCKERD_ROOTLESS_CHILD + if [ "$(id -u)" = "0" ]; then + echo "This script must be executed as a non-privileged user" + exit 1 + fi + # `selinuxenabled` always returns false in RootlessKit child, so we execute `selinuxenabled` in the parent. + # https://github.com/rootless-containers/rootlesskit/issues/94 + if command -v selinuxenabled > /dev/null 2>&1 && selinuxenabled; then + _DOCKERD_ROOTLESS_SELINUX=1 + export _DOCKERD_ROOTLESS_SELINUX + fi + # Re-exec the script via RootlessKit, so as to create unprivileged {user,mount,network} namespaces. + # + # --copy-up allows removing/creating files in the directories by creating tmpfs and symlinks + # * /etc: copy-up is required so as to prevent `/etc/resolv.conf` in the + # namespace from being unexpectedly unmounted when `/etc/resolv.conf` is recreated on the host + # (by either systemd-networkd or NetworkManager) + # * /run: copy-up is required so that we can create /run/docker (hardcoded for plugins) in our namespace + exec $rootlesskit \ + --state-dir=$DOCKERD_ROOTLESS_ROOTLESSKIT_STATE_DIR \ + --net=$net --mtu=$mtu \ + --slirp4netns-sandbox=$DOCKERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX \ + --slirp4netns-seccomp=$DOCKERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP \ + $host_loopback --port-driver=$DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER \ + --copy-up=/etc --copy-up=/run \ + --propagation=rslave \ + $DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS \ + "$0" "$@" +else + [ "$_DOCKERD_ROOTLESS_CHILD" = 1 ] + + # The Container Device Interface (CDI) specs can be found by default + # under {/etc,/var/run}/cdi. More information at: + # https://github.com/cncf-tags/container-device-interface + # + # In order to use the Container Device Interface (CDI) integration, + # the CDI paths need to exist before the Docker daemon is started in + # order for it to read the CDI specification files. Otherwise, a + # Docker daemon restart will be required for the daemon to discover + # them. + # + # If another set of CDI paths (other than the default /etc/cdi and + # /var/run/cdi) are configured through the Docker configuration file + # (using "cdi-spec-dirs"), they need to be bind mounted in rootless + # mode; otherwise the Docker daemon won't have access to the CDI + # specification files. + mount_directory /etc/cdi + mount_directory /var/run/cdi + + # remove the symlinks for the existing files in the parent namespace if any, + # so that we can create our own files in our mount namespace. + rm -f /run/docker /run/containerd /run/xtables.lock + + if [ -n "$_DOCKERD_ROOTLESS_SELINUX" ]; then + # iptables requires /run in the child to be relabeled. The actual /run in the parent is unaffected. + # https://github.com/containers/podman/blob/e6fc34b71aa9d876b1218efe90e14f8b912b0603/libpod/networking_linux.go#L396-L401 + # https://github.com/moby/moby/issues/41230 + chcon system_u:object_r:iptables_var_run_t:s0 /run + fi + + if [ "$(stat -c %T -f /etc)" = "tmpfs" ] && [ -L "/etc/ssl" ]; then + # Workaround for "x509: certificate signed by unknown authority" on openSUSE Tumbleweed. + # https://github.com/rootless-containers/rootlesskit/issues/225 + mount_directory /etc/ssl "--rbind" + fi + + exec "$dockerd" "$@" +fi diff --git a/Moodle/Tools/entovi.sh b/Moodle/Tools/entovi.sh new file mode 100644 index 0000000..64d6ba1 --- /dev/null +++ b/Moodle/Tools/entovi.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Tìm tất cả các tệp có tên kết thúc bằng .en.vtt nhưng không phải *.en.en.vtt hoặc *.vi.vi.vtt +find . -type f -name "*.en.vtt" ! -name "*.en.en.vtt" ! -name "*.vi.vi.vtt" -print0 | while IFS= read -r -d $'\0' file; do + # Lấy phần tên tệp trước .en.vtt + base_name=$(basename "$file" .en.vtt) + + # Lấy thư mục chứa tệp + dir_name=$(dirname "$file") + + # Tạo đường dẫn đầy đủ cho tệp đích + destination_file="${dir_name}/${base_name}.vi.vtt" + + # Sao chép tệp + cp "$file" "$destination_file" + + # In thông báo + echo "Đã sao chép: \"$file\" thành \"$destination_file\"" +done + +echo "Hoàn tất quá trình sao chép." diff --git a/Moodle/Tools/envtt.sh b/Moodle/Tools/envtt.sh new file mode 100644 index 0000000..b4f7faf --- /dev/null +++ b/Moodle/Tools/envtt.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Quét toàn bộ file *.vtt (trừ *.en.vtt và *.vi.vtt) trong thư mục hiện tại và thư mục con +find . -type f -name "*.vtt" ! -name "*.en.vtt" ! -name "*.vi.vtt" | while read file; do + # Tạo tên file mới bằng cách thay .vtt thành .en.vtt + new_file="${file%.vtt}.en.vtt" + + # Copy nội dung sang file mới + cp "$file" "$new_file" + + # Hiển thị thông báo + echo "Đã tạo: $new_file" +done + +echo "Hoàn thành sao chép tất cả các tệp .vtt thành .en.vtt (trừ *.en.vtt và *.vi.vtt)" diff --git a/Moodle/Tools/getdesrt.sh b/Moodle/Tools/getdesrt.sh new file mode 100644 index 0000000..9d22544 --- /dev/null +++ b/Moodle/Tools/getdesrt.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# Set the custom cache directory for Whisper models +export XDG_CACHE_HOME="/backup/whisper" + +# Create the cache directory if it does not exist +mkdir -p "$XDG_CACHE_HOME" + +# Iterate through all MP4 files in the current directory and subdirectories +find . -type f -name "*.mp4" | while read -r file; do + # Extract the directory and file name without the .mp4 extension + dir=$(dirname "$file") + filename=$(basename "$file" .mp4) + srt_file="$dir/$filename.srt" + + # Check if the corresponding SRT file exists + if [ ! -f "$srt_file" ]; then + # Run Whisper to generate SRT subtitles with English as the source language + echo "Generating subtitles for: $file" + whisper "$file" --model medium --output_format srt --task transcribe + + # Rename the generated subtitle file to match the required format + if [ -f "$dir/$filename_en.srt" ]; then + mv "$dir/$filename_en.srt" "$srt_file" + else + echo "Warning: Expected $dir/$filename_en.srt not found." + fi + else + echo "Subtitle already exists for: $file" + fi +done + +echo "Process completed!" diff --git a/Moodle/Tools/gets.sh b/Moodle/Tools/gets.sh new file mode 100644 index 0000000..7192dbb --- /dev/null +++ b/Moodle/Tools/gets.sh @@ -0,0 +1,2 @@ +wget --no-check-certificate --content-disposition --max-redirect=10 --user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36" --referer="https://en.git.ir/" -i list.txt + diff --git a/Moodle/Tools/getsrt.sh b/Moodle/Tools/getsrt.sh new file mode 100644 index 0000000..a7a7559 --- /dev/null +++ b/Moodle/Tools/getsrt.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# Set the custom cache directory for Whisper models +export XDG_CACHE_HOME="/backup/whisper" + +# Create the cache directory if it does not exist +mkdir -p "$XDG_CACHE_HOME" + +# Iterate through all MP4 files in the current directory and subdirectories +find . -type f -name "*.mp4" | while read -r file; do + # Extract the directory and file name without the .mp4 extension + dir=$(dirname "$file") + filename=$(basename "$file" .mp4) + srt_file="$dir/$filename.srt" + + # Check if the corresponding SRT file exists + if [ ! -f "$srt_file" ]; then + # Run Whisper to generate SRT subtitles with English as the source language + echo "Generating subtitles for: $file" + whisper "$file" --model medium --output_format srt --task transcribe --language en --output_dir "$dir" + + # Rename the generated subtitle file to match the required format + if [ -f "$dir/$filename_en.srt" ]; then + mv "$dir/$filename_en.srt" "$srt_file" + else + echo "Warning: Expected $dir/$filename_en.srt not found." + fi + else + echo "Subtitle already exists for: $file" + fi +done + +echo "Process completed!" diff --git a/Moodle/Tools/gettext.sh b/Moodle/Tools/gettext.sh new file mode 100644 index 0000000..2bc3e49 --- /dev/null +++ b/Moodle/Tools/gettext.sh @@ -0,0 +1,135 @@ +#!/usr/bin/sh +# +# Copyright (C) 2003, 2005-2007, 2011, 2018-2020 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# + +# Find a way to echo strings without interpreting backslash. +if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then + echo='echo' +else + if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then + echo='printf %s\n' + else + echo_func () { + cat < +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law." + echo "Written by" "Bruno Haible" + } + if test $# = 1; then + case "$1" in + --help | --hel | --he | --h ) + func_usage; exit 0 ;; + --version | --versio | --versi | --vers | --ver | --ve | --v ) + func_version; exit 0 ;; + esac + fi + func_usage 1>&2 + exit 1 + ;; + esac +fi + +# eval_gettext MSGID +# looks up the translation of MSGID and substitutes shell variables in the +# result. +eval_gettext () { + gettext "$1" | (export PATH `envsubst --variables "$1"`; envsubst "$1") +} + +# eval_ngettext MSGID MSGID-PLURAL COUNT +# looks up the translation of MSGID / MSGID-PLURAL for COUNT and substitutes +# shell variables in the result. +eval_ngettext () { + ngettext "$1" "$2" "$3" | (export PATH `envsubst --variables "$1 $2"`; envsubst "$1 $2") +} + +# eval_pgettext MSGCTXT MSGID +# looks up the translation of MSGID in the context MSGCTXT and substitutes +# shell variables in the result. +eval_pgettext () { + gettext --context="$1" "$2" | (export PATH `envsubst --variables "$2"`; envsubst "$2") +} + +# eval_npgettext MSGCTXT MSGID MSGID-PLURAL COUNT +# looks up the translation of MSGID / MSGID-PLURAL for COUNT in the context +# MSGCTXT and substitutes shell variables in the result. +eval_npgettext () { + ngettext --context="$1" "$2" "$3" "$4" | (export PATH `envsubst --variables "$2 $3"`; envsubst "$2 $3") +} + +# Note: This use of envsubst is much safer than using the shell built-in 'eval' +# would be. +# 1) The security problem with Chinese translations that happen to use a +# character such as \xe0\x60 is avoided. +# 2) The security problem with malevolent translators who put in command lists +# like "$(...)" or "`...`" is avoided. +# 3) The translations can only refer to shell variables that are already +# mentioned in MSGID or MSGID-PLURAL. +# +# Note: "export PATH" above is a dummy; this is for the case when +# `envsubst --variables ...` returns nothing. +# +# Note: In eval_ngettext above, "$1 $2" means a string whose variables set is +# the union of the variables set of "$1" and "$2". +# +# Note: The minimal use of backquote above ensures that trailing newlines are +# not dropped, not from the gettext invocation and not from the value of any +# shell variable. +# +# Note: Field splitting on the `envsubst --variables ...` result is desired, +# since envsubst outputs the variables, separated by newlines. Pathname +# wildcard expansion or tilde expansion has no effect here, since the words +# output by "envsubst --variables ..." consist solely of alphanumeric +# characters and underscore. diff --git a/Moodle/Tools/getvtt.sh b/Moodle/Tools/getvtt.sh new file mode 100644 index 0000000..0016c3f --- /dev/null +++ b/Moodle/Tools/getvtt.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Set the custom cache directory for Whisper models +export XDG_CACHE_HOME="/backup/whisper" + +# Create the cache directory if it does not exist +mkdir -p "$XDG_CACHE_HOME" + +# Function để xử lý từng file (chạy trong từng tiến trình riêng) +process_file() { + file="$1" + dir=$(dirname "$file") + filename=$(basename "$file" .mp4) + vtt_file="$dir/$filename.vtt" + + if [ ! -f "$vtt_file" ]; then + echo "🔄 Generating subtitles for: $file" + whisper "$file" --model medium --output_format vtt --task transcribe --language en --output_dir "$dir" + + if [ -f "$dir/${filename}_en.vtt" ]; then + mv "$dir/${filename}_en.vtt" "$vtt_file" + echo "✅ Created: $vtt_file" + else + echo "⚠️ Warning: Expected $dir/${filename}_en.vtt not found." + fi + else + echo "⏩ Subtitle already exists for: $file" + fi +} + +export -f process_file +export XDG_CACHE_HOME + +# Tìm tất cả file .mp4 và xử lý song song với tối đa 4 tiến trình +find . -type f -name "*.mp4" | xargs -n 1 -P 4 -I {} bash -c 'process_file "$@"' _ {} + +echo "🎉 All done!" diff --git a/Moodle/Tools/mkv2mp4.sh b/Moodle/Tools/mkv2mp4.sh new file mode 100644 index 0000000..41f3353 --- /dev/null +++ b/Moodle/Tools/mkv2mp4.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Kịch bản chuyển đổi tất cả các file .mkv sang .mp4 một cách đệ quy. +# Phiên bản cải tiến: dễ cấu hình, ghi log cho từng file và dùng tùy chọn ffmpeg hiện đại. + +# --- Phần cấu hình --- +# Bạn có thể thay đổi các giá trị này để phù hợp với nhu cầu. +# CRF (Constant Rate Factor) cho video: 0-51. Càng thấp chất lượng càng cao. 18-28 là khoảng hợp lý. +CRF="23" +# Preset cho tốc độ encode: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow. +# Càng chậm thì nén càng tốt (file nhỏ hơn) nhưng tốn thời gian hơn. 'medium' là mặc định cân bằng. +PRESET="medium" +# Bitrate cho audio. '128k' hoặc '192k' là phổ biến cho codec AAC. +AUDIO_BITRATE="128k" + +# --- Bắt đầu kịch bản --- + +# 1. Kiểm tra xem ffmpeg đã được cài đặt chưa +if ! command -v ffmpeg >/dev/null 2>&1; then + echo "❌ Lỗi: Lệnh 'ffmpeg' không tồn tại. Vui lòng cài đặt ffmpeg." + exit 1 +fi + +# 2. Xác định thư mục mục tiêu (lấy tham số đầu vào, mặc định là thư mục hiện tại) +DIR="${1:-.}" +echo "🔍 Bắt đầu quét thư mục '$DIR'..." +echo "---" + +# 3. Tìm và lặp qua từng file .mkv +# -print0 và -d '' là cách an toàn nhất để xử lý tên file có chứa ký tự đặc biệt hoặc dấu cách. +find "$DIR" -type f -iname '*.mkv' -print0 | while IFS= read -r -d '' file; do + # Tạo tên file output bằng cách thay thế đuôi .mkv thành .mp4 một cách hiệu quả + out_file="${file%.mkv}.mp4" + log_file="${out_file}.log" + + # Kiểm tra nếu file .mp4 đã tồn tại thì bỏ qua + if [ -f "$out_file" ]; then + echo "⚠️ Bỏ qua (đã tồn tại): '$out_file'" + continue + fi + + echo "▶️ Đang chuyển đổi: '$file'" + + # 4. Thực thi lệnh ffmpeg + # -hide_banner: Ẩn thông tin banner của ffmpeg cho log gọn hơn. + # -c:v libx264 -crf $CRF -preset $PRESET: Tùy chọn encode video H.264 chất lượng cao. + # -c:a aac -b:a $AUDIO_BITRATE: Tùy chọn encode audio AAC. + # Ghi log (cả stdout và stderr) vào một file riêng cho mỗi video để tránh ghi đè. + ffmpeg -nostdin -hide_banner -i "$file" \ + -c:v libx264 -crf "$CRF" -preset "$PRESET" \ + -c:a aac -b:a "$AUDIO_BITRATE" \ + "$out_file" > "$log_file" 2>&1 + + # 5. Kiểm tra kết quả chuyển đổi và thông báo + if [ $? -eq 0 ]; then + echo "✅ Hoàn thành: '$out_file'" + # Nếu muốn tự động xóa log khi thành công, bỏ comment dòng dưới + # rm "$log_file" + else + echo "❌ Lỗi khi chuyển đổi file trên. Xem chi tiết trong log: '$log_file'" + fi + echo "---" +done + +echo "🎉 Tất cả đã xong!" \ No newline at end of file diff --git a/Moodle/Tools/removeeng.sh b/Moodle/Tools/removeeng.sh new file mode 100644 index 0000000..e8865e0 --- /dev/null +++ b/Moodle/Tools/removeeng.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Qut cc file k?t thc b?ng .en.vtt, .vi.vtt ho?c .srt +find . -type f \( -name "*.en.vtt" -o -name "*.vi.vtt" -o -name "*.srt" \) -print0 | while IFS= read -r -d $'\0' file; do + # Ki?m tra xem tn t?p c ch?a ' English' khng + if [[ "$file" == *" English."* ]]; then + # ?i tn file b?ng cch xa ' English' tru?c ph?n m? r?ng + newfile=$(echo "$file" | sed 's/ English\././') + + # ?i tn t?p + mv "$file" "$newfile" + + echo " d?i tn: \"$file\" ? \"$ diff --git a/Moodle/Tools/rename_topics.sh b/Moodle/Tools/rename_topics.sh new file mode 100644 index 0000000..59302c5 --- /dev/null +++ b/Moodle/Tools/rename_topics.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Lấy thư mục hiện hành nơi người dùng đang gọi lệnh +TARGET_DIR="$(pwd)" + +# Đổi tên các thư mục con cấp 1 theo mẫu: 1 - ABC → 1. ABC +find "$TARGET_DIR" -mindepth 1 -maxdepth 1 -type d -regextype posix-extended -regex '.*/[0-9]+ - .+' | while read dir; do + base=$(basename "$dir") + newname=$(echo "$base" | sed -E 's/^([0-9]+) - (.+)$/\1. \2/') + if [ "$base" != "$newname" ]; then + echo "🔁 Đổi tên: $base → $newname" + mv "$TARGET_DIR/$base" "$TARGET_DIR/$newname" + fi +done diff --git a/Moodle/Tools/rescan-scsi-bus.sh b/Moodle/Tools/rescan-scsi-bus.sh new file mode 100644 index 0000000..89d6422 --- /dev/null +++ b/Moodle/Tools/rescan-scsi-bus.sh @@ -0,0 +1,1401 @@ +#!/bin/bash +# Script to rescan SCSI bus, using the scsi add-single-device mechanism. +# (c) 1998--2010 Kurt Garloff , GNU GPL v2 or v3 +# (c) 2006--2018 Hannes Reinecke, GNU GPL v2 or later +# $Id: rescan-scsi-bus.sh,v 1.57 2012/03/31 14:08:48 garloff Exp $ + +VERSION="20180615" +SCAN_WILD_CARD=4294967295 + +setcolor () +{ + red="\e[0;31m" + green="\e[0;32m" + yellow="\e[0;33m" + bold="\e[0;1m" + norm="\e[0;0m" +} + +unsetcolor () +{ + red=""; green="" + yellow=""; norm="" +} + +echo_debug() +{ + if [ "$debug" -eq 1 ] ; then + echo "$1" + fi +} + +# Output some text and return cursor to previous position +# (only works for simple strings) +# Stores length of string in LN and returns it +print_and_scroll_back () +{ + STRG="$1" + LN=${#STRG} + BK="" + declare -i cntr=0 + while [ $cntr -lt "$LN" ] ; do BK="$BK\e[D"; let cntr+=1; done + echo -en "$STRG$BK" + return "$LN" +} + +# Overwrite a text of length $LN with whitespace +white_out () +{ + BK=""; WH="" + declare -i cntr=0 + while [ $cntr -lt "$LN" ] ; do BK="$BK\e[D"; WH="$WH "; let cntr+=1; done + echo -en "$WH$BK" +} + +# Return hosts. sysfs must be mounted +findhosts_26 () +{ + hosts= + for hostdir in /sys/class/scsi_host/host* ; do + [ -e "$hostdir" ] || continue + hostno=${hostdir#/sys/class/scsi_host/host} + if [ -f "$hostdir/isp_name" ] ; then + hostname="qla2xxx" + elif [ -f "$hostdir/lpfc_drvr_version" ] ; then + hostname="lpfc" + else + hostname=$(cat "$hostdir/proc_name") + fi + hosts="$hosts $hostno" + echo_debug "Host adapter $hostno ($hostname) found." + done + if [ -z "$hosts" ] ; then + echo "No SCSI host adapters found in sysfs" + exit 1; + fi + # Not necessary just use double quotes around variable to preserve new lines + #hosts=$(echo $hosts | tr ' ' '\n') +} + +# Return hosts. /proc/scsi/HOSTADAPTER/? must exist +findhosts () +{ + hosts= + for driverdir in /proc/scsi/*; do + driver=${driverdir#/proc/scsi/} + if [ "$driver" = scsi ] || [ "$driver" = sg ] || [ "$driver" = dummy ] || [ "$driver" = device_info ] ; then continue; fi + for hostdir in $driverdir/*; do + name=${hostdir#/proc/scsi/*/} + if [ "$name" = add_map ] || [ "$name" = map ] || [ "$name" = mod_parm ] ; then continue; fi + num=$name + driverinfo=$driver + if [ -r "$hostdir/status" ] ; then + num=$(printf '%d\n' "$(sed -n 's/SCSI host number://p' "$hostdir/status")") + driverinfo="$driver:$name" + fi + hosts="$hosts $num" + echo "Host adapter $num ($driverinfo) found." + done + done +} + +printtype () +{ + local type=$1 + + case "$type" in + 0) echo "Direct-Access" ;; + 1) echo "Sequential-Access" ;; + 2) echo "Printer" ;; + 3) echo "Processor" ;; + 4) echo "WORM" ;; + 5) echo "CD-ROM" ;; + 6) echo "Scanner" ;; + 7) echo "Optical-Device" ;; + 8) echo "Medium-Changer" ;; + 9) echo "Communications" ;; + 10) echo "Unknown" ;; + 11) echo "Unknown" ;; + 12) echo "RAID" ;; + 13) echo "Enclosure" ;; + 14) echo "Direct-Access-RBC" ;; + *) echo "Unknown" ;; + esac +} + +print02i() +{ + if [ "$1" = "*" ] ; then + echo "00" + else + printf "%02i" "$1" + fi +} + +# Get /proc/scsi/scsi info for device $host:$channel:$id:$lun +# Optional parameter: Number of lines after first (default = 2), +# result in SCSISTR, return code 1 means empty. +procscsiscsi () +{ + if [ -z "$1" ] ; then + LN=2 + else + LN=$1 + fi + CHANNEL=$(print02i "$channel") + ID=$(print02i "$id") + LUN=$(print02i "$lun") + if [ -d /sys/class/scsi_device ]; then + SCSIPATH="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}" + if [ -d "$SCSIPATH" ] ; then + SCSISTR="Host: scsi${host} Channel: $CHANNEL Id: $ID Lun: $LUN" + if [ "$LN" -gt 0 ] ; then + IVEND=$(cat "${SCSIPATH}/device/vendor") + IPROD=$(cat "${SCSIPATH}/device/model") + IPREV=$(cat "${SCSIPATH}/device/rev") + SCSIDEV=$(printf ' Vendor: %-08s Model: %-16s Rev: %-4s' "$IVEND" "$IPROD" "$IPREV") + SCSISTR="$SCSISTR +$SCSIDEV" + fi + if [ "$LN" -gt 1 ] ; then + ILVL=$(cat "${SCSIPATH}/device/scsi_level") + type=$(cat "${SCSIPATH}/device/type") + ITYPE=$(printtype "$type") + SCSITMP=$(printf ' Type: %-17s ANSI SCSI revision: %02d' "$ITYPE" "$((ILVL - 1))") + SCSISTR="$SCSISTR +$SCSITMP" + fi + else + return 1 + fi + else + grepstr="scsi$host Channel: $CHANNEL Id: $ID Lun: $LUN" + SCSISTR=$(grep -A "$LN" -e "$grepstr" /proc/scsi/scsi) + fi + if [ -z "$SCSISTR" ] ; then + return 1 + else + return 0 + fi +} + +# Find sg device with 2.6 sysfs support +sgdevice26 () +{ + local gendev + + gendev=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/generic + if [ -e "$gendev" ] ; then + SGDEV=$(basename "$(readlink "$gendev")") + else + for SGDEV in /sys/class/scsi_generic/sg*; do + DEV=$(readlink "$SGDEV/device") + if [ "${DEV##*/}" = "$host:$channel:$id:$lun" ] ; then + SGDEV=$(basename "$SGDEV"); return + fi + done + SGDEV="" + fi +} + +# Find sg device with 2.4 report-devs extensions +sgdevice24 () +{ + if procscsiscsi 3; then + SGDEV=$(echo "$SCSISTR" | grep 'Attached drivers:' | sed 's/^ *Attached drivers: \(sg[0-9]*\).*/\1/') + fi +} + +# Find sg device that belongs to SCSI device $host $channel $id $lun +# and return in SGDEV +sgdevice () +{ + SGDEV= + if [ -d /sys/class/scsi_device ] ; then + sgdevice26 + else + DRV=$(grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null) + repdevstat=$((1-$?)) + if [ $repdevstat = 0 ]; then + echo "scsi report-devs 1" >/proc/scsi/scsi + DRV=$(grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null) + [ $? -eq 1 ] && return + fi + if ! echo "$DRV" | grep -q 'drivers: sg'; then + modprobe sg + fi + sgdevice24 + if [ $repdevstat = 0 ]; then + echo "scsi report-devs 0" >/proc/scsi/scsi + fi + fi +} + +# Whether or not the RMB (removable) bit has been set in the INQUIRY response. +# Uses ${host}, ${channel}, ${id} and ${lun}. Assumes that sg_device() has +# already been called. How to test this function: copy/paste this function +# in a shell and run +# (cd /sys/class/scsi_device && for d in *; do set ${d//:/ }; echo -n "$d $( "; SGDEV=bsg/$d host=$1 channel=$2 id=$3 lun=$4 is_removable; done) +is_removable () +{ + local b p + + p=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/inquiry + # Extract the second byte of the INQUIRY response and check bit 7 (mask 0x80). + b=$(hexdump -n1 -e '/1 "%02X"' "$p" 2>/dev/null) + if [ -n "$b" ]; then + echo $(((0x$b & 0x80) != 0)) + else + sg_inq /dev/$SGDEV 2>/dev/null | sed -n 's/^.*RMB=\([0-9]*\).*$/\1/p' + fi +} + +# Test if SCSI device is still responding to commands +# Return values: +# 0 device is present +# 1 device has changed +# 2 device has been removed +testonline () +{ + local ctr RC RMB + + : testonline + ctr=0 + RC=0 + # Set default values + IPTYPE=31 + IPQUAL=3 + [ ! -x /usr/bin/sg_turs ] && return 0 + sgdevice + [ -z "$SGDEV" ] && return 0 + sg_turs /dev/$SGDEV >/dev/null 2>&1 + RC=$? + + # Handle in progress of becoming ready and unit attention + while [ $RC = 2 -o $RC = 6 ] && [ $ctr -le 30 ] ; do + if [ $RC = 2 ] && [ "$RMB" != "1" ] ; then + echo -n "." + let LN+=1 + sleep 1 + else + sleep 0.02 + fi + let ctr+=1 + sg_turs /dev/$SGDEV >/dev/null 2>&1 + RC=$? + # Check for removable device; TEST UNIT READY obviously will + # fail for a removable device with no medium + RMB=$(is_removable) + print_and_scroll_back "$host:$channel:$id:$lun $SGDEV ($RMB) " + [ $RC = 2 ] && [ "$RMB" = "1" ] && break + done + if [ $ctr != 0 ] ; then + white_out + fi + # echo -e "\e[A\e[A\e[A${yellow}Test existence of $SGDEV = $RC ${norm} \n\n\n" + [ $RC = 1 ] && return $RC + # Reset RC (might be !=0 for passive paths) + RC=0 + # OK, device online, compare INQUIRY string + INQ=$(sg_inq "$sg_len_arg" /dev/$SGDEV 2>/dev/null) + if [ -z "$INQ" ] ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}INQUIRY failed${norm} \n\n\n" + return 2 + fi + IVEND=$(echo "$INQ" | grep 'Vendor identification:' | sed 's/^[^:]*: \(.*\)$/\1/') + IPROD=$(echo "$INQ" | grep 'Product identification:' | sed 's/^[^:]*: \(.*\)$/\1/') + IPREV=$(echo "$INQ" | grep 'Product revision level:' | sed 's/^[^:]*: \(.*\)$/\1/') + STR=$(printf " Vendor: %-08s Model: %-16s Rev: %-4s" "$IVEND" "$IPROD" "$IPREV") + IPTYPE=$(echo "$INQ" | sed -n 's/.* Device_type=\([0-9]*\) .*/\1/p') + if [ -z "$IPTYPE" ]; then + IPTYPE=$(echo "$INQ" | sed -n 's/.* PDT=\([0-9]*\) .*/\1/p') + fi + IPQUAL=$(echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) Device.*/\1/p') + if [ -z "$IPQUAL" ] ; then + IPQUAL=$(echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) PDT.*/\1/p') + fi + if [ "$IPQUAL" != 0 ] ; then + [ -z "$IPQUAL" ] && IPQUAL=3 + [ -z "$IPTYPE" ] && IPTYPE=31 + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}LU not available (PQual $IPQUAL)${norm} \n\n\n" + return 2 + fi + + TYPE=$(printtype $IPTYPE) + if ! procscsiscsi ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV removed.\n\n\n" + return 2 + fi + TMPSTR=$(echo "$SCSISTR" | grep 'Vendor:') + if [ "$ignore_rev" -eq 0 ] ; then + if [ "$TMPSTR" != "$STR" ]; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n" + return 1 + fi + else + # Ignore disk revision change + local old_str_no_rev= + local new_str_no_rev= + + old_str_no_rev=${TMPSTR%Rev:*} + new_str_no_rev=${STR%Rev:*} + if [ "$old_str_no_rev" != "$new_str_no_rev" ]; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n" + return 1 + fi + fi + TMPSTR=$(echo "$SCSISTR" | sed -n 's/.*Type: *\(.*\) *ANSI.*/\1/p' | sed 's/ *$//g') + if [ "$TMPSTR" != "$TYPE" ] ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${TMPSTR} \nto: $TYPE ${norm} \n\n\n" + return 1 + fi + return $RC +} + +# Test if SCSI device $host $channel $id $lun exists +# Outputs description from /proc/scsi/scsi (unless arg passed) +# Returns SCSISTR (empty if no dev) +testexist () +{ + : testexist + SCSISTR= + if procscsiscsi && [ -z "$1" ] ; then + echo "$SCSISTR" | head -n1 + echo "$SCSISTR" | tail -n2 | pr -o4 -l1 + fi +} + +# Returns the list of existing channels per host +chanlist () +{ + local hcil + local cil + local chan + local tmpchan + + for dev in /sys/class/scsi_device/${host}:* ; do + [ -d "$dev" ] || continue; + hcil=${dev##*/} + cil=${hcil#*:} + chan=${cil%%:*} + for tmpchan in $channelsearch ; do + if [ "$chan" -eq "$tmpchan" ] ; then + chan= + fi + done + if [ -n "$chan" ] ; then + channelsearch="$channelsearch $chan" + fi + done + if [ -z "$channelsearch" ] ; then + channelsearch="0" + fi +} + +# Returns the list of existing targets per host +idlist () +{ + local tmpid + local newid + local oldid + + oldlist=$(find /sys/class/scsi_device -name "${host}:${channel}:*" -printf "%f\n") + # Rescan LUN 0 to check if we found new targets + echo "${channel} - -" > "/sys/class/scsi_host/host${host}/scan" + newlist=$(find /sys/class/scsi_device -name "${host}:${channel}:*" -printf "%f\n") + for newid in $newlist ; do + oldid=$newid + for tmpid in $oldlist ; do + if [ "$newid" = "$tmpid" ] ; then + oldid= + break + fi + done + if [ -n "$oldid" ] ; then + if [ -d /sys/class/scsi_device/$oldid ] ; then + hcil=${oldid} + printf "\r${green}NEW: %s" "$norm" + testexist + if [ "$SCSISTR" ] ; then + incrfound "$hcil" + fi + fi + fi + done + idsearch=$(find /sys/bus/scsi/devices -name "target${host}:${channel}:*" -printf "%f\n" | cut -f 3 -d :) +} + +# Returns the list of existing LUNs from device $host $channel $id $lun +# and returns list to stdout +getluns() +{ + sgdevice + [ -z "$SGDEV" ] && return 1 + if [ ! -x /usr/bin/sg_luns ] ; then + echo 0 + return 1 + fi + LLUN=$(sg_luns /dev/$SGDEV 2>/dev/null | sed -n 's/ \(.*\)/\1/p') + # Added -z $LLUN condition because $? gets the RC from sed, not sg_luns + if [ $? -ne 0 ] || [ -z "$LLUN" ] ; then + echo 0 + return 1 + fi + for lun in $LLUN ; do + # Swap LUN number + l0=0x$lun + l1=$(( (l0 >> 48) & 0xffff )) + l2=$(( (l0 >> 32) & 0xffff )) + l3=$(( (l0 >> 16) & 0xffff )) + l4=$(( l0 & 0xffff )) + l0=$(( ( ( (l4 * 0xffff) + l3 ) * 0xffff + l2 ) * 0xffff + l1 )) + printf "%u\n" $l0 + done + return 0 +} + +# Wait for udev to settle (create device nodes etc.) +udevadm_settle() +{ + local tmo=60 + if [ -x /bin/udevadm ] ; then + print_and_scroll_back " Calling udevadm settle (can take a while) " + # Loop for up to 60 seconds if sd devices still are settling.. + # This allows us to continue if udev events are stuck on multipaths in recovery mode + while [ $tmo -gt 0 ] ; do + if ! /bin/udevadm settle --timeout=1 | egrep -q sd[a-z]+ ; then + break; + fi + let tmo=$tmo-1 + done + white_out + elif [ -x /sbin/udevsettle ] ; then + print_and_scroll_back " Calling udevsettle (can take a while) " + /sbin/udevsettle + white_out + else + sleep 0.02 + fi +} + +# Perform scan on a single lun $host $channel $id $lun +dolunscan() +{ + local remappedlun0= + local devpath + SCSISTR= + devnr="$host $channel $id $lun" + echo -e " Scanning for device $devnr ... " + printf "${yellow}OLD: %s" "$norm" + testexist + # Device exists: Test whether it's still online + # (testonline returns 2 if it's gone and 1 if it has changed) + devpath="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device" + if [ "$SCSISTR" ] ; then + testonline + RC=$? + # Well known lun transition case. Only for Direct-Access devs (type 0) + # If block directory exists && and PQUAL != 0, we unmapped lun0 and just have a well-known lun + # If block directory doesn't exist && PQUAL == 0, we mapped a real lun0 + if [ "$lun" -eq 0 ] && [ $IPTYPE -eq 0 ] ; then + if [ $RC = 2 ] ; then + if [ -e "$devpath" ] ; then + if [ -d "$devpath/block" ] ; then + remappedlun0=2 # Transition from real lun 0 to well-known + else + RC=0 # Set this so the system leaves the existing well known lun alone. This is a lun 0 with no block directory + fi + fi + elif [ $RC = 0 ] && [ $IPTYPE -eq 0 ] ; then + if [ -e "$devpath" ] ; then + if [ ! -d "$devpath/block" ] ; then + remappedlun0=1 # Transition from well-known to real lun 0 + fi + fi + fi + fi + fi + + # Special case: lun 0 just got added (for reportlunscan), + # so make sure we correctly treat it as new + if [ "$lun" = "0" ] && [ "$1" = "1" ] && [ -z "$remappedlun0" ] ; then + SCSISTR="" + printf "\r\e[A\e[A\e[A" + fi + + : f "$remove" s $SCSISTR + if [ "$remove" ] && [ "$SCSISTR" -o "$remappedlun0" = "1" ] ; then + if [ $RC != 0 ] || [ ! -z "$forceremove" ] || [ -n "$remappedlun0" ] ; then + if [ "$remappedlun0" != "1" ] ; then + echo -en "\r\e[A\e[A\e[A${red}REM: " + echo "$SCSISTR" | head -n1 + echo -e "${norm}\e[B\e[B" + fi + if [ -e "$devpath" ] ; then + # have to preemptively do this so we can figure out the mpath device + # Don't do this if we're deleting a well known lun to replace it + if [ "$remappedlun0" != "1" ] ; then + incrrmvd "$host:$channel:$id:$lun" + fi + echo 1 > "$devpath/delete" + sleep 0.02 + else + echo "scsi remove-single-device $devnr" > /proc/scsi/scsi + if [ $RC -eq 1 ] || [ "$lun" -eq 0 ] ; then + # Try readding, should fail if device is gone + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + fi + fi + if [ $RC = 0 ] || [ "$forcerescan" ] ; then + if [ -e "$devpath" ] ; then + echo 1 > "$devpath/rescan" + fi + fi + printf "\r\e[A\e[A\e[A${yellow}OLD: %s" "$norm" + testexist + if [ -z "$SCSISTR" ] && [ $RC != 1 ] && [ "$remappedlun0" != "1" ] ; then + printf "\r${red}DEL: %s\r\n\n" "$norm" + # In the event we're replacing with a well known node, we need to let it continue, to create the replacement node + [ "$remappedlun0" != "2" ] && return 2 + fi + fi + if [ -z "$SCSISTR" ] || [ -n "$remappedlun0" ] ; then + if [ "$remappedlun0" != "2" ] ; then + # Device does not exist, try to add + printf "\r${green}NEW: %s" "$norm" + fi + if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then + echo "$channel $id $lun" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null + else + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + testexist + if [ -z "$SCSISTR" ] ; then + # Device not present + printf "\r\e[A"; + # Optimization: if lun==0, stop here (only if in non-remove mode) + if [ "$lun" = 0 ] && [ -z "$remove" ] && [ "$optscan" = 1 ] ; then + return 1; + fi + else + if [ "$remappedlun0" != "2" ] ; then + incrfound "$host:$channel:$id:$lun" + fi + fi + fi + return 0; +} + +# Perform report lun scan on $host $channel $id using REPORT_LUNS +doreportlun() +{ + lun=0 + SCSISTR= + devnr="$host $channel $id $lun" + echo -en " Scanning for device $devnr ...\r" + lun0added= + #printf "${yellow}OLD: %s" "$norm" + # Phase one: If LUN0 does not exist, try to add + testexist -q + if [ -z "$SCSISTR" ] ; then + # Device does not exist, try to add + #printf "\r${green}NEW: %s" "$norm" + if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then + echo "$channel $id $lun" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null + udevadm_settle + else + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + testexist -q + if [ -n "$SCSISTR" ] ; then + lun0added=1 + #testonline + else + # Device not present + # return + # Find alternative LUN to send getluns to + for dev in /sys/class/scsi_device/${host}:${channel}:${id}:*; do + [ -d "$dev" ] || continue + lun=${dev##*:} + break + done + fi + fi + targetluns=$(getluns) + REPLUNSTAT=$? + lunremove= + #echo "getluns reports " $targetluns + olddev=$(find /sys/class/scsi_device/ -name "$host:$channel:$id:*" 2>/dev/null | sort -t: -k4 -n) + oldtargets="$targetluns" + # OK -- if we don't have a LUN to send a REPORT_LUNS to, we could + # fall back to wildcard scanning. Same thing if the device does not + # support REPORT_LUNS + # TODO: We might be better off to ALWAYS use wildcard scanning if + # it works + if [ "$REPLUNSTAT" = "1" ] ; then + if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then + echo "$channel $id -" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null + udevadm_settle + else + echo "scsi add-single-device $host $channel $id $SCAN_WILD_CARD" > /proc/scsi/scsi + fi + targetluns=$(find /sys/class/scsi_device/ -name "$host:$channel:$id:*" -printf "%f\n" | cut -d : -f 4) + let found+=$(echo "$targetluns" | wc -l) + let found-=$(echo "$olddev" | wc -l) + fi + [ -z "$targetluns" ] && targetluns="$oldtargets" + # Check existing luns + for dev in $olddev; do + [ -d "$dev" ] || continue + lun=${dev##*:} + newsearch= + inlist= + # OK, is existing $lun (still) in reported list + for tmplun in $targetluns; do + if [ "$tmplun" = "$lun" ] ; then + inlist=1 + dolunscan $lun0added + [ $? -eq 1 ] && break + else + newsearch="$newsearch $tmplun" + fi + done + # OK, we have now done a lunscan on $lun and + # $newsearch is the old $targetluns without $lun + if [ -z "$inlist" ]; then + # Stale lun + lunremove="$lunremove $lun" + fi + # $lun removed from $lunsearch + targetluns=${newsearch# } + done + # Add new ones and check stale ones + for lun in $targetluns $lunremove; do + dolunscan $lun0added + [ $? -eq 1 ] && break + done +} + +# Perform search (scan $host) +dosearch () +{ + if [ -z "$channelsearch" ] ; then + chanlist + fi + for channel in $channelsearch; do + if [ -z "$idsearch" ] ; then + if [ -z "$lunsearch" ] ; then + idlist + else + idsearch=$(find /sys/bus/scsi/devices -name "target${host}:${channel}:*" -printf "%f\n" | cut -f 3 -d :) + fi + fi + for id in $idsearch; do + if [ -z "$lunsearch" ] ; then + doreportlun + else + for lun in $lunsearch; do + dolunscan + [ $? -eq 1 ] && break + done + fi + done + done +} + +expandlist () +{ + list=$1 + result="" + first=${list%%,*} + rest=${list#*,} + while [ ! -z "$first" ] ; do + beg=${first%%-*}; + if [ "$beg" = "$first" ] ; then + result="$result $beg"; + else + end=${first#*-} + result="$result $(seq $beg $end)" + fi + [ "$rest" = "$first" ] && rest="" + first=${rest%%,*} + rest=${rest#*,} + done + echo "$result" +} + +searchexisting() +{ + local tmpch; + local tmpid + local match=0 + local targets= + + targets=$(find /sys/bus/scsi/devices -name "target${host}:*" -printf "%f\n" | cut -d : -f 2-3) + # Nothing came back on this host, so we should skip it + [ -z "$targets" ] && return + + local target=; + for target in $targets ; do + channel=${target%:*} + id=${target#*:} + if [ -n "$channelsearch" ] ; then + for tmpch in $channelsearch ; do + [ $tmpch -eq "$channel" ] && match=1 + done + else + match=1 + fi + + [ $match -eq 0 ] && continue + match=0 + + if [ "$filter_ids" -eq 1 ] ; then + for tmpid in $idsearch ; do + if [ "$tmpid" = "$id" ] ; then + match=1 + fi + done + else + match=1 + fi + + [ $match -eq 0 ] && continue + + if [ -z "$lunsearch" ] ; then + doreportlun + else + for lun in $lunsearch ; do + dolunscan + [ $? -eq 1 ] && break + done + fi + done +} + +# Go through all of the existing devices and figure out any that have been remapped +findremapped() +{ + local hctl=; + local devs= + local sddev= + local id_serial= + local id_serial_old= + local remapped= + mpaths="" + local tmpfile= + + tmpfile=$(mktemp /tmp/rescan-scsi-bus.XXXXXXXX 2> /dev/null) + if [ -z "$tmpfile" ] ; then + tmpfile="/tmp/rescan-scsi-bus.$$" + rm -f $tmpfile + fi + + # Get all of the ID_SERIAL attributes, after finding their sd node + devs=$(ls /sys/class/scsi_device/) + for hctl in $devs ; do + if [ -d "/sys/class/scsi_device/$hctl/device/block" ] ; then + sddev=$(ls "/sys/class/scsi_device/$hctl/device/block") + id_serial_old=$(udevadm info -q all -n "$sddev" | grep "ID_SERIAL=" | cut -d"=" -f2) + [ -z "$id_serial_old" ] && id_serial_old="none" + echo "$hctl $sddev $id_serial_old" >> $tmpfile + fi + done + + # Trigger udev to update the info + echo -n "Triggering udev to update device information... " + /bin/udevadm trigger + udevadm_settle 2>&1 /dev/null + echo "Done" + + # See what changed and reload the respective multipath device if applicable + while read -r hctl sddev id_serial_old ; do + remapped=0 + id_serial=$(udevadm info -q all -n "$sddev" | grep "ID_SERIAL=" | cut -d"=" -f2) + [ -z "$id_serial" ] && id_serial="none" + if [ "$id_serial_old" != "$id_serial" ] ; then + remapped=1 + fi + # If udev events updated the disks already, but the multipath device isn't update + # check for old devices to make sure we found remapped luns + if [ -n "$mp_enable" ] && [ $remapped -eq 0 ]; then + findmultipath "$sddev" $id_serial + if [ $? -eq 1 ] ; then + remapped=1 + fi + fi + + # if uuid is 1, it's unmapped, so we don't want to treat it as a remap + # if remapped flag is 0, just skip the rest of the logic + if [ "$id_serial" = "1" ] || [ $remapped -eq 0 ] ; then + continue + fi + printf "${yellow}REMAPPED: %s" "$norm" + host=$(echo "$hctl" | cut -d":" -f1) + channel=$(echo "$hctl" | cut -d":" -f2) + id=$(echo "$hctl" | cut -d":" -f3) + lun=$(echo "$hctl" | cut -d":" -f4) + procscsiscsi + echo "$SCSISTR" + incrchgd "$hctl" + done < $tmpfile + rm -f $tmpfile + + if [ -n "$mp_enable" ] && [ -n "$mpaths" ] ; then + echo "Updating multipath device mappings" + flushmpaths + $MULTIPATH | grep "create:" 2> /dev/null + fi +} + +incrfound() +{ + local hctl="$1" + if [ -n "$hctl" ] ; then + let found+=1 + FOUNDDEVS="$FOUNDDEVS\t[$hctl]\n" + else + return + fi +} + +incrchgd() +{ + local hctl="$1" + if [ -n "$hctl" ] ; then + if ! echo "$CHGDEVS" | grep -q "\[$hctl\]"; then + let updated+=1 + CHGDEVS="$CHGDEVS\t[$hctl]\n" + fi + else + return + fi + + if [ -n "$mp_enable" ] ; then + local sdev + + sdev=$(findsddev "$hctl") + if [ -n "$sdev" ] ; then + findmultipath "$sdev" + fi + fi +} + +incrrmvd() +{ + local hctl="$1" + if [ -n "$hctl" ] ; then + let rmvd+=1; + RMVDDEVS="$RMVDDEVS\t[$hctl]\n" + else + return + fi + + if [ -n "$mp_enable" ] ; then + local sdev + + sdev=$(findsddev "$hctl") + if [ -n "$sdev" ] ; then + findmultipath "$sdev" + fi + fi +} + +findsddev() +{ + local hctl="$1" + local sddev= + local blkpath + + blkpath="/sys/class/scsi_device/$hctl/device/block" + if [ -e "$blkpath" ] ; then + sddev=$(ls "$blkpath") + echo "$sddev" + fi +} + +addmpathtolist() +{ + local mp="$1" + local mp2= + + for mp2 in $mpaths ; do + # The multipath device is already in the list + if [ "$mp2" = "$mp" ] ; then + return + fi + done + mpaths="$mpaths $mp" +} + +findmultipath() +{ + local dev="$1" + local find_mismatch="$2" + local mp= + local mp2= + local found_dup=0 + local maj_min= + + # Need a sdev, and executable multipath and dmsetup command here + if [ -z "$dev" ] || [ ! -x "$DMSETUP" ] || [ ! -x "$MULTIPATH" ] ; then + return 1 + fi + + maj_min=$(cat "/sys/block/$dev/dev") + for mp in $($DMSETUP ls --target=multipath | cut -f 1) ; do + [ "$mp" = "No" ] && break; + if "$DMSETUP" status "$mp" | grep -q " $maj_min "; then + # With two arguments, look up current uuid from sysfs + # if it doesn't match what was passed, this multipath + # device is not updated, so this is a remapped LUN + if [ -n "$find_mismatch" ] ; then + mp2=$($MULTIPATH -l "$mp" | egrep -o "dm-[0-9]+") + mp2=$(cut -f2 -d- "/sys/block/$mp2/dm/uuid") + if [ "$find_mismatch" != "$mp2" ] ; then + addmpathtolist "$mp" + found_dup=1 + fi + continue + fi + # Normal mode: Find the first multipath with the sdev + # and add it to the list + addmpathtolist "$mp" + return + fi + done + + # Return 1 to signal that a duplicate was found to the calling function + if [ $found_dup -eq 1 ] ; then + return 1 + else + return 0 + fi +} + +reloadmpaths() +{ + local mpath + if [ ! -x "$MULTIPATH" ] ; then + echo "no -x multipath" + return + fi + + # Pass 1 as the argument to reload all mpaths + if [ "$1" = "1" ] ; then + echo "Reloading all multipath devices" + $MULTIPATH -r > /dev/null 2>&1 + return + fi + + # Reload the multipath devices + for mpath in $mpaths ; do + echo -n "Reloading multipath device $mpath... " + if $MULTIPATH -r "$mpath" > /dev/null 2>&1 ; then + echo "Done" + else + echo "Fail" + fi + done +} + +resizempaths() +{ + local mpath + + for mpath in $mpaths ; do + echo -n "Resizing multipath map $mpath ..." + multipathd -k"resize map $mpath" + let updated+=1 + done +} + +flushmpaths() +{ + local mpath + local remove="" + local i + local flush_retries=5 + + if [ -n "$1" ] ; then + for mpath in $($DMSETUP ls --target=multipath | cut -f 1) ; do + [ "$mpath" = "No" ] && break + num=$($DMSETUP status "$mpath" | awk 'BEGIN{RS=" ";active=0}/[0-9]+:[0-9]+/{dev=1}/A/{if (dev == 1) active++; dev=0} END{ print active }') + if [ "$num" -eq 0 ] ; then + remove="$remove $mpath" + fi + done + else + remove="$mpaths" + fi + + for mpath in $remove ; do + i=0 + echo -n "Flushing multipath device $mpath... " + while [ $i -lt $flush_retries ] ; do + $DMSETUP message "$mpath" 0 fail_if_no_path > /dev/null 2>&1 + if $MULTIPATH -f "$mpath" > /dev/null 2>&1 ; then + echo "Done ($i retries)" + break + elif [ $i -eq $flush_retries ] ; then + echo "Fail" + fi + sleep 0.02 + let i=$i+1 + done + done +} + + +# Find resized luns +findresized() +{ + local devs= + local size= + local new_size= + local sysfs_path= + local sddev= + local i= + local m= + local mpathsize= + declare -a mpathsizes + + if [ -z "$lunsearch" ] ; then + devs=$(ls /sys/class/scsi_device/) + else + for lun in $lunsearch ; do + devs="$devs $(cd /sys/class/scsi_device/ && ls -d *:${lun})" + done + fi + + for hctl in $devs ; do + sysfs_path="/sys/class/scsi_device/$hctl/device" + if [ -d "$sysfs_path/block" ] ; then + sddev=$(ls "$sysfs_path/block") + size=$(cat "$sysfs_path/block/$sddev/size") + + echo 1 > "$sysfs_path/rescan" + new_size=$(cat "$sysfs_path/block/$sddev/size") + + if [ "$size" != "$new_size" ] && [ "$size" != "0" ] && [ "$new_size" != "0" ] ; then + printf "${yellow}RESIZED: %s" "$norm" + host=$(echo "$hctl" | cut -d":" -f1) + channel=$(echo "$hctl" | cut -d":" -f2) + id=$(echo "$hctl" | cut -d":" -f3) + lun=$(echo "$hctl" | cut -d":" -f4) + + procscsiscsi + echo "$SCSISTR" + incrchgd "$hctl" + fi + fi + done + + if [ -n "$mp_enable" ] && [ -n "$mpaths" ] ; then + i=0 + for m in $mpaths ; do + mpathsizes[$i]="$($MULTIPATH -l "$m" | egrep -o [0-9]+.[0-9]+[KMGT])" + let i=$i+1 + done + resizempaths + i=0 + for m in $mpaths ; do + mpathsize="$($MULTIPATH -l "$m" | egrep -o [0-9\.]+[KMGT])" + echo "$m ${mpathsizes[$i]} => $mpathsize" + let i=$i+1 + done + fi +} + +FOUNDDEVS="" +CHGDEVS="" +RMVDDEVS="" + +# main +if [ "@$1" = @--help ] || [ "@$1" = @-h ] || [ "@$1" = "@-?" ] ; then + echo "Usage: rescan-scsi-bus.sh [options] [host [host ...]]" + echo "Options:" + echo " -a scan all targets, not just currently existing [default: disabled]" + echo " -c enables scanning of channels 0 1 [default: 0 / all detected ones]" + echo " -d enable debug [default: 0]" + echo " -f flush failed multipath devices [default: disabled]" + echo " -h help: print this usage message then exit" + echo " -i issue a FibreChannel LIP reset [default: disabled]" + echo " -I SECS issue a FibreChannel LIP reset and wait for SECS seconds [default: disabled]" + echo " -l activates scanning for LUNs 0--7 [default: 0]" + echo " -L NUM activates scanning for LUNs 0--NUM [default: 0]" + echo " -m update multipath devices [default: disabled]" + echo " -r enables removing of devices [default: disabled]" + echo " -s look for resized disks and reload associated multipath devices, if applicable" + echo " -u look for existing disks that have been remapped" + echo " -V print version date then exit" + echo " -w scan for target device IDs 0--15 [default: 0--7]" + echo "--alltargets: same as -a" + echo "--attachpq3: Tell kernel to attach sg to LUN 0 that reports PQ=3" + echo "--channels=LIST: Scan only channel(s) in LIST" + echo "--color: use coloured prefixes OLD/NEW/DEL" + echo "--flush: same as -f" + echo "--forceremove: Remove stale devices (DANGEROUS)" + echo "--forcerescan: Remove and readd existing devices (DANGEROUS)" + echo "--help: print this usage message then exit" + echo "--hosts=LIST: Scan only host(s) in LIST" + echo "--ids=LIST: Scan only target ID(s) in LIST" + echo "--ignore-rev: Ignore the revision change" + echo "--issue-lip: same as -i" + echo "--issue-lip-wait=SECS: same as -I" + echo "--largelun: Tell kernel to support LUNs > 7 even on SCSI2 devs" + echo "--luns=LIST: Scan only lun(s) in LIST" + echo "--multipath: same as -m" + echo "--nooptscan: don't stop looking for LUNs if 0 is not found" + echo "--remove: same as -r" + echo "--reportlun2: Tell kernel to try REPORT_LUN even on SCSI2 devices" + echo "--resize: same as -s" + echo "--sparselun: Tell kernel to support sparse LUN numbering" + echo "--sync/nosync: Issue a sync / no sync [default: sync if remove]" + echo "--update: same as -u" + echo "--version: same as -V" + echo "--wide: same as -w" + echo "" + echo "Host numbers may thus be specified either directly on cmd line (deprecated)" + echo "or with the --hosts=LIST parameter (recommended)." + echo "LIST: A[-B][,C[-D]]... is a comma separated list of single values and ranges" + echo "(No spaces allowed.)" + exit 0 +fi + +if [ "@$1" = @--version ] || [ "@$1" = @-V ] ; then + echo ${VERSION} + exit 0 +fi + +if [ ! -d /sys/class/scsi_host/ ] && [ ! -d /proc/scsi/ ] ; then + echo "Error: SCSI subsystem not active" + exit 1 +fi + +# Make sure sg is there +modprobe sg >/dev/null 2>&1 + +if [ -x /usr/bin/sg_inq ] ; then + sg_version=$(sg_inq -V 2>&1 | cut -d " " -f 3) + if [ -n "$sg_version" ] ; then + sg_ver_maj=${sg_version:0:1} + sg_version=${sg_version##?.} + let sg_version+=$((100 * sg_ver_maj)) + fi + sg_version=${sg_version##0.} + #echo "\"$sg_version\"" + if [ -z "$sg_version" ] || [ "$sg_version" -lt 70 ] ; then + sg_len_arg="-36" + else + sg_len_arg="--len=36" + fi +else + echo "WARN: /usr/bin/sg_inq not present -- please install sg3_utils" + echo " or rescan-scsi-bus.sh might not fully work." +fi + +# defaults +unsetcolor +debug=0 +lunsearch= +opt_idsearch=$(seq 0 7) +filter_ids=0 +opt_channelsearch= +remove= +updated=0 +update=0 +resize=0 +forceremove= +optscan=1 +sync=1 +existing_targets=1 +mp_enable= +lipreset=-1 +declare -i scan_flags=0 +ignore_rev=0 + +# Scan options +opt="$1" +while [ ! -z "$opt" ] && [ -z "${opt##-*}" ] ; do + opt=${opt#-} + case "$opt" in + a) existing_targets=;; #Scan ALL targets when specified + c) opt_channelsearch="0 1" ;; + d) debug=1 ;; + f) flush=1 ;; + i) lipreset=0 ;; + I) shift; lipreset=$opt ;; + l) lunsearch=$(seq 0 7) ;; + L) lunsearch=$(seq 0 "$2"); shift ;; + m) mp_enable=1 ;; + r) remove=1 ;; + s) resize=1; mp_enable=1 ;; + u) update=1 ;; + w) opt_idsearch=$(seq 0 15) ;; + -alltargets) existing_targets=;; + -attachpq3) scan_flags=$((scan_flags|0x1000000)) ;; + -channels=*) arg=${opt#-channels=};opt_channelsearch=$(expandlist "$arg") ;; + -color) setcolor ;; + -flush) flush=1 ;; + -forceremove) remove=1; forceremove=1 ;; + -forcerescan) remove=1; forcerescan=1 ;; + -hosts=*) arg=${opt#-hosts=}; hosts=$(expandlist "$arg") ;; + -ids=*) arg=${opt#-ids=}; opt_idsearch=$(expandlist "$arg") ; filter_ids=1;; + -ignore-rev) ignore_rev=1;; + -issue-lip) lipreset=0 ;; + -issue-lip-wait) lipreset=${opt#-issue-lip-wait=};; + -largelun) scan_flags=$((scan_flags|0x200)) ;; + -luns=*) arg=${opt#-luns=}; lunsearch=$(expandlist "$arg") ;; + -multipath) mp_enable=1 ;; + -nooptscan) optscan=0 ;; + -nosync) sync=0 ;; + -remove) remove=1 ;; + -reportlun2) scan_flags=$((scan_flags|0x20000)) ;; + -resize) resize=1;; + -sparselun) scan_flags=$((scan_flags|0x40)) ;; + -sync) sync=2 ;; + -update) update=1;; + -wide) opt_idsearch=$(seq 0 15) ;; + *) echo "Unknown option -$opt !" ;; + esac + shift + opt="$1" +done + +if [ -z "$hosts" ] ; then + if [ -d /sys/class/scsi_host ] ; then + findhosts_26 + else + findhosts + fi +fi + +if [ -d /sys/class/scsi_host ] && [ ! -w /sys/class/scsi_host ]; then + echo "You need to run scsi-rescan-bus.sh as root" + exit 2 +fi +[ "$sync" = 1 ] && [ "$remove" = 1 ] && sync=2 +if [ "$sync" = 2 ] ; then + echo "Syncing file systems" + sync +fi +if [ -w /sys/module/scsi_mod/parameters/default_dev_flags ] && [ $scan_flags != 0 ] ; then + OLD_SCANFLAGS=$(cat /sys/module/scsi_mod/parameters/default_dev_flags) + NEW_SCANFLAGS=$((OLD_SCANFLAGS|scan_flags)) + if [ "$OLD_SCANFLAGS" != "$NEW_SCANFLAGS" ] ; then + echo -n "Temporarily setting kernel scanning flags from " + printf "0x%08x to 0x%08x\n" "$OLD_SCANFLAGS" "$NEW_SCANFLAGS" + echo $NEW_SCANFLAGS > /sys/module/scsi_mod/parameters/default_dev_flags + else + unset OLD_SCANFLAGS + fi +fi +DMSETUP=$(which dmsetup) +[ -z "$DMSETUP" ] && flush= && mp_enable= +MULTIPATH=$(which multipath) +[ -z "$MULTIPATH" ] && flush= && mp_enable= + +echo -n "Scanning SCSI subsystem for new devices" +[ -z "$flush" ] || echo -n ", flush failed multipath devices," +[ -z "$remove" ] || echo -n " and remove devices that have disappeared" +echo +declare -i found=0 +declare -i updated=0 +declare -i rmvd=0 + +if [ -n "$flush" ] ; then + if [ -x "$MULTIPATH" ] ; then + flushmpaths 1 + fi +fi + +# Update existing mappings +if [ $update -eq 1 ] ; then + echo "Searching for remapped LUNs" + findremapped + # If you've changed the mapping, there's a chance it's a different size + mpaths="" + findresized +# Search for resized LUNs +elif [ $resize -eq 1 ] ; then + echo "Searching for resized LUNs" + findresized +# Normal rescan mode +else + for host in $hosts; do + echo -n "Scanning host $host " + if [ -e "/sys/class/fc_host/host$host" ] ; then + # It's pointless to do a target scan on FC + issue_lip=/sys/class/fc_host/host$host/issue_lip + if [ -e "$issue_lip" ] && [ "$lipreset" -ge 0 ] ; then + echo 1 > "$issue_lip" 2> /dev/null; + udevadm_settle + [ "$lipreset" -gt 0 ] && sleep "$lipreset" + fi + channelsearch= + idsearch= + else + channelsearch=$opt_channelsearch + idsearch=$opt_idsearch + fi + [ -n "$channelsearch" ] && echo -n "channels $channelsearch " + echo -n "for " + if [ -n "$idsearch" ] ; then + echo -n " SCSI target IDs $idsearch" + else + echo -n " all SCSI target IDs" + fi + if [ -n "$lunsearch" ] ; then + echo ", LUNs $lunsearch" + else + echo ", all LUNs" + fi + + if [ -n "$existing_targets" ] ; then + searchexisting + else + dosearch + fi + done + if [ -n "$OLD_SCANFLAGS" ] ; then + echo "$OLD_SCANFLAGS" > /sys/module/scsi_mod/parameters/default_dev_flags + fi +fi + +let rmvd_found=$rmvd+$found +if [ -n "$mp_enable" ] && [ $rmvd_found -gt 0 ] ; then + echo "Attempting to update multipath devices..." + if [ $rmvd -gt 0 ] ; then + udevadm_settle + echo "Removing multipath mappings for removed devices if all paths are now failed... " + flushmpaths 1 + fi + if [ $found -gt 0 ] ; then + /bin/udevadm trigger --sysname-match=sd* + udevadm_settle + if [ -x "$MULTIPATH" ] ; then + echo "Trying to discover new multipath mappings for newly discovered devices... " + $MULTIPATH | grep "create:" 2> /dev/null + fi + fi +fi + +echo "$found new or changed device(s) found. " +if [ ! -z "$FOUNDDEVS" ] ; then + printf "%s" "$FOUNDDEVS" +fi +echo "$updated remapped or resized device(s) found." +if [ ! -z "$CHGDEVS" ] ; then + printf "%s" "$CHGDEVS" +fi +echo "$rmvd device(s) removed. " +if [ ! -z "$RMVDDEVS" ] ; then + printf "%s" "$RMVDDEVS" +fi + +# Local Variables: +# sh-basic-offset: 2 +# End: + diff --git a/Moodle/Tools/srttovtt.sh b/Moodle/Tools/srttovtt.sh new file mode 100644 index 0000000..50df8a1 --- /dev/null +++ b/Moodle/Tools/srttovtt.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Kiểm tra xem ffmpeg đã được cài đặt chưa +if ! command -v ffmpeg &> /dev/null; then + echo "ffmpeg chưa được cài đặt. Vui lòng cài đặt ffmpeg trước khi chạy script." + exit 1 +fi + +# Tìm tất cả các tệp .srt và chuyển đổi chúng thành .vtt nếu chưa tồn tại +find . -type f -name "*.srt" | while read -r srt_file; do + # Xác định đường dẫn và tên tệp đích + vtt_file="${srt_file%.srt}.vtt" + + # Nếu tệp .vtt đã tồn tại, bỏ qua + if [ -f "$vtt_file" ]; then + echo "Đã tồn tại: $vtt_file → bỏ qua." + continue + fi + + # Chuyển đổi tệp .srt sang .vtt bằng ffmpeg + ffmpeg -i "$srt_file" "$vtt_file" + + # Kiểm tra nếu chuyển đổi thành công + if [ $? -eq 0 ]; then + echo "Chuyển đổi thành công: $srt_file -> $vtt_file" + else + echo "Lỗi khi chuyển đổi: $srt_file" + fi +done diff --git a/Moodle/Tools/whisperenv.sh b/Moodle/Tools/whisperenv.sh new file mode 100644 index 0000000..a057987 --- /dev/null +++ b/Moodle/Tools/whisperenv.sh @@ -0,0 +1 @@ +source /whisper-env/bin/activate