service-config-foundry 0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- service-config-foundry-0.1/LICENSE +21 -0
- service-config-foundry-0.1/PKG-INFO +304 -0
- service-config-foundry-0.1/README.md +284 -0
- service-config-foundry-0.1/service_config_foundry/__init__.py +3 -0
- service-config-foundry-0.1/service_config_foundry/config_parser.py +74 -0
- service-config-foundry-0.1/service_config_foundry/file_type.py +212 -0
- service-config-foundry-0.1/service_config_foundry/sections/__init__.py +11 -0
- service-config-foundry-0.1/service_config_foundry/sections/automount.py +6 -0
- service-config-foundry-0.1/service_config_foundry/sections/install.py +8 -0
- service-config-foundry-0.1/service_config_foundry/sections/mount.py +10 -0
- service-config-foundry-0.1/service_config_foundry/sections/path.py +12 -0
- service-config-foundry-0.1/service_config_foundry/sections/scope.py +7 -0
- service-config-foundry-0.1/service_config_foundry/sections/service.py +44 -0
- service-config-foundry-0.1/service_config_foundry/sections/slice.py +7 -0
- service-config-foundry-0.1/service_config_foundry/sections/socket.py +12 -0
- service-config-foundry-0.1/service_config_foundry/sections/swap.py +8 -0
- service-config-foundry-0.1/service_config_foundry/sections/timer.py +14 -0
- service-config-foundry-0.1/service_config_foundry/sections/unit.py +13 -0
- service-config-foundry-0.1/service_config_foundry/service.py +192 -0
- service-config-foundry-0.1/service_config_foundry/service_location.py +37 -0
- service-config-foundry-0.1/service_config_foundry/utils.py +78 -0
- service-config-foundry-0.1/service_config_foundry.egg-info/PKG-INFO +304 -0
- service-config-foundry-0.1/service_config_foundry.egg-info/SOURCES.txt +25 -0
- service-config-foundry-0.1/service_config_foundry.egg-info/dependency_links.txt +1 -0
- service-config-foundry-0.1/service_config_foundry.egg-info/top_level.txt +1 -0
- service-config-foundry-0.1/setup.cfg +4 -0
- service-config-foundry-0.1/setup.py +27 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Yush Raj Kapoor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: service-config-foundry
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Helps create non-templated systemd services.
|
|
5
|
+
Home-page: https://github.com/yushdotkapoor/service-config-foundry
|
|
6
|
+
Author: Yush Kapoor
|
|
7
|
+
Author-email: yushdotkapoor@gmail.com
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/yushdotkapoor/service-config-foundry/issues
|
|
10
|
+
Platform: UNKNOWN
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Requires-Python: >=3.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
|
|
19
|
+
# service-config-foundry
|
|
20
|
+
|
|
21
|
+
**service-config-foundry** is a Python-based library that simplifies the process of creating, updating, replacing, and deleting systemd service files for Linux users. With this tool, you can easily manage systemd services programmatically, reducing the complexity of writing and maintaining service configuration files manually.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Create and manage non-templated systemd service files
|
|
26
|
+
- Support for various systemd file types including `.service`, `.timer`, `.socket`, `.mount`, and more
|
|
27
|
+
- Programmatic control over file attributes and configurations
|
|
28
|
+
- Replace and update existing services without conflicts
|
|
29
|
+
- Fully integrated Python API with no external dependencies
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
You can install the library using `pip` directly from the GitHub repository:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install git+https://github.com/yushdotkapoor/service-config-foundry.git
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Alternatively, clone the repository to get started:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/yushdotkapoor/service-config-foundry.git
|
|
43
|
+
cd service-config-foundry
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
Below are detailed examples of how to use `service-config-foundry` to create and manage systemd files of various types.
|
|
49
|
+
|
|
50
|
+
### Example: Creating a Service, Timer, and Socket
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from service import Service
|
|
54
|
+
from service_location import ServiceLocation
|
|
55
|
+
|
|
56
|
+
# Create a new service instance
|
|
57
|
+
service = Service("example", service_location=ServiceLocation.GLOBAL, auto_start=False, enable_at_startup=False, force_overwrite=False)
|
|
58
|
+
|
|
59
|
+
# Configure the service file
|
|
60
|
+
service_file = service.service_file
|
|
61
|
+
service_file.unit.description = "Example service for demonstration purposes"
|
|
62
|
+
service_file.service.user = "yushrajkapoor"
|
|
63
|
+
service_file.service.exec_start = "echo Hello, world >> /tmp/example.log"
|
|
64
|
+
service_file.install.wanted_by = "multi-user.target"
|
|
65
|
+
|
|
66
|
+
# Configure the timer file
|
|
67
|
+
timer_file = service.timer_file
|
|
68
|
+
timer_file.unit.description = "Example timer for demonstration purposes"
|
|
69
|
+
timer_file.timer.on_calendar = "*-*-* *:00:00"
|
|
70
|
+
timer_file.install.wanted_by = "timers.target"
|
|
71
|
+
|
|
72
|
+
# Configure the socket file
|
|
73
|
+
socket_file = service.socket_file
|
|
74
|
+
socket_file.unit.description = "Example socket for demonstration purposes"
|
|
75
|
+
socket_file.socket.listen_stream = "/run/example.sock"
|
|
76
|
+
socket_file.install.wanted_by = "sockets.target"
|
|
77
|
+
|
|
78
|
+
# Create or update the service, timer, and socket files
|
|
79
|
+
service.update()
|
|
80
|
+
|
|
81
|
+
# Enable the service to start at boot time
|
|
82
|
+
service.enable_service_at_startup()
|
|
83
|
+
|
|
84
|
+
# Start the service
|
|
85
|
+
service.start_service()
|
|
86
|
+
|
|
87
|
+
# Display the status of the service
|
|
88
|
+
service.status()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Output Files
|
|
92
|
+
|
|
93
|
+
#### `example.service`
|
|
94
|
+
```ini
|
|
95
|
+
[Unit]
|
|
96
|
+
Description = Example service for demonstration purposes
|
|
97
|
+
|
|
98
|
+
[Service]
|
|
99
|
+
User = yushrajkapoor
|
|
100
|
+
ExecStart = echo Hello, world >> /tmp/example.log
|
|
101
|
+
|
|
102
|
+
[Install]
|
|
103
|
+
WantedBy = multi-user.target
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `example.timer`
|
|
107
|
+
```ini
|
|
108
|
+
[Unit]
|
|
109
|
+
Description = Example timer for demonstration purposes
|
|
110
|
+
|
|
111
|
+
[Timer]
|
|
112
|
+
OnCalendar = *-*-* *:00:00
|
|
113
|
+
|
|
114
|
+
[Install]
|
|
115
|
+
WantedBy = timers.target
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### `example.socket`
|
|
119
|
+
```ini
|
|
120
|
+
[Unit]
|
|
121
|
+
Description = Example socket for demonstration purposes
|
|
122
|
+
|
|
123
|
+
[Socket]
|
|
124
|
+
ListenStream = /run/example.sock
|
|
125
|
+
|
|
126
|
+
[Install]
|
|
127
|
+
WantedBy = sockets.target
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Example: Replacing an Existing Service
|
|
131
|
+
|
|
132
|
+
If you want to replace an existing service configuration, you can use the `replace()` method. This ensures that any old files are removed and replaced with the new configuration.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# Replace the existing service configuration
|
|
136
|
+
service.replace()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This is particularly useful when you want to ensure that your updates overwrite any conflicting configurations from previous versions of the service.
|
|
140
|
+
|
|
141
|
+
### Example: Creating Mount and Automount Files
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# Configure the mount file
|
|
145
|
+
mount_file = service.mount_file
|
|
146
|
+
mount_file.unit.description = "Example mount for demonstration purposes"
|
|
147
|
+
mount_file.mount.what = "/dev/sda1"
|
|
148
|
+
mount_file.mount.where = "/mnt/example"
|
|
149
|
+
mount_file.mount.type = "ext4"
|
|
150
|
+
mount_file.install.wanted_by = "multi-user.target"
|
|
151
|
+
|
|
152
|
+
# Configure the automount file
|
|
153
|
+
automount_file = service.automount_file
|
|
154
|
+
automount_file.unit.description = "Example automount for demonstration purposes"
|
|
155
|
+
automount_file.automount.where = "/mnt/example"
|
|
156
|
+
automount_file.install.wanted_by = "multi-user.target"
|
|
157
|
+
|
|
158
|
+
# Create or update the files
|
|
159
|
+
service.update()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `example.mount`
|
|
163
|
+
```ini
|
|
164
|
+
[Unit]
|
|
165
|
+
Description = Example mount for demonstration purposes
|
|
166
|
+
|
|
167
|
+
[Mount]
|
|
168
|
+
What = /dev/sda1
|
|
169
|
+
Where = /mnt/example
|
|
170
|
+
Type = ext4
|
|
171
|
+
|
|
172
|
+
[Install]
|
|
173
|
+
WantedBy = multi-user.target
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `example.automount`
|
|
177
|
+
```ini
|
|
178
|
+
[Unit]
|
|
179
|
+
Description = Example automount for demonstration purposes
|
|
180
|
+
|
|
181
|
+
[Automount]
|
|
182
|
+
Where = /mnt/example
|
|
183
|
+
|
|
184
|
+
[Install]
|
|
185
|
+
WantedBy = multi-user.target
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Example: Creating Swap and Path Files
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
# Configure the swap file
|
|
192
|
+
swap_file = service.swap_file
|
|
193
|
+
swap_file.unit.description = "Example swap for demonstration purposes"
|
|
194
|
+
swap_file.swap.what = "/swapfile"
|
|
195
|
+
swap_file.install.wanted_by = "multi-user.target"
|
|
196
|
+
|
|
197
|
+
# Configure the path file
|
|
198
|
+
path_file = service.path_file
|
|
199
|
+
path_file.unit.description = "Example path for demonstration purposes"
|
|
200
|
+
path_file.path.path_exists = "/tmp/example"
|
|
201
|
+
path_file.install.wanted_by = "multi-user.target"
|
|
202
|
+
|
|
203
|
+
# Create or update the files
|
|
204
|
+
service.update()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### `example.swap`
|
|
208
|
+
```ini
|
|
209
|
+
[Unit]
|
|
210
|
+
Description = Example swap for demonstration purposes
|
|
211
|
+
|
|
212
|
+
[Swap]
|
|
213
|
+
What = /swapfile
|
|
214
|
+
|
|
215
|
+
[Install]
|
|
216
|
+
WantedBy = multi-user.target
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### `example.path`
|
|
220
|
+
```ini
|
|
221
|
+
[Unit]
|
|
222
|
+
Description = Example path for demonstration purposes
|
|
223
|
+
|
|
224
|
+
[Path]
|
|
225
|
+
PathExists = /tmp/example
|
|
226
|
+
|
|
227
|
+
[Install]
|
|
228
|
+
WantedBy = multi-user.target
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Example: Creating Slice and Scope Files
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# Configure the slice file
|
|
235
|
+
slice_file = service.slice_file
|
|
236
|
+
slice_file.unit.description = "Example slice for demonstration purposes"
|
|
237
|
+
|
|
238
|
+
# Configure the scope file
|
|
239
|
+
scope_file = service.scope_file
|
|
240
|
+
scope_file.unit.description = "Example scope for demonstration purposes"
|
|
241
|
+
scope_file.scope.slice = "example.slice"
|
|
242
|
+
|
|
243
|
+
# Create or update the files
|
|
244
|
+
service.update()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### `example.slice`
|
|
248
|
+
```ini
|
|
249
|
+
[Unit]
|
|
250
|
+
Description = Example slice for demonstration purposes
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### `example.scope`
|
|
254
|
+
```ini
|
|
255
|
+
[Unit]
|
|
256
|
+
Description = Example scope for demonstration purposes
|
|
257
|
+
|
|
258
|
+
[Scope]
|
|
259
|
+
Slice = example.slice
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Deleting a Service
|
|
263
|
+
|
|
264
|
+
To delete a service and its associated files:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
service.delete()
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
This will remove all files matching the service name in the systemd directory.
|
|
271
|
+
|
|
272
|
+
## Configuration Options
|
|
273
|
+
|
|
274
|
+
`service-config-foundry` supports multiple systemd file types, including:
|
|
275
|
+
|
|
276
|
+
- `.service` - Main service configuration
|
|
277
|
+
- `.socket` - Socket configuration
|
|
278
|
+
- `.timer` - Timer configuration
|
|
279
|
+
- `.mount` - Mount point configuration
|
|
280
|
+
- `.automount` - Automount configuration
|
|
281
|
+
- `.swap` - Swap space configuration
|
|
282
|
+
- `.path` - Path configuration
|
|
283
|
+
- `.slice` - Slice configuration
|
|
284
|
+
- `.scope` - Scope configuration
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
|
289
|
+
|
|
290
|
+
## Contributing
|
|
291
|
+
|
|
292
|
+
Contributions are welcome! If you encounter any issues or have suggestions for improvement, please open an issue or submit a pull request.
|
|
293
|
+
|
|
294
|
+
## Contact
|
|
295
|
+
|
|
296
|
+
- **Author**: Yush Kapoor
|
|
297
|
+
- **Email**: [yushdotkapoor@gmail.com](mailto:yushdotkapoor@gmail.com)
|
|
298
|
+
- **GitHub**: [https://github.com/yushdotkapoor/service-config-foundry](https://github.com/yushdotkapoor/service-config-foundry)
|
|
299
|
+
|
|
300
|
+
## Acknowledgments
|
|
301
|
+
|
|
302
|
+
Special thanks to the Linux and Python communities for their inspiration and support in creating this project.
|
|
303
|
+
|
|
304
|
+
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# service-config-foundry
|
|
2
|
+
|
|
3
|
+
**service-config-foundry** is a Python-based library that simplifies the process of creating, updating, replacing, and deleting systemd service files for Linux users. With this tool, you can easily manage systemd services programmatically, reducing the complexity of writing and maintaining service configuration files manually.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Create and manage non-templated systemd service files
|
|
8
|
+
- Support for various systemd file types including `.service`, `.timer`, `.socket`, `.mount`, and more
|
|
9
|
+
- Programmatic control over file attributes and configurations
|
|
10
|
+
- Replace and update existing services without conflicts
|
|
11
|
+
- Fully integrated Python API with no external dependencies
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
You can install the library using `pip` directly from the GitHub repository:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install git+https://github.com/yushdotkapoor/service-config-foundry.git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Alternatively, clone the repository to get started:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/yushdotkapoor/service-config-foundry.git
|
|
25
|
+
cd service-config-foundry
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Below are detailed examples of how to use `service-config-foundry` to create and manage systemd files of various types.
|
|
31
|
+
|
|
32
|
+
### Example: Creating a Service, Timer, and Socket
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from service import Service
|
|
36
|
+
from service_location import ServiceLocation
|
|
37
|
+
|
|
38
|
+
# Create a new service instance
|
|
39
|
+
service = Service("example", service_location=ServiceLocation.GLOBAL, auto_start=False, enable_at_startup=False, force_overwrite=False)
|
|
40
|
+
|
|
41
|
+
# Configure the service file
|
|
42
|
+
service_file = service.service_file
|
|
43
|
+
service_file.unit.description = "Example service for demonstration purposes"
|
|
44
|
+
service_file.service.user = "yushrajkapoor"
|
|
45
|
+
service_file.service.exec_start = "echo Hello, world >> /tmp/example.log"
|
|
46
|
+
service_file.install.wanted_by = "multi-user.target"
|
|
47
|
+
|
|
48
|
+
# Configure the timer file
|
|
49
|
+
timer_file = service.timer_file
|
|
50
|
+
timer_file.unit.description = "Example timer for demonstration purposes"
|
|
51
|
+
timer_file.timer.on_calendar = "*-*-* *:00:00"
|
|
52
|
+
timer_file.install.wanted_by = "timers.target"
|
|
53
|
+
|
|
54
|
+
# Configure the socket file
|
|
55
|
+
socket_file = service.socket_file
|
|
56
|
+
socket_file.unit.description = "Example socket for demonstration purposes"
|
|
57
|
+
socket_file.socket.listen_stream = "/run/example.sock"
|
|
58
|
+
socket_file.install.wanted_by = "sockets.target"
|
|
59
|
+
|
|
60
|
+
# Create or update the service, timer, and socket files
|
|
61
|
+
service.update()
|
|
62
|
+
|
|
63
|
+
# Enable the service to start at boot time
|
|
64
|
+
service.enable_service_at_startup()
|
|
65
|
+
|
|
66
|
+
# Start the service
|
|
67
|
+
service.start_service()
|
|
68
|
+
|
|
69
|
+
# Display the status of the service
|
|
70
|
+
service.status()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Output Files
|
|
74
|
+
|
|
75
|
+
#### `example.service`
|
|
76
|
+
```ini
|
|
77
|
+
[Unit]
|
|
78
|
+
Description = Example service for demonstration purposes
|
|
79
|
+
|
|
80
|
+
[Service]
|
|
81
|
+
User = yushrajkapoor
|
|
82
|
+
ExecStart = echo Hello, world >> /tmp/example.log
|
|
83
|
+
|
|
84
|
+
[Install]
|
|
85
|
+
WantedBy = multi-user.target
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### `example.timer`
|
|
89
|
+
```ini
|
|
90
|
+
[Unit]
|
|
91
|
+
Description = Example timer for demonstration purposes
|
|
92
|
+
|
|
93
|
+
[Timer]
|
|
94
|
+
OnCalendar = *-*-* *:00:00
|
|
95
|
+
|
|
96
|
+
[Install]
|
|
97
|
+
WantedBy = timers.target
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### `example.socket`
|
|
101
|
+
```ini
|
|
102
|
+
[Unit]
|
|
103
|
+
Description = Example socket for demonstration purposes
|
|
104
|
+
|
|
105
|
+
[Socket]
|
|
106
|
+
ListenStream = /run/example.sock
|
|
107
|
+
|
|
108
|
+
[Install]
|
|
109
|
+
WantedBy = sockets.target
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Example: Replacing an Existing Service
|
|
113
|
+
|
|
114
|
+
If you want to replace an existing service configuration, you can use the `replace()` method. This ensures that any old files are removed and replaced with the new configuration.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
# Replace the existing service configuration
|
|
118
|
+
service.replace()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This is particularly useful when you want to ensure that your updates overwrite any conflicting configurations from previous versions of the service.
|
|
122
|
+
|
|
123
|
+
### Example: Creating Mount and Automount Files
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# Configure the mount file
|
|
127
|
+
mount_file = service.mount_file
|
|
128
|
+
mount_file.unit.description = "Example mount for demonstration purposes"
|
|
129
|
+
mount_file.mount.what = "/dev/sda1"
|
|
130
|
+
mount_file.mount.where = "/mnt/example"
|
|
131
|
+
mount_file.mount.type = "ext4"
|
|
132
|
+
mount_file.install.wanted_by = "multi-user.target"
|
|
133
|
+
|
|
134
|
+
# Configure the automount file
|
|
135
|
+
automount_file = service.automount_file
|
|
136
|
+
automount_file.unit.description = "Example automount for demonstration purposes"
|
|
137
|
+
automount_file.automount.where = "/mnt/example"
|
|
138
|
+
automount_file.install.wanted_by = "multi-user.target"
|
|
139
|
+
|
|
140
|
+
# Create or update the files
|
|
141
|
+
service.update()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### `example.mount`
|
|
145
|
+
```ini
|
|
146
|
+
[Unit]
|
|
147
|
+
Description = Example mount for demonstration purposes
|
|
148
|
+
|
|
149
|
+
[Mount]
|
|
150
|
+
What = /dev/sda1
|
|
151
|
+
Where = /mnt/example
|
|
152
|
+
Type = ext4
|
|
153
|
+
|
|
154
|
+
[Install]
|
|
155
|
+
WantedBy = multi-user.target
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `example.automount`
|
|
159
|
+
```ini
|
|
160
|
+
[Unit]
|
|
161
|
+
Description = Example automount for demonstration purposes
|
|
162
|
+
|
|
163
|
+
[Automount]
|
|
164
|
+
Where = /mnt/example
|
|
165
|
+
|
|
166
|
+
[Install]
|
|
167
|
+
WantedBy = multi-user.target
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Example: Creating Swap and Path Files
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Configure the swap file
|
|
174
|
+
swap_file = service.swap_file
|
|
175
|
+
swap_file.unit.description = "Example swap for demonstration purposes"
|
|
176
|
+
swap_file.swap.what = "/swapfile"
|
|
177
|
+
swap_file.install.wanted_by = "multi-user.target"
|
|
178
|
+
|
|
179
|
+
# Configure the path file
|
|
180
|
+
path_file = service.path_file
|
|
181
|
+
path_file.unit.description = "Example path for demonstration purposes"
|
|
182
|
+
path_file.path.path_exists = "/tmp/example"
|
|
183
|
+
path_file.install.wanted_by = "multi-user.target"
|
|
184
|
+
|
|
185
|
+
# Create or update the files
|
|
186
|
+
service.update()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### `example.swap`
|
|
190
|
+
```ini
|
|
191
|
+
[Unit]
|
|
192
|
+
Description = Example swap for demonstration purposes
|
|
193
|
+
|
|
194
|
+
[Swap]
|
|
195
|
+
What = /swapfile
|
|
196
|
+
|
|
197
|
+
[Install]
|
|
198
|
+
WantedBy = multi-user.target
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `example.path`
|
|
202
|
+
```ini
|
|
203
|
+
[Unit]
|
|
204
|
+
Description = Example path for demonstration purposes
|
|
205
|
+
|
|
206
|
+
[Path]
|
|
207
|
+
PathExists = /tmp/example
|
|
208
|
+
|
|
209
|
+
[Install]
|
|
210
|
+
WantedBy = multi-user.target
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Example: Creating Slice and Scope Files
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
# Configure the slice file
|
|
217
|
+
slice_file = service.slice_file
|
|
218
|
+
slice_file.unit.description = "Example slice for demonstration purposes"
|
|
219
|
+
|
|
220
|
+
# Configure the scope file
|
|
221
|
+
scope_file = service.scope_file
|
|
222
|
+
scope_file.unit.description = "Example scope for demonstration purposes"
|
|
223
|
+
scope_file.scope.slice = "example.slice"
|
|
224
|
+
|
|
225
|
+
# Create or update the files
|
|
226
|
+
service.update()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### `example.slice`
|
|
230
|
+
```ini
|
|
231
|
+
[Unit]
|
|
232
|
+
Description = Example slice for demonstration purposes
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### `example.scope`
|
|
236
|
+
```ini
|
|
237
|
+
[Unit]
|
|
238
|
+
Description = Example scope for demonstration purposes
|
|
239
|
+
|
|
240
|
+
[Scope]
|
|
241
|
+
Slice = example.slice
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Deleting a Service
|
|
245
|
+
|
|
246
|
+
To delete a service and its associated files:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
service.delete()
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This will remove all files matching the service name in the systemd directory.
|
|
253
|
+
|
|
254
|
+
## Configuration Options
|
|
255
|
+
|
|
256
|
+
`service-config-foundry` supports multiple systemd file types, including:
|
|
257
|
+
|
|
258
|
+
- `.service` - Main service configuration
|
|
259
|
+
- `.socket` - Socket configuration
|
|
260
|
+
- `.timer` - Timer configuration
|
|
261
|
+
- `.mount` - Mount point configuration
|
|
262
|
+
- `.automount` - Automount configuration
|
|
263
|
+
- `.swap` - Swap space configuration
|
|
264
|
+
- `.path` - Path configuration
|
|
265
|
+
- `.slice` - Slice configuration
|
|
266
|
+
- `.scope` - Scope configuration
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
|
271
|
+
|
|
272
|
+
## Contributing
|
|
273
|
+
|
|
274
|
+
Contributions are welcome! If you encounter any issues or have suggestions for improvement, please open an issue or submit a pull request.
|
|
275
|
+
|
|
276
|
+
## Contact
|
|
277
|
+
|
|
278
|
+
- **Author**: Yush Kapoor
|
|
279
|
+
- **Email**: [yushdotkapoor@gmail.com](mailto:yushdotkapoor@gmail.com)
|
|
280
|
+
- **GitHub**: [https://github.com/yushdotkapoor/service-config-foundry](https://github.com/yushdotkapoor/service-config-foundry)
|
|
281
|
+
|
|
282
|
+
## Acknowledgments
|
|
283
|
+
|
|
284
|
+
Special thanks to the Linux and Python communities for their inspiration and support in creating this project.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from configparser import ConfigParser
|
|
2
|
+
from collections import defaultdict, OrderedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CaseSensitiveConfigParser(ConfigParser):
|
|
6
|
+
def __init__(self, *args, **kwargs):
|
|
7
|
+
# Disable interpolation to prevent issues with list values
|
|
8
|
+
kwargs['interpolation'] = None
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
self._dict = OrderedDict
|
|
11
|
+
|
|
12
|
+
def optionxform(self, optionstr):
|
|
13
|
+
# Preserve case sensitivity for option names
|
|
14
|
+
return optionstr
|
|
15
|
+
|
|
16
|
+
def _read(self, fp, fpname):
|
|
17
|
+
"""Override _read to allow duplicate keys."""
|
|
18
|
+
cur_section = None
|
|
19
|
+
for lineno, line in enumerate(fp, start=1):
|
|
20
|
+
comment_start = line.find("#")
|
|
21
|
+
if comment_start != -1:
|
|
22
|
+
line = line[:comment_start]
|
|
23
|
+
line = line.strip()
|
|
24
|
+
if not line:
|
|
25
|
+
continue
|
|
26
|
+
if line.startswith("[") and line.endswith("]"):
|
|
27
|
+
cur_section = line[1:-1].strip()
|
|
28
|
+
if cur_section not in self._sections:
|
|
29
|
+
self._sections[cur_section] = defaultdict(list)
|
|
30
|
+
else:
|
|
31
|
+
if cur_section is None:
|
|
32
|
+
raise ValueError(f"Missing section header in {fpname} at line {lineno}")
|
|
33
|
+
key, _, value = line.partition("=")
|
|
34
|
+
key = key.strip()
|
|
35
|
+
value = value.strip()
|
|
36
|
+
self._sections[cur_section][key].append(value)
|
|
37
|
+
|
|
38
|
+
def get(self, section, option, *, raw=False, vars=None, fallback=None):
|
|
39
|
+
"""Override `get` to handle list values."""
|
|
40
|
+
if section not in self._sections:
|
|
41
|
+
if fallback is not None:
|
|
42
|
+
return fallback
|
|
43
|
+
raise KeyError(f"Section '{section}' not found")
|
|
44
|
+
if option not in self._sections[section]:
|
|
45
|
+
if fallback is not None:
|
|
46
|
+
return fallback
|
|
47
|
+
raise KeyError(f"Option '{option}' not found in section '{section}'")
|
|
48
|
+
|
|
49
|
+
values = self._sections[section][option]
|
|
50
|
+
if isinstance(values, list):
|
|
51
|
+
# Join list values into a single string for compatibility
|
|
52
|
+
return "\n".join(values) if not raw else values
|
|
53
|
+
return values
|
|
54
|
+
|
|
55
|
+
def items(self, section=None, *, raw=False, vars=None):
|
|
56
|
+
"""Override items to handle list values for both section-specific and global cases."""
|
|
57
|
+
if section is None:
|
|
58
|
+
# Global case: return all sections and their items
|
|
59
|
+
return [(s, dict(self.items(s))) for s in self._sections]
|
|
60
|
+
if section not in self._sections:
|
|
61
|
+
raise KeyError(f"Section '{section}' not found")
|
|
62
|
+
# Section-specific case: return all key-value pairs in the section
|
|
63
|
+
return ((key, "\n".join(values) if isinstance(values, list) else values)
|
|
64
|
+
for key, values in self._sections[section].items())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def write(self, fp):
|
|
68
|
+
"""Override the write method to handle lists for duplicate keys."""
|
|
69
|
+
for section in self._sections:
|
|
70
|
+
fp.write(f"[{section}]\n")
|
|
71
|
+
for key, values in self._sections[section].items():
|
|
72
|
+
for value in values:
|
|
73
|
+
fp.write(f"{key} = {value}\n")
|
|
74
|
+
fp.write("\n")
|