commit c65fd63fd95eb954546d50855e1df8f63775e0af Author: Cametendo Date: Mon May 4 08:55:27 2026 +0200 Inital commit diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 0000000..61a37d0 --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,15 @@ +pkgbase = cflash + pkgdesc = A small and lightweight wrapper for dd that strips away the complexity of CLI flashing. + pkgver = 1.0.1 + pkgrel = 1 + url = https://github.com/cametendo/cflash-git + arch = any + license = MIT + makedepends = java-environment>=21 + makedepends = maven + makedepends = git + depends = java-runtime>=21 + source = cflash::git+https://github.com/cametendo/cflash-git.git + sha256sums = SKIP + +pkgname = cflash diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85078d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +target/ +*.class +.mtj.tmp/ +*.jar +*.war +*.ear +hs_err_pid* + +# VS Code specific +.classpath +.project +.settings/ +.vscode/ +.claude/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bef221f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,19 @@ +## Contributing + +Contributions from the community are welcome! To contribute: + +1. Fork the repository. +2. Create a new branch for your feature or bug fix. +3. Make your changes with proper testing. +4. Submit a pull request detailing your modifications. + +### Developer Documentation + +Developer documentation is generated using JavaDoc. To generate and view the documentation: + +```bash +chmod +x generate_javadoc.sh +./generate_javadoc.sh +``` + +This will create a `docs/` folder with HTML documentation you can view in your browser. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c0186e5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cametendo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..8aadc5b --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,34 @@ +# Maintainer: Cametendo cameronmathis08@gmail.com +pkgname=cflash +pkgver=1.0.1 +pkgrel=1 +pkgdesc="A small and lightweight wrapper for dd that strips away the complexity of CLI flashing." +arch=('any') +url="https://github.com/cametendo/cflash-git" +license=('MIT') +depends=('java-runtime>=21') +makedepends=('java-environment>=21' 'maven' 'git') +source=("cflash::git+https://github.com/cametendo/cflash-git.git") +sha256sums=('SKIP') + +build() { + cd "$pkgname" + # using 'package' because pom.xml triggers the assembly plugin during this phase + mvn clean package +} + +package() { + cd "$srcdir/cflash" + + install -Dm644 "target/${pkgname}-${pkgver}-jar-with-dependencies.jar" \ + "$pkgdir/usr/share/java/${pkgname}/${pkgname}.jar" + + # Create the executable + install -d "$pkgdir/usr/bin" + cat < "$pkgdir/usr/bin/cflash" +#!/bin/sh +exec /usr/bin/java -jar /usr/share/java/cflash/cflash.jar "\$@" +EOF + # makes the program executable + chmod +x "$pkgdir/usr/bin/cflash" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..890d00c --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# cflash +A small and lightweight wrapper for `dd` that strips away the complexity of CLI flashing. + +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Java Version](https://img.shields.io/badge/Java-21%2B-orange.svg)](https://www.java.com/en/) +[![Platform](https://img.shields.io/badge/Platform-Linux-brightgreen.svg)](https://www.linux.org/) +[![Version](https://img.shields.io/badge/version-1.0.0-blue)](https://github.com/Cametendo/cflash-git/releases/tag/cflash-1.0.0) + +# About +Small and lightweight wrapper written in Java for `dd` designed to simplify CLI flashing while protecting your hardware from accidental command-line errors. cflash replaces the syntax with a clear, safe interface, providing a reliable workflow for both newcomers and power users. + +# Requirements +- `Java`: 21 (Download [here](https://www.oracle.com/java/technologies/downloads/#java21)) +- `util-linux`: 2.41 +- `coreutils`: 9.10 +- `maven`: 3.9.15 +- Operating System: Linux +### Building and Running Locally + +1. Clone the repository: + + ```bash + git clone https://github.com/cametendo/cflash-git.git + cd cflash-git + ``` + +2. Compile the code: + + ```bash + mvn clean package + ``` + +3. Run cflash: + + ```bash + java -jar target/cflash-.jar (optionally add arguments here, like with dd) + ``` + +4. Update cflash: + + ```bash + git pull + mvn clean package + ``` + +### System-wide Installation + +To install cflash globally so that it can be run from any terminal: + +1. Clone the repository (if not done already): + + ```bash + git clone https://github.com/cametendo/cflash-git.git + cd cflash-git + ``` + +2. Make the build and install scripts executable: + + ```bash + chmod +x build.sh install.sh + ``` + +3. Build the project using the provided build script (requires root privileges): + + ```bash + sudo ./build.sh + ``` + +4. Install globally (requires root privileges): + + ```bash + sudo ./install.sh + ``` + +5. Run cflash from anywhere: + + ```bash + cflash + ``` + +6. Update cflash (reguires root privileges): + + ```bash + sudo cflash --update + ``` + +**Notes:** + +* The `build.sh` script compiles all Java source files and creates an executable `cflash.jar`. +* The `install.sh` script copies `cflash.jar` to `/usr/local/lib/cflash` and installs a wrapper script in `/usr/local/bin` for easy execution. +* If you use Arch Linux: + * You can directly install it with `yay` or `paru` via `yay -S cflash` / `paru -S cflash` or + * You can clone the repository from the [AUR](https://aur.archlinux.org/packages/cflash) and manually build it: + ```bash + git clone https://aur.archlinux.org/cflash.git + cd cflash + makepkg -si + ``` + * You can update cflash using `yay` / `paru` via `yay -S cflash` / `paru -S cflash` or by rebuilding the package: + ```bash + git clone https://aur.archlinux.org/cflash.git + cd cflash + makepkg -si + ``` + +# Usage +- Using the command `cflash` in the terminal, will start the flashing process. You will be asked several question before the flashing begins: + 1. You will see a list of every drive your system see's (excluding system drives) and the be asked to enter the device you want to flash the image onto. (F.e. **/dev/sda**) + 2. You will be prompted to enter the path of the iso / image you want to flash + 3. You will be prompted to choose a byte size (default: 4M) + 4. You will be prompted to enter your oflag (default: direct) + 5. You will be asked if you are absolutely sure that you want to continue (flashing will wipe all data) +- Alternative: using `cflash [device] [iso-path] [block-size] [oflag]` will skip the questions and instantly ask you, if you're absolutely sure you want to continue. +- Once confirmed, the flash will start and a small progress bar will appear showing the flashing progress. +- After completion, the program will detect the OS from the iso and wish you a great time with your new OS. (Example: "Done! Have fun with your new Linux installation!) +- **IMPORTANT**: Since dd needs sudo rights, ensure you have root priviliges. + +# Supported OS +- Linux + +# Installation +1. Clone the repository onto your device and cd into it. +2. Run the `build.sh` file to build the program. +3. Run the `ìnstall.sh`to install the program. +4. Open a terminal and use the program with `cflash`, optionally add all the arguments you need. + +# License and Credits +**Author**: [Cametendo](https://www.github.com/Cametendo) +**License**: MIT diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..cb741ed --- /dev/null +++ b/build.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# --- Build script for cflash --- +# Compiles sources and bundles jline via Maven into a single fat JAR. +# If Maven is missing, prompts the user to install it using the detected package manager. + +set -e + +JAR_FILE="cflash.jar" + +# --- Detect package manager --- +detect_package_manager() { + if command -v pacman >/dev/null 2>&1; then + echo "pacman" + elif command -v apt >/dev/null 2>&1; then + echo "apt" + elif command -v dnf >/dev/null 2>&1; then + echo "dnf" + elif command -v yum >/dev/null 2>&1; then + echo "yum" + elif command -v zypper >/dev/null 2>&1; then + echo "zypper" + elif command -v brew >/dev/null 2>&1; then + echo "brew" + elif command -v apk >/dev/null 2>&1; then + echo "apk" + elif command -v emerge >/dev/null 2>&1; then + echo "emerge" + else + echo "" + fi +} + +# --- Generate Maven install command --- +maven_install_command() { + local pm="$1" + case "$pm" in + pacman) echo "pacman -Sy --noconfirm maven" ;; + apt) echo "apt update && apt install -y maven" ;; + dnf) echo "dnf install -y maven" ;; + yum) echo "yum install -y maven" ;; + zypper) echo "zypper install -y maven" ;; + brew) echo "brew install maven" ;; + apk) echo "apk add maven" ;; + emerge) echo "emerge dev-java/maven-bin" ;; + *) echo "" ;; + esac +} + +# --- Check for mvn --- +if ! command -v mvn >/dev/null 2>&1; then + echo "Maven (mvn) not found." + PM=$(detect_package_manager) + + if [[ -z "$PM" ]]; then + echo "Please install Maven manually and rerun this script." + exit 1 + fi + + CMD=$(maven_install_command "$PM") + if [[ $EUID -ne 0 && "$PM" != "brew" ]]; then + echo "Please run this script as root to install Maven, or install it manually." + exit 1 + fi + + read -rp "Do you want to run the following command to install Maven? [$CMD] (y/n): " ANSWER + case "$ANSWER" in + y|Y) + echo "Installing Maven..." + eval "$CMD" + echo "Maven installed successfully." + ;; + *) + echo "Maven installation cancelled. Please install manually and rerun." + exit 1 + ;; + esac +fi + +# --- Build process --- +echo "Building cflash with Maven..." +mvn package -q + +echo "Copying fat JAR to ${JAR_FILE}..." +cp target/cflash-*-jar-with-dependencies.jar "$JAR_FILE" + +echo "Build completed successfully: ${JAR_FILE}" diff --git a/cflash.sh b/cflash.sh new file mode 100755 index 0000000..8f00d19 --- /dev/null +++ b/cflash.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# cflash launcher with self-update via git + rebuild (requires sudo for update) + +CFLASH_DIR="/usr/local/lib/cflash" +CFLASH_JAR="$CFLASH_DIR/cflash.jar" +GIT_REPO="https://gitea.cametendo.org/cametendo/cflash.git" + +update_cflash() { + # Check for sudo/root + if [[ $EUID -ne 0 ]]; then + echo "cflash --update requires root privileges. Please run with sudo." + exit 1 + fi + + echo "Updating cflash from repository..." + + TMP_DIR=$(mktemp -d) + echo "Cloning repository into $TMP_DIR..." + + if ! git clone --depth 1 "$GIT_REPO" "$TMP_DIR"; then + echo "Failed to clone repository." + rm -rf "$TMP_DIR" + exit 1 + fi + + pushd "$TMP_DIR" >/dev/null + + # Logic to handle if the repo contents are nested inside a 'cflash' folder + if [[ ! -f "./build.sh" && -d "cflash" ]]; then + echo "Detected nested directory, moving into cflash/..." + cd cflash + fi + + # Ensure build.sh is executable (git usually preserves this, but just in case) + if [[ -f "./build.sh" ]]; then + chmod +x ./build.sh + echo "Building cflash..." + if ! ./build.sh; then + echo "Build failed." + popd >/dev/null + rm -rf "$TMP_DIR" + exit 1 + fi + else + echo "Error: build.sh not found in the repository root or cflash/ directory." + popd >/dev/null + rm -rf "$TMP_DIR" + exit 1 + fi + + echo "Installing new version..." + if [[ -f "./install.sh" ]]; then + chmod +x ./install.sh + if ! ./install.sh; then + echo "Install failed." + popd >/dev/null + rm -rf "$TMP_DIR" + exit 1 + fi + else + echo "Error: install.sh not found." + popd >/dev/null + rm -rf "$TMP_DIR" + exit 1 + fi + + popd >/dev/null + rm -rf "$TMP_DIR" + echo "Update completed successfully!" + exit 0 +} + +# Handle --update argument +if [[ "$1" == "--update" ]]; then + update_cflash +fi + +# Run cflash normally +if [[ -f "$CFLASH_JAR" ]]; then + java --enable-native-access=ALL-UNNAMED -jar "$CFLASH_JAR" "$@" +else + echo "Error: $CFLASH_JAR not found. Please run 'sudo cflash --update' to install." + exit 1 +fi \ No newline at end of file diff --git a/generate_javadoc.sh b/generate_javadoc.sh new file mode 100755 index 0000000..955e55d --- /dev/null +++ b/generate_javadoc.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# --- Generate JavaDoc for cflash --- +# This script generates developer documentation using javadoc. +# If JDK is missing, it prompts the user to install it using the detected package manager. + +set -e + +SRC_DIR="src/main/java" +DOC_DIR="docs" + +# --- Detect package manager --- +detect_package_manager() { + if command -v pacman >/dev/null 2>&1; then + echo "pacman" + elif command -v apt >/dev/null 2>&1; then + echo "apt" + elif command -v dnf >/dev/null 2>&1; then + echo "dnf" + elif command -v yum >/dev/null 2>&1; then + echo "yum" + elif command -v zypper >/dev/null 2>&1; then + echo "zypper" + elif command -v brew >/dev/null 2>&1; then + echo "brew" + elif command -v apk >/dev/null 2>&1; then + echo "apk" + elif command -v emerge >/dev/null 2>&1; then + echo "emerge" + else + echo "" + fi +} + +# --- Generate JDK install command --- +jdk_install_command() { + local pm="$1" + case "$pm" in + pacman) echo "pacman -Sy --noconfirm jdk-openjdk" ;; + apt) echo "apt update && apt install -y openjdk-21-jdk || apt install -y default-jdk" ;; + dnf) echo "dnf install -y java-21-openjdk-devel || dnf install -y java-latest-openjdk-devel" ;; + yum) echo "yum install -y java-21-openjdk-devel || yum install -y java-latest-openjdk-devel" ;; + zypper) echo "zypper install -y java-21-openjdk-devel || zypper install -y java-latest-openjdk-devel" ;; + brew) echo "brew install openjdk" ;; + apk) echo "apk add openjdk21" ;; + emerge) echo "emerge dev-java/openjdk-bin" ;; + *) echo "" ;; + esac +} + +# --- Check for javadoc --- +if ! command -v javadoc >/dev/null 2>&1; then + echo "Java Development Kit (JDK) with javadoc not found." + PM=$(detect_package_manager) + + if [[ -z "$PM" ]]; then + echo "Please install the latest JDK manually and rerun this script." + exit 1 + fi + + CMD=$(jdk_install_command "$PM") + if [[ $EUID -ne 0 && "$PM" != "brew" ]]; then + echo "Please rerun this script with sudo if you want automatic JDK installation." + exit 1 + fi + + # Prompt user + read -rp "Do you want to run the following command to install the JDK? [$CMD] (y/n): " ANSWER + case "$ANSWER" in + y|Y) + echo "Installing JDK..." + eval "$CMD" + echo "JDK installed successfully." + ;; + *) + echo "JDK installation cancelled. Please install manually and rerun." + exit 1 + ;; + esac +fi + +# --- Check if source directory exists --- +if [[ ! -d "$SRC_DIR" ]]; then + echo -e "\033[31mSource directory '$SRC_DIR' not found!" + echo "Please ensure you're running this script from the project root directory." + exit 1 +fi + +# --- Generate JavaDoc --- +echo "Generating JavaDoc for cflash..." +rm -rf "$DOC_DIR" +mkdir -p "$DOC_DIR" + +# Check if Maven is available +if command -v mvn >/dev/null 2>&1; then + echo "Using Maven to generate JavaDoc with dependencies..." + if mvn javadoc:javadoc -Dquiet=true > /dev/null 2>&1; then + # Copy generated docs from Maven location to our docs directory + if [[ -d "target/reports/apidocs" ]]; then + cp -r target/reports/apidocs/* "$DOC_DIR/" + echo -e "\033[32mJavaDoc generated successfully using Maven!" + echo -e "\033[0m→ Open file://$PWD/$DOC_DIR/index.html to view it." + else + echo -e "\033[31mMaven generated docs but couldn't find them in target/reports/apidocs" + exit 1 + fi + else + echo -e "\033[31mMaven JavaDoc generation failed." + exit 1 + fi +else + echo "Maven not found. Attempting to generate JavaDoc without dependencies..." + # Try to download JLine dependency manually + JLINE_JAR="$HOME/.m2/repository/org/jline/jline/3.25.1/jline-3.25.1.jar" + if [[ -f "$JLINE_JAR" ]]; then + echo "Found JLine dependency in Maven local repository..." + CLASSPATH="$SRC_DIR:$JLINE_JAR" + else + echo "JLine dependency not found. JavaDoc may have missing links." + CLASSPATH="$SRC_DIR" + fi + + if javadoc -quiet -d "$DOC_DIR" \ + -sourcepath "$SRC_DIR" \ + -classpath "$CLASSPATH" \ + -subpackages org.cametendo \ + -private \ + -author \ + -version \ + -doctitle "cflash - Disk Image Flashing Utility" \ + -windowtitle "cflash Documentation" \ + -bottom "Copyright © 2026 Cametendo. All rights reserved." > /dev/null 2>&1; then + echo -e "\033[32mJavaDoc generated successfully!" + echo -e "\033[0m→ Open file://$PWD/$DOC_DIR/index.html to view it." + else + echo -e "\033[31mJavaDoc generation failed. Install Maven for better dependency handling." + echo "Run: apt install maven (Ubuntu/Debian) or pacman -S maven (Arch)" + exit 1 + fi +fi diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9282cd6 --- /dev/null +++ b/install.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Install script for system-wide usage + +# Ensure running as root +if [[ $EUID -ne 0 ]]; then + echo "Please run as root to install globally." + exit 1 +fi + +# Create installation directories +mkdir -p /usr/local/lib/cflash +cp cflash.jar /usr/local/lib/cflash/ +cp cflash.sh /usr/local/bin/cflash +chmod +x /usr/local/bin/cflash + +echo "cflash installed successfully! You can now run 'cflash' from anywhere." diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b62efe4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.cametendo + cflash + 1.0.1 + + + 21 + 21 + UTF-8 + + + + + org.jline + jline + 3.25.1 + + + + + + + maven-assembly-plugin + 3.6.0 + + + + org.cametendo.Main + + + ALL-UNNAMED + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/cametendo/BlockSize.java b/src/main/java/org/cametendo/BlockSize.java new file mode 100644 index 0000000..1dca793 --- /dev/null +++ b/src/main/java/org/cametendo/BlockSize.java @@ -0,0 +1,62 @@ +package org.cametendo; +import java.util.Scanner; + +/** + * Utility class for managing block size configuration for disk flashing operations. + * + *

This class provides methods to both interactively prompt users for block size selection + * and to map command-line inputs to valid block size values. The default block size is 4M.

+ * + * @author Cametendo + * @version 1.0 + */ +public class BlockSize { + + /** + * Default block size string value. + * Set to "4M" as the default block size for flashing operations. + */ + public static String blockSizeString = "4M"; + + /** + * Maps user input to a valid block size value. + * + *

Supports both numeric inputs (1-6) and string inputs (512K,1M,2M,4M,8M,16M). + * If the input is not recognized, returns the default block size.

+ * + * @param input User input string, either numeric (1-6) or block size string + * @return Valid block size string (512K, 1M, 2M, 4M, 8M, 16M, or default) + */ + public static String mapBlockSize(String input) { + return switch (input) { + case "1", "512K" -> "512K"; + case "2", "1M" -> "1M"; + case "3", "2M" -> "2M"; + case "4", "4M" -> "4M"; + case "5", "8M" -> "8M"; + case "6", "16M" -> "16M"; + default -> blockSizeString; + }; + } + + /** + * Interactively prompts the user to select a block size. + * + *

Displays a menu of available block sizes and maps the user's input + * to a valid block size value using {@link #mapBlockSize(String)}.

+ * + * @param UserInput Scanner object for reading user input + * @return The selected block size string + */ + static String blockSize(Scanner UserInput) { + System.out.println("Choose a block size (Default: 4M)"); + System.out.println("512KB (1), 1M (2), 2M (3), 4M (4), 8M (5), 16M (6)"); + + String input = UserInput.nextLine(); + + blockSizeString = mapBlockSize(input); + + System.out.println("Using blocksize of: " + blockSizeString); + return blockSizeString; + } +} \ No newline at end of file diff --git a/src/main/java/org/cametendo/Dd.java b/src/main/java/org/cametendo/Dd.java new file mode 100644 index 0000000..a631b93 --- /dev/null +++ b/src/main/java/org/cametendo/Dd.java @@ -0,0 +1,47 @@ +package org.cametendo; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Executes the dd command for disk flashing operations. + * + *

This class handles the actual execution of the dd command with the configured + * parameters (input file, output device, block size, and output flags). It provides + * real-time output streaming and calls the OSDetector for completion messages.

+ * + * @author Cametendo + * @version 1.0 + */ +public class Dd { + + /** + * Executes the dd command with the configured parameters. + * + *

Runs the dd command using sudo with the specified image file, target device, + * block size, and output flags. Streams the command output in real-time to show + * progress. Upon completion, calls {@link OSDetector#wishWell(String)} to display + * a completion message based on the image file name.

+ */ + public static void dd() { + try { + ProcessBuilder pb = new ProcessBuilder("sudo", "dd", "if=" + FilePathAdd.ImagePath, "of=" + StorageDeviceLister.fullPath, "bs=" + BlockSize.blockSizeString, "status=progress", "oflag=" + OflagHandler.oflagHandleString); + pb.redirectErrorStream(true); + Process process = pb.start(); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()) + ); + + int character; + while ((character = reader.read()) != -1) { + char c = (char) character; + System.out.print(c); + System.out.flush(); + } + OSDetector.wishWell(FilePathAdd.ImagePath); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/cametendo/FilePathAdd.java b/src/main/java/org/cametendo/FilePathAdd.java new file mode 100644 index 0000000..ad7a5b8 --- /dev/null +++ b/src/main/java/org/cametendo/FilePathAdd.java @@ -0,0 +1,108 @@ +package org.cametendo; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.impl.completer.FileNameCompleter; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +/** + * Handles file path input and validation for disk image files. + * *

This class provides functionality to interactively prompt users for image file paths + * with tab completion support via JLine. It handles Unix-style tilde (~) expansion + * to the user's home directory and validates that paths point to existing regular files.

+ * * @author Cametendo + * @version 1.1 + */ +public class FilePathAdd { + + /** + * Stores the validated absolute path to the selected image file. + */ + public static String ImagePath = ""; + + /** + * Constant representing the current user's home directory path. + */ + public static final String Home = System.getProperty("user.home"); + + /** + * Interactively prompts the user to select an image file path. + * *

Uses JLine for enhanced terminal interaction with {@link FileNameCompleter}. + * The method expands the tilde (~) character if present at the start of the string + * and validates that the resulting path is a regular file before returning.

+ * * @return The validated absolute path to the selected image file + * @throws IOException If there are I/O errors during terminal setup or file resolution + */ + protected static String filePath() throws IOException { + fileQuestion(); + + Terminal terminal = TerminalBuilder.terminal(); + LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(new FileNameCompleter()).build(); + + while (true) { + String input = reader.readLine("Path: ").trim(); + + if (input.isBlank()) { + System.out.println("Oops... You didn't specify a file!"); + continue; + } + + // Expand tilde to the full home directory path + if (input.startsWith("~")) { + input = Home + input.substring(1); + } + + Path path = Path.of(input); + + if (!Files.exists(path) || !Files.isRegularFile(path)) { + System.out.println("Invalid file! Please ensure the path points to an ISO / image file."); + continue; + } + + // Convert to a real, absolute path and return + ImagePath = path.toRealPath().toString(); + System.out.println("Using File: " + ImagePath); + return ImagePath; + } + } + + /** + * Validates and returns the full path to an image file from a provided string. + * *

This is primarily used for validating command-line arguments. It supports + * tilde expansion and verifies file existence and type.

+ * * @param inputPath Raw path string to validate + * @return Full validated absolute path, or {@code null} if the path is invalid or inaccessible + */ + public static String validateAndGetFile(String inputPath) { + if (inputPath == null) return null; + + try { + if (inputPath.startsWith("~")) { + inputPath = Home + inputPath.substring(1); + } + + Path path = Path.of(inputPath); + if (Files.exists(path) && Files.isRegularFile(path)) { + return path.toRealPath().toString(); + } else { + System.out.println("Invalid file! Please ensure the path points to an ISO / image file."); + return null; + } + } catch (IOException e) { + System.out.println("File not found! Invalid Path or no access."); + return null; + } + } + + /** + * Displays the prompt for file path input to the standard output. + */ + protected static void fileQuestion() { + System.out.println("Please enter the FULL Path of your ISO / Image. (Tab-completion supported)"); + } +} \ No newline at end of file diff --git a/src/main/java/org/cametendo/Flasher.java b/src/main/java/org/cametendo/Flasher.java new file mode 100644 index 0000000..f6027ce --- /dev/null +++ b/src/main/java/org/cametendo/Flasher.java @@ -0,0 +1,42 @@ +package org.cametendo; +import java.util.Scanner; + +/** + * Handles the final confirmation and execution of the disk flashing process. + * + *

This class displays the current configuration to the user for confirmation + * before proceeding with the actual flashing operation using the dd command.

+ * + * @author Cametendo + * @version 1.0 + */ +public class Flasher { + + /** + * Displays the current flashing configuration and prompts for user confirmation. + * + *

Shows the device path, image file path, block size, and output flag that will be used + * for the flashing operation. If the user confirms, proceeds with the flashing process + * by calling {@link Dd#dd()}. If the user cancels, exits the application.

+ * + * @param UserInput Scanner object for reading user confirmation + */ + public static void flasher(Scanner UserInput) { + + String input = ""; + + System.out.println("The program will use the following configuration, do you want to flash with this? (Y/n)"); + System.out.println(" - To be flashed device: " + StorageDeviceLister.fullPath); + System.out.println(" - To be used path: " + FilePathAdd.ImagePath); + System.out.println(" - To be used blocksize: " + BlockSize.blockSizeString); + System.out.println(" - To be used oflag: " + OflagHandler.oflagHandleString); + input = UserInput.nextLine(); + if (YesNo.check(input)) { + System.out.println("Starting to flash..."); + } else { + System.out.println("Canceling..."); + System.exit(0); + } + Dd.dd(); + } +} \ No newline at end of file diff --git a/src/main/java/org/cametendo/GetHome.java b/src/main/java/org/cametendo/GetHome.java new file mode 100644 index 0000000..09c0ba9 --- /dev/null +++ b/src/main/java/org/cametendo/GetHome.java @@ -0,0 +1 @@ + diff --git a/src/main/java/org/cametendo/Greeting.java b/src/main/java/org/cametendo/Greeting.java new file mode 100644 index 0000000..24f17d8 --- /dev/null +++ b/src/main/java/org/cametendo/Greeting.java @@ -0,0 +1,27 @@ +package org.cametendo; + +import java.util.Scanner; + +/** + * Handles the initial greeting and user confirmation for the cflash application. + * + *

This class provides a welcome message and prompts the user to confirm + * whether they want to proceed with flashing an image to a storage device.

+ * + * @author Cametendo + * @version 1.0 + */ +public class Greeting { + + /** + * Displays a welcome message and prompts for user confirmation. + * + *

Shows the cflash welcome message and asks the user if they want to + * flash an image. If the user declines, exits the application.

+ * + * @param UserInput Scanner object for reading user confirmation + */ + public static void greeting(Scanner UserInput) { + System.out.println("Welcome to cflash!"); + } +} diff --git a/src/main/java/org/cametendo/Main.java b/src/main/java/org/cametendo/Main.java new file mode 100644 index 0000000..b9c27eb --- /dev/null +++ b/src/main/java/org/cametendo/Main.java @@ -0,0 +1,70 @@ +package org.cametendo; +import java.io.IOException; +import java.util.Scanner; + +/** + * Main entry point for the cflash application. + * + *

cflash is a command-line utility for flashing disk images to storage devices. + * It supports both interactive mode and command-line argument mode for automation.

+ * + *

In interactive mode, the user is guided through device selection, file path input, + * block size configuration, and output flag selection. In command-line mode, all parameters + * can be specified as arguments for automated flashing.

+ * + * @author Cametendo + * @version 1.0 + */ +public class Main { + + /** + * Main method that serves as the entry point for the cflash application. + * + *

The application can operate in two modes:

+ *
    + *
  • Interactive mode: No arguments provided, user is guided through the process
  • + *
  • Command-line mode: Four arguments provided for automated execution
  • + *
+ * + *

Command-line arguments format:

+ *
    + *
  1. Device name (e.g., "sda" without /dev/ prefix)
  2. + *
  3. Image file path (absolute or relative path to ISO/image file)
  4. + *
  5. Block size (1-6 or 512K,1M,2M,4M,8M,16M)
  6. + *
  7. Output flag (1-4 or direct,dsync,sync,nocache)
  8. + *
+ * + * @param args Command-line arguments. If 4 arguments are provided, runs in automated mode. + * If no arguments, runs in interactive mode. + * @throws InterruptedException If the flashing process is interrupted + * @throws IOException If there are I/O errors during device/file validation + */ + public static void main(String[] args) throws InterruptedException, IOException { + Scanner UserInput = new Scanner(System.in); + if (args.length == 4) { + String validatedDevice = StorageDeviceLister.validateAndGetPath(args[0]); + if (validatedDevice == null) { + return; + } + + String validatedFile = FilePathAdd.validateAndGetFile(args[1]); + if (validatedFile == null) { + return; + } + + StorageDeviceLister.fullPath = validatedDevice; + FilePathAdd.ImagePath = validatedFile; + BlockSize.blockSizeString = BlockSize.mapBlockSize(args[2]); + OflagHandler.oflagHandleString = OflagHandler.mapOflagHandle(args[3]); + + } else { + Greeting.greeting(UserInput); + + StorageDeviceLister.deviceCheck(UserInput); + FilePathAdd.filePath(); + BlockSize.blockSize(UserInput); + OflagHandler.Oflag(UserInput); + } + Flasher.flasher(UserInput); + } +} diff --git a/src/main/java/org/cametendo/OSDetector.java b/src/main/java/org/cametendo/OSDetector.java new file mode 100644 index 0000000..9e9a845 --- /dev/null +++ b/src/main/java/org/cametendo/OSDetector.java @@ -0,0 +1,97 @@ +package org.cametendo; +import java.nio.file.Path; + +/** + * Detects operating system types from image file names and displays completion messages. + * + *

This class analyzes the filename of disk images to identify the operating system + * and displays a personalized completion message with some fun descriptions for various + * Linux distributions, BSD variants, and other operating systems.

+ * + * @author Cametendo + * @version 1.0 + */ +public class OSDetector { + + /** + * Analyzes an image file path and displays a personalized completion message. + * + *

Extracts the filename from the provided path and attempts to identify the + * operating system based on filename patterns. Displays a fun, personalized message + * wishing the user well with their new OS installation.

+ * + * @param imagePath Path to the image file that was flashed + */ + public static void wishWell(String imagePath) { + String fileName = Path.of(imagePath).getFileName().toString().toLowerCase(); + + String osName; + + // Specialized & Advanced Distros + if (fileName.contains("nyarch")) { + osName = "Nyarch Linux (Nyaa~!)"; + } else if (fileName.contains("artix")) { + osName = "Artix Linux (Systemd-free Arch!)"; + } else if (fileName.contains("gentoo")) { + osName = "Gentoo (Enjoy the compiling...)"; + } else if (fileName.contains("nixos")) { + osName = "NixOS (Immutable & Reproducible!)"; + } else if (fileName.contains("void")) { + osName = "Void Linux"; + } else if (fileName.contains("arch")) { + osName = "Arch Linux (btw)"; + } else if (fileName.contains("alpine")) { + osName = "Alpine Linux (Minimalism at its peak)"; + } else if (fileName.contains("winux")) { + osName = "Winux (Windows without Windows)"; + } + // Mainstream Linux + else if (fileName.contains("fedora")) { + osName = "Fedora (Freehat Linux)"; + } else if (fileName.contains("debian")) { + osName = "Debian (Universal OS)"; + } else if (fileName.contains("ubuntu")) { + osName = "Ubuntu (Debian but fancy)"; + } else if (fileName.contains("mint")) { + osName = "Linux Mint"; + } else if (fileName.contains("pop-os") || fileName.contains("pop_os")) { + osName = "Pop!_OS"; + } + // The BSD Family + else if (fileName.contains("freebsd")) { + osName = "FreeBSD"; + } else if (fileName.contains("openbsd")) { + osName = "OpenBSD (Secure by default)"; + } else if (fileName.contains("netbsd")) { + osName = "NetBSD (It runs on everything!)"; + } + // Security & Privacy + else if (fileName.contains("kali")) { + osName = "Kali Linux (Happy Hacking)"; + } else if (fileName.contains("tails")) { + osName = "Tails (Incognito mode: ON)"; + } else if (fileName.contains("qubes")) { + osName = "Qubes OS (Security by Compartmentalization)"; + } + // Others & Legacy + else if (fileName.contains("win") && (fileName.contains("10") || fileName.contains("11"))) { + osName = "Windows (Spies... Spies everywhere)"; + } else if (fileName.contains("haiku")) { + osName = "Haiku OS"; + } else if (fileName.contains("reactos")) { + osName = "ReactOS"; + } else if (fileName.contains("jgh")) { + osName = "JGH OS (Sauerkraut juice)"; + } + // Generic Fallbacks + else if (fileName.contains("linux")) { + osName = "Linux"; + } else if (fileName.contains("bsd")) { + osName = "BSD"; + } else { + osName = "new OS"; + } + + System.out.println("\nFlash complete! Have fun with your " + osName + " installation!"); + } +} \ No newline at end of file diff --git a/src/main/java/org/cametendo/OflagHandler.java b/src/main/java/org/cametendo/OflagHandler.java new file mode 100644 index 0000000..39e6457 --- /dev/null +++ b/src/main/java/org/cametendo/OflagHandler.java @@ -0,0 +1,60 @@ +package org.cametendo; +import java.util.Scanner; + +/** + * Handles output flag (oflag) configuration for the dd command. + * + *

This class provides functionality to both interactively prompt users for output flag selection + * and to map command-line inputs to valid oflag values. The default output flag is "direct".

+ * + * @author Cametendo + * @version 1.0 + */ +public class OflagHandler { + + /** + * Default output flag string value. + * Set to "direct" as the default output flag for dd operations. + */ + public static String oflagHandleString = "direct"; + + /** + * Maps user input to a valid output flag value. + * + *

Supports both numeric inputs (1-4) and string inputs (direct,dsync,sync,nocache). + * If the input is not recognized, returns the default output flag.

+ * + * @param input User input string, either numeric (1-4) or output flag string + * @return Valid output flag string (direct, dsync, sync, nocache, or default) + */ + public static String mapOflagHandle(String input) { + return switch (input) { + case "1", "direct" -> "direct"; + case "2", "dsync" -> "dsync"; + case "3", "sync" -> "sync"; + case "4", "nocache" -> "nocache"; + default -> oflagHandleString; + }; + } + + /** + * Interactively prompts the user to select an output flag. + * + *

Displays a menu of available output flags and maps the user's input + * to a valid output flag value using {@link #mapOflagHandle(String)}.

+ * + * @param UserInput Scanner object for reading user input + * @return The selected output flag string + */ + static String Oflag(Scanner UserInput) { + System.out.println("Choose an Oflag (Default: direct)"); + System.out.println("direct (1), dsync (2), sync (3), nocache (4)"); + + String input = UserInput.nextLine(); + + oflagHandleString = mapOflagHandle(input); + + System.out.println("Using Oflag: " + oflagHandleString); + return oflagHandleString; + } +} diff --git a/src/main/java/org/cametendo/StorageDeviceLister.java b/src/main/java/org/cametendo/StorageDeviceLister.java new file mode 100644 index 0000000..b3b94d7 --- /dev/null +++ b/src/main/java/org/cametendo/StorageDeviceLister.java @@ -0,0 +1,134 @@ +package org.cametendo; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.Scanner; + +/** + * Handles storage device detection, listing, and validation for the flashing process. + * *

Includes safety checks to ensure we don't nuke a partition that's currently in use. + * Keeps the user in the loop with a more personal touch.

+ * * @author Cametendo + * @version 1.1 + */ +public class StorageDeviceLister { + + public static String device = ""; + public static String fullPath = ""; + + /** + * Guides the user through selecting a safe, unmounted storage device. + */ + protected static String deviceCheck(Scanner UserInput) { + deviceList(); + + while (true) { + System.out.print("Target device: "); + device = UserInput.nextLine().trim(); + + if (device.isBlank()) { + System.out.println("Oops... Device name is empty. Did you missclick?"); + continue; + } + + // Standardize path - allows entering 'sda' or '/dev/sda' + String checkPath = device.startsWith("/dev/") ? device : "/dev/" + device; + Path path = Path.of(checkPath); + + try { + fullPath = path.toRealPath().toString(); + + // Check if the user is about to break their system + if (isMounted(fullPath)) { + System.out.println("Wait a second! " + fullPath + " is currently mounted."); + System.out.println("I can't flash to a device that's in use. Unmount it and try again!"); + continue; + } + + System.out.println("Solid choice. Using device: " + fullPath); + return fullPath; + + } catch (IOException e) { + System.out.println("Hmm... I can't seem to find or access that device. Are you sure you have access to it?"); + } + } + } + + /** + * Asks lsblk if the device or its children have an active mount point. + */ + private static boolean isMounted(String devicePath) { + try { + ProcessBuilder pb = new ProcessBuilder("lsblk", "-no", "MOUNTPOINT", devicePath); + Process process = pb.start(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.trim().isEmpty()) { + return true; + } + } + } + process.waitFor(); + } catch (IOException | InterruptedException e) { + System.out.println("Warning: Mountstatus couldn't be verified"); + return true; + } + return false; + } + + /** + * Lists devices with enough info for the user to make a smart decision. + */ + private static void deviceList() { + try { + System.out.println("Scanning for block devices..."); + ProcessBuilder pb = new ProcessBuilder("lsblk", "-o", "NAME,SIZE,TYPE,RM,MOUNTPOINT"); + Process process = pb.start(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + process.waitFor(); + + System.out.println("Enter the name of your device (e.g., sdb or nvme1n1):"); + + } catch (IOException | InterruptedException e) { + System.out.println("Failed to run lsblk. Do you have it installed."); + } + } + + /** + * Validates a device path (e.g., from a CLI argument). + * *

Checks if the device exists, resolves the real path, and ensures + * it isn't currently mounted before giving the green light.

+ * * @param deviceName Device name (e.g., "sda" or "/dev/sda") + * @return Full validated path, or null if it's a bad idea to use it + */ + public static String validateAndGetPath(String deviceName) { + if (deviceName == null || deviceName.isBlank()) return null; + + try { + // Support both "sda" and "/dev/sda" + String checkPath = deviceName.startsWith("/dev/") ? deviceName : "/dev/" + deviceName; + Path path = Path.of(checkPath); + String resolvedPath = path.toRealPath().toString(); + + // Safety check for CLI arguments too! + if (isMounted(resolvedPath)) { + System.out.println("Hold up! " + resolvedPath + " is mounted. I won't let you flash it like that."); + return null; + } + + return resolvedPath; + } catch (IOException e) { + System.out.println("Hmm... I couldn't find a device at '" + deviceName + "'. Is it plugged in?"); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/cametendo/YesNo.java b/src/main/java/org/cametendo/YesNo.java new file mode 100644 index 0000000..31de3ab --- /dev/null +++ b/src/main/java/org/cametendo/YesNo.java @@ -0,0 +1,37 @@ +package org.cametendo; + +/** + * Utility class for parsing yes/no user responses. + * + *

This class provides a simple method to interpret user input as a boolean + * value, accepting various forms of "yes" and treating everything else as "no".

+ * + * @author Cametendo + * @version 1.0 + */ +public class YesNo { + + /** + * Parses user input to determine if it represents a "yes" response. + * + *

Accepts "Y", "y", and empty string (default) as yes responses. + * All other inputs are treated as no responses.

+ * + * @param input User input string to parse + * @return true if the input represents a yes response, false otherwise + */ + public static boolean check(String input) { + switch (input) { + case "Y": + return true; + case "y": + return true; + case "": + return true; + case "n": + return false; + default: + return false; + } + } +} diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..dbc13fc --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Uninstall script for cflash + +if [[ $EUID -ne 0 ]]; then + echo "Please run as root to uninstall globally." + exit 1 +fi + +rm -f /usr/local/bin/cflash +rm -rf /usr/local/lib/cflash + +echo "cflash uninstalled successfully."