first upload

This commit is contained in:
2025-11-20 16:05:18 +08:00
commit caffb757c8
7 changed files with 1159 additions and 0 deletions

76
README.md Normal file
View File

@@ -0,0 +1,76 @@
# 🗂️ Trashbox - Минималистичный файловый сервер
### Установка зависимостей
``` bash
sudo apt update
sudo apt install nginx busybox gcc
```
### Настройка структуры
```bash
mkdir -p /home/romkazvo/www/cgi-bin
cd /home/romkazvo/www/cgi-bin
```
### Компиляция CGI приложений
```bash
gcc -o index.cgi index.c
gcc -o style.cgi style.c
gcc -o status.cgi status.c
chmod +x *.cgi
```
### Конфигурация Nginx
Создайте файл конфигурации `/etc/nginx/sites-available/trashbox`:
server {
listen 443 ssl http2;
server_name home.mashup.su www.home.mashup.su;
ssl_certificate /etc/letsencrypt/live/www.home.mashup.su/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.home.mashup.su/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
location = /gachi {
proxy_pass http://127.0.0.1:8001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
add_header X-Stream-Name "Gachi Stream" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS, HEAD" always;
add_header Access-Control-Allow-Headers "Range, Accept-Encoding" always;
}
location /cgi-bin/ {
proxy_pass http://127.0.0.1:8050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:8050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name home.mashup.su www.home.mashup.su;
return 301 https://$server_name$request_uri;
}
### Запуск BusyBox
```bash
busybox httpd -p 127.0.0.1:8050 -h /home/romkazvo/www
```

50
busybox_nginx_reverse.txt Executable file
View File

@@ -0,0 +1,50 @@
server {
listen 443 ssl http2;
server_name home.mashup.su www.home.mashup.su;
ssl_certificate /etc/letsencrypt/live/www.home.mashup.su/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.home.mashup.su/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
# Прокси для MPD потока /gachi - ИСПРАВЛЕННЫЙ ВАРИАНТ
location = /gachi {
proxy_pass http://127.0.0.1:8001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
add_header X-Stream-Name "Gachi Stream" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS, HEAD" always;
add_header Access-Control-Allow-Headers "Range, Accept-Encoding" always;
}
location /cgi-bin/ {
proxy_pass http://127.0.0.1:8050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:8050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name home.mashup.su www.home.mashup.su;
return 301 https://$server_name$request_uri;
}

436
index.c Executable file
View File

@@ -0,0 +1,436 @@
// /home/romkazvo/www/cgi-bin/index.c
// Основной файловый сервер
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <locale.h>
#define MAX_ENTRIES 1000
#define TEMPLATE_PATH "/home/romkazvo/www/cgi-bin/template.html"
#define OUTPUT_BUFFER_SIZE 65536
typedef struct {
char name[256];
int is_dir;
long size;
char icon[8];
char encoded_path[1024];
char file_url[1024];
} entry_t;
typedef struct {
char *data;
size_t size;
size_t capacity;
} buffer_t;
void buffer_init(buffer_t *buf) {
buf->capacity = OUTPUT_BUFFER_SIZE;
buf->data = malloc(buf->capacity);
buf->size = 0;
}
void buffer_append(buffer_t *buf, const char *str) {
size_t len = strlen(str);
if (buf->size + len >= buf->capacity) {
buf->capacity *= 2;
buf->data = realloc(buf->data, buf->capacity);
}
memcpy(buf->data + buf->size, str, len);
buf->size += len;
}
void buffer_append_size(buffer_t *buf, const char *str, size_t len) {
if (buf->size + len >= buf->capacity) {
buf->capacity *= 2;
buf->data = realloc(buf->data, buf->capacity);
}
memcpy(buf->data + buf->size, str, len);
buf->size += len;
}
void buffer_free(buffer_t *buf) {
free(buf->data);
}
const char* get_file_icon(const char* filename) {
const char *ext = strrchr(filename, '.');
if (!ext) return "📄";
ext++;
if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 ||
strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0)
return "🖼️";
if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 ||
strcasecmp(ext, "flac") == 0 || strcasecmp(ext, "ogg") == 0)
return "🎵";
if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "avi") == 0 ||
strcasecmp(ext, "mkv") == 0 || strcasecmp(ext, "mov") == 0)
return "🎬";
if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "rar") == 0 ||
strcasecmp(ext, "7z") == 0 || strcasecmp(ext, "tar") == 0 ||
strcasecmp(ext, "gz") == 0)
return "📦";
if (strcasecmp(ext, "pdf") == 0) return "📕";
if (strcasecmp(ext, "doc") == 0 || strcasecmp(ext, "docx") == 0) return "📘";
if (strcasecmp(ext, "xls") == 0 || strcasecmp(ext, "xlsx") == 0) return "📗";
if (strcasecmp(ext, "txt") == 0) return "📝";
return "📄";
}
int compare_entries(const void *a, const void *b) {
const entry_t *entryA = (const entry_t *)a;
const entry_t *entryB = (const entry_t *)b;
if (entryA->is_dir && !entryB->is_dir) return -1;
if (!entryA->is_dir && entryB->is_dir) return 1;
return strcasecmp(entryA->name, entryB->name);
}
void format_size(long size, char* buffer) {
if (size < 1024) {
snprintf(buffer, 32, "%ld B", size);
} else if (size < 1024 * 1024) {
snprintf(buffer, 32, "%.1f KB", size / 1024.0);
} else if (size < 1024 * 1024 * 1024) {
snprintf(buffer, 32, "%.1f MB", size / (1024.0 * 1024.0));
} else {
snprintf(buffer, 32, "%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
}
}
void url_encode(const char *src, char *dst, size_t dst_size) {
static const char *hex = "0123456789ABCDEF";
char *p = dst;
size_t i = 0;
while (*src && i < dst_size - 3) {
unsigned char c = (unsigned char)*src;
if ((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
strchr("-_.~", c)) {
*p++ = c;
i++;
}
else if (c == ' ') {
*p++ = '%';
*p++ = '2';
*p++ = '0';
i += 3;
}
else {
*p++ = '%';
*p++ = hex[(c >> 4) & 0xF];
*p++ = hex[c & 0xF];
i += 3;
}
src++;
}
*p = '\0';
}
void url_decode_enhanced(const char *src, char *dst, size_t dst_size) {
char *p = dst;
size_t decoded_len = 0;
while (*src && decoded_len < dst_size - 1) {
if (*src == '%') {
if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) {
char hex[3] = {src[1], src[2], '\0'};
unsigned char c = (unsigned char)strtol(hex, NULL, 16);
if (c >= 0x80) {
*p++ = c;
decoded_len++;
} else if (c >= 0x20 || c == 0x0A || c == 0x0D) {
*p++ = c;
decoded_len++;
} else {
*p++ = '_';
decoded_len++;
}
src += 3;
} else {
*p++ = *src++;
decoded_len++;
}
} else if (*src == '+') {
*p++ = ' ';
decoded_len++;
src++;
} else {
*p++ = *src++;
decoded_len++;
}
}
*p = '\0';
}
void safe_path_join(char *result, size_t result_size, const char *base, const char *path) {
snprintf(result, result_size, "%s/%s", base, path);
}
void print_breadcrumb(buffer_t *buf, const char *display_path) {
buffer_append(buf, "<div class=\"breadcrumb\">\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\">🏠 Главная</a>");
if (display_path && display_path[0]) {
char temp_path[1024] = "";
char encoded[2048];
char *path_copy = strdup(display_path);
char *token = strtok(path_copy, "/");
while (token) {
buffer_append(buf, " / ");
if (temp_path[0]) strcat(temp_path, "/");
strcat(temp_path, token);
url_encode(temp_path, encoded, sizeof(encoded));
char link[4096];
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, token);
buffer_append(buf, link);
token = strtok(NULL, "/");
}
free(path_copy);
}
buffer_append(buf, "\n</div>\n");
}
void print_content(buffer_t *buf, const char *base_path, const char *display_path) {
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char full_path[1024];
entry_t entries[MAX_ENTRIES];
int entry_count = 0;
int dir_count = 0, file_count = 0;
long long total_size = 0;
dir = opendir(base_path);
if (!dir) {
char alt_path[1024];
snprintf(alt_path, sizeof(alt_path), "/home/romkazvo/www");
if (display_path[0]) {
char *encoded = strdup(display_path);
url_decode_enhanced(encoded, alt_path + strlen(alt_path),
sizeof(alt_path) - strlen(alt_path));
free(encoded);
}
dir = opendir(alt_path);
if (!dir) {
buffer_append(buf, "<div class=\"empty-state\">\n");
buffer_append(buf, " <div class=\"icon\">📁</div>\n");
buffer_append(buf, " <h3>Ошибка открытия директории</h3>\n");
char error_msg[512];
snprintf(error_msg, sizeof(error_msg), " <p>Путь: %s</p>\n", base_path);
buffer_append(buf, error_msg);
snprintf(error_msg, sizeof(error_msg), " <p>Ошибка: %s</p>\n", strerror(errno));
buffer_append(buf, error_msg);
buffer_append(buf, " <p><a href=\"/cgi-bin/index.cgi\">← Вернуться на главную</a></p>\n");
buffer_append(buf, "</div>\n");
return;
}
}
while ((entry = readdir(dir)) != NULL && entry_count < MAX_ENTRIES) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
if (strcmp(entry->d_name, "cgi-bin") == 0)
continue;
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
if (stat(full_path, &file_stat) == 0) {
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
if (S_ISDIR(file_stat.st_mode)) {
entries[entry_count].is_dir = 1;
entries[entry_count].size = 0;
strcpy(entries[entry_count].icon, "📁");
char new_path[2048];
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path,
display_path[0] ? "/" : "", entries[entry_count].name);
url_encode(new_path, entries[entry_count].encoded_path, sizeof(entries[0].encoded_path));
dir_count++;
} else if (S_ISREG(file_stat.st_mode)) {
entries[entry_count].is_dir = 0;
entries[entry_count].size = file_stat.st_size;
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url),
"%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
file_count++;
total_size += file_stat.st_size;
} else {
continue;
}
entry_count++;
}
}
closedir(dir);
if (entry_count == 0) {
buffer_append(buf, "<div class=\"empty-state\">\n");
buffer_append(buf, " <div class=\"icon\">📄</div>\n");
buffer_append(buf, " <h3>Здесь пусто</h3>\n");
buffer_append(buf, " <p>В этой директории нет файлов или папок</p>\n");
buffer_append(buf, "</div>\n");
return;
}
qsort(entries, entry_count, sizeof(entry_t), compare_entries);
buffer_append(buf, "<ul>\n");
if (dir_count > 0) {
char section_title[128];
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", dir_count);
buffer_append(buf, section_title);
for (int i = 0; i < entry_count; i++) {
if (entries[i].is_dir) {
buffer_append(buf, " <li>\n");
char dir_link[512];
snprintf(dir_link, sizeof(dir_link),
" <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=%s\">\n",
entries[i].encoded_path);
buffer_append(buf, dir_link);
buffer_append(buf, " <span class=\"file-icon\">");
buffer_append(buf, entries[i].icon);
buffer_append(buf, "</span>\n");
buffer_append(buf, " <span class=\"file-name\">");
buffer_append(buf, entries[i].name);
buffer_append(buf, "</span>\n");
buffer_append(buf, " </a>\n");
buffer_append(buf, " </li>\n");
}
}
}
if (file_count > 0) {
char section_title[128];
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count);
buffer_append(buf, section_title);
for (int i = 0; i < entry_count; i++) {
if (!entries[i].is_dir) {
char size_str[32];
format_size(entries[i].size, size_str);
buffer_append(buf, " <li>\n");
char file_link[512];
snprintf(file_link, sizeof(file_link),
" <a class=\"file-link\" href=\"/%s\" download>\n",
entries[i].file_url);
buffer_append(buf, file_link);
buffer_append(buf, " <span class=\"file-icon\">");
buffer_append(buf, entries[i].icon);
buffer_append(buf, "</span>\n");
buffer_append(buf, " <span class=\"file-name\">");
buffer_append(buf, entries[i].name);
buffer_append(buf, "</span>\n");
buffer_append(buf, " </a>\n");
buffer_append(buf, " <span class=\"size\">");
buffer_append(buf, size_str);
buffer_append(buf, "</span>\n");
buffer_append(buf, " </li>\n");
}
}
}
buffer_append(buf, "</ul>\n");
char total_size_str[32];
format_size(total_size, total_size_str);
char stats[256];
snprintf(stats, sizeof(stats),
"<div class=\"stats\">\n Папки: %d | Файлы: %d | Общий размер: %s\n</div>\n",
dir_count, file_count, total_size_str);
buffer_append(buf, stats);
}
void print_template(const char *base_path, const char *display_path) {
FILE *file = fopen(TEMPLATE_PATH, "r");
if (!file) {
printf("Error: Cannot read template\n");
return;
}
buffer_t output;
buffer_init(&output);
char line[4096];
while (fgets(line, sizeof(line), file)) {
if (strstr(line, "<!--BREADCRUMB-->")) {
print_breadcrumb(&output, display_path);
} else if (strstr(line, "<!--CONTENT-->")) {
print_content(&output, base_path, display_path);
} else {
buffer_append(&output, line);
}
}
fclose(file);
fwrite(output.data, 1, output.size, stdout);
buffer_free(&output);
}
int main() {
setlocale(LC_ALL, "en_US.UTF-8");
setlocale(LC_CTYPE, "en_US.UTF-8");
printf("Content-type: text/html; charset=utf-8\n\n");
char base_path[1024] = "/home/romkazvo/www";
char display_path[1024] = "";
char safe_display_path[1024] = "";
char *query_string = getenv("QUERY_STRING");
if (query_string) {
char *path_start = strstr(query_string, "path=");
if (path_start) {
path_start += 5;
char *path_end = strchr(path_start, '&');
int path_len = path_end ? path_end - path_start : strlen(path_start);
if (path_len > 0 && path_len < sizeof(display_path) - 1) {
char encoded_path[1024];
strncpy(encoded_path, path_start, path_len);
encoded_path[path_len] = '\0';
url_decode_enhanced(encoded_path, display_path, sizeof(display_path));
strncpy(safe_display_path, display_path, sizeof(safe_display_path) - 1);
safe_display_path[sizeof(safe_display_path) - 1] = '\0';
safe_path_join(base_path, sizeof(base_path), "/home/romkazvo/www", safe_display_path);
}
}
}
print_template(base_path, display_path);
return 0;
}

56
status.c Executable file
View File

@@ -0,0 +1,56 @@
// /home/romkazvo/www/cgi-bin/status.c
// Отрисовка статистики
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
long get_nginx_memory() {
long total_kb = 0;
char command[] = "ps -o rss= -C nginx 2>/dev/null";
FILE *ps = popen(command, "r");
if (!ps) return 0;
char line[64];
while (fgets(line, sizeof(line), ps)) {
long kb;
if (sscanf(line, "%ld", &kb) == 1) {
total_kb += kb;
}
}
pclose(ps);
return total_kb;
}
int get_process_memory(const char *process_name, long *memory_kb) {
char command[256];
snprintf(command, sizeof(command), "ps -o rss= -C %s 2>/dev/null | head -1", process_name);
FILE *ps = popen(command, "r");
if (!ps) return 0;
int result = fscanf(ps, "%ld", memory_kb);
pclose(ps);
return result == 1;
}
int main() {
printf("Content-type: text/plain; charset=utf-8\n\n");
// Получаем память процессов
long busybox_kb = 0, nginx_kb = 0;
get_process_memory("busybox", &busybox_kb);
nginx_kb = get_nginx_memory();
// Выводим в одну строку
printf("BusyBox: %ld KB | Nginx: %ld MB | Всего: %.1f MB",
busybox_kb,
nginx_kb / 1024,
(busybox_kb + nginx_kb) / 1024.0);
return 0;
}

37
style.c Executable file
View File

@@ -0,0 +1,37 @@
// Подгрузка CSS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
int main() {
char css_path[1024] = "/home/romkazvo/www/cgi-bin/style.css";
struct stat st;
if (stat(css_path, &st) != 0) {
printf("Status: 404 Not Found\n");
printf("Content-type: text/plain\n\n");
printf("CSS file not found\n");
return 1;
}
printf("Content-type: text/css\n");
printf("Cache-Control: public, max-age=3600\n\n");
FILE *css_file = fopen(css_path, "r");
if (!css_file) {
printf("Status: 500 Internal Server Error\n");
printf("Content-type: text/plain\n\n");
printf("Cannot open CSS file\n");
return 1;
}
char buffer[4096];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), css_file)) > 0) {
fwrite(buffer, 1, bytes_read, stdout);
}
fclose(css_file);
return 0;
}

371
style.css Executable file
View File

@@ -0,0 +1,371 @@
:root {
--bg-primary: #f8f9fa;
--bg-secondary: #ffffff;
--bg-header: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--text-primary: #333333;
--text-secondary: #5f6368;
--text-muted: #6c757d;
--border-color: #e9ecef;
--border-light: #f1f3f4;
--accent-blue: #1a73e8;
--accent-green: #188038;
--hover-bg: #f8f9fa;
--shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.dark-theme {
--bg-primary: #121212;
--bg-secondary: #1e1e1e;
--bg-header: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--text-muted: #888888;
--border-color: #2d3748;
--border-light: #2d3748;
--accent-blue: #63b3ed;
--accent-green: #68d391;
--hover-bg: #2d3748;
--shadow: 0 2px 4px rgba(0,0,0,0.3);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
transition: none;
}
body.loaded * {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
.container {
max-width: 100%;
margin: 0 auto;
background: var(--bg-secondary);
min-width: 320px;
min-height: 100vh;
box-shadow: var(--shadow);
}
@media (min-width: 768px) {
.container {
max-width: 1000px;
margin: 10px auto;
min-height: auto;
border-radius: 8px;
overflow: hidden;
}
}
.theme-toggle {
position: fixed;
top: 10px;
right: 10px;
background: rgba(255,255,255,0.2);
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 1.3rem;
cursor: pointer;
z-index: 1000;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.dark-theme .theme-toggle {
background: rgba(0,0,0,0.2);
color: var(--text-primary);
}
.scroll-top {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: var(--accent-blue);
color: white;
border: none;
border-radius: 50%;
font-size: 1.3rem;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
display: flex;
align-items: center;
justify-content: center;
}
.scroll-top.visible {
opacity: 1;
visibility: visible;
}
.scroll-top:hover {
background: var(--accent-green);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}
.theme-toggle:hover {
transform: scale(1.1);
background: rgba(255,255,255,0.3);
}
.dark-theme .theme-toggle:hover {
background: rgba(0,0,0,0.3);
}
.header {
background: var(--bg-header);
color: white;
padding: 1.5rem 1rem;
text-align: center;
position: relative;
}
h1 {
font-size: 1.8rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.content {
padding: 1rem;
min-width: 0;
}
.breadcrumb {
background: var(--bg-primary);
padding: 1rem;
margin: 0 0 1rem 0;
border-bottom: 1px solid var(--border-color);
font-size: 0.9rem;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.breadcrumb a {
color: var(--accent-blue);
text-decoration: none;
font-weight: 500;
display: inline-block;
}
.breadcrumb a:hover {
text-decoration: underline;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
padding: 1rem 0.5rem;
border-bottom: 1px solid var(--border-light);
display: flex;
align-items: center;
flex-wrap: wrap;
}
li:last-child { border-bottom: none; }
li:hover {
background-color: var(--hover-bg);
}
a {
text-decoration: none;
flex-grow: 1;
display: flex;
align-items: center;
min-width: 0;
padding-right: 0.5rem;
}
.file-link {
color: var(--accent-blue);
font-weight: 500;
}
.dir-link {
color: var(--accent-green);
font-weight: 600;
}
.size {
color: var(--text-secondary);
font-size: 0.85rem;
margin-left: auto;
padding-left: 0.5rem;
flex-shrink: 0;
white-space: nowrap;
}
.file-icon {
margin-right: 1rem;
font-size: 1.4rem;
flex-shrink: 0;
width: 24px;
text-align: center;
}
.file-name {
flex-grow: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.95rem;
}
.section-title {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 600;
margin: 1.5rem 0 0.5rem 0;
padding: 0.75rem 0.5rem;
border-bottom: 2px solid var(--border-light);
text-transform: uppercase;
letter-spacing: 0.5px;
background: var(--bg-primary);
}
.stats {
text-align: center;
color: var(--text-secondary);
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color);
font-size: 0.9rem;
}
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
}
.empty-state .icon {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.footer {
background: var(--bg-primary);
border-top: 1px solid var(--border-color);
padding: 1rem;
text-align: center;
color: var(--text-secondary);
font-size: 0.9rem;
}
.footer-content {
max-width: 1000px;
margin: 0 auto;
text-align: center;
}
.footer-content span,
.footer-content a {
display: inline;
vertical-align: baseline;
}
.footer-link {
color: var(--accent-blue);
text-decoration: none;
font-weight: 500;
transition: opacity 0.2s ease;
margin-left: 0.3rem;
}
.footer-link:hover {
opacity: 0.8;
text-decoration: underline;
}
@media (max-width: 480px) {
.theme-toggle {
top: 5px;
right: 5px;
width: 45px;
height: 45px;
font-size: 1.2rem;
}
.scroll-top {
bottom: 15px;
right: 15px;
width: 45px;
height: 45px;
font-size: 1.2rem;
}
.header {
padding: 1rem 0.5rem;
}
h1 {
font-size: 1.5rem;
padding: 0 0.5rem;
}
.content {
padding: 0.75rem;
}
.breadcrumb {
padding: 0.75rem;
font-size: 0.85rem;
}
li {
padding: 0.75rem 0.25rem;
}
.size {
font-size: 0.8rem;
padding-left: 0.25rem;
}
.file-icon {
margin-right: 0.75rem;
font-size: 1.2rem;
width: 20px;
}
.file-name {
font-size: 0.9rem;
}
.section-title {
font-size: 0.85rem;
padding: 0.5rem;
margin: 1rem 0 0.25rem 0;
}
.footer {
padding: 0.75rem;
font-size: 0.85rem;
}
}
@media (max-width: 360px) {
.container {
min-width: 100%;
margin: 0;
}
.content {
padding: 0.5rem;
}
.file-name {
font-size: 0.85rem;
}
.size {
font-size: 0.75rem;
}
}

133
template.html Executable file
View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
<title>Trashbox</title>
<link rel="shortcut icon" href="/logo.png" type="image/png">
<link rel="stylesheet" href="/cgi-bin/style.cgi">
<meta property="og:image" content="https://home.mashup.su/og.png" />
<meta property="og:image:width" content="600" />
<meta property="og:image:height" content="315" />
<meta property="og:locale" content="ru_RU" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Файлопомойка от RomkaZVO" />
<meta property="og:description" content="Моя личная помоечка" />
<meta property="og:url" content="https://home.mashup.su/" />
<meta property="og:site_name" content="Помойка от RomkaZVO" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Файлопомойка от RomkaZVO" />
<meta name="twitter:description" content="Моя личная помоечка" />
<meta name="twitter:image" content="https://home.mashup.su/og.png" />
<meta name="description" content="Моя личная помоечка" />
<meta name="keywords" content="файлы, обмен, скачать, загрузить" />
<script>
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
(function() {
const savedTheme = getCookie('theme');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark-theme');
}
})();
</script>
</head>
<body>
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<div class="header">
<h1>Trashbox</h1>
<div style="margin-top: 0.5rem; opacity: 0.9; font-size: 1.1rem;">
Моя личная помоечка
</div>
</div>
<div class="content">
<!--BREADCRUMB-->
<!--CONTENT-->
</div>
</div>
<footer class="footer">
<div class="footer-content">
<span>Навайбкодил с любовью ❤️</span>
<a href="https://romkazvo.ru" target="_blank" class="footer-link">RomkaZVO</a><br>
Сделано при помощи BusyBox httpd и nginx для SSL<br>
Боже, храни Китай партия за DeepSeek
</div>
<div id="status" style="margin-top: 1rem; font-size: 0.8rem; opacity: 0.7; line-height: 1.4;">
Загрузка статистики...
</div>
<script>
fetch('/cgi-bin/status.cgi')
.then(response => response.text())
.then(data => {
document.getElementById('status').innerHTML = data;
})
.catch(err => {
document.getElementById('status').innerHTML = 'Ошибка загрузки статуса';
});
</script>
</footer>
<button class="scroll-top" onclick="scrollToTop()"></button>
<script>
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
window.addEventListener('scroll', function() {
const scrollBtn = document.querySelector('.scroll-top');
if (window.scrollY > 300) {
scrollBtn.classList.add('visible');
} else {
scrollBtn.classList.remove('visible');
}
});
function toggleTheme() {
const isDark = document.body.classList.contains('dark-theme');
const newTheme = isDark ? 'light' : 'dark';
document.body.classList.toggle('dark-theme');
document.documentElement.classList.toggle('dark-theme');
const button = document.querySelector('.theme-toggle');
button.textContent = isDark ? '🌙' : '☀️';
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
document.cookie = `theme=${newTheme}; expires=${date.toUTCString()}; path=/`;
}
document.addEventListener('DOMContentLoaded', function() {
const savedTheme = getCookie('theme');
const button = document.querySelector('.theme-toggle');
if (savedTheme === 'dark') {
document.body.classList.add('dark-theme');
button.textContent = '☀️';
} else {
document.body.classList.remove('dark-theme');
button.textContent = '🌙';
}
document.body.classList.add('loaded');
});
document.addEventListener('touchstart', function() {}, { passive: true });
</script>
</body>
</html>