- Fix issues with cache dir
- Fix responsiveness issue with navbars - Support user entry for users running as non-root - Other minor fixes
This commit is contained in:
@@ -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 != "" {
|
||||||
|
|||||||
@@ -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": "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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^="#"]');
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user