aboutsummaryrefslogtreecommitdiff
path: root/cmd/mobius-hotline-server
AgeCommit message (Collapse)Author
2026-08-23Add feed-backed threaded news importsJeff Halter
2026-07-10Wire presence tracker via WithPresenceTracker optionJeff Halter
Move the Redis/ban backend selection ahead of NewServer so the presence tracker is supplied through the WithPresenceTracker option rather than a direct field write, matching how the other server config flows through options. The ban list shares the Redis client, so it is captured in the same block and assigned after construction.
2026-07-10Overhaul regression testing: e2e suite, fuzzing, CI, and bug fixesJeff Halter
Add a protocol-level end-to-end suite (in-process fully wired server on an ephemeral port pair, driven by hotline.Client over TCP) covering handshake, login, public and private chat, message board, threaded news, file list/download/upload, account admin, disconnect notification, and shutdown broadcast. The harness retries on a fresh port pair when another process steals a probed port before ListenAndServe binds it, and Server gains WithConnectionRateLimit so tests can disable the per-IP connection throttle. Add native fuzz tests for Transaction, Field, and flattened file object decoding, and fix the bugs the new tests surfaced: - Transaction.Write panicked on out-of-range attacker-controlled size fields, and transactionScanner's uint32 length addition could wrap and yield a truncated token. The information fork size declared in an untrusted fork header is now bounded too. - Client keepalive read c.done unsynchronized while Disconnect replaces it under the mutex. - The shared Agreement's Seek+ReadAll login path raced concurrent logins; the server now prefers an AgreementBytes() snapshot. Fill unit-test gaps (main's config-copy helpers, file resume data, ReloaderFunc, R2 error injection and env validation) and add a CI test workflow (build/vet + race-enabled shuffled suite), fixed lint workflow triggers with golangci-lint v2.6, and Makefile test/cover/ lint/fuzz targets.
2026-07-09Send a goodbye message to clients on signal-initiated shutdownJeff Halter
SIGTERM and SIGINT canceled the server context directly, so clients were force-closed without the TranDisconnectMsg broadcast that the API shutdown endpoint sends. The signal handler now calls Server.Shutdown with a goodbye message, giving both shutdown paths the same behavior: broadcast, a brief flush window, then force-close and drain. Default signal handling is restored before the graceful shutdown begins, so a second signal still kills the process immediately rather than waiting out the flush window.
2026-07-09Resolve FileRoot per storage backend to keep object keys host-independentJeff Halter
LoadConfig unconditionally rewrote a relative FileRoot to an absolute path under the config dir. R2FileStore.key() then embedded that local path in every object key (e.g. Users/jhalter/.../config/Files/...), so bucket contents were tied to the host's directory layout and orphaned by moving the config dir or pointing another server at the bucket. FileRoot is a path within the selected file store's namespace, so only the backend selection in main knows how to resolve it: the os backend resolves relative values against the config dir as before, while the memory and r2 backends keep the configured value verbatim, yielding portable keys like Files/foo.txt. An absolute FileRoot combined with an object-store backend now logs a warning. WithConfig copies the config struct, so the option is appended after the backend selection mutates FileRoot rather than before.
2026-07-08Add Cloudflare R2 file library storage backendJeff Halter
Implement R2FileStore, a FileStore backed by Cloudflare R2 via its S3-compatible API, selectable with --file-store r2 and configured through R2_* environment variables. The store follows the MemFileStore model: a flat keyspace where directories are derived from key prefixes, with zero-byte marker objects so empty folders persist, and errors.ErrUnsupported for symlink/alias operations. Because R2 has no append, in-progress .incomplete uploads are routed to a local staging directory (real O_APPEND and size-based resume) and promoted to a finished R2 object on the terminal upload-commit Rename; all other paths live in R2. A narrow s3API seam plus an s3Uploader interface make the backend unit-testable against an in-memory fake without a network. Adds a user setup guide at docs/r2-file-store.md.
2026-07-08Add --file-store flag to select the file library backendJeff Halter
Wire an os|memory selector mirroring the existing Redis-vs-file backend selection. This marks the seam where a future object-store backend (e.g. Cloudflare R2 / S3) slots in via hotline.WithFileStore.
2026-06-25Introduce PresenceTracker interface for online-user trackingJeff Halter
Replace the leaky *redis.Client field on Server with a PresenceTracker interface that receives connect/rename/disconnect lifecycle events. This removes Redis-specific set encoding from hotline/server.go and the session handlers, and drops the redis dependency from the hotline package. The Redis implementation moves to mobius.RedisPresenceTracker, which owns the legacy "login::ip"/"login:nickname:ip" set encoding so existing deployments keep working. The API server reads online users through a new OnlineLister interface and falls back to the in-memory ClientMgr when no presence tracker is configured. Startup clearing of stale online state now happens unconditionally when Redis is configured, not only when the API server is enabled.
2026-06-12Add Reloader interface to remove type assertions from reload pathJeff Halter
main.go's reloadFunc reached through the server's interface fields with concrete type assertions (srv.MessageBoard.(*mobius.FlatNews), etc.) to trigger SIGHUP/API reloads, leaking storage implementation details past the manager interfaces. Storage backends now implement a one-method Reloader interface, with compile-time assertions for FlatNews, BanFile, ThreadedNewsYAML, and Agreement. BanFile.Load and ThreadedNewsYAML.Load are renamed Reload for a uniform method set, matching FlatNews's existing convention of using Reload for the initial load as well. main.go registers each backend in a named reloader list as it is constructed, and reloadFunc iterates the list. The banner reload is a ReloaderFunc that also performs the initial load, and the Redis-backed ban list simply registers no reloader, replacing the old type-switch special case.
2026-06-12Fix data races on ClientConn state, banner reload, and rate limiter growthJeff Halter
ClientConn's mutable session state (Flags, UserName, Icon, IdleTime, AutoReply) was guarded inconsistently: two mutexes (FlagsMU and mu) covered some paths while others mutated or read the fields with no locking at all, including HandleSetClientUserInfo, HandleUpdateUser (which writes other clients' admin flag), the login flow, the HTTP API handlers, and the keepalive loop. Consolidate on a single mutex with accessor methods (SetFlag/IsFlagSet/FlagBytes, SetUserName/GetUserName, and so on) used by all production code; direct field access remains for test construction. The idle/away logic moves into incrementIdleTime and clearIdleAndAway helpers that report whether a notification is needed, so SendAll is no longer called while holding the lock. HandleRejectChatInvite also no longer appends to the username slice, which could write past its length into the backing buffer. The server banner is now behind Banner/SetBanner with an RWMutex: the SIGHUP reload previously reassigned the field while banner download goroutines read it, and nilled it when the file read failed. Reload now keeps the previous banner on failure. Per-IP rate limiter entries now record a last-seen time, and the keepalive ticker evicts entries idle for over seven days, so the map no longer grows unboundedly with each unique client IP.
2026-06-12Shut down gracefully instead of exiting from library codeJeff Halter
ListenAndServe previously started each listener in a goroutine that called log.Fatal on any error, which skipped deferred cleanup and made errors unobservable to callers, and Server.Shutdown terminated the process with os.Exit. Context cancellation was also ineffective: Serve only checked ctx between Accept calls, which block indefinitely. ListenAndServe now binds its listeners up front and returns bind errors, closes every listener when the context is canceled so accept loops unblock and return, and reports the first serve loop error to the caller. Shutdown closes a lazily-initialized channel that cancels ListenAndServe's context, so the shutdown API works race-free even though it starts before ListenAndServe. "Server shutting down" is logged once by ListenAndServe rather than per accept loop, which produced duplicate or missing lines depending on scheduling. main.go now treats context.Canceled as a clean exit, logs other server errors and exits nonzero, and runs deferred cleanup (e.g. Bonjour shutdown) on the way out.
2026-06-01Improve startup logging and logging consistencyJeff Halter
Startup log: - Add interface, port, and fileTransferPort fields to the "Hotline server started" line so operators can see what the server bound to. - Resolve an empty -interface flag to 0.0.0.0 for display. Levels: - Demote the two Redis startup messages (ban management, cleared online users) from Info to Debug. Consistency: - Standardize the error field key to "err" (was "Err" in a few account handlers) and lowercase "Account" -> "account". - Standardize the remote-address key to "remoteAddr" (was "RemoteAddr"). - Replace fmt.Sprintf in the tracker-registration message and string concatenation in the config-dir-init and ban-disconnect messages with structured fields; use the standard "err" key. - Give the two bare rLogger.Error(err.Error()) file-transfer calls a descriptive message and an "err" field. logger.go: - Only attach the rotating lumberjack file writer when --log-file is set. An empty Filename made lumberjack write to a temp file by default.
2026-03-15Add configurable text encoding for file and folder namesJeff Halter
Replace hardcoded Mac Roman encoding globals with a configurable Encoding field in config.yaml. Servers that exclusively serve modern UTF-8 clients can now set Encoding: utf8 to disable Mac Roman conversion. The default remains "macintosh" for backward compatibility. - Add Encoding field to Config struct (macintosh|utf8) - Store TextDecoder/TextEncoder on Server, initialized from config - Add TextDecoder()/TextEncoder() accessors on ClientConn - Pass decoder/encoder explicitly to ReadPath and GetFileNameList - Remove package-level txtEncoder/txtDecoder globals - Add warning log when files are skipped due to encoding errors - Log and return error replies in HandleGetFileNameList on failure - Document the new config option in docs/text-encoding.md
2026-03-14Refactor ban management behind BanMgr interfaceJeff Halter
Extract ban logic into a BanMgr interface with two implementations: - BanFile: file-based YAML storage with support for IP, username, and nickname bans (backwards-compatible with legacy format) - RedisBanMgr: Redis-backed implementation with permanent and temporary ban support, using fail-safe deny-on-error behavior This replaces scattered Redis calls in API handlers, transaction handlers, and server connection logic with unified interface calls, removing the Redis dependency from the API server constructor and enabling ban functionality for both file-only and Redis deployments.
2025-11-29Ran goimports -w . to tidy upJeff Halter
2025-11-22Add optional TLS support for encrypted client connectionsJeff Halter
- Add TLSConfig and TLSPort fields to Server struct - Add WithTLS option function for configuration - Add ServeWithTLS and ServeFileTransfersWithTLS methods - Update ListenAndServe to start TLS listeners when configured - Add -tls-cert, -tls-key, -tls-port command-line flags - Fix data race in rateLimiters map access with mutex - Add TLS documentation with certificate generation instructions
2025-11-18Update BannerFile doc comment in default config.yamlJeff Halter
2025-07-04Fix file handle close warnings by ignoring return valuesJeff Halter
Updated all file close operations to use anonymous functions that ignore return values to satisfy golangci-lint errcheck warnings.
2025-06-30Improve error handling consistency in main.goJeff Halter
- Fix incorrect error message for banner loading - Convert all error logging to structured format - Remove server shutdown during config reload failures
2025-06-26Replace filepath.Join with path.Join for Windows compatibilityJeff Halter
Replace all instances of filepath.Join with path.Join across the codebase to improve Windows compatibility following the guidance from https://github.com/golang/go/issues/44305. Key changes: - Replaced filepath.Join with path.Join in 14 files - Updated import statements appropriately - Resolved variable shadowing issues where function parameters named 'path' were conflicting with the path package - Maintained filepath imports where needed for OS-specific functions like filepath.Walk, filepath.IsAbs, filepath.Dir, and filepath.Base All tests pass, confirming the changes maintain functionality while improving cross-platform compatibility.
2025-06-25Refactor copyDir and findConfigPath functions in main.goJeff Halter
- Refactor copyDir: Add proper error handling, resource cleanup with defer, true recursion, better permissions (0755), and separation of concerns - Refactor configSearchPaths -> findConfigPath: Add directory validation, better naming, and clearer documentation - Add comprehensive test suite for all functions with 100% test coverage - Remove panic in copyDir, replace with proper error propagation - Fix resource leaks by using defer for file cleanup
2025-05-22Update main.goTheo Knez
2024-08-04Delete cmd/mobius-hotline-server/mobius/config/Files/hello.txtJeff Halter
2024-08-04Create About This Area.txtJeff Halter
2024-07-28Improve human readability of account config filesJeff Halter
2024-07-27Fix missing version in Docker and Makefile buildJeff Halter
2024-07-26Make Bonjour optional and disabled by defaultJeff Halter
Bonjour doesn't seem happy inside Docker, so I'm making it optional and off by default.
2024-07-26Limit guest permissionsJeff Halter
Limit default guest permissions to: File System Maintenance * Can Download files * Can Download folders * Can Upload Files * Can Upload Folders Messaging * Can Send Messages News * Can Read Articles * Can Post Articles Chat * Can Initiate Private Chat * Can Read Chat * Can Send Chat Misc * Can Use Any Name
2024-07-26Register server address on local network with BonjourJeff Halter
2024-07-21Clean up loggingJeff Halter
2024-07-18Add support for trackers that require a passwordJeff Halter
2024-07-18Add initial HTTP API endpointsJeff Halter
2024-07-17Extensive refactor, quality of life enhancementsJeff Halter
* Added ability to reload config, agreement, news, and user accounts without restarting the server by sending SIGHUP to the running process * Added ability to use modern unix or windows line breaks in Agreement.txt and MessageBoard.txt instead of classic MacOS `\r` breaks. * Extensive refactor towards swappable backends for the active server state * Extensive refactored towards making the hotline package generic and re-usable for alternate server implemenations * Fix bug where users whose accounts have been deleted would not be disconnected
2024-07-09Extensive refactor and clean upJeff Halter
2024-06-26Fix Windows compatibility for -init flagJeff Halter
2024-06-24Refactoring, cleanup, test backfillingJeff Halter
2024-06-17Clean up various linter warningsJeff Halter
2024-06-15Replace hardcoded version with ldflag usageJeff Halter
2024-06-15Refactoring and cleanupJeff Halter
* Split CLI client into separate project * Convert more functions to follow common Golang idioms e.g io.Reader, io.Writer * Use ldflags for versioning * Misc cleanup and simplification
2024-06-10Replace zap logger with slogJeff Halter
2024-06-01If no predefined default path for server config, look in the current dirCharlotte Koch
2024-03-28Add cmdline flag to specify network interfaceJeff Halter
2023-04-19Ran 'golangci-lint run -E gocritic,whitespace --fix' 🤞Jeff Halter
2023-04-19Replace deprecated rand.Seed usageJeff Halter
2022-11-05Register with Gloarbline 1.9.7 client default trackersJeff Halter
2022-11-03Add comment clarifying usage of NewsDateFormat config optionJeff Halter
2022-11-03Add optional logging to fileJeff Halter
2022-07-04Add basic stat countersJeff Halter
2022-06-29Replacement banner imageAde Thompson
New banner image for default hotline server installs incorporating Mobius branding.
2022-06-26Fix example IgnoreFiles filterJeff Halter
'^\.*' works on macOS but is ignoring all files on Linux.