From 9c9cfd8b0a41c59f0be6e7e76182304241c18263 Mon Sep 17 00:00:00 2001 From: Brandon4466 Date: Mon, 28 Jul 2025 17:01:59 -0700 Subject: [PATCH] admin panel, password protection --- README.md | 155 ++++++++++++++++++++++++++++++++++ admin.php | 216 +++++++++++++++++++++++++++++++++++++----------- index.html | 182 +++++++++++++++++++++++----------------- landing.html | 61 ++++++++++++++ list-images.php | 48 +++++++++-- passwords.json | 2 +- 6 files changed, 534 insertions(+), 130 deletions(-) create mode 100644 README.md create mode 100644 landing.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..1391228 --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +

+ galpal logo
+ galpal +

+ +

galpal gives you and your clients a quick, easy, and beautiful website to access and download photographs. Has all the features of any professional image hosting software, easy to setup, made specifically for photographers.

+ + + +galpal ui
+*[Light version too!](.github/images/front-light.png)* + +## Documentation + +Full documentation is available at [https://github.com/Brandon4466/galpal](https://github.com/Brandon4466/galpal) + +## What Is galpal? + +You've already done the hard work. The photography, the editing, everything. Now you're ready to distribute your photos... But how? This is where galpal steps in! No more hassel of how you're going to get the photos to the client or who you're going to have to explain how to use dropbox to. galpal gives a nice and easy to use interface, complete with password protection, to distribute photos to your clients. + +## Key Features + +- Album password protection +- Download all photos or only selected +- Supports multiple albums +- Easily configurable +- Supports multiple platforms (Linux, Windows, macOS) on many architectures (x86, ARM) +- Container support (Docker, Kubernetes) + +## Installation + +For complete installation instructions, visit our [Installation Guide](https://https://github.com/Brandon4466/galpal/installation/linux). Guides available for Windows, Linux, Docker, and more. + +### Linux (One-Click Installer) + +#### Installation Script + +```bash +wget https://github.com/Brandon4466/galpal/install && bash install +``` + +### Docker Compose + +Create `docker-compose.yml` and add the following. If you have an existing setup change to fit that. + +```yml +version: "1.0" + +services: + galpal: + container_name: galpal + image: github.com/Brandon4466/galpal/galpal:latest + restart: unless-stopped + environment: + - TZ=${TZ} + user: 1000:1000 + volumes: + - ${BASE_DOCKER_DATA_PATH}/galpal/config:/config + ports: + - 7474:7474 +``` + +Then start with: + +```bash +docker compose up -d +``` + +### Windows + +Download the latest Windows installer from [here](https://github.com/Brandon4466/galpal/installation/windows). + +### MacOS + +#### One-Click Installer + +Also compatible with macOS. + +#### App Installer + +Download the latest macOS .dmg from [here](https://github.com/Brandon4466/galpal/installation/macos). + +#### Install with Homebrew + +Install Homebrew + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +Install galpal + +```bash +brew install galpal +``` + +Run + +```bash +brew services start galpal +``` + +#### Systemd (Recommended) + +On Linux-based systems, it is recommended to run galpal as a sort of service with auto-restarting capabilities, in +order to account for potential downtime. The most common way is to do it via systemd. This is setup automatically when using the installation script. + +If not using the installation script, you will need to create a service file in `/etc/systemd/system/` called `galpal.service`. + +```bash +touch /etc/systemd/system/galpal@.service +``` + +Then place the following content inside the file (e.g. via nano/vim/ed): + +```systemd title="/etc/systemd/system/galpal@.service" +[Unit] +Description=galpal service for %i +After=syslog.target network-online.target + +[Service] +Type=simple +User=%i +Group=%i +ExecStart=/usr/bin/galpal --config=/home/%i/.config/galpal/ + +[Install] +WantedBy=multi-user.target +``` + +Start the service. Enable will make it startup on reboot. + +```bash +systemctl enable -q --now --user galpal@$USER +``` + +By default, the configuration is set to listen on `127.0.0.1`. While galpal works fine as is exposed to the internet, +it is recommended to use a reverse proxy +like [nginx](https://github.com/Brandon4466/galpal/installation/linux#nginx), [caddy](https://github.com/Brandon4466/galpal/installation/linux#caddy) +or [traefik](https://github.com/Brandon4466/galpal/installation/docker#traefik). + +If you are not running a reverse proxy change `host` in the `config.toml` to `0.0.0.0`. + +## Community + +We have a great community on [Discord](https://github.com/Brandon4466/galpal)! Connect with other galpal users, get notified of new updates, and ask questions! + +## License + +galpal © 2025 by Brandon Brunson is licensed under CC BY-NC-SA 4.0 + +- **Run:** You can run galpal in any noncommercial environment. +- **Study and Modify:** Access to the source code allows you to study and modify galpal to suit your needs. + +Copyright 2025 \ No newline at end of file diff --git a/admin.php b/admin.php index 0115dd4..cb1cdd6 100644 --- a/admin.php +++ b/admin.php @@ -1,17 +1,40 @@ /password.txt // Simple admin login (hardcoded for demo) $adminPassword = 'admin123'; $loggedIn = isset($_SESSION['admin']) && $_SESSION['admin'] === true; +// AJAX endpoint to get album info +if (isset($_GET['get_album_info']) && isset($_GET['album'])) { + $album = $_GET['album']; + $pwFile = __DIR__ . '/images/' . $album . '/info.yaml'; + $pw = ''; + $title = ''; + if (file_exists($pwFile)) { + if (function_exists('yaml_parse_file')) { + $yaml = yaml_parse_file($pwFile); + $pw = isset($yaml['password']) ? $yaml['password'] : ''; + $title = isset($yaml['title']) ? $yaml['title'] : ''; + } else { + $lines = file($pwFile); + foreach ($lines as $line) { + if (preg_match('/^password:\s*(.+)$/', trim($line), $m)) { + $pw = $m[1]; + } + if (preg_match('/^title:\s*(.+)$/', trim($line), $m)) { + $title = $m[1]; + } + } + } + } + header('Content-Type: application/json'); + echo json_encode(['password' => $pw, 'title' => $title]); + exit; +} + if (isset($_POST['admin_login'])) { if ($_POST['admin_password'] === $adminPassword) { $_SESSION['admin'] = true; @@ -24,10 +47,17 @@ if (isset($_POST['admin_login'])) { if ($loggedIn && isset($_POST['set_album_password'])) { $album = $_POST['album_name']; $pw = $_POST['album_password']; + $title = isset($_POST['album_title']) ? $_POST['album_title'] : ''; if ($album && $pw !== null) { - $passwords[$album] = $pw; - file_put_contents($passwordFile, json_encode($passwords)); - $success = "Password set for album '$album'."; + $pwFile = __DIR__ . '/images/' . $album . '/info.yaml'; + $yamlArr = ['password' => $pw, 'title' => $title]; + $yamlContent = "password: " . $pw . "\ntitle: " . $title . "\n"; + if (function_exists('yaml_emit_file')) { + yaml_emit_file($pwFile, $yamlArr); + } else { + file_put_contents($pwFile, $yamlContent); + } + $success = "Password and title set for album '$album'."; } } @@ -53,49 +83,143 @@ if (is_dir($dir)) { Admin - Album Passwords -
-

Administrator Mode

- -
- - - - $error
"; ?> - - -
- - - - - -
- $success"; ?> -
- -
-

Current Album Passwords:

- + + + +
+

Administrator Mode

+ +
+ + + + $error
"; ?> + + +
+ + + + + + +
+ + +
+ +
+ + $success"; ?> +
+ +
+ + + diff --git a/index.html b/index.html index 2cbf316..e7bac3c 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,12 @@ - Album Viewer + Photos -

Album Viewer

+

Photos

- Download All - + Download All + + diff --git a/landing.html b/landing.html new file mode 100644 index 0000000..2885733 --- /dev/null +++ b/landing.html @@ -0,0 +1,61 @@ + + + + + + Photos + + + +
+

Uh oh! Let's try again.

+

That didn't work. Try clicking on the link again.

+
+ + + + diff --git a/list-images.php b/list-images.php index df264b2..a9ee31d 100644 --- a/list-images.php +++ b/list-images.php @@ -4,8 +4,7 @@ $dir = __DIR__ . '/images/'; $extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']; $albums = []; -$passwordFile = __DIR__ . '/passwords.json'; -$passwords = file_exists($passwordFile) ? json_decode(file_get_contents($passwordFile), true) : []; +// Passwords now stored per album in images//password.txt // If album is requested, check password if (isset($_GET['album'])) { @@ -16,8 +15,29 @@ if (isset($_GET['album'])) { echo json_encode(['error' => 'Album not found']); exit; } - if (isset($passwords[$album]) && $passwords[$album] !== '') { - if ($pw !== $passwords[$album]) { + $pwFile = $dir . $album . '/info.yaml'; + $albumPassword = ''; + $albumTitle = ''; + if (file_exists($pwFile)) { + if (function_exists('yaml_parse_file')) { + $yaml = yaml_parse_file($pwFile); + $albumPassword = isset($yaml['password']) ? $yaml['password'] : ''; + $albumTitle = isset($yaml['title']) ? $yaml['title'] : ''; + } else { + // Fallback: parse manually + $lines = file($pwFile); + foreach ($lines as $line) { + if (preg_match('/^password:\s*(.+)$/', trim($line), $m)) { + $albumPassword = $m[1]; + } + if (preg_match('/^title:\s*(.+)$/', trim($line), $m)) { + $albumTitle = $m[1]; + } + } + } + } + if ($albumPassword !== '') { + if ($pw !== $albumPassword) { http_response_code(403); echo json_encode(['error' => 'Password required']); exit; @@ -39,7 +59,7 @@ if (isset($_GET['album'])) { } } header('Content-Type: application/json'); - echo json_encode($albumImages); + echo json_encode(['title' => $albumTitle, 'images' => $albumImages]); exit; } @@ -47,8 +67,24 @@ if (isset($_GET['album'])) { if (is_dir($dir)) { foreach (scandir($dir) as $album) { if ($album === '.' || $album === '..' || !is_dir($dir . $album)) continue; + $pwFile = $dir . $album . '/info.yaml'; + $protected = false; + if (file_exists($pwFile)) { + if (function_exists('yaml_parse_file')) { + $yaml = yaml_parse_file($pwFile); + $protected = isset($yaml['password']) && $yaml['password'] !== ''; + } else { + $lines = file($pwFile); + foreach ($lines as $line) { + if (preg_match('/^password:\s*(.+)$/', trim($line), $m)) { + if ($m[1] !== '') $protected = true; + break; + } + } + } + } $albums[$album] = [ - 'protected' => isset($passwords[$album]) && $passwords[$album] !== '', + 'protected' => $protected, ]; } } diff --git a/passwords.json b/passwords.json index 0967ef4..a798204 100644 --- a/passwords.json +++ b/passwords.json @@ -1 +1 @@ -{} +{"psu2025":"111"} \ No newline at end of file