試圖在Fedora26上使用Rime輸入法,經過數小時嘗試都不能完整打出自己想要的句子,只能吐血半升後放棄。IBus Intelligent Pinyin雖然有不少缺點也傻傻的,但仍然是我目前遇到最好用的開源拼音輸入法。查看源碼發現已經智能拼音已經更新到了1.10.92,在Fedora26上嘗試編譯失敗後(一天內兩次失敗,真是受打擊?),決定升級Fedora。
迅速新裝了一個Fedora 29 Xfce,很好用,美滋滋。
試圖在Fedora26上使用Rime輸入法,經過數小時嘗試都不能完整打出自己想要的句子,只能吐血半升後放棄。IBus Intelligent Pinyin雖然有不少缺點也傻傻的,但仍然是我目前遇到最好用的開源拼音輸入法。查看源碼發現已經智能拼音已經更新到了1.10.92,在Fedora26上嘗試編譯失敗後(一天內兩次失敗,真是受打擊?),決定升級Fedora。
迅速新裝了一個Fedora 29 Xfce,很好用,美滋滋。
注:本文旨在學習研究wifi熱點相關知識,請勿用於任何違反道德之事。
需要一台Root過的Android(系統版本不低於5.0)手機,安裝好Termux和python。實現Captive Portal需要兩部分,請求重定向和授權頁面。重定向的實現有多種,這裡既然是假熱點,所以使用最簡單的iptables重定向dns。授權頁面則直接採用jczic/MicroWebSrv實現的網頁服務器。把MicroWebSrv解壓到~/scripts/MicroWeb,然後新建如下文件
#~/scripts/MicroWeb/forward.sh
remove127(){
/system/bin/iptables -t nat -D PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.43.1:8000&&/system/bin/iptables -t nat -D PREROUTING -p tcp --dport 443 -j DNAT --to-destination 192.168.43.1:8000
}
add127(){
/system/bin/iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.43.1:8000&&/system/bin/iptables -t nat -I PREROUTING -p tcp --dport 443 -j DNAT --to-destination 192.168.43.1:8000
}
if [[ $1 = "1" ]]; then
echo "start"
add127
elif [[ $1 = "0" ]]; then
echo "stop"
remove127
fi
#~/scripts/MicroWeb/web.py
from microWebSrv import MicroWebSrv
mws = MicroWebSrv(port=8000,bindIP='0.0.0.0',webPath="/data/data/com.termux/files/home/scripts/MicroWeb/www")
mws.SetNotFoundPageUrl("http://192.168.43.1:8000/hotspot-detect.html")
mws.Start(False)
#~/scripts/MicroWeb/www/hotspot-detect.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
<meta name="format-detection" content="telephone=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>Captive Portal</title>
<script>
/*!
* clipboard.js v2.0.0
* https://zenorocha.github.io/clipboard.js
*
* Licensed MIT © Zeno Rocha
*/
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(e,n,o){return n&&t(e.prototype,n),o&&t(e,o),e}}(),a=function(){function t(e){n(this,t),this.resolveOptions(e),this.initSelection()}return i(t,[{key:"resolveOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n},function(t,e,n){var o,r,i;!function(a,c){r=[t,n(0),n(2),n(1)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e,n,o){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function c(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function u(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}var l=r(e),s=r(n),f=r(o),d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},h=function(){function t(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(e,n,o){return n&&t(e.prototype,n),o&&t(e,o),e}}(),p=function(t){function e(t,n){i(this,e);var o=a(this,(e.__proto__||Object.getPrototypeOf(e)).call(this));return o.resolveOptions(n),o.listenClick(t),o}return c(e,t),h(e,[{key:"resolveOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])});
</script>
<style>
.wrapper {
text-align: center;
}
.button {
position: absolute;
top: 50%;
margin-top:2em;
}
</style>
</head>
<body>
<div class="wrapper">
<button class="btn" id="clickButton" data-clipboard-text="just kidding :)">
Connect to the Internet
</button>
</div>
<script>
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
e.clearSelection();
});
clipboard.on('error', function(e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});
</script>
</body>
</html>
上面文件唯一需要修改的地方是你手機的wifi網關,可以在打開手機熱點後通過ip addr命令查看,我的是wlan0網卡,網關是192.168.43.1。
#iptable需要root權限 su ./forward.sh 1 #然后新开一个session運行web server python web.py #這時其他手機連上此熱點,即會彈出自定義頁面hotspot-detect.html。
本文更新於 2018/11/02。
安卓手機如果root了,便可以通過AFWall+(Github項目地址)來使用iptables管控網絡。
由於TCP旁路阻斷技術的應用,DNSCrypt已經不能為日常使用網絡帶來太多方便,所以僅僅是記錄一下使用方法而已。先安裝好AFWall+並根據自己的喜好定製軟件的聯網規則,然後啟用iptables。我們使用Termux來至執行iptabls命令。根據自己的手機系統下載dnscrypt-proxy,比如我的就是dnscrypt-proxy-android_arm64-2.0.17.zip。如果不知道自己的手機系統,可以運行uname -a,如果出現aarch64就跟我一樣下載android_arm64版本就行。下載後解壓到/data/data/com.termux/files/home/opt/dns目錄里。複製一份文件夾里的配置文件即cp example-dnscrypt-proxy.toml dnscrypt-proxy.toml就可以了,配置也可以參考Fedora使用DNSCrypt。下面的文件我是放在家目錄下的,文件名是dns.sh,記得加上執行權限。
remove127(){
/system/bin/iptables -t nat -D OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:53&&/system/bin/iptables -t nat -D OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:53&&/system/bin/iptables -t nat -D OUTPUT -d 114.114.114.114 -p tcp --dport 53 -j DNAT --to-destination 114.114.114.114:53&&/system/bin/iptables -t nat -D OUTPUT -d 114.114.114.114 -p udp --dport 53 -j DNAT --to-destination 114.114.114.114:53
}
add127(){
/system/bin/iptables -t nat -I OUTPUT -d 114.114.114.114 -p tcp --dport 53 -j DNAT --to-destination 114.114.114.114:53&&/system/bin/iptables -t nat -I OUTPUT -d 114.114.114.114 -p udp --dport 53 -j DNAT --to-destination 114.114.114.114:53&&/system/bin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:53&&/system/bin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:53
}
startDns(){
/data/data/com.termux/files/home/opt/dns/dnscrypt-proxy -config /data/data/com.termux/files/home/opt/dns/dnscrypt-proxy.toml > /dev/null 2>1 &
echo "starting dns"
sleep 15
}
stopDns(){
pkill dnscrypt-proxy
echo "killed dns"
}
test(){
var1=$(su -c "/system/bin/iptables -C OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:53")
}
status(){
vt1="$(ps -ef | grep dnscrypt-proxy | wc -l)"
if [[ $vt1 = "2" ]]; then
dnsproxyStatus="1"
else
dnsproxyStatus="0"
fi
vt2="$(/system/bin/iptables -t nat -L | grep 127.0.0.1:53 | wc -l)"
if [[ $vt2 = "2" ]]; then
iptablesStatus="1"
else
iptablesStatus="0"
fi
}
if [[ $1 = "1" ]]; then
echo "start"
remove127
sleep 5
startDns
add127
elif [[ $1 = "2" ]]; then
echo "restart"
remove127
stopDns
sleep 3
startDns
add127
elif [[ $1 = "0" ]]; then
echo "stop"
remove127
stopDns
elif [[ $1 = "s" ]]; then
status
echo "dns: $dnsproxyStatus | iptables: $iptablesStatus"
else
status
if [[ $dnsproxyStatus = "1" ]]; then
echo "$(date) dns ok" >> /data/data/com.termux/files/home/log.log
else
remove127
stopDns
remove127
sleep 5
startDns
add127
echo "$(date) dns started" >> /data/data/com.termux/files/home/log.log
fi
fi
#使用說明 #首先切換的超級用戶 su #查看當前狀態,1代表開啟,0代表關閉 ./dns.sh s #關閉dnscrypt-proxy ./dns.sh 0 #開啟dnscrypt-proxy ./dns.sh 1 #重新啟動dnscrypt-proxy ./dns.sh 2
啟動完成後可以通過dnsleaktest.com來查看當前的本機的dns。本來腳本還做了自啟動的適配,但是無論是Termux的自動啟動還是Magisk的自動啟動,都不太好用就算了。
本文更新於 2019/08/08。
下面腳本實現定時檢測tomcat是否還活着,如果死了就啟動它,如果活太久了(24小時)就殺掉再啟動。
#!/data/pythons/p35/bin/python
# -*- coding: utf8 -*-
#定時任務 /etc/crontab
#*/5 * * * * root (/data/pythons/p36/bin/python /data/pythons/scripts/serviceDeamon.py tomcat-8780)
import psutil,time,datetime,subprocess,sys
def log42(logFile,logText):
ts = int(time.time())
dt = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
with open("/tmp/"+dt[0:10]+logFile+".log", "a") as myfile:
myfile.write(dt+" "+logText+"\n")
def startByCmdline(cmdlineKeyword):
command = 'export JAVA_HOME=/usr/local/java/jdk1.7.0_71;/bin/sh /data/web/'+cmdlineKeyword+'/bin/startup.sh'
status,output = subprocess.getstatusoutput(command)
log42('service'+cmdlineKeyword,"started"+ str(status))
def killByCmdline(cmdlineKeyword):
alive = False
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=['pid', 'name','cmdline','create_time','ppid'])
except psutil.NoSuchProcess:
pass
else:
if any('/'+cmdlineKeyword in s for s in pinfo['cmdline']):
alive = True
aliveTime = time.time()-pinfo['create_time']
if aliveTime > 60*60*24:
log42('service'+cmdlineKeyword,"old enough "+str(aliveTime/60))
p = psutil.Process(pinfo['pid'])
p.kill()
time.sleep(3)
startByCmdline(cmdlineKeyword)
else:
log42('service'+cmdlineKeyword,"too young "+str(aliveTime/60))
else:
pass
if not alive :
log42('service'+cmdlineKeyword,"died")
startByCmdline(cmdlineKeyword)
if __name__ == '__main__':
#'tomcat-8780'
if str(sys.argv[1]).__contains__('tomcat') :
killByCmdline(str(sys.argv[1]))
else:
log42('service',"wrong param")
print('param like:tomcat-8780')
新西蘭簽證的網上送簽很方便,只要將資料拍成照片轉成pdf上傳就可以了。在Fedora上,可能需要如下命令來完成。
為了使用convert將jpg轉換pdf,需要安裝ImageMagick sudo dnf install ImageMagick #轉換單張jpg為pdf convert -density 200 Deposit.JPG Deposit.pdf #轉換多個jpg到一個pdf,如果超過10個,前面的命名要為Passport01.JPG,Passport02.JPG…… convert -density 200 Passport*.JPG Passport.pdf #壓縮pdf,/ebook可替換為/prepress、/screen獲得更大或更小文檔大小 gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile=PassportMini.pdf Passport.pdf #如果要對pdf進行反轉或增刪頁面,可以安裝圖形界面pdfmod sudo dnf install pdfmod
順帶一提,我嘗試在Windows下使用免費的PDFCreator(V3.1.2)來將jpg轉為pdf,但是轉出的pdf有3條奇怪的豎線。
後記:再次誇一誇新西蘭的在線簽證,三天就出簽了,而且一下子給了5年,真是非常夠意思!
還有一個簡單方便的在線工具 Smallpdf 也能完成上述工作,壓縮效果也非常顯著。免費用戶有當天次數限制,超出的話請付費支持。如果實在囊中羞澀,可以用瀏覽器的隱私模式增加次數。
雖然可以用 MuPDF mini 在安卓手機上查看 pdf 文件,但是如果有很多文件,開開關關還是不太方便。轉成圖片就可以一個一個瀏覽所有文件。這裏使用 ImageMagick 來完成轉換,Fedora 似乎已經預裝了,如果系統沒有安裝的話可以看看 ImageMagick 的官方網站。在 Alma Linux 上安裝的命令為 sudo dnf install -y ImageMagick 。
mogrify -density 300 -format png -background white -alpha remove -alpha off *.pdf
本文更新於 2025/01/18。
之前寫過一個python腳本通過paramiko使用SSH鏈接越獄後的iPhone執行一些安裝軟件或重啟SpringBoard之類的操作。後來遇到Termux覺得安卓手機也可以試試,不試不知道,一試坑不少,好在最後還是裝好了。
#首先安裝系統的依賴 apt install libffi-dev clang libsodium libsodium-dev openssl-dev libcrypt-dev python-dev #然後安裝pynacl,直接pip裝會報錯,指定使用系統的sodium庫即可 SODIUM_INSTALL=system pip install pynacl #最後再安裝paramiko和python-nmap pip install paramiko python-nmap
本文更新於 2018/04/04。
DNSCrypt是一種認證DNS客戶端和DNS解析器之間通信的協議。 它可以防止DNS欺騙。下面記錄下在Fedora25上使用DNSCrypt的步驟。
dnscrypt-proxy 2 是一個更棒的升級,如果你用的是v1,非常建議升級。官方的安裝文檔已經寫的很詳盡,這裡再簡單記錄一下。
#用超級用戶操作 su -s #下載dnscrypt-proxy-linux_x86_64-2.0.25.tar.gz並解壓至/opt/dnscrypt-proxy/ #其實解壓到哪裡都無所謂啦 cd /opt/dnscrypt-proxy/ cp example-dnscrypt-proxy.toml dnscrypt-proxy.toml #選擇性修改fallback dns如 fallback_resolver = '114.114.114.114:53' #開啟分流文件 #曾經在不分流的情況下用了一年左右,奈何網絡持續惡化,現在不分流體驗會很差 #中國域名來自https://github.com/felixonmars/dnsmasq-china-list/blob/master/accelerated-domains.china.conf #需要替換成dnscrypt-proxy的格式 forwarding_rules = '/home/fred/Programs/dnscrypt-proxy/forwarding-rules.txt' ./dnscrypt-proxy #出現dnscrypt-proxy is ready即為成功,可以先Ctrl+C關閉 #执行下面命令使普通用戶也能運行dnscrypt-proxy setcap cap_net_bind_service=+pe dnscrypt-proxy #下面設置本機dns解析方法一: cp /etc/resolv.conf /etc/resolv.conf.backup nano /etc/resolv.conf #文件內容如下 nameserver 127.0.0.1 options edns0 single-request-reopen #下面設置本機dns解析方法二: #右擊網絡連接圖標,選擇“編輯網絡連接”,選擇網絡,點齒輪圖標。 #在“IPv4設定”選項卡,Method選“只用自動(DHCP)位址” #DNS servers填入127.0.0.1, 8.8.4.4,按“儲存”。 #右擊網絡連接圖標,反選啟用網絡,再勾選啟用網絡。 #設置完成,測試解析 ./dnscrypt-proxy -resolve ft.shaman.eu.org #如果返回了IP就說明OK #自動啟動我都是用的xfce的啟動程序,位於 #應用程式選單/設定值/工作階段與初始啟動/應用程式自動啟動/加入 #指令框輸入/opt/dnscrypt-proxy/dnscrypt-proxy #NetworkManager會在開機時生成重寫/etc/resolv.conf #所以需要在NetworkManager的IPV4設定中指定127.0.0.1為dns服務器 #或者 #給resolv.conf上把鎖 chattr +i /etc/resolv.conf #解鎖 chattr -i /etc/resolv.conf
如果想分享dnscrypt-proxy服務給局域網用戶,可以修改dnscrypt-proxy.toml中的監聽地址為['0.0.0.0:53']。然後開啟防火牆的53端口。
#查看已開啟的防火牆端口 sudo firewall-cmd --list-all #開啟 DNS 端口自動(方法一) sudo firewall-cmd --permanent --add-service=dns #開啟 DNS 端口手動(方法二) sudo firewall-cmd --permanent --add-port=53/udp sudo firewall-cmd --permanent --add-port=53/tcp #重啟防火牆 sudo firewall-cmd --reload
#程序日誌 log_file = '/home/ft/Programs/dnscrypt-proxy/log.log' #解析日誌 [query_log] file = '/home/ft/Programs/dnscrypt-proxy/query.log' ignored_qtypes = ['AAAA', 'DNSKEY', 'NS'] #域名黑名單 [blocked_names] blocked_names_file = '/home/ft/Programs/dnscrypt-proxy/mybase_blocked-names.txt' #此處使用的黑名單來自 https://download.dnscrypt.info/blacklists/domains/ #NextDNS 解析 [static] [static.'NextDNS-12abcd'] stamp = 'sdns://AgEAAAAAAAAAAAAOZG5zLm5leHRkbnMuaW8HLzc3WiXnZO' #使用 NextDNS 時還要設置 server_names server_names = ['NextDNS-12abcd']
值得提醒的是,如果你使用 BT 下載,那麼很多黑名單是禁止 trackers 相關域名的,記得禁用這一條#tracker.*。自定義的 DNS 服務器可以使用 Online DNS Stamp calculator 來生成簽章。
安裝dnscrypt-proxy並新建一個執行用戶
sudo dnf install dnscrypt-proxy sudo adduser -m -N -r -s /bin/false dnscrypt
安裝完成後可以從/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv的第一列挑選一個服務商,我嘗試用香港的發現連接不上,於是選擇法國cs-fr。嘗試運行dnscrypt-proxy:
sudo dnscrypt-proxy -u dnscrypt -R cs-fr
我的桌面是Xfce,所以設置DNS很方便。找到程序/設定值/網路連線,選中需要編輯的網路點編輯。在IPv4設定面板中,如果你之前方法是“自動(DHCP)”,則變更方法為“只用自動(DHCP)位址”,並在DNS伺服器一欄填入127.0.0.1。如果之前就有固定IP,則只需要把DNS伺服器一欄改為127.0.0.1即可。多個DNS伺服器可以用半角逗號隔開。
如果沒有桌面環境,要設置DNS的話可以參考The adventurous can edit the appropriate script in /etc/sysconfig/network-scripts/. If you don't have NetworkManager installed, editing /etc/resolv.conf would work too.
重啟網絡服務已使剛剛新設置的DNS生效。可以使用dig驗證當前dns,出現SERVER: 127.0.0.1#53即說明成功。
sudo systemctl restart network.service dig ft.wupo.info
設置自動重啟也很簡單,新建文件/etc/systemd/system/dnscrypt.service,內容如下:
[Unit] Description=dnscrypt - Encrypted DNS service provided by OpenDNS After=NetworkManager.service [Service] ExecStart=/usr/sbin/dnscrypt-proxy -u dnscrypt -R cs-fr ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=basic.target
重啟系統守護
sudo systemctl daemon-reload
添加dnscrypt到開啟啟動
sudo systemctl enable dnscrypt.service
當然你也可以手動通過systemctl命令來啟動和停止dnscrypt-proxy,像這樣:
sudo systemctl start dnscrypt.service
本文更新於 2025/07/24。