diff --git a/.github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml b/.github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml new file mode 100644 index 0000000..6d37acb --- /dev/null +++ b/.github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml @@ -0,0 +1,47 @@ +name: "Build and release for big-bear-casaos-user-management" + +on: + push: + branches: + - main + paths: + - "big-bear-casaos-user-management/**" + +jobs: + create: + name: "Creates the newest release by version" + runs-on: "ubuntu-latest" + + steps: + - name: Checkout project + uses: actions/checkout@v2.3.4 + + # New step to read the VERSION file and set the version as an output + - name: Get the version + id: get_version + run: echo "big_bear_casaos_user_management_version=$(cat big-bear-casaos-user-management/VERSION)" >> $GITHUB_ENV + + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + + - name: Set up Docker Build + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + platforms: linux/amd64,linux/arm64 + context: ./big-bear-casaos-user-management + file: ./big-bear-casaos-user-management/Dockerfile + tags: | + bigbeartechworld/big-bear-casaos-user-management:latest + bigbeartechworld/big-bear-casaos-user-management:${{ env.big_bear_casaos_user_management_version }} diff --git a/.gitignore b/.gitignore index 9804a16..ed1ad03 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ pihole-unbound/build.log pihole-unbound/build.sh genmon/build.log genmon/build.sh +genmon/build.log +genmon/build.sh +big-bear-casaos-user-management/build.log +big-bear-casaos-user-management/build.sh \ No newline at end of file diff --git a/big-bear-casaos-user-management/Dockerfile b/big-bear-casaos-user-management/Dockerfile new file mode 100644 index 0000000..18dcf4f --- /dev/null +++ b/big-bear-casaos-user-management/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.9-slim + +# Install system dependencies (systemd includes systemctl) +RUN apt-get update && \ + apt-get install -y systemd sudo && \ + rm -rf /var/lib/apt/lists/* + +# Create necessary directories +RUN mkdir -p /var/lib/casaos/db + +# Set working directory +WORKDIR /app + +# Copy all application files +COPY app/ . + +# Install Python dependencies +RUN pip install flask flask-wtf + +# Create a volume for persistent storage +VOLUME /var/lib/casaos/db + +# Expose port for Flask +EXPOSE 5000 + +# Run Flask app +CMD ["python", "app.py"] diff --git a/big-bear-casaos-user-management/VERSION b/big-bear-casaos-user-management/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/big-bear-casaos-user-management/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/big-bear-casaos-user-management/app/app.py b/big-bear-casaos-user-management/app/app.py new file mode 100644 index 0000000..9c20912 --- /dev/null +++ b/big-bear-casaos-user-management/app/app.py @@ -0,0 +1,71 @@ +from flask import Flask, render_template, redirect, url_for, flash, request +from user_management import (list_users, add_user, edit_password, + remove_user, reset_database, hash_password) +from functools import wraps +from flask import request, Response +import os + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'your-secret-key' + +ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME', 'admin') +ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'YOUR_SECURE_PASSWORD') + +def check_auth(username, password): + """Verify admin credentials""" + return username == ADMIN_USERNAME and password == ADMIN_PASSWORD + +def authenticate(): + return Response( + 'Could not verify your access level for that URL.\n' + 'You have to login with proper credentials', 401, + {'WWW-Authenticate': 'Basic realm="Login Required"'} + ) + +def requires_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + auth = request.authorization + if not auth or not check_auth(auth.username, auth.password): + return authenticate() + return f(*args, **kwargs) + return decorated + +@app.route('/') +@requires_auth +def index(): + users = list_users(return_data=True) + return render_template('index.html', users=users) + +@app.route('/add_user', methods=['POST']) +def add_user_route(): + username = request.form.get('username') + password = request.form.get('password') + if add_user(username, password): + flash('User added successfully', 'success') + return redirect(url_for('index')) + +@app.route('/edit_password', methods=['POST']) +def edit_password_route(): + user_id = request.form.get('user_id') + new_password = request.form.get('new_password') + if edit_password(user_id, new_password): + flash('Password updated successfully', 'success') + return redirect(url_for('index')) + +@app.route('/remove_user', methods=['POST']) +def remove_user_route(): + user_id = request.form.get('user_id') + if remove_user(user_id): + flash('User removed successfully', 'success') + return redirect(url_for('index')) + +@app.route('/reset_database', methods=['POST']) +@requires_auth +def reset_database_route(): + if reset_database(): + flash('Database reset successfully', 'success') + return redirect(url_for('index')) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/big-bear-casaos-user-management/app/templates/base.html b/big-bear-casaos-user-management/app/templates/base.html new file mode 100644 index 0000000..5733ef6 --- /dev/null +++ b/big-bear-casaos-user-management/app/templates/base.html @@ -0,0 +1,15 @@ + + + + BigBearCasaOS User Management + + + + + +
{% block content %}{% endblock %}
+ + diff --git a/big-bear-casaos-user-management/app/templates/index.html b/big-bear-casaos-user-management/app/templates/index.html new file mode 100644 index 0000000..1beb4bc --- /dev/null +++ b/big-bear-casaos-user-management/app/templates/index.html @@ -0,0 +1,128 @@ +{% extends "base.html" %} {% block content %} +

BigBearCasaOS User Management

+ + +
+
Users
+
+ + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
IDUsernameRoleActions
{{ user[0] }}{{ user[1] }}{{ user[2] }} + +
+ + +
+
+ + + {% for user in users %} + + {% endfor %} +
+
+ + +
+
Add New User
+
+
+
+ + +
+
+ + +
+ +
+
+
+ +
+
Database Management
+
+
+ +
+
+
+{% endblock %} diff --git a/big-bear-casaos-user-management/app/user_management.py b/big-bear-casaos-user-management/app/user_management.py new file mode 100644 index 0000000..f192a02 --- /dev/null +++ b/big-bear-casaos-user-management/app/user_management.py @@ -0,0 +1,170 @@ +# Based on https://github.com/carlbomsdata/casaos-user-management +# Original discussion: https://community.bigbeartechworld.com/t/casaos-user-management/2225 +# Modified version by bigbeartechworld: Added UI implementation and adapted user_management.py +# to integrate with the UI while maintaining core functionality from the original. + +import sqlite3 +import os +import shutil +import subprocess +import hashlib +import getpass +from datetime import datetime + +# Constants +DB_PATH = "/var/lib/casaos/db/user.db" +BACKUP_PATH = "/var/lib/casaos/db/user_backup.db" +SERVICE_NAME = "casaos-user-service.service" + +# Helper Functions +def check_casaos_installation(): + if not os.path.exists(DB_PATH): + return False + service_status = subprocess.run( + ["systemctl", "status", SERVICE_NAME], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return service_status.returncode == 0 + +def backup_database(): + try: + shutil.copy(DB_PATH, BACKUP_PATH) + return True + except Exception: + return False + +def hash_password(password): + """Hash the password using MD5.""" + return hashlib.md5(password.encode()).hexdigest() + +def connect_db(): + """Connect to the SQLite database.""" + try: + return sqlite3.connect(DB_PATH) + except sqlite3.Error as e: + print(f"Database connection error: {e}") + exit(1) + +def manage_service(action): + """Start, stop, or restart the CasaOS user service on host OS.""" + try: + result = subprocess.run( + ["systemctl", "--system", action, SERVICE_NAME], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True + ) + return True + except subprocess.CalledProcessError as e: + print(f"Service {action} failed: {e.stderr}") + return False + +def list_users(return_data=False): + """List all users in the database.""" + conn = connect_db() + cursor = conn.cursor() + try: + cursor.execute("SELECT id, username, role FROM o_users") + users = cursor.fetchall() + if return_data: + return users + else: + if users: + print("\nUsers in the database:") + for user in users: + print(f"ID: {user[0]}, Username: {user[1]}, Role: {user[2]}") + else: + print("\nNo users found.") + except sqlite3.Error as e: + print(f"Error fetching users: {e}") + return [] if return_data else None + finally: + conn.close() + +def add_user(username, password): + """Add a new user to the database.""" + if not username or not password: + print("Username and password cannot be empty.") + return False + + conn = connect_db() + cursor = conn.cursor() + try: + # Check for duplicate username + cursor.execute("SELECT 1 FROM o_users WHERE username = ?", (username,)) + if cursor.fetchone(): + print(f"Error: A user with the username '{username}' already exists.") + return False + + hashed_password = hash_password(password) + created_at = datetime.now().isoformat() + + # Insert the user into the database + cursor.execute( + "INSERT INTO o_users (username, password, role, created_at) VALUES (?, ?, ?, ?)", + (username, hashed_password, "admin", created_at), + ) + conn.commit() + print(f"User '{username}' added successfully.") + return True + except sqlite3.Error as e: + print(f"Error adding user: {e}") + return False + finally: + conn.close() + +def edit_password(user_id, new_password): + if not user_id or not new_password: + return False + + hashed_password = hash_password(new_password) + updated_at = datetime.now().isoformat() + + conn = connect_db() + cursor = conn.cursor() + try: + cursor.execute( + "UPDATE o_users SET password = ?, updated_at = ? WHERE id = ?", + (hashed_password, updated_at, user_id), + ) + if cursor.rowcount > 0: + conn.commit() + return True + return False + except sqlite3.Error: + return False + finally: + conn.close() + +def remove_user(user_id): + if not user_id: + return False + + conn = connect_db() + cursor = conn.cursor() + try: + cursor.execute("DELETE FROM o_users WHERE id = ?", (user_id,)) + if cursor.rowcount > 0: + conn.commit() + return True + return False + except sqlite3.Error: + return False + finally: + conn.close() + +def reset_database(): + """Reset the database by deleting the user.db file.""" + try: + if os.path.exists(DB_PATH): + os.remove(DB_PATH) + if not manage_service("restart"): + print("Warning: Database reset successful but service restart failed") + return True + return False + except Exception as e: + print(f"Reset failed: {str(e)}") + return False + diff --git a/big-bear-casaos-user-management/docker-compose.yml b/big-bear-casaos-user-management/docker-compose.yml new file mode 100644 index 0000000..03153cb --- /dev/null +++ b/big-bear-casaos-user-management/docker-compose.yml @@ -0,0 +1,45 @@ +services: + big-bear-casaos-user-management: + # The service name for the user management system + image: bigbeartechworld/big-bear-casaos-user-management:0.0.1 + # The Docker image to use for this service, with the specified version tag + container_name: big-bear-casaos-user-management + # A custom name for the container, making it easier to identify + + ports: + # Exposing port 5000 on the host and mapping it to port 5000 in the container + - "5000:5000" + + volumes: + # Mounting the cgroup filesystem in read-only mode for system resource monitoring + - /sys/fs/cgroup:/sys/fs/cgroup:ro + # Persisting CasaOS database files to retain data across container restarts + - /var/lib/casaos/db:/var/lib/casaos/db + # Mounting systemd's runtime directory for integration with the host system + - /run/systemd/system:/run/systemd/system + # Providing access to the D-Bus system bus for communication with host services + - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket + + # Ensures the container restarts automatically unless explicitly stopped + restart: unless-stopped + + environment: + # Sets the Flask application entry point + - FLASK_APP=app.py + # Configures Flask to run in production mode + - FLASK_ENV=production + # Specifies the admin username for the service + - ADMIN_USERNAME=casaos + # Specifies the admin password for the service + - ADMIN_PASSWORD=casaos + + # Grants the container extended privileges, necessary for some operations + privileged: true + + cap_add: + # Adds the SYS_ADMIN capability to the container for advanced administrative actions + - SYS_ADMIN + + security_opt: + # Disables the default seccomp security profile to allow unrestricted system calls + - seccomp:unconfined \ No newline at end of file