10 Commits

Author SHA1 Message Date
Cametendo
9f0338740a Added PKGBUILD for AUR publishing 2026-04-23 19:30:11 +02:00
Cametendo
ae8a44fe76 update gitignore 2026-04-23 18:54:14 +02:00
64985fa2d8 Update README.md for installation 2026-04-23 18:48:29 +02:00
Cametendo
d8b3795b3e test: verify update script 2026-04-23 18:32:36 +02:00
9157b3514a Update README.md 2026-04-23 15:02:57 +02:00
Cametendo
4ed8cdc792 Fixed blocksize and oflag and only supporting number
in both the CLI version aswell as the TUI version
2026-04-23 14:40:36 +02:00
Cametendo
23f5ff77ec Fixed blocksize and oflag and only supporting number
in both the CLI version aswell as the TUI version
2026-04-23 14:26:32 +02:00
Cametendo
ee7c2ba46b Fix 'Program doesnt close when saying i dont want to flash a image' 2026-04-23 11:41:23 +02:00
Cametendo
d0d239e0ca Added tab-completion and cli-arguments 2026-04-22 22:12:33 +02:00
Cametendo
5fd53af017 Removed MacOS and FreeBSD from supported OS
Will add them once they are also supported
2026-04-21 22:04:08 +02:00
16 changed files with 523 additions and 91 deletions

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ hs_err_pid*
.project
.settings/
.vscode/
.claude/

35
PKGBUILD Normal file
View File

@@ -0,0 +1,35 @@
# Maintainer: Cametendo cameronmathis08@gmail.com
pkgname=cflash
pkgver=1.0.0
pkgrel=1
pkgdesc="Small and lightweight image and iso flasher build on dd."
arch=('any')
url="https://github.com/cametendo/cflash-git"
license=('MIT')
depends=('java-runtime>=21')
makedepends=('java-environment>=21' 'maven')
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"
# for some reason MAKEPKD won't accept my version, using the wildcard for literally anything if may find
install -Dm644 target/cflash-*-jar-with-dependencies.jar \
"$pkgdir/usr/share/java/cflash/cflash.jar"
# Create the executable
install -d "$pkgdir/usr/bin"
cat <<EOF > "$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"
}

View File

@@ -9,10 +9,74 @@ Small and lightweight image and iso flasher build on `dd`.
# About
Java program using `dd` to make flashing iso and image files easier on the terminal. This program allows anyone to flash iso and image files without having to search for extra GUI tools by keeping it simple and resource-friendly.
## Getting Started
# Requirements
- `Java`: 21 (Download [here](https://www.oracle.com/java/technologies/downloads/#java21)
- `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 the application:
```bash
java -jar target/cflash-<version>.jar (optionally add arguments here, like with dd)
```
### 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:
```bash
./build.sh
```
4. Install globally (requires root privileges):
```bash
sudo ./install.sh
```
5. Run cflash from anywhere:
```bash
cflash
```
**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.
# Usage
- Using the command `cflash` in the terminal, will start the flashing process. You will be asked several question before the flashing begins:
@@ -27,13 +91,13 @@ Java program using `dd` to make flashing iso and image files easier on the termi
- **IMPORTANT**: Since dd needs sudo rights, ensure you have root priviliges.
# Supported OS
- Linux, MacOS, FreeBSD
- Linux
# Installation
1. Clone the repository onto your local device.
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`.
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)

86
build.sh Executable file
View File

@@ -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}"

84
cflash.sh Executable file
View File

@@ -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

16
install.sh Executable file
View File

@@ -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."

32
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>org.cametendo</groupId>
<artifactId>cflash</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0.1</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
@@ -22,4 +22,34 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<mainClass>org.cametendo.Main</mainClass>
</manifest>
<manifestEntries>
<Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -3,36 +3,28 @@ import java.util.Scanner;
public class BlockSize {
public static String blockSizeString = "";
public static String blockSizeString = "4M";
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;
};
}
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 blockSizeInput = UserInput.nextLine();
switch (blockSizeInput) {
case "1":
blockSizeString = "512KB";
break;
case "2":
blockSizeString = "1M";
break;
case "3":
blockSizeString = "2M";
break;
case "4":
blockSizeString = "4M";
break;
case "5":
blockSizeString = "8M";
break;
case "6":
blockSizeString = "16M";
break;
default:
blockSizeString = "4M";
break;
}
String input = UserInput.nextLine();
blockSizeString = mapBlockSize(input);
System.out.println("Using blocksize of: " + blockSizeString);
return blockSizeString;
}

View File

@@ -20,6 +20,7 @@ public class Dd {
System.out.print(c);
System.out.flush();
}
OSDetector.wishWell(FilePathAdd.ImagePath);
} catch (IOException e) {
e.printStackTrace();
}

View File

@@ -2,32 +2,59 @@ package org.cametendo;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Scanner;
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;
public class FilePathAdd {
public static String ImagePath = "";
protected static String filePath(Scanner UserInput) {
protected static String filePath() throws IOException {
fileQuestion();
Terminal terminal = TerminalBuilder.terminal();
LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(new FileNameCompleter()).build();
while (true) {
ImagePath = UserInput.nextLine();
ImagePath = reader.readLine("Path: ").trim();
if (ImagePath.isBlank()) {
System.out.println("Oops.. You didn't specify a file, did you missclick?");
System.out.println("Oops... You didn't specify a file!");
continue;
}
Path path = Path.of(ImagePath);
try {
Files.readAttributes(path, "basic:size");
if (!Files.exists(path) || !Files.isRegularFile(path)) {
System.out.println("Invalid file! Please ensure the path points to an ISO / image file.");
continue;
}
System.out.println("Using File: " + ImagePath);
return ImagePath;
} catch (IOException e) {
System.out.println("Failed to access file, invalid path or no access to file! Please try again.");
}
}
public static String validateAndGetFile(String ImagePath) {
try {
Path path = Path.of(ImagePath);
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;
}
}
protected static void fileQuestion() {
System.out.println("Please enter the FULL Path of your ISO / Image. (No tab-complete)");
System.out.println("Please enter the FULL Path of your ISO / Image. (Tab-completion supported)");
}
}

View File

@@ -6,10 +6,10 @@ public class Flasher {
String input = "";
System.out.println("The programm wil use the following configuration, do you want to flash with this? (Y/n)");
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 bed used blocksize: " + BlockSize.blockSizeString);
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)) {
@@ -19,6 +19,5 @@ public class Flasher {
System.exit(0);
}
Dd.dd();
System.out.println("Flash completed.");
}
}

View File

@@ -1,7 +1,18 @@
package org.cametendo;
import java.util.Scanner;
public class Greeting {
public static void greeting() {
public static void greeting(Scanner UserInput) {
System.out.println("Welcome to cflash!");
System.out.println("Would you like to flash an image (Y/n)");
String input = UserInput.nextLine();
if (YesNo.check(input)) {
System.out.println("Please choose the to be flashed device (f. e. sda)");
} else {
System.out.println("Canceling...");
System.exit(0);
}
}
}

View File

@@ -1,25 +1,34 @@
package org.cametendo;
import java.io.IOException;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws InterruptedException {
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;
}
Greeting.greeting();
String input = UserInput.nextLine();
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]);
if (YesNo.check(input)) {
System.out.println("Please choose the to be flashed device (f. e. sda)");
} else {
System.out.println("Canceling...");
System.exit(0);
}
Greeting.greeting(UserInput);
StorageDeviceLister.deviceCheck(UserInput);
FilePathAdd.filePath(UserInput);
FilePathAdd.filePath();
BlockSize.blockSize(UserInput);
OflagHandler.handleOflag(UserInput);
OflagHandler.Oflag(UserInput);
}
Flasher.flasher(UserInput);
}
}

View File

@@ -0,0 +1,76 @@
package org.cametendo;
import java.nio.file.Path;
public class OSDetector {
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";
}
// 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! 🚀");
}
}

View File

@@ -3,31 +3,27 @@ import java.util.Scanner;
public class OflagHandler {
public static String oflagHandleString = "";
public static String oflagHandleString = "direct";
static String handleOflag(Scanner UserInput) {
System.out.println("Okay, next up please define your oflag (Default: direct)");
System.out.println("Available flags: direct (1), dsync (2), sync (3), nocache (4)");
String oflagHandleInput = UserInput.nextLine();
switch (oflagHandleInput) {
case "1":
oflagHandleString = "direct";
break;
case "2":
oflagHandleString = "dsync";
break;
case "3":
oflagHandleString = "sync";
break;
case "4":
oflagHandleString = "nocache";
break;
default:
oflagHandleString = "direct";
break;
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;
};
}
System.out.println("Using oflag: " + oflagHandleString);
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;
}
}

View File

@@ -11,10 +11,8 @@ public class StorageDeviceLister {
public static String fullPath = "";
protected static String deviceCheck(Scanner UserInput) {
// 1. lsblk wird genau EINMAL aufgerufen
deviceList();
// 2. Die Abfrage-Schleife
while (true) {
device = UserInput.nextLine();
if (device.isBlank()) {
@@ -24,20 +22,27 @@ public class StorageDeviceLister {
Path path = Path.of("/dev/" + device);
try {
// Versuche, den echten Pfad zu finden
fullPath = path.toRealPath().toString();
// Wenn wir hier ankommen, war der Pfad gültig
System.out.println("Using device: " + fullPath);
return fullPath;
} catch (IOException e) {
// Fehler-Output, danach springt die Schleife wieder nach oben
System.out.println("Failed to access device! Invalid path or no access. Please try again.");
}
}
}
public static String validateAndGetPath(String deviceName) {
try {
Path path = Path.of("/dev/" + deviceName);
return path.toRealPath().toString();
} catch (IOException e) {
System.out.println("Device not found. Invalid Path or no access.");
return null;
}
}
private static void deviceList() {
try {
ProcessBuilder pb = new ProcessBuilder("lsblk");