├── .gitignore
├── LICENSE.md
├── README.md
├── config.py
├── icons
├── fail.svg
├── main.svg
└── ok.svg
├── servicectl
├── servicectl.ui
└── systemctl.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /*.pyc
2 | /*~
3 | /install-dev.sh
4 | /README.html
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright © `2016` `Stanislav Tamat`
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the “Software”), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Service Control
2 |
3 | GUI tray program to control systemd (systemctl) services
4 |
5 | 
6 |
7 | ## Dependencies
8 |
9 | In Ubuntu, you need install following package:
10 | ```
11 | sudo apt-get update
12 | sudo apt-get install gir1.2-appindicator3-0.1
13 | ```
14 |
15 | If you get `ImportError: No module named pam` you need install
16 | manually from [Python.org](https://pypi.python.org/pypi/python-pam) or
17 | `pip install python-pam`
18 |
19 | ## License
20 |
21 | Licensed under the [MIT License](https://opensource.org/licenses/mit-license.php)
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | services = {
2 | 'apache2',
3 | 'mongodb',
4 | 'mysql',
5 | 'nginx',
6 | 'php5-fpm',
7 | 'ssh'
8 | }
9 |
10 | services = sorted(services)
11 |
12 | settings = {
13 | 'start_show': True
14 | }
15 |
--------------------------------------------------------------------------------
/icons/fail.svg:
--------------------------------------------------------------------------------
1 |
2 |
57 |
--------------------------------------------------------------------------------
/icons/main.svg:
--------------------------------------------------------------------------------
1 |
2 |
58 |
--------------------------------------------------------------------------------
/icons/ok.svg:
--------------------------------------------------------------------------------
1 |
2 |
57 |
--------------------------------------------------------------------------------
/servicectl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import gi
5 | import systemctl
6 | import config
7 | gi.require_version('Gtk', '3.0')
8 | from gi.repository import Gtk, Gdk, GdkPixbuf
9 |
10 | APPIND_SUPPORT = True
11 | STATE_ID = 2
12 | ICON_MAIN = "icons/main.svg"
13 | ICON_OK = "icons/ok.svg"
14 | ICON_FAIL = "icons/fail.svg"
15 |
16 | try:
17 | gi.require_version('AppIndicator3', '0.1')
18 | from gi.repository import AppIndicator3
19 | except:
20 | APPIND_SUPPORT = False
21 |
22 | class ServiceControl:
23 | def __init__(self, title):
24 | self.systemctl = systemctl.Systemctl()
25 |
26 | self.icon = "%s/%s" % (self.systemctl.get_path(), ICON_MAIN)
27 | self.title = title
28 |
29 | self.builder = Gtk.Builder()
30 | self.builder.add_from_file("servicectl.ui")
31 | handlers = {
32 | "onSelectionChanged": lambda x: self.on_selection_changed(),
33 | "onButtonStartStopClicked": lambda x: self.on_button_click_multi_action("startstop"),
34 | "onButtonRestartClicked": lambda x: self.on_button_click("restart"),
35 | "onButtonReloadClicked": lambda x: self.on_button_click("reload")
36 | }
37 | self.builder.connect_signals(handlers)
38 |
39 | self.menu = self.builder.get_object("popupmenu")
40 |
41 | if APPIND_SUPPORT:
42 | self.tray = AppIndicator3.Indicator.new("servicectl", self.icon, AppIndicator3.IndicatorCategory.SYSTEM_SERVICES)
43 | self.tray.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
44 | self._add_menu_item(self.on_click, "Show/Hide")
45 | self.tray.set_menu(self.menu)
46 | self.tray.set_title(title)
47 | else:
48 | self.tray = Gtk.StatusIcon()
49 | self.tray.set_from_file(self.icon)
50 | self.tray.connect('activate', self.on_click)
51 | self.tray.connect('popup-menu', self.on_r_click)
52 | self.tray.set_tooltip_text(title)
53 |
54 | self._add_menu_item(self.about, "About")
55 | self._add_menu_item(Gtk.main_quit, "Quit")
56 |
57 | self.window = self.builder.get_object("window")
58 | self.treeview = self.builder.get_object("treeview")
59 | self.model = self.treeview.get_model()
60 |
61 | col_state = self.builder.get_object("col-state")
62 | self.cell_state = self.builder.get_object("cell-state")
63 | col_state.set_cell_data_func(self.cell_state, self._render_icon)
64 |
65 | for service in config.services:
66 | description = self.systemctl.description(service)
67 | state = self.systemctl.is_active(service)
68 | self.model.append((service, description, state))
69 |
70 | hide_btn_image = Gtk.Image.new_from_icon_name("window-close-symbolic", Gtk.IconSize.MENU)
71 | hide_btn = Gtk.Button(image=hide_btn_image)
72 | hide_btn.connect_object("clicked", lambda x: self.hide(), self.window)
73 |
74 | headerbar = Gtk.HeaderBar()
75 | headerbar.set_show_close_button(False)
76 | headerbar.set_title(title)
77 | headerbar.pack_end(hide_btn)
78 |
79 | self.window.set_titlebar(headerbar)
80 | self.window.set_icon_from_file(self.icon)
81 | self.window.show_all()
82 |
83 | #################
84 | ## EVENTS HANDLE
85 | #################
86 |
87 | def on_click(self, w):
88 | if self.window.get_property("visible"):
89 | self.hide()
90 | else:
91 | self.show()
92 |
93 | def on_r_click(self, icon, button, time):
94 | self._get_tray_menu()
95 |
96 | def pos(menu, aicon):
97 | return (Gtk.StatusIcon.position_menu(menu, aicon))
98 |
99 | self.menu.popup(None, None, pos, icon, button, time)
100 |
101 | def on_button_click_multi_action(self, action):
102 | if action == "startstop":
103 | if self.current_state:
104 | action = "stop"
105 | else:
106 | action = "start"
107 | self.on_button_click(action)
108 |
109 | def on_button_click(self, action):
110 | if not hasattr(self, "pwd"):
111 | self.pwd = self.get_pwd()
112 | if self.systemctl.chk_pwd(self.pwd):
113 | while self.systemctl.run(self.pwd, action, self.current_service):
114 | pass
115 | self.update()
116 | else:
117 | delattr(self, "pwd")
118 | self.incorrect_pwd()
119 |
120 | def on_selection_changed(self):
121 | treeselection = self.builder.get_object("treeview-selection")
122 | (model, iter) = treeselection.get_selected()
123 | self.current_iter = iter
124 | self.current_service = model.get_value(iter, 0)
125 | self.current_state = model.get_value(iter, STATE_ID)
126 | self._buttons_toogle(self.current_state)
127 |
128 | def on_key_release(self, w, e):
129 | if e.keyval == Gdk.KEY_Return:
130 | w.response(Gtk.ResponseType.OK)
131 | if e.keyval == Gdk.KEY_Escape:
132 | w.destroy()
133 |
134 | ###########
135 | ## ACTIONS
136 | ###########
137 |
138 | def update(self):
139 | state = self.systemctl.is_active(self.current_service)
140 | icon = self.cell_state.get_property('pixbuf')
141 | self.model.set_value(self.current_iter, STATE_ID, state)
142 | self._render_icon(STATE_ID, self.cell_state, self.model, self.current_iter, icon)
143 | self.on_selection_changed()
144 |
145 | def show(self):
146 | self.window.show_all()
147 |
148 | def hide(self):
149 | self.window.hide()
150 |
151 | ##########
152 | ## DIALOG
153 | ##########
154 |
155 | def about(self, w):
156 | title = "About | %s" % (self.title,)
157 | dialog = self.builder.get_object("aboutdialog")
158 |
159 | headerbar = Gtk.HeaderBar()
160 | headerbar.set_show_close_button(False)
161 | headerbar.set_title(title)
162 | headerbar.show()
163 |
164 | pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon)
165 |
166 | dialog.set_titlebar(headerbar)
167 | dialog.set_logo(pixbuf)
168 | dialog.run()
169 | dialog.hide()
170 |
171 | def get_pwd(self):
172 | title = "Password | %s" % (self.title,)
173 |
174 | dialog = self.builder.get_object("getpwddialog")
175 | dialog.connect("key-release-event", self.on_key_release)
176 |
177 | headerbar = Gtk.HeaderBar()
178 | headerbar.set_show_close_button(False)
179 | headerbar.set_title(title)
180 | headerbar.show()
181 |
182 | dialog.set_titlebar(headerbar)
183 | dialog.show_all()
184 |
185 | response = dialog.run()
186 | entry = self.builder.get_object("entry")
187 | password = entry.get_text()
188 |
189 | dialog.hide()
190 |
191 | if (response == Gtk.ResponseType.OK) and (password != ''):
192 | return password
193 | return None
194 |
195 | def incorrect_pwd(self):
196 | title = "Error | %s" % (self.title,)
197 |
198 | dialog = self.builder.get_object("incorrectpwddialog")
199 | dialog.connect("key-release-event", self.on_key_release)
200 |
201 | headerbar = Gtk.HeaderBar()
202 | headerbar.set_show_close_button(False)
203 | headerbar.set_title(title)
204 |
205 | dialog.set_titlebar(headerbar)
206 | dialog.show_all()
207 | dialog.run()
208 | dialog.hide()
209 |
210 | ########
211 | ## MISC
212 | ########
213 | def _render_icon(self, column, cell, model, iter, icon):
214 | data = self.model.get_value(iter, STATE_ID)
215 | if data:
216 | icon = ICON_OK
217 | else:
218 | icon = ICON_FAIL
219 |
220 | pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon, width=22, height=22, preserve_aspect_ratio=False)
221 | cell.set_property('pixbuf', pixbuf)
222 |
223 | def _buttons_toogle(self, state):
224 | button_enable_disable = self.builder.get_object('button_enable_disable')
225 | button_start_stop = self.builder.get_object('button_start_stop')
226 | button_restart = self.builder.get_object('button_restart')
227 | button_reload = self.builder.get_object('button_reload')
228 | if state:
229 | button_restart.set_sensitive(True)
230 | button_reload.set_sensitive(True)
231 | else:
232 | button_restart.set_sensitive(False)
233 | button_reload.set_sensitive(False)
234 |
235 | def _add_menu_item(self, command, title):
236 | menuitem = Gtk.MenuItem()
237 | menuitem.set_label(title)
238 | menuitem.connect("activate", command)
239 |
240 | self.menu.append(menuitem)
241 | self.menu.show_all()
242 |
243 | def _get_tray_menu(self):
244 | return self.menu
245 |
246 | def main(self):
247 | Gtk.main()
248 |
249 | if __name__ == '__main__':
250 | app = ServiceControl("Service Control")
251 | if config.settings["start_show"]:
252 | app.show()
253 | app.main()
254 |
--------------------------------------------------------------------------------
/servicectl.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
152 |
153 | False
154 | False
155 | True
156 | normal
157 | west
158 | window
159 | Service Control
160 | 0.5
161 | (C) 2016 Stanislav Tamat
162 | https://github.com/YokiToki/service_ctl
163 | https://github.com/YokiToki/service_ctl
164 | Stanislav Tamat <libastral.so@yandex.ru>
165 | mit-x11
166 |
167 |
168 | False
169 | vertical
170 | 2
171 |
172 |
173 | False
174 | end
175 |
176 |
177 | False
178 | False
179 | 0
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | False
190 | False
191 | True
192 | dialog
193 | window
194 | question
195 | ok-cancel
196 | This application lets you modify essential parts of your system.
197 |
198 |
199 | False
200 | vertical
201 | 2
202 |
203 |
204 | False
205 | True
206 | expand
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | False
216 | False
217 | 0
218 |
219 |
220 |
221 |
222 | True
223 | True
224 | 10
225 | 10
226 | False
227 | *
228 | password
229 |
230 |
231 | False
232 | True
233 | 2
234 |
235 |
236 |
237 |
238 |
239 |
240 | False
241 | False
242 | True
243 | dialog
244 | window
245 | ok
246 | You have entered the wrong password. Please, try again.
247 |
248 |
249 | False
250 | vertical
251 | 2
252 |
253 |
254 | False
255 | True
256 | expand
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | False
266 | False
267 | 0
268 |
269 |
270 |
271 |
272 |
273 |
277 |
278 |
--------------------------------------------------------------------------------
/systemctl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 |
6 | PAM_LIB = True
7 | try:
8 | import PAM
9 | except ImportError:
10 | PAM_LIB = False
11 | import pam
12 |
13 | class Systemctl():
14 |
15 | def get_path(self):
16 | return os.path.dirname(os.path.realpath(__file__))
17 |
18 | def run(self, pwd, action, service):
19 | os.system('echo \'%s\'|sudo -S systemctl %s %s.service' % (pwd, action, service))
20 |
21 | def is_enabled(self, service):
22 | out = os.popen("systemctl is-enabled %s.service" % (service,)).read().strip()
23 | if out == 'enabled':
24 | return True
25 | return False
26 |
27 | def is_active(self, service):
28 | out = os.popen("systemctl is-active %s.service" % (service,)).read().strip()
29 | if out == 'active':
30 | return True
31 | return False
32 |
33 | def description(self, service):
34 | return os.popen("systemctl show -p Description %s.service" % (service,)).read().replace("Description=", "").strip()
35 |
36 | def chk_pwd(self, pwd):
37 | user = os.getenv('USER')
38 | if pwd == None:
39 | return False
40 | if PAM_LIB:
41 | def pam_conv(a, q, d):
42 | return [(pwd, 0)]
43 |
44 | auth = PAM.pam()
45 | auth.start("passwd")
46 | auth.set_item(PAM.PAM_USER, user)
47 | auth.set_item(PAM.PAM_CONV, pam_conv)
48 | try:
49 | auth.authenticate()
50 | auth.acct_mgmt()
51 | except:
52 | return False
53 | else:
54 | return True
55 |
56 | else:
57 |
58 | auth = pam.pam()
59 | return auth.authenticate(user, pwd)
60 |
--------------------------------------------------------------------------------