This commit is contained in:
spitkov 2025-01-05 14:22:47 +01:00
commit 0add4e98be
7 changed files with 766 additions and 0 deletions

42
.gitignore vendored Normal file
View file

@ -0,0 +1,42 @@
# Build directories
build/
cmake-build-*/
# IDE specific files
.idea/
.vscode/
*.swp
*.swo
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Package manager specific
/var/cache/yns/
/var/lib/yns/

38
CMakeLists.txt Normal file
View file

@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.10)
project(yns VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find required packages
find_package(CURL REQUIRED)
find_package(nlohmann_json REQUIRED)
# Add executable
add_executable(yns
src/main.cpp
src/package_manager.cpp
)
# Include directories
target_include_directories(yns PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CURL_INCLUDE_DIRS}
)
# Link libraries
target_link_libraries(yns PRIVATE
${CURL_LIBRARIES}
nlohmann_json::nlohmann_json
)
# Installation
install(TARGETS yns DESTINATION bin)
# Create required directories during installation
install(CODE "
execute_process(
COMMAND mkdir -p /var/cache/yns
COMMAND mkdir -p /var/lib/yns
)
")

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 YNS Package Manager
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.

150
README.md Normal file
View file

@ -0,0 +1,150 @@
# YNS Package Manager
A lightweight package manager for Linux that uses a single repository source.
## Dependencies
- CMake (>= 3.10)
- C++17 compiler
- libcurl
- nlohmann-json
On Ubuntu/Debian, you can install the dependencies with:
```bash
sudo apt-get install build-essential cmake libcurl4-openssl-dev nlohmann-json3-dev
```
## Building
```bash
mkdir build
cd build
cmake ..
make
```
## Installation
After building, you can install the package manager with:
```bash
sudo make install
```
This will:
- Install the `yns` binary to `/usr/local/bin`
- Create necessary directories:
- `/var/cache/yns/` - For package cache
- `/var/lib/yns/` - For installed package database
## Usage
### Update package cache
```bash
yns update
```
### Install a package
```bash
yns install <package_name>
```
### Remove a package
```bash
yns remove <package_name>
```
### Upgrade a package
```bash
yns upgrade <package_name>
```
### List all packages
```bash
yns list
```
### Interactive Mode
```bash
yns interactive
```
Interactive mode provides a shell-like interface where you can execute multiple commands without the `yns` prefix. Available commands in interactive mode:
- `help` - Show available commands
- `update` - Update package cache
- `install <package>` - Install a package
- `remove <package>` - Remove a package
- `upgrade <package>` - Upgrade a package
- `list` - List all packages
- `clear` - Clear the screen
- `exit` - Exit interactive mode
Example interactive session:
```
$ yns interactive
YNS Package Manager Interactive Mode
Type 'help' for available commands or 'exit' to quit
yns> update
[100%] Package cache updated successfully
yns> list
Available packages:
==================
calc [installed 1.1, update available 2.0]
notepad [available 2.0]
yns> install notepad
[50%] Installing notepad@2.0
notepad@2.0 installed successfully
yns> exit
Goodbye!
```
## Color Coding
The package manager uses color-coded output for better visibility:
🔵 Blue:
- Progress indicators (e.g., "[50%] Installing package...")
- Available but not installed packages in `yns list`
- Interactive mode prompt
🟢 Green:
- Success messages
- Installed packages that are up to date in `yns list`
- Interactive mode welcome message
🟡 Yellow:
- Installed packages with updates available in `yns list`
🔴 Red:
- Error messages
Example `yns list` output:
```
Available packages:
==================
calc [installed 1.1, update available 2.0] # Yellow
notepad [available 2.0] # Blue
vim [installed 3.0] # Green
```
## Repository Structure
The package manager uses a single repository source at:
`https://raw.githubusercontent.com/spitkov/ynsrepo/refs/heads/main/repo.json`
The repository contains package information in JSON format, including:
- Package name
- Version
- Installation script URL
- Update script URL
- Remove script URL
## Features
- Colored terminal output
- Progress indicators
- Automatic package cache management
- Version tracking for installed packages
- Secure script execution
- Error handling and recovery
- Interactive shell mode

View file

@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
class PackageManager {
public:
PackageManager();
bool update();
bool install(const std::string& package_name);
bool remove(const std::string& package_name);
bool upgrade(const std::string& package_name);
bool list();
bool interactive_mode();
private:
static constexpr const char* REPO_URL = "https://raw.githubusercontent.com/spitkov/ynsrepo/refs/heads/main/repo.json";
static constexpr const char* CACHE_DIR = "/var/cache/yns/";
static constexpr const char* CACHE_FILE = "/var/cache/yns/repo.json";
static constexpr const char* INSTALLED_DB = "/var/lib/yns/installed.json";
bool download_file(const std::string& url, const std::string& output_path);
bool execute_script(const std::string& script_path);
bool cache_repo();
json read_cache();
json read_installed_db();
void save_installed_db(const json& db);
void print_progress(const std::string& message, int percentage);
void print_error(const std::string& message);
void print_success(const std::string& message);
void print_interactive_help();
json repo_cache;
json installed_packages;
};

96
src/main.cpp Normal file
View file

@ -0,0 +1,96 @@
#include "package_manager.hpp"
#include <iostream>
#include <string>
#include <unistd.h>
#include <vector>
void print_usage() {
std::cout << "Usage: yns <command> [package_name]\n\n"
<< "Commands:\n"
<< " update Update package cache\n"
<< " install <package> Install a package\n"
<< " remove <package> Remove a package\n"
<< " upgrade <package> Upgrade a package\n"
<< " list List all packages\n"
<< " interactive Start interactive mode\n\n"
<< "Interactive Mode:\n"
<< " Run 'yns interactive' to enter interactive mode where you can\n"
<< " execute multiple commands without prefix. Type 'help' in\n"
<< " interactive mode for available commands.\n";
}
bool needs_sudo(const std::string& command) {
return command == "update" || command == "install" ||
command == "remove" || command == "upgrade" ||
command == "interactive" || command == "list";
}
std::string get_self_path() {
char path[1024];
ssize_t count = readlink("/proc/self/exe", path, sizeof(path));
return std::string(path, (count > 0) ? count : 0);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
print_usage();
return 1;
}
std::string command = argv[1];
if (needs_sudo(command) && geteuid() != 0) {
std::string self = get_self_path();
std::string sudo_cmd = "sudo " + self;
for (int i = 1; i < argc; i++) {
sudo_cmd += " ";
sudo_cmd += argv[i];
}
std::cout << "This operation requires root privileges.\n";
return system(sudo_cmd.c_str());
}
PackageManager pm;
try {
if (command == "update") {
return pm.update() ? 0 : 1;
}
else if (command == "list") {
return pm.list() ? 0 : 1;
}
else if (command == "interactive") {
return pm.interactive_mode() ? 0 : 1;
}
else if (command == "install" || command == "remove" || command == "upgrade") {
if (argc < 3) {
std::cerr << "Error: Package name required for " << command << " command\n";
print_usage();
return 1;
}
std::string package_name = argv[2];
if (command == "install") {
return pm.install(package_name) ? 0 : 1;
}
else if (command == "remove") {
return pm.remove(package_name) ? 0 : 1;
}
else {
return pm.upgrade(package_name) ? 0 : 1;
}
}
else {
std::cerr << "Error: Unknown command '" << command << "'\n";
print_usage();
return 1;
}
}
catch (const std::exception& e) {
std::cerr << "\033[31mError: " << e.what() << "\033[0m\n";
return 1;
}
}

380
src/package_manager.cpp Normal file
View file

@ -0,0 +1,380 @@
#include "package_manager.hpp"
#include <curl/curl.h>
#include <fstream>
#include <iostream>
#include <filesystem>
#include <cstdlib>
#include <sstream>
#include <sys/wait.h>
namespace fs = std::filesystem;
const std::string RED = "\033[31m";
const std::string GREEN = "\033[32m";
const std::string BLUE = "\033[34m";
const std::string YELLOW = "\033[33m";
const std::string RESET = "\033[0m";
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
PackageManager::PackageManager() {
fs::create_directories(CACHE_DIR);
fs::create_directories("/var/lib/yns");
curl_global_init(CURL_GLOBAL_DEFAULT);
installed_packages = read_installed_db();
}
bool PackageManager::download_file(const std::string& url, const std::string& output_path) {
CURL* curl = curl_easy_init();
if (!curl) return false;
std::string response_data;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) return false;
std::ofstream output_file(output_path);
if (!output_file) return false;
output_file << response_data;
return true;
}
bool PackageManager::execute_script(const std::string& script_path) {
std::string cmd = "chmod +x " + script_path + " && " + script_path;
int exit_code = system(cmd.c_str());
if (WIFEXITED(exit_code)) {
int status = WEXITSTATUS(exit_code);
if (status != 0) {
print_error("Script failed with exit code: " + std::to_string(status));
return false;
}
return true;
}
print_error("Script terminated abnormally");
return false;
}
bool PackageManager::cache_repo() {
print_progress("Updating package cache", 0);
if (!download_file(REPO_URL, CACHE_FILE)) {
print_error("Failed to download repository data");
return false;
}
print_progress("Updating package cache", 100);
print_success("Package cache updated successfully");
return true;
}
json PackageManager::read_cache() {
try {
std::ifstream cache_file(CACHE_FILE);
if (!cache_file) return json::object();
return json::parse(cache_file);
} catch (...) {
return json::object();
}
}
json PackageManager::read_installed_db() {
try {
std::ifstream db_file(INSTALLED_DB);
if (!db_file) return json::object();
return json::parse(db_file);
} catch (...) {
return json::object();
}
}
void PackageManager::save_installed_db(const json& db) {
std::ofstream db_file(INSTALLED_DB);
db_file << db.dump(4);
}
void PackageManager::print_progress(const std::string& message, int percentage) {
std::cout << BLUE << "[" << percentage << "%] " << message << RESET << std::endl;
}
void PackageManager::print_error(const std::string& message) {
std::cerr << RED << "Error: " << message << RESET << std::endl;
}
void PackageManager::print_success(const std::string& message) {
std::cout << GREEN << message << RESET << std::endl;
}
bool PackageManager::update() {
return cache_repo();
}
bool PackageManager::install(const std::string& package_name) {
if (!update()) return false;
repo_cache = read_cache();
if (!repo_cache["packages"].contains(package_name)) {
print_error("Package '" + package_name + "' not found");
return false;
}
auto package = repo_cache["packages"][package_name];
std::string repo_version = package["version"];
if (installed_packages.contains(package_name)) {
std::string installed_version = installed_packages[package_name]["version"];
if (installed_version == repo_version) {
print_success(package_name + " is already installed (version " + installed_version + ")");
return true;
} else {
print_progress("New version available: " + repo_version + " (currently installed: " + installed_version + ")", 0);
std::cout << "Would you like to upgrade? [y/N] ";
std::string response;
std::getline(std::cin, response);
if (response == "y" || response == "Y") {
return upgrade(package_name);
} else {
print_success("Keeping current version " + installed_version);
return true;
}
}
}
std::string install_script = package["install"];
std::string temp_script = "/tmp/yns_install_" + package_name + ".sh";
print_progress("Downloading installation script", 0);
if (!download_file(install_script, temp_script)) {
print_error("Failed to download installation script");
return false;
}
print_progress("Installing " + package_name + "@" + repo_version, 50);
std::string cmd = "chmod +x " + temp_script + " && " + temp_script;
int exit_code = system(cmd.c_str());
fs::remove(temp_script);
if (!WIFEXITED(exit_code)) {
print_error("Installation script terminated abnormally");
return false;
}
int status = WEXITSTATUS(exit_code);
if (status != 0) {
print_error("Installation failed with exit code: " + std::to_string(status));
return false;
}
installed_packages[package_name] = {
{"version", repo_version}
};
save_installed_db(installed_packages);
print_success(package_name + "@" + repo_version + " installed successfully");
return true;
}
bool PackageManager::remove(const std::string& package_name) {
if (!installed_packages.contains(package_name)) {
print_error("Package '" + package_name + "' is not installed");
return false;
}
repo_cache = read_cache();
if (!repo_cache["packages"].contains(package_name)) {
print_error("Package information not found in repository");
return false;
}
std::string remove_script = repo_cache["packages"][package_name]["remove"];
std::string temp_script = "/tmp/yns_remove_" + package_name + ".sh";
print_progress("Downloading removal script", 0);
if (!download_file(remove_script, temp_script)) {
print_error("Failed to download removal script");
return false;
}
print_progress("Removing " + package_name, 50);
std::string cmd = "chmod +x " + temp_script + " && " + temp_script;
int exit_code = system(cmd.c_str());
fs::remove(temp_script);
if (!WIFEXITED(exit_code)) {
print_error("Removal script terminated abnormally");
return false;
}
int status = WEXITSTATUS(exit_code);
if (status != 0) {
print_error("Removal failed with exit code: " + std::to_string(status));
return false;
}
installed_packages.erase(package_name);
save_installed_db(installed_packages);
print_success(package_name + " removed successfully");
return true;
}
bool PackageManager::upgrade(const std::string& package_name) {
if (!installed_packages.contains(package_name)) {
print_error("Package '" + package_name + "' is not installed");
return false;
}
if (!update()) return false;
repo_cache = read_cache();
if (!repo_cache["packages"].contains(package_name)) {
print_error("Package information not found in repository");
return false;
}
std::string installed_version = installed_packages[package_name]["version"];
std::string repo_version = repo_cache["packages"][package_name]["version"];
if (installed_version == repo_version) {
print_success(package_name + " is already up to date (" + installed_version + ")");
return true;
}
std::string update_script = repo_cache["packages"][package_name]["update"];
std::string temp_script = "/tmp/yns_update_" + package_name + ".sh";
print_progress("Downloading update script", 0);
if (!download_file(update_script, temp_script)) {
print_error("Failed to download update script");
return false;
}
print_progress("Updating " + package_name + " from " + installed_version + " to " + repo_version, 50);
std::string cmd = "chmod +x " + temp_script + " && " + temp_script;
int exit_code = system(cmd.c_str());
fs::remove(temp_script);
if (!WIFEXITED(exit_code)) {
print_error("Update script terminated abnormally");
return false;
}
int status = WEXITSTATUS(exit_code);
if (status != 0) {
print_error("Update failed with exit code: " + std::to_string(status));
return false;
}
installed_packages[package_name]["version"] = repo_version;
save_installed_db(installed_packages);
print_success(package_name + " updated to version " + repo_version);
return true;
}
bool PackageManager::list() {
if (!update()) return false;
repo_cache = read_cache();
std::cout << "\nAvailable packages:\n";
std::cout << "==================\n";
for (const auto& [name, package] : repo_cache["packages"].items()) {
std::string repo_version = package["version"];
std::string status;
if (installed_packages.contains(name)) {
std::string installed_version = installed_packages[name]["version"];
if (installed_version == repo_version) {
status = GREEN + "[installed " + installed_version + "]" + RESET;
} else {
status = YELLOW + "[installed " + installed_version + ", update available " + repo_version + "]" + RESET;
}
} else {
status = BLUE + "[available " + repo_version + "]" + RESET;
}
std::cout << name << " " << status << "\n";
}
return true;
}
void PackageManager::print_interactive_help() {
std::cout << "\nAvailable commands:\n"
<< " help Show this help message\n"
<< " update Update package cache\n"
<< " install <package> Install a package\n"
<< " remove <package> Remove a package\n"
<< " upgrade <package> Upgrade a package\n"
<< " list List all packages\n"
<< " clear Clear the screen\n"
<< " exit Exit interactive mode\n\n";
}
bool PackageManager::interactive_mode() {
std::cout << GREEN << "YNS Package Manager Interactive Mode\n" << RESET;
std::cout << "Type 'help' for available commands or 'exit' to quit\n";
std::string line;
while (true) {
std::cout << BLUE << "yns> " << RESET;
if (!std::getline(std::cin, line) || line == "exit") {
std::cout << "Goodbye!\n";
break;
}
if (line.empty()) {
continue;
}
std::istringstream iss(line);
std::string command;
iss >> command;
if (command == "help") {
print_interactive_help();
}
else if (command == "update") {
update();
}
else if (command == "list") {
list();
}
else if (command == "clear") {
system("clear");
}
else if (command == "install" || command == "remove" || command == "upgrade") {
std::string package_name;
if (!(iss >> package_name)) {
print_error("Package name required for " + command + " command");
continue;
}
if (command == "install") {
install(package_name);
}
else if (command == "remove") {
remove(package_name);
}
else {
upgrade(package_name);
}
}
else {
print_error("Unknown command '" + command + "'. Type 'help' for available commands.");
}
}
return true;
}