├── .gitignore
├── LICENSE
├── _demo
├── _com.devework.onnetworkchange.plist
├── _run.applescript
└── readme.md
├── _screenshots
├── first.png
├── install.png
├── notice.png
└── notice2.png
├── example.sh
├── install.py
├── readme.md
└── readme.zh.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | dynamic.sh
3 | test.*
4 | demo.scpt
5 |
6 | ./run.applescript
7 | ./com.devework.onnetworkchange.plist
8 |
9 | !_demo/run.applescript
10 | !_demo/com.devework.onnetworkchange.plist
11 |
12 | *.log
13 |
14 | dynamic-tc.sh
15 |
16 | com.devework.onnetworkchange.plist
17 |
18 | run.applescript
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Jeff Ma
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.
22 |
--------------------------------------------------------------------------------
/_demo/_com.devework.onnetworkchange.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Label
6 | com.devework.onnetworkchange
7 | ProgramArguments
8 |
9 | /usr/bin/osascript
10 | /Users/Jeff/Documents/workspace/launchd-with-networkchange/run.applescript
11 |
12 | StandardOutPath
13 | /Users/Jeff/Documents/workspace/launchd-with-networkchange/onnetworkchange.log
14 | StandardErrorPath
15 | /Users/Jeff/Documents/workspace/launchd-with-networkchange/onnetworkchange.err.log
16 | WatchPaths
17 |
18 | /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist
19 | /Library/Preferences/SystemConfiguration/com.apple.wifi.message-tracer.plist
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/_demo/_run.applescript:
--------------------------------------------------------------------------------
1 | # get account password from Keychain
2 | set _username to "JeffMa"
3 | set _password to do shell script "/usr/bin/security find-generic-password -l 'launchd with networkchange' -a " & _username & " -w || echo denied"
4 |
5 | # failed to get password
6 | if _password is "denied" then
7 | display dialog "Failed to get the password from Keychain" buttons {"OK"}
8 | return
9 | end if
10 |
11 | set current_path to (POSIX path of ((path to me as text) & "::"))
12 |
13 | on FileExists(theFile) -- (String) as Boolean
14 | tell application "System Events"
15 | if exists file theFile then
16 | return true
17 | else
18 | return false
19 | end if
20 | end tell
21 | end FileExists
22 |
23 | if FileExists(current_path & "/dynamic.sh") then
24 | set file_path to ("'" & current_path & "/dynamic.sh" & "'")
25 | do shell script file_path user name _username password _password with administrator privileges
26 | else
27 | set file_path to ("'" & current_path & "/example.sh" & "'")
28 | do shell script file_path user name _username password _password with administrator privileges
29 | end if
30 |
31 |
--------------------------------------------------------------------------------
/_demo/readme.md:
--------------------------------------------------------------------------------
1 | This directory shows the result demo, which is what happen when you run `python install.py`.
--------------------------------------------------------------------------------
/_screenshots/first.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff2Ma/launchd-with-networkchange/5e0673c0f4f67e713782619c4328084f16f2b7da/_screenshots/first.png
--------------------------------------------------------------------------------
/_screenshots/install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff2Ma/launchd-with-networkchange/5e0673c0f4f67e713782619c4328084f16f2b7da/_screenshots/install.png
--------------------------------------------------------------------------------
/_screenshots/notice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff2Ma/launchd-with-networkchange/5e0673c0f4f67e713782619c4328084f16f2b7da/_screenshots/notice.png
--------------------------------------------------------------------------------
/_screenshots/notice2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff2Ma/launchd-with-networkchange/5e0673c0f4f67e713782619c4328084f16f2b7da/_screenshots/notice2.png
--------------------------------------------------------------------------------
/example.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # author: JeffMa
4 | # created: 2016.11.17
5 | # launchd-with-networkchange
6 |
7 | # proxifier
8 | proxifier_quit()
9 | {
10 | ps -ef | grep Proxifier | awk '{print $2}' | xargs kill
11 | }
12 |
13 | # WORK MOD
14 | at_work_mod()
15 | {
16 | # empty dns servers when connect office wifi
17 | /usr/sbin/networksetup -setdnsservers Wi-Fi Empty
18 |
19 | # set pac url for work place Wi-Fi
20 | # slient
21 | osascript -e "set Volume 0"
22 |
23 | osascript <
62 |
63 |
64 |
65 | Label
66 | com.devework.onnetworkchange
67 | ProgramArguments
68 |
69 | /usr/bin/osascript
70 | %s
71 |
72 | StandardOutPath
73 | %s
74 | StandardErrorPath
75 | %s
76 | WatchPaths
77 |
78 | /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist
79 | /Library/Preferences/SystemConfiguration/com.apple.wifi.message-tracer.plist
80 |
81 |
82 | """ % (appscript_file_path, stdout_log, stderr_log)
83 |
84 | with open('com.devework.onnetworkchange.plist', 'w') as f:
85 | f.write(file_content)
86 |
87 | print "[2] Create `com.devework.onnetworkchange.plist` file with success!"
88 |
89 | def create_applescript_file(username):
90 | file_content ="""# get account password from Keychain
91 | set _username to "%s"
92 | set _password to do shell script "/usr/bin/security find-generic-password -l 'launchd with networkchange' -a " & _username & " -w || echo denied"
93 |
94 | # failed to get password
95 | if _password is "denied" then
96 | display dialog "Failed to get the password from Keychain" buttons {"OK"}
97 | return
98 | end if
99 |
100 | set current_path to (POSIX path of ((path to me as text) & "::"))
101 |
102 | on FileExists(theFile) -- (String) as Boolean
103 | tell application "System Events"
104 | if exists file theFile then
105 | return true
106 | else
107 | return false
108 | end if
109 | end tell
110 | end FileExists
111 |
112 | if FileExists(current_path & "/dynamic.sh") then
113 | set file_path to ("'" & current_path & "/dynamic.sh" & "'")
114 | do shell script file_path user name _username password _password with administrator privileges
115 | else
116 | set file_path to ("'" & current_path & "/example.sh" & "'")
117 | do shell script file_path user name _username password _password with administrator privileges
118 | end if
119 |
120 | """ % username
121 |
122 | with open('run.applescript', 'w') as f:
123 | f.write(file_content)
124 |
125 | print "[3] Create `run.applescript` file with success!"
126 |
127 | def ln_s_file():
128 | plist_file_path = dir_path + "/com.devework.onnetworkchange.plist"
129 | shell_commend = "ln -s %s ~/Library/LaunchAgents/" % plist_file_path
130 | p = subprocess.Popen(shell_commend, shell=True, stdout=subprocess.PIPE)
131 | p.communicate()
132 | if p.returncode != 0:
133 | print "Failed to run `ln -s` script!"
134 | else:
135 | print "[4] Run `ln -s` script well."
136 |
137 | def load_plist():
138 | shell_commend2 = "launchctl load -w ~/Library/LaunchAgents/com.devework.onnetworkchange.plist"
139 | p = subprocess.Popen(shell_commend2, shell=True, stdout=subprocess.PIPE)
140 | p.communicate()
141 | if p.returncode != 0:
142 | print "Failed to launchctl load!"
143 | else:
144 | print "[5] Launchctl load successfully!"
145 |
146 | def unload_plist():
147 | shell_commend3 = "launchctl unload ~/Library/LaunchAgents/com.devework.onnetworkchange.plist && rm -rf ~/Library/LaunchAgents/com.devework.onnetworkchange.plist"
148 | p = subprocess.Popen(shell_commend3, shell=True, stdout=subprocess.PIPE)
149 | p.communicate()
150 | if p.returncode != 0:
151 | print "Failed to unload plist!"
152 | else:
153 | print "Unload launchctl done."
154 |
155 | def main_install():
156 | script_header()
157 |
158 | user_name = raw_input("Please input your user name: ")
159 | password = getpass.getpass("Please input your password: ")
160 | script_one = add_to_keychain(user_name, password)
161 | if not script_one:
162 | print "Failed to add account to Keychain, please run this script again."
163 | delete_from_keychain(user_name)
164 | else:
165 | print "[1] Your user name is %s, Your password will be saved to keychain safely." % user_name
166 |
167 | create_plist_file()
168 | create_applescript_file(user_name)
169 | ln_s_file()
170 | load_plist()
171 | script_footer()
172 |
173 | if len(sys.argv) == 2 and sys.argv[1] == "debug":
174 | delete_from_keychain(user_name)
175 | unload_plist()
176 | print "Debug Mod Done."
177 |
178 | def main_uninstall():
179 | user_name = raw_input("Please input your user name: ")
180 | script_two = delete_from_keychain(user_name)
181 | if not script_two:
182 | print "Some thing wrong when delete account from keychain."
183 | else:
184 | print "Deleted account from keychain."
185 | unload_plist()
186 | print "All done!"
187 |
188 | if __name__ == "__main__":
189 | if len(sys.argv) == 1 or sys.argv[1] == "debug":
190 | main_install()
191 | elif sys.argv[1] == "uninstall":
192 | main_uninstall()
193 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # launchd with networkchange
2 |
3 | A tool with shell script and launchd to trigger actions whenever a Mac's network information is changed.
4 |
5 | [[简体中文版使用说明] 请点击这里](https://github.com/Jeff2Ma/launchd-with-networkchange/blob/master/readme.zh.md)
6 |
7 | ## Introduction
8 |
9 | This repo is an easy way to deal with contexts like:
10 |
11 | > At workplace, your Mac Device has to make some settings such as change proxy address, set specical pac file, open some apps and so on. While at home your have change it again to others.
12 |
13 | With the help of [launchd](https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html), those operations can be automated. And that's what this repo do! It is fast, easy and safe.
14 |
15 | ## Features
16 |
17 | - Easy to install and uninstall.
18 |
19 | - Password of your system account will be saved in keychain safely.
20 |
21 | - Add example so that your can easily change the code according to your conditions.
22 |
23 | ## How to start
24 |
25 | ```bash
26 | $ git clone https://github.com/Jeff2Ma/launchd-with-networkchange
27 |
28 | $ cd launchd-with-networkchange
29 |
30 | $ python install.py
31 | ```
32 | Then input the info when is asked.
33 |
34 | 
35 |
36 | After that, please edit the code in `example.sh ` according to your conditions.
37 |
38 | When your Mac's network change, a notification will show when it run shell script with success.
39 |
40 | 
41 |
42 | ## Notices
43 |
44 | 1) When the first run of the script, system will ask you like:
45 |
46 | 
47 |
48 | Remember to choose `Always Allow`.
49 |
50 | 2)you can make a `example.sh` copy one and rename it to `dynamic.sh`, it will be run instead of `example.sh`[see how it work](https://github.com/Jeff2Ma/launchd-with-networkchange/blob/master/_demo/_run.applescript#L23-L29).
51 |
52 | 3) If you want to uninstall it, you can run `python install.py uninstall`.
53 |
54 | 4) Check `/var/log/system.log` if you are having issues with `plist`.
55 |
56 | ## Contributing
57 |
58 | Thanks to this [Repo](https://github.com/tjluoma/onnetworkchange) and this [Alfred Workflow](https://github.com/Jeff2Ma/AlfredWorkflow-DuoTai-Helper) to provide inspiration for me.
59 |
60 | [Issues](https://github.com/Jeff2Ma/launchd-with-networkchange/issues) and [Pull requests](https://github.com/Jeff2Ma/launchd-with-networkchange/pulls) are warm welcome.
--------------------------------------------------------------------------------
/readme.zh.md:
--------------------------------------------------------------------------------
1 | # launchd with networkchange
2 |
3 | 一个借助`launchd` 实现Mac 系统中网络变化时自动触发并运行指定脚本的工具。
4 |
5 | ## 介绍
6 |
7 | 本项目旨在通过自动化的方式解决如下场景:
8 |
9 | > 在工作的时候,你的Mac 设备(特指MacBook、MacBook Air 或MacBook Pro)需要进行一些特殊的环境设置(比如修改代理地址,设置特殊的PAC 文件,需要专门打开某些应用,甚至是默认设备静音)。当下班回到家的时候,这些网络设置需要重新改变以适应你的家庭网络。然而第二天上班又要再次修改,如此往往复复。
10 |
11 | 借助Mac 中的`launchd`(一个类似 crontab 的执行定时任务的东西),我们可以通过监控网络的变化(对于上面的场景具体而言是监控SSID 的变化)来自动触发运行指定脚本。本项目即是这么一个帮你快速配置这个`launchd`服务的一个工具。
12 |
13 | **相关文章**:《[高效 Mac 人士必备:实现工作/家庭间网络环境切换的自动化](http://devework.com/mac-automatic-network.html)》
14 |
15 | ## 原理
16 |
17 | 在Mac 上,当网络有变化的时候(如关闭/开启WiFi,连接到不同SSID 等),`/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist`这个文件就会有变化。因此借助`launchd`服务中的`WatchPaths`特征,就可以实现监控到文件变化的时候执行指定的脚本文件。
18 |
19 | ## 特色
20 |
21 | 本工具具有如下特色:
22 |
23 | - 快速帮你进行配置`launchd`服务,免除繁琐的步骤,同时当你不喜欢的时候可以很方便地卸载。
24 |
25 | - 因为涉及到网络变动的操作,所以需要用户权限。本工具会将你的用户密码保存在keychain 中,提高安全性。
26 |
27 | - 提供了一个脚本文件(`example.sh`),里面的代码可以方便按照个人需求进行改写。
28 |
29 | ## 使用方法
30 |
31 | ```bash
32 | $ git clone https://github.com/Jeff2Ma/launchd-with-networkchange
33 |
34 | $ cd launchd-with-networkchange
35 |
36 | $ python install.py
37 | ```
38 | 按照说明输入用户名及密码。
39 |
40 | 
41 |
42 | 如上,成功完成后请根据个人实际情况改写`example.sh`的代码。
43 |
44 | 网络变化的时候shell 文件就会自动运行,运行成功则会右上角提示如下:
45 |
46 | 
47 |
48 | ## 提示
49 |
50 | 1) 完成上面的步骤之后,首次自动运行脚本的时候系统会弹窗如下:
51 |
52 | 
53 |
54 | 请选择“**始终允许**”按钮。
55 |
56 | 2) 如果需要卸载已经生成的`launchd`服务,执行`python install.py uninstall`即可。
57 |
58 | ## 自定义代码
59 |
60 | 以下是一些可能用到的Shell 代码帮助你根据个人实际需求定制运行的Shell 基本文件。
61 |
62 | 提示:你可以将`example.sh`文件复制一份重命名为`dynamic.sh`,直接在`dynamic.sh`上改写代码,两文件同时存在的时候会优先运行`dynamic.sh`(实现方式在[此处源代码](https://github.com/Jeff2Ma/launchd-with-networkchange/blob/master/_demo/_run.applescript#L23-L29))。
63 |
64 | ### 设置PAC 文件路径
65 |
66 | ```bash
67 | /usr/sbin/networksetup -setautoproxyurl Wi-Fi http://example.com/proxy.pac
68 | ```
69 |
70 | ### 运行或关闭某些APP
71 |
72 | 本质上是调用 applescript 命令
73 |
74 | ```bash
75 | osascript <