Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
742d8fb088 |
@@ -52,10 +52,6 @@ func (m *Manager) mountWithRetry(provider, webdavURL string, maxRetries int) err
|
||||
func (m *Manager) performMount(provider, webdavURL string) error {
|
||||
cfg := config.Get()
|
||||
mountPath := filepath.Join(cfg.Rclone.MountPath, provider)
|
||||
cacheDir := ""
|
||||
if cfg.Rclone.CacheDir != "" {
|
||||
cacheDir = filepath.Join(cfg.Rclone.CacheDir, provider)
|
||||
}
|
||||
|
||||
// Create mount directory
|
||||
if err := os.MkdirAll(mountPath, 0755); err != nil {
|
||||
@@ -78,14 +74,13 @@ func (m *Manager) performMount(provider, webdavURL string) error {
|
||||
}
|
||||
|
||||
// Create rclone config for this provider
|
||||
configName := fmt.Sprintf("decypharr-%s", provider)
|
||||
if err := m.createConfig(configName, webdavURL); err != nil {
|
||||
if err := m.createConfig(provider, webdavURL); err != nil {
|
||||
return fmt.Errorf("failed to create rclone config: %w", err)
|
||||
}
|
||||
|
||||
// Prepare mount arguments
|
||||
mountArgs := map[string]interface{}{
|
||||
"fs": fmt.Sprintf("%s:", configName),
|
||||
"fs": fmt.Sprintf("%s:", provider),
|
||||
"mountPoint": mountPath,
|
||||
"mountType": "mount", // Use standard FUSE mount
|
||||
"mountOpt": map[string]interface{}{
|
||||
@@ -103,15 +98,6 @@ func (m *Manager) performMount(provider, webdavURL string) error {
|
||||
configOpts["BufferSize"] = cfg.Rclone.BufferSize
|
||||
}
|
||||
|
||||
if cacheDir != "" {
|
||||
// Create cache directory if specified
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
m.logger.Warn().Str("cacheDir", cacheDir).Msg("Failed to create cache directory")
|
||||
} else {
|
||||
configOpts["CacheDir"] = cacheDir
|
||||
}
|
||||
}
|
||||
|
||||
if len(configOpts) > 0 {
|
||||
// Only add _config if there are options to set
|
||||
mountArgs["_config"] = configOpts
|
||||
@@ -180,7 +166,7 @@ func (m *Manager) performMount(provider, webdavURL string) error {
|
||||
WebDAVURL: webdavURL,
|
||||
Mounted: true,
|
||||
MountedAt: time.Now().Format(time.RFC3339),
|
||||
ConfigName: configName,
|
||||
ConfigName: provider,
|
||||
}
|
||||
|
||||
m.mountsMutex.Lock()
|
||||
@@ -319,7 +305,7 @@ func (m *Manager) RefreshDir(provider string, dirs []string) error {
|
||||
dirs = []string{"/"}
|
||||
}
|
||||
args := map[string]interface{}{
|
||||
"fs": fmt.Sprintf("decypharr-%s:", provider),
|
||||
"fs": fmt.Sprintf("%s:", provider),
|
||||
}
|
||||
for i, dir := range dirs {
|
||||
if dir != "" {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (m *Manager) checkMountHealth(provider string) bool {
|
||||
req := RCRequest{
|
||||
Command: "operations/list",
|
||||
Args: map[string]interface{}{
|
||||
"fs": fmt.Sprintf("decypharr-%s:", provider),
|
||||
"fs": fmt.Sprintf("%s:", provider),
|
||||
"remote": "/",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -105,6 +105,11 @@ func (m *Manager) Start(ctx context.Context) error {
|
||||
"--config", filepath.Join(m.configDir, "rclone.conf"),
|
||||
"--log-level", "INFO",
|
||||
}
|
||||
if cfg.Rclone.CacheDir != "" {
|
||||
if err := os.MkdirAll(cfg.Rclone.CacheDir, 0755); err == nil {
|
||||
args = append(args, "--cache-dir", cfg.Rclone.CacheDir)
|
||||
}
|
||||
}
|
||||
m.cmd = exec.CommandContext(ctx, "rclone", args...)
|
||||
m.cmd.Dir = m.configDir
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -414,8 +414,91 @@ class DecypharrUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile navigation dropdown handler
|
||||
setupMobileNavigation() {
|
||||
const mobileMenuBtn = document.querySelector('.navbar-start .dropdown [role="button"]');
|
||||
const mobileMenu = document.querySelector('.navbar-start .dropdown .dropdown-content');
|
||||
const dropdown = document.querySelector('.navbar-start .dropdown');
|
||||
|
||||
if (!mobileMenuBtn || !mobileMenu || !dropdown) return;
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
const openDropdown = () => {
|
||||
if (!isOpen) {
|
||||
dropdown.classList.add('dropdown-open');
|
||||
mobileMenuBtn.setAttribute('aria-expanded', 'true');
|
||||
isOpen = true;
|
||||
}
|
||||
};
|
||||
|
||||
const closeDropdown = () => {
|
||||
if (isOpen) {
|
||||
dropdown.classList.remove('dropdown-open');
|
||||
mobileMenuBtn.setAttribute('aria-expanded', 'false');
|
||||
isOpen = false;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDropdown = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isOpen) {
|
||||
closeDropdown();
|
||||
} else {
|
||||
openDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle button clicks (both mouse and touch)
|
||||
mobileMenuBtn.addEventListener('click', toggleDropdown);
|
||||
mobileMenuBtn.addEventListener('touchend', (e) => {
|
||||
e.preventDefault();
|
||||
toggleDropdown(e);
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (isOpen && !dropdown.contains(e.target)) {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown when touching outside
|
||||
document.addEventListener('touchend', (e) => {
|
||||
if (isOpen && !dropdown.contains(e.target)) {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown when clicking menu items
|
||||
mobileMenu.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'A') {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle keyboard navigation
|
||||
mobileMenuBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
toggleDropdown(e);
|
||||
} else if (e.key === 'Escape') {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial aria attributes
|
||||
mobileMenuBtn.setAttribute('aria-expanded', 'false');
|
||||
mobileMenuBtn.setAttribute('aria-haspopup', 'true');
|
||||
}
|
||||
|
||||
// Global event listeners
|
||||
setupGlobalEventListeners() {
|
||||
// Setup mobile navigation dropdown
|
||||
this.setupMobileNavigation();
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.addEventListener('click', (e) => {
|
||||
const link = e.target.closest('a[href^="#"]');
|
||||
|
||||
@@ -487,7 +487,7 @@
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="rclone.umask">
|
||||
<span class="label-text font-medium">Group ID (PGID)</span>
|
||||
<span class="label-text font-medium">UMASK</span>
|
||||
</label>
|
||||
<input type="text" class="input input-bordered" name="rclone.umask" id="rclone.umask" placeholder="0022">
|
||||
<div class="label">
|
||||
|
||||
@@ -60,9 +60,6 @@
|
||||
<li><a href="{{.URLBase}}webdav" target="_blank">
|
||||
<i class="bi bi-cloud text-success"></i>WebDAV
|
||||
</a></li>
|
||||
<li><a href="{{.URLBase}}stats" class="{{if eq .Page "stats"}}active{{end}}">
|
||||
<i class="bi bi-graph-up text-info"></i>Stats
|
||||
</a></li>
|
||||
<li><a href="{{.URLBase}}logs" target="_blank">
|
||||
<i class="bi bi-journal-text text-warning"></i>Logs
|
||||
</a></li>
|
||||
|
||||
@@ -180,8 +180,8 @@
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Elapsed Time</div>
|
||||
<div class="stat-value text-accent">${((cs.elapsedTime || 0) / 60).toFixed(1)}m</div>
|
||||
<div class="stat-desc">Transfer: ${((cs.transferTime || 0) / 60).toFixed(1)}m</div>
|
||||
<div class="stat-value text-accent">${window.decypharrUtils.formatDuration(cs.elapsedTime)}</div>
|
||||
<div class="stat-desc">Transfer: ${window.decypharrUtils.formatDuration(cs.transferTime)}m</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -225,7 +225,7 @@
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-base-content/60 mt-1">
|
||||
<span>${window.decypharrUtils.formatBytes(transfer.bytes || 0)} / ${window.decypharrUtils.formatBytes(transfer.size || 0)}</span>
|
||||
<span>ETA: ${transfer.eta ? Math.ceil(transfer.eta / 60) + 'm' : 'Unknown'}</span>
|
||||
<span>ETA: ${transfer.eta ? window.decypharrUtils.formatDuration(transfer.eta) : 'Unknown'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,38 @@ set -e
|
||||
# Default values
|
||||
PUID=${PUID:-1000}
|
||||
PGID=${PGID:-1000}
|
||||
UMASK=${UMASK:-022}
|
||||
|
||||
# Set umask
|
||||
umask "$UMASK"
|
||||
|
||||
# Function to create directories and files
|
||||
setup_directories() {
|
||||
# Ensure directories exist
|
||||
mkdir -p /app/logs /app/cache 2>/dev/null || true
|
||||
|
||||
# Create log file if it doesn't exist
|
||||
touch /app/logs/decypharr.log 2>/dev/null || true
|
||||
|
||||
# Try to set permissions if possible
|
||||
chmod 755 /app 2>/dev/null || true
|
||||
chmod 666 /app/logs/decypharr.log 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Check if we're running as root
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "Running as non-root user $(id -u):$(id -g) with umask $UMASK"
|
||||
|
||||
# Try to create directories as the current user
|
||||
setup_directories
|
||||
|
||||
export USER="$(id -un)"
|
||||
export HOME="/app"
|
||||
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
echo "Running as root, setting up user $PUID:$PGID with umask $UMASK"
|
||||
|
||||
# Create group if it doesn't exist
|
||||
if ! getent group "$PGID" > /dev/null 2>&1; then
|
||||
@@ -19,7 +51,7 @@ fi
|
||||
USERNAME=$(getent passwd "$PUID" | cut -d: -f1)
|
||||
GROUPNAME=$(getent group "$PGID" | cut -d: -f1)
|
||||
|
||||
# Ensure directories exist and have correct permissions
|
||||
# Create directories and set proper ownership
|
||||
mkdir -p /app/logs /app/cache
|
||||
chown -R "$PUID:$PGID" /app
|
||||
chmod 755 /app
|
||||
|
||||
Reference in New Issue
Block a user