Files
Jacky 54b1f2096e Nginx-UI disk I/O (#1547)
* fix: prevent disk I/O overload from static files in nginx config directory

This fix addresses the issue where placing static asset folders (like
frontend dist folders) under the nginx config directory would cause
severe disk I/O and crash the server.

Root cause: The file watcher (fsnotify) was monitoring ALL files under
the nginx config directory, including static assets. When extracting
zip files or copying large frontend distributions, each file change
event would trigger a scan, overwhelming the system.

Changes made:
- Added more excluded directories: html, www, static, assets, public, webroot
- Added shouldWatchDirectory() to skip directories that typically contain
  static assets (dist, build, node_modules, __MACOSX, etc.)
- Added isConfigFilePath() to filter files by extension and path patterns
- Only process files that appear to be nginx config files (.conf, files
  in sites-available/sites-enabled/streams-available/streams-enabled, etc.)
- Skip files with common static asset extensions (images, scripts, styles,
  archives, etc.)

This prevents the scanner from being overwhelmed by thousands of file
events from static asset directories while still properly monitoring
actual nginx configuration files.

Fixes: disk I/O overload when static files placed in nginx config dir

Co-authored-by: Jacky <me@jackyu.cn>

* Fix path matching bugs in shouldSkipPath and shouldWatchDirectory

Fix two issues in directory/path filtering logic:

1. shouldSkipPath: Add path separator boundary check to prevent false-positive
   prefix matching. e.g., excludedDir='/etc/nginx/html' should match
   '/etc/nginx/html/file' but NOT '/etc/nginx/html-configs/file'

2. shouldWatchDirectory: Split static directory patterns into two separate
   loops - one for slash-terminated patterns (intermediate directories via
   Contains) and one for slash-prefixed patterns (directories at end of path
   via HasSuffix). This prevents false positives where '/js' would match
   '/json-schemas/' because the old code had HasSuffix || Contains which
   effectively reduced to just Contains.

* fix: use relative paths for static directory pattern matching to avoid ancestor match

- Fixed shouldWatchDirectory and isConfigFilePath to check static directory
  patterns against only the path portion relative to the nginx config root,
  not the full absolute path. This prevents false matches when the nginx
  config directory itself is under a path containing pattern names like
  /opt/vendor/nginx/conf.

- Consolidated the duplicate staticDirPatterns and staticDirSuffixes lists
  into a single staticDirNames list, reducing maintenance burden and
  ensuring consistency between Contains and HasSuffix checks.

* fix: use filepath.Separator for cross-platform path matching in static directory checks

Replace hardcoded forward slashes with filepath.Separator in shouldWatchDirectory()
and isConfigFilePath() functions to ensure proper path matching on Windows.

The shouldSkipPath() function already uses filepath.Separator correctly, but the
static directory name checks were using hardcoded '/' which would never match
on Windows where paths use '\' as separator. This would cause all directories
to be watched and all files scanned on Windows, completely disabling the static
directory filtering.

* fix: reorder static dir checks before config dir checks to prevent I/O overload

Two bugs fixed:

1. Config dir check uses full path instead of relative - isConfigFilePath now
   uses relative path for config directory pattern checks, matching the logic
   in shouldWatchDirectory. This prevents paths like /srv/conf.d/nginx/ from
   incorrectly matching all files.

2. Config dir patterns short-circuit static directory filtering - Both
   shouldWatchDirectory and isConfigFilePath now check static directory
   patterns BEFORE config directory patterns. This ensures paths like
   /etc/nginx/conf.d/myapp/node_modules/ are correctly filtered out,
   preventing the I/O overload this PR aims to prevent.

* fix: remove redundant config dir check and add path-separator boundaries

Two bugs fixed:

1. Redundant config directory check in shouldWatchDirectory - Removed the
   configDirPatterns check which was dead code since both branches returned
   true. The function now simply returns true for any directory that passes
   the static directory filter.

2. Config dir patterns lack path-separator boundary checks - Modified
   isConfigFilePath to use separator-bounded matching for config directory
   patterns (conf.d, sites-enabled, etc.) to avoid false positives like
   'myconf.db' matching 'conf.d' across the name/extension boundary.

* perf: move constant data structures to package level to avoid allocations

Move configDirPatterns, configFilePatterns, and nonConfigExtensions from
function-local variables to package-level variables. These data structures
are constant and were being allocated on every isConfigFilePath call,
which is called twice per file during batch scans and for every file event
during watching.

This change eliminates thousands of unnecessary allocations during scans,
improving performance in a PR specifically aimed at reducing resource overhead.

* fix: remove redundant HasPrefix check in isConfigFilePath

The first operand strings.HasPrefix(lowerRelativePath, sep+pattern+sep)
was dead code because the strings.Contains check above already covers
this case - if a string starts with X, it necessarily contains X.

Simplified to only check HasPrefix(lowerRelativePath, pattern+sep) which
adds meaningful coverage for paths starting with the pattern directly
after the config root.

* fix: remove dead HasPrefix branch in config dir pattern matching

The strings.HasPrefix(lowerRelativePath, pattern+sep) check was dead code
because relativePath always starts with a path separator after TrimPrefix
from a filepath.Clean'd configRoot. Since pattern values like 'sites-available'
don't start with '/', HasPrefix with 'pattern+sep' (e.g., 'sites-available/')
could never match a path starting with '/sites-available/'.

The Contains(lowerRelativePath, sep+pattern+sep) check already correctly
handles this case since it looks for '/pattern/' anywhere in the path.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-02-07 21:38:44 +08:00
..