1+ using System . Runtime . Versioning ;
2+ #if NET8_0_OR_GREATER && WINDOWS
3+ using System . Security . AccessControl ;
4+ using System . Security . Principal ;
5+ #endif
6+
7+ namespace ThingConnect . Pulse . Server . Services ;
8+
9+ public interface IPathService
10+ {
11+ string GetRootDirectory ( ) ;
12+ string GetConfigDirectory ( ) ;
13+ string GetConfigFilePath ( ) ;
14+ string GetVersionsDirectory ( ) ;
15+ string GetLogsDirectory ( ) ;
16+ string GetDataDirectory ( ) ;
17+ string GetDatabaseFilePath ( ) ;
18+ Task EnsureDirectoriesExistAsync ( ) ;
19+ }
20+
21+ public sealed class PathService : IPathService
22+ {
23+ private readonly string _rootDirectory ;
24+
25+ public PathService ( )
26+ {
27+ _rootDirectory = Path . Combine (
28+ Environment . GetFolderPath ( Environment . SpecialFolder . CommonApplicationData ) ,
29+ "ThingConnect.Pulse" ) ;
30+ }
31+
32+ public string GetRootDirectory ( ) => _rootDirectory ;
33+
34+ public string GetConfigDirectory ( ) => Path . Combine ( _rootDirectory , "config" ) ;
35+
36+ public string GetConfigFilePath ( ) => Path . Combine ( GetConfigDirectory ( ) , "config.yaml" ) ;
37+
38+ public string GetVersionsDirectory ( ) => Path . Combine ( _rootDirectory , "versions" ) ;
39+
40+ public string GetLogsDirectory ( ) => Path . Combine ( _rootDirectory , "logs" ) ;
41+
42+ public string GetDataDirectory ( ) => Path . Combine ( _rootDirectory , "data" ) ;
43+
44+ public string GetDatabaseFilePath ( ) => Path . Combine ( GetDataDirectory ( ) , "pulse.db" ) ;
45+
46+ public async Task EnsureDirectoriesExistAsync ( )
47+ {
48+ var directories = new [ ]
49+ {
50+ GetRootDirectory ( ) ,
51+ GetConfigDirectory ( ) ,
52+ GetVersionsDirectory ( ) ,
53+ GetLogsDirectory ( ) ,
54+ GetDataDirectory ( )
55+ } ;
56+
57+ foreach ( string directory in directories )
58+ {
59+ if ( ! Directory . Exists ( directory ) )
60+ {
61+ Directory . CreateDirectory ( directory ) ;
62+
63+ // Set proper permissions for the service account (Windows only)
64+ if ( OperatingSystem . IsWindows ( ) )
65+ {
66+ SetDirectoryPermissions ( directory ) ;
67+ }
68+ }
69+ }
70+
71+ // Create default config file if it doesn't exist
72+ string configFile = GetConfigFilePath ( ) ;
73+ if ( ! File . Exists ( configFile ) )
74+ {
75+ await CreateDefaultConfigFileAsync ( configFile ) ;
76+ }
77+ }
78+
79+ [ SupportedOSPlatform ( "windows" ) ]
80+ private static void SetDirectoryPermissions ( string directory )
81+ {
82+ #if NET8_0_OR_GREATER && WINDOWS
83+ try
84+ {
85+ var directoryInfo = new DirectoryInfo ( directory ) ;
86+ DirectorySecurity security = directoryInfo . GetAccessControl ( ) ;
87+
88+ // Grant full control to SYSTEM account
89+ var systemAccount = new SecurityIdentifier ( WellKnownSidType . LocalSystemSid , null ) ;
90+ var systemRule = new FileSystemAccessRule (
91+ systemAccount ,
92+ FileSystemRights . FullControl ,
93+ InheritanceFlags . ContainerInherit | InheritanceFlags . ObjectInherit ,
94+ PropagationFlags . None ,
95+ AccessControlType . Allow ) ;
96+
97+ // Grant read/modify to Administrators
98+ var adminAccount = new SecurityIdentifier ( WellKnownSidType . BuiltinAdministratorsSid , null ) ;
99+ var adminRule = new FileSystemAccessRule (
100+ adminAccount ,
101+ FileSystemRights . Modify ,
102+ InheritanceFlags . ContainerInherit | InheritanceFlags . ObjectInherit ,
103+ PropagationFlags . None ,
104+ AccessControlType . Allow ) ;
105+
106+ security . SetAccessRule ( systemRule ) ;
107+ security . SetAccessRule ( adminRule ) ;
108+
109+ directoryInfo . SetAccessControl ( security ) ;
110+ }
111+ catch ( Exception ex )
112+ {
113+ // Log warning but don't fail startup - permissions might work anyway
114+ Console . WriteLine ( $ "Warning: Could not set directory permissions for { directory } : { ex . Message } ") ;
115+ }
116+ #endif
117+ }
118+
119+ private static async Task CreateDefaultConfigFileAsync ( string configFile )
120+ {
121+ const string defaultConfig = """
122+ # ThingConnect Pulse Configuration
123+ # This is the main configuration file for network monitoring
124+ #
125+ # For configuration syntax and examples, see:
126+ # https://github.com/MachDatum/ThingConnect.Pulse/blob/main/docs/config.schema.json
127+
128+ # Example configuration:
129+ # targets:
130+ # - name: "Router"
131+ # endpoints:
132+ # - host: "192.168.1.1"
133+ # type: "icmp"
134+ # - name: "Web Services"
135+ # endpoints:
136+ # - host: "www.example.com"
137+ # type: "http"
138+ # path: "/health"
139+
140+ # Empty configuration - add your monitoring targets above
141+ targets: []
142+ """ ;
143+
144+ await File . WriteAllTextAsync ( configFile , defaultConfig ) ;
145+ }
146+ }
0 commit comments