- Fix issues with cache dir
Some checks failed
GoReleaser / goreleaser (push) Has been cancelled
Release Docker Build / docker (push) Has been cancelled

- Fix responsiveness issue with navbars
- Support user entry for users running as non-root
- Other minor fixes
This commit is contained in:
Mukhtar Akere
2025-08-12 15:14:42 +01:00
parent a0e9f7f553
commit 742d8fb088
10 changed files with 132 additions and 29 deletions

View File

@@ -52,10 +52,6 @@ func (m *Manager) mountWithRetry(provider, webdavURL string, maxRetries int) err
func (m *Manager) performMount(provider, webdavURL string) error { func (m *Manager) performMount(provider, webdavURL string) error {
cfg := config.Get() cfg := config.Get()
mountPath := filepath.Join(cfg.Rclone.MountPath, provider) mountPath := filepath.Join(cfg.Rclone.MountPath, provider)
cacheDir := ""
if cfg.Rclone.CacheDir != "" {
cacheDir = filepath.Join(cfg.Rclone.CacheDir, provider)
}
// Create mount directory // Create mount directory
if err := os.MkdirAll(mountPath, 0755); err != nil { 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 // Create rclone config for this provider
configName := fmt.Sprintf("decypharr-%s", provider) if err := m.createConfig(provider, webdavURL); err != nil {
if err := m.createConfig(configName, webdavURL); err != nil {
return fmt.Errorf("failed to create rclone config: %w", err) return fmt.Errorf("failed to create rclone config: %w", err)
} }
// Prepare mount arguments // Prepare mount arguments
mountArgs := map[string]interface{}{ mountArgs := map[string]interface{}{
"fs": fmt.Sprintf("%s:", configName), "fs": fmt.Sprintf("%s:", provider),
"mountPoint": mountPath, "mountPoint": mountPath,
"mountType": "mount", // Use standard FUSE mount "mountType": "mount", // Use standard FUSE mount
"mountOpt": map[string]interface{}{ "mountOpt": map[string]interface{}{
@@ -103,15 +98,6 @@ func (m *Manager) performMount(provider, webdavURL string) error {
configOpts["BufferSize"] = cfg.Rclone.BufferSize 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 { if len(configOpts) > 0 {
// Only add _config if there are options to set // Only add _config if there are options to set
mountArgs["_config"] = configOpts mountArgs["_config"] = configOpts
@@ -180,7 +166,7 @@ func (m *Manager) performMount(provider, webdavURL string) error {
WebDAVURL: webdavURL, WebDAVURL: webdavURL,
Mounted: true, Mounted: true,
MountedAt: time.Now().Format(time.RFC3339), MountedAt: time.Now().Format(time.RFC3339),
ConfigName: configName, ConfigName: provider,
} }
m.mountsMutex.Lock() m.mountsMutex.Lock()
@@ -319,7 +305,7 @@ func (m *Manager) RefreshDir(provider string, dirs []string) error {
dirs = []string{"/"} dirs = []string{"/"}
} }
args := map[string]interface{}{ args := map[string]interface{}{
"fs": fmt.Sprintf("decypharr-%s:", provider), "fs": fmt.Sprintf("%s:", provider),
} }
for i, dir := range dirs { for i, dir := range dirs {
if dir != "" { if dir != "" {

View File

@@ -44,7 +44,7 @@ func (m *Manager) checkMountHealth(provider string) bool {
req := RCRequest{ req := RCRequest{
Command: "operations/list", Command: "operations/list",
Args: map[string]interface{}{ Args: map[string]interface{}{
"fs": fmt.Sprintf("decypharr-%s:", provider), "fs": fmt.Sprintf("%s:", provider),
"remote": "/", "remote": "/",
}, },
} }

View File

@@ -105,6 +105,11 @@ func (m *Manager) Start(ctx context.Context) error {
"--config", filepath.Join(m.configDir, "rclone.conf"), "--config", filepath.Join(m.configDir, "rclone.conf"),
"--log-level", "INFO", "--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 = exec.CommandContext(ctx, "rclone", args...)
m.cmd.Dir = m.configDir 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

View File

@@ -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 // Global event listeners
setupGlobalEventListeners() { setupGlobalEventListeners() {
// Setup mobile navigation dropdown
this.setupMobileNavigation();
// Smooth scroll for anchor links // Smooth scroll for anchor links
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
const link = e.target.closest('a[href^="#"]'); const link = e.target.closest('a[href^="#"]');

View File

@@ -487,7 +487,7 @@
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="rclone.umask"> <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> </label>
<input type="text" class="input input-bordered" name="rclone.umask" id="rclone.umask" placeholder="0022"> <input type="text" class="input input-bordered" name="rclone.umask" id="rclone.umask" placeholder="0022">
<div class="label"> <div class="label">

View File

@@ -60,9 +60,6 @@
<li><a href="{{.URLBase}}webdav" target="_blank"> <li><a href="{{.URLBase}}webdav" target="_blank">
<i class="bi bi-cloud text-success"></i>WebDAV <i class="bi bi-cloud text-success"></i>WebDAV
</a></li> </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"> <li><a href="{{.URLBase}}logs" target="_blank">
<i class="bi bi-journal-text text-warning"></i>Logs <i class="bi bi-journal-text text-warning"></i>Logs
</a></li> </a></li>

View File

@@ -180,8 +180,8 @@
</div> </div>
<div class="stat"> <div class="stat">
<div class="stat-title">Elapsed Time</div> <div class="stat-title">Elapsed Time</div>
<div class="stat-value text-accent">${((cs.elapsedTime || 0) / 60).toFixed(1)}m</div> <div class="stat-value text-accent">${window.decypharrUtils.formatDuration(cs.elapsedTime)}</div>
<div class="stat-desc">Transfer: ${((cs.transferTime || 0) / 60).toFixed(1)}m</div> <div class="stat-desc">Transfer: ${window.decypharrUtils.formatDuration(cs.transferTime)}m</div>
</div> </div>
`; `;
} }
@@ -225,7 +225,7 @@
</div> </div>
<div class="flex justify-between text-xs text-base-content/60 mt-1"> <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>${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> </div>
</div> </div>

View File

@@ -4,6 +4,38 @@ set -e
# Default values # Default values
PUID=${PUID:-1000} PUID=${PUID:-1000}
PGID=${PGID:-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 # Create group if it doesn't exist
if ! getent group "$PGID" > /dev/null 2>&1; then if ! getent group "$PGID" > /dev/null 2>&1; then
@@ -19,7 +51,7 @@ fi
USERNAME=$(getent passwd "$PUID" | cut -d: -f1) USERNAME=$(getent passwd "$PUID" | cut -d: -f1)
GROUPNAME=$(getent group "$PGID" | 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 mkdir -p /app/logs /app/cache
chown -R "$PUID:$PGID" /app chown -R "$PUID:$PGID" /app
chmod 755 /app chmod 755 /app