diff --git a/adbyby/Makefile b/adbyby/Makefile
new file mode 100644
index 000000000..831dcb5b6
--- /dev/null
+++ b/adbyby/Makefile
@@ -0,0 +1,76 @@
+#
+# Copyright (C) 2015-2016 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v3.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=adbyby
+PKG_VERSION:=2.7
+PKG_RELEASE:=20200315
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/$(PKG_NAME)
+ SECTION:=net
+ CATEGORY:=Network
+ TITLE:=Powerful adblock module to block ad.
+ DEPENDS:=@(i386||x86_64||arm||mipsel||mips||aarch64)
+ URL:=http://www.adbyby.com/
+endef
+
+define Package/$(PKG_NAME)/description
+Adbyby is a powerful adblock module to block ad,just like adblock.
+endef
+
+ifeq ($(ARCH),x86_64)
+ ADBYBY_DIR:=amd64
+endif
+ifeq ($(ARCH),mipsel)
+ ADBYBY_DIR:=mipsle
+endif
+ifeq ($(ARCH),mips)
+ ADBYBY_DIR:=mips
+endif
+ifeq ($(ARCH),i386)
+ ADBYBY_DIR:=x86
+endif
+ifeq ($(ARCH),arm)
+ ADBYBY_DIR:=armv7
+ ifeq ($(BOARD),bcm53xx)
+ ADBYBY_DIR:=arm
+ endif
+ ifeq ($(BOARD),kirkwood)
+ ADBYBY_DIR:=arm
+ endif
+endif
+ifeq ($(ARCH),aarch64)
+ ADBYBY_DIR:=armv7
+endif
+
+define Build/Prepare
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/$(PKG_NAME)/install
+ $(INSTALL_DIR) $(1)/usr/share/adbyby
+ $(INSTALL_BIN) ./files/adbyby.sh $(1)/usr/share/adbyby/
+ $(INSTALL_CONF) ./files/adhook.ini $(1)/usr/share/adbyby/
+ $(INSTALL_CONF) ./files/user.action $(1)/usr/share/adbyby/
+
+ $(INSTALL_DIR) $(1)/usr/share/adbyby/data
+ $(INSTALL_DATA) ./files/data/* $(1)/usr/share/adbyby/data/
+
+ $(INSTALL_DIR) $(1)/usr/share/adbyby/doc
+ $(INSTALL_DATA) ./files/doc/* $(1)/usr/share/adbyby/doc/
+
+ $(INSTALL_BIN) ./files/$(ADBYBY_DIR)/adbyby $(1)/usr/share/adbyby/adbyby
+endef
+
+$(eval $(call BuildPackage,$(PKG_NAME)))
diff --git a/adbyby/files/adbyby.sh b/adbyby/files/adbyby.sh
new file mode 100755
index 000000000..146a070d1
--- /dev/null
+++ b/adbyby/files/adbyby.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+PROG_PATH=/usr/share/adbyby
+err=0
+until [ $err -ge 5 ]; do
+ if [ -n "$(pgrep $PROG_PATH/adbyby)" ]; then
+ iptables-save | grep ADBYBY >/dev/null || \
+ /etc/init.d/adbyby add_rule
+ sleep 10
+ err=0
+ else
+ $PROG_PATH/adbyby --no-daemon &>/dev/null &
+ sleep 1
+ err=$((err+1))
+ fi
+done
+/etc/init.d/adbyby del_rule
diff --git a/adbyby/files/adhook.ini b/adbyby/files/adhook.ini
new file mode 100644
index 000000000..6a7613ea8
--- /dev/null
+++ b/adbyby/files/adhook.ini
@@ -0,0 +1,14 @@
+[cfg]
+### 2.1 ###
+listen-address=0.0.0.0:8118
+buffer-limit=1024
+keep-alive-timeout=30
+socket-timeout=60
+### 2.5 ###
+max_client_connections=0
+### 2.6 ###
+stack_size=200
+auto_restart=0
+### 2.7 ###
+debug=0
+ipset=0
diff --git a/adbyby/files/amd64/adbyby b/adbyby/files/amd64/adbyby
new file mode 100755
index 000000000..76c8fa084
Binary files /dev/null and b/adbyby/files/amd64/adbyby differ
diff --git a/adbyby/files/arm/adbyby b/adbyby/files/arm/adbyby
new file mode 100755
index 000000000..a7da1900d
Binary files /dev/null and b/adbyby/files/arm/adbyby differ
diff --git a/adbyby/files/armv7/adbyby b/adbyby/files/armv7/adbyby
new file mode 100755
index 000000000..98dbfbe54
Binary files /dev/null and b/adbyby/files/armv7/adbyby differ
diff --git a/adbyby/files/data/lazy.bin b/adbyby/files/data/lazy.bin
new file mode 100644
index 000000000..23e6a8d70
Binary files /dev/null and b/adbyby/files/data/lazy.bin differ
diff --git a/adbyby/files/data/lazy.txt b/adbyby/files/data/lazy.txt
new file mode 100644
index 000000000..5d383c711
--- /dev/null
+++ b/adbyby/files/data/lazy.txt
@@ -0,0 +1,25 @@
+! -----ʱ: 2018-10-08 23:07:52 by:xwhyc-----
+.php?ad=
+/99rejs/js
+/agetsou.js^
+/c@*!25.js|$script
+/cococ-66/$script
+/defaultts.tc.qq.com/*.ts?index=0&start=0&
+/defaultts.tc.qq.com/*.ts?index=0&start=0&end=*&ver=4
+/gg_js/
+/imgs.js?t=
+/js/adtop.js|
+/js/guanggao_gg.js?d=
+/js/sss/ad.js
+/static/js/aall.js
+/unionjs/$script
+/variety.tc.qq.com/*.mp4^*fmt=hd
+/variety.tc.qq.com/*.mp4^*fmt=shd
+/video.dispatch.tc.qq.com/*.mp4^*sdtfrom=
+/view/image/QT_HY.jpg?t=
+/view/js/slade.js|$script
+/vlive.qqvideo.tc.qq.com/*.mp4^*fmt=hd
+/vlive.qqvideo.tc.qq.com/*.mp4^*fmt=shd
+/vmind.qqvideo.tc.qq.com/*.mp4^*sdtfrom=
+/vmindhls.tc.qq.com/*ugc=0&ga=0&gv=0&start=
+/zzhzgg.js
\ No newline at end of file
diff --git a/adbyby/files/data/rules.txt b/adbyby/files/data/rules.txt
new file mode 100644
index 000000000..5c44b0e8c
--- /dev/null
+++ b/adbyby/files/data/rules.txt
@@ -0,0 +1,15 @@
+! ------------------------------ ADByby 自定义过滤语法简表---------------------------------
+! -------------- 规则基于abp规则,并进行了字符替换部分的扩展-----------------------------
+! ABP规则请参考https://adblockplus.org/zh_CN/filters,下面为大致摘要
+! "!" 为行注释符,注释行以该符号起始作为一行注释语义,用于规则描述
+! "*" 为字符通配符,能够匹配0长度或任意长度的字符串,该通配符不能与正则语法混用。
+! "^" 为分隔符,可以是除了字母、数字或者 _ - . % 之外的任何字符。
+! "|" 为管线符号,来表示地址的最前端或最末端
+! "||" 为子域通配符,方便匹配主域名下的所有子域。
+! "~" 为排除标识符,通配符能过滤大多数广告,但同时存在误杀, 可以通过排除标识符修正误杀链接。
+! "##" 为元素选择器标识符,后面跟需要隐藏元素的CSS样式例如 #ad_id .ad_class
+!! 元素隐藏暂不支持全局规则和排除规则
+!! 字符替换扩展
+! 文本替换选择器标识符,后面跟需要替换的文本数据,格式:$s@模式字符串@替换后的文本@
+! 支持通配符*和?
+! -------------------------------------------------------------------------------------------
diff --git a/adbyby/files/data/user.txt b/adbyby/files/data/user.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/adbyby/files/data/video.txt b/adbyby/files/data/video.txt
new file mode 100644
index 000000000..3991c48fc
--- /dev/null
+++ b/adbyby/files/data/video.txt
@@ -0,0 +1,30 @@
+! -----ʱ: 2018-10-08 23:06:11 by:xwhyc-----------------------------------------------
+! -----淴:http://www.adbyby.com/help.htm QQȺ: 79547134(), 364066294()470705224()534897434,438394572--------------
+
+
+!-------------------------------------------
+/showadv/
+3199.cn###ad640,[id^="ad0"]
+76fengyun.com###down_box{display:block!important;}.gggg,.ggao
+9553.com##[class="zm"]
+962.net##.show_ad,.g-962-ad
+amazon.cn##.adv-float-div
+downsx.rocks##.adbox,.Left-ad3_fixed,.google-ad2_fixed
+edu-acc.cn###doyoo_monitor
+fx678.com##[class^="adv-"]
+||76fengyun.com/body22233.js
+||76fengyun.com/file/$s@onclick="window.open('*');startWait();"@onclick="window.open(document.getElementById('downpage_link').href);startWait();"@
+||atanx.alicdn.com/t/tanxssp.js?_v=
+||biquguan.com/guan/$script
+||djv99sxoqpv11.cloudfront.net
+||jmxlaser.com
+||jump2.bdimg.com/p/$s@
+~,\s])\s*(\*|(?:[-\w*]|[^\x00-\xa0]|\\.)*)/,trimLeft=/^\s+/,trimRight=/\s+$/,reg_comma=/^\s*,\s*/,reg_sequence=/^([#\.:]|\[\s*)((?:[-\w]|[^\x00-\xa0]|\\.)+)/,reg_pseudo=/^\(\s*("([^"]*)"|'([^']*)'|[^\(\)]*(\([^\(\)]*\))?)\s*\)/,reg_attrib=/^\s*(?:(\S?=)\s*(?:(['"])(.*?)\2|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,reg_attrval=/\\([0-9a-fA-F]{2,2})/g,reg_sensitive=/^(?:title|id|name|class|for|href|src)$/,reg_backslash=/\\/g,reg_tag=/^((?:[-\w\*]|[^\x00-\xa0]|\\.)+)/,reg_parse_pseudo=/(-?)(\d*)n([-+]?\d*)/,reg_quick=/^(^|[#.])((?:[-\w]|[^\x00-\xa0]|\\.)+)$/,has_in=function(a,b){return(a in b)},dom={html:DOC.documentElement,mix:function(target,source){var args=[].slice.call(arguments),key,ride=typeof args[args.length-1]=="boolean"?args.pop():true;target=target||{};for(var i=1;source=args[i++];){for(key in source){if(ride||!has_in(key,target)){target[key]=source[key]}}}return target},rword:/[^, ]+/g,uuid:1,getUid:w3c_css?function(node){return node.uniqueNumber||(node.uniqueNumber=dom.uuid++)}:function(node){var uid=node.getAttribute("uniqueNumber");if(!uid){uid=dom.uuid++;node.setAttribute("uniqueNumber",uid)}return uid},oneObject:function(array,val){if(typeof array=="string"){array=array.match(dom.rword)||[]}var result={},value=val!==undef?val:1;for(var i=0,n=array.length;i
=0);(found^flag_not)&&(result[ri++]=el)}return result}}},onePosition=dom.oneObject("eq,gt,lt,first,last,even,odd"),siblingCheck=function(a,b,ret){if(a===b){return ret}var cur=a.nextSibling;while(cur){if(cur===b){return -1}cur=cur.nextSibling}return 1},sortOrder1=function(a,b){if(a===b){sortOrder1.hasDuplicate=true;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition){return a.compareDocumentPosition?-1:1}return a.compareDocumentPosition(b)&4?-1:1},sortOrder2=function(a,b){if(a===b){sortOrder2.hasDuplicate=true;return 0}var al,bl,ap=[],bp=[],aup=a.parentNode,bup=b.parentNode,cur=aup;if(aup===bup){return siblingCheck(a,b)}else{if(!aup){return -1}else{if(!bup){return 1}}}while(cur){ap.unshift(cur);cur=cur.parentNode}cur=bup;while(cur){bp.unshift(cur);cur=cur.parentNode}al=ap.length;bl=bp.length;for(var i=0;i0&&els.length&&els[0].lookupNamespaceURI){var arr=tagName.split(":");prefix=arr[0];tagName=arr[1];method="getElementsByTagNameNS";prefix=els[0].lookupNamespaceURI(prefix)}switch(els.length){case 0:return elems;case 1:var all=prefix?els[0][method](prefix,tagName):els[0][method](tagName);for(var i=0,ri=0,el;el=all[i++];){if(el.nodeType===1){elems[ri++]=el}}return elems;default:for(var nodes,i=0,ri=0;el=els[i++];){nodes=prefix?el[method](prefix,tagName):el[method](tagName);for(var uid,j=0,node;node=nodes[j++];){uid=dom.getUid(node);if(!uniqResult[uid]){uniqResult[uid]=elems[ri++]=node}}}return elems}},getElementsByXPath=function(xpath,context,doc){var result=[];try{if(global.DOMParser){var nodes=doc.evaluate(xpath,context,null,7,null);for(var i=0,n=nodes.snapshotLength;i";root.insertBefore(form,root.firstChild);fixById=!!DOC.getElementById(id);root.removeChild(form)})();try{slice.call(dom.html.childNodes,0)[0].nodeType}catch(e){makeArray=function(nodes,result,flag_multi){var ret=result||[],ri=ret.length;for(var i=0,el;el=nodes[i++];){ret[ri++]=el}return flag_multi?dom.unique(ret):ret}}var Icarus=dom.query=function(expr,contexts,result,lastResult,flag_xml,flag_multi,flag_dirty){result=result||[];contexts=contexts||DOC;var pushResult=makeArray;if(!contexts.nodeType){contexts=pushResult(contexts);if(!contexts.length){return result}}else{contexts=[contexts]}var rrelative=reg_combinator,rBackslash=reg_backslash,rcomma=reg_comma,context=contexts[0],doc=context.ownerDocument||context,rtag=reg_tag,flag_all,uniqResult,elems,nodes,tagName,last,ri,uid;expr=expr.replace(trimLeft,"").replace(trimRight,"");flag_xml=flag_xml!==undef?flag_xml:dom.isXML(doc);if(flag_xml&&expr==="body"&&context.body){return pushResult([context.body],result,flag_multi)}if(!flag_xml&&doc.querySelectorAll){var query=expr,fix_icarus_sqa="fix_icarus_sqa";if(contexts.length>2||doc.documentMode==8&&context.nodeType==1){if(contexts.length>2){context=doc}query="."+fix_icarus_sqa+" "+query;for(var i=0,node;node=contexts[i++];){if(node.nodeType===1){node.className=fix_icarus_sqa+" "+node.className}}}if(doc.documentMode!==8||context.nodeName.toLowerCase()!=="object"){try{return pushResult(context.querySelectorAll(query),result,flag_multi)}catch(e){}finally{if(!query.indexOf("."+fix_icarus_sqa)){for(i=0;node=contexts[i++];){if(node.nodeType===1){node.className=node.className.replace(fix_icarus_sqa+" ","")}}}}}}var match=expr.match(reg_quick);if(match){var value=match[2].replace(rBackslash,""),key=match[1];if(key==""){nodes=getElementsByTagName(value,contexts,flag_xml)}else{if(key==="."&&contexts.length===1){if(flag_xml){nodes=getElementsByXPath("//*[@class='"+value+"']",context,doc)}else{if(context.getElementsByClassName){nodes=context.getElementsByClassName(value)}}}else{if(key==="#"&&contexts.length===1){if(flag_xml){nodes=getElementsByXPath("//*[@id='"+value+"']",context,doc)}else{if(context.nodeType==Math.pow(3,2)){node=doc.getElementById(value);nodes=!node?[]:!fixById?[node]:node.getAttributeNode("id").nodeValue===value?[node]:false}}}}}if(nodes){return pushResult(nodes,result,flag_multi)}}lastResult=contexts;if(lastResult.length){loop:while(expr&&last!==expr){flag_dirty=false;elems=null;uniqResult={};if((match=expr.match(rrelative))){expr=RegExp.rightContext;elems=[];tagName=(flag_xml?match[2]:match[2].toUpperCase()).replace(rBackslash,"")||"*";i=0;ri=0;flag_all=tagName==="*";switch(match[1]){case" ":if(expr.length||match[2]){elems=getElementsByTagName(tagName,lastResult,flag_xml)}else{elems=lastResult;break loop}break;case">":while((node=lastResult[i++])){for(node=node.firstChild;node;node=node.nextSibling){if(node.nodeType===1&&(flag_all||tagName===node.nodeName)){elems[ri++]=node}}}break;case"+":while((node=lastResult[i++])){while((node=node.nextSibling)){if(node.nodeType===1){if(flag_all||tagName===node.nodeName){elems[ri++]=node}break}}}break;case"~":while((node=lastResult[i++])){while((node=node.nextSibling)){if(node.nodeType===1&&(flag_all||tagName===node.nodeName)){uid=dom.getUid(node);if(uniqResult[uid]){break}else{uniqResult[uid]=elems[ri++]=node}}}}elems=dom.unique(elems);break}}else{if((match=expr.match(rtag))){expr=RegExp.rightContext;elems=getElementsByTagName(match[1].replace(rBackslash,""),lastResult,flag_xml)}}if(expr){var arr=Icarus.filter(expr,elems,lastResult,doc,flag_xml);expr=arr[0];elems=arr[1];if(!elems){flag_dirty=true;elems=getElementsByTagName("*",lastResult,flag_xml)}if((match=expr.match(rcomma))){expr=RegExp.rightContext;pushResult(elems,result);return Icarus(expr,contexts,result,[],flag_xml,true,flag_dirty)}else{lastResult=elems}}}}if(flag_multi){if(elems.length){return pushResult(elems,result,flag_multi)}}else{if(DOC!==doc||fixByTag&&flag_dirty){for(result=[],ri=0,i=0;node=elems[i++];){if(node.nodeType===1){result[ri++]=node}}return result}}return elems},filterPseudoNoExp=function(name,isLast,isOnly){var A="for(var result=[],flag_not=A.not,node,el,tagName,i=0,ri=0,found=0;node=el=B[i++];found=0){",B="{0}while(!found&&(node=node.{1})){(node.{2}==={3})&&++found}",C="node=el;while(!found&&(node=node.previousSibling)){node.{2}==={3}&&++found}",D="!found^flag_not&&(result[ri++]=el)}return result",start=isLast?"nextSibling":"previousSibling",fills={type:["tagName=el.nodeName;",start,"nodeName","tagName"],child:["",start,"nodeType","1"]}[name],body=A+B+(isOnly?C:"")+D;return{exec:new Function("A","B",body.replace(/{(\d)}/g,function($,$1){return fills[$1]}))}};dom.mix(Icarus,{getAttribute:!fixGetAttribute?function(elem,name){return elem.getAttribute(name)||""}:function(elem,name,flag_xml){if(flag_xml){return elem.getAttribute(name)||""}name=name.toLowerCase();if(attrURL[name]){return elem.getAttribute(name,2)||""}if(elem.tagName==="INPUT"&&name=="type"){return elem.getAttribute("type")||elem.type}var attr=boolOne[name]?(elem.getAttribute(name)?name:""):(elem=elem.getAttributeNode(name))&&elem.value||"";return reg_sensitive.test(name)?attr:attr.toLowerCase()},hasAttribute:!fixHasAttribute?function(elem,name,flag_xml){return flag_xml?!!elem.getAttribute(name):elem.hasAttribute(name)}:function(elem,name){elem=elem.getAttributeNode(name.toLowerCase());return !!(elem&&(elem.specified||elem.nodeValue))},filter:function(expr,elems,lastResult,doc,flag_xml,flag_get){var rsequence=reg_sequence,rattrib=reg_attrib,rpseudo=reg_pseudo,rBackslash=reg_backslash,rattrval=reg_attrval,pushResult=makeArray,toHex=_toHex,_hash_op=hash_operator,parsePseudo=parse_pseudo,match,key,tmp;while((match=expr.match(rsequence))){expr=RegExp.rightContext;key=(match[2]||"").replace(rBackslash,"");if(!elems){if(lastResult.length===1&&lastResult[0]===doc){switch(match[1]){case"#":if(!flag_xml){tmp=doc.getElementById(key);if(!tmp){elems=[];continue}if(fixById?tmp.id===key:tmp.getAttributeNode("id").nodeValue===key){elems=[tmp];continue}}break;case":":switch(key){case"root":elems=[doc.documentElement];continue;case"link":elems=pushResult(doc.links||[]);continue}break}}elems=getElementsByTagName("*",lastResult,flag_xml)}var filter=0,flag_not=false,args;switch(match[1]){case"#":filter=["id","=",key];break;case".":filter=["class","~=",key];break;case":":tmp=Icarus.hook[key];if((match=expr.match(rpseudo))){expr=RegExp.rightContext;args=parsePseudo(key,match)}if(tmp){filter=tmp}else{if(key==="not"){flag_not=true;if(args==="*"){elems=[]}else{if(reg_tag.test(args)){tmp=[];match=flag_xml?args:args.toUpperCase();for(var i=0,ri=0,elem;elem=elems[i++];){if(match!==elem.nodeName){tmp[ri++]=elem}}elems=tmp}else{var obj=Icarus.filter(args,elems,lastResult,doc,flag_xml,true);filter=obj.filter;args=obj.args}}}else{throw"!"}}break;default:filter=[key.toLowerCase()];if((match=expr.match(rattrib))){expr=RegExp.rightContext;if(match[1]){filter[1]=match[1];filter[2]=match[3]||match[4];filter[2]=filter[2]?filter[2].replace(rattrval,toHex).replace(rBackslash,""):""}}break}if(flag_get){return{filter:filter,args:args}}if(elems.length&&filter){tmp=[];i=0;ri=0;if(typeof filter==="function"){if(onePosition[key]){args=args===undef?elems.length-1:~~args;for(;elem=elems[i];){if(filter(i++,args)^flag_not){tmp[ri++]=elem}}}else{while((elem=elems[i++])){if((!!filter(elem,args))^flag_not){tmp[ri++]=elem}}}}else{if(typeof filter.exec==="function"){tmp=filter.exec({not:flag_not,xml:flag_xml},elems,args,doc)}else{var name=filter[0],op=_hash_op[filter[1]],val=filter[2]||"",flag,attr,className;if(!flag_xml&&name==="class"&&op===4){val=" "+val+" ";while((elem=elems[i++])){className=elem.className;if(!!(className&&~(" "+className+" ").indexOf(val))^flag_not){tmp[ri++]=elem}}}else{if(!flag_xml&&op&&val&&!reg_sensitive.test(name)){val=val.toLowerCase()}if(op===4){val=" "+val+" "}while((elem=elems[i++])){if(!op){flag=Icarus.hasAttribute(elem,name,flag_xml)}else{if(val===""&&op>3){flag=false}else{attr=Icarus.getAttribute(elem,name,flag_xml);switch(op){case 1:(flag=attr===val);break;case 2:(flag=attr!==val);break;case 3:(flag=attr===val||attr.substr(0,val.length+1)===val+"-");break;case 4:(flag=attr!==""&&!!~(" "+attr+" ").indexOf(val));break;case 5:(flag=attr!==""&&!attr.indexOf(val));break;case 6:(flag=attr!==""&&attr.substr(attr.length-val.length)===val);break;case 7:(flag=attr!==""&&!!~attr.indexOf(val));break}}}if(flag^flag_not){tmp[ri++]=elem}}}}}elems=tmp}}return[expr,elems]}});Icarus.hook={root:function(el){return el===(el.ownerDocument||el.document).documentElement},"first-child":filterPseudoNoExp("child",false,false),"last-child":filterPseudoNoExp("child",true,false),"only-child":filterPseudoNoExp("child",true,true),"nth-child":filterPseudoHasExp("firstChild","nextSibling",false),link:{exec:function(flags,elems){var links=(elems[0].ownerDocument||elems[0].document).links;if(!links){return[]}var result=[],checked={},flag_not=flags.not;for(var i=0,ri=0,elem;elem=links[i++];){checked[dom.getUid(elem)]=1}for(i=0;elem=elems[i++];){if(checked[dom.getUid(elem)]^flag_not){result[ri++]=elem}}return result}},lang:{exec:function(flags,elems,arg){var result=[],reg=new RegExp("^"+arg,"i"),flag_not=flags.not;for(var tmp,i=0,ri=0,elem;elem=elems[i++];){tmp=elem;while(tmp&&!tmp.getAttribute("lang")){tmp=tmp.parentNode}tmp=!!(tmp&®.test(tmp.getAttribute("lang")));if(tmp^flag_not){result[ri++]=elem}}return result}},contains:{exec:function(flags,elems,arg){var res=[],flag_not=flags.not;for(var i=0,ri=0,elem;elem=elems[i++];){if(!!~((elem.innerText||elem.textContent||dom.getText([elem])).indexOf(arg))^flag_not){res[ri++]=elem}}return res}},selected:function(el){el.parentNode&&el.parentNode.selectedIndex;return el.selected===true},header:function(el){return/h\d/i.test(el.nodeName)},button:function(el){return"button"===el.type||el.nodeName==="BUTTON"},input:function(el){return/input|select|textarea|button/i.test(el.nodeName)},parent:function(el){return !!el.firstChild},has:function(el,expr){return !!dom.query(expr,[el]).length},first:function(index){return index===0},last:function(index,num){return index===num},even:function(index){return index%2===0},odd:function(index){return index%2===1},lt:function(index,num){return indexnum},eq:function(index,num){return index===num},hidden:function(el){return(el.offsetWidth+el.offsetHeight)==0||(el.currentStyle||{}).display=="none"}};Icarus.hook.visible=function(el){return !Icarus.hook.hidden(el)};"text,radio,checkbox,file,password,submit,image,reset".replace(dom.rword,function(name){Icarus.hook[name]=function(el){return(el.getAttribute("type")||el.type)===name}});var blockCss=/(?:function|open|document|location|alert|confirm|prompt|showmodelessdialog|activexobject|xmlhttprequest|execscript|eval)\s*[(.=;]/i,checkCss=function(a,b){b=b||"visibility";return"hidden"==(w3c_css?DOC.defaultView.getComputedStyle(a,null).getPropertyValue(b):a.currentStyle[b])},removeNode=!!global.VBArray?function(){var b;return function(a){if(a&&"BODY"!=a.tagName){b=b||DOC.createElement("DIV");b.appendChild(a);b.innerHTML=""}}}():function(a){a&&a.parentNode&&"BODY"!=a.tagName&&a.parentNode.removeChild(a)},execFunc=[function(){},function(a){for(var b,c=a.length;c--;){b=a[c];checkCss(b)||(b.style.display="none",b.style.visibility="hidden")}},function(a){for(var c=a.length;c--;){removeNode(a[c])}}],execMainFunc=function(a,b){var c;try{c=dom.query(a)}catch(e){return((c=null),0)}return("function"==typeof execFunc[b]&&execFunc[b](c),c.length)},const_DOMContentLoaded="DOMContentLoaded",const_complete="complete",domReadyList=[],domisReady=0,domReady=function(fn){domisReady?fn():domReadyList.unshift(fn)},domfireReady=function(){if(!domisReady){if(!DOC.body){return setTimeout(domfireReady,32)}domisReady=1;for(var len=domReadyList.length;len--;){domReadyList[len]()}}},createCss=(function(){var style,media,self={},reg_media=/screen|all/i,splitCss=/[\[+>~:].+?/,addCss="{display:none;position:absolute;top:-1000000px;visibility:hidden}",styles=DOC.getElementsByTagName("style"),len=styles.length,new_css=function(g){for(var r=[],z=[],o,y=g.split(","),E=y.length;E--;){o=y[E];o&&(splitCss.test(o)?r:z).push(o)}z.length&&r.push(z.join());return(r.join(addCss)||"iframe")+addCss};while(len--){style=styles[len];media=style.getAttribute("media");if(media===null||reg_media.test(media)){self.style=style;break}}if(!self.style){var head=DOC.head||DOC.getElementsByTagName("head")[0];style=DOC.createElement("style");head.insertBefore(style,head.firstChild);domReady(function(){DOC.body&&DOC.body.insertBefore(style,DOC.body.firstChild)})}return style.styleSheet?function(css){style.styleSheet.cssText+=new_css(css)}:!!global.Components?function(css){style.innerHTML+=new_css(css)}:function(css){style.appendChild(DOC.createTextNode(new_css(css)))}})();if(const_complete==DOC.readyState){domfireReady()}else{if(!DOC.attachEvent){DOC.addEventListener(const_DOMContentLoaded,function(){DOC.removeEventListener(const_DOMContentLoaded,arguments.callee,false);domfireReady()},false)}else{DOC.attachEvent("onreadystatechange",function(){const_complete==DOC.readyState&&(DOC.detachEvent("onreadystatechange",arguments.callee),domfireReady())});(function(){if(domisReady){return}try{var node=new Image();node.doScroll();node=null}catch(e){setTimeout(arguments.callee,64);return}domfireReady()})()}}var AdSafe_CheckRule={regIDS:null,regSub:null,format:function(){var str=arguments[0];var regM=/\{\s*(\d+)\s*\}/,arr,temp="",xb;while((arr=regM.exec(str))!=null){xb=parseInt(arr[1]);if(isNaN(xb)||typeof(arguments[xb+1])=="undefined"){throw"��ָ������:"+arr[0]}temp+=str.substr(0,arr.index)+arguments[xb+1];str=str.substr(arr.index+arr[0].length)}temp+=str;return temp},isNullOrEmpty:function(str){var t=str;if(str.constructor==Array){t=str[0]}if(t==null||t==""){return true}return false},trim:function(str,dir){var regTrim;if(dir=="left"){regTrim=/^\s*/g}else{if(dir=="right"){regTrim=/\s*$/g}else{regTrim=/(?:^\s*|\s*$)/g}}if(str.constructor==String){str=str.replace(regTrim,"")}else{if(str.constructor==Array){for(var i=0;i+]\\s*)?(?:{0}|{1}|{2})+)+",str_iden,str_attr,str_quick),str_jquerys=this.format("^{0}\\s*(?:,\\s*{0}\\s*)*$",str_alone);this.regIDS=new RegExp(str_jquerys,"i");this.regSub=new RegExp(strSub,"i")},Checking:function(str){if(this.isNullOrEmpty(str)){return false}var result=this.AloneSub(str);if(!result){return false}if(this.isNullOrEmpty(str)){return true}if(!this.CheckRules(str)){return false}return true},CheckRules:function(str){var strArr=str[0].split(",");var lst=[];for(var t=0;t0){return false}else{return true}},GetPair:function(k){var t=[],result=[],c;t.push("0");for(var i=0;i0){var rc=str[0].charAt(temp1.length-1);if(rc==">"||rc=="+"||rc==","){if(temp1.length-1>=0){temp1=temp1.substr(0,temp1.length-1)}hasSub=true}}if(hasSub){str[0]=temp1+" "+str[0].substr(m.index+m[0].length+temp[0].length+1)}else{str[0]=str[0].substr(0,m.index)+str[0].substr(m.index+m[0].length+temp[0].length+1)}str[0]=this.trim(str[0])}if(this.isNullOrEmpty(str)){return true}return this.AloneSub(str)}else{return this.CheckRules(str)}}};var isIni=false;var checkCSSRule=function(line){if(!isIni){AdSafe_CheckRule.ini();isIni=true}var i=line.indexOf("??1");if(i>=0){line=line.substr(0,i)}line=AdSafe_CheckRule.trim(line);if(line==""){return false}return AdSafe_CheckRule.Checking([line])};global.hidecss=function(c){if(c.indexOf("??1")>-1||c.indexOf("??")<0){try{if(!checkCSSRule(c)){return}}catch(e){}}if(!c||blockCss.test(c)){return}var d=String(c).split("??"),f=parseInt(d[1],10)||1,g=d[0],l=100;var setCookie=function(name,value,expires,path,domain,secure){var curCookie=name+"="+escape(value)+((expires)?"; expires="+expires.toGMTString():"")+((path)?"; path="+path:"")+((domain)?"; domain="+domain:"")+((secure)?"; secure":"");document.cookie=curCookie};var addCookieForHours=function(name,value,path,domain,hour,secure){var t;if(hour){t=new Date();hour=isNaN(parseFloat(hour))?1:hour;t.setTime(t.getTime()+hour*3600*1000)}setCookie(name,value,t,path,domain,secure)};var deleteCookie=function(name,path,domain){document.cookie=name+"="+((path)?"; path="+path:"")+((domain)?"; domain="+domain:"")+"; expires=Thu, 01-Jan-70 00:00:01 GMT"};var clearCookie=function(path,domain,arr){var keys=[],r,reg=/([^=; ]+)=([^;]+)/g,cook=document.cookie;while((r=reg.exec(cook))!=null){keys.push(r[1])}if(keys&&keys.length>0){var _f;if(typeof arr=="undefined"||!arr){arr=[]}if(typeof arr=="string"){arr=[arr]}for(var i=keys.length-1;i--;i>=0){_f=false;for(var k=0;k/dev/null") == 0
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(e)
+end
+
+
+function refresh_data()
+ local set = luci.http.formvalue("set")
+ local icount = 0
+
+if set == "rule_data" then
+luci.sys.exec("/usr/share/adbyby/rule-update")
+ icount = luci.sys.exec("/usr/share/adbyby/rule-count '/tmp/rules/'")
+
+ if tonumber(icount)>0 then
+ if nixio.fs.access("/usr/share/adbyby/rules/") then
+ oldcount = luci.sys.exec("/usr/share/adbyby/rule-count '/usr/share/adbyby/rules/'")
+ else
+ oldcount=0
+ end
+ else
+ retstring ="-1"
+ end
+
+ if tonumber(icount) ~= tonumber(oldcount) then
+ luci.sys.exec("rm -f /usr/share/adbyby/rules/data/* /usr/share/adbyby/rules/host/* && cp -a /tmp/rules /usr/share/adbyby/")
+ luci.sys.exec("/etc/init.d/adbyby restart &")
+ retstring=tostring(math.ceil(tonumber(icount)))
+ else
+ retstring ="0"
+ end
+else
+refresh_cmd = "uclient-fetch -q --no-check-certificate -O - 'https://easylist-downloads.adblockplus.org/easylistchina+easylist.txt' > /tmp/adnew.conf"
+sret = luci.sys.call(refresh_cmd .. " 2>/dev/null")
+if sret== 0 then
+ luci.sys.call("/usr/share/adbyby/ad-update")
+ icount = luci.sys.exec("cat /tmp/ad.conf | wc -l")
+ if tonumber(icount)>0 then
+ if nixio.fs.access("/usr/share/adbyby/dnsmasq.adblock") then
+ oldcount = luci.sys.exec("cat /usr/share/adbyby/dnsmasq.adblock | wc -l")
+ else
+ oldcount=0
+ end
+ if tonumber(icount) ~= tonumber(oldcount) then
+ luci.sys.exec("cp -f /tmp/ad.conf /usr/share/adbyby/dnsmasq.adblock")
+ luci.sys.exec("cp -f /tmp/ad.conf /tmp/etc/dnsmasq-adbyby.d/adblock")
+ luci.sys.exec("/etc/init.d/adbyby restart &")
+ retstring=tostring(math.ceil(tonumber(icount)))
+ else
+ retstring ="0"
+ end
+ else
+ retstring ="-1"
+ end
+ luci.sys.exec("rm -f /tmp/ad.conf")
+else
+ retstring ="-1"
+end
+end
+luci.http.prepare_content("application/json")
+luci.http.write_json({ ret=retstring ,retcount=icount})
+end
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/advanced.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/advanced.lua
new file mode 100644
index 000000000..1d9faa69a
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/advanced.lua
@@ -0,0 +1,57 @@
+
+local SYS = require "luci.sys"
+local ND = SYS.exec("cat /usr/share/adbyby/dnsmasq.adblock | wc -l")
+
+local ad_count=0
+if nixio.fs.access("/usr/share/adbyby/dnsmasq.adblock") then
+ad_count=tonumber(SYS.exec("cat /usr/share/adbyby/dnsmasq.adblock | wc -l"))
+end
+
+local rule_count=0
+if nixio.fs.access("/usr/share/adbyby/rules/") then
+rule_count=tonumber(SYS.exec("/usr/share/adbyby/rule-count '/usr/share/adbyby/rules/'"))
+end
+
+m = Map("adbyby")
+
+s = m:section(TypedSection, "adbyby")
+s.anonymous = true
+
+o = s:option(Flag, "block_ios")
+o.title = translate("Block Apple iOS OTA update")
+o.default = 0
+o.rmempty = false
+
+o = s:option(Flag, "block_cnshort")
+o.title = translate("Block CNshort APP and Website")
+o.default = 0
+o.rmempty = false
+
+o = s:option(Flag, "cron_mode")
+o.title = translate("Update the rule at 6 a.m. every morning and restart adbyby")
+o.default = 0
+o.rmempty = false
+
+o=s:option(DummyValue,"ad_data",translate("Adblock Plus Data"))
+o.rawhtml = true
+o.template = "adbyby/refresh"
+o.value =ad_count .. " " .. translate("Records")
+
+o=s:option(DummyValue,"rule_data",translate("Subscribe 3rd Rules Data"))
+o.rawhtml = true
+o.template = "adbyby/refresh"
+o.value =rule_count .. " " .. translate("Records")
+o.description = translate("AdGuardHome / Host / DNSMASQ rules auto-convert")
+
+o = s:option(Button,"delete",translate("Delete All Subscribe Rules"))
+o.inputstyle = "reset"
+o.write = function()
+ SYS.exec("rm -f /usr/share/adbyby/rules/data/* /usr/share/adbyby/rules/host/*")
+ SYS.exec("/etc/init.d/adbyby restart 2>&1 &")
+ luci.http.redirect(luci.dispatcher.build_url("admin", "services", "adbyby", "advanced"))
+end
+
+o = s:option(DynamicList, "subscribe_url", translate("Anti-AD Rules Subscribe"))
+o.rmempty = true
+
+return m
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/base.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/base.lua
new file mode 100644
index 000000000..b9872cb19
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/base.lua
@@ -0,0 +1,66 @@
+local NXFS = require "nixio.fs"
+local SYS = require "luci.sys"
+local HTTP = require "luci.http"
+local DISP = require "luci.dispatcher"
+
+local DL = SYS.exec("head -1 /tmp/adbyby/data/lazy.txt | awk -F' ' '{print $3,$4}'") or ""
+local DV = SYS.exec("head -1 /tmp/adbyby/data/video.txt | awk -F' ' '{print $3,$4}'") or ""
+local NR = SYS.exec("grep -v '^!' /usr/share/adbyby/data/rules.txt | wc -l")
+local NU = SYS.exec("cat /usr/share/adbyby/data/user.txt | wc -l")
+local UD = NXFS.readfile("/tmp/adbyby.updated") or "1970-01-01 00:00:00"
+
+m = Map("adbyby")
+m.title = translate("Adbyby Plus + Settings")
+m.description = translate("Adbyby Plus + can filter all kinds of banners, popups, video ads, and prevent tracking, privacy theft and a variety of malicious websites
Plus + version combination mode can operation with Adblock Plus Host,filtering ads without losing bandwidth")
+
+m:section(SimpleSection).template = "adbyby/adbyby_status"
+
+s = m:section(TypedSection, "adbyby")
+s.anonymous = true
+
+o = s:option(Flag, "enable", translate("Enable"))
+o.default = 0
+o.rmempty = false
+
+o = s:option(ListValue, "wan_mode", translate("Running Mode"))
+o:value("0", translate("Global Mode (The slowest and the best effects)"))
+o:value("1", translate("Plus + Mode (Filter domain name list and blacklist website.Recommended)"))
+o:value("2", translate("No filter Mode (Must set in Client Filter Mode Settings manually)"))
+o.default = 1
+o.rmempty = false
+
+o = s:option(Button, "restart", translate("Adbyby and Rule state"))
+o.inputtitle = translate("Update Adbyby Rules Manually")
+o.description = string.format(""..translate("Last Update Checked")..": %s
"..translate("Lazy Rule")..":%s
"..translate("Video Rule")..":%s", UD, DL, DV)
+o.inputstyle = "reload"
+o.write = function()
+ SYS.call("rm -rf /tmp/adbyby.updated /tmp/adbyby/admd5.json && /usr/share/adbyby/adbybyupdate.sh > /tmp/adupdate.log 2>&1 &")
+ SYS.call("sleep 5")
+ HTTP.redirect(DISP.build_url("admin", "services", "adbyby"))
+end
+
+t = m:section(TypedSection, "acl_rule", translate("Client Filter Mode Settings"))
+t.description = translate("Filter mode settings can be set to specific LAN clients ( No filter , Global filter ) . Does not need to be set by default.")
+t.template = "cbi/tblsection"
+t.sortable = true
+t.anonymous = true
+t.addremove = true
+
+e = t:option(Value, "ipaddr", translate("IP Address"))
+e.width = "40%"
+e.datatype = "ip4addr"
+e.placeholder = "0.0.0.0/0"
+luci.ip.neighbors({ family = 4 }, function(entry)
+ if entry.reachable then
+ e:value(entry.dest:string())
+ end
+end)
+
+e = t:option(ListValue, "filter_mode", translate("Filter Mode"))
+e.width = "40%"
+e.default = "disable"
+e.rmempty = false
+e:value("disable", translate("No filter"))
+e:value("global", translate("Global filter"))
+
+return m
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/black.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/black.lua
new file mode 100644
index 000000000..3fd32ec4e
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/black.lua
@@ -0,0 +1,23 @@
+local fs = require "nixio.fs"
+local conffile = "/usr/share/adbyby/adblack.conf"
+
+f = SimpleForm("custom")
+
+t = f:field(TextValue, "conf")
+t.rmempty = true
+t.rows = 13
+function t.cfgvalue()
+ return fs.readfile(conffile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.conf then
+ fs.writefile(conffile, data.conf:gsub("\r\n", "\n"))
+ luci.sys.call("/etc/init.d/adbyby restart")
+ end
+ end
+ return true
+end
+
+return f
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/block.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/block.lua
new file mode 100644
index 000000000..dd715fd88
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/block.lua
@@ -0,0 +1,23 @@
+local fs = require "nixio.fs"
+local conffile = "/usr/share/adbyby/blockip.conf"
+
+f = SimpleForm("custom")
+
+t = f:field(TextValue, "conf")
+t.rmempty = true
+t.rows = 13
+function t.cfgvalue()
+ return fs.readfile(conffile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.conf then
+ fs.writefile(conffile, data.conf:gsub("\r\n", "\n"))
+ luci.sys.call("/etc/init.d/adbyby restart")
+ end
+ end
+ return true
+end
+
+return f
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/esc.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/esc.lua
new file mode 100644
index 000000000..5c6f168a1
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/esc.lua
@@ -0,0 +1,23 @@
+local fs = require "nixio.fs"
+local conffile = "/usr/share/adbyby/adesc.conf"
+
+f = SimpleForm("custom")
+
+t = f:field(TextValue, "conf")
+t.rmempty = true
+t.rows = 13
+function t.cfgvalue()
+ return fs.readfile(conffile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.conf then
+ fs.writefile(conffile, data.conf:gsub("\r\n", "\n"))
+ luci.sys.call("/etc/init.d/adbyby restart")
+ end
+ end
+ return true
+end
+
+return f
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/help.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/help.lua
new file mode 100644
index 000000000..a0c20b90d
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/help.lua
@@ -0,0 +1,23 @@
+local fs = require "nixio.fs"
+local conffile = "/usr/share/adbyby/adhost.conf"
+
+f = SimpleForm("custom")
+
+t = f:field(TextValue, "conf")
+t.rmempty = true
+t.rows = 13
+function t.cfgvalue()
+ return fs.readfile(conffile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.conf then
+ fs.writefile(conffile, data.conf:gsub("\r\n", "\n"))
+ luci.sys.call("/etc/init.d/adbyby restart")
+ end
+ end
+ return true
+end
+
+return f
diff --git a/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/user.lua b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/user.lua
new file mode 100644
index 000000000..13887db99
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/model/cbi/adbyby/user.lua
@@ -0,0 +1,23 @@
+local fs = require "nixio.fs"
+local conffile = "/usr/share/adbyby/rules.txt"
+
+f = SimpleForm("custom")
+
+t = f:field(TextValue, "conf")
+t.rmempty = true
+t.rows = 13
+function t.cfgvalue()
+ return fs.readfile(conffile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.conf then
+ fs.writefile(conffile, data.conf:gsub("\r\n", "\n"))
+ luci.sys.call("/etc/init.d/adbyby restart")
+ end
+ end
+ return true
+end
+
+return f
diff --git a/luci-app-adbyby-plus/luasrc/view/adbyby/adbyby_status.htm b/luci-app-adbyby-plus/luasrc/view/adbyby/adbyby_status.htm
new file mode 100644
index 000000000..3b2f40e06
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/view/adbyby/adbyby_status.htm
@@ -0,0 +1,22 @@
+
+
+
diff --git a/luci-app-adbyby-plus/luasrc/view/adbyby/refresh.htm b/luci-app-adbyby-plus/luasrc/view/adbyby/refresh.htm
new file mode 100644
index 000000000..5c0285bcd
--- /dev/null
+++ b/luci-app-adbyby-plus/luasrc/view/adbyby/refresh.htm
@@ -0,0 +1,35 @@
+<%+cbi/valueheader%>
+
+
+<%=self.value%>
+<%+cbi/valuefooter%>
diff --git a/luci-app-adbyby-plus/po/zh-cn/adbyby.po b/luci-app-adbyby-plus/po/zh-cn/adbyby.po
new file mode 100644
index 000000000..f82a0b38c
--- /dev/null
+++ b/luci-app-adbyby-plus/po/zh-cn/adbyby.po
@@ -0,0 +1,198 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: dingpengyu \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+"X-Generator: Poedit 2.3.1\n"
+
+msgid "ADBYBY Plus +"
+msgstr "广告屏蔽大师 Plus+"
+
+msgid "Adbyby Plus + is Running"
+msgstr "广告屏蔽大师 Plus + 正在运行"
+
+msgid "Adbyby Plus + is Not Running"
+msgstr "广告屏蔽大师 Plus + 没有运行"
+
+msgid "Adbyby Plus + can filter all kinds of banners, popups, video ads, and prevent tracking, privacy theft and a variety of malicious websites
Plus + version combination mode can operation with Adblock Plus Host,filtering ads without losing bandwidth"
+msgstr "广告屏蔽大师 Plus + 可以全面过滤各种横幅、弹窗、视频广告,同时阻止跟踪、隐私窃取及各种恶意网站
Plus + 版本可以和 Adblock Plus Host 结合方式运行,过滤广告不损失带宽"
+
+msgid "Adbyby Plus +"
+msgstr "广告屏蔽大师 Plus+"
+
+msgid "Adbyby Plus + Settings"
+msgstr "广告屏蔽大师 Plus+(支持 AdGuardHome /Host / DNSMASQ 规则)"
+
+msgid "Base Setting"
+msgstr "基本设置"
+
+msgid "Running Mode"
+msgstr "运行模式"
+
+msgid "Global Mode (The slowest and the best effects)"
+msgstr "全局模式(最慢, 效果最好)"
+
+msgid "Plus + Mode (Filter domain name list and blacklist website.Recommended)"
+msgstr "Plus + 模式(只过滤列表内域名结合ABP名单。推荐!)"
+
+msgid "No filter Mode (Must set in Client Filter Mode Settings manually)"
+msgstr "手动代理模式(必须手动设置浏览器代理,或者客户端过滤模式设置)"
+
+msgid "Transparent proxy"
+msgstr "透明代理"
+
+msgid "Click to disable"
+msgstr "点击关闭"
+
+msgid "Click to enable"
+msgstr "点击开启"
+
+msgid "Adbyby and Rule state"
+msgstr "规则状态"
+
+msgid "Restart Adbyby"
+msgstr "重启Adbyby"
+
+msgid "Plus+ Domain List"
+msgstr "Plus+ 模式过滤的域名"
+
+msgid "Bypass Domain List"
+msgstr "域名白名单"
+
+msgid "Black Domain List"
+msgstr "域名黑名单"
+
+msgid "Black IP List"
+msgstr "IP黑名单"
+
+msgid "Advance Setting"
+msgstr "高级设置"
+
+msgid "Update the rule at 6 a.m. every morning and restart adbyby"
+msgstr "每天凌晨6点更新规则并重启"
+
+msgid "ADP Host List"
+msgstr "Adblock Plus Hosts 记录数"
+
+msgid "Adblock Plus Data"
+msgstr "Adblock Plus 规则数据库"
+
+msgid "Subscribe 3rd Rules Data"
+msgstr "订阅第三方去广告规则"
+
+msgid "Records"
+msgstr "条记录"
+
+msgid "Refresh Data"
+msgstr "更新数据库"
+
+msgid "Refresh..."
+msgstr "正在更新,请稍候.."
+
+msgid "Refresh OK!"
+msgstr "更新成功!"
+
+msgid "Refresh Error!"
+msgstr "更新失败!"
+
+msgid "No new data!"
+msgstr "你已经是最新数据,无需更新!"
+
+msgid "Total Records:"
+msgstr "新的总纪录数:"
+
+msgid "Delete All Subscribe Rules"
+msgstr "清空所有订阅的规则库"
+
+msgid "Anti-AD Rules Subscribe"
+msgstr "广告过滤规则订阅URL"
+
+msgid "AdGuardHome / Host / DNSMASQ rules auto-convert"
+msgstr "AdGuardHome / Host / DNSMASQ 规则自动识别"
+
+msgid "Manually force update
Adblock Plus Host List"
+msgstr "手动强制更新
Adblock Plus Host List"
+
+msgid "Update Adbyby Rules Manually"
+msgstr "手动更新规则"
+
+msgid "Block Apple iOS OTA update"
+msgstr "拦截 Apple iOS 的OTA更新"
+
+msgid "Block CNshort APP and Website"
+msgstr "拦截 短视频 APP 和网站"
+
+msgid "RAM Running Mode"
+msgstr "内存运行模式"
+
+msgid "Running Adbyby in RAM.More speed,less disk consumption"
+msgstr "在内存中运行Adbyby。更快的速度,更少的存储空间损耗"
+
+msgid "User-defined Rule"
+msgstr "用户自定义规则"
+
+msgid "Client Filter Mode Settings"
+msgstr "客户端过滤模式设置"
+
+msgid "Filter mode settings can be set to specific LAN clients ( No filter , Global filter ) . Does not need to be set by default."
+msgstr "可以为局域网客户端分别设置不同的过滤模式 ( 不过滤 , 全局过滤 ) 。默认无需设置。"
+
+msgid "IP Address"
+msgstr "IP地址"
+
+msgid "Filter Mode"
+msgstr "过滤模式"
+
+msgid "(!)Note that you should fill to the domain name ONLY. For example, http://www.baidu.com only needs to write to baidu.com. One line for each"
+msgstr "这些域名在 Plus 模式中会被过滤。你需要要填写域名即可,例如 http://www.baidu.com,你写 baidu.com 即可。每行一个域名"
+
+msgid "(!)Will Never filter these Domain"
+msgstr "永不过滤白名单内的域名(所有模式中生效)"
+
+msgid "(!)Will Always block these Domain"
+msgstr "拦截黑名单内的域名(所有模式中生效)"
+
+msgid "(!)Will Always block these IP"
+msgstr "拦截黑名单内的IP地址(所有模式中生效)"
+
+msgid "Each line of the beginning exclamation mark is considered an annotation."
+msgstr "每行一条规则,感叹号开头的被认为是注释。"
+
+msgid "Adblock Plus Host List:"
+msgstr "Adblock Plus Host 列表:"
+
+msgid "Note: It needs to download and convert the rules. The background process may takes 60-120 seconds to run.
After completed it would automatically refresh, please do not duplicate click!"
+msgstr "注意:需要下载并转换规则。后台进程可能需要60-120秒运行。完成后会自动刷新,请不要重复点击!"
+
+msgid "No filter"
+msgstr "不过滤"
+
+msgid "Global filter"
+msgstr "全局过滤"
+
+msgid "Update adbyby rules form official website first"
+msgstr "优先从官方网站更新规则"
+
+msgid "Last Update Checked"
+msgstr "上一次检查规则更新"
+
+msgid "Lazy Rule"
+msgstr "正式版规则"
+
+msgid "Video Rule"
+msgstr "测试版规则"
+
+msgid "Adblock Plus Host List"
+msgstr "广告屏蔽大师 Plus 规则列表"
+
+msgid "Block Domain List"
+msgstr "域名黑名单"
+
+msgid "Block IP List"
+msgstr "IP黑名单"
diff --git a/luci-app-adbyby-plus/root/etc/config/adbyby b/luci-app-adbyby-plus/root/etc/config/adbyby
new file mode 100644
index 000000000..d2afc6ed7
--- /dev/null
+++ b/luci-app-adbyby-plus/root/etc/config/adbyby
@@ -0,0 +1,11 @@
+
+config adbyby
+ option daemon '2'
+ option lan_mode '0'
+ option cron_mode '1'
+ option block_ios '0'
+ option enable '0'
+ option wan_mode '1'
+ option mem_mode '1'
+ option update_source '1'
+
diff --git a/luci-app-adbyby-plus/root/etc/init.d/adbyby b/luci-app-adbyby-plus/root/etc/init.d/adbyby
new file mode 100755
index 000000000..7fe29caba
--- /dev/null
+++ b/luci-app-adbyby-plus/root/etc/init.d/adbyby
@@ -0,0 +1,254 @@
+#!/bin/sh /etc/rc.common
+
+START=96
+STOP=10
+
+
+EXTRA_COMMANDS="add_rule del_rule reload_rule"
+PROG_PATH=/usr/share/adbyby
+DATA_PATH=$PROG_PATH/data
+WAN_FILE=/var/etc/dnsmasq-adbyby.d/03-adbyby-ipset.conf
+CRON_FILE=/etc/crontabs/root
+CONFIG=adbyby
+ipt_n="iptables -t nat"
+
+uci_get_by_name() {
+ local ret=$(uci get $CONFIG.$1.$2 2>/dev/null)
+ echo ${ret:=$3}
+}
+
+uci_get_by_type() {
+ local index=0
+ if [ -n $4 ]; then
+
+ index=$4
+ fi
+ local ret=$(uci get $CONFIG.@$1[$index].$2 2>/dev/null)
+ echo ${ret:=$3}
+}
+
+get_config()
+{
+ config_get_bool enable $1 enable 0
+ config_get_bool cron_mode $1 cron_mode 1
+ config_get wan_mode $1 wan_mode 1
+ config_get_bool block_ios $1 block_ios 0
+ config_get_bool mem_mode $1 mem_mode 1
+ config_get_bool block_cnshort $1 block_cnshort 0
+}
+
+add_rules()
+{
+ rm -f $DATA_PATH/user.bin
+ grep -v ^! $PROG_PATH/rules.txt > $DATA_PATH/user.txt
+ cp $PROG_PATH/rules.txt $DATA_PATH/rules.txt
+}
+
+
+add_cron()
+{
+ if [ $cron_mode -eq 1 ]; then
+ sed -i '/adblock.sh/d' $CRON_FILE
+ echo '0 6 * * * /usr/share/adbyby/adblock.sh > /tmp/adupdate.log 2>&1' >> $CRON_FILE
+ crontab $CRON_FILE
+ fi
+}
+
+del_cron()
+{
+ sed -i '/adblock.sh/d' $CRON_FILE
+ /etc/init.d/cron restart
+}
+
+ip_rule()
+{
+
+ ipset -N adbyby_esc hash:ip
+ $ipt_n -A ADBYBY -m set --match-set adbyby_esc dst -j RETURN
+
+ local icount=$(uci show adbyby | grep 'filter_mode' | wc -l)
+ let icount=icount-1
+ for i in $(seq 0 $icount)
+ do
+ local ip=$(uci_get_by_type acl_rule ipaddr '' $i)
+ local mode=$(uci_get_by_type acl_rule filter_mode '' $i)
+ case "$mode" in
+ disable)
+ $ipt_n -A ADBYBY -s $ip -j RETURN
+ ;;
+ global)
+ $ipt_n -A ADBYBY -s $ip -p tcp -j REDIRECT --to-ports 8118
+ $ipt_n -A ADBYBY -s $ip -j RETURN
+ ;;
+ esac
+ done
+
+
+
+ case $wan_mode in
+ 0)
+ ;;
+ 1)
+ ipset -N adbyby_wan hash:ip
+ $ipt_n -A ADBYBY -m set ! --match-set adbyby_wan dst -j RETURN
+ ;;
+ 2)
+ $ipt_n -A ADBYBY -j RETURN
+ ;;
+ esac
+
+ echo "create blockip hash:net family inet hashsize 1024 maxelem 65536" > /tmp/blockip.ipset
+ awk '!/^$/&&!/^#/{printf("add blockip %s'" "'\n",$0)}' /usr/share/adbyby/blockip.conf >> /tmp/blockip.ipset
+ ipset -! restore < /tmp/blockip.ipset 2>/dev/null
+ iptables -I FORWARD -m set --match-set blockip dst -j DROP
+ iptables -I OUTPUT -m set --match-set blockip dst -j DROP
+}
+
+add_dns()
+{
+ mkdir -p /var/etc/dnsmasq-adbyby.d
+ mkdir -p /tmp/dnsmasq.d
+
+ awk '!/^$/&&!/^#/{printf("ipset=/%s/'"adbyby_esc"'\n",$0)}' $PROG_PATH/adesc.conf > /var/etc/dnsmasq-adbyby.d/06-dnsmasq.esc
+ awk '!/^$/&&!/^#/{printf("address=/%s/'"0.0.0.0"'\n",$0)}' $PROG_PATH/adblack.conf > /var/etc/dnsmasq-adbyby.d/07-dnsmasq.black
+
+ echo 'conf-dir=/var/etc/dnsmasq-adbyby.d' > /tmp/dnsmasq.d/dnsmasq-adbyby.conf
+
+ local var=1
+ if [ $wan_mode -eq 1 ]; then
+ awk '!/^$/&&!/^#/{printf("ipset=/%s/'"adbyby_wan"'\n",$0)}' $PROG_PATH/adhost.conf > $WAN_FILE
+ if ls /var/etc/dnsmasq-adbyby.d/* >/dev/null 2>&1; then
+ mkdir -p /tmp/dnsmasq.d
+ cp /usr/share/adbyby/dnsmasq.adblock /var/etc/dnsmasq-adbyby.d/04-dnsmasq.adblock
+ cp /usr/share/adbyby/dnsmasq.ads /var/etc/dnsmasq-adbyby.d/05-dnsmasq.ads
+ fi
+ fi
+
+ mkdir -p /tmp/adbyby/rules/data /tmp/adbyby/rules/host
+ rm -f /tmp/adbyby/rules/data/* /tmp/adbyby/rules/host/*
+ cp -a /usr/share/adbyby/rules/data/* /tmp/adbyby/rules/data 2>/dev/null
+ cp -a /usr/share/adbyby/rules/host/* /tmp/adbyby/rules/host 2>/dev/null
+ echo 'addn-hosts=/tmp/adbyby/rules/host/' >> /tmp/dnsmasq.d/dnsmasq-adbyby.conf
+ echo 'conf-dir=/tmp/adbyby/rules/data' >> /tmp/dnsmasq.d/dnsmasq-adbyby.conf
+
+ [ $block_ios -eq 1 ] && echo 'address=/mesu.apple.com/0.0.0.0' >> /tmp/dnsmasq.d/dnsmasq-adbyby.conf
+ if [ $block_cnshort -eq 1 ]; then
+ cat <<-EOF >/tmp/etc/dnsmasq-adbyby.d/08-dnsmasq.cnshort
+address=/api.amemv.com/0.0.0.0
+address=/.amemv.com/0.0.0.0
+address=/.tiktokv.com/0.0.0.0
+address=/.snssdk.com/0.0.0.0
+address=/.douyin.com/0.0.0.0
+address=/.ixigua.com/0.0.0.0
+address=/.pstatp.com/0.0.0.0
+address=/.ixiguavideo.com/0.0.0.0
+address=/.v.kandian.qq.com/0.0.0.0
+address=/.yximgs.com/0.0.0.0
+address=/.gifshow.com/0.0.0.0
+address=/.ksapisrv.com/0.0.0.0
+address=/.kuaishoupay.com/0.0.0.0
+address=/.ksyun.com/0.0.0.0
+address=/.live.xycdn.com/0.0.0.0
+address=/.danuoyi.alicdn.com/0.0.0.0
+address=/.v.weishi.qq.com/0.0.0.0
+address=/.pearvideo.com/0.0.0.0
+address=/.miaopai.com/0.0.0.0
+address=/.kuaishou.com/0.0.0.0
+address=/.qupai.me/0.0.0.0
+address=/.meipai.com/0.0.0.0
+address=/.huoshan.com/0.0.0.0
+address=/.ergengtv.com/0.0.0.0
+address=/.baijiahao.baidu.com/0.0.0.0
+address=/.xiongzhang.baidu.com/0.0.0.0
+ EOF
+ fi
+}
+
+del_dns()
+{
+ rm -f /tmp/dnsmasq.d/dnsmasq-adbyby.conf
+ rm -f /var/etc/dnsmasq-adbyby.d/*
+ rm -f /tmp/adbyby_host.conf
+}
+
+
+add_rule()
+{
+ $ipt_n -N ADBYBY
+ $ipt_n -A ADBYBY -d 0.0.0.0/8 -j RETURN
+ $ipt_n -A ADBYBY -d 10.0.0.0/8 -j RETURN
+ $ipt_n -A ADBYBY -d 127.0.0.0/8 -j RETURN
+ $ipt_n -A ADBYBY -d 169.254.0.0/16 -j RETURN
+ $ipt_n -A ADBYBY -d 172.16.0.0/12 -j RETURN
+ $ipt_n -A ADBYBY -d 192.168.0.0/16 -j RETURN
+ $ipt_n -A ADBYBY -d 224.0.0.0/4 -j RETURN
+ $ipt_n -A ADBYBY -d 240.0.0.0/4 -j RETURN
+ ip_rule
+ if [ $(ipset list music -name -quiet | grep music) ]; then
+ $ipt_n -A ADBYBY -m set --match-set music dst -j RETURN 2>/dev/null
+ fi
+ $ipt_n -A ADBYBY -p tcp -j REDIRECT --to-ports 8118 2>/dev/null
+ $ipt_n -I PREROUTING -p tcp --dport 80 -j ADBYBY 2>/dev/null
+
+ mkdir -p /var/etc
+ echo -e "/etc/init.d/adbyby restart" > "/var/etc/adbyby.include"
+}
+
+del_rule()
+{
+ $ipt_n -D PREROUTING -p tcp --dport 80 -j ADBYBY 2>/dev/null
+ $ipt_n -F ADBYBY 2>/dev/null
+ $ipt_n -X ADBYBY 2>/dev/null
+ iptables -D FORWARD -m set --match-set blockip dst -j DROP 2>/dev/null
+ iptables -D OUTPUT -m set --match-set blockip dst -j DROP 2>/dev/null
+ ipset -F adbyby_esc 2>/dev/null
+ ipset -X adbyby_esc 2>/dev/null
+ ipset -F adbyby_wan 2>/dev/null
+ ipset -X adbyby_wan 2>/dev/null
+ ipset -F blockip 2>/dev/null
+ ipset -X blockip 2>/dev/null
+}
+
+reload_rule()
+{
+ config_load adbyby
+ config_foreach get_config adbyby
+ del_rule
+ [ $enable -eq 0 ] && exit 0
+ add_rule
+}
+
+start()
+{
+ config_load adbyby
+ config_foreach get_config adbyby
+
+ if [ $enable -ne 0 ]; then
+ add_cron
+ [ ! -d "/tmp/adbyby/data" ] && cp -a /usr/share/adbyby /tmp/ && rm -f /tmp/adbyby.updated
+ add_rules
+
+ /tmp/adbyby/adbyby >/dev/null 2>&1 &
+
+ add_dns
+ add_rule
+ /etc/init.d/dnsmasq restart
+ fi
+}
+
+stop()
+{
+ kill -9 $(busybox ps -w | grep "/tmp/adbyby/adbyby" | grep -v grep | grep -v update| grep -v adblock | awk '{print $1}') >/dev/null 2>&1
+ config_load adbyby
+ config_foreach get_config adbyby
+ del_rule
+ del_cron
+ del_dns
+ /etc/init.d/dnsmasq restart
+}
+
+boot()
+{
+ mkdir -p /tmp/adbyby && cp -a /usr/share/adbyby /tmp/
+ start
+}
diff --git a/luci-app-adbyby-plus/root/etc/uci-defaults/luci-adbyby b/luci-app-adbyby-plus/root/etc/uci-defaults/luci-adbyby
new file mode 100755
index 000000000..33cfa4787
--- /dev/null
+++ b/luci-app-adbyby-plus/root/etc/uci-defaults/luci-adbyby
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+ delete ucitrack.@adbyby[-1]
+ add ucitrack adbyby
+ set ucitrack.@adbyby[-1].init=adbyby
+ commit ucitrack
+ delete firewall.adbyby
+ set firewall.adbyby=include
+ set firewall.adbyby.type=script
+ set firewall.adbyby.path=/var/etc/adbyby.include
+ set firewall.adbyby.reload=1
+ delete firewall.adblock
+ add firewall rule
+ rename firewall.@rule[-1]="adblock"
+ set firewall.@rule[-1].name="adblock"
+ set firewall.@rule[-1].target="DROP"
+ set firewall.@rule[-1].src="wan"
+ set firewall.@rule[-1].proto="tcp"
+ set firewall.@rule[-1].dest_port="8118"
+ commit firewall
+EOF
+
+mkdir -p /etc/dnsmasq.d
+
+/etc/init.d/adbyby stop
+/etc/init.d/adbyby enable
+
+rm -f /tmp/luci-indexcache
+exit 0
diff --git a/luci-app-adbyby-plus/root/lib/upgrade/keep.d/adbyby b/luci-app-adbyby-plus/root/lib/upgrade/keep.d/adbyby
new file mode 100644
index 000000000..49b3eaafd
--- /dev/null
+++ b/luci-app-adbyby-plus/root/lib/upgrade/keep.d/adbyby
@@ -0,0 +1,6 @@
+/usr/share/adbyby/adhost.conf
+/usr/share/adbyby/adblack.conf
+/usr/share/adbyby/blockip.conf
+/usr/share/adbyby/adesc.conf
+/usr/share/adbyby/rules.txt
+/usr/share/adbyby/dnsmasq.adblock
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/ad-update b/luci-app-adbyby-plus/root/usr/share/adbyby/ad-update
new file mode 100755
index 000000000..77b43438f
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/ad-update
@@ -0,0 +1,11 @@
+#!/bin/sh -e
+if [ -f /tmp/adnew.conf ]; then
+ if (grep -wq "address=" /tmp/adnew.conf) ; then
+ cp /tmp/adnew.conf /tmp/ad.conf
+ elif (grep -wq "0.0.0.0" /tmp/adnew.conf) ; then
+ cp /tmp/adnew.conf /tmp/ad.conf
+ else
+ cat /tmp/adnew.conf | grep ^\|\|[^\*]*\^$ | sed -e 's:||:address\=\/:' -e 's:\^:/0\.0\.0\.0:' > /tmp/ad.conf
+ fi
+fi
+rm -f /tmp/adnew.conf
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adblack.conf b/luci-app-adbyby-plus/root/usr/share/adbyby/adblack.conf
new file mode 100644
index 000000000..a532f5825
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adblack.conf
@@ -0,0 +1,14 @@
+gvod.aiseejapp.atianqi.com
+stat.pandora.xiaomi.com
+upgrade.mishop.pandora.xiaomi.com
+logonext.tv.kuyun.com
+config.kuyun.com
+mishop.pandora.xiaomi.com
+dvb.pandora.xiaomi.com
+api.ad.xiaomi.com
+de.pandora.xiaomi.com
+data.mistat.xiaomi.com
+jellyfish.pandora.xiaomi.com
+gallery.pandora.xiaomi.com
+o2o.api.xiaomi.com
+bss.pandora.xiaomi.com
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adblock.sh b/luci-app-adbyby-plus/root/usr/share/adbyby/adblock.sh
new file mode 100755
index 000000000..8224179dd
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adblock.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+uclient-fetch --no-check-certificate -O - 'https://easylist-downloads.adblockplus.org/easylistchina+easylist.txt' > /tmp/adnew.conf
+if [ -s "/tmp/adnew.conf" ];then
+ /usr/share/adbyby/ad-update
+fi
+
+rm -f /tmp/adbyby.updated
+sleep 10
+/etc/init.d/adbyby restart
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adbybyfirst.sh b/luci-app-adbyby-plus/root/usr/share/adbyby/adbybyfirst.sh
new file mode 100755
index 000000000..532fe182d
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adbybyfirst.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+PROG_PATH=$(pwd)
+$PROG_PATH/adbybyupdate.sh
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adbybyupdate.sh b/luci-app-adbyby-plus/root/usr/share/adbyby/adbybyupdate.sh
new file mode 100755
index 000000000..5143cca3d
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adbybyupdate.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+if [ ! -f "/tmp/adbyby.updated" ];then
+ wget_ok="0"
+ while [ "$wget_ok" = "0" ]
+ do
+ uclient-fetch --spider --quiet --timeout=3 http://www.baidu.com
+ if [ "$?" == "0" ]; then
+ wget_ok="1"
+
+ touch /tmp/md5.json && uclient-fetch --no-check-certificate -T 10 -O /tmp/md5.json https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/md5.json
+ adm5=$(md5sum /tmp/md5.json | awk -F' ' '{print $1}')
+ touch /tmp/adbyby/admd5.json && bmd5=$(md5sum /tmp/adbyby/admd5.json | awk -F' ' '{print $1}')
+ if [ "$adm5" == "$bmd5" ];then
+ echo "Rules MD5 are the same!"
+ echo $(date "+%Y-%m-%d %H:%M:%S") > /tmp/adbyby.updated
+ exit 0
+ elif [ -s /tmp/md5.json ];then
+
+ touch /tmp/lazy.txt && uclient-fetch --no-check-certificate -T 10 -O /tmp/lazy.txt https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/lazy.txt
+ touch /tmp/video.txt && uclient-fetch --no-check-certificate -T 10 -O /tmp/video.txt https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/video.txt
+
+ touch /tmp/local-md5.json && md5sum /tmp/lazy.txt /tmp/video.txt > /tmp/local-md5.json
+
+
+ lazy_local=$(grep 'lazy' /tmp/local-md5.json | awk -F' ' '{print $1}')
+ video_local=$(grep 'video' /tmp/local-md5.json | awk -F' ' '{print $1}')
+ lazy_online=$(sed 's/":"/\n/g' /tmp/md5.json | sed 's/","/\n/g' | sed -n '2p')
+ video_online=$(sed 's/":"/\n/g' /tmp/md5.json | sed 's/","/\n/g' | sed -n '4p')
+
+ if [ "$lazy_online"x == "$lazy_local"x -a "$video_online"x == "$video_local"x ]; then
+ echo "adbyby rules MD5 OK!"
+ mv /tmp/lazy.txt /tmp/adbyby/data/lazy.txt
+ mv /tmp/video.txt /tmp/adbyby/data/video.txt
+ mv /tmp/md5.json /tmp/adbyby/admd5.json
+ echo $(date "+%Y-%m-%d %H:%M:%S") > /tmp/adbyby.updated
+ fi
+ fi
+ else
+ sleep 10
+ fi
+ done
+
+ rm -f /tmp/adbyby/data/*.bak
+
+ kill -9 $(busybox ps -w | grep "/tmp/adbyby/adbyby" | grep -v grep | grep -v update| grep -v adblock | awk '{print $1}') >/dev/null 2>&1
+ /tmp/adbyby/adbyby >/dev/null 2>&1 &
+fi
+
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adesc.conf b/luci-app-adbyby-plus/root/usr/share/adbyby/adesc.conf
new file mode 100644
index 000000000..c5898a128
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adesc.conf
@@ -0,0 +1,3 @@
+weixin.qq.com
+qpic.cn
+imtt.qq.com
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adhost.conf b/luci-app-adbyby-plus/root/usr/share/adbyby/adhost.conf
new file mode 100644
index 000000000..5f4f1a124
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adhost.conf
@@ -0,0 +1,68 @@
+cbjs.baidu.com
+list.video.baidu.com
+nsclick.baidu.com
+play.baidu.com
+sclick.baidu.com
+tieba.baidu.com
+baidustatic.com
+bdimg.com
+bdstatic.com
+share.baidu.com
+hm.baidu.com
+v.baidu.com
+cpro.baidu.com
+1000fr.net
+atianqi.com
+56.com
+v-56.com
+acfun.com
+acfun.tv
+baofeng.com
+baofeng.net
+cntv.cn
+hoopchina.com.cn
+funshion.com
+fun.tv
+hitvs.cn
+hljtv.com
+iqiyi.com
+qiyi.com
+agn.aty.sohu.com
+itc.cn
+kankan.com
+ku6.com
+letv.com
+letvcloud.com
+letvimg.com
+pplive.cn
+pps.tv
+ppsimg.com
+pptv.com
+www.qq.com
+l.qq.com
+v.qq.com
+video.sina.com.cn
+tudou.com
+wasu.cn
+analytics-union.xunlei.com
+kankan.xunlei.com
+youku.com
+hunantv.com
+ifeng.com
+renren.com
+mediav.com
+cnbeta.com
+mydrivers.com
+168f.info
+doubleclick.net
+126.net
+sohu.com
+right.com.cn
+50bang.org
+you85.cn
+jiuzhilan.com
+googles.com
+cnbetacdn.com
+ptqy.gitv.tv
+admaster.com.cn
+serving-sys.com
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/admem.sh b/luci-app-adbyby-plus/root/usr/share/adbyby/admem.sh
new file mode 100755
index 000000000..36d6deb19
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/admem.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+if [ ! -f "/tmp/adbyby.updated" ];then
+ touch /tmp/adbyby.mem
+ wget_ok="0"
+ while [ "$wget_ok" = "0" ]
+ do
+ uclient-fetch --spider --quiet --tries=1 --timeout=3 www.baidu.com
+ if [ "$?" == "0" ]; then
+ wget_ok="1"
+
+ touch /tmp/md5.json && uclient-fetch --no-check-certificate -t 1 -T 10 -O /tmp/md5.json https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/md5.json
+ adm5=$(md5sum /tmp/md5.json | awk -F' ' '{print $1}')
+ touch /tmp/adbyby/admd5.json && bmd5=$(md5sum /tmp/adbyby/admd5.json | awk -F' ' '{print $1}')
+ if [ "$adm5" == "$bmd5" ];then
+ echo "Rules MD5 are the same!"
+ echo $(date "+%Y-%m-%d %H:%M:%S") > /tmp/adbyby.updated
+ exit 0
+ else
+
+ touch /tmp/lazy.txt && uclient-fetch --no-check-certificate -t 1 -T 10 -O /tmp/lazy.txt https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/lazy.txt
+ touch /tmp/video.txt && uclient-fetch --no-check-certificate -t 1 -T 10 -O /tmp/video.txt https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/video.txt
+
+ touch /tmp/local-md5.json && md5sum /tmp/lazy.txt /tmp/video.txt > /tmp/local-md5.json
+
+
+ lazy_local=$(grep 'lazy' /tmp/local-md5.json | awk -F' ' '{print $1}')
+ video_local=$(grep 'video' /tmp/local-md5.json | awk -F' ' '{print $1}')
+ lazy_online=$(sed 's/":"/\n/g' /tmp/md5.json | sed 's/","/\n/g' | sed -n '2p')
+ video_online=$(sed 's/":"/\n/g' /tmp/md5.json | sed 's/","/\n/g' | sed -n '4p')
+
+ if [ "$lazy_online"x == "$lazy_local"x -a "$video_online"x == "$video_local"x ]; then
+ echo "adbyby rules MD5 OK!"
+ mv /tmp/lazy.txt /tmp/adbyby/data/lazy.txt
+ mv /tmp/video.txt /tmp/adbyby/data/video.txt
+ mv /tmp/md5.json /tmp/adbyby/admd5.json
+ echo $(date "+%Y-%m-%d %H:%M:%S") > /tmp/adbyby.updated
+ fi
+ fi
+ else
+ sleep 10
+ fi
+ done
+
+ sleep 10 && /etc/init.d/adbyby restart
+fi
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/adupdate.sh b/luci-app-adbyby-plus/root/usr/share/adbyby/adupdate.sh
new file mode 100755
index 000000000..7ae9e6424
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/adupdate.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+rm -f /usr/share/adbyby/data/*.bak
+
+touch /tmp/local-md5.json && md5sum /usr/share/adbyby/data/lazy.txt /usr/share/adbyby/data/video.txt > /tmp/local-md5.json
+touch /tmp/md5.json && uclient-fetch --no-check-certificate -T 10 -O /tmp/md5.json https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/md5.json
+
+lazy_local=$(grep 'lazy' /tmp/local-md5.json | awk -F' ' '{print $1}')
+video_local=$(grep 'video' /tmp/local-md5.json | awk -F' ' '{print $1}')
+lazy_online=$(sed 's/":"/\n/g' /tmp/md5.json | sed 's/","/\n/g' | sed -n '2p')
+video_online=$(sed 's/":"/\n/g' /tmp/md5.json | sed 's/","/\n/g' | sed -n '4p')
+
+if [ "$lazy_online"x != "$lazy_local"x -o "$video_online"x != "$video_local"x ]; then
+ echo "MD5 not match! Need update!"
+ touch /tmp/lazy.txt && uclient-fetch --no-check-certificate -T 10 -O /tmp/lazy.txt https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/lazy.txt
+ touch /tmp/video.txt && uclient-fetch --no-check-certificate -T 10 -O /tmp/video.txt https://adbyby.coding.net/p/xwhyc-rules/d/xwhyc-rules/git/raw/master/video.txt
+ touch /tmp/local-md5.json && md5sum /tmp/lazy.txt /tmp/video.txt > /tmp/local-md5.json
+ lazy_local=$(grep 'lazy' /tmp/local-md5.json | awk -F' ' '{print $1}')
+ video_local=$(grep 'video' /tmp/local-md5.json | awk -F' ' '{print $1}')
+ if [ "$lazy_online"x == "$lazy_local"x -a "$video_online"x == "$video_local"x ]; then
+ echo "New rules MD5 match!"
+ mv /tmp/lazy.txt /usr/share/adbyby/data/lazy.txt
+ mv /tmp/video.txt /usr/share/adbyby/data/video.txt
+ echo $(date +"%Y-%m-%d %H:%M:%S") > /tmp/adbyby.updated
+ fi
+else
+ echo "MD5 match! No need to update!"
+fi
+
+rm -f /tmp/lazy.txt /tmp/video.txt /tmp/local-md5.json /tmp/md5.json
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/blockip.conf b/luci-app-adbyby-plus/root/usr/share/adbyby/blockip.conf
new file mode 100644
index 000000000..55cb84b53
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/blockip.conf
@@ -0,0 +1 @@
+2.2.2.2
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.adblock b/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.adblock
new file mode 100644
index 000000000..e69de29bb
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.ads b/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.ads
new file mode 100644
index 000000000..ec1a22189
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.ads
@@ -0,0 +1,4 @@
+address=/p.tanx.com/0.0.0.0
+address=/googlesyndication.com/0.0.0.0
+address=/linkvans.com/0.0.0.0
+server=/valf.atm.youku.com/114.114.114.114
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.esc b/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.esc
new file mode 100644
index 000000000..6cfd87056
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/dnsmasq.esc
@@ -0,0 +1,3 @@
+ipset=/weixin.qq.com/adbyby_wan
+ipset=/qpic.cn/adbyby_wan
+ipset=/imtt.qq.com/adbyby_wan
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/firewall.include b/luci-app-adbyby-plus/root/usr/share/adbyby/firewall.include
new file mode 100755
index 000000000..4dcec1e26
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/firewall.include
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+if [ -f /tmp/blockip.ipset ];then
+
+adbyby_enable=$(uci get adbyby.@adbyby[0].enable)
+
+if [ $adbyby_enable -eq 1 ]; then
+ if pidof adbyby>/dev/null; then
+ /etc/init.d/adbyby reload_rule
+ else
+ /etc/init.d/adbyby restart
+ fi
+fi
+
+fi
+
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/rule-count b/luci-app-adbyby-plus/root/usr/share/adbyby/rule-count
new file mode 100755
index 000000000..9f7388738
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/rule-count
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+find $1 -type f -exec cat {} \; | wc -l
\ No newline at end of file
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/rule-update b/luci-app-adbyby-plus/root/usr/share/adbyby/rule-update
new file mode 100755
index 000000000..95239bac8
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/rule-update
@@ -0,0 +1,36 @@
+#!/bin/sh -e
+
+url_md5() {
+ echo -n $1|md5sum|cut -d ' ' -f1
+}
+
+convert_rules() {
+mkdir -p /tmp/rules/data
+mkdir -p /tmp/rules/host
+
+uclient-fetch -q --no-check-certificate $1 -O /tmp/adnew.conf
+
+rulename=$(url_md5 $1)
+
+if [ -f /tmp/adnew.conf ]; then
+ if (grep -wq "address=" /tmp/adnew.conf) ; then
+ cp /tmp/adnew.conf /tmp/rules/data/$rulename.conf
+ elif (grep -wq -e"0.0.0.0" -e"127.0.0.1" /tmp/adnew.conf) ; then
+ cp /tmp/adnew.conf /tmp/rules/host/$rulename.txt
+ else
+ cat /tmp/adnew.conf | grep ^\|\|[^\*]*\^$ | sed -e 's:||:address\=\/:' -e 's:\^:/0\.0\.0\.0:' > /tmp/rules/data/$rulename.conf
+ fi
+fi
+rm -rf /tmp/adnew.conf
+}
+
+ARRAY=$(uci get adbyby.@adbyby[0].subscribe_url 2> /dev/null)
+
+rm -rf /tmp/rules
+
+i=1
+for j in $ARRAY
+do
+ convert_rules $j
+ i=`expr $i + 1`
+done
\ No newline at end of file
diff --git a/luci-app-adbyby-plus/root/usr/share/adbyby/rules.txt b/luci-app-adbyby-plus/root/usr/share/adbyby/rules.txt
new file mode 100644
index 000000000..5c44b0e8c
--- /dev/null
+++ b/luci-app-adbyby-plus/root/usr/share/adbyby/rules.txt
@@ -0,0 +1,15 @@
+! ------------------------------ ADByby 自定义过滤语法简表---------------------------------
+! -------------- 规则基于abp规则,并进行了字符替换部分的扩展-----------------------------
+! ABP规则请参考https://adblockplus.org/zh_CN/filters,下面为大致摘要
+! "!" 为行注释符,注释行以该符号起始作为一行注释语义,用于规则描述
+! "*" 为字符通配符,能够匹配0长度或任意长度的字符串,该通配符不能与正则语法混用。
+! "^" 为分隔符,可以是除了字母、数字或者 _ - . % 之外的任何字符。
+! "|" 为管线符号,来表示地址的最前端或最末端
+! "||" 为子域通配符,方便匹配主域名下的所有子域。
+! "~" 为排除标识符,通配符能过滤大多数广告,但同时存在误杀, 可以通过排除标识符修正误杀链接。
+! "##" 为元素选择器标识符,后面跟需要隐藏元素的CSS样式例如 #ad_id .ad_class
+!! 元素隐藏暂不支持全局规则和排除规则
+!! 字符替换扩展
+! 文本替换选择器标识符,后面跟需要替换的文本数据,格式:$s@模式字符串@替换后的文本@
+! 支持通配符*和?
+! -------------------------------------------------------------------------------------------
diff --git a/luci-app-docker/Makefile b/luci-app-docker/Makefile
new file mode 100644
index 000000000..d468ad0dc
--- /dev/null
+++ b/luci-app-docker/Makefile
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Luci for Docker-CE
+LUCI_DEPENDS:=+dockerd +docker +e2fsprogs +fdisk
+LUCI_PKGARCH:=all
+PKG_VERSION:=1
+PKG_RELEASE:=12
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-docker/luasrc/controller/docker.lua b/luci-app-docker/luasrc/controller/docker.lua
new file mode 100644
index 000000000..eb5dadbc2
--- /dev/null
+++ b/luci-app-docker/luasrc/controller/docker.lua
@@ -0,0 +1,17 @@
+module("luci.controller.docker", package.seeall)
+
+function index()
+ if not nixio.fs.access("/etc/config/dockerd") then
+ return
+ end
+
+ entry({"admin", "services", "docker"}, cbi("docker"), _("Docker CE Container"), 199).dependent = true
+ entry({"admin", "services", "docker", "status"}, call("act_status")).leaf = true
+end
+
+function act_status()
+ local e = {}
+ e.running = luci.sys.call("pgrep /usr/bin/dockerd >/dev/null") == 0
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(e)
+end
diff --git a/luci-app-docker/luasrc/model/cbi/docker.lua b/luci-app-docker/luasrc/model/cbi/docker.lua
new file mode 100644
index 000000000..037f3ee87
--- /dev/null
+++ b/luci-app-docker/luasrc/model/cbi/docker.lua
@@ -0,0 +1,23 @@
+local running = (luci.sys.call("pidof portainer >/dev/null") == 0)
+local button = ""
+
+if running then
+ button = "
"
+end
+
+m = Map("dockerd", "Docker CE", translate("Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers.") .. button)
+
+
+m:section(SimpleSection).template = "docker/docker_status"
+
+s = m:section(TypedSection, "docker")
+s.anonymous = true
+
+wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Docker"), translate("Enable WAN access docker mapped ports"))
+wan_mode.default = 0
+wan_mode.rmempty = false
+
+o = s:option(DummyValue,"readme",translate(" "))
+o.description = translate(""..translate("Download DockerReadme.pdf").."")
+
+return m
diff --git a/luci-app-docker/luasrc/view/docker/docker_status.htm b/luci-app-docker/luasrc/view/docker/docker_status.htm
new file mode 100644
index 000000000..f3cf6b761
--- /dev/null
+++ b/luci-app-docker/luasrc/view/docker/docker_status.htm
@@ -0,0 +1,22 @@
+
+
+
diff --git a/luci-app-docker/po/zh-cn/docker.po b/luci-app-docker/po/zh-cn/docker.po
new file mode 100644
index 000000000..0e378bc29
--- /dev/null
+++ b/luci-app-docker/po/zh-cn/docker.po
@@ -0,0 +1,39 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Luci ARP Bind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-06-23 20:16+0800\n"
+"PO-Revision-Date: 2015-06-23 20:17+0800\n"
+"Last-Translator: coolsnowwolf \n"
+"Language-Team: PandoraBox Team\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.8.1\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+msgid "Docker CE Container"
+msgstr "Docker CE 容器"
+
+msgid "Open Portainer Docker Admin"
+msgstr "打开 Portainer Docker 管理页面"
+
+msgid "Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers."
+msgstr "Docker是一组平台即服务(platform-as-a-service,PaaS)产品,它使用操作系统级容器虚拟化来交付软件包。"
+
+msgid "Enable WAN access Docker"
+msgstr "允许 WAN 访问 Docker"
+
+msgid "Enable WAN access docker mapped ports"
+msgstr "允许 WAN 访问 Docker 映射后的端口(易受攻击!)。
推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
+
+msgid "Docker Readme First"
+msgstr "Docker 初始化无脑配置教程"
+
+msgid "Download DockerReadme.pdf"
+msgstr "下载 Docker 初始化无脑配置教程"
+
+msgid "Please download DockerReadme.pdf to read when first-running"
+msgstr "初次在OpenWrt中运行Docker必读(只需执行一次流程)"
diff --git a/luci-app-docker/root/etc/config/dockerd b/luci-app-docker/root/etc/config/dockerd
new file mode 100644
index 000000000..7f1e0880b
--- /dev/null
+++ b/luci-app-docker/root/etc/config/dockerd
@@ -0,0 +1,3 @@
+
+config docker
+ option wan_mode '0'
diff --git a/luci-app-docker/root/etc/docker-init b/luci-app-docker/root/etc/docker-init
new file mode 100755
index 000000000..9bade865f
--- /dev/null
+++ b/luci-app-docker/root/etc/docker-init
@@ -0,0 +1,26 @@
+#!/bin/sh
+dtype=`fdisk -l /dev/sda | grep 'Disklabel type' | awk '{print $3}'`
+partid="0"
+
+if [ "$dtype" = "gpt" ]
+then
+ partid=`echo "n
+
+
+
+w
+" | fdisk /dev/sda | grep 'Created a new partition' | awk '{print $5}'`
+
+elif [ "$dtype" = "dos" ]
+then
+ partid=`echo "n
+p
+
+
+
+w
+" | fdisk /dev/sda | grep 'Created a new partition' | awk '{print $5}'`
+fi
+
+echo "y" | mkfs.ext4 /dev/sda$partid
+
diff --git a/luci-app-docker/root/etc/docker-web b/luci-app-docker/root/etc/docker-web
new file mode 100755
index 000000000..361589b39
--- /dev/null
+++ b/luci-app-docker/root/etc/docker-web
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+docker run -d --restart=always --name="portainer" -p 9999:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
\ No newline at end of file
diff --git a/luci-app-docker/root/etc/docker/daemon.json b/luci-app-docker/root/etc/docker/daemon.json
new file mode 100644
index 000000000..b8a99a4ce
--- /dev/null
+++ b/luci-app-docker/root/etc/docker/daemon.json
@@ -0,0 +1,4 @@
+{
+"data-root": "/opt/",
+"log-level": "warn"
+}
diff --git a/luci-app-docker/root/etc/init.d/dockerd b/luci-app-docker/root/etc/init.d/dockerd
new file mode 100755
index 000000000..5d6f36bf2
--- /dev/null
+++ b/luci-app-docker/root/etc/init.d/dockerd
@@ -0,0 +1,22 @@
+#!/bin/sh /etc/rc.common
+
+USE_PROCD=1
+START=25
+
+start_service() {
+ local nofile=$(cat /proc/sys/fs/nr_open)
+ local wanmode=$(uci get dockerd.@docker[0].wan_mode)
+
+ if [ $wanmode = "1" ] ;then
+ dockerwan=" "
+ else
+ dockerwan="--iptables=false"
+ fi
+
+ procd_open_instance
+ procd_set_param stderr 1
+ procd_set_param command /usr/bin/dockerd $dockerwan
+ procd_set_param limits nofile="${nofile} ${nofile}"
+ procd_close_instance
+
+}
diff --git a/luci-app-docker/root/etc/uci-defaults/luci-docker b/luci-app-docker/root/etc/uci-defaults/luci-docker
new file mode 100755
index 000000000..e03f47783
--- /dev/null
+++ b/luci-app-docker/root/etc/uci-defaults/luci-docker
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+ delete ucitrack.@dockerd[-1]
+ add ucitrack dockerd
+ set ucitrack.@dockerd[-1].init=dockerd
+ commit ucitrack
+EOF
+
+rm -f /tmp/luci-indexcache
+exit 0
diff --git a/luci-app-docker/root/www/DockerReadme.pdf b/luci-app-docker/root/www/DockerReadme.pdf
new file mode 100644
index 000000000..8a9a94b83
Binary files /dev/null and b/luci-app-docker/root/www/DockerReadme.pdf differ
diff --git a/luci-app-dockerman/Makefile b/luci-app-dockerman/Makefile
new file mode 100644
index 000000000..7c553c86c
--- /dev/null
+++ b/luci-app-dockerman/Makefile
@@ -0,0 +1,30 @@
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Docker Manager interface for LuCI
+LUCI_DEPENDS:=+luci-lib-docker +luci-lib-ip +docker +dockerd +ttyd
+PKG_NAME:=luci-app-dockerman
+PKG_VERSION:=v0.5.25
+PKG_RELEASE:=1
+PKG_MAINTAINER:=lisaac
+PKG_LICENSE:=AGPL-3.0
+
+define Package/luci-app-dockerman/postinst
+#!/bin/sh
+
+/init.sh env
+touch /etc/config/dockerd
+uci set dockerd.dockerman=dockerman
+uci set dockerd.dockerman.socket_path=`uci get dockerd.dockerman.socket_path 2&> /dev/null || echo '/var/run/docker.sock'`
+uci set dockerd.dockerman.status_path=`uci get dockerd.dockerman.status_path 2&> /dev/null || echo '/tmp/.docker_action_status'`
+uci set dockerd.dockerman.debug=`uci get dockerd.dockerman.debug 2&> /dev/null || echo 'false'`
+uci set dockerd.dockerman.debug_path=`uci get dockerd.dockerman.debug_path 2&> /dev/null || echo '/tmp/.docker_debug'`
+uci set dockerd.dockerman.remote_port=`uci get dockerd.dockerman.remote_port 2&> /dev/null || echo '2375'`
+uci set dockerd.dockerman.remote_endpoint=`uci get dockerd.dockerman.remote_endpoint 2&> /dev/null || echo '0'`
+uci del_list dockerd.dockerman.ac_allowed_interface='br-lan'
+uci add_list dockerd.dockerman.ac_allowed_interface='br-lan'
+uci commit dockerd
+endef
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg
new file mode 100644
index 000000000..4165f90bd
--- /dev/null
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-icon.png b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-icon.png
new file mode 100644
index 000000000..f156dc1c7
Binary files /dev/null and b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-icon.png differ
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-manager.css b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-manager.css
new file mode 100644
index 000000000..911693b62
--- /dev/null
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-manager.css
@@ -0,0 +1,91 @@
+.fb-container {
+ margin-top: 1rem;
+}
+.fb-container .cbi-button {
+ height: 1.8rem;
+}
+.fb-container .cbi-input-text {
+ margin-bottom: 1rem;
+ width: 100%;
+}
+.fb-container .panel-title {
+ padding-bottom: 0;
+ width: 50%;
+ border-bottom: none;
+}
+.fb-container .panel-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #eee;
+}
+.fb-container .upload-container {
+ display: none;
+ margin: 1rem 0;
+}
+.fb-container .upload-file {
+ margin-right: 2rem;
+}
+.fb-container .cbi-value-field {
+ text-align: left;
+}
+.fb-container .parent-icon strong {
+ margin-left: 1rem;
+}
+.fb-container td[class$="-icon"] {
+ cursor: pointer;
+}
+.fb-container .file-icon, .fb-container .folder-icon, .fb-container .link-icon {
+ position: relative;
+}
+.fb-container .file-icon:before, .fb-container .folder-icon:before, .fb-container .link-icon:before {
+ display: inline-block;
+ width: 1.5rem;
+ height: 1.5rem;
+ content: '';
+ background-size: contain;
+ margin: 0 0.5rem 0 1rem;
+ vertical-align: middle;
+}
+.fb-container .file-icon:before {
+ background-image: url(file-icon.png);
+}
+.fb-container .folder-icon:before {
+ background-image: url(folder-icon.png);
+}
+.fb-container .link-icon:before {
+ background-image: url(link-icon.png);
+}
+@media screen and (max-width: 480px) {
+ .fb-container .upload-file {
+ width: 14.6rem;
+ }
+ .fb-container .cbi-value-owner,
+ .fb-container .cbi-value-perm {
+ display: none;
+ }
+}
+
+.cbi-section-table {
+ width: 100%;
+}
+
+.cbi-section-table-cell {
+ text-align: right;
+}
+
+.cbi-button-install {
+border-color: #c44;
+ color: #c44;
+ margin-left: 3px;
+}
+
+.cbi-value-field {
+ padding: 10px 0;
+}
+
+.parent-icon {
+ height: 1.8rem;
+ padding: 10px 0;
+}
\ No newline at end of file
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/folder-icon.png b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/folder-icon.png
new file mode 100644
index 000000000..1370df3ad
Binary files /dev/null and b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/folder-icon.png differ
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg
new file mode 100644
index 000000000..90ca5a1c7
--- /dev/null
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/link-icon.png b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/link-icon.png
new file mode 100644
index 000000000..03cc82cdf
Binary files /dev/null and b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/link-icon.png differ
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg
new file mode 100644
index 000000000..3eb12a393
--- /dev/null
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/tar.min.js b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/tar.min.js
new file mode 100644
index 000000000..d9c06667f
--- /dev/null
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/tar.min.js
@@ -0,0 +1,185 @@
+// https://github.com/thiscouldbebetter/TarFileExplorer
+class TarFileTypeFlag
+{constructor(value,name)
+{this.value=value;this.id="_"+this.value;this.name=name;}
+static _instances;static Instances()
+{if(TarFileTypeFlag._instances==null)
+{TarFileTypeFlag._instances=new TarFileTypeFlag_Instances();}
+return TarFileTypeFlag._instances;}}
+class TarFileTypeFlag_Instances
+{constructor()
+{this.Normal=new TarFileTypeFlag("0","Normal");this.HardLink=new TarFileTypeFlag("1","Hard Link");this.SymbolicLink=new TarFileTypeFlag("2","Symbolic Link");this.CharacterSpecial=new TarFileTypeFlag("3","Character Special");this.BlockSpecial=new TarFileTypeFlag("4","Block Special");this.Directory=new TarFileTypeFlag("5","Directory");this.FIFO=new TarFileTypeFlag("6","FIFO");this.ContiguousFile=new TarFileTypeFlag("7","Contiguous File");this.LongFilePath=new TarFileTypeFlag("L","././@LongLink");this._All=[this.Normal,this.HardLink,this.SymbolicLink,this.CharacterSpecial,this.BlockSpecial,this.Directory,this.FIFO,this.ContiguousFile,this.LongFilePath,];for(var i=0;ia+=String.fromCharCode(b),"");entryNext.header.fileName=entryNext.header.fileName.replace(/\0/g,"");entries.splice(i,1);i--;}}}
+downloadAs(fileNameToSaveAs)
+{return FileHelper.saveBytesAsFile
+(this.toBytes(),fileNameToSaveAs)}
+entriesForDirectories()
+{return this.entries.filter(x=>x.header.typeFlag.name==TarFileTypeFlag.Instances().Directory);}
+toBytes()
+{this.toBytes_PrependLongPathEntriesAsNeeded();var fileAsBytes=[];var entriesAsByteArrays=this.entries.map(x=>x.toBytes());this.consolidateLongPathEntries();for(var i=0;imaxLength)
+{var entryFileNameAsBytes=entryFileName.split("").map(x=>x.charCodeAt(0));var entryContainingLongPathToPrepend=TarFileEntry.fileNew
+(typeFlagLongPath.name,entryFileNameAsBytes);entryContainingLongPathToPrepend.header.typeFlag=typeFlagLongPath;entryContainingLongPathToPrepend.header.timeModifiedInUnixFormat=entryHeader.timeModifiedInUnixFormat;entryContainingLongPathToPrepend.header.checksumCalculate();entryHeader.fileName=entryFileName.substr(0,maxLength)+String.fromCharCode(0);entries.splice(i,0,entryContainingLongPathToPrepend);i++;}}}
+toString()
+{var newline="\n";var returnValue="[TarFile]"+newline;for(var i=0;i{var fileLoadedAsBinaryString=fileLoadedEvent.target.result;var fileLoadedAsBytes=ByteHelper.stringUTF8ToBytes(fileLoadedAsBinaryString);callback(fileToLoad.name,fileLoadedAsBytes);}
+fileReader.readAsBinaryString(fileToLoad);}
+static loadFileAsText(fileToLoad,callback)
+{var fileReader=new FileReader();fileReader.onload=(fileLoadedEvent)=>{var textFromFileLoaded=fileLoadedEvent.target.result;callback(fileToLoad.name,textFromFileLoaded);};fileReader.readAsText(fileToLoad);}
+static saveBytesAsFile(bytesToWrite,fileNameToSaveAs)
+{var bytesToWriteAsArrayBuffer=new ArrayBuffer(bytesToWrite.length);var bytesToWriteAsUIntArray=new Uint8Array(bytesToWriteAsArrayBuffer);for(var i=0;i
+
+
+
diff --git a/luci-app-dockerman/luasrc/controller/dockerman.lua b/luci-app-dockerman/luasrc/controller/dockerman.lua
new file mode 100644
index 000000000..ebc89c2df
--- /dev/null
+++ b/luci-app-dockerman/luasrc/controller/dockerman.lua
@@ -0,0 +1,620 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+-- local uci = (require "luci.model.uci").cursor()
+
+module("luci.controller.dockerman",package.seeall)
+
+function index()
+ entry({"admin", "docker"},
+ alias("admin", "docker", "config"),
+ _("Docker"),
+ 40).acl_depends = { "luci-app-dockerman" }
+
+ entry({"admin", "docker", "config"},cbi("dockerman/configuration"),_("Configuration"), 8).leaf=true
+
+ -- local uci = (require "luci.model.uci").cursor()
+ -- if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ -- local host = uci:get("dockerd", "dockerman", "remote_host")
+ -- local port = uci:get("dockerd", "dockerman", "remote_port")
+ -- if not host or not port then
+ -- return
+ -- end
+ -- else
+ -- local socket = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock"
+ -- if socket and not nixio.fs.access(socket) then
+ -- return
+ -- end
+ -- end
+
+ -- if (require "luci.model.docker").new():_ping().code ~= 200 then
+ -- return
+ -- end
+
+ entry({"admin", "docker", "overview"}, form("dockerman/overview"),_("Overview"), 2).leaf=true
+ entry({"admin", "docker", "containers"}, form("dockerman/containers"), _("Containers"), 3).leaf=true
+ entry({"admin", "docker", "images"}, form("dockerman/images"), _("Images"), 4).leaf=true
+ entry({"admin", "docker", "networks"}, form("dockerman/networks"), _("Networks"), 5).leaf=true
+ entry({"admin", "docker", "volumes"}, form("dockerman/volumes"), _("Volumes"), 6).leaf=true
+ entry({"admin", "docker", "events"}, call("action_events"), _("Events"), 7)
+
+ entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true
+ entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true
+ entry({"admin", "docker", "container"}, form("dockerman/container")).leaf=true
+
+ entry({"admin", "docker", "call"}, call("action_call_docker")).leaf=true
+
+ entry({"admin", "docker", "container_stats"}, call("action_get_container_stats")).leaf=true
+ entry({"admin", "docker", "containers_stats"}, call("action_get_containers_stats")).leaf=true
+ entry({"admin", "docker", "get_system_df"}, call("action_get_system_df")).leaf=true
+ entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true
+ entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true
+ entry({"admin", "docker", "container_list_file"}, call("list_file")).leaf=true
+ entry({"admin", "docker", "container_remove_file"}, call("remove_file")).leaf=true
+ entry({"admin", "docker", "container_rename_file"}, call("rename_file")).leaf=true
+ entry({"admin", "docker", "container_export"}, call("export_container")).leaf=true
+ entry({"admin", "docker", "images_save"}, call("save_images")).leaf=true
+ entry({"admin", "docker", "images_load"}, call("load_images")).leaf=true
+ entry({"admin", "docker", "images_import"}, call("import_images")).leaf=true
+ entry({"admin", "docker", "images_get_tags"}, call("get_image_tags")).leaf=true
+ entry({"admin", "docker", "images_tag"}, call("tag_image")).leaf=true
+ entry({"admin", "docker", "images_untag"}, call("untag_image")).leaf=true
+ entry({"admin", "docker", "confirm"}, call("action_confirm")).leaf=true
+end
+
+function action_call_docker()
+
+end
+
+function action_get_system_df()
+ local res = docker.new():df()
+ luci.http.status(res.code, res.message)
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(res.body)
+end
+
+function scandir(id, directory)
+ local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
+ if not cmd_docker or cmd_docker:match("^%s+$") then
+ return
+ end
+ local i, t, popen = 0, {}, io.popen
+ local uci = (require "luci.model.uci").cursor()
+ local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint")
+ local socket_path = not remote and uci:get("dockerd", "dockerman", "socket_path") or nil
+ local host = remote and uci:get("dockerd", "dockerman", "remote_host") or nil
+ local port = remote and uci:get("dockerd", "dockerman", "remote_port") or nil
+ if remote and host and port then
+ hosts = "tcp://" .. host .. ':'.. port
+ elseif socket_path then
+ hosts = "unix://" .. socket_path
+ else
+ return
+ end
+ local pfile = popen(cmd_docker .. ' -H "'.. hosts ..'" exec ' ..id .." ls -lh \""..directory.."\" | egrep -v '^total'")
+ for fileinfo in pfile:lines() do
+ i = i + 1
+ t[i] = fileinfo
+ end
+ pfile:close()
+ return t
+end
+
+function list_response(id, path, success)
+ luci.http.prepare_content("application/json")
+ local result
+ if success then
+ local rv = scandir(id, path)
+ result = {
+ ec = 0,
+ data = rv
+ }
+ else
+ result = {
+ ec = 1
+ }
+ end
+ luci.http.write_json(result)
+end
+
+function list_file(id)
+ local path = luci.http.formvalue("path")
+ list_response(id, path, true)
+end
+
+function rename_file(id)
+ local filepath = luci.http.formvalue("filepath")
+ local newpath = luci.http.formvalue("newpath")
+ local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
+ if not cmd_docker or cmd_docker:match("^%s+$") then
+ return
+ end
+ local uci = (require "luci.model.uci").cursor()
+ local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint")
+ local socket_path = not remote and uci:get("dockerd", "dockerman", "socket_path") or nil
+ local host = remote and uci:get("dockerd", "dockerman", "remote_host") or nil
+ local port = remote and uci:get("dockerd", "dockerman", "remote_port") or nil
+ if remote and host and port then
+ hosts = "tcp://" .. host .. ':'.. port
+ elseif socket_path then
+ hosts = "unix://" .. socket_path
+ else
+ return
+ end
+ local success = os.execute(cmd_docker .. ' -H "'.. hosts ..'" exec '.. id ..' mv "'..filepath..'" "'..newpath..'"')
+ list_response(nixio.fs.dirname(filepath), success)
+end
+
+function remove_file(id)
+ local path = luci.http.formvalue("path")
+ local isdir = luci.http.formvalue("isdir")
+ local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
+ if not cmd_docker or cmd_docker:match("^%s+$") then
+ return
+ end
+ local uci = (require "luci.model.uci").cursor()
+ local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint")
+ local socket_path = not remote and uci:get("dockerd", "dockerman", "socket_path") or nil
+ local host = remote and uci:get("dockerd", "dockerman", "remote_host") or nil
+ local port = remote and uci:get("dockerd", "dockerman", "remote_port") or nil
+ if remote and host and port then
+ hosts = "tcp://" .. host .. ':'.. port
+ elseif socket_path then
+ hosts = "unix://" .. socket_path
+ else
+ return
+ end
+ path = path:gsub("<>", "/")
+ path = path:gsub(" ", "\ ")
+ local success
+ if isdir then
+ success = os.execute(cmd_docker .. ' -H "'.. hosts ..'" exec '.. id ..' rm -r "'..path..'"')
+ else
+ success = os.remove(path)
+ end
+ list_response(nixio.fs.dirname(path), success)
+end
+
+function action_events()
+ local logs = ""
+ local query ={}
+
+ local dk = docker.new()
+ query["until"] = os.time()
+ local events = dk:events({query = query})
+
+ if events.code == 200 then
+ for _, v in ipairs(events.body) do
+ local date = "unknown"
+ if v and v.time then
+ date = os.date("%Y-%m-%d %H:%M:%S", v.time)
+ end
+
+ local name = v.Actor.Attributes.name or "unknown"
+ local action = v.Action or "unknown"
+
+ if v and v.Type == "container" then
+ local id = v.Actor.ID or "unknown"
+ logs = logs .. string.format("[%s] %s %s Container ID: %s Container Name: %s\n", date, v.Type, action, id, name)
+ elseif v.Type == "network" then
+ local container = v.Actor.Attributes.container or "unknown"
+ local network = v.Actor.Attributes.type or "unknown"
+ logs = logs .. string.format("[%s] %s %s Container ID: %s Network Name: %s Network type: %s\n", date, v.Type, action, container, name, network)
+ elseif v.Type == "image" then
+ local id = v.Actor.ID or "unknown"
+ logs = logs .. string.format("[%s] %s %s Image: %s Image name: %s\n", date, v.Type, action, id, name)
+ end
+ end
+ end
+
+ luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}})
+end
+
+local calculate_cpu_percent = function(d)
+ if type(d) ~= "table" then
+ return
+ end
+
+ local cpu_count = tonumber(d["cpu_stats"]["online_cpus"])
+ local cpu_percent = 0.0
+ local cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"])
+ local system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) -- tonumber(d["precpu_stats"]["system_cpu_usage"])
+ if system_delta > 0.0 then
+ cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count)
+ end
+
+ return cpu_percent
+end
+
+local get_memory = function(d)
+ if type(d) ~= "table" then
+ return
+ end
+
+ -- local limit = string.format("%.2f", tonumber(d["memory_stats"]["limit"]) / 1024 / 1024)
+ -- local usage = string.format("%.2f", (tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])) / 1024 / 1024)
+ -- return usage .. "MB / " .. limit.. "MB"
+
+ local limit =tonumber(d["memory_stats"]["limit"])
+ local usage = tonumber(d["memory_stats"]["usage"])
+ -- - tonumber(d["memory_stats"]["stats"]["total_cache"])
+
+ return usage, limit
+end
+
+local get_rx_tx = function(d)
+ if type(d) ~="table" then
+ return
+ end
+
+ local data = {}
+ if type(d["networks"]) == "table" then
+ for e, v in pairs(d["networks"]) do
+ data[e] = {
+ bw_tx = tonumber(v.tx_bytes),
+ bw_rx = tonumber(v.rx_bytes)
+ }
+ end
+ end
+
+ return data
+end
+
+local function get_stat(container_id)
+ if container_id then
+ local dk = docker.new()
+ local response = dk.containers:inspect({id = container_id})
+ if response.code == 200 and response.body.State.Running then
+ response = dk.containers:stats({id = container_id, query = {stream = false, ["one-shot"] = true}})
+ if response.code == 200 then
+ local container_stats = response.body
+ local cpu_percent = calculate_cpu_percent(container_stats)
+ local mem_useage, mem_limit = get_memory(container_stats)
+ local bw_rxtx = get_rx_tx(container_stats)
+ return response.code, response.body.message, {
+ cpu_percent = cpu_percent,
+ memory = {
+ mem_useage = mem_useage,
+ mem_limit = mem_limit
+ },
+ bw_rxtx = bw_rxtx
+ }
+ else
+ return response.code, response.body.message
+ end
+ else
+ if response.code == 200 then
+ return 500, "container "..container_id.." not running"
+ else
+ return response.code, response.body.message
+ end
+ end
+ else
+ return 404, "No container name or id"
+ end
+end
+function action_get_container_stats(container_id)
+ local code, msg, res = get_stat(container_id)
+ luci.http.status(code, msg)
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(res)
+end
+
+function action_get_containers_stats()
+ local res = luci.http.formvalue(containers) or ""
+ local stats = {}
+ res = luci.jsonc.parse(res.containers)
+ if res and type(res) == "table" then
+ for i, v in ipairs(res) do
+ _,_,stats[v] = get_stat(v)
+ end
+ end
+ luci.http.status(200, "OK")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(stats)
+end
+
+function action_confirm()
+ local data = docker:read_status()
+ if data then
+ data = data:gsub("\n","
"):gsub(" "," ")
+ code = 202
+ msg = data
+ else
+ code = 200
+ msg = "finish"
+ data = "finish"
+ end
+
+ luci.http.status(code, msg)
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({info = data})
+end
+
+function export_container(id)
+ local dk = docker.new()
+ local first
+
+ local cb = function(res, chunk)
+ if res.code == 200 then
+ if not first then
+ first = true
+ luci.http.header('Content-Disposition', 'inline; filename="'.. id ..'.tar"')
+ luci.http.header('Content-Type', 'application\/x-tar')
+ end
+ luci.ltn12.pump.all(chunk, luci.http.write)
+ else
+ if not first then
+ first = true
+ luci.http.prepare_content("text/plain")
+ end
+ luci.ltn12.pump.all(chunk, luci.http.write)
+ end
+ end
+
+ local res = dk.containers:export({id = id}, cb)
+end
+
+function download_archive()
+ local id = luci.http.formvalue("id")
+ local path = luci.http.formvalue("path")
+ local filename = luci.http.formvalue("filename") or "archive"
+ local dk = docker.new()
+ local first
+
+ local cb = function(res, chunk)
+ if res and res.code and res.code == 200 then
+ if not first then
+ first = true
+ luci.http.header('Content-Disposition', 'inline; filename="'.. filename .. '.tar"')
+ luci.http.header('Content-Type', 'application\/x-tar')
+ end
+ luci.ltn12.pump.all(chunk, luci.http.write)
+ else
+ if not first then
+ first = true
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("text/plain")
+ end
+ luci.ltn12.pump.all(chunk, luci.http.write)
+ end
+ end
+
+ local res = dk.containers:get_archive({
+ id = id,
+ query = {
+ path = luci.http.urlencode(path)
+ }
+ }, cb)
+end
+
+function upload_archive(container_id)
+ local path = luci.http.formvalue("upload-path")
+ local dk = docker.new()
+ local ltn12 = require "luci.ltn12"
+
+ local rec_send = function(sinkout)
+ luci.http.setfilehandler(function (meta, chunk, eof)
+ if chunk then
+ ltn12.pump.step(ltn12.source.string(chunk), sinkout)
+ end
+ end)
+ end
+
+ local res = dk.containers:put_archive({
+ id = container_id,
+ query = {
+ path = luci.http.urlencode(path)
+ },
+ body = rec_send
+ })
+
+ local msg = res and res.body and res.body.message or nil
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = msg or "unknow"})
+end
+
+-- function save_images()
+-- local names = luci.http.formvalue("names")
+-- local dk = docker.new()
+-- local first
+
+-- local cb = function(res, chunk)
+-- if res.code == 200 then
+-- if not first then
+-- first = true
+-- luci.http.status(res.code, res.message)
+-- luci.http.header('Content-Disposition', 'inline; filename="'.. "images" ..'.tar"')
+-- luci.http.header('Content-Type', 'application\/x-tar')
+-- end
+-- luci.ltn12.pump.all(chunk, luci.http.write)
+-- else
+-- if not first then
+-- first = true
+-- luci.http.prepare_content("text/plain")
+-- end
+-- luci.ltn12.pump.all(chunk, luci.http.write)
+-- end
+-- end
+
+-- docker:write_status("Images: saving" .. " " .. names .. "...")
+-- local res = dk.images:get({
+-- query = {
+-- names = luci.http.urlencode(names)
+-- }
+-- }, cb)
+-- docker:clear_status()
+
+-- local msg = res and res.body and res.body.message or nil
+-- luci.http.status(res.code, msg)
+-- luci.http.prepare_content("application/json")
+-- luci.http.write_json({message = msg})
+-- end
+
+function load_images()
+ local archive = luci.http.formvalue("upload-archive")
+ local dk = docker.new()
+ local ltn12 = require "luci.ltn12"
+
+ local rec_send = function(sinkout)
+ luci.http.setfilehandler(function (meta, chunk, eof)
+ if chunk then
+ ltn12.pump.step(ltn12.source.string(chunk), sinkout)
+ end
+ end)
+ end
+
+ docker:write_status("Images: loading...")
+ local res = dk.images:load({body = rec_send})
+ local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error ) or nil
+ if res and res.code == 200 and msg and msg:match("Loaded image ID") then
+ docker:clear_status()
+ else
+ docker:append_status("code:" .. (res and res.code or "500") .." ".. (msg or "unknow"))
+ end
+
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = msg or "unknow"})
+end
+
+function import_images()
+ local src = luci.http.formvalue("src")
+ local itag = luci.http.formvalue("tag")
+ local dk = docker.new()
+ local ltn12 = require "luci.ltn12"
+
+ local rec_send = function(sinkout)
+ luci.http.setfilehandler(function (meta, chunk, eof)
+ if chunk then
+ ltn12.pump.step(ltn12.source.string(chunk), sinkout)
+ end
+ end)
+ end
+
+ docker:write_status("Images: importing".. " ".. itag .."...\n")
+ local repo = itag and itag:match("^([^:]+)")
+ local tag = itag and itag:match("^[^:]-:([^:]+)")
+ local res = dk.images:create({
+ query = {
+ fromSrc = luci.http.urlencode(src or "-"),
+ repo = repo or nil,
+ tag = tag or nil
+ },
+ body = not src and rec_send or nil
+ }, docker.import_image_show_status_cb)
+
+ local msg = res and res.body and ( res.body.message )or nil
+ if not msg and #res.body == 0 then
+ msg = res.body.status or res.body.error
+ elseif not msg and #res.body >= 1 then
+ msg = res.body[#res.body].status or res.body[#res.body].error
+ end
+
+ if res.code == 200 and msg and msg:match("sha256:") then
+ docker:clear_status()
+ else
+ docker:append_status("code:" .. (res and res.code or "500") .." ".. (msg or "unknow"))
+ end
+
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = msg or "unknow"})
+end
+
+function get_image_tags(image_id)
+ if not image_id then
+ luci.http.status(400, "no image id")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = "no image id"})
+ return
+ end
+
+ local dk = docker.new()
+ local res = dk.images:inspect({
+ id = image_id
+ })
+ local msg = res and res.body and res.body.message or nil
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("application/json")
+
+ if res.code == 200 then
+ local tags = res.body.RepoTags
+ luci.http.write_json({tags = tags})
+ else
+ local msg = res and res.body and res.body.message or nil
+ luci.http.write_json({message = msg or "unknow"})
+ end
+end
+
+function tag_image(image_id)
+ local src = luci.http.formvalue("tag")
+ local image_id = image_id or luci.http.formvalue("id")
+
+ if type(src) ~= "string" or not image_id then
+ luci.http.status(400, "no image id or tag")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = "no image id or tag"})
+ return
+ end
+
+ local repo = src:match("^([^:]+)")
+ local tag = src:match("^[^:]-:([^:]+)")
+ local dk = docker.new()
+ local res = dk.images:tag({
+ id = image_id,
+ query={
+ repo=repo,
+ tag=tag
+ }
+ })
+ local msg = res and res.body and res.body.message or nil
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("application/json")
+
+ if res.code == 201 then
+ local tags = res.body.RepoTags
+ luci.http.write_json({tags = tags})
+ else
+ local msg = res and res.body and res.body.message or nil
+ luci.http.write_json({message = msg or "unknow"})
+ end
+end
+
+function untag_image(tag)
+ local tag = tag or luci.http.formvalue("tag")
+
+ if not tag then
+ luci.http.status(400, "no tag name")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = "no tag name"})
+ return
+ end
+
+ local dk = docker.new()
+ local res = dk.images:inspect({name = tag})
+
+ if res.code == 200 then
+ local tags = res.body.RepoTags
+ if #tags > 1 then
+ local r = dk.images:remove({name = tag})
+ local msg = r and r.body and r.body.message or nil
+ luci.http.status(r.code, msg)
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = msg})
+ else
+ luci.http.status(500, "Cannot remove the last tag")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = "Cannot remove the last tag"})
+ end
+ else
+ local msg = res and res.body and res.body.message or nil
+ luci.http.status(res and res.code or 500, msg or "unknow")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({message = msg or "unknow"})
+ end
+end
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua
new file mode 100644
index 000000000..f62650fe5
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua
@@ -0,0 +1,152 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2021 Florian Eckert
+Copyright 2021 lisaac
+]]--
+
+local uci = (require "luci.model.uci").cursor()
+
+local m, s, o
+
+m = Map("dockerd",
+ translate("Docker - Configuration"),
+ translate("DockerMan is a simple docker manager client for LuCI"))
+
+if nixio.fs.access("/usr/bin/dockerd") and not m.uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ s = m:section(NamedSection, "globals", "section", translate("Docker Daemon settings"))
+
+ o = s:option(Flag, "auto_start", translate("Auto start"))
+ o.rmempty = false
+ o.write = function(self, section, value)
+ if value == "1" then
+ luci.util.exec("/etc/init.d/dockerd enable")
+ else
+ luci.util.exec("/etc/init.d/dockerd disable")
+ end
+ m.uci:set("dockerd", "globals", "auto_start", value)
+ end
+
+ o = s:option(Value, "data_root",
+ translate("Docker Root Dir"))
+ o.placeholder = "/opt/docker/"
+ o:depends("remote_endpoint", 0)
+
+ o = s:option(Value, "bip",
+ translate("Default bridge"),
+ translate("Configure the default bridge network"))
+ o.placeholder = "172.17.0.1/16"
+ o.datatype = "ipaddr"
+ o:depends("remote_endpoint", 0)
+
+ o = s:option(DynamicList, "registry_mirrors",
+ translate("Registry Mirrors"),
+ translate("It replaces the daemon registry mirrors with a new set of registry mirrors"))
+ o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
+ o:depends("remote_endpoint", 0)
+ o.forcewrite = true
+
+ o = s:option(ListValue, "log_level",
+ translate("Log Level"),
+ translate('Set the logging level'))
+ o:value("debug", translate("Debug"))
+ o:value("", translate("Info")) -- This is the default debug level from the deamon is optin is not set
+ o:value("warn", translate("Warning"))
+ o:value("error", translate("Error"))
+ o:value("fatal", translate("Fatal"))
+ o.rmempty = true
+ o:depends("remote_endpoint", 0)
+
+ o = s:option(DynamicList, "hosts",
+ translate("Client connection"),
+ translate('Specifies where the Docker daemon will listen for client connections (default: unix:///var/run/docker.sock)'))
+ o:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock")
+ o:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375")
+ o.rmempty = true
+ o:depends("remote_endpoint", 0)
+end
+
+s = m:section(NamedSection, "dockerman", "section", translate("DockerMan settings"))
+s:tab("ac", translate("Access Control"))
+s:tab("dockerman", translate("DockerMan"))
+
+o = s:taboption("dockerman", Flag, "remote_endpoint",
+ translate("Remote Endpoint"),
+ translate("Connect to remote docker endpoint"))
+o.rmempty = false
+o.validate = function(self, value, sid)
+ local res = luci.http.formvaluetable("cbid.dockerd")
+ if res["dockerman.remote_endpoint"] == "1" then
+ if res["dockerman.remote_port"] and res["dockerman.remote_port"] ~= "" and res["dockerman.remote_host"] and res["dockerman.remote_host"] ~= "" then
+ return 1
+ else
+ return nil, translate("Please input the PORT or HOST IP of remote docker instance!")
+ end
+ else
+ if not res["dockerman.socket_path"] then
+ return nil, translate("Please input the SOCKET PATH of docker daemon!")
+ end
+ end
+ return 0
+end
+
+o = s:taboption("dockerman", Value, "socket_path",
+ translate("Docker Socket Path"))
+o.default = "/var/run/docker.sock"
+o.placeholder = "/var/run/docker.sock"
+o:depends("remote_endpoint", 0)
+
+o = s:taboption("dockerman", Value, "remote_host",
+ translate("Remote Host"),
+ translate("Host or IP Address for the connection to a remote docker instance"))
+o.datatype = "host"
+o.placeholder = "10.1.1.2"
+o:depends("remote_endpoint", 1)
+
+o = s:taboption("dockerman", Value, "remote_port",
+ translate("Remote Port"))
+o.placeholder = "2375"
+o.datatype = "port"
+o:depends("remote_endpoint", 1)
+
+-- o = s:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
+-- o = s:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
+-- o.enabled="true"
+-- o.disabled="false"
+-- o = s:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
+
+if nixio.fs.access("/usr/bin/dockerd") and not m.uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ o = s:taboption("ac", DynamicList, "ac_allowed_interface", translate("Allowed access interfaces"), translate("Which interface(s) can access containers under the bridge network, fill-in Interface Name"))
+ local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
+ for i, v in ipairs(interfaces) do
+ o:value(v, v)
+ end
+ o = s:taboption("ac", DynamicList, "ac_allowed_ports", translate("Ports allowed to be accessed"), translate("Which Port(s) can be accessed, it's not restricted by the Allowed Access interfaces configuration. Use this configuration with caution!"))
+ o.placeholder = "8080/tcp"
+ local docker = require "luci.model.docker"
+ local containers, res, lost_state
+ local dk = docker.new()
+ if dk:_ping().code ~= 200 then
+ lost_state = true
+ else
+ lost_state = false
+ res = dk.containers:list()
+ if res and res.code and res.code < 300 then
+ containers = res.body
+ end
+ end
+
+ -- allowed_container.placeholder = "container name_or_id"
+ if containers then
+ for i, v in ipairs(containers) do
+ if v.State == "running" and v.Ports then
+ for _, port in ipairs(v.Ports) do
+ if port.PublicPort and port.IP and not string.find(port.IP,":") then
+ o:value(port.PublicPort.."/"..port.Type, v.Names[1]:sub(2) .. " | " .. port.PublicPort .. " | " .. port.Type)
+ end
+ end
+ end
+ end
+ end
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua
new file mode 100644
index 000000000..66cd2742d
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua
@@ -0,0 +1,802 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+require "luci.util"
+
+local docker = require "luci.model.docker"
+local dk = docker.new()
+
+container_id = arg[1]
+local action = arg[2] or "info"
+
+local m, s, o
+local images, networks, container_info, res
+
+if not container_id then
+ return
+end
+
+res = dk.containers:inspect({id = container_id})
+if res.code < 300 then
+ container_info = res.body
+else
+ return
+end
+
+local get_ports = function(d)
+ local data
+
+ if d.HostConfig and d.HostConfig.PortBindings then
+ for inter, out in pairs(d.HostConfig.PortBindings) do
+ data = (data and (data .. "
") or "") .. out[1]["HostPort"] .. ":" .. inter
+ end
+ end
+
+ return data
+end
+
+local get_env = function(d)
+ local data
+
+ if d.Config and d.Config.Env then
+ for _,v in ipairs(d.Config.Env) do
+ data = (data and (data .. "
") or "") .. v
+ end
+ end
+
+ return data
+end
+
+local get_command = function(d)
+ local data
+
+ if d.Config and d.Config.Cmd then
+ for _,v in ipairs(d.Config.Cmd) do
+ data = (data and (data .. " ") or "") .. v
+ end
+ end
+
+ return data
+end
+
+local get_mounts = function(d)
+ local data
+
+ if d.Mounts then
+ for _,v in ipairs(d.Mounts) do
+ local v_sorce_d, v_dest_d
+ local v_sorce = ""
+ local v_dest = ""
+ for v_sorce_d in v["Source"]:gmatch('[^/]+') do
+ if v_sorce_d and #v_sorce_d > 12 then
+ v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..."
+ else
+ v_sorce = v_sorce .."/".. v_sorce_d
+ end
+ end
+ for v_dest_d in v["Destination"]:gmatch('[^/]+') do
+ if v_dest_d and #v_dest_d > 12 then
+ v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..."
+ else
+ v_dest = v_dest .."/".. v_dest_d
+ end
+ end
+ data = (data and (data .. "
") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "")
+ end
+ end
+
+ return data
+end
+
+local get_device = function(d)
+ local data
+
+ if d.HostConfig and d.HostConfig.Devices then
+ for _,v in ipairs(d.HostConfig.Devices) do
+ data = (data and (data .. "
") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
+ end
+ end
+
+ return data
+end
+
+local get_links = function(d)
+ local data
+
+ if d.HostConfig and d.HostConfig.Links then
+ for _,v in ipairs(d.HostConfig.Links) do
+ data = (data and (data .. "
") or "") .. v
+ end
+ end
+
+ return data
+end
+
+local get_tmpfs = function(d)
+ local data
+
+ if d.HostConfig and d.HostConfig.Tmpfs then
+ for k, v in pairs(d.HostConfig.Tmpfs) do
+ data = (data and (data .. "
") or "") .. k .. (v~="" and ":" or "")..v
+ end
+ end
+
+ return data
+end
+
+local get_dns = function(d)
+ local data
+
+ if d.HostConfig and d.HostConfig.Dns then
+ for _, v in ipairs(d.HostConfig.Dns) do
+ data = (data and (data .. "
") or "") .. v
+ end
+ end
+
+ return data
+end
+
+local get_sysctl = function(d)
+ local data
+
+ if d.HostConfig and d.HostConfig.Sysctls then
+ for k, v in pairs(d.HostConfig.Sysctls) do
+ data = (data and (data .. "
") or "") .. k..":"..v
+ end
+ end
+
+ return data
+end
+
+local get_networks = function(d)
+ local data={}
+
+ if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
+ for k,v in pairs(d.NetworkSettings.Networks) do
+ data[k] = v.IPAddress or ""
+ end
+ end
+
+ return data
+end
+
+
+local start_stop_remove = function(m, cmd)
+ local res
+
+ docker:clear_status()
+ docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
+
+ if cmd ~= "upgrade" then
+ res = dk.containers[cmd](dk, {id = container_id})
+ else
+ res = dk.containers_upgrade(dk, {id = container_id})
+ end
+
+ if res and res.code >= 300 then
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
+ else
+ docker:clear_status()
+ if cmd ~= "remove" and cmd ~= "upgrade" then
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
+ else
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
+ end
+ end
+end
+
+m=SimpleForm("docker",
+ translatef("Docker - Container (%s)", container_info.Name:sub(2)),
+ translate("On this page, the selected container can be managed."))
+m.redirect = luci.dispatcher.build_url("admin/docker/containers")
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err=docker:read_status()
+s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(Table,{{}})
+s.notitle=true
+s.rowcolors=false
+s.template = "cbi/nullsection"
+
+o = s:option(Button, "_start")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Start")
+o.inputstyle = "apply"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"start")
+end
+
+o = s:option(Button, "_restart")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Restart")
+o.inputstyle = "reload"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"restart")
+end
+
+o = s:option(Button, "_stop")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Stop")
+o.inputstyle = "reset"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"stop")
+end
+
+o = s:option(Button, "_kill")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Kill")
+o.inputstyle = "reset"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"kill")
+end
+
+o = s:option(Button, "_export")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Export")
+o.inputstyle = "apply"
+o.forcewrite = true
+o.write = function(self, section)
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/container_export/"..container_id))
+end
+
+o = s:option(Button, "_upgrade")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Upgrade")
+o.inputstyle = "reload"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"upgrade")
+end
+
+o = s:option(Button, "_duplicate")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Duplicate/Edit")
+o.inputstyle = "add"
+o.forcewrite = true
+o.write = function(self, section)
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
+end
+
+o = s:option(Button, "_remove")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle=translate("Remove")
+o.inputstyle = "remove"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"remove")
+end
+
+s = m:section(SimpleSection)
+s.template = "dockerman/container"
+
+if action == "info" then
+ res = dk.networks:list()
+ if res.code < 300 then
+ networks = res.body
+ else
+ return
+ end
+ m.submit = false
+ m.reset = false
+ table_info = {
+ ["01name"] = {
+ _key = translate("Name"),
+ _value = container_info.Name:sub(2) or "-",
+ _button=translate("Update")
+ },
+ ["02id"] = {
+ _key = translate("ID"),
+ _value = container_info.Id or "-"
+ },
+ ["03image"] = {
+ _key = translate("Image"),
+ _value = container_info.Config.Image .. "
" .. container_info.Image
+ },
+ ["04status"] = {
+ _key = translate("Status"),
+ _value = container_info.State and container_info.State.Status or "-"
+ },
+ ["05created"] = {
+ _key = translate("Created"),
+ _value = container_info.Created or "-"
+ },
+ }
+
+ if container_info.State.Status == "running" then
+ table_info["06start"] = {
+ _key = translate("Start Time"),
+ _value = container_info.State and container_info.State.StartedAt or "-"
+ }
+ else
+ table_info["06start"] = {
+ _key = translate("Finish Time"),
+ _value = container_info.State and container_info.State.FinishedAt or "-"
+ }
+ end
+
+ table_info["07healthy"] = {
+ _key = translate("Healthy"),
+ _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"
+ }
+ table_info["08restart"] = {
+ _key = translate("Restart Policy"),
+ _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-",
+ _button=translate("Update")
+ }
+ table_info["081user"] = {
+ _key = translate("User"),
+ _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"
+ }
+ table_info["09mount"] = {
+ _key = translate("Mount/Volume"),
+ _value = get_mounts(container_info) or "-"
+ }
+ table_info["10cmd"] = {
+ _key = translate("Command"),
+ _value = get_command(container_info) or "-"
+ }
+ table_info["11env"] = {
+ _key = translate("Env"),
+ _value = get_env(container_info) or "-"
+ }
+ table_info["12ports"] = {
+ _key = translate("Ports"),
+ _value = get_ports(container_info) or "-"
+ }
+ table_info["13links"] = {
+ _key = translate("Links"),
+ _value = get_links(container_info) or "-"
+ }
+ table_info["14device"] = {
+ _key = translate("Device"),
+ _value = get_device(container_info) or "-"
+ }
+ table_info["15tmpfs"] = {
+ _key = translate("Tmpfs"),
+ _value = get_tmpfs(container_info) or "-"
+ }
+ table_info["16dns"] = {
+ _key = translate("DNS"),
+ _value = get_dns(container_info) or "-"
+ }
+ table_info["17sysctl"] = {
+ _key = translate("Sysctl"),
+ _value = get_sysctl(container_info) or "-"
+ }
+
+ info_networks = get_networks(container_info)
+ list_networks = {}
+ for _, v in ipairs (networks) do
+ if v and v.Name then
+ local parent = v.Options and v.Options.parent or nil
+ local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
+ ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
+ local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
+ list_networks[v.Name] = network_name
+ end
+ end
+
+ if type(info_networks)== "table" then
+ for k,v in pairs(info_networks) do
+ table_info["14network"..k] = {
+ _key = translate("Network"),
+ _value = k.. (v~="" and (" | ".. v) or ""),
+ _button=translate("Disconnect")
+ }
+ list_networks[k]=nil
+ end
+ end
+
+ table_info["15connect"] = {
+ _key = translate("Connect Network"),
+ _value = list_networks ,_opts = "",
+ _button=translate("Connect")
+ }
+
+ s = m:section(Table,table_info)
+ s.nodescr=true
+ s.formvalue=function(self, section)
+ return table_info
+ end
+
+ o = s:option(DummyValue, "_key", translate("Info"))
+ o.width = "20%"
+
+ o = s:option(ListValue, "_value")
+ o.render = function(self, section, scope)
+ if table_info[section]._key == translate("Name") then
+ self:reset_values()
+ self.template = "cbi/value"
+ self.size = 30
+ self.keylist = {}
+ self.vallist = {}
+ self.default=table_info[section]._value
+ Value.render(self, section, scope)
+ elseif table_info[section]._key == translate("Restart Policy") then
+ self.template = "cbi/lvalue"
+ self:reset_values()
+ self.size = nil
+ self:value("no", "No")
+ self:value("unless-stopped", "Unless stopped")
+ self:value("always", "Always")
+ self:value("on-failure", "On failure")
+ self.default=table_info[section]._value
+ ListValue.render(self, section, scope)
+ elseif table_info[section]._key == translate("Connect Network") then
+ self.template = "cbi/lvalue"
+ self:reset_values()
+ self.size = nil
+ for k,v in pairs(list_networks) do
+ if k ~= "host" then
+ self:value(k,v)
+ end
+ end
+ self.default=table_info[section]._value
+ ListValue.render(self, section, scope)
+ else
+ self:reset_values()
+ self.rawhtml=true
+ self.template = "cbi/dvalue"
+ self.default=table_info[section]._value
+ DummyValue.render(self, section, scope)
+ end
+ end
+ o.forcewrite = true
+ o.write = function(self, section, value)
+ table_info[section]._value=value
+ end
+ o.validate = function(self, value)
+ return value
+ end
+
+ o = s:option(Value, "_opts")
+ o.forcewrite = true
+ o.write = function(self, section, value)
+ table_info[section]._opts=value
+ end
+ o.validate = function(self, value)
+ return value
+ end
+ o.render = function(self, section, scope)
+ if table_info[section]._key==translate("Connect Network") then
+ self.template = "cbi/value"
+ self.keylist = {}
+ self.vallist = {}
+ self.placeholder = "10.1.1.254"
+ self.datatype = "ip4addr"
+ self.default=table_info[section]._opts
+ Value.render(self, section, scope)
+ else
+ self.rawhtml=true
+ self.template = "cbi/dvalue"
+ self.default=table_info[section]._opts
+ DummyValue.render(self, section, scope)
+ end
+ end
+
+ o = s:option(Button, "_button")
+ o.forcewrite = true
+ o.render = function(self, section, scope)
+ if table_info[section]._button and table_info[section]._value ~= nil then
+ self.inputtitle=table_info[section]._button
+ self.template = "cbi/button"
+ self.inputstyle = "edit"
+ Button.render(self, section, scope)
+ else
+ self.template = "cbi/dvalue"
+ self.default=""
+ DummyValue.render(self, section, scope)
+ end
+ end
+ o.write = function(self, section, value)
+ local res
+
+ docker:clear_status()
+
+ if section == "01name" then
+ docker:append_status("Containers: rename " .. container_id .. "...")
+ local new_name = table_info[section]._value
+ res = dk.containers:rename({
+ id = container_id,
+ query = {
+ name=new_name
+ }
+ })
+ elseif section == "08restart" then
+ docker:append_status("Containers: update " .. container_id .. "...")
+ local new_restart = table_info[section]._value
+ res = dk.containers:update({
+ id = container_id,
+ body = {
+ RestartPolicy = {
+ Name = new_restart
+ }
+ }
+ })
+ elseif table_info[section]._key == translate("Network") then
+ local _,_,leave_network
+
+ _, _, leave_network = table_info[section]._value:find("(.-) | .+")
+ leave_network = leave_network or table_info[section]._value
+ docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...")
+ res = dk.networks:disconnect({
+ name = leave_network,
+ body = {
+ Container = container_id
+ }
+ })
+ elseif section == "15connect" then
+ local connect_network = table_info[section]._value
+ local network_opiton
+ if connect_network ~= "none"
+ and connect_network ~= "bridge"
+ and connect_network ~= "host" then
+
+ network_opiton = table_info[section]._opts ~= "" and {
+ IPAMConfig={
+ IPv4Address=table_info[section]._opts
+ }
+ } or nil
+ end
+ docker:append_status("Network: connect " .. connect_network .. container_id .. "...")
+ res = dk.networks:connect({
+ name = connect_network,
+ body = {
+ Container = container_id,
+ EndpointConfig= network_opiton
+ }
+ })
+ end
+
+ if res and res.code > 300 then
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
+ else
+ docker:clear_status()
+ end
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
+ end
+elseif action == "resources" then
+ s = m:section(SimpleSection)
+ o = s:option( Value, "cpus",
+ translate("CPUs"),
+ translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
+ o.placeholder = "1.5"
+ o.rmempty = true
+ o.datatype="ufloat"
+ o.default = container_info.HostConfig.NanoCpus / (10^9)
+
+ o = s:option(Value, "cpushares",
+ translate("CPU Shares Weight"),
+ translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
+ o.placeholder = "1024"
+ o.rmempty = true
+ o.datatype="uinteger"
+ o.default = container_info.HostConfig.CpuShares
+
+ o = s:option(Value, "memory",
+ translate("Memory"),
+ translate("Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
+ o.placeholder = "128m"
+ o.rmempty = true
+ o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
+
+ o = s:option(Value, "blkioweight",
+ translate("Block IO Weight"),
+ translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
+ o.placeholder = "500"
+ o.rmempty = true
+ o.datatype="uinteger"
+ o.default = container_info.HostConfig.BlkioWeight
+
+ m.handle = function(self, state, data)
+ if state == FORM_VALID then
+ local memory = data.memory
+ if memory and memory ~= 0 then
+ _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
+ if n then
+ unit = unit and unit:sub(1,1):upper() or "B"
+ if unit == "M" then
+ memory = tonumber(n) * 1024 * 1024
+ elseif unit == "G" then
+ memory = tonumber(n) * 1024 * 1024 * 1024
+ elseif unit == "K" then
+ memory = tonumber(n) * 1024
+ else
+ memory = tonumber(n)
+ end
+ end
+ end
+
+ request_body = {
+ BlkioWeight = tonumber(data.blkioweight),
+ NanoCPUs = tonumber(data.cpus)*10^9,
+ Memory = tonumber(memory),
+ CpuShares = tonumber(data.cpushares)
+ }
+
+ docker:write_status("Containers: update " .. container_id .. "...")
+ local res = dk.containers:update({id = container_id, body = request_body})
+ if res and res.code >= 300 then
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
+ else
+ docker:clear_status()
+ end
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources"))
+ end
+ end
+
+elseif action == "file" then
+ m.submit = false
+ m.reset = false
+ s= m:section(SimpleSection)
+ s.template = "dockerman/container_file_manager"
+ s.container = container_id
+ m.redirect = nil
+elseif action == "inspect" then
+ s = m:section(SimpleSection)
+ s.syslog = luci.jsonc.stringify(container_info, true)
+ s.title = translate("Container Inspect")
+ s.template = "dockerman/logs"
+ m.submit = false
+ m.reset = false
+elseif action == "logs" then
+ local logs = ""
+ local query ={
+ stdout = 1,
+ stderr = 1,
+ tail = 1000
+ }
+
+ s = m:section(SimpleSection)
+
+ logs = dk.containers:logs({id = container_id, query = query})
+ if logs.code == 200 then
+ s.syslog=logs.body
+ else
+ s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
+ end
+
+ s.title=translate("Container Logs")
+ s.template = "dockerman/logs"
+ m.submit = false
+ m.reset = false
+elseif action == "console" then
+ m.submit = false
+ m.reset = false
+ local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
+ local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil
+
+ if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then
+ local cmd = "/bin/sh"
+ local uid
+
+ s = m:section(SimpleSection)
+
+ o = s:option(Value, "command", translate("Command"))
+ o:value("/bin/sh", "/bin/sh")
+ o:value("/bin/ash", "/bin/ash")
+ o:value("/bin/bash", "/bin/bash")
+ o.default = "/bin/sh"
+ o.forcewrite = true
+ o.write = function(self, section, value)
+ cmd = value
+ end
+
+ o = s:option(Value, "uid", translate("UID"))
+ o.forcewrite = true
+ o.write = function(self, section, value)
+ uid = value
+ end
+
+ o = s:option(Button, "connect")
+ o.render = function(self, section, scope)
+ self.inputstyle = "add"
+ self.title = " "
+ self.inputtitle = translate("Connect")
+ Button.render(self, section, scope)
+ end
+ o.write = function(self, section)
+ local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
+ local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil
+
+ if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then
+ return
+ end
+
+ local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1"))
+ if pid and pid ~= "" then
+ luci.util.exec("kill -9 " .. pid)
+ end
+
+ local hosts
+ local uci = (require "luci.model.uci").cursor()
+ local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint") or false
+ local host = nil
+ local port = nil
+ local socket = nil
+
+ if remote then
+ host = uci:get("dockerd", "dockerman", "remote_host") or nil
+ port = uci:get("dockerd", "dockerman", "remote_port") or nil
+ else
+ socket = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock"
+ end
+
+ if remote and host and port then
+ hosts = "tcp://" .. host .. ':'.. port
+ elseif socket then
+ hosts = "unix://" .. socket
+ else
+ return
+ end
+
+ if uid and uid ~= "" then
+ uid = "-u " .. uid
+ else
+ uid = ""
+ end
+
+ local start_cmd = string.format('%s -d 2 --once -p 7682 %s -H "%s" exec -it %s %s %s&', cmd_ttyd, cmd_docker, hosts, uid, container_id, cmd)
+
+ os.execute(start_cmd)
+
+ o = s:option(DummyValue, "console")
+ o.container_id = container_id
+ o.template = "dockerman/container_console"
+ end
+ end
+elseif action == "stats" then
+ local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
+ local container_top
+
+ if response.code == 200 then
+ container_top=response.body
+ else
+ response = dk.containers:top({id = container_id})
+ if response.code == 200 then
+ container_top=response.body
+ end
+ end
+
+ if type(container_top) == "table" then
+ s = m:section(SimpleSection)
+ s.container_id = container_id
+ s.template = "dockerman/container_stats"
+ table_stats = {
+ cpu={
+ key=translate("CPU Useage"),
+ value='-'
+ },
+ memory={
+ key=translate("Memory Useage"),
+ value='-'
+ }
+ }
+
+ container_top = response.body
+ s = m:section(Table, table_stats, translate("Stats"))
+ s:option(DummyValue, "key", translate("Stats")).width="33%"
+ s:option(DummyValue, "value")
+ top_section = m:section(Table, container_top.Processes, translate("TOP"))
+ for i, v in ipairs(container_top.Titles) do
+ top_section:option(DummyValue, i, translate(v))
+ end
+ end
+
+ m.submit = false
+ m.reset = false
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua
new file mode 100644
index 000000000..fa7765261
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua
@@ -0,0 +1,284 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local http = require "luci.http"
+local docker = require "luci.model.docker"
+
+local m, s, o
+local images, networks, containers, res, lost_state
+local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode
+local dk = docker.new()
+
+if dk:_ping().code ~= 200 then
+ lost_state = true
+else
+ res = dk.images:list()
+ if res and res.code and res.code < 300 then
+ images = res.body
+ end
+
+ res = dk.networks:list()
+ if res and res.code and res.code < 300 then
+ networks = res.body
+ end
+
+ res = dk.containers:list({
+ query = {
+ all = true
+ }
+ })
+ if res and res.code and res.code < 300 then
+ containers = res.body
+ end
+end
+
+function get_containers()
+ local data = {}
+ if type(containers) ~= "table" then
+ return nil
+ end
+
+ for i, v in ipairs(containers) do
+ local index = (10^12 - v.Created) .. "_id_" .. v.Id
+
+ data[index]={}
+ data[index]["_selected"] = 0
+ data[index]["_id"] = v.Id:sub(1,12)
+ -- data[index]["name"] = v.Names[1]:sub(2)
+ data[index]["_status"] = v.Status
+
+ if v.Status:find("^Up") then
+ data[index]["_name"] = ""..v.Names[1]:sub(2)..""
+ data[index]["_status"] = "".. data[index]["_status"] .. "" .. "
"
+ else
+ data[index]["_name"] = ""..v.Names[1]:sub(2)..""
+ data[index]["_status"] = ''.. data[index]["_status"] .. ""
+ end
+
+ if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
+ for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
+ data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
+ end
+ end
+
+ -- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge"
+ -- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil
+ -- local _, _, image = v.Image:find("^sha256:(.+)")
+ -- if image ~= nil then
+ -- image=image:sub(1,12)
+ -- end
+
+ if v.Ports and next(v.Ports) ~= nil then
+ data[index]["_ports"] = nil
+ local ip = require "luci.ip"
+ for _,v2 in ipairs(v.Ports) do
+ -- display ipv4 only
+ if ip.new(v2.IP or "0.0.0.0"):is4() then
+ data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "")
+ .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('') or "")
+ .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "")
+ .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "" or "")
+ end
+ end
+ end
+
+ for ii,iv in ipairs(images) do
+ if iv.Id == v.ImageID then
+ data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
+ end
+ end
+ data[index]["_id_name"] = ''.. data[index]["_name"] .. "
ID: " .. data[index]["_id"]
+ .. "
Image: " .. (data[index]["_image"] or "<none>")
+ .. "
"
+
+ if type(v.Mounts) == "table" and next(v.Mounts) then
+ for _, v2 in pairs(v.Mounts) do
+ if v2.Type ~= "volume" then
+ local v_sorce_d, v_dest_d
+ local v_sorce = ""
+ local v_dest = ""
+ for v_sorce_d in v2["Source"]:gmatch('[^/]+') do
+ if v_sorce_d and #v_sorce_d > 12 then
+ v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,8) .. ".."
+ else
+ v_sorce = v_sorce .."/".. v_sorce_d
+ end
+ end
+ for v_dest_d in v2["Destination"]:gmatch('[^/]+') do
+ if v_dest_d and #v_dest_d > 12 then
+ v_dest = v_dest .. "/" .. v_dest_d:sub(1,8) .. ".."
+ else
+ v_dest = v_dest .."/".. v_dest_d
+ end
+ end
+ data[index]["_mounts"] = (data[index]["_mounts"] and (data[index]["_mounts"] .. "
") or "") .. '' .. v_sorce .. "→" .. v_dest..''
+ end
+ end
+ end
+
+ data[index]["_image_id"] = v.ImageID:sub(8,20)
+ data[index]["_command"] = v.Command
+ end
+ return data
+end
+
+local container_list = not lost_state and get_containers() or {}
+
+m = SimpleForm("docker",
+ translate("Docker - Containers"),
+ translate("This page displays all containers that have been created on the connected docker host."))
+m.submit=false
+m.reset=false
+m:append(Template("dockerman/containers_running_stats"))
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err=docker:read_status()
+s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(Table, container_list, translate("Containers"))
+s.nodescr=true
+s.config="containers"
+
+o = s:option(Flag, "_selected","")
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+o.width = "1%"
+o.write=function(self, section, value)
+ container_list[section]._selected = value
+end
+
+-- o = s:option(DummyValue, "_id", translate("ID"))
+-- o.width="10%"
+
+-- o = s:option(DummyValue, "_name", translate("Container Name"))
+-- o.rawhtml = true
+
+o = s:option(DummyValue, "_id_name", translate("Container Info"))
+o.rawhtml = true
+o.width="15%"
+
+o = s:option(DummyValue, "_status", translate("Status"))
+o.width="15%"
+o.rawhtml=true
+
+o = s:option(DummyValue, "_network", translate("Network"))
+o.width="10%"
+
+o = s:option(DummyValue, "_ports", translate("Ports"))
+o.width="5%"
+o.rawhtml = true
+o = s:option(DummyValue, "_mounts", translate("Mounts"))
+o.width="25%"
+o.rawhtml = true
+
+-- o = s:option(DummyValue, "_image", translate("Image"))
+-- o.width="8%"
+
+o = s:option(DummyValue, "_command", translate("Command"))
+o.width="15%"
+
+local start_stop_remove = function(m, cmd)
+ local container_selected = {}
+ -- 遍历table中sectionid
+ for k in pairs(container_list) do
+ -- 得到选中项的名字
+ if container_list[k]._selected == 1 then
+ container_selected[#container_selected + 1] = container_list[k]["_id"]
+ end
+ end
+ if #container_selected > 0 then
+ local success = true
+
+ docker:clear_status()
+ for _, cont in ipairs(container_selected) do
+ docker:append_status("Containers: " .. cmd .. " " .. cont .. "...")
+ local res = dk.containers[cmd](dk, {id = cont})
+ if res and res.code and res.code >= 300 then
+ success = false
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
+ else
+ docker:append_status("done\n")
+ end
+ end
+
+ if success then
+ docker:clear_status()
+ end
+
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
+ end
+end
+
+s = m:section(Table,{{}})
+s.notitle=true
+s.rowcolors=false
+s.template="cbi/nullsection"
+
+o = s:option(Button, "_new")
+o.inputtitle = translate("Add")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "add"
+o.forcewrite = true
+o.write = function(self, section)
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
+end
+o.disable = lost_state
+
+o = s:option(Button, "_start")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle = translate("Start")
+o.inputstyle = "apply"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"start")
+end
+o.disable = lost_state
+
+o = s:option(Button, "_restart")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle = translate("Restart")
+o.inputstyle = "reload"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"restart")
+end
+o.disable = lost_state
+
+o = s:option(Button, "_stop")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle = translate("Stop")
+o.inputstyle = "reset"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"stop")
+end
+o.disable = lost_state
+
+o = s:option(Button, "_kill")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle = translate("Kill")
+o.inputstyle = "reset"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m,"kill")
+end
+o.disable = lost_state
+
+o = s:option(Button, "_remove")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputtitle = translate("Remove")
+o.inputstyle = "remove"
+o.forcewrite = true
+o.write = function(self, section)
+ start_stop_remove(m, "remove")
+end
+o.disable = lost_state
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua
new file mode 100644
index 000000000..c3d3eab0d
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua
@@ -0,0 +1,284 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+local dk = docker.new()
+
+local containers, images, res, lost_state
+local m, s, o
+
+if dk:_ping().code ~= 200 then
+ lost_state = true
+else
+ res = dk.images:list()
+ if res and res.code and res.code < 300 then
+ images = res.body
+ end
+
+ res = dk.containers:list({ query = { all = true } })
+ if res and res.code and res.code < 300 then
+ containers = res.body
+ end
+end
+
+function get_images()
+ local data = {}
+
+ for i, v in ipairs(images) do
+ local index = v.Created .. v.Id
+
+ data[index]={}
+ data[index]["_selected"] = 0
+ data[index]["id"] = v.Id:sub(8)
+ data[index]["_id"] = '' .. v.Id:sub(8,20) .. ''
+
+ if v.RepoTags and next(v.RepoTags)~=nil then
+ for i, v1 in ipairs(v.RepoTags) do
+ data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "
" )or "") .. ((v1:match("") or (#v.RepoTags == 1)) and v1 or ('' .. v1 .. ''))
+
+ if not data[index]["tag"] then
+ data[index]["tag"] = v1
+ end
+ end
+ else
+ data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
+ data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "" ).. ":"
+ end
+
+ data[index]["_tags"] = data[index]["_tags"]:gsub("","<none>")
+ for ci,cv in ipairs(containers) do
+ if v.Id == cv.ImageID then
+ data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
+ ''.. cv.Names[1]:sub(2)..""
+ end
+ end
+
+ data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
+ data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
+ end
+
+ return data
+end
+
+local image_list = not lost_state and get_images() or {}
+
+m = SimpleForm("docker",
+ translate("Docker - Images"),
+ translate("On this page all images are displayed that are available on the system and with which a container can be created."))
+m.submit=false
+m.reset=false
+
+local pull_value={
+ _image_tag_name="",
+ _registry="index.docker.io"
+}
+
+s = m:section(SimpleSection,
+ translate("Pull Image"),
+ translate("By entering a valid image name with the corresponding version, the docker image can be downloaded from the configured registry."))
+s.template="cbi/nullsection"
+
+o = s:option(Value, "_image_tag_name")
+o.template = "dockerman/cbi/inlinevalue"
+o.placeholder="lisaac/luci:latest"
+o.write = function(self, section, value)
+ local hastag = value:find(":")
+
+ if not hastag then
+ value = value .. ":latest"
+ end
+ pull_value["_image_tag_name"] = value
+end
+
+o = s:option(Button, "_pull")
+o.inputtitle= translate("Pull")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "add"
+o.disable = lost_state
+o.write = function(self, section)
+ local tag = pull_value["_image_tag_name"]
+ local json_stringify = luci.jsonc and luci.jsonc.stringify
+
+ if tag and tag ~= "" then
+ docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
+ local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
+
+ if res and res.code and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then
+ docker:clear_status()
+ else
+ docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
+ end
+ else
+ docker:append_status("code: 400 please input the name of image name!")
+ end
+
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
+end
+
+s = m:section(SimpleSection,
+ translate("Import Image"),
+ translate("When pressing the Import button, both a local image can be loaded onto the system and a valid image tar can be downloaded from remote."))
+
+o = s:option(DummyValue, "_image_import")
+o.template = "dockerman/images_import"
+o.disable = lost_state
+
+s = m:section(Table, image_list, translate("Images overview"))
+
+o = s:option(Flag, "_selected","")
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+o.write = function(self, section, value)
+ image_list[section]._selected = value
+end
+
+o = s:option(DummyValue, "_id", translate("ID"))
+o.rawhtml = true
+
+o = s:option(DummyValue, "_tags", translate("RepoTags"))
+o.rawhtml = true
+
+o = s:option(DummyValue, "_containers", translate("Containers"))
+o.rawhtml = true
+
+o = s:option(DummyValue, "_size", translate("Size"))
+
+o = s:option(DummyValue, "_created", translate("Created"))
+
+local remove_action = function(force)
+ local image_selected = {}
+
+ for k in pairs(image_list) do
+ if image_list[k]._selected == 1 then
+ image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("
") or image_list[k]["_tags"]:match("<none>")) and image_list[k].id or image_list[k].tag
+ end
+ end
+
+ if next(image_selected) ~= nil then
+ local success = true
+
+ docker:clear_status()
+ for _, img in ipairs(image_selected) do
+ local query
+ docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
+
+ if force then
+ query = {force = true}
+ end
+
+ local msg = dk.images:remove({
+ id = img,
+ query = query
+ })
+ if msg and msg.code ~= 200 then
+ docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
+ success = false
+ else
+ docker:append_status("done\n")
+ end
+ end
+
+ if success then
+ docker:clear_status()
+ end
+
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
+ end
+end
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err = docker:read_status()
+s.err = s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(Table,{{}})
+s.notitle=true
+s.rowcolors=false
+s.template="cbi/nullsection"
+
+o = s:option(Button, "remove")
+o.inputtitle= translate("Remove")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "remove"
+o.forcewrite = true
+o.write = function(self, section)
+ remove_action()
+end
+o.disable = lost_state
+
+o = s:option(Button, "forceremove")
+o.inputtitle= translate("Force Remove")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "remove"
+o.forcewrite = true
+o.write = function(self, section)
+ remove_action(true)
+end
+o.disable = lost_state
+
+o = s:option(Button, "save")
+o.inputtitle= translate("Save")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "edit"
+o.disable = lost_state
+o.forcewrite = true
+o.write = function (self, section)
+ local image_selected = {}
+
+ for k in pairs(image_list) do
+ if image_list[k]._selected == 1 then
+ image_selected[#image_selected + 1] = image_list[k].id
+ end
+ end
+
+ if next(image_selected) ~= nil then
+ local names, first, show_name
+
+ for _, img in ipairs(image_selected) do
+ names = names and (names .. "&names=".. img) or img
+ end
+ if #image_selected > 1 then
+ show_name = "images"
+ else
+ show_name = image_selected[1]
+ end
+ local cb = function(res, chunk)
+ if res and res.code and res.code == 200 then
+ if not first then
+ first = true
+ luci.http.header('Content-Disposition', 'inline; filename="'.. show_name .. '.tar"')
+ luci.http.header('Content-Type', 'application\/x-tar')
+ end
+ luci.ltn12.pump.all(chunk, luci.http.write)
+ else
+ if not first then
+ first = true
+ luci.http.prepare_content("text/plain")
+ end
+ luci.ltn12.pump.all(chunk, luci.http.write)
+ end
+ end
+
+ docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
+ local msg = dk.images:get({query = {names = names}}, cb)
+ if msg and msg.code and msg.code ~= 200 then
+ docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
+ else
+ docker:clear_status()
+ end
+ end
+end
+
+o = s:option(Button, "load")
+o.inputtitle= translate("Load")
+o.template = "dockerman/images_load"
+o.inputstyle = "add"
+o.disable = lost_state
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua
new file mode 100644
index 000000000..37702c783
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua
@@ -0,0 +1,159 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+
+local m, s, o
+local networks, dk, res, lost_state
+
+dk = docker.new()
+
+if dk:_ping().code ~= 200 then
+ lost_state = true
+else
+ res = dk.networks:list()
+ if res and res.code and res.code < 300 then
+ networks = res.body
+ end
+end
+
+local get_networks = function ()
+ local data = {}
+
+ if type(networks) ~= "table" then
+ return nil
+ end
+
+ for i, v in ipairs(networks) do
+ local index = v.Created .. v.Id
+
+ data[index]={}
+ data[index]["_selected"] = 0
+ data[index]["_id"] = v.Id:sub(1,12)
+ data[index]["_name"] = v.Name
+ data[index]["_driver"] = v.Driver
+
+ if v.Driver == "bridge" then
+ data[index]["_interface"] = v.Options["com.docker.network.bridge.name"]
+ elseif v.Driver == "macvlan" then
+ data[index]["_interface"] = v.Options.parent
+ end
+
+ data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
+ data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil
+ end
+
+ return data
+end
+
+local network_list = not lost_state and get_networks() or {}
+
+m = SimpleForm("docker",
+ translate("Docker - Networks"),
+ translate("This page displays all docker networks that have been created on the connected docker host."))
+m.submit=false
+m.reset=false
+
+s = m:section(Table, network_list, translate("Networks overview"))
+s.nodescr=true
+
+o = s:option(Flag, "_selected","")
+o.template = "dockerman/cbi/xfvalue"
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+o.render = function(self, section, scope)
+ self.disable = 0
+ if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then
+ self.disable = 1
+ end
+ Flag.render(self, section, scope)
+end
+o.write = function(self, section, value)
+ network_list[section]._selected = value
+end
+
+o = s:option(DummyValue, "_id", translate("ID"))
+
+o = s:option(DummyValue, "_name", translate("Network Name"))
+
+o = s:option(DummyValue, "_driver", translate("Driver"))
+
+o = s:option(DummyValue, "_interface", translate("Parent Interface"))
+
+o = s:option(DummyValue, "_subnet", translate("Subnet"))
+
+o = s:option(DummyValue, "_gateway", translate("Gateway"))
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err = docker:read_status()
+s.err = s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(Table,{{}})
+s.notitle=true
+s.rowcolors=false
+s.template="cbi/nullsection"
+
+o = s:option(Button, "_new")
+o.inputtitle= translate("New")
+o.template = "dockerman/cbi/inlinebutton"
+o.notitle=true
+o.inputstyle = "add"
+o.forcewrite = true
+o.disable = lost_state
+o.write = function(self, section)
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
+end
+
+o = s:option(Button, "_remove")
+o.inputtitle= translate("Remove")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "remove"
+o.forcewrite = true
+o.disable = lost_state
+o.write = function(self, section)
+ local network_selected = {}
+ local network_name_selected = {}
+ local network_driver_selected = {}
+
+ for k in pairs(network_list) do
+ if network_list[k]._selected == 1 then
+ network_selected[#network_selected + 1] = network_list[k]._id
+ network_name_selected[#network_name_selected + 1] = network_list[k]._name
+ network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver
+ end
+ end
+
+ if next(network_selected) ~= nil then
+ local success = true
+ docker:clear_status()
+
+ for ii, net in ipairs(network_selected) do
+ docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
+ local res = dk.networks["remove"](dk, {id = net})
+
+ if res and res.code and res.code >= 300 then
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
+ success = false
+ else
+ docker:append_status("done\n")
+ if network_driver_selected[ii] == "macvlan" then
+ docker.remove_macvlan_interface(network_name_selected[ii])
+ end
+ end
+ end
+
+ if success then
+ docker:clear_status()
+ end
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
+ end
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua
new file mode 100644
index 000000000..bafe73196
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua
@@ -0,0 +1,911 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+
+local m, s, o
+
+local dk = docker.new()
+
+local cmd_line = table.concat(arg, '/')
+local images, networks
+local create_body = {}
+
+if dk:_ping().code ~= 200 then
+ lost_state = true
+ images = {}
+ networks = {}
+else
+ images = dk.images:list().body
+ networks = dk.networks:list().body
+end
+
+local is_quot_complete = function(str)
+ local num = 0, w
+ require "math"
+
+ if not str then
+ return true
+ end
+
+ local num = 0, w
+ for w in str:gmatch("\"") do
+ num = num + 1
+ end
+
+ if math.fmod(num, 2) ~= 0 then
+ return false
+ end
+
+ num = 0
+ for w in str:gmatch("\'") do
+ num = num + 1
+ end
+
+ if math.fmod(num, 2) ~= 0 then
+ return false
+ end
+
+ return true
+end
+
+function contains(list, x)
+ for _, v in pairs(list) do
+ if v == x then
+ return true
+ end
+ end
+ return false
+end
+
+local resolve_cli = function(cmd_line)
+ local config = {
+ advance = 1
+ }
+
+ local key_no_val = {
+ 't',
+ 'd',
+ 'i',
+ 'tty',
+ 'rm',
+ 'read_only',
+ 'interactive',
+ 'init',
+ 'help',
+ 'detach',
+ 'privileged',
+ 'P',
+ 'publish_all',
+ }
+
+ local key_with_val = {
+ 'sysctl',
+ 'add_host',
+ 'a',
+ 'attach',
+ 'blkio_weight_device',
+ 'cap_add',
+ 'cap_drop',
+ 'device',
+ 'device_cgroup_rule',
+ 'device_read_bps',
+ 'device_read_iops',
+ 'device_write_bps',
+ 'device_write_iops',
+ 'dns',
+ 'dns_option',
+ 'dns_search',
+ 'e',
+ 'env',
+ 'env_file',
+ 'expose',
+ 'group_add',
+ 'l',
+ 'label',
+ 'label_file',
+ 'link',
+ 'link_local_ip',
+ 'log_driver',
+ 'log_opt',
+ 'network_alias',
+ 'p',
+ 'publish',
+ 'security_opt',
+ 'storage_opt',
+ 'tmpfs',
+ 'v',
+ 'volume',
+ 'volumes_from',
+ 'blkio_weight',
+ 'cgroup_parent',
+ 'cidfile',
+ 'cpu_period',
+ 'cpu_quota',
+ 'cpu_rt_period',
+ 'cpu_rt_runtime',
+ 'c',
+ 'cpu_shares',
+ 'cpus',
+ 'cpuset_cpus',
+ 'cpuset_mems',
+ 'detach_keys',
+ 'disable_content_trust',
+ 'domainname',
+ 'entrypoint',
+ 'gpus',
+ 'health_cmd',
+ 'health_interval',
+ 'health_retries',
+ 'health_start_period',
+ 'health_timeout',
+ 'h',
+ 'hostname',
+ 'ip',
+ 'ip6',
+ 'ipc',
+ 'isolation',
+ 'kernel_memory',
+ 'log_driver',
+ 'mac_address',
+ 'm',
+ 'memory',
+ 'memory_reservation',
+ 'memory_swap',
+ 'memory_swappiness',
+ 'mount',
+ 'name',
+ 'network',
+ 'no_healthcheck',
+ 'oom_kill_disable',
+ 'oom_score_adj',
+ 'pid',
+ 'pids_limit',
+ 'restart',
+ 'runtime',
+ 'shm_size',
+ 'sig_proxy',
+ 'stop_signal',
+ 'stop_timeout',
+ 'ulimit',
+ 'u',
+ 'user',
+ 'userns',
+ 'uts',
+ 'volume_driver',
+ 'w',
+ 'workdir'
+ }
+
+ local key_abb = {
+ net='network',
+ a='attach',
+ c='cpu-shares',
+ d='detach',
+ e='env',
+ h='hostname',
+ i='interactive',
+ l='label',
+ m='memory',
+ p='publish',
+ P='publish_all',
+ t='tty',
+ u='user',
+ v='volume',
+ w='workdir'
+ }
+
+ local key_with_list = {
+ 'sysctl',
+ 'add_host',
+ 'a',
+ 'attach',
+ 'blkio_weight_device',
+ 'cap_add',
+ 'cap_drop',
+ 'device',
+ 'device_cgroup_rule',
+ 'device_read_bps',
+ 'device_read_iops',
+ 'device_write_bps',
+ 'device_write_iops',
+ 'dns',
+ 'dns_optiondns_search',
+ 'e',
+ 'env',
+ 'env_file',
+ 'expose',
+ 'group_add',
+ 'l',
+ 'label',
+ 'label_file',
+ 'link',
+ 'link_local_ip',
+ 'log_driver',
+ 'log_opt',
+ 'network_alias',
+ 'p',
+ 'publish',
+ 'security_opt',
+ 'storage_opt',
+ 'tmpfs',
+ 'v',
+ 'volume',
+ 'volumes_from',
+ }
+
+ local key = nil
+ local _key = nil
+ local val = nil
+ local is_cmd = false
+
+ cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)")
+ for w in cmd_line:gmatch("[^%s]+") do
+ if w =='\\' then
+ elseif not key and not _key and not is_cmd then
+ --key=val
+ key, val = w:match("^%-%-([%lP%-]-)=(.+)")
+ if not key then
+ --key val
+ key = w:match("^%-%-([%lP%-]+)")
+ if not key then
+ -- -v val
+ key = w:match("^%-([%lP%-]+)")
+ if key then
+ -- for -dit
+ if key:match("i") or key:match("t") or key:match("d") then
+ if key:match("i") then
+ config[key_abb["i"]] = true
+ key:gsub("i", "")
+ end
+ if key:match("t") then
+ config[key_abb["t"]] = true
+ key:gsub("t", "")
+ end
+ if key:match("d") then
+ config[key_abb["d"]] = true
+ key:gsub("d", "")
+ end
+ if key:match("P") then
+ config[key_abb["P"]] = true
+ key:gsub("P", "")
+ end
+ if key == "" then
+ key = nil
+ end
+ end
+ end
+ end
+ end
+ if key then
+ key = key:gsub("-","_")
+ key = key_abb[key] or key
+ if contains(key_no_val, key) then
+ config[key] = true
+ val = nil
+ key = nil
+ elseif contains(key_with_val, key) then
+ -- if key == "cap_add" then config.privileged = true end
+ else
+ key = nil
+ val = nil
+ end
+ else
+ config.image = w
+ key = nil
+ val = nil
+ is_cmd = true
+ end
+ elseif (key or _key) and not is_cmd then
+ if key == "mount" then
+ -- we need resolve mount options here
+ -- type=bind,source=/source,target=/app
+ local _type = w:match("^type=([^,]+),") or "bind"
+ local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or ""
+ local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
+ local ro = w:match(",readonly") and "ro" or nil
+
+ if source and target then
+ if _type ~= "tmpfs" then
+ local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
+ val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or ""))
+ else
+ local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
+ local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
+ key = "tmpfs"
+ val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "")
+ if not config[key] then
+ config[key] = {}
+ end
+ table.insert( config[key], val )
+ key = nil
+ val = nil
+ end
+ end
+ else
+ val = w
+ end
+ elseif is_cmd then
+ config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w
+ end
+ if (key or _key) and val then
+ key = _key or key
+ if contains(key_with_list, key) then
+ if not config[key] then
+ config[key] = {}
+ end
+ if _key then
+ config[key][#config[key]] = config[key][#config[key]] .. " " .. w
+ else
+ table.insert( config[key], val )
+ end
+ if is_quot_complete(config[key][#config[key]]) then
+ config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
+ _key = nil
+ else
+ _key = key
+ end
+ else
+ config[key] = (config[key] and (config[key] .. " ") or "") .. val
+ if is_quot_complete(config[key]) then
+ config[key] = config[key]:gsub("[\"\']", "")
+ _key = nil
+ else
+ _key = key
+ end
+ end
+ key = nil
+ val = nil
+ end
+ end
+
+ return config
+end
+
+local default_config = {}
+
+if cmd_line and cmd_line:match("^DOCKERCLI.+") then
+ default_config = resolve_cli(cmd_line)
+elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
+ local container_id = cmd_line:match("^duplicate/(.+)")
+ create_body = dk:containers_duplicate_config({id = container_id}) or {}
+ if not create_body.HostConfig then
+ create_body.HostConfig = {}
+ end
+
+ if next(create_body) ~= nil then
+ default_config.name = nil
+ default_config.image = create_body.Image
+ default_config.hostname = create_body.Hostname
+ default_config.tty = create_body.Tty and true or false
+ default_config.interactive = create_body.OpenStdin and true or false
+ default_config.privileged = create_body.HostConfig.Privileged and true or false
+ default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
+ -- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode
+ -- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig
+ default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil
+ default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil
+ default_config.link = create_body.HostConfig.Links
+ default_config.env = create_body.Env
+ default_config.dns = create_body.HostConfig.Dns
+ default_config.volume = create_body.HostConfig.Binds
+ default_config.cap_add = create_body.HostConfig.CapAdd
+ default_config.publish_all = create_body.HostConfig.PublishAllPorts
+
+ if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then
+ default_config.sysctl = {}
+ for k, v in pairs(create_body.HostConfig.Sysctls) do
+ table.insert( default_config.sysctl, k.."="..v )
+ end
+ end
+
+ if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then
+ default_config.log_opt = {}
+ for k, v in pairs(create_body.HostConfig.LogConfig.Config) do
+ table.insert( default_config.log_opt, k.."="..v )
+ end
+ end
+
+ if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
+ default_config.publish = {}
+ for k, v in pairs(create_body.HostConfig.PortBindings) do
+ for x, y in ipairs(v) do
+ table.insert( default_config.publish, y.HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
+ end
+ end
+ end
+
+ default_config.user = create_body.User or nil
+ default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil
+ default_config.advance = 1
+ default_config.cpus = create_body.HostConfig.NanoCPUs
+ default_config.cpu_shares = create_body.HostConfig.CpuShares
+ default_config.memory = create_body.HostConfig.Memory
+ default_config.blkio_weight = create_body.HostConfig.BlkioWeight
+
+ if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then
+ default_config.device = {}
+ for _, v in ipairs(create_body.HostConfig.Devices) do
+ table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
+ end
+ end
+
+ if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
+ default_config.tmpfs = {}
+ for k, v in pairs(create_body.HostConfig.Tmpfs) do
+ table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v )
+ end
+ end
+ end
+end
+
+m = SimpleForm("docker", translate("Docker - Containers"))
+m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
+if lost_state then
+ m.submit=false
+ m.reset=false
+end
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err=docker:read_status()
+s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(SimpleSection, translate("Create new docker container"))
+s.addremove = true
+s.anonymous = true
+
+o = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
+o.rawhtml = true
+o.template = "dockerman/newcontainer_resolve"
+
+o = s:option(Value, "name", translate("Container Name"))
+o.rmempty = true
+o.default = default_config.name or nil
+
+o = s:option(Flag, "interactive", translate("Interactive (-i)"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = default_config.interactive and 1 or 0
+
+o = s:option(Flag, "tty", translate("TTY (-t)"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = default_config.tty and 1 or 0
+
+o = s:option(Value, "image", translate("Docker Image"))
+o.rmempty = true
+o.default = default_config.image or nil
+for _, v in ipairs (images) do
+ if v.RepoTags then
+ o:value(v.RepoTags[1], v.RepoTags[1])
+ end
+end
+
+o = s:option(Flag, "_force_pull", translate("Always pull image first"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+
+o = s:option(Flag, "privileged", translate("Privileged"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = default_config.privileged and 1 or 0
+
+o = s:option(ListValue, "restart", translate("Restart Policy"))
+o.rmempty = true
+o:value("no", "No")
+o:value("unless-stopped", "Unless stopped")
+o:value("always", "Always")
+o:value("on-failure", "On failure")
+o.default = default_config.restart or "unless-stopped"
+
+local d_network = s:option(ListValue, "network", translate("Networks"))
+d_network.rmempty = true
+d_network.default = default_config.network or "bridge"
+
+local d_ip = s:option(Value, "ip", translate("IPv4 Address"))
+d_ip.datatype="ip4addr"
+d_ip:depends("network", "nil")
+d_ip.default = default_config.ip or nil
+
+o = s:option(DynamicList, "link", translate("Links with other containers"))
+o.placeholder = "container_name:alias"
+o.rmempty = true
+o:depends("network", "bridge")
+o.default = default_config.link or nil
+
+o = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
+o.placeholder = "8.8.8.8"
+o.rmempty = true
+o.default = default_config.dns or nil
+
+o = s:option(Value, "user",
+ translate("User(-u)"),
+ translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
+o.placeholder = "1000:1000"
+o.rmempty = true
+o.default = default_config.user or nil
+
+o = s:option(DynamicList, "env",
+ translate("Environmental Variable(-e)"),
+ translate("Set environment variables to inside the container"))
+o.placeholder = "TZ=Asia/Shanghai"
+o.rmempty = true
+o.default = default_config.env or nil
+
+o = s:option(DynamicList, "volume",
+ translate("Bind Mount(-v)"),
+ translate("Bind mount a volume"))
+o.placeholder = "/media:/media:slave"
+o.rmempty = true
+o.default = default_config.volume or nil
+
+local d_publish = s:option(DynamicList, "publish",
+ translate("Exposed Ports(-p)"),
+ translate("Publish container's port(s) to the host"))
+d_publish.placeholder = "2200:22/tcp"
+d_publish.rmempty = true
+d_publish.default = default_config.publish or nil
+
+o = s:option(Value, "command", translate("Run command"))
+o.placeholder = "/bin/sh init.sh"
+o.rmempty = true
+o.default = default_config.command or nil
+
+o = s:option(Flag, "advance", translate("Advance"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = default_config.advance or 0
+
+o = s:option(Value, "hostname",
+ translate("Host Name"),
+ translate("The hostname to use for the container"))
+o.rmempty = true
+o.default = default_config.hostname or nil
+o:depends("advance", 1)
+
+o = s:option(Flag, "publish_all",
+ translate("Exposed All Ports(-P)"),
+ translate("Allocates an ephemeral host port for all of a container's exposed ports"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = default_config.publish_all and 1 or 0
+o:depends("advance", 1)
+
+o = s:option(DynamicList, "device",
+ translate("Device(--device)"),
+ translate("Add host device to the container"))
+o.placeholder = "/dev/sda:/dev/xvdc:rwm"
+o.rmempty = true
+o:depends("advance", 1)
+o.default = default_config.device or nil
+
+o = s:option(DynamicList, "tmpfs",
+ translate("Tmpfs(--tmpfs)"),
+ translate("Mount tmpfs directory"))
+o.placeholder = "/run:rw,noexec,nosuid,size=65536k"
+o.rmempty = true
+o:depends("advance", 1)
+o.default = default_config.tmpfs or nil
+
+o = s:option(DynamicList, "sysctl",
+ translate("Sysctl(--sysctl)"),
+ translate("Sysctls (kernel parameters) options"))
+o.placeholder = "net.ipv4.ip_forward=1"
+o.rmempty = true
+o:depends("advance", 1)
+o.default = default_config.sysctl or nil
+
+o = s:option(DynamicList, "cap_add",
+ translate("CAP-ADD(--cap-add)"),
+ translate("A list of kernel capabilities to add to the container"))
+o.placeholder = "NET_ADMIN"
+o.rmempty = true
+o:depends("advance", 1)
+o.default = default_config.cap_add or nil
+
+o = s:option(Value, "cpus",
+ translate("CPUs"),
+ translate("Number of CPUs. Number is a fractional number. 0.000 means no limit"))
+o.placeholder = "1.5"
+o.rmempty = true
+o:depends("advance", 1)
+o.datatype="ufloat"
+o.default = default_config.cpus or nil
+
+o = s:option(Value, "cpu_shares",
+ translate("CPU Shares Weight"),
+ translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024"))
+o.placeholder = "1024"
+o.rmempty = true
+o:depends("advance", 1)
+o.datatype="uinteger"
+o.default = default_config.cpu_shares or nil
+
+o = s:option(Value, "memory",
+ translate("Memory"),
+ translate("Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M"))
+o.placeholder = "128m"
+o.rmempty = true
+o:depends("advance", 1)
+o.default = default_config.memory or nil
+
+o = s:option(Value, "blkio_weight",
+ translate("Block IO Weight"),
+ translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
+o.placeholder = "500"
+o.rmempty = true
+o:depends("advance", 1)
+o.datatype="uinteger"
+o.default = default_config.blkio_weight or nil
+
+o = s:option(DynamicList, "log_opt",
+ translate("Log driver options"),
+ translate("The logging configuration for this container"))
+o.placeholder = "max-size=1m"
+o.rmempty = true
+o:depends("advance", 1)
+o.default = default_config.log_opt or nil
+
+for _, v in ipairs (networks) do
+ if v.Name then
+ local parent = v.Options and v.Options.parent or nil
+ local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
+ ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
+ local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
+ d_network:value(v.Name, network_name)
+
+ if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then
+ d_ip:depends("network", v.Name)
+ end
+
+ if v.Driver == "bridge" then
+ d_publish:depends("network", v.Name)
+ end
+ end
+end
+
+m.handle = function(self, state, data)
+ if state ~= FORM_VALID then
+ return
+ end
+
+ local tmp
+ local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
+ local hostname = data.hostname
+ local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false
+ local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false
+ local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false
+ local image = data.image
+ local user = data.user
+
+ if image and not image:match(".-:.+") then
+ image = image .. ":latest"
+ end
+
+ local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false
+ local restart = data.restart
+ local env = data.env
+ local dns = data.dns
+ local cap_add = data.cap_add
+ local sysctl = {}
+
+ tmp = data.sysctl
+ if type(tmp) == "table" then
+ for i, v in ipairs(tmp) do
+ local k,v1 = v:match("(.-)=(.+)")
+ if k and v1 then
+ sysctl[k]=v1
+ end
+ end
+ end
+
+ local log_opt = {}
+ tmp = data.log_opt
+ if type(tmp) == "table" then
+ for i, v in ipairs(tmp) do
+ local k,v1 = v:match("(.-)=(.+)")
+ if k and v1 then
+ log_opt[k]=v1
+ end
+ end
+ end
+
+ local network = data.network
+ local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
+ local volume = data.volume
+ local memory = data.memory or nil
+ local cpu_shares = data.cpu_shares or nil
+ local cpus = data.cpus or nil
+ local blkio_weight = data.blkio_weight or nil
+
+ local portbindings = {}
+ local exposedports = {}
+
+ local tmpfs = {}
+ tmp = data.tmpfs
+ if type(tmp) == "table" then
+ for i, v in ipairs(tmp)do
+ local k= v:match("([^:]+)")
+ local v1 = v:match(".-:([^:]+)") or ""
+ if k then
+ tmpfs[k]=v1
+ end
+ end
+ end
+
+ local device = {}
+ tmp = data.device
+ if type(tmp) == "table" then
+ for i, v in ipairs(tmp) do
+ local t = {}
+ local _,_, h, c, p = v:find("(.-):(.-):(.+)")
+ if h and c then
+ t['PathOnHost'] = h
+ t['PathInContainer'] = c
+ t['CgroupPermissions'] = p or "rwm"
+ else
+ local _,_, h, c = v:find("(.-):(.+)")
+ if h and c then
+ t['PathOnHost'] = h
+ t['PathInContainer'] = c
+ t['CgroupPermissions'] = "rwm"
+ else
+ t['PathOnHost'] = v
+ t['PathInContainer'] = v
+ t['CgroupPermissions'] = "rwm"
+ end
+ end
+
+ if next(t) ~= nil then
+ table.insert( device, t )
+ end
+ end
+ end
+
+ tmp = data.publish or {}
+ for i, v in ipairs(tmp) do
+ for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do
+ local _,_,p= v2:find("^%d+/(%w+)")
+ if p == nil then
+ v2=v2..'/tcp'
+ end
+ portbindings[v2] = {{HostPort=v1}}
+ exposedports[v2] = {HostPort=v1}
+ end
+ end
+
+ local link = data.link
+ tmp = data.command
+ local command = {}
+ if tmp ~= nil then
+ for v in string.gmatch(tmp, "[^%s]+") do
+ command[#command+1] = v
+ end
+ end
+
+ if memory and memory ~= 0 then
+ _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
+ if n then
+ unit = unit and unit:sub(1,1):upper() or "B"
+ if unit == "M" then
+ memory = tonumber(n) * 1024 * 1024
+ elseif unit == "G" then
+ memory = tonumber(n) * 1024 * 1024 * 1024
+ elseif unit == "K" then
+ memory = tonumber(n) * 1024
+ else
+ memory = tonumber(n)
+ end
+ end
+ end
+
+ create_body.Hostname = network ~= "host" and (hostname or name) or nil
+ create_body.Tty = tty and true or false
+ create_body.OpenStdin = interactive and true or false
+ create_body.User = user
+ create_body.Cmd = command
+ create_body.Env = env
+ create_body.Image = image
+ create_body.ExposedPorts = exposedports
+ create_body.HostConfig = create_body.HostConfig or {}
+ create_body.HostConfig.Dns = dns
+ create_body.HostConfig.Binds = volume
+ create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
+ create_body.HostConfig.Privileged = privileged and true or false
+ create_body.HostConfig.PortBindings = portbindings
+ create_body.HostConfig.Memory = memory and tonumber(memory)
+ create_body.HostConfig.CpuShares = cpu_shares and tonumber(cpu_shares)
+ create_body.HostConfig.NanoCPUs = cpus and tonumber(cpus) * 10 ^ 9
+ create_body.HostConfig.BlkioWeight = blkio_weight and tonumber(blkio_weight)
+ create_body.HostConfig.PublishAllPorts = publish_all
+
+ if create_body.HostConfig.NetworkMode ~= network then
+ create_body.NetworkingConfig = nil
+ end
+
+ create_body.HostConfig.NetworkMode = network
+
+ if ip then
+ if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then
+ for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
+ if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
+ v.IPAMConfig.IPv4Address = ip
+ else
+ create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } }
+ end
+ break
+ end
+ else
+ create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
+ end
+ elseif not create_body.NetworkingConfig then
+ create_body.NetworkingConfig = nil
+ end
+
+ create_body["HostConfig"]["Tmpfs"] = tmpfs
+ create_body["HostConfig"]["Devices"] = device
+ create_body["HostConfig"]["Sysctls"] = sysctl
+ create_body["HostConfig"]["CapAdd"] = cap_add
+ create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil
+
+ if network == "bridge" then
+ create_body["HostConfig"]["Links"] = link
+ end
+
+ local pull_image = function(image)
+ local json_stringify = luci.jsonc and luci.jsonc.stringify
+ docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
+ local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb)
+ if res and res.code and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then
+ docker:append_status("done\n")
+ else
+ res.code = (res.code == 200) and 500 or res.code
+ docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
+ end
+ end
+
+ docker:clear_status()
+ local exist_image = false
+
+ if image then
+ for _, v in ipairs (images) do
+ if v.RepoTags and v.RepoTags[1] == image then
+ exist_image = true
+ break
+ end
+ end
+ if not exist_image then
+ pull_image(image)
+ elseif data._force_pull == 1 then
+ pull_image(image)
+ end
+ end
+
+ create_body = docker.clear_empty_tables(create_body)
+
+ docker:append_status("Container: " .. "create" .. " " .. name .. "...")
+ local res = dk.containers:create({name = name, body = create_body})
+ if res and res.code and res.code == 201 then
+ docker:clear_status()
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
+ else
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
+ end
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua
new file mode 100644
index 000000000..c87678b85
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua
@@ -0,0 +1,258 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+
+local m, s, o
+
+local dk = docker.new()
+if dk:_ping().code ~= 200 then
+ lost_state = true
+end
+
+m = SimpleForm("docker", translate("Docker - Network"))
+m.redirect = luci.dispatcher.build_url("admin", "docker", "networks")
+if lost_state then
+ m.submit=false
+ m.reset=false
+end
+
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err=docker:read_status()
+s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(SimpleSection, translate("Create new docker network"))
+s.addremove = true
+s.anonymous = true
+
+o = s:option(Value, "name",
+ translate("Network Name"),
+ translate("Name of the network that can be selected during container creation"))
+o.rmempty = true
+
+o = s:option(ListValue, "driver", translate("Driver"))
+o.rmempty = true
+o:value("bridge", translate("Bridge device"))
+o:value("macvlan", translate("MAC VLAN"))
+o:value("ipvlan", translate("IP VLAN"))
+o:value("overlay", translate("Overlay network"))
+
+o = s:option(Value, "parent", translate("Base device"))
+o.rmempty = true
+o:depends("driver", "macvlan")
+local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
+for _, v in ipairs(interfaces) do
+ o:value(v, v)
+end
+o.default="br-lan"
+o.placeholder="br-lan"
+
+o = s:option(ListValue, "macvlan_mode", translate("Mode"))
+o.rmempty = true
+o:depends("driver", "macvlan")
+o.default="bridge"
+o:value("bridge", translate("Bridge (Support direct communication between MAC VLANs)"))
+o:value("private", translate("Private (Prevent communication between MAC VLANs)"))
+o:value("vepa", translate("VEPA (Virtual Ethernet Port Aggregator)"))
+o:value("passthru", translate("Pass-through (Mirror physical device to single MAC VLAN)"))
+
+o = s:option(ListValue, "ipvlan_mode", translate("Ipvlan Mode"))
+o.rmempty = true
+o:depends("driver", "ipvlan")
+o.default="l3"
+o:value("l2", translate("L2 bridge"))
+o:value("l3", translate("L3 bridge"))
+
+o = s:option(Flag, "ingress",
+ translate("Ingress"),
+ translate("Ingress network is the network which provides the routing-mesh in swarm mode"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+o:depends("driver", "overlay")
+
+o = s:option(DynamicList, "options", translate("Options"))
+o.rmempty = true
+o.placeholder="com.docker.network.driver.mtu=1500"
+
+o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
+o.rmempty = true
+o:depends("driver", "overlay")
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+
+if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then
+ o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt"))
+ o:depends("driver", "macvlan")
+ o.disabled = 0
+ o.enabled = 1
+ o.default = 1
+end
+
+o = s:option(Value, "subnet", translate("Subnet"))
+o.rmempty = true
+o.placeholder="10.1.0.0/16"
+o.datatype="ip4addr"
+
+o = s:option(Value, "gateway", translate("Gateway"))
+o.rmempty = true
+o.placeholder="10.1.1.1"
+o.datatype="ip4addr"
+
+o = s:option(Value, "ip_range", translate("IP range"))
+o.rmempty = true
+o.placeholder="10.1.1.0/24"
+o.datatype="ip4addr"
+
+o = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
+o.rmempty = true
+o.placeholder="my-route=10.1.1.1"
+
+o = s:option(Flag, "ipv6", translate("Enable IPv6"))
+o.rmempty = true
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+
+o = s:option(Value, "subnet6", translate("IPv6 Subnet"))
+o.rmempty = true
+o.placeholder="fe80::/10"
+o.datatype="ip6addr"
+o:depends("ipv6", 1)
+
+o = s:option(Value, "gateway6", translate("IPv6 Gateway"))
+o.rmempty = true
+o.placeholder="fe80::1"
+o.datatype="ip6addr"
+o:depends("ipv6", 1)
+
+m.handle = function(self, state, data)
+ if state == FORM_VALID then
+ local name = data.name
+ local driver = data.driver
+
+ local internal = data.internal == 1 and true or false
+
+ local subnet = data.subnet
+ local gateway = data.gateway
+ local ip_range = data.ip_range
+
+ local aux_address = {}
+ local tmp = data.aux_address or {}
+ for i,v in ipairs(tmp) do
+ _,_,k1,v1 = v:find("(.-)=(.+)")
+ aux_address[k1] = v1
+ end
+
+ local options = {}
+ tmp = data.options or {}
+ for i,v in ipairs(tmp) do
+ _,_,k1,v1 = v:find("(.-)=(.+)")
+ options[k1] = v1
+ end
+
+ local ipv6 = data.ipv6 == 1 and true or false
+
+ local create_body = {
+ Name = name,
+ Driver = driver,
+ EnableIPv6 = ipv6,
+ IPAM = {
+ Driver= "default"
+ },
+ Internal = internal
+ }
+
+ if subnet or gateway or ip_range then
+ create_body["IPAM"]["Config"] = {
+ {
+ Subnet = subnet,
+ Gateway = gateway,
+ IPRange = ip_range,
+ AuxAddress = aux_address,
+ AuxiliaryAddresses = aux_address
+ }
+ }
+ end
+
+ if driver == "macvlan" then
+ create_body["Options"] = {
+ macvlan_mode = data.macvlan_mode,
+ parent = data.parent
+ }
+ elseif driver == "ipvlan" then
+ create_body["Options"] = {
+ ipvlan_mode = data.ipvlan_mode
+ }
+ elseif driver == "overlay" then
+ create_body["Ingress"] = data.ingerss == 1 and true or false
+ end
+
+ if ipv6 and data.subnet6 and data.subnet6 then
+ if type(create_body["IPAM"]["Config"]) ~= "table" then
+ create_body["IPAM"]["Config"] = {}
+ end
+ local index = #create_body["IPAM"]["Config"]
+ create_body["IPAM"]["Config"][index+1] = {
+ Subnet = data.subnet6,
+ Gateway = data.gateway6
+ }
+ end
+
+ if next(options) ~= nil then
+ create_body["Options"] = create_body["Options"] or {}
+ for k, v in pairs(options) do
+ create_body["Options"][k] = v
+ end
+ end
+
+ create_body = docker.clear_empty_tables(create_body)
+ docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
+
+ local res = dk.networks:create({
+ body = create_body
+ })
+
+ if res and res.code == 201 then
+ docker:write_status("Network: " .. "create macvlan interface...")
+ res = dk.networks:inspect({
+ name = create_body.Name
+ })
+
+ if driver == "macvlan" and
+ data.op_macvlan ~= 0 and
+ res and
+ res.code and
+ res.code == 200 and
+ res.body and
+ res.body.IPAM and
+ res.body.IPAM.Config and
+ res.body.IPAM.Config[1] and
+ res.body.IPAM.Config[1].Gateway and
+ res.body.IPAM.Config[1].Subnet then
+
+ docker.create_macvlan_interface(data.name,
+ data.parent,
+ res.body.IPAM.Config[1].Gateway,
+ res.body.IPAM.Config[1].Subnet)
+ end
+
+ docker:clear_status()
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
+ else
+ docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
+ end
+ end
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua
new file mode 100644
index 000000000..c91f349ce
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua
@@ -0,0 +1,151 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+local uci = (require "luci.model.uci").cursor()
+
+local m, s, o, lost_state
+local dk = docker.new()
+
+if dk:_ping().code ~= 200 then
+ lost_state = true
+end
+
+m = SimpleForm("dockerd",
+ translate("Docker - Overview"),
+ translate("An overview with the relevant data is displayed here with which the LuCI docker client is connected.")
+..
+ " " ..
+ [[]] ..
+ translate("Github") ..
+ [[]])
+m.submit=false
+m.reset=false
+
+local docker_info_table = {}
+-- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'}
+-- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'}
+-- docker_info_table['2KernelVersion'] = {_key=translate("Kernel Version"),_value='-'}
+docker_info_table['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'}
+docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'}
+docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'}
+docker_info_table['6MemTotal'] = {_key=translate("Total Memory"),_value='-'}
+docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value='-'}
+docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'}
+docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'}
+
+if nixio.fs.access("/usr/bin/dockerd") and not uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ s = m:section(SimpleSection)
+ s.template = "dockerman/apply_widget"
+ s.err=docker:read_status()
+ s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ")
+ if s.err then
+ docker:clear_status()
+ end
+ s = m:section(Table,{{}})
+ s.notitle=true
+ s.rowcolors=false
+ s.template = "cbi/nullsection"
+
+ o = s:option(Button, "_start")
+ o.template = "dockerman/cbi/inlinebutton"
+ o.inputtitle = lost_state and translate("Start") or translate("Stop")
+ o.inputstyle = lost_state and "add" or "remove"
+ o.forcewrite = true
+ o.write = function(self, section)
+ docker:clear_status()
+
+ if lost_state then
+ docker:append_status("Docker daemon: starting")
+ luci.util.exec("/etc/init.d/dockerd start")
+ luci.util.exec("sleep 5")
+ luci.util.exec("/etc/init.d/dockerman start")
+
+ else
+ docker:append_status("Docker daemon: stopping")
+ luci.util.exec("/etc/init.d/dockerd stop")
+ end
+ docker:clear_status()
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/overview"))
+ end
+
+ o = s:option(Button, "_restart")
+ o.template = "dockerman/cbi/inlinebutton"
+ o.inputtitle = translate("Restart")
+ o.inputstyle = "reload"
+ o.forcewrite = true
+ o.write = function(self, section)
+ docker:clear_status()
+ docker:append_status("Docker daemon: restarting")
+ luci.util.exec("/etc/init.d/dockerd restart")
+ luci.util.exec("sleep 5")
+ luci.util.exec("/etc/init.d/dockerman start")
+ docker:clear_status()
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/overview"))
+ end
+end
+
+s = m:section(Table, docker_info_table)
+s:option(DummyValue, "_key", translate("Info"))
+s:option(DummyValue, "_value")
+
+s = m:section(SimpleSection)
+s.template = "dockerman/overview"
+
+s.containers_running = '-'
+s.images_used = '-'
+s.containers_total = '-'
+s.images_total = '-'
+s.networks_total = '-'
+s.volumes_total = '-'
+
+-- local socket = luci.model.uci.cursor():get("dockerd", "dockerman", "socket_path")
+if not lost_state then
+ local containers_list = dk.containers:list({query = {all=true}}).body
+ local images_list = dk.images:list().body
+ local vol = dk.volumes:list()
+ local volumes_list = vol and vol.body and vol.body.Volumes or {}
+ local networks_list = dk.networks:list().body or {}
+ local docker_info = dk:info()
+
+ -- docker_info_table['0OperatingSystem']._value = docker_info.body.OperatingSystem
+ -- docker_info_table['1Architecture']._value = docker_info.body.Architecture
+ -- docker_info_table['2KernelVersion']._value = docker_info.body.KernelVersion
+ docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion
+ docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"]
+ docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU)
+ docker_info_table['6MemTotal']._value = docker.byte_format(docker_info.body.MemTotal)
+ if docker_info.body.DockerRootDir then
+ local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir)
+ local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0
+ docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(docker.byte_format(size)) .. " " .. translate("Available") .. ")"
+ end
+
+ docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
+ for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
+ docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v)
+ end
+
+ s.images_used = 0
+ for i, v in ipairs(images_list) do
+ for ci,cv in ipairs(containers_list) do
+ if v.Id == cv.ImageID then
+ s.images_used = s.images_used + 1
+ break
+ end
+ end
+ end
+
+ s.containers_running = tostring(docker_info.body.ContainersRunning)
+ s.images_used = tostring(s.images_used)
+ s.containers_total = tostring(docker_info.body.Containers)
+ s.images_total = tostring(#images_list)
+ s.networks_total = tostring(#networks_list)
+ s.volumes_total = tostring(#volumes_list)
+else
+ docker_info_table['3ServerVersion']._value = translate("Can NOT connect to docker daemon, please check!!")
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua
new file mode 100644
index 000000000..6c7064c41
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua
@@ -0,0 +1,143 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.model.docker"
+local dk = docker.new()
+
+local m, s, o
+
+local res, containers, volumes, lost_state
+
+function get_volumes()
+ local data = {}
+ for i, v in ipairs(volumes) do
+ local index = v.Name
+ data[index]={}
+ data[index]["_selected"] = 0
+ data[index]["_nameraw"] = v.Name
+ data[index]["_name"] = v.Name:sub(1,12)
+
+ for ci,cv in ipairs(containers) do
+ if cv.Mounts and type(cv.Mounts) ~= "table" then
+ break
+ end
+ for vi, vv in ipairs(cv.Mounts) do
+ if v.Name == vv.Name then
+ data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
+ ''.. cv.Names[1]:sub(2)..''
+ end
+ end
+ end
+ data[index]["_driver"] = v.Driver
+ data[index]["_mountpoint"] = nil
+
+ for v1 in v.Mountpoint:gmatch('[^/]+') do
+ if v1 == index then
+ data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..."
+ else
+ data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1
+ end
+ end
+ data[index]["_created"] = v.CreatedAt
+ data[index]["_size"] = "-"
+ end
+
+ return data
+end
+
+if dk:_ping().code ~= 200 then
+ lost_state = true
+else
+ res = dk.volumes:list()
+ if res and res.code and res.code <300 then
+ volumes = res.body.Volumes
+ end
+
+ res = dk.containers:list({
+ query = {
+ all=true
+ }
+ })
+ if res and res.code and res.code <300 then
+ containers = res.body
+ end
+end
+
+local volume_list = not lost_state and get_volumes() or {}
+
+m = SimpleForm("docker", translate("Docker - Volumes"))
+m.submit=false
+m.reset=false
+m:append(Template("dockerman/volume_size"))
+
+s = m:section(Table, volume_list, translate("Volumes overview"))
+
+o = s:option(Flag, "_selected","")
+o.disabled = 0
+o.enabled = 1
+o.default = 0
+o.write = function(self, section, value)
+ volume_list[section]._selected = value
+end
+
+o = s:option(DummyValue, "_name", translate("Name"))
+o = s:option(DummyValue, "_driver", translate("Driver"))
+o = s:option(DummyValue, "_containers", translate("Containers"))
+o.rawhtml = true
+o = s:option(DummyValue, "_mountpoint", translate("Mount Point"))
+o = s:option(DummyValue, "_size", translate("Size"))
+o.rawhtml = true
+o = s:option(DummyValue, "_created", translate("Created"))
+
+s = m:section(SimpleSection)
+s.template = "dockerman/apply_widget"
+s.err=docker:read_status()
+s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ")
+if s.err then
+ docker:clear_status()
+end
+
+s = m:section(Table,{{}})
+s.notitle=true
+s.rowcolors=false
+s.template="cbi/nullsection"
+
+o = s:option(Button, "remove")
+o.inputtitle= translate("Remove")
+o.template = "dockerman/cbi/inlinebutton"
+o.inputstyle = "remove"
+o.forcewrite = true
+o.disable = lost_state
+o.write = function(self, section)
+ local volume_selected = {}
+
+ for k in pairs(volume_list) do
+ if volume_list[k]._selected == 1 then
+ volume_selected[#volume_selected+1] = k
+ end
+ end
+
+ if next(volume_selected) ~= nil then
+ local success = true
+ docker:clear_status()
+ for _,vol in ipairs(volume_selected) do
+ docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
+ local msg = dk.volumes["remove"](dk, {id = vol})
+ if msg and msg.code and msg.code ~= 204 then
+ docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
+ success = false
+ else
+ docker:append_status("done\n")
+ end
+ end
+
+ if success then
+ docker:clear_status()
+ end
+ luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
+ end
+end
+
+return m
diff --git a/luci-app-dockerman/luasrc/model/docker.lua b/luci-app-dockerman/luasrc/model/docker.lua
new file mode 100644
index 000000000..bf8fc6254
--- /dev/null
+++ b/luci-app-dockerman/luasrc/model/docker.lua
@@ -0,0 +1,504 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+
+local docker = require "luci.docker"
+local fs = require "nixio.fs"
+local uci = (require "luci.model.uci").cursor()
+
+local _docker = {}
+_docker.options = {}
+
+--pull image and return iamge id
+local update_image = function(self, image_name)
+ local json_stringify = luci.jsonc and luci.jsonc.stringify
+ _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
+ local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb)
+
+ if res and res.code and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then
+ _docker:append_status("done\n")
+ else
+ res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
+ end
+
+ new_image_id = self.images:inspect({name = image_name}).body.Id
+ return new_image_id, res
+end
+
+local table_equal = function(t1, t2)
+ if not t1 then
+ return true
+ end
+
+ if not t2 then
+ return false
+ end
+
+ if #t1 ~= #t2 then
+ return false
+ end
+
+ for i, v in ipairs(t1) do
+ if t1[i] ~= t2[i] then
+ return false
+ end
+ end
+
+ return true
+end
+
+local table_subtract = function(t1, t2)
+ if not t1 or next(t1) == nil then
+ return nil
+ end
+
+ if not t2 or next(t2) == nil then
+ return t1
+ end
+
+ local res = {}
+ for _, v1 in ipairs(t1) do
+ local found = false
+ for _, v2 in ipairs(t2) do
+ if v1 == v2 then
+ found= true
+ break
+ end
+ end
+ if not found then
+ table.insert(res, v1)
+ end
+ end
+
+ return next(res) == nil and nil or res
+end
+
+local map_subtract = function(t1, t2)
+ if not t1 or next(t1) == nil then
+ return nil
+ end
+
+ if not t2 or next(t2) == nil then
+ return t1
+ end
+
+ local res = {}
+ for k1, v1 in pairs(t1) do
+ local found = false
+ for k2, v2 in ipairs(t2) do
+ if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then
+ found= true
+ break
+ end
+ end
+
+ if not found then
+ res[k1] = v1
+ end
+ end
+
+ return next(res) ~= nil and res or nil
+end
+
+_docker.clear_empty_tables = function ( t )
+ local k, v
+
+ if next(t) == nil then
+ t = nil
+ else
+ for k, v in pairs(t) do
+ if type(v) == 'table' then
+ t[k] = _docker.clear_empty_tables(v)
+ end
+ end
+ end
+
+ return t
+end
+
+local get_config = function(container_config, image_config)
+ local config = container_config.Config
+ local old_host_config = container_config.HostConfig
+ local old_network_setting = container_config.NetworkSettings.Networks or {}
+
+ if config.WorkingDir == image_config.WorkingDir then
+ config.WorkingDir = ""
+ end
+
+ if config.User == image_config.User then
+ config.User = ""
+ end
+
+ if table_equal(config.Cmd, image_config.Cmd) then
+ config.Cmd = nil
+ end
+
+ if table_equal(config.Entrypoint, image_config.Entrypoint) then
+ config.Entrypoint = nil
+ end
+
+ if table_equal(config.ExposedPorts, image_config.ExposedPorts) then
+ config.ExposedPorts = nil
+ end
+
+ config.Env = table_subtract(config.Env, image_config.Env)
+ config.Labels = table_subtract(config.Labels, image_config.Labels)
+ config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
+
+ if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
+ config.ExposedPorts = {}
+ for p, v in pairs(old_host_config.PortBindings) do
+ config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
+ end
+ end
+
+ local network_setting = {}
+ local multi_network = false
+ local extra_network = {}
+
+ for k, v in pairs(old_network_setting) do
+ if multi_network then
+ extra_network[k] = v
+ else
+ network_setting[k] = v
+ end
+ multi_network = true
+ end
+
+ local host_config = old_host_config
+ host_config.Mounts = {}
+ for i, v in ipairs(container_config.Mounts) do
+ if v.Type == "volume" then
+ table.insert(host_config.Mounts, {
+ Type = v.Type,
+ Target = v.Destination,
+ Source = v.Source:match("([^/]+)\/_data"),
+ BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil,
+ ReadOnly = not v.RW
+ })
+ end
+ end
+
+ local create_body = config
+ create_body["HostConfig"] = host_config
+ create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
+ create_body = _docker.clear_empty_tables(create_body) or {}
+ extra_network = _docker.clear_empty_tables(extra_network) or {}
+
+ return create_body, extra_network
+end
+
+local upgrade = function(self, request)
+ _docker:clear_status()
+
+ local container_info = self.containers:inspect({id = request.id})
+
+ if container_info.code > 300 and type(container_info.body) == "table" then
+ return container_info
+ end
+
+ local image_name = container_info.body.Config.Image
+ if not image_name:match(".-:.+") then
+ image_name = image_name .. ":latest"
+ end
+
+ local old_image_id = container_info.body.Image
+ local container_name = container_info.body.Name:sub(2)
+
+ local image_id, res = update_image(self, image_name)
+ if res and res.code and res.code ~= 200 then
+ return res
+ end
+
+ if image_id == old_image_id then
+ return {code = 305, body = {message = "Already up to date"}}
+ end
+
+ local t = os.date("%Y%m%d%H%M%S")
+ _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old_".. t .. "...")
+ res = self.containers:rename({name = container_name, query = { name = container_name .. "_old_" ..t }})
+ if res and res.code and res.code < 300 then
+ _docker:append_status("done\n")
+ else
+ return res
+ end
+
+ local image_config = self.images:inspect({id = old_image_id}).body.Config
+ local create_body, extra_network = get_config(container_info.body, image_config)
+
+ -- create new container
+ _docker:append_status("Container: Create" .. " " .. container_name .. "...")
+ create_body = _docker.clear_empty_tables(create_body)
+ res = self.containers:create({name = container_name, body = create_body})
+ if res and res.code and res.code > 300 then
+ return res
+ end
+ _docker:append_status("done\n")
+
+ -- extra networks need to network connect action
+ for k, v in pairs(extra_network) do
+ _docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
+ res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
+ if res and res.code and res.code > 300 then
+ return res
+ end
+ _docker:append_status("done\n")
+ end
+
+ _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "_old_".. t .. "...")
+ res = self.containers:stop({name = container_name .. "_old_" ..t })
+ if res and res.code and res.code < 305 then
+ _docker:append_status("done\n")
+ else
+ return res
+ end
+
+ _docker:append_status("Container: " .. "Start" .. " " .. container_name .. "...")
+ res = self.containers:start({name = container_name})
+ if res and res.code and res.code < 305 then
+ _docker:append_status("done\n")
+ else
+ return res
+ end
+
+ _docker:clear_status()
+ return res
+end
+
+local duplicate_config = function (self, request)
+ local container_info = self.containers:inspect({id = request.id})
+ if container_info.code > 300 and type(container_info.body) == "table" then
+ return nil
+ end
+
+ local old_image_id = container_info.body.Image
+ local image_config = self.images:inspect({id = old_image_id}).body.Config
+
+ return get_config(container_info.body, image_config)
+end
+
+_docker.new = function()
+ local host = nil
+ local port = nil
+ local socket_path = nil
+ local debug_path = nil
+
+ if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ host = uci:get("dockerd", "dockerman", "remote_host") or nil
+ port = uci:get("dockerd", "dockerman", "remote_port") or nil
+ else
+ socket_path = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock"
+ end
+
+ local debug = uci:get_bool("dockerd", "dockerman", "debug")
+ if debug then
+ debug_path = uci:get("dockerd", "dockerman", "debug_path") or "/tmp/.docker_debug"
+ end
+
+ local status_path = uci:get("dockerd", "dockerman", "status_path") or "/tmp/.docker_action_status"
+
+ _docker.options = {
+ host = host,
+ port = port,
+ socket_path = socket_path,
+ debug = debug,
+ debug_path = debug_path,
+ status_path = status_path
+ }
+
+ local _new = docker.new(_docker.options)
+ _new.containers_upgrade = upgrade
+ _new.containers_duplicate_config = duplicate_config
+
+ return _new
+end
+
+_docker.options.status_path = uci:get("dockerd", "dockerman", "status_path") or "/tmp/.docker_action_status"
+
+_docker.append_status=function(self,val)
+ if not val then
+ return
+ end
+ local file_docker_action_status=io.open(self.options.status_path, "a+")
+ file_docker_action_status:write(val)
+ file_docker_action_status:close()
+end
+
+_docker.write_status=function(self,val)
+ if not val then
+ return
+ end
+ local file_docker_action_status=io.open(self.options.status_path, "w+")
+ file_docker_action_status:write(val)
+ file_docker_action_status:close()
+end
+
+_docker.read_status=function(self)
+ return fs.readfile(self.options.status_path)
+end
+
+_docker.clear_status=function(self)
+ fs.remove(self.options.status_path)
+end
+
+local status_cb = function(res, source, handler)
+ res.body = res.body or {}
+ while true do
+ local chunk = source()
+ if chunk then
+ --standard output to res.body
+ table.insert(res.body, chunk)
+ handler(chunk)
+ else
+ return
+ end
+ end
+end
+
+--{"status":"Pulling from library\/debian","id":"latest"}
+--{"status":"Pulling fs layer","progressDetail":[],"id":"50e431f79093"}
+--{"status":"Downloading","progressDetail":{"total":50381971,"current":2029978},"id":"50e431f79093","progress":"[==> ] 2.03MB\/50.38MB"}
+--{"status":"Download complete","progressDetail":[],"id":"50e431f79093"}
+--{"status":"Extracting","progressDetail":{"total":50381971,"current":17301504},"id":"50e431f79093","progress":"[=================> ] 17.3MB\/50.38MB"}
+--{"status":"Pull complete","progressDetail":[],"id":"50e431f79093"}
+--{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"}
+--{"status":"Status: Downloaded newer image for debian:latest"}
+_docker.pull_image_show_status_cb = function(res, source)
+ return status_cb(res, source, function(chunk)
+ local json_parse = luci.jsonc.parse
+ local step = json_parse(chunk)
+ if type(step) == "table" then
+ local buf = _docker:read_status()
+ local num = 0
+ local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
+ if step.id then
+ buf, num = buf:gsub("\t"..step.id .. ": .-\n", str)
+ end
+ if num == 0 then
+ buf = buf .. str
+ end
+ _docker:write_status(buf)
+ end
+ end)
+end
+
+--{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"}
+--{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"}
+--{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
+_docker.import_image_show_status_cb = function(res, source)
+ return status_cb(res, source, function(chunk)
+ local json_parse = luci.jsonc.parse
+ local step = json_parse(chunk)
+ if type(step) == "table" then
+ local buf = _docker:read_status()
+ local num = 0
+ local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
+ if step.status then
+ buf, num = buf:gsub("\t"..step.status .. " .-\n", str)
+ end
+ if num == 0 then
+ buf = buf .. str
+ end
+ _docker:write_status(buf)
+ end
+ end)
+end
+
+_docker.create_macvlan_interface = function(name, device, gateway, subnet)
+ if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
+ return
+ end
+
+ if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ return
+ end
+
+ local ip = require "luci.ip"
+ local if_name = "docker_"..name
+ local dev_name = "macvlan_"..name
+ local net_mask = tostring(ip.new(subnet):mask())
+ local lan_interfaces
+
+ -- add macvlan device
+ uci:delete("network", dev_name)
+ uci:set("network", dev_name, "device")
+ uci:set("network", dev_name, "name", dev_name)
+ uci:set("network", dev_name, "ifname", device)
+ uci:set("network", dev_name, "type", "macvlan")
+ uci:set("network", dev_name, "mode", "bridge")
+
+ -- add macvlan interface
+ uci:delete("network", if_name)
+ uci:set("network", if_name, "interface")
+ uci:set("network", if_name, "proto", "static")
+ uci:set("network", if_name, "ifname", dev_name)
+ uci:set("network", if_name, "ipaddr", gateway)
+ uci:set("network", if_name, "netmask", net_mask)
+ uci:foreach("firewall", "zone", function(s)
+ if s.name == "lan" then
+ local interfaces
+ if type(s.network) == "table" then
+ interfaces = table.concat(s.network, " ")
+ uci:delete("firewall", s[".name"], "network")
+ else
+ interfaces = s.network and s.network or ""
+ end
+ interfaces = interfaces .. " " .. if_name
+ interfaces = interfaces:gsub("%s+", " ")
+ uci:set("firewall", s[".name"], "network", interfaces)
+ end
+ end)
+
+ uci:commit("firewall")
+ uci:commit("network")
+
+ os.execute("ifup " .. if_name)
+end
+
+_docker.remove_macvlan_interface = function(name)
+ if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
+ return
+ end
+
+ if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
+ return
+ end
+
+ local if_name = "docker_"..name
+ local dev_name = "macvlan_"..name
+ uci:foreach("firewall", "zone", function(s)
+ if s.name == "lan" then
+ local interfaces
+ if type(s.network) == "table" then
+ interfaces = table.concat(s.network, " ")
+ else
+ interfaces = s.network and s.network or ""
+ end
+ interfaces = interfaces and interfaces:gsub(if_name, "")
+ interfaces = interfaces and interfaces:gsub("%s+", " ")
+ uci:set("firewall", s[".name"], "network", interfaces)
+ end
+ end)
+
+ uci:delete("network", dev_name)
+ uci:delete("network", if_name)
+ uci:commit("network")
+ uci:commit("firewall")
+
+ os.execute("ip link del " .. if_name)
+end
+
+_docker.byte_format = function (byte)
+ if not byte then return 'NaN' end
+ local suff = {"B", "KB", "MB", "GB", "TB"}
+ for i=1, 5 do
+ if byte > 1024 and i < 5 then
+ byte = byte / 1024
+ else
+ return string.format("%.2f %s", byte, suff[i])
+ end
+ end
+end
+
+return _docker
diff --git a/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm b/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm
new file mode 100644
index 000000000..f96b2d72a
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm
@@ -0,0 +1,147 @@
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm
new file mode 100644
index 000000000..a061a6dba
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm
@@ -0,0 +1,7 @@
+
+ <% if self:cfgvalue(section) ~= false then %>
+ " type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
+ <% else %>
+ -
+ <% end %>
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm
new file mode 100644
index 000000000..e4b0cf7a0
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm
@@ -0,0 +1,33 @@
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm
new file mode 100644
index 000000000..244d2c10a
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm
@@ -0,0 +1,9 @@
+<% if self:cfgvalue(self.section) then section = self.section %>
+
+ <%+cbi/tabmenu%>
+
+ <%+cbi/ucisection%>
+
+
+<% end %>
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm
new file mode 100644
index 000000000..04f7bc2ee
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm
@@ -0,0 +1,10 @@
+<%+cbi/valueheader%>
+ />
+ disabled <% end %><%=
+ attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
+ ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
+ %> />
+
+<%+cbi/valuefooter%>
diff --git a/luci-app-dockerman/luasrc/view/dockerman/container.htm b/luci-app-dockerman/luasrc/view/dockerman/container.htm
new file mode 100644
index 000000000..9f05d9d58
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/container.htm
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/container_console.htm b/luci-app-dockerman/luasrc/view/dockerman/container_console.htm
new file mode 100644
index 000000000..7f626b3dc
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/container_console.htm
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm b/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm
new file mode 100644
index 000000000..af2f6f43f
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm
@@ -0,0 +1,331 @@
+
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm b/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm
new file mode 100644
index 000000000..bbcd633e7
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm
@@ -0,0 +1,81 @@
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/containers_running_stats.htm b/luci-app-dockerman/luasrc/view/dockerman/containers_running_stats.htm
new file mode 100644
index 000000000..d88e28be9
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/containers_running_stats.htm
@@ -0,0 +1,91 @@
+
\ No newline at end of file
diff --git a/luci-app-dockerman/luasrc/view/dockerman/images_import.htm b/luci-app-dockerman/luasrc/view/dockerman/images_import.htm
new file mode 100644
index 000000000..0ad6e0fce
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/images_import.htm
@@ -0,0 +1,104 @@
+
+
+
+ disabled <% end %>/>
+
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/images_load.htm b/luci-app-dockerman/luasrc/view/dockerman/images_load.htm
new file mode 100644
index 000000000..b201510ac
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/images_load.htm
@@ -0,0 +1,40 @@
+
+ disabled <% end %>/>
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/logs.htm b/luci-app-dockerman/luasrc/view/dockerman/logs.htm
new file mode 100644
index 000000000..6cd2cb095
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/logs.htm
@@ -0,0 +1,13 @@
+<% if self.title == "Events" then %>
+<%+header%>
+<%:Docker - Events%>
+
+
<%:Events%>
+<% end %>
+
+
+
+<% if self.title == "Events" then %>
+
+<%+footer%>
+<% end %>
diff --git a/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm b/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm
new file mode 100644
index 000000000..338fd59d5
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm
@@ -0,0 +1,102 @@
+
+
+
+<%+cbi/valueheader%>
+
+
+
+<%+cbi/valuefooter%>
diff --git a/luci-app-dockerman/luasrc/view/dockerman/overview.htm b/luci-app-dockerman/luasrc/view/dockerman/overview.htm
new file mode 100644
index 000000000..e491fc512
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/overview.htm
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/volume_size.htm b/luci-app-dockerman/luasrc/view/dockerman/volume_size.htm
new file mode 100644
index 000000000..dc024734b
--- /dev/null
+++ b/luci-app-dockerman/luasrc/view/dockerman/volume_size.htm
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/luci-app-dockerman/po/zh-cn/dockerman.po b/luci-app-dockerman/po/zh-cn/dockerman.po
new file mode 100644
index 000000000..2bdc11b8d
--- /dev/null
+++ b/luci-app-dockerman/po/zh-cn/dockerman.po
@@ -0,0 +1,1094 @@
+msgid ""
+msgstr ""
+"PO-Revision-Date: 2021-03-19 04:16+0000\n"
+"Last-Translator: Eric \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_Hans\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.5.2-dev\n"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:619
+msgid "A list of kernel capabilities to add to the container"
+msgstr "要添加到容器的内核功能列表"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:69
+msgid "Access Control"
+msgstr "访问控制"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:223
+msgid "Add"
+msgstr "新增"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:595
+msgid "Add host device to the container"
+msgstr "将主机设备添加到容器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:571
+msgid "Advance"
+msgstr "高级选项"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:586
+msgid "Allocates an ephemeral host port for all of a container's exposed ports"
+msgstr "为容器的所有暴露端口分配临时主机端口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:118
+msgid "Allowed access interfaces"
+msgstr "允许的访问接口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:498
+msgid "Always pull image first"
+msgstr "总是先拉取镜像"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:29
+msgid ""
+"An overview with the relevant data is displayed here with which the LuCI "
+"docker client is connected."
+msgstr "在此展示与LuCI docker客户端相连接的相关数据的概览。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:43
+msgid "Api Version"
+msgstr "Api 版本"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:94
+msgid "Auto create macvlan interface in Openwrt"
+msgstr "在 Openwrt 中自动创建 macvlan 界面"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:18
+msgid "Auto start"
+msgstr "自动启动"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:134
+msgid "Available"
+msgstr "可用"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:47
+msgid "Base device"
+msgstr "基设备"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:553
+msgid "Bind Mount(-v)"
+msgstr "绑定挂载(-v)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:554
+msgid "Bind mount a volume"
+msgstr "绑定挂载卷"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:596
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:652
+msgid "Block IO Weight"
+msgstr "块 IO 权重"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:653
+msgid ""
+"Block IO weight (relative weight) accepts a weight value between 10 and 1000"
+msgstr "块 IO 权重(相对权重)接受10到1000之间的数值"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:597
+msgid ""
+"Block IO weight (relative weight) accepts a weight value between 10 and 1000."
+msgstr "块 IO 权重(相对权重)接受10到1000之间的数值。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:61
+msgid "Bridge (Support direct communication between MAC VLANs)"
+msgstr "桥接(支持 MAC VLAN 之间的直接通信)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:42
+msgid "Bridge device"
+msgstr "Bridge device"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:84
+msgid ""
+"By entering a valid image name with the corresponding version, the docker "
+"image can be downloaded from the configured registry."
+msgstr ""
+"通过输入具有相应版本的有效映像名称,可以从镜像存储中心(Registry)中下载"
+"docker映像。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:618
+msgid "CAP-ADD(--cap-add)"
+msgstr "权限控制(--cap-add)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:581
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:635
+msgid "CPU Shares Weight"
+msgstr "CPU 共享权重"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:779
+msgid "CPU Useage"
+msgstr "CPU 使用率"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:636
+msgid ""
+"CPU shares relative weight, if 0 is set, the system will ignore the value "
+"and use the default of 1024"
+msgstr "CPU 共享相对权重,如果设置为 0,则系统将忽略该值并使用默认值 1024"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:582
+msgid ""
+"CPU shares relative weight, if 0 is set, the system will ignore the value "
+"and use the default of 1024."
+msgstr "CPU 共享相对权重,如果设置为 0,则系统将忽略该值并使用默认值 1024。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:573
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:626
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:44
+msgid "CPUs"
+msgstr "线程数量"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:159
+msgid "Can NOT connect to docker daemon, please check!!"
+msgstr "无法连接到docker守护进程(docker daemon),请检查!!"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91
+msgid "Cancel"
+msgstr "取消"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:60
+msgid "Client connection"
+msgstr "客户端连接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:347
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:687
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:182
+msgid "Command"
+msgstr "命令"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:100
+msgid "Command line"
+msgstr "命令行"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:72
+msgid "Command line Error"
+msgstr "命令行错误"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:17
+msgid "Configuration"
+msgstr "配置"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:36
+msgid "Configure the default bridge network"
+msgstr "配置默认桥接网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:405
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:707
+msgid "Connect"
+msgstr "连接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:403
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:437
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:473
+msgid "Connect Network"
+msgstr "连接网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:74
+msgid "Connect to remote docker endpoint"
+msgstr "连接到远程docker"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:7
+msgid "Console"
+msgstr "控制台"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:161
+msgid "Container Info"
+msgstr "容器信息"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:650
+msgid "Container Inspect"
+msgstr "检查容器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:671
+msgid "Container Logs"
+msgstr "容器日志"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:473
+msgid "Container Name"
+msgstr "容器名称"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:92
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:58
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:29
+msgid "Container detail"
+msgstr "容器详情"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:38
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:142
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:148
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:87
+#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:133
+msgid "Containers"
+msgstr "容器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:94
+msgid "Create macvlan interface"
+msgstr "创建 macvlan 接口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:465
+msgid "Create new docker container"
+msgstr "创建 docker 容器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:31
+msgid "Create new docker network"
+msgstr "创建 docker 网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:312
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:153
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:92
+msgid "Created"
+msgstr "创建时间"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:33
+msgid "DELETING"
+msgstr "删除中"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:371
+msgid "DNS"
+msgstr "DNS"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:51
+msgid "Debug"
+msgstr "调试"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:35
+msgid "Default bridge"
+msgstr "默认桥接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:363
+msgid "Device"
+msgstr "设备"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:594
+msgid "Device(--device)"
+msgstr "设备(--device)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:396
+msgid "Disconnect"
+msgstr "断开"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:14
+msgid "Docker"
+msgstr "Docker"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:12
+msgid "Docker - Configuration"
+msgstr "Docker - 配置"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:192
+msgid "Docker - Container (%s)"
+msgstr "Docker - 容器 (%s)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:128
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:450
+msgid "Docker - Containers"
+msgstr "Docker - 容器"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm:3
+msgid "Docker - Events"
+msgstr "Docker - 事件"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:72
+msgid "Docker - Images"
+msgstr "Docker - 镜像"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:15
+msgid "Docker - Network"
+msgstr "Docker - 网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:54
+msgid "Docker - Networks"
+msgstr "Docker - 网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:28
+msgid "Docker - Overview"
+msgstr "Docker - 概览"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:69
+msgid "Docker - Volumes"
+msgstr "Docker - 存储卷"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:16
+msgid "Docker Daemon settings"
+msgstr "Docker 服务端(Docker Daemon)设置"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:489
+msgid "Docker Image"
+msgstr "Docker 镜像"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:30
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:46
+msgid "Docker Root Dir"
+msgstr "Docker 根目录"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:93
+msgid "Docker Socket Path"
+msgstr "Docker 套接字路径"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:42
+msgid "Docker Version"
+msgstr "Docker 版本"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm:91
+msgid "Docker actions done."
+msgstr "Docker 执行完成。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:70
+msgid "DockerMan"
+msgstr "DockerMan"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:13
+msgid "DockerMan is a simple docker manager client for LuCI"
+msgstr "DockerMan是用于LuCI的简单docker管理器客户端"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:68
+msgid "DockerMan settings"
+msgstr "DockerMan设置"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:172
+msgid "Download"
+msgstr "下载"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:82
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:40
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:85
+msgid "Driver"
+msgstr "驱动"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:265
+msgid "Duplicate/Edit"
+msgstr "复制/编辑"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:120
+msgid "Enable IPv6"
+msgstr "启用 IPv6"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:351
+msgid "Env"
+msgstr "环境变量"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:546
+msgid "Environmental Variable(-e)"
+msgstr "环境变量(-e)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:54
+msgid "Error"
+msgstr "错误"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:42
+#: applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm:5
+msgid "Events"
+msgstr "事件"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:116
+msgid "Exclude IPs"
+msgstr "排除 IP"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:247
+msgid "Export"
+msgstr "导出"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:585
+msgid "Exposed All Ports(-P)"
+msgstr "暴露所有端口(-P)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:560
+msgid "Exposed Ports(-p)"
+msgstr "暴露端口(-p)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:55
+msgid "Fatal"
+msgstr "致命的"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:6
+msgid "File"
+msgstr "文件"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:324
+msgid "Finish Time"
+msgstr "完成时间"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:220
+msgid "Force Remove"
+msgstr "强制移除"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:88
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:106
+msgid "Gateway"
+msgstr "网关"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:33
+msgid "Github"
+msgstr "Github"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm:4
+msgid "Go to relevant configuration page"
+msgstr "进入相关配置页面"
+
+#: applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json:3
+msgid "Grant UCI access for luci-app-dockerman"
+msgstr "授予 UCI 访问 luci-app-dockerman 的权限"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:330
+msgid "Healthy"
+msgstr "健康"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:578
+msgid "Host Name"
+msgstr "主机名"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:100
+msgid "Host or IP Address for the connection to a remote docker instance"
+msgstr "连接到远程Docker实例的主机名或IP地址"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:300
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:142
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:78
+msgid "ID"
+msgstr "ID"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:44
+msgid "IP VLAN"
+msgstr "IP VLAN"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:111
+msgid "IP range"
+msgstr "IP 范围"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:522
+msgid "IPv4 Address"
+msgstr "IPv4 地址"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:132
+msgid "IPv6 Gateway"
+msgstr "IPv6 网关"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:126
+msgid "IPv6 Subnet"
+msgstr "IPv6 子网"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:304
+#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54
+msgid "Image"
+msgstr "镜像"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:39
+#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:151
+msgid "Images"
+msgstr "镜像"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:132
+msgid "Images overview"
+msgstr "镜像概览"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:4
+msgid "Import"
+msgstr "导入"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:125
+msgid "Import Image"
+msgstr "导入镜像"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:47
+msgid "Index Server Address"
+msgstr "索引服务器地址"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:52
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:414
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:102
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:3
+msgid "Info"
+msgstr "信息"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:74
+msgid "Ingress"
+msgstr "入口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:75
+msgid ""
+"Ingress network is the network which provides the routing-mesh in swarm mode"
+msgstr "入口网络是以群模式提供路由网格的网络"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:8
+msgid "Inspect"
+msgstr "检查"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:477
+msgid "Interactive (-i)"
+msgstr "交互(-i)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:86
+msgid "Internal"
+msgstr "内部"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:66
+msgid "Ipvlan Mode"
+msgstr "Ipvlan 模式"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:43
+msgid ""
+"It replaces the daemon registry mirrors with a new set of registry mirrors"
+msgstr ""
+"设置新的镜像存储中心(Registry)镜像源,这将取代服务端(daemon)配置的镜像存"
+"储中心(Registry)的镜像源"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:238
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:264
+msgid "Kill"
+msgstr "强制关闭"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:70
+msgid "L2 bridge"
+msgstr "L2 桥接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:71
+msgid "L3 bridge"
+msgstr "L3 桥接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:359
+msgid "Links"
+msgstr "链接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:527
+msgid "Links with other containers"
+msgstr "与其他容器的链接"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:283
+#: applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm:2
+msgid "Load"
+msgstr "负载"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:49
+msgid "Log Level"
+msgstr "日志等级"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:661
+msgid "Log driver options"
+msgstr "日志驱动选项"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:9
+msgid "Logs"
+msgstr "日志"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:43
+msgid "MAC VLAN"
+msgstr "MAC VLAN"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:589
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:644
+msgid "Memory"
+msgstr "内存"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:783
+msgid "Memory Useage"
+msgstr "内存使用率"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:645
+msgid ""
+"Memory limit (format: []). Number is a positive integer. Unit "
+"can be one of b, k, m, or g. Minimum is 4M"
+msgstr ""
+"内存限制(格式:<数字>[<单位>])。数字是正整数。单位可以是 b、k、m 或 g 之一。"
+"最小值为 4M"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:590
+msgid ""
+"Memory limit (format: []). Number is a positive integer. Unit "
+"can be one of b, k, m, or g. Minimum is 4M."
+msgstr ""
+"内存限制(格式:<数字>[<单位>])。数字是正整数。单位可以是 b、k、m 或 g 之一。"
+"最小值为 4M。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:57
+msgid "Mode"
+msgstr "模式"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:90
+msgid "Mount Point"
+msgstr "挂载点"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:603
+msgid "Mount tmpfs directory"
+msgstr "挂载 tmpfs 目录"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:343
+msgid "Mount/Volume"
+msgstr "挂载/卷"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:175
+msgid "Mounts"
+msgstr "挂载点"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:295
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:419
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:83
+msgid "Name"
+msgstr "名称"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:37
+msgid "Name of the network that can be selected during container creation"
+msgstr "在容器创建时可以选择网络的名称"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:394
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:528
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:169
+msgid "Network"
+msgstr "网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:80
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:36
+msgid "Network Name"
+msgstr "网络名称"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:40
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:518
+#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:169
+msgid "Networks"
+msgstr "网络"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:59
+msgid "Networks overview"
+msgstr "网络概览"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:104
+msgid "New"
+msgstr "新建"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:39
+#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54
+msgid "New tag"
+msgstr "新建标签"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:627
+msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit"
+msgstr "CPU 数量。数字是小数。0.000 表示没有限制"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:574
+msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit."
+msgstr "CPU 数量。数字是小数。0.000 表示没有限制。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:73
+msgid ""
+"On this page all images are displayed that are available on the system and "
+"with which a container can be created."
+msgstr "在此页面上,显示系统上可用的所有镜像文件,并可以用它们来创建容器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:193
+msgid "On this page, the selected container can be managed."
+msgstr "在此页面可以管理所选的容器。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:82
+msgid "Options"
+msgstr "选项"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:45
+msgid "Overlay network"
+msgstr "Overlay network"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:37
+msgid "Overview"
+msgstr "概览"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:33
+msgid "PLEASE CONFIRM"
+msgstr "请确认"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:84
+msgid "Parent Interface"
+msgstr "父接口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:64
+msgid "Pass-through (Mirror physical device to single MAC VLAN)"
+msgstr "直通(将物理设备镜像到单独的 MAC VLAN)"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54
+msgid "Please input new tag"
+msgstr "请输入新的标签"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:270
+msgid "Please input the PATH and select the file !"
+msgstr "请输入路径并选择文件!"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:82
+msgid "Please input the PORT or HOST IP of remote docker instance!"
+msgstr "请输入合法的远程docker实例端口和主机IP"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:86
+msgid "Please input the SOCKET PATH of docker daemon!"
+msgstr "请输入合法docker服务端(docker daemon)的SOCKET地址"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91
+msgid "Plese input command line:"
+msgstr "请输入 的命令行:"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:355
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:172
+msgid "Ports"
+msgstr "端口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:124
+msgid "Ports allowed to be accessed"
+msgstr "允许访问的端口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:62
+msgid "Private (Prevent communication between MAC VLANs)"
+msgstr "专用(阻止 MAC VLAN 之间的通信)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:504
+msgid "Privileged"
+msgstr "特权模式"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:561
+msgid "Publish container's port(s) to the host"
+msgstr "将容器的端口发布到主机"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:100
+msgid "Pull"
+msgstr "拉取"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:83
+msgid "Pull Image"
+msgstr "拉取镜像"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:42
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:48
+msgid "Registry Mirrors"
+msgstr "镜像加速器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:73
+msgid "Remote Endpoint"
+msgstr "远程实例"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:99
+msgid "Remote Host"
+msgstr "远程主机"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:106
+msgid "Remote Port"
+msgstr "远程端口"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:274
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:274
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:210
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:115
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:108
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:173
+msgid "Remove"
+msgstr "移除"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:43
+#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:82
+msgid "Remove tag"
+msgstr "移除标签"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:171
+msgid "Rename"
+msgstr "重命名"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:145
+msgid "RepoTags"
+msgstr "仓库标签"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:469
+msgid "Resolve CLI"
+msgstr "解析 CLI"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:4
+msgid "Resources"
+msgstr "资源"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:220
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:244
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:87
+msgid "Restart"
+msgstr "重新启动"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:334
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:427
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:510
+msgid "Restart Policy"
+msgstr "重启策略"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:86
+msgid "Restrict external access to the network"
+msgstr "限制外部网络访问"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm:31
+msgid "Reveal/hide password"
+msgstr "显示/隐藏 密码"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:566
+msgid "Run command"
+msgstr "运行命令"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:230
+msgid "Save"
+msgstr "保存"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:533
+msgid "Set custom DNS servers"
+msgstr "设置自定义 DNS 服务器"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:547
+msgid "Set environment variables to inside the container"
+msgstr "在容器内部设置环境变量"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:50
+msgid "Set the logging level"
+msgstr "设置日志记录级别"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:151
+msgid "Size"
+msgstr "大小"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:61
+msgid ""
+"Specifies where the Docker daemon will listen for client connections "
+"(default: unix:///var/run/docker.sock)"
+msgstr ""
+"指定Docker服务端(Docker daemon)将在何处侦听客户端连接(默认: unix:///var/"
+"run/docker.sock)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:211
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:234
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:65
+msgid "Start"
+msgstr "启动"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:319
+msgid "Start Time"
+msgstr "开始时间"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:789
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:790
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:5
+msgid "Stats"
+msgstr "状态"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:308
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:165
+msgid "Status"
+msgstr "状态"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:229
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:254
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:65
+msgid "Stop"
+msgstr "停止"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91
+msgid "Submit"
+msgstr "提交"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:86
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:101
+msgid "Subnet"
+msgstr "子网"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:375
+msgid "Sysctl"
+msgstr "系统控制"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:610
+msgid "Sysctl(--sysctl)"
+msgstr "系统控制(--sysctl)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:611
+msgid "Sysctls (kernel parameters) options"
+msgstr "系统控制(内核参数)选项"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:792
+msgid "TOP"
+msgstr "TOP"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:483
+msgid "TTY (-t)"
+msgstr "TTY(-t)"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm:56
+msgid "TX/RX"
+msgstr "发射/接收"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:579
+msgid "The hostname to use for the container"
+msgstr "容器使用的主机名"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:662
+msgid "The logging configuration for this container"
+msgstr "该容器的日志记录配置"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:540
+msgid ""
+"The user that commands are run as inside the container.(format: name|uid[:"
+"group|gid])"
+msgstr "在容器中以用户运行命令。(格式:name|uid[:group|gid])"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:129
+msgid ""
+"This page displays all containers that have been created on the connected "
+"docker host."
+msgstr "此页面显示在连接的Docker主机上已创建的所有容器。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:55
+msgid ""
+"This page displays all docker networks that have been created on the "
+"connected docker host."
+msgstr "此页面显示在已连接的Docker主机上创建的所有Docker网络。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:367
+msgid "Tmpfs"
+msgstr "Tmpfs"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:602
+msgid "Tmpfs(--tmpfs)"
+msgstr "Tmpfs(--tmpfs)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:45
+msgid "Total Memory"
+msgstr "总内存"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:697
+msgid "UID"
+msgstr "UID"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:297
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:336
+msgid "Update"
+msgstr "更新"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:256
+msgid "Upgrade"
+msgstr "升级"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:7
+msgid "Upload"
+msgstr "上传"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:303
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:304
+msgid "Upload Error"
+msgstr "上传错误"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:294
+msgid "Upload Success"
+msgstr "上传成功"
+
+#: applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm:48
+msgid "Upload/Download"
+msgstr "上传/下载"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:339
+msgid "User"
+msgstr "用户"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:539
+msgid "User(-u)"
+msgstr "用户(-u)"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:63
+msgid "VEPA (Virtual Ethernet Port Aggregator)"
+msgstr "VEPA(虚拟以太网端口聚合器)"
+
+#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:41
+#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:187
+msgid "Volumes"
+msgstr "存储卷"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:73
+msgid "Volumes overview"
+msgstr "卷概览"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:53
+msgid "Warning"
+msgstr "警告"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:126
+msgid ""
+"When pressing the Import button, both a local image can be loaded onto the "
+"system and a valid image tar can be downloaded from remote."
+msgstr ""
+"按下导入按钮时,既可以将本地镜像文件加载到系统上,也可以从远程下载有效的Tar格"
+"式的镜像文件。"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:124
+msgid ""
+"Which Port(s) can be accessed, it's not restricted by the Allowed Access "
+"interfaces configuration. Use this configuration with caution!"
+msgstr "设置可以被访问的端口,该配置不受“允许的访问接口”配置的限制。请谨慎使用该配置选项!"
+
+#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:118
+msgid ""
+"Which interface(s) can access containers under the bridge network, fill-in "
+"Interface Name"
+msgstr "哪些接口可以访问桥接网络下的容器,请填写接口名称"
+
+#~ msgid "Containers allowed to be accessed"
+#~ msgstr "允许访问的容器"
+
+#~ msgid ""
+#~ "Which container(s) under bridge network can be accessed, even from "
+#~ "interfaces that are not allowed, fill-in Container Id or Name"
+#~ msgstr ""
+#~ "桥接网络下哪些容器可以访问,即使是不允许从接口访问,也要填写容器 ID 或名称"
+
+#~ msgid "Connect to remote endpoint"
+#~ msgstr "连接到远程终端"
+
+#~ msgid "Global settings"
+#~ msgstr "全局设定"
+
+#~ msgid "Path"
+#~ msgstr "路径"
+
+#~ msgid "Please input the PATH !"
+#~ msgstr "请输入合法路径!"
+
+#~ msgid "Setting"
+#~ msgstr "设置"
+
+#~ msgid "Specifies where the Docker daemon will listen for client connections"
+#~ msgstr "指定Docker服务端(Docker daemon)侦听客户端连接的位置"
+
+#~ msgid "Docker Container"
+#~ msgstr "Docker 容器"
+
+#~ msgid ""
+#~ "DockerMan is a Simple Docker manager client for LuCI, If you have any "
+#~ "issue please visit:"
+#~ msgstr ""
+#~ "DockerMan 是一个简单的 LuCI 客户端 Docker 管理器,如果您有任何问题,请访"
+#~ "问:"
+
+#~ msgid "Import Images"
+#~ msgstr "导入镜像"
+
+#~ msgid "New Container"
+#~ msgstr "新建容器"
+
+#~ msgid "New Network"
+#~ msgstr "新建网络"
+
+#~ msgid "Macvlan Mode"
+#~ msgstr "Macvlan 模式"
+
+#~ msgid ""
+#~ "Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts "
+#~ "(tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock"
+#~ msgstr ""
+#~ "守护进程 unix 套接字 (unix:///var/run/docker.sock) 或 TCP 远程主机 "
+#~ "(tcp://0.0.0.0:2375),默认值:unix:///var/run/docker.sock"
+
+#~ msgid "Docker Daemon"
+#~ msgstr "Docker 服务端"
+
+#~ msgid "Dockerman connect to remote endpoint"
+#~ msgstr "Dockerman 连接到远程端点"
+
+#~ msgid "Enable"
+#~ msgstr "启用"
+
+#~ msgid "Server Host"
+#~ msgstr "服务器主机"
+
+#~ msgid "Contaienr Info"
+#~ msgstr "容器信息"
diff --git a/luci-app-dockerman/root/etc/init.d/dockerman b/luci-app-dockerman/root/etc/init.d/dockerman
new file mode 100755
index 000000000..304cf91d0
--- /dev/null
+++ b/luci-app-dockerman/root/etc/init.d/dockerman
@@ -0,0 +1,131 @@
+#!/bin/sh /etc/rc.common
+
+START=99
+USE_PROCD=1
+# PROCD_DEBUG=1
+config_load 'dockerd'
+# config_get daemon_ea "dockerman" daemon_ea
+_DOCKERD=/etc/init.d/dockerd
+
+docker_running(){
+ docker version > /dev/null 2>&1
+ return $?
+}
+
+add_ports() {
+ [ $# -eq 0 ] && return
+ $($_DOCKERD running) && docker_running || return 1
+ ids=$@
+ for id in $ids; do
+ id=$(docker ps --filter "ID=$id" --quiet)
+ [ -z "$id" ] && {
+ echo "Docker containner not running";
+ return 1;
+ }
+ ports=$(docker ps --filter "ID=$id" --format "{{.Ports}}")
+ # echo "$ports"
+ for port in $ports; do
+ echo "$port" | grep -qE "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:.*$" || continue;
+ [ "${port: -1}" == "," ] && port="${port:0:-1}"
+ local protocol=""
+ [ "${port%tcp}" != "$port" ] && protocol="/tcp"
+ [ "${port%udp}" != "$port" ] && protocol="/udp"
+ [ "$protocol" == "" ] && continue
+ port="${port%%->*}"
+ port="${port##*:}"
+ uci_add_list dockerd dockerman ac_allowed_ports "${port}${protocol}"
+ done
+ done
+ uci_commit dockerd
+}
+
+
+convert() {
+ _convert() {
+ _id=$1
+ _id=$(docker ps --all --filter "ID=$_id" --quiet)
+ if [ -z "$_id" ]; then
+ uci_remove_list dockerd dockerman ac_allowed_container "$1"
+ return
+ fi
+ if /etc/init.d/dockerman add_ports "$_id"; then
+ uci_remove_list dockerd dockerman ac_allowed_container "$_id"
+ fi
+ }
+ config_list_foreach dockerman ac_allowed_container _convert
+ uci_commit dockerd
+}
+
+iptables_append(){
+ # Wait for a maximum of 10 second per command, retrying every millisecond
+ local iptables_wait_args="--wait 10 --wait-interval 1000"
+ if ! iptables ${iptables_wait_args} --check $@ 2>/dev/null; then
+ iptables ${iptables_wait_args} -A $@ 2>/dev/null
+ fi
+}
+
+init_dockerman_chain(){
+ iptables -N DOCKER-MAN >/dev/null 2>&1
+ iptables -F DOCKER-MAN >/dev/null 2>&1
+ iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
+ iptables -I DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
+}
+
+delete_dockerman_chain(){
+ iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
+ iptables -F DOCKER-MAN >/dev/null 2>&1
+ iptables -X DOCKER-MAN >/dev/null 2>&1
+}
+
+add_allowed_interface(){
+ iptables_append DOCKER-MAN -i $1 -o docker0 -j RETURN
+}
+
+add_allowed_ports(){
+ port=$1
+ if [ "${port%/tcp}" != "$port" ]; then
+ iptables_append DOCKER-MAN -p tcp -m conntrack --ctorigdstport ${port%/tcp} --ctdir ORIGINAL -j RETURN
+ elif [ "${port%/udp}" != "$port" ]; then
+ iptables_append DOCKER-MAN -p udp -m conntrack --ctorigdstport ${port%/udp} --ctdir ORIGINAL -j RETURN
+ fi
+}
+
+handle_allowed_ports(){
+ config_list_foreach "dockerman" "ac_allowed_ports" add_allowed_ports
+}
+
+handle_allowed_interface(){
+ config_list_foreach "dockerman" "ac_allowed_interface" add_allowed_interface
+ iptables_append DOCKER-MAN -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1
+ iptables_append DOCKER-MAN -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1
+ iptables_append DOCKER-MAN -j RETURN >/dev/null 2>&1
+}
+
+start_service(){
+ [ -x "$_DOCKERD" ] && $($_DOCKERD enabled) || return 0
+ delete_dockerman_chain
+ $($_DOCKERD running) && docker_running || return 0
+ init_dockerman_chain
+ handle_allowed_ports
+ handle_allowed_interface
+}
+
+stop_service(){
+ delete_dockerman_chain
+}
+
+service_triggers() {
+ procd_add_reload_trigger 'dockerd'
+}
+
+reload_service() {
+ start
+}
+
+boot() {
+ sleep 5s
+ start
+}
+
+extra_command "add_ports" "Add allowed ports based on the container ID(s)"
+extra_command "convert" "Convert Ac allowed container to AC allowed ports"
diff --git a/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman b/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman
new file mode 100755
index 000000000..4358728a1
--- /dev/null
+++ b/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+. $IPKG_INSTROOT/lib/functions.sh
+
+[ -x "$(command -v dockerd)" ] && chmod +x /etc/init.d/dockerman && /etc/init.d/dockerman enable >/dev/null 2>&1
+sed -i 's/self:cfgvalue(section) or {}/self:cfgvalue(section) or self.default or {}/' /usr/lib/lua/luci/view/cbi/dynlist.htm
+/etc/init.d/uhttpd restart >/dev/null 2>&1
+rm -fr /tmp/luci-indexcache /tmp/luci-modulecache >/dev/null 2>&1
+touch /etc/config/dockerd
+ls /etc/rc.d/*dockerd &> /dev/null && uci -q set dockerd.globals.auto_start="1" || uci -q set dockerd.globals.auto_start="0"
+uci -q batch <<-EOF >/dev/null
+ set uhttpd.main.script_timeout="3600"
+ commit uhttpd
+ set dockerd.dockerman=dockerman
+ set dockerd.dockerman.socket_path='/var/run/docker.sock'
+ set dockerd.dockerman.status_path='/tmp/.docker_action_status'
+ set dockerd.dockerman.debug='false'
+ set dockerd.dockerman.debug_path='/tmp/.docker_debug'
+ set dockerd.dockerman.remote_endpoint='0'
+
+ del_list dockerd.dockerman.ac_allowed_interface='br-lan'
+ add_list dockerd.dockerman.ac_allowed_interface='br-lan'
+
+ commit dockerd
+EOF
+# remove dockerd firewall
+config_load dockerd
+remove_firewall(){
+ cfg=${1}
+ uci_remove dockerd ${1}
+}
+config_foreach remove_firewall firewall
+# Convert ac_allowed_container to ac_allowed_ports
+(sleep 30s && /etc/init.d/dockerman convert;/etc/init.d/dockerman restart) &
+
+exit 0
diff --git a/luci-app-netdata/Makefile b/luci-app-netdata/Makefile
new file mode 100644
index 000000000..8794d5a4d
--- /dev/null
+++ b/luci-app-netdata/Makefile
@@ -0,0 +1,16 @@
+# Copyright (C) 2016 Openwrt.org
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for Netdata
+LUCI_DEPENDS:=+netdata
+LUCI_PKGARCH:=all
+PKG_VERSION:=1.0
+PKG_RELEASE:=3
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-netdata/luasrc/controller/netdata.lua b/luci-app-netdata/luasrc/controller/netdata.lua
new file mode 100644
index 000000000..09e9943b2
--- /dev/null
+++ b/luci-app-netdata/luasrc/controller/netdata.lua
@@ -0,0 +1,6 @@
+module("luci.controller.netdata", package.seeall)
+
+function index()
+
+ entry({"admin", "status", "netdata"}, template("netdata/netdata"), _("NetData"), 10).leaf = true
+end
diff --git a/luci-app-netdata/luasrc/view/netdata/netdata.htm b/luci-app-netdata/luasrc/view/netdata/netdata.htm
new file mode 100644
index 000000000..a6620e64d
--- /dev/null
+++ b/luci-app-netdata/luasrc/view/netdata/netdata.htm
@@ -0,0 +1,9 @@
+<%+header%>
+
+
<%=translate("NetData")%>
+
+
+
+<%+footer%>
diff --git a/luci-app-netdata/po/zh-cn/netdata.po b/luci-app-netdata/po/zh-cn/netdata.po
new file mode 100644
index 000000000..e8966b815
--- /dev/null
+++ b/luci-app-netdata/po/zh-cn/netdata.po
@@ -0,0 +1,5 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "NetData"
+msgstr "实时监控"
diff --git a/luci-app-samba4/Makefile b/luci-app-samba4/Makefile
new file mode 100644
index 000000000..dae7f8071
--- /dev/null
+++ b/luci-app-samba4/Makefile
@@ -0,0 +1,10 @@
+# This is free software, licensed under the Apache License, Version 2.0 .
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Network Shares - Samba 4 SMB/CIFS module
+LUCI_DEPENDS:=+samba4-server
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-samba4/luasrc/controller/samba4.lua b/luci-app-samba4/luasrc/controller/samba4.lua
new file mode 100644
index 000000000..2de221e98
--- /dev/null
+++ b/luci-app-samba4/luasrc/controller/samba4.lua
@@ -0,0 +1,11 @@
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.samba4", package.seeall)
+
+function index()
+ if not nixio.fs.access("/etc/config/samba4") then
+ return
+ end
+
+ entry({"admin", "services", "samba4"}, cbi("samba4"), _("Network Shares")).dependent = true
+end
diff --git a/luci-app-samba4/luasrc/model/cbi/samba4.lua b/luci-app-samba4/luasrc/model/cbi/samba4.lua
new file mode 100644
index 000000000..2e80096fb
--- /dev/null
+++ b/luci-app-samba4/luasrc/model/cbi/samba4.lua
@@ -0,0 +1,112 @@
+-- Licensed to the public under the Apache License 2.0.
+
+m = Map("samba4", translate("Network Shares"))
+
+s = m:section(TypedSection, "samba", "Samba")
+s.anonymous = true
+
+s:tab("general", translate("General Settings"))
+s:tab("template", translate("Edit Template"))
+
+s:taboption("general", Value, "name", translate("Hostname"))
+s:taboption("general", Value, "description", translate("Description"))
+s:taboption("general", Value, "workgroup", translate("Workgroup"))
+h = s:taboption("general", Flag, "homes", translate("Share home-directories"),
+ translate("Allow system users to reach their home directories via " ..
+ "network shares"))
+h.rmempty = false
+
+macos = s:taboption("general", Flag, "macos", translate("Enable macOS compatible shares"))
+macos.description = translate("Enables Apple's AAPL extension globally and adds macOS compatibility options to all shares.")
+macos.rmempty = false
+
+if nixio.fs.access("/usr/sbin/nmbd") then
+ s:taboption("general", Flag, "disable_netbios", translate("Disable Netbios"))
+end
+if nixio.fs.access("/usr/sbin/samba") then
+ s:taboption("general", Flag, "disable_ad_dc", translate("Disable Active Directory Domain Controller"))
+end
+if nixio.fs.access("/usr/sbin/winbindd") then
+ s:taboption("general", Flag, "disable_winbind", translate("Disable Winbind"))
+end
+
+tmpl = s:taboption("template", Value, "_tmpl",
+ translate("Edit the template that is used for generating the samba configuration."),
+ translate("This is the content of the file '/etc/samba/smb.conf.template' from which your samba configuration will be generated. " ..
+ "Values enclosed by pipe symbols ('|') should not be changed. They get their values from the 'General Settings' tab."))
+
+tmpl.template = "cbi/tvalue"
+tmpl.rows = 20
+
+function tmpl.cfgvalue(self, section)
+ return nixio.fs.readfile("/etc/samba/smb.conf.template")
+end
+
+function tmpl.write(self, section, value)
+ value = value:gsub("\r\n?", "\n")
+ nixio.fs.writefile("/etc/samba/smb.conf.template", value)
+end
+
+
+s = m:section(TypedSection, "sambashare", translate("Shared Directories")
+ , translate("Please add directories to share. Each directory refers to a folder on a mounted device."))
+s.anonymous = true
+s.addremove = true
+s.template = "cbi/tblsection"
+
+s:option(Value, "name", translate("Name"))
+pth = s:option(Value, "path", translate("Path"))
+if nixio.fs.access("/etc/config/fstab") then
+ pth.titleref = luci.dispatcher.build_url("admin", "system", "fstab")
+end
+
+br = s:option(Flag, "browseable", translate("Browse-able"))
+br.enabled = "yes"
+br.disabled = "no"
+br.default = "yes"
+
+ro = s:option(Flag, "read_only", translate("Read-only"))
+ro.enabled = "yes"
+ro.disabled = "no"
+ro.default = "yes"
+
+s:option(Flag, "force_root", translate("Force Root"))
+
+au = s:option(Value, "users", translate("Allowed users"))
+au.rmempty = true
+
+go = s:option(Flag, "guest_ok", translate("Allow guests"))
+go.enabled = "yes"
+go.disabled = "no"
+go.default = "no"
+
+gon = s:option(Flag, "guest_only", translate("Guests only"))
+gon.enabled = "yes"
+gon.disabled = "no"
+gon.default = "no"
+
+iown = s:option(Flag, "inherit_owner", translate("Inherit owner"))
+iown.enabled = "yes"
+iown.disabled = "no"
+iown.default = "no"
+
+cm = s:option(Value, "create_mask", translate("Create mask"))
+cm.rmempty = true
+cm.maxlength = 4
+cm.placeholder = "0666"
+
+dm = s:option(Value, "dir_mask", translate("Directory mask"))
+dm.rmempty = true
+dm.maxlength = 4
+dm.placeholder = "0777"
+
+vfs = s:option(Value, "vfs_objects", translate("Vfs objects"))
+vfs.rmempty = true
+
+s:option(Flag, "timemachine", translate("Apple Time-machine share"))
+
+tms = s:option(Value, "timemachine_maxsize", translate("Time-machine size in GB"))
+tms.rmempty = true
+tms.maxlength = 5
+
+return m
diff --git a/luci-app-samba4/po/ca/samba4.po b/luci-app-samba4/po/ca/samba4.po
new file mode 100644
index 000000000..b8f3301ac
--- /dev/null
+++ b/luci-app-samba4/po/ca/samba4.po
@@ -0,0 +1,170 @@
+# samba.pot
+# generated from ./applications/luci-samba/luasrc/i18n/samba.en.lua
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-10 03:40+0200\n"
+"PO-Revision-Date: 2014-07-01 05:47+0200\n"
+"Last-Translator: Alex \n"
+"Language-Team: LANGUAGE \n"
+"Language: ca\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.6\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Permet convidats"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Permet que els usuaris del sistema pugin arribar als seus directoris d'inici "
+"via comparticions de xarxa"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Usuaris permesos"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Crea màscara"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Descripció"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Màscara de directori"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Edita plantilla"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Edita la plantilla que s'usa per generar la configuració de samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Ajusts generals"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Nom de màquina"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nom"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Comparticions de xarxa"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Ruta"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Només lectura"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Comparteix directoris d'inici"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Directoris compartits"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Això és el contingut del fitxer '/etc/samba/smb.conf.template' del qual la "
+"vostra configuració de samba es generarà. Valors encerclats per símbols de "
+"barra ('|') no es deuen canviar. Reben els seus valors de la pestanya "
+"'Ajusts generals'."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Grup de treball"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Màscara per directoris nous"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Màscara per fitxers nous"
+
+#~ msgid "Shared Directory"
+#~ msgstr "Directori compartit"
+
+#~ msgid "Physical Path"
+#~ msgstr "Ruta física"
+
+#~ msgid "optional"
+#~ msgstr "opcional"
diff --git a/luci-app-samba4/po/cs/samba4.po b/luci-app-samba4/po/cs/samba4.po
new file mode 100644
index 000000000..58913db49
--- /dev/null
+++ b/luci-app-samba4/po/cs/samba4.po
@@ -0,0 +1,158 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2014-05-31 13:56+0200\n"
+"Last-Translator: koli \n"
+"Language-Team: none\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: Pootle 2.0.6\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Povolení hosté"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Povoluje systémovým uživatelům přístup do jejich domácích adresářů skrze "
+"sdílení přes síť."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Povolení uživatelé"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Vytvořit masku"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Popis"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Maska adresáře"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Editovat šablonu"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+"Editovat šablonu, která je použita pro generování konfiguračního souboru pro "
+"sambu."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Obecné nastavení"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Název počítače."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Jméno"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Síťová sdílení"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Cesta"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Pouze pro čtení"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Sdílet domácí adresáře"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Sdílené adresáře"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Toto je obsah souboru \"/etc/samba/smb.conf.template\", ze kterého je "
+"konfigurace samby generována. Hodnoty uzavřené rourou (\"|\"), by se neměly "
+"měnit. Tyto hodnoty jsou brány ze záložky \"Obecná nastavení\"."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Skupina"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Maska pro nové adresáře"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Maska pro nové soubory"
diff --git a/luci-app-samba4/po/de/samba4.po b/luci-app-samba4/po/de/samba4.po
new file mode 100644
index 000000000..4d52f1b40
--- /dev/null
+++ b/luci-app-samba4/po/de/samba4.po
@@ -0,0 +1,165 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-26 17:57+0200\n"
+"PO-Revision-Date: 2011-10-18 13:13+0200\n"
+"Last-Translator: Manuel \n"
+"Language-Team: LANGUAGE \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Gastzugang"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Systembenutzer dürfen ihre Heimatverzeichnis über Netzwerkfreigaben "
+"erreichen."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Legitimierte Benutzer"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr "Durchsuchbar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Berechtigungs-maske für neue Dateien"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Beschreibung"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Verzeichnis-maske"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr "Deaktiviere Active Directory Domain Controller"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr "Deaktiviere Netbios"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr "Deaktiviere Winbind"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Template bearbeiten"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+"Hier kann das Template bearbeitet werden, das zur Erstellung der Samba-"
+"Konfigurationsdateien verwendet wird."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Allgemeine Einstellungen"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr "Nur Gaeste"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Hostname"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr "Besitzer Erben"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Name"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Netzwerk-freigaben"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Pfad"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Nur Lesen"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Heimat-verzeichnisse freigeben"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Freigegebene Verzeichnisse"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Dieses Fenster zeigt den Inhalt der Datei '/etc/samba/smb.conf.template', "
+"die als Template zum Erstellen der Samba-Konfiguration verwendet wird. Werte "
+"die von Pipe Symbolen (|) eingeschlossen sind sollten nicht verändert "
+"werden, da diese beim Erstellen der Konfiguration mit den Werten aus dem Tab "
+"'Allgemeine Einstellungen' ersetzt werden."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Arbeitsgruppe"
+
+#~ msgid "Browseable"
+#~ msgstr "Suchbar"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Maske für neue Verzeichnisse"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Maske für neue Dateien"
diff --git a/luci-app-samba4/po/el/samba4.po b/luci-app-samba4/po/el/samba4.po
new file mode 100644
index 000000000..f02225094
--- /dev/null
+++ b/luci-app-samba4/po/el/samba4.po
@@ -0,0 +1,147 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-28 02:08+0200\n"
+"PO-Revision-Date: 2012-03-18 15:31+0200\n"
+"Last-Translator: Vasilis \n"
+"Language-Team: LANGUAGE \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Όνομα"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr ""
diff --git a/luci-app-samba4/po/en/samba4.po b/luci-app-samba4/po/en/samba4.po
new file mode 100644
index 000000000..0e515052f
--- /dev/null
+++ b/luci-app-samba4/po/en/samba4.po
@@ -0,0 +1,157 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-03-22 15:23+0100\n"
+"PO-Revision-Date: 2011-10-25 21:26+0200\n"
+"Last-Translator: awm1 \n"
+"Language-Team: LANGUAGE \n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Allow guests"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr "Allow system users to reach their home directories via network shares"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Allowed users"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Create mask"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Description"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Directory mask"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Edit template"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Edit the template that is used for generating the Samba configuration."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "General settings"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Hostname"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Name"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Network Shares"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Path"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Read-only"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Share home-directories"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Shared Directories"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your Samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"settings' tab."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Workgroup"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Mask for new directories"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Mask for new files"
diff --git a/luci-app-samba4/po/es/samba4.po b/luci-app-samba4/po/es/samba4.po
new file mode 100644
index 000000000..bd28e52f2
--- /dev/null
+++ b/luci-app-samba4/po/es/samba4.po
@@ -0,0 +1,167 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-10 03:41+0200\n"
+"PO-Revision-Date: 2012-08-22 17:45+0200\n"
+"Last-Translator: José Vicente \n"
+"Language-Team: LANGUAGE \n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.6\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Permitir invitados"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Permitir a los usuarios acceder a sus directorios de inicio vía "
+"comparticiones de red"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Usuarios permitidos"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Crear máscara"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Descripción"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Máscara de directorio"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Editar plantilla"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Editar la plantilla usada para generar la configuración de samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Configuración general"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Nombre de máquina"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nombre"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Comparticiones de red"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Dirección"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Solo lectura"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Compartir directorios personales"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Directorios compartidos"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Contenido del fichero '/etc/samba/smb.conf.template' desde el que se "
+"generará la configuración de samba. Los valores entre tuberías ('|') no "
+"deben cambiarse. Su valor se toma desde la pestaña 'Configuración General'."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Grupo de trabajo"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Máscara para directorios nuevos"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Máscara para archivos nuevos"
+
+#~ msgid "Shared Directory"
+#~ msgstr "Directorio compatido"
+
+#~ msgid "Physical Path"
+#~ msgstr "Ruta Física"
+
+#~ msgid "optional"
+#~ msgstr "opcional"
diff --git a/luci-app-samba4/po/fr/samba4.po b/luci-app-samba4/po/fr/samba4.po
new file mode 100644
index 000000000..3375bfb9c
--- /dev/null
+++ b/luci-app-samba4/po/fr/samba4.po
@@ -0,0 +1,159 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-19 19:36+0200\n"
+"PO-Revision-Date: 2011-11-23 22:36+0200\n"
+"Last-Translator: fredb \n"
+"Language-Team: LANGUAGE \n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Invités autorisés"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Autoriser les utilisateurs système à atteindre leurs dossiers personnels via "
+"les partages réseau"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Utilisateurs autorisés"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Maque de création"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Description"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Masque des dossiers"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Éditer le modèle"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Éditer le modèle utilisé pour générer la configuration Samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Paramètres généraux"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Nom d'hôte"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nom"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Partages réseau"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Chemin"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Lecture seule"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Partager les dossiers personnels"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Dossiers partagés"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Voici le contenu du fichier '/etc/samba/smb.conf.template' d'où sera généré "
+"votre configuration Samba. Les valeurs entre les symboles barre-verticale "
+" (« | ») ne doivent pas être modifiées, elles proviennent de l'onglet "
+"« Paramètres généraux »."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Groupe de travail"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Masque pour les nouveaux dossiers"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Masque pour les nouveaux fichiers"
diff --git a/luci-app-samba4/po/he/samba4.po b/luci-app-samba4/po/he/samba4.po
new file mode 100644
index 000000000..49f38a87d
--- /dev/null
+++ b/luci-app-samba4/po/he/samba4.po
@@ -0,0 +1,142 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr ""
diff --git a/luci-app-samba4/po/hu/samba4.po b/luci-app-samba4/po/hu/samba4.po
new file mode 100644
index 000000000..ebd0f7dba
--- /dev/null
+++ b/luci-app-samba4/po/hu/samba4.po
@@ -0,0 +1,159 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2012-04-06 10:56+0200\n"
+"Last-Translator: juhosg \n"
+"Language-Team: none\n"
+"Language: hu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Vendég hozzáférés"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"A rendszerfelhasználók hálózati megosztáson keresztül hozzáférhetnek a home "
+"könyvtárukhoz."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Engedélyezett felhasználók"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Létrehozási maszk"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Leírás"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Könyvtár maszk"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Sablon szerkesztése"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+"Itt szerkesztheti a sablont, ami a végleges samba konfiguráció "
+"elkészítéséhez kerül felhasználásra."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Általános beállítások"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Gépnév"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Név"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Hálózati megosztások"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Elérési út"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Csak olvasható"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Home könyvtárak megosztása"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Megosztott könyvtárak"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Itt látható a /etc/samba/smb.conf.template file tartalma, ami a samba "
+"konfiguráció előállításához kerül felhasználásra. A pipe szimbólumok ('|') "
+"közé zárt értékek módosítása nem szükséges, az értéküket az általános "
+"beállítások fülről kapják."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Munkacsoport"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Új könyvtárak maszkja"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Új fájlok maszkja"
diff --git a/luci-app-samba4/po/it/samba4.po b/luci-app-samba4/po/it/samba4.po
new file mode 100644
index 000000000..6a6fd03b6
--- /dev/null
+++ b/luci-app-samba4/po/it/samba4.po
@@ -0,0 +1,165 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-19 19:36+0200\n"
+"PO-Revision-Date: 2017-09-06 01:28+0200\n"
+"Last-Translator: bubu83 \n"
+"Language-Team: LANGUAGE \n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Permetti ospiti"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Autorizza gli utenti del sistema a raggiungere la loro cartella home "
+"attraverso le condivisioni di rete"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Utenti ammessi"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Crea maschera"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Descrizione"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Maschera della cartella"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Modifica Template"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+"Modifica il template utilizzato per generare la configurazione di samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Opzioni Generali"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Hostname"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nome"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Condivisioni di rete"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Percorso"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+"Per favore aggiungi le directory da condividere. Ogni directory si riferisce "
+"a una cartella su un dispositivo montato."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Solo lettura"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Condividi cartelle home"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Cartelle Condivise"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Questo è il contenuto del file '/etc/samba/smb.conf.template' dal quale sarà "
+"generata la tua configurazione di samba. I valori racchiusi tra il simbolo "
+"('|') non dovrebbero essere toccati. Essi vengono generati dalla schermata "
+"'Opzioni Generali'."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Gruppo di lavoro"
+
+#~ msgid "Browseable"
+#~ msgstr "Sfogliabile"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Maschera per le nuove cartelle"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Maschera per i nuovi files"
diff --git a/luci-app-samba4/po/ja/samba4.po b/luci-app-samba4/po/ja/samba4.po
new file mode 100644
index 000000000..95d7c01c7
--- /dev/null
+++ b/luci-app-samba4/po/ja/samba4.po
@@ -0,0 +1,158 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-19 19:36+0200\n"
+"PO-Revision-Date: 2018-08-06 05:17+0900\n"
+"Last-Translator: INAGAKI Hiroshi \n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 2.1.1\n"
+"Language-Team: \n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "ゲストアクセスを許可"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr "sambaを介してユーザーのホームディレクトリへのアクセスを許可します"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "許可されたユーザー"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "マスクの作成"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "説明"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "ディレクトリのマスク"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr "Active Directory ドメインコントローラを無効化"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr "Netbios を無効化"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr "Winbind を無効化"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "テンプレートの編集"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "samba設定を生成するテンプレートを編集します。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "一般設定"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr "ゲストのみ"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "ホスト名"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr "オーナーの継承"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "名前"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "ネットワーク共有"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "パス"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+"共有するディレクトリを追加してください。マウントされたデバイス等のディレクト"
+"リを設定し、公開することができます。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "読み込みのみ"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "ホームディレクトリの共有"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "共有ディレクトリ"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"これは、samba設定を生成するための'/etc/samba/smb.conf.template' ファイルの内"
+"容です。パイプ('|')で閉じられた値は変更しないでください。これらの値は'一般設"
+"定'タブ内の値によって置き換えられます。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "ワークグループ"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "新規ディレクトリのマスク"
+
+#~ msgid "Mask for new files"
+#~ msgstr "新規ファイルのマスク"
diff --git a/luci-app-samba4/po/ms/samba4.po b/luci-app-samba4/po/ms/samba4.po
new file mode 100644
index 000000000..ab8a5a854
--- /dev/null
+++ b/luci-app-samba4/po/ms/samba4.po
@@ -0,0 +1,141 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr ""
diff --git a/luci-app-samba4/po/no/samba4.po b/luci-app-samba4/po/no/samba4.po
new file mode 100644
index 000000000..e8276b983
--- /dev/null
+++ b/luci-app-samba4/po/no/samba4.po
@@ -0,0 +1,147 @@
+msgid ""
+msgstr ""
+"Last-Translator: Lars Hardy \n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Tillat gjester"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr "Tillat systembrukere å nå sine hjemmekataloger via nettverks mapper."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Tillatte brukere"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Opprett Maske"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Beskrivelse"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Katalog maske"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Rediger Mal"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Rediger malen som brukes til å generere samba konfigurasjonen."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Generelle Innstillinger"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Vertsnavn"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Navn"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Nettverks Mapper"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Fysisk bane"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Skrivebeskyttet"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Del Hjemmekataloger"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Delte Kataloger"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Dette er innholdet av filen '/etc/samba/smb.conf.template' som din samba "
+"konfigurasjon vil bli generert fra. Verdier omsluttet av ('|') bør ikke "
+"endres. De får sine verdier fra 'Generelle Innstillinger' fanen."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Arbeidsgruppe"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Maske for nye kataloger"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Maske for nye filer"
diff --git a/luci-app-samba4/po/pl/samba4.po b/luci-app-samba4/po/pl/samba4.po
new file mode 100644
index 000000000..120ecd857
--- /dev/null
+++ b/luci-app-samba4/po/pl/samba4.po
@@ -0,0 +1,158 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2011-08-26 09:51+0200\n"
+"Last-Translator: Staszek \n"
+"Language-Team: none\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Zezwalaj Gościom"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Użytkownicy systemu mogą dostać się do swoich katalogów domowych za "
+"pośrednictwem udziałów sieciowych."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Użytkownicy z prawem dostępu"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Utwórz maskę"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Opis"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Maska katalogu"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Edytuj szablon"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Edytuj szablon, który jest używany do generowania konfiguracji samby."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Ustawienia ogólne"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Nazwa hosta"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nazwa"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Udziały sieciowe"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Ścieżka"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Tylko do odczytu"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Udostępniaj katalogi domowe"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Udostępniane katalogi"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"To jest zawartość pliku '/etc/samba/smb.conf.template\", na podstawie "
+"którego zostanie wygenerowana konfiguracja samby. Wartości otoczone symbolem "
+"kreski pionowej ('|') nie powinny być zmieniane. Wartości ich zostaną "
+"pobrane z zakładki \"Ustawienia ogólne\"."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Grupa robocza"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Maska dla nowych katalogów"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Maska dla nowych plików"
diff --git a/luci-app-samba4/po/pt-br/samba4.po b/luci-app-samba4/po/pt-br/samba4.po
new file mode 100644
index 000000000..f6b6677dd
--- /dev/null
+++ b/luci-app-samba4/po/pt-br/samba4.po
@@ -0,0 +1,172 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-10 03:41+0200\n"
+"PO-Revision-Date: 2018-09-20 19:48-0300\n"
+"Last-Translator: Luiz Angelo Daros de Luca \n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 2.1.1\n"
+"Language-Team: \n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Permitir convidados"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Usuários do sistema poderão acessar seu diretório home através dos "
+"compartilhamentos de rede"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Usuários permitidos"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr "Compartilhamento Time Machine da Apple"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr "Navegável"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Máscara de criação"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Descrição"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Máscara do diretório"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr "Desabilitar o Controlador de Domínios AD"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr "Desabilitar o NetBIOS"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr "Desabilitar o Winbind"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Editar modelo"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Edita o modelo que é usado para gerar a configuração do samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr "Habilitar compartilhamentos compatíveis com MacOS"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+"Habilitar globalmente a extensão AAPL da Apple e adicione a opção de "
+"compatibilidade com MacOS em todos os compartilhamentos."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr "Forçar Usuário Root"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Configurações Gerais"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr "Somente convidados"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Nome do equipamento"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr "Herdar o dono"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nome"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Compartilhamentos de Rede"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Caminho"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+"Por favor, adicione diretórios para compartilhar. Cada diretório refere-se a "
+"uma porta em um dispositivo montado."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Somente leitura"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Compartilhar diretórios home"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Diretórios Compartilhados"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Este é o conteúdo do arquivo '/etc/samba/smb.conf.template' a partir do qual "
+"sua configuração do samba será gerada. Valores entre simbolos de pipe ('|') "
+"não devem ser alterados. Estes valores serão obtidos a partir da aba "
+"'Configurações Gerais'."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr "Tamanho do Time Machine em GB"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr "Objetos VFS"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Grupo de trabalho"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Máscara para novos diretórios"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Máscara para novos arquivos"
+
+#~ msgid "Shared Directory"
+#~ msgstr "Diretório Compartilhado"
+
+#~ msgid "Physical Path"
+#~ msgstr "Caminho Físico"
+
+#~ msgid "optional"
+#~ msgstr "opcional"
diff --git a/luci-app-samba4/po/pt/samba4.po b/luci-app-samba4/po/pt/samba4.po
new file mode 100644
index 000000000..d68ab6680
--- /dev/null
+++ b/luci-app-samba4/po/pt/samba4.po
@@ -0,0 +1,168 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-26 19:03+0200\n"
+"PO-Revision-Date: 2013-05-01 01:13+0200\n"
+"Last-Translator: pedromrgoncalves \n"
+"Language-Team: LANGUAGE \n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.6\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Permitir Convidados"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Utilizadores do sistema poderão aceder ao seu directório home através das "
+"partilhas de rede."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Utilizadores Permitidos"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Criar Máscara"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Descrição"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Máscara do Directório"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Editar Template"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Editar a template que é utilizada para gerar a configuração samba"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Definições Gerais"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Nome do host"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nome"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Partilhas da Rede"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Caminho"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Apenas Leitura"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Partilha de directórios home"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Directórios Partilhados"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Isto é o conteúdo do ficheiro 'etc/samba/smb.conf.template' a partir do qual "
+"será gerado o ficheiro de configuração do samba. Os valores entre o símbolo "
+"| não devem ser alterados. Eles recebem os valores do separador 'Definições "
+"Gerais'."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Grupo de trabalho"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Máscara para novos directórios"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Máscara para novos ficheiros"
+
+#~ msgid "Shared Directory"
+#~ msgstr "Diretório Compartilhado"
+
+#~ msgid "Physical Path"
+#~ msgstr "Caminho Físico"
+
+#~ msgid "optional"
+#~ msgstr "opcional"
diff --git a/luci-app-samba4/po/ro/samba4.po b/luci-app-samba4/po/ro/samba4.po
new file mode 100644
index 000000000..2e1ecdaf6
--- /dev/null
+++ b/luci-app-samba4/po/ro/samba4.po
@@ -0,0 +1,157 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2011-10-07 17:16+0200\n"
+"Last-Translator: Daniel \n"
+"Language-Team: none\n"
+"Language: ro\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
+"20)) ? 1 : 2);;\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Permite oaspeti"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Permite utilizatorii de sistem sa acceseze directoarele lor peste "
+"partajarile de retea"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Utilizatori acceptati"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Creaza masca"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Descriere"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Masca director"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Editeaza sablon"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Editeaza sablonul care e folosit pentru generarea configuratiei samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Setari generale"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Numele de host"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Nume"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Partajari pe retea"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Cale"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Doar citire"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Partajeaza directoarele proprii"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Directoare partajate"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Acesta este continutul fisierului '/etc/samba/smb.conf.template' din care se "
+"genereaza configuratia samba. Valorile dintre liniuta verticala ('|') n-ar "
+"trebui schimbate, ele iau valorile direct din tab-ul de \"Setari generale\"."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Workgroup"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Masca pentru directoarele noi"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Masca pentru fisierele noi"
diff --git a/luci-app-samba4/po/ru/samba4.po b/luci-app-samba4/po/ru/samba4.po
new file mode 100644
index 000000000..9829dc50b
--- /dev/null
+++ b/luci-app-samba4/po/ru/samba4.po
@@ -0,0 +1,166 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: LuCI: samba\n"
+"POT-Creation-Date: 2009-05-19 19:36+0200\n"
+"PO-Revision-Date: 2018-01-14 11:43+0300\n"
+"Language-Team: http://cyber-place.ru\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.7.1\n"
+"Last-Translator: Vladimir aka sunny \n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Language: ru\n"
+"Project-Info: Это технический перевод, не дословный. Главное-удобный русский "
+"интерфейс, все проверялось в графическом режиме, совместим с другими apps\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Разрешить гостевой вход"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Разрешить пользователям получать доступ к их домашним папкам, через "
+"локальную сеть."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Разрешенные пользователи"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Создать маску"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Описание"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Маска папок"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Настройка config файла"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "Настройка config
файла samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Основные настройки"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Имя хоста"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Имя"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Сетевые ресурсы"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Путь"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+"Добавьте папки для совместного доступа. Каждая папка - соответствует разделу "
+"на подключенном устройстве."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Только для чтения"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Совместно использовать домашние папки"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Совместно используемые папки"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Это содержимое файла '/etc/samba/smb.conf.template', из которого "
+"генерируется config файл - samba.
Значения, заключенные в символы "
+"('|'), не должны быть изменены.
Они будут автоматически заменены на "
+"значения со страницы 'Основные настройки'."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Рабочая группа"
+
+#~ msgid "Browseable"
+#~ msgstr "Виден в списке доступных ресурсов"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Маска для новых папок"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Маска для новых файлов"
diff --git a/luci-app-samba4/po/sk/samba4.po b/luci-app-samba4/po/sk/samba4.po
new file mode 100644
index 000000000..a1c1d38a3
--- /dev/null
+++ b/luci-app-samba4/po/sk/samba4.po
@@ -0,0 +1,142 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr ""
diff --git a/luci-app-samba4/po/sv/samba4.po b/luci-app-samba4/po/sv/samba4.po
new file mode 100644
index 000000000..756fa6a27
--- /dev/null
+++ b/luci-app-samba4/po/sv/samba4.po
@@ -0,0 +1,150 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Tillåt gäster"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr "Tillåt systemanvändare att nå deras hem-mappar via nätverksdelningar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Tillåtna användare"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Skapa mask"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Beskrivning"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Mask för mapp"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Redigera mall"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+"Redigera mallen som används för att generera konfigurationen för samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Generella inställningar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Värdnamn"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Namn"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Nätverksdelningar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Genväg"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Endast läsbar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Dela hem-mappar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Delade mappar"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Arbetsgrupp"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Mask för nya mappar"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Mask för nya filer"
diff --git a/luci-app-samba4/po/templates/samba4.pot b/luci-app-samba4/po/templates/samba4.pot
new file mode 100644
index 000000000..d930c139e
--- /dev/null
+++ b/luci-app-samba4/po/templates/samba4.pot
@@ -0,0 +1,135 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr ""
diff --git a/luci-app-samba4/po/tr/samba4.po b/luci-app-samba4/po/tr/samba4.po
new file mode 100644
index 000000000..570ad2f38
--- /dev/null
+++ b/luci-app-samba4/po/tr/samba4.po
@@ -0,0 +1,142 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr ""
diff --git a/luci-app-samba4/po/uk/samba4.po b/luci-app-samba4/po/uk/samba4.po
new file mode 100644
index 000000000..5acd96cee
--- /dev/null
+++ b/luci-app-samba4/po/uk/samba4.po
@@ -0,0 +1,159 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2012-03-18 20:35+0200\n"
+"Last-Translator: YuriPet \n"
+"Language-Team: none\n"
+"Language: uk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
+"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Pootle 2.0.4\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "Дозволити гостьовий вхід"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Дозволити користувачам системи досягати своїх домашніх каталогів через "
+"загальні мережеві ресурси"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "Дозволені користувачі"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "Створити маску"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Опис"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "Маска каталогу"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "Редагувати шаблон"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+"Редагувати шаблон, який використовується для створення конфігурації samba."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "Загальні настройки"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "Назва (ім'я) вузла"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "Ім'я"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Загальні мережеві ресурси"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "Шлях"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "Тільки читання"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Спільно використовувати домашні каталоги"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Загальні каталоги"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"Це вміст файлу '/etc/samba/smb.conf.template', з якого буде генеруватися "
+"ваша конфігурація samba. Значення, укладені в символи \"вертикальна риска"
+"\" (\"|\") не повинні змінюватися. Вони отримують свої значення з вкладки "
+"\"Загальні налаштування\"."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Робоча група"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Маска для нових каталогів"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Маска для нових файлів"
diff --git a/luci-app-samba4/po/vi/samba4.po b/luci-app-samba4/po/vi/samba4.po
new file mode 100644
index 000000000..2920759f4
--- /dev/null
+++ b/luci-app-samba4/po/vi/samba4.po
@@ -0,0 +1,171 @@
+# samba.pot
+# generated from ./applications/luci-samba/luasrc/i18n/samba.en.lua
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-16 06:59+0200\n"
+"PO-Revision-Date: 2009-08-13 03:54+0200\n"
+"Last-Translator: Hong Phuc Dang \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+#, fuzzy
+msgid "Allow guests"
+msgstr "Cho phép khách"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+#, fuzzy
+msgid "Allow system users to reach their home directories via network shares"
+msgstr ""
+"Những người sử dụng hệ thống có thể tiếp cận những thư mục tại nhà thông qua "
+"mạng lưới chia sẻ trực tuyến."
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+#, fuzzy
+msgid "Allowed users"
+msgstr "Người sử dụng được cho phép"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+#, fuzzy
+msgid "Create mask"
+msgstr "Tạo Mask"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "Mô tả"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+#, fuzzy
+msgid "Directory mask"
+msgstr "Thư mục Mask"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "tên máy chủ"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "Mạng chia sẻ"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+#, fuzzy
+msgid "Read-only"
+msgstr "Chỉ đọc "
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "Chia sẻ danh bạ chính"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "Thư mục chia sẻ"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr ""
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "Nhóm làm việc "
+
+#~ msgid "Mask for new directories"
+#~ msgstr "Mask cho thư mục mới"
+
+#~ msgid "Mask for new files"
+#~ msgstr "Mask cho tập tin mới"
+
+#~ msgid "Shared Directory"
+#~ msgstr "Đã chia sẻ thư mục"
+
+#~ msgid "Physical Path"
+#~ msgstr "Đường dẫn vật lý"
+
+#~ msgid "optional"
+#~ msgstr "Tùy thích"
diff --git a/luci-app-samba4/po/zh-cn/samba4.po b/luci-app-samba4/po/zh-cn/samba4.po
new file mode 100644
index 000000000..4b65be81c
--- /dev/null
+++ b/luci-app-samba4/po/zh-cn/samba4.po
@@ -0,0 +1,164 @@
+#
+# Yangfl , 2017, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-10 03:40+0200\n"
+"PO-Revision-Date: 2020-06-07 11:21+0800\n"
+"Last-Translator: dingpengyu \n"
+"Language-Team: \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 2.3.1\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "允许匿名用户"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr "允许系统用户通过网络共享访问他们的家目录"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "允许用户"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr "Apple Time-machine 共享"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr "可浏览"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "创建权限掩码"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "描述"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "目录权限掩码"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr "禁用 Active Directory 域控制器"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr "禁用 Netbios"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr "禁用 Winbind"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "编辑模板"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "编辑用来生成 samba 设置的模板。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr "启用 macOS 兼容共享"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr "全局启用 Apple 的 AAPL 扩展,并为所有共享添加 macOS 兼容性选项。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr "强制 Root"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "基本设置"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr "仅来宾用户"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "主机名"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr "继承所有者"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "名称"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "网络共享"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "目录"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr "请添加要共享的目录。每个目录指到已挂载设备上的文件夹。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "只读"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "共享家目录"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "共享目录"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"这是将从其上生成 samba 配置的文件“/etc/samba/smb.conf.template”的内容。由管道"
+"符(“|”)包围的值不应更改。它们将从“常规设置”标签中获取其值。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr "Time-machine 大小(GB)"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr "VFS 对象"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "工作组"
+
+#~ msgid "Browseable"
+#~ msgstr "可浏览"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "新目录权限掩码"
+
+#~ msgid "Mask for new files"
+#~ msgstr "新文件权限掩码"
+
+#~ msgid "Physical Path"
+#~ msgstr "物理路径"
diff --git a/luci-app-samba4/po/zh-tw/samba4.po b/luci-app-samba4/po/zh-tw/samba4.po
new file mode 100644
index 000000000..5846fe3b7
--- /dev/null
+++ b/luci-app-samba4/po/zh-tw/samba4.po
@@ -0,0 +1,164 @@
+#
+# Yangfl , 2017, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-10 03:40+0200\n"
+"PO-Revision-Date: 2018-10-01 11:12+0800\n"
+"Last-Translator: Yangfl \n"
+"Language-Team: \n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Gtranslator 2.91.7\n"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:78
+msgid "Allow guests"
+msgstr "允許匿名使用者"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:15
+msgid "Allow system users to reach their home directories via network shares"
+msgstr "允許系統使用者通過網路共享訪問他們的家目錄"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:75
+msgid "Allowed users"
+msgstr "允許使用者"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:106
+msgid "Apple Time-machine share"
+msgstr "Apple Time-machine 共享"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:63
+msgid "Browse-able"
+msgstr "可瀏覽"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:93
+msgid "Create mask"
+msgstr "建立權限掩碼"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:12
+msgid "Description"
+msgstr "描述"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:98
+msgid "Directory mask"
+msgstr "目錄權限掩碼"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:27
+msgid "Disable Active Directory Domain Controller"
+msgstr "禁用 Active Directory 域控制器"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:24
+msgid "Disable Netbios"
+msgstr "禁用 Netbios"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:30
+msgid "Disable Winbind"
+msgstr "禁用 Winbind"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:9
+msgid "Edit Template"
+msgstr "編輯模板"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:34
+msgid "Edit the template that is used for generating the samba configuration."
+msgstr "編輯用來生成 samba 設定的模板"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:19
+msgid "Enable macOS compatible shares"
+msgstr "啟用 macOS 相容共享"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:20
+msgid ""
+"Enables Apple's AAPL extension globally and adds macOS compatibility options "
+"to all shares."
+msgstr "全域性啟用 Apple 的 AAPL 擴充套件,併為所有共享新增 macOS 相容性選項。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:73
+msgid "Force Root"
+msgstr "強制 Root"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:8
+msgid "General Settings"
+msgstr "基本設定"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:83
+msgid "Guests only"
+msgstr "僅來賓使用者"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:11
+msgid "Hostname"
+msgstr "主機名"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:88
+msgid "Inherit owner"
+msgstr "繼承所有者"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:57
+msgid "Name"
+msgstr "名称"
+
+#: applications/luci-app-samba4/luasrc/controller/samba4.lua:12
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:3
+msgid "Network Shares"
+msgstr "網路共享"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:58
+msgid "Path"
+msgstr "目錄"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:52
+msgid ""
+"Please add directories to share. Each directory refers to a folder on a "
+"mounted device."
+msgstr "請新增要共享的目錄。每個目錄指到已掛載裝置上的資料夾。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:68
+msgid "Read-only"
+msgstr "只讀"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:14
+msgid "Share home-directories"
+msgstr "共享家目錄"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:51
+msgid "Shared Directories"
+msgstr "共享目錄"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:35
+msgid ""
+"This is the content of the file '/etc/samba/smb.conf.template' from which "
+"your samba configuration will be generated. Values enclosed by pipe symbols "
+"('|') should not be changed. They get their values from the 'General "
+"Settings' tab."
+msgstr ""
+"這是將從其上生成 samba 配置的檔案“/etc/samba/smb.conf.template”的內容。由管道"
+"符(“|”)包圍的值不應更改。它們將從“常規設定”標籤中獲取其值。"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:108
+msgid "Time-machine size in GB"
+msgstr "Time-machine 大小(GB)"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:103
+msgid "Vfs objects"
+msgstr "VFS 物件"
+
+#: applications/luci-app-samba4/luasrc/model/cbi/samba4.lua:13
+msgid "Workgroup"
+msgstr "工作組"
+
+#~ msgid "Browseable"
+#~ msgstr "可瀏覽"
+
+#~ msgid "Mask for new directories"
+#~ msgstr "新目錄權限掩碼"
+
+#~ msgid "Mask for new files"
+#~ msgstr "新檔案權限掩碼"
+
+#~ msgid "Physical Path"
+#~ msgstr "物理路徑"
diff --git a/luci-app-usb-printer/Makefile b/luci-app-usb-printer/Makefile
new file mode 100644
index 000000000..57d895e66
--- /dev/null
+++ b/luci-app-usb-printer/Makefile
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=USB Printer Share via TCP/IP
+LUCI_DEPENDS:=+p910nd +kmod-usb-printer
+PKG_NAME:=luci-app-usb-printer
+PKG_VERSION:=1.0
+PKG_RELEASE:=2
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-usb-printer/ipkg/postinst b/luci-app-usb-printer/ipkg/postinst
new file mode 100755
index 000000000..97a343c7b
--- /dev/null
+++ b/luci-app-usb-printer/ipkg/postinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+[ -n "${IPKG_INSTROOT}" ] || {
+ ( . /etc/uci-defaults/luci-usb-printer ) && rm -f /etc/uci-defaults/luci-usb-printer
+ exit 0
+}
+
diff --git a/luci-app-usb-printer/luasrc/controller/usb_printer.lua b/luci-app-usb-printer/luasrc/controller/usb_printer.lua
new file mode 100644
index 000000000..c678858da
--- /dev/null
+++ b/luci-app-usb-printer/luasrc/controller/usb_printer.lua
@@ -0,0 +1,26 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+require("luci.sys")
+
+module("luci.controller.usb_printer", package.seeall)
+
+function index()
+ if not nixio.fs.access("/etc/config/usb_printer") then
+ return
+ end
+
+ entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false
+ entry({"admin", "nas", "usb_printer"}, cbi("usb_printer"), _("USB Printer Server"), 50)
+end
diff --git a/luci-app-usb-printer/luasrc/model/cbi/usb_printer.lua b/luci-app-usb-printer/luasrc/model/cbi/usb_printer.lua
new file mode 100644
index 000000000..3cc7526ef
--- /dev/null
+++ b/luci-app-usb-printer/luasrc/model/cbi/usb_printer.lua
@@ -0,0 +1,130 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth
+Copyright 2005-2013 hackpascal
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+require "luci.util"
+local uci = luci.model.uci.cursor_state()
+local net = require "luci.model.network"
+
+m = Map("usb_printer", translate("USB Printer Server"),
+ translate("Shares multiple USB printers via TCP/IP.
When modified bingings, re-plug usb connectors to take effect.
This module requires kmod-usb-printer."))
+
+function hex_align(hex, num)
+ local len = num - string.len(hex)
+
+ return string.rep("0", len) .. hex
+end
+
+function detect_usb_printers()
+ local data = {}
+
+ local lps = luci.util.execi("/usr/bin/detectlp")
+
+ for value in lps do
+ local row = {}
+
+ --[[
+ detectlp 的输出格式:
+ 设备名,VID/PID/?,描述,型号
+ ]]--
+
+ local pos = string.find(value, ",")
+
+ local devname = string.sub(value, 1, pos - 1)
+
+ local value = string.sub(value, pos + 1, string.len(value))
+
+ pos = string.find(value, ",")
+ local product = string.sub(value, 1, pos - 1)
+
+ value = string.sub(value, pos + 1, string.len(value))
+
+ pos = string.find(value, ",")
+ local model = string.sub(value, 1, pos - 1)
+
+ local name = string.sub(value, pos + 1, string.len(value))
+
+ pos = string.find(product, "/");
+
+ local vid = string.sub(product, 1, pos - 1)
+
+ local pid = string.sub(product, pos + 1, string.len(product))
+
+ pos = string.find(pid, "/")
+ pid = string.sub(pid, 1, pos - 1)
+
+ row["description"] = name
+ row["model"] = model
+ row["id"] = hex_align(vid, 4) .. ":" .. hex_align(pid, 4)
+ row["name"] = devname
+ row["product"] = product
+
+ table.insert(data, row)
+ end
+
+ return data
+end
+
+local printers = detect_usb_printers()
+
+v = m:section(Table, printers, translate("Detected printers"))
+
+v:option(DummyValue, "description", translate("Description"))
+v:option(DummyValue, "model", translate("Printer Model"))
+v:option(DummyValue, "id", translate("VID/PID"))
+v:option(DummyValue, "name", translate("Device Name"))
+
+net = net.init(m.uci)
+
+s = m:section(TypedSection, "printer", translate("Bindings"))
+s.addremove = true
+s.anonymous = true
+
+s:option(Flag, "enabled", translate("enable"))
+
+d = s:option(Value, "device", translate("Device"))
+d.rmempty = true
+
+for key, item in ipairs(printers) do
+ d:value(item["product"], item["description"] .. " [" .. item["id"] .. "]")
+end
+
+b = s:option(Value, "bind", translate("Interface"), translate("Specifies the interface to listen on."))
+b.template = "cbi/network_netlist"
+b.nocreate = true
+b.unspecified = true
+
+function b.cfgvalue(...)
+ local v = Value.cfgvalue(...)
+ if v then
+ return (net:get_status_by_address(v))
+ end
+end
+
+function b.write(self, section, value)
+ local n = net:get_network(value)
+ if n and n:ipaddr() then
+ Value.write(self, section, n:ipaddr())
+ end
+end
+
+p = s:option(ListValue, "port", translate("Port"), translate("TCP listener port."))
+p.rmempty = true
+for i = 0, 9 do
+ p:value(i, 9100 + i)
+end
+
+s:option(Flag, "bidirectional", translate("Bidirectional mode"))
+
+return m
diff --git a/luci-app-usb-printer/po/zh-cn/usb-printer.po b/luci-app-usb-printer/po/zh-cn/usb-printer.po
new file mode 100644
index 000000000..05ce0d1ee
--- /dev/null
+++ b/luci-app-usb-printer/po/zh-cn/usb-printer.po
@@ -0,0 +1,65 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-05-18 01:34+0800\n"
+"PO-Revision-Date: 2014-05-18 01:34+0800\n"
+"Last-Translator: hackpascal \n"
+"Language-Team: \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 2.0.6\n"
+
+msgid "Bidirectional mode"
+msgstr "双向模式"
+
+msgid "Bindings"
+msgstr "绑定"
+
+msgid "Device"
+msgstr "设备"
+
+msgid "Device Name"
+msgstr "设备名"
+
+msgid "Detected printers"
+msgstr "检测到的打印机"
+
+msgid ""
+"Shares multiple USB printers via TCP/IP.
"
+"When modified bingings, re-plug usb connectors to take effect.
"
+"This module requires kmod-usb-printer."
+msgstr ""
+"通过 TCP/IP 共享 USB 打印机。
修改设置后,请重新连接打印机以使设置生效。
"
+"此模块需要 kmod-usb-printer 支持。"
+
+msgid "Port"
+msgstr "端口"
+
+msgid "Printer Model"
+msgstr "打印机型号"
+
+msgid "Settings"
+msgstr "设置"
+
+msgid "TCP listener port."
+msgstr "TCP 监听端口。"
+
+msgid "enable"
+msgstr "启用"
+
+msgid "USB Printer Server"
+msgstr "USB 打印服务器"
+
+msgid "Specifies the interface to listen on."
+msgstr "指定要监听的接口。"
+
+msgid "NAS"
+msgstr "网络存储"
+
+msgid "Architecture"
+msgstr "架构"
+
diff --git a/luci-app-usb-printer/root/etc/config/usb_printer b/luci-app-usb-printer/root/etc/config/usb_printer
new file mode 100644
index 000000000..e69de29bb
diff --git a/luci-app-usb-printer/root/etc/hotplug.d/usb/10-usb_printer b/luci-app-usb-printer/root/etc/hotplug.d/usb/10-usb_printer
new file mode 100755
index 000000000..c250fe78f
--- /dev/null
+++ b/luci-app-usb-printer/root/etc/hotplug.d/usb/10-usb_printer
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Copyright (C) 2005-2014 NowRush Studio
+# Author: hackpascal
+
+if [ x"$INTERFACE" = x"7/1/1" ] || [ x"$INTERFACE" = x"7/1/2" ]; then
+ /usr/bin/usb_printer_hotplug "$PRODUCT" "$ACTION"
+fi
diff --git a/luci-app-usb-printer/root/etc/init.d/usb_printer b/luci-app-usb-printer/root/etc/init.d/usb_printer
new file mode 100755
index 000000000..4da5e3445
--- /dev/null
+++ b/luci-app-usb-printer/root/etc/init.d/usb_printer
@@ -0,0 +1,22 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2005-2013 NowRush Studio
+# Author: hackpascal
+
+START=70
+
+stop() {
+ killall p910nd 2>/dev/null
+}
+
+start() {
+ for lps in `/usr/bin/detectlp`; do
+ product=`echo $lps | cut -d , -f 2`
+
+ /usr/bin/usb_printer_hotplug "$product" add
+ done
+}
+
+restart() {
+ stop
+ start
+}
diff --git a/luci-app-usb-printer/root/etc/uci-defaults/luci-usb-printer b/luci-app-usb-printer/root/etc/uci-defaults/luci-usb-printer
new file mode 100755
index 000000000..38c214f9c
--- /dev/null
+++ b/luci-app-usb-printer/root/etc/uci-defaults/luci-usb-printer
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+ delete ucitrack.@usb_printer[-1]
+ add ucitrack usb_printer
+ set ucitrack.@usb_printer[-1].init=usb_printer
+ commit ucitrack
+EOF
+
+[ -f /etc/init.d/p910nd ] && /etc/init.d/p910nd disable
+
+exit 0
diff --git a/luci-app-usb-printer/root/usr/bin/detectlp b/luci-app-usb-printer/root/usr/bin/detectlp
new file mode 100755
index 000000000..b69385bb9
--- /dev/null
+++ b/luci-app-usb-printer/root/usr/bin/detectlp
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+lp_path=/sys/class/usbmisc
+
+if ! [ -d "$lp_path" ]; then
+ exit
+fi
+
+cd $lp_path
+
+for lps in `ls`; do
+ desc_file=$lp_path/$lps/device/ieee1284_id
+ uevent_file=$lp_path/$lps/device/uevent
+
+ name=`cat $desc_file | sed 's/.*DES:\(.*\);.*/\1/' | cut -d ';' -f 1`
+ model=`cat $desc_file | sed 's/.*MDL:\(.*\);.*/\1/' | cut -d ';' -f 1`
+ product=`cat $uevent_file | grep PRODUCT= | sed 's/PRODUCT=\(.*\)/\1/'`
+
+ echo $lps,$product,$model,$name;
+done
diff --git a/luci-app-usb-printer/root/usr/bin/usb_printer_hotplug b/luci-app-usb-printer/root/usr/bin/usb_printer_hotplug
new file mode 100755
index 000000000..16b4ccc4d
--- /dev/null
+++ b/luci-app-usb-printer/root/usr/bin/usb_printer_hotplug
@@ -0,0 +1,72 @@
+#!/bin/sh
+# Copyright (C) 2005-2014 NowRush Studio
+# Author: hackpascal
+
+. $IPKG_INSTROOT/lib/functions.sh
+
+PRODUCT=$1
+ACTION=$2
+
+DEVICES=
+
+check_printer() {
+ local cfg=$1
+ local enabled
+ local device_id
+ local bind_ip
+ local port
+ local bidirect
+ local device_file
+ local args=""
+ local pid_file
+
+ config_get_bool enabled "$cfg" enabled 0
+ [ "$enabled" -eq 0 ] && return 0
+
+ config_get device_id "$cfg" device ""
+ config_get bind_ip "$cfg" bind "0.0.0.0"
+ config_get port "$cfg" port ""
+ config_get_bool bidirect "$cfg" bidirectional "0"
+
+ if [ -z "$device_id" ] || [ -z "$port" ]; then
+ return
+ fi
+
+ if [ x"$PRODUCT" != x"$device_id" ]; then
+ return
+ fi
+
+ device_file=`echo "$DEVICES" | grep $device_id | cut -d , -f 1`
+
+ if [ "$ACTION" = "add" ] && [ -z "$device_file" ]; then
+ return
+ fi
+
+ pid_file=/var/run/p910${port}d.pid
+ [ -f $pid_file ] && kill `cat $pid_file` 2>/dev/null
+
+ if [ "$ACTION" = "add" ]; then
+ if [ "$bidirect" != 0 ]; then
+ args='-b'
+ fi
+
+ logger "usb_printer: start p910nd on $bind_ip:$port for /dev/usb/$device_file"
+ /usr/sbin/p910nd $args -f /dev/usb/$device_file -i $bind_ip $port
+ fi
+}
+
+if [ -z "$PRODUCT" ] || [ -z "$ACTION" ]; then
+ echo "Arguements required"
+ exit 1
+fi
+
+if [ "$ACTION" != "add" ] && [ "$ACTION" != "remove" ]; then
+ echo "Invalid action arguement"
+ exit 1
+fi
+
+DEVICES=`/usr/bin/detectlp`
+
+config_load usb_printer
+
+config_foreach check_printer printer
diff --git a/luci-app-xlnetacc/Makefile b/luci-app-xlnetacc/Makefile
new file mode 100644
index 000000000..3f8b3116f
--- /dev/null
+++ b/luci-app-xlnetacc/Makefile
@@ -0,0 +1,19 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=luci-app-xlnetacc
+PKG_VERSION:=1.0.5
+PKG_RELEASE:=2
+
+PKG_MAINTAINER:=Sense
+
+LUCI_TITLE:=LuCI Support for XLNetAcc
+LUCI_PKGARCH:=all
+LUCI_DEPENDS:=+jshn +curl +openssl-util
+
+define Package/$(PKG_NAME)/conffiles
+/etc/config/xlnetacc
+endef
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-xlnetacc/luasrc/controller/xlnetacc.lua b/luci-app-xlnetacc/luasrc/controller/xlnetacc.lua
new file mode 100644
index 000000000..bcbdfa34d
--- /dev/null
+++ b/luci-app-xlnetacc/luasrc/controller/xlnetacc.lua
@@ -0,0 +1,36 @@
+module("luci.controller.xlnetacc",package.seeall)
+
+function index()
+ if not nixio.fs.access("/etc/config/xlnetacc") then
+ return
+ end
+
+ entry({"admin", "services", "xlnetacc"}, alias("admin", "services", "xlnetacc", "general"), _("XLNetAcc"), 90).dependent = true
+ entry({"admin", "services", "xlnetacc", "general"}, cbi("xlnetacc"), _("Settings"), 1).leaf = true
+ entry({"admin", "services", "xlnetacc", "log"}, template("xlnetacc/logview"), _("Log"), 2).leaf = true
+ entry({"admin", "services", "xlnetacc", "status"}, call("action_status")).leaf = true
+ entry({"admin", "services", "xlnetacc", "logdata"}, call("action_log")).leaf = true
+end
+
+function action_status()
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({
+ run_state = luci.sys.call("ps -w | grep xlnetacc.sh | grep -v grep >/dev/null")==0,
+ down_state = nixio.fs.readfile("/var/state/xlnetacc_down_state") or "",
+ up_state = nixio.fs.readfile("/var/state/xlnetacc_up_state") or ""
+ })
+end
+
+function action_log()
+ local uci = require "luci.model.uci".cursor()
+ local util = require "luci.util"
+ local log_data = {}
+
+ log_data.syslog = util.trim(util.exec("logread | grep xlnetacc"))
+ if uci:get("xlnetacc","general","logging")~="0" then
+ log_data.client = nixio.fs.readfile("/var/log/xlnetacc.log") or ""
+ end
+ uci:unload("xlnetacc")
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(log_data)
+end
diff --git a/luci-app-xlnetacc/luasrc/model/cbi/xlnetacc.lua b/luci-app-xlnetacc/luasrc/model/cbi/xlnetacc.lua
new file mode 100644
index 000000000..b45beff80
--- /dev/null
+++ b/luci-app-xlnetacc/luasrc/model/cbi/xlnetacc.lua
@@ -0,0 +1,57 @@
+local m,s,o
+local uci = luci.model.uci.cursor()
+
+m = Map("xlnetacc")
+m.title = translate("XLNetAcc - Settings")
+m.description = translate("XLNetAcc is a Thunder joint broadband operators launched a commitment to help users solve the low broadband, slow Internet access, poor Internet experience of professional-grade broadband upgrade software.")
+
+m:section(SimpleSection).template = "xlnetacc/xlnetacc_status"
+
+s = m:section(NamedSection, "general", "general")
+s.title = translate("General Settings")
+s.anonymous = true
+s.addremove = false
+
+o = s:option(Flag, "enabled", translate("Enabled"))
+o.rmempty = false
+
+o = s:option(Flag, "down_acc", translate("Enable DownLink Upgrade"))
+
+o = s:option(Flag, "up_acc", translate("Enable UpLink Upgrade"))
+
+o = s:option(Flag, "logging", translate("Enable Logging"))
+o.default = 1
+
+o = s:option(Flag, "verbose", translate("Enable verbose logging"))
+o:depends("logging",1)
+
+o = s:option(ListValue, "network", translate("Upgrade interface"))
+uci:foreach("network","interface",function(section)
+ if section[".name"]~="loopback" then
+ o:value(section[".name"])
+ end
+end)
+
+o = s:option(Value, "keepalive", translate("Keepalive interval"))
+o.description = translate("5 ~ 60 minutes")
+for _,v in ipairs({5,10,20,30,60}) do
+ o:value(v,v.." "..translate("minutes"))
+end
+o.datatype = "range(5,60)"
+o.default = 10
+
+o = s:option(Value, "relogin", translate("Account relogin"))
+o.description = translate("1 ~ 48 hours")
+o:value(0, translate("Not enabled"))
+for _,v in ipairs({3,12,18,24,30}) do
+ o:value(v,v.." "..translate("hours"))
+end
+o.datatype = "max(48)"
+o.default = 0
+
+o = s:option(Value, "account", translate("XLNetAcc account"))
+
+o = s:option(Value, "password", translate("XLNetAcc password"))
+o.password = true
+
+return m
diff --git a/luci-app-xlnetacc/luasrc/view/xlnetacc/logview.htm b/luci-app-xlnetacc/luasrc/view/xlnetacc/logview.htm
new file mode 100644
index 000000000..bd84ff98d
--- /dev/null
+++ b/luci-app-xlnetacc/luasrc/view/xlnetacc/logview.htm
@@ -0,0 +1,49 @@
+<%css=[[
+ #log_text{
+ padding:10px;
+ text-align:left;
+ height:500px;
+ overflow:auto;
+ }
+ #log_text pre{
+ word-break:break-all;
+ margin:0;
+ }
+ .description{
+ color:#ffffff;
+ background-color:#0099ff;
+ }
+]]
+%>
+
+<%+header%>
+
+
+
<%:XLNetAcc - Log%>
+
+
+
+
+
+
+<%+footer%>
diff --git a/luci-app-xlnetacc/luasrc/view/xlnetacc/xlnetacc_status.htm b/luci-app-xlnetacc/luasrc/view/xlnetacc/xlnetacc_status.htm
new file mode 100644
index 000000000..74123e689
--- /dev/null
+++ b/luci-app-xlnetacc/luasrc/view/xlnetacc/xlnetacc_status.htm
@@ -0,0 +1,21 @@
+
+
+
+ <%:XLNetAcc Running Status%> | <%:Collecting data...%> |
+ <%:DownLink Upgrade Status%> | <%:Collecting data...%> |
+ <%:UpLink Upgrade Status%> | <%:Collecting data...%> |
+
+
+
+
diff --git a/luci-app-xlnetacc/po/zh-cn/xlnetacc.po b/luci-app-xlnetacc/po/zh-cn/xlnetacc.po
new file mode 100644
index 000000000..648083df4
--- /dev/null
+++ b/luci-app-xlnetacc/po/zh-cn/xlnetacc.po
@@ -0,0 +1,116 @@
+msgid "XLNetAcc"
+msgstr "迅雷快鸟"
+
+msgid "XLNetAcc - Settings"
+msgstr "迅雷快鸟 - 设置"
+
+msgid "XLNetAcc - Log"
+msgstr "迅雷快鸟 - 日志"
+
+msgid "XLNetAcc is a Thunder joint broadband operators launched a commitment to help users solve the low broadband, slow Internet access, poor Internet experience of professional-grade broadband upgrade software."
+msgstr "迅雷快鸟是迅雷联合宽带运营商推出的一款致力于帮助用户解决宽带低、网速慢、上网体验差的专业级宽带加速软件。"
+
+msgid "Settings"
+msgstr "设置"
+
+msgid "Log"
+msgstr "日志"
+
+msgid "Running Status"
+msgstr "运行状态"
+
+msgid "XLNetAcc Running Status"
+msgstr "快鸟运行状态"
+
+msgid "DownLink Upgrade Status"
+msgstr "下行提速状态"
+
+msgid "UpLink Upgrade Status"
+msgstr "上行提速状态"
+
+msgid "RUNNING"
+msgstr "运行中"
+
+msgid "NOT RUNNING"
+msgstr "未运行"
+
+msgid "No upgrade information"
+msgstr "暂无提速信息"
+
+msgid "General Settings"
+msgstr "基本设置"
+
+msgid "Enabled"
+msgstr "启用"
+
+msgid "Enable DownLink Upgrade"
+msgstr "开启下行提速"
+
+msgid "Enable UpLink Upgrade"
+msgstr "开启上行提速"
+
+msgid "Enable Logging"
+msgstr "启用日志记录"
+
+msgid "Enable verbose logging"
+msgstr "启用详细日志"
+
+msgid "Upgrade interface"
+msgstr "指定提速接口"
+
+msgid "Keepalive interval"
+msgstr "保持连接周期"
+
+msgid "Account relogin"
+msgstr "帐号重新登录"
+
+msgid "Not enabled"
+msgstr "未启用"
+
+msgid "5 ~ 60 minutes"
+msgstr "5 ~ 60 分钟"
+
+msgid "minutes"
+msgstr "分钟"
+
+msgid "1 ~ 48 hours"
+msgstr "1 ~ 48 小时"
+
+msgid "hours"
+msgstr "小时"
+
+msgid "XLNetAcc account"
+msgstr "迅雷快鸟帐号"
+
+msgid "XLNetAcc password"
+msgstr "迅雷快鸟密码"
+
+msgid "Does not store the plaintext password, automatically emptied after start."
+msgstr "不存储明文密码,启动后自动清空。"
+
+msgid "Encrypted password"
+msgstr "加密后的密码"
+
+msgid "Auto-generate in accordance with the plaintext password, do not modify it!"
+msgstr "根据明文密码自动生成,请勿修改!"
+
+msgid "Log Data"
+msgstr "日志数据"
+
+msgid "Loading..."
+msgstr "正在加载..."
+
+msgid "Refresh every 5 seconds."
+msgstr "每 5 秒刷新。"
+
+msgid "syslog:"
+msgstr "系统日志:"
+
+msgid "log file:"
+msgstr "日志文件:"
+
+msgid "No log data."
+msgstr "无日志数据。"
+
+msgid "Error get log data."
+msgstr "获取日志数据失败。"
diff --git a/luci-app-xlnetacc/root/etc/config/xlnetacc b/luci-app-xlnetacc/root/etc/config/xlnetacc
new file mode 100644
index 000000000..4345070c4
--- /dev/null
+++ b/luci-app-xlnetacc/root/etc/config/xlnetacc
@@ -0,0 +1,5 @@
+
+config general 'general'
+ option enabled '0'
+ option network 'wan'
+
diff --git a/luci-app-xlnetacc/root/etc/hotplug.d/iface/95-xlnetacc b/luci-app-xlnetacc/root/etc/hotplug.d/iface/95-xlnetacc
new file mode 100755
index 000000000..be0271ad8
--- /dev/null
+++ b/luci-app-xlnetacc/root/etc/hotplug.d/iface/95-xlnetacc
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+/etc/init.d/xlnetacc enabled || exit 0
+[ "$INTERFACE" != "$(uci get xlnetacc.general.network)" ] && exit 0
+
+case "$ACTION" in
+ ifup)
+ /etc/init.d/xlnetacc start
+ ;;
+ ifdown)
+ /etc/init.d/xlnetacc stop
+ ;;
+esac
diff --git a/luci-app-xlnetacc/root/etc/init.d/xlnetacc b/luci-app-xlnetacc/root/etc/init.d/xlnetacc
new file mode 100755
index 000000000..7dcf23101
--- /dev/null
+++ b/luci-app-xlnetacc/root/etc/init.d/xlnetacc
@@ -0,0 +1,49 @@
+#!/bin/sh /etc/rc.common
+
+START=95
+STOP=10
+SERVICE_DAEMONIZE=1
+
+NAME=xlnetacc
+
+start() {
+ local retry=1
+ while ps -w | grep ${NAME}.sh | grep -v grep >/dev/null 2>&1; do
+ [ $retry -ge 10 ] && return 1 || let retry++
+ sleep 1
+ done
+ rm -f /var/lock/xlnetacc.lock
+
+ config_load "$NAME"
+ config_get_bool enabled "general" "enabled" 0
+ config_get_bool down_acc "general" "down_acc" 0
+ config_get_bool up_acc "general" "up_acc" 0
+ config_get network "general" "network"
+ config_get username "general" "account"
+ config_get password "general" "password"
+ ( [ $enabled -eq 0 ] || [ $down_acc -eq 0 -a $up_acc -eq 0 ] || [ -z "$username" -o -z "$password" -o -z "$network" ] ) && return 2
+
+ logger -p "daemon.notice" -t "$NAME" "XLNetAcc is starting ..."
+ service_start /usr/bin/${NAME}.sh --start
+}
+
+stop() {
+ ps -w | grep ${NAME}.sh | grep -v grep >/dev/null 2>&1 || return 1
+ local pid spid
+ for pid in $(ps -w | grep ${NAME}.sh | grep -v grep | awk '{print $1}'); do
+ echo "Stop XLNetAcc process PID: $pid"
+ kill $pid >/dev/null 2>&1
+ for spid in $(pgrep -P $pid "sleep"); do
+ echo "Stop XLNetAcc process SPID: $spid"
+ kill $spid >/dev/null 2>&1
+ done
+ done
+ rm -f /var/lock/xlnetacc.lock
+ logger -p "daemon.notice" -t "$NAME" "XLNetAcc has stoped."
+ return 0
+}
+
+restart() {
+ stop && sleep 1
+ start
+}
diff --git a/luci-app-xlnetacc/root/etc/uci-defaults/luci-xlnetacc b/luci-app-xlnetacc/root/etc/uci-defaults/luci-xlnetacc
new file mode 100755
index 000000000..867cca063
--- /dev/null
+++ b/luci-app-xlnetacc/root/etc/uci-defaults/luci-xlnetacc
@@ -0,0 +1,6 @@
+uci batch <> $LOGFILE 2> /dev/null
+ [ $(( $flag & 2 )) -ne 0 ] && logger -p "daemon.info" -t "$NAME" "$msg"
+
+ [ $(( $flag & 32 )) = 0 ] && local color="green" || local color="red"
+ [ $(( $flag & 8 )) -ne 0 ] && echo -n "$timestamp $msg" > $down_state_file 2> /dev/null
+ [ $(( $flag & 16 )) -ne 0 ] && echo -n "$timestamp $msg" > $up_state_file 2> /dev/null
+}
+
+# 清理日志
+clean_log(){
+ [ $logging = 1 -a -f "$LOGFILE" ] || return
+ [ $(wc -l "$LOGFILE" | awk '{print $1}') -le 800 ] && return
+ _log "清理日志文件"
+ local logdata=$(tail -n 500 "$LOGFILE")
+ echo "$logdata" > $LOGFILE 2> /dev/null
+ unset logdata
+}
+
+# 获取接口IP地址
+get_bind_ip(){
+ network=$(uci get "xlnetacc.general.network" 2> /dev/null)
+ json_cleanup;json_load "$(ubus call network.interface.$network status 2> /dev/null)" >/dev/null 2>&1
+ json_select "ipv4-address" >/dev/null 2>&1;json_select 1 >/dev/null 2>&1
+ json_get_var _bind_ip "address"
+ if [ -z "$_bind_ip" -o "$_bind_ip" = "0.0.0.0" ];then
+ _log "获取网络 $network IP地址失败"
+ return 1
+ else
+ _log "绑定IP地址: $_bind_ip"
+ return 0
+ fi
+}
+
+# 生成设备标识
+gen_device_sign(){
+ local ifname macaddr
+ while :;do
+ ifname=$(uci get "network.$network.ifname" 2> /dev/null)
+ [ "${ifname:0:1}" = @ ] && network="${ifname:1}" || break
+ done
+ [ -z "$ifname" ] && { _log "获取网络 $network 信息出错";return;}
+ json_cleanup;json_load "$(ubus call network.device status {\"name\":\"$ifname\"} 2> /dev/null)" >/dev/null 2>&1
+ json_get_var macaddr "macaddr"
+ [ -z "$macaddr" ] && { _log "获取网络 $network MAC地址出错";return;}
+ macaddr=$(echo -n "$macaddr" | awk '{print toupper($0)}')
+
+ # 计算peerID
+ local fake_peerid=$(awk -F- '{print toupper($5)}' '/proc/sys/kernel/random/uuid')
+ readonly _peerid="${fake_peerid}004V"
+ _log "_peerid is $_peerid" $(( 1 | 4 ))
+
+ # 计算devicesign
+ # sign = div.10?.device_id + md5(sha1(packageName + businessType + md5(a protocolVersion specific GUID)))
+ local fake_device_id=$(echo -n "${macaddr//:/}" | openssl dgst -md5 | awk '{print $2}')
+ local fake_device_sign=$(echo -n "${fake_device_id}${packageName}${businessType}c7f21687eed3cdb400ca11fc2263c998" \
+ | openssl dgst -sha1 | awk '{print $2}')
+ readonly _devicesign="div101.${fake_device_id}"$(echo -n "$fake_device_sign" | openssl dgst -md5 | awk '{print $2}')
+ _log "_devicesign is $_devicesign" $(( 1 | 4 ))
+}
+
+# 快鸟帐号通用参数
+swjsq_json(){
+ let sequence_xl++
+ # 生成POST数据
+ json_init
+ json_add_string protocolVersion "$protocolVersion"
+ json_add_string sequenceNo "$sequence_xl"
+ json_add_string platformVersion '10'
+ json_add_string isCompressed '0'
+ json_add_string appid "$businessType"
+ json_add_string clientVersion "$clientVersion"
+ json_add_string peerID "$_peerid"
+ json_add_string appName "ANDROID-$packageName"
+ json_add_string sdkVersion "${sdkVersion##*.}"
+ json_add_string devicesign "$_devicesign"
+ json_add_string netWorkType 'WIFI'
+ json_add_string providerName 'OTHER'
+ json_add_string deviceModel 'MI'
+ json_add_string deviceName 'Xiaomi Mi'
+ json_add_string OSVersion "7.1.1"
+}
+
+# 帐号登录
+swjsq_login(){
+ swjsq_json
+ if [ -z "$_userid" -o -z "$_loginkey" ];then
+ access_url='https://mobile-login.xunlei.com/login'
+ json_add_string userName "$username"
+ json_add_string passWord "$password"
+ json_add_string verifyKey
+ json_add_string verifyCode
+ json_add_string isMd5Pwd '0'
+ else
+ access_url='https://mobile-login.xunlei.com/loginkey'
+ json_add_string userName "$_userid"
+ json_add_string loginKey "$_loginkey"
+ fi
+ json_close_object
+
+ local ret=$($_http_cmd -A "$agent_xl" -d "$(json_dump)" "$access_url")
+ case $? in
+ 0)_log "login is $ret" $(( 1 | 4 ));json_cleanup;json_load "$ret" >/dev/null 2>&1;json_get_var lasterr "errorCode";;
+ 2)lasterr=-2;;
+ 28)lasterr=-3;;
+ *)lasterr=-1;;
+ esac
+
+ case ${lasterr:=-1} in
+ 0)json_get_var _userid "userID";json_get_var _loginkey "loginKey";json_get_var _sessionid "sessionID";_log "_sessionid is $_sessionid" $(( 1 | 4 ));local outmsg="帐号登录成功";_log "$outmsg" $(( 1 | 8 ));;
+ 15)_userid=;_loginkey=;;# 身份信息已失效
+ -1)local outmsg="帐号登录失败。迅雷服务器未响应,请稍候";_log "$outmsg";;
+ -2)local outmsg="cURL 参数解析错误,请更新 cURL";_log "$outmsg" $(( 1 | 8 | 32 ));;
+ -3)local outmsg="cURL 网络通信失败,请稍候";_log "$outmsg";;
+ *)local errorDesc;json_get_var errorDesc "errorDesc";local outmsg="帐号登录失败。错误代码: ${lasterr}";[ -n "$errorDesc" ] && outmsg="${outmsg},原因: $errorDesc";_log "$outmsg" $(( 1 | 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 帐号注销
+swjsq_logout(){
+ swjsq_json
+ json_add_string userID "$_userid"
+ json_add_string sessionID "$_sessionid"
+ json_close_object
+
+ local ret=$($_http_cmd -A "$agent_xl" -d "$(json_dump)" 'https://mobile-login.xunlei.com/logout')
+ _log "logout is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errorCode"
+
+ case ${lasterr:=-1} in
+ 0)_sessionid=;local outmsg="帐号注销成功";_log "$outmsg" $(( 1 | 8 ));;
+ -1)local outmsg="帐号注销失败。迅雷服务器未响应,请稍候";_log "$outmsg";;
+ *)local errorDesc;json_get_var errorDesc "errorDesc";local outmsg="帐号注销失败。错误代码: ${lasterr}";[ -n "$errorDesc" ] && outmsg="${outmsg},原因: $errorDesc";_log "$outmsg" $(( 1 | 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 获取用户信息
+swjsq_getuserinfo(){
+ local _vasid vasid_down=14 vasid_up=33 outmsg
+ [ $down_acc -ne 0 ] && _vasid="${_vasid}${vasid_down},";[ $up_acc -ne 0 ] && _vasid="${_vasid}${vasid_up},"
+ swjsq_json
+ json_add_string userID "$_userid"
+ json_add_string sessionID "$_sessionid"
+ json_add_string vasid "$_vasid"
+ json_close_object
+
+ local ret=$($_http_cmd -A "$agent_xl" -d "$(json_dump)" 'https://mobile-login.xunlei.com/getuserinfo')
+ _log "getuserinfo is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errorCode"
+
+ case ${lasterr:=-1} in
+ 0)local index=1 can_down=0 vasid isVip isYear expireDate;json_select "vipList" >/dev/null 2>&1
+ while :;do
+ json_select $index >/dev/null 2>&1
+ [ $? -ne 0 ] && break
+ json_get_var vasid "vasid"
+ json_get_var isVip "isVip"
+ json_get_var isYear "isYear"
+ json_get_var expireDate "expireDate"
+ json_select ".." >/dev/null 2>&1
+ let index++
+ case ${vasid:-0} in
+ 2)[ $down_acc -ne 0 ] && outmsg="迅雷超级会员" || continue;;
+ $vasid_down)outmsg="迅雷快鸟会员";;
+ $vasid_up)outmsg="上行提速会员";;
+ *)continue;;
+ esac
+ if [ ${isVip:-0} = 1 -o ${isYear:-0} = 1 ];then
+ outmsg="${outmsg}有效。会员到期时间:${expireDate:0:4}-${expireDate:4:2}-${expireDate:6:2}"
+ [ $vasid = $vasid_up ] && _log "$outmsg" $(( 1 | 16 )) || _log "$outmsg" $(( 1 | 8 ))
+ [ $vasid -ne $vasid_up ] && can_down=$(( $can_down | 1 ))
+ else
+ if [ ${#expireDate} -ge 8 ];then
+ outmsg="${outmsg}已到期。会员到期时间:${expireDate:0:4}-${expireDate:4:2}-${expireDate:6:2}"
+ else
+ outmsg="${outmsg}无效"
+ fi
+ [ $vasid = $vasid_up ] && _log "$outmsg" $(( 1 | 16 | 32 )) || _log "$outmsg" $(( 1 | 8 | 32 ))
+ [ $vasid = $vasid_up ] && up_acc=0
+ fi
+ done
+ [ $can_down = 0 ] && down_acc=0;;
+ -1)outmsg="获取迅雷会员信息失败。迅雷服务器未响应,请稍候";_log "$outmsg";;
+ *)local errorDesc;json_get_var errorDesc "errorDesc";outmsg="获取迅雷会员信息失败。错误代码: ${lasterr}";[ -n "$errorDesc" ] && outmsg="${outmsg},原因: $errorDesc";_log "$outmsg" $(( 1 | 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 登录时间更新
+swjsq_renewal(){
+ xlnetacc_var 1
+ local limitdate=$(date +%Y%m%d -d "1970.01.01-00:00:$(( $(date +%s) + 30 * 24 * 60 * 60 ))")
+
+ access_url='http://api.ext.swjsq.vip.xunlei.com'
+ local ret=$($_http_cmd -A "$user_agent" "$access_url/renewal?${http_args%&dial_account=*}&limitdate=$limitdate")
+ _log "renewal is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ case ${lasterr:=-1} in
+ 0)local outmsg="更新登录时间成功。帐号登录展期:${limitdate:0:4}-${limitdate:4:2}-${limitdate:6:2}";_log "$outmsg";;
+ -1)local outmsg="更新登录时间失败。迅雷服务器未响应,请稍候";_log "$outmsg";;
+ *)local message;json_get_var message "richmessage";local outmsg="更新登录时间失败。错误代码: ${lasterr}";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 获取提速入口
+swjsq_portal(){
+ xlnetacc_var $1
+
+ [ $1 = 1 ] && access_url='http://api.portal.swjsq.vip.xunlei.com:81/v2/queryportal' || \
+ access_url='http://api.upportal.swjsq.vip.xunlei.com/v2/queryportal'
+ local ret=$($_http_cmd -A "$user_agent" "$access_url")
+ _log "portal $1 is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ case ${lasterr:=-1} in
+ 0)local interface_ip interface_port province sp;json_get_var interface_ip "interface_ip";json_get_var interface_port "interface_port";json_get_var province "province_name";json_get_var sp "sp_name"
+ if [ $1 = 1 ];then
+ _portal_down="http://$interface_ip:$interface_port/v2"
+ _log "_portal_down is $_portal_down" $(( 1 | 4 ))
+ else
+ _portal_up="http://$interface_ip:$interface_port/v2"
+ _log "_portal_up is $_portal_up" $(( 1 | 4 ))
+ fi
+ local outmsg="获取${link_cn}提速入口成功";[ -n "$province" -a -n "$sp" ] && outmsg="${outmsg}。运营商:${province}${sp}";_log "$outmsg" $(( 1 | $1 * 8 ));;
+ -1)local outmsg="获取${link_cn}提速入口失败。迅雷服务器未响应,请稍候";_log "$outmsg";;
+ *)local message;json_get_var message "message";local outmsg="获取${link_cn}提速入口失败。错误代码: ${lasterr}";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 获取网络带宽信息
+isp_bandwidth(){
+ xlnetacc_var $1
+
+ local ret=$($_http_cmd -A "$user_agent" "$access_url/bandwidth?${http_args%&dial_account=*}")
+ _log "bandwidth $1 is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ case ${lasterr:=-1} in
+ # 获取带宽数据
+ 0)local can_upgrade bind_dial_account dial_account stream cur_bandwidth max_bandwidth;[ $1 = 1 ] && stream="downstream" || stream="upstream";json_get_var can_upgrade "can_upgrade"
+ json_get_var bind_dial_account "bind_dial_account";json_get_var dial_account "dial_account";json_select;json_select "bandwidth" >/dev/null 2>&1;json_get_var cur_bandwidth "$stream"
+ json_select;json_select "max_bandwidth" >/dev/null 2>&1;json_get_var max_bandwidth "$stream";json_select;cur_bandwidth=$(( ${cur_bandwidth:-0} / 1024 ));max_bandwidth=$(( ${max_bandwidth:-0} / 1024 ))
+ if [ -n "$bind_dial_account" -a "$bind_dial_account" != "$dial_account" ];then
+ local outmsg="绑定宽带账号 $bind_dial_account 与当前宽带账号 $dial_account 不一致,请联系迅雷客服解绑(每月仅一次)";_log "$outmsg" $(( 1 | 8 | 32 ))
+ down_acc=0;up_acc=0
+ elif [ $can_upgrade = 0 ];then
+ local message;json_get_var message "richmessage";[ -z "$message" ] && json_get_var message "message"
+ local outmsg="${link_cn}无法提速";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | $1 * 8 | 32 ))
+ [ $1 = 1 ] && down_acc=0 || up_acc=0
+ elif [ $cur_bandwidth -ge $max_bandwidth ];then
+ local outmsg="${link_cn}无需提速。当前带宽 ${cur_bandwidth}M,超过最大可提升带宽 ${max_bandwidth}M";_log "$outmsg" $(( 1 | $1 * 8 ))
+ [ $1 = 1 ] && down_acc=0 || up_acc=0
+ else
+ if [ -z "$_dial_account" -a -n "$dial_account" ];then
+ _dial_account=$dial_account
+ _log "_dial_account is $_dial_account" $(( 1 | 4 ))
+ fi
+ local outmsg="${link_cn}可以提速。当前带宽 ${cur_bandwidth}M,可提升至 ${max_bandwidth}M";_log "$outmsg" $(( 1 | $1 * 8 ))
+ fi;;
+ # 724 账号存在异常
+ 724)lasterr=-2;local outmsg="获取${link_cn}网络带宽信息失败。原因: 您的账号存在异常,请联系迅雷客服反馈";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ # 3103 线路暂不支持
+ 3103)lasterr=0;local province sp;json_get_var province "province_name";json_get_var sp "sp_name";local outmsg="${link_cn}无法提速。原因: ${province}${sp}线路暂不支持";_log "$outmsg" $(( 1 | $1 * 8 | 32 ))
+ [ $1 = 1 ] && down_acc=0 || up_acc=0;;
+ -1)local outmsg="获取${link_cn}网络带宽信息失败。运营商服务器未响应,请稍候";_log "$outmsg";;
+ *)local message;json_get_var message "richmessage";[ -z "$message" ] && json_get_var message "message"
+ local outmsg="获取${link_cn}网络带宽信息失败。错误代码: ${lasterr}";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 发送带宽提速信号
+isp_upgrade(){
+ xlnetacc_var $1
+
+ local ret=$($_http_cmd -A "$user_agent" "$access_url/upgrade?$http_args")
+ _log "upgrade $1 is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ case ${lasterr:=-1} in
+ 0)local bandwidth;json_select "bandwidth" >/dev/null 2>&1;json_get_var bandwidth "downstream";bandwidth=$(( ${bandwidth:-0} / 1024 ))
+ local outmsg="${link_cn}提速成功,带宽已提升到 ${bandwidth}M";_log "$outmsg" $(( 1 | $1 * 8 ));[ $1 = 1 ] && down_acc=2 || up_acc=2;;
+ # 812 已处于提速状态
+ 812)lasterr=0;local outmsg="${link_cn}提速成功,当前宽带已处于提速状态";_log "$outmsg" $(( 1 | $1 * 8 ));[ $1 = 1 ] && down_acc=2 || up_acc=2;;
+ # 724 账号存在异常
+ 724)lasterr=-2;local outmsg="${link_cn}提速失败。原因: 您的账号存在异常,请联系迅雷客服反馈";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ -1)local outmsg="${link_cn}提速失败。运营商服务器未响应,请稍候";_log "$outmsg";;
+ *)local message;json_get_var message "richmessage";[ -z "$message" ] && json_get_var message "message"
+ local outmsg="${link_cn}提速失败。错误代码: ${lasterr}";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 发送提速心跳信号
+isp_keepalive(){
+ xlnetacc_var $1
+
+ local ret=$($_http_cmd -A "$user_agent" "$access_url/keepalive?$http_args")
+ _log "keepalive $1 is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ case ${lasterr:=-1} in
+ 0)local outmsg="${link_cn}心跳信号返回正常";_log "$outmsg";;
+ # 513 提速通道不存在
+ 513)lasterr=-2;local outmsg="${link_cn}提速超时,提速通道不存在";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ -1)local outmsg="${link_cn}心跳信号发送失败。运营商服务器未响应,请稍候";_log "$outmsg";;
+ *)local message;json_get_var message "richmessage";[ -z "$message" ] && json_get_var message "message"
+ local outmsg="${link_cn}提速失效。错误代码: ${lasterr}";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 发送带宽恢复信号
+isp_recover(){
+ xlnetacc_var $1
+
+ local ret=$($_http_cmd -A "$user_agent" "$access_url/recover?$http_args")
+ _log "recover $1 is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ case ${lasterr:=-1} in
+ 0)local outmsg="${link_cn}带宽已恢复";_log "$outmsg" $(( 1 | $1 * 8 ));[ $1 = 1 ] && down_acc=1 || up_acc=1;;
+ -1)local outmsg="${link_cn}带宽恢复失败。运营商服务器未响应,请稍候";_log "$outmsg";;
+ *)local message;json_get_var message "richmessage";[ -z "$message" ] && json_get_var message "message"
+ local outmsg="${link_cn}带宽恢复失败。错误代码: ${lasterr}";[ -n "$message" ] && outmsg="${outmsg},原因: $message";_log "$outmsg" $(( 1 | $1 * 8 | 32 ));;
+ esac
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 查询提速信息,未使用
+isp_query(){
+ xlnetacc_var $1
+
+ local ret=$($_http_cmd -A "$user_agent" "$access_url/query_try_info?$http_args")
+ _log "query_try_info $1 is $ret" $(( 1 | 4 ))
+ json_cleanup;json_load "$ret" >/dev/null 2>&1
+ json_get_var lasterr "errno"
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 设置参数变量
+xlnetacc_var(){
+ if [ $1 = 1 ];then
+ let sequence_down++
+ access_url=$_portal_down
+ http_args="sequence=${sequence_down}&client_type=${client_type_down}-${clientVersion}&client_version=${client_type_down//-/}-${clientVersion}&chanel=umeng-10900011&time_and=$(date +%s)000"
+ user_agent=$agent_down
+ link_cn="下行"
+ else
+ let sequence_up++
+ access_url=$_portal_up
+ http_args="sequence=${sequence_up}&client_type=${client_type_up}-${clientVersion}&client_version=${client_type_up//-/}-${clientVersion}"
+ user_agent=$agent_down
+ link_cn="上行"
+ fi
+ http_args="${http_args}&peerid=${_peerid}&userid=${_userid}&sessionid=${_sessionid}&user_type=1&os=android-7.1.1"
+ [ -n "$_dial_account" ] && http_args="${http_args}&dial_account=${_dial_account}"
+}
+
+# 重试循环
+xlnetacc_retry(){
+ if [ $# -ge 3 ];then
+ if [ $3 -ne 0 ];then
+ [ $2 = 1 -a $down_acc -ne $3 ] && return 0
+ [ $2 = 2 -a $up_acc -ne $3 ] && return 0
+ fi
+ fi
+ local retry=1
+ while :;do
+ lasterr=
+ eval $1 $2 && break # 成功
+ [ $# -ge 4 -a $retry -ge $4 ] && break || let retry++ # 重试超时
+ case $lasterr in
+ -1)sleep 5s;;# 服务器未响应
+ -2)break;;# 严重错误
+ *)sleep 3s;;# 其它错误
+ esac
+ done
+
+ [ ${lasterr:-0} = 0 ] && return 0 || return 1
+}
+
+# 注销已登录帐号
+xlnetacc_logout(){
+ [ -z "$_sessionid" ] && return 2
+ [ $# -ge 1 ] && local retry=$1 || local retry=1
+
+ xlnetacc_retry 'isp_recover' 1 2 $retry
+ xlnetacc_retry 'isp_recover' 2 2 $retry
+ xlnetacc_retry 'swjsq_logout' 0 0 $retry
+ [ $down_acc -ne 0 ] && down_acc=1;[ $up_acc -ne 0 ] && up_acc=1
+ _sessionid=;_dial_account=
+
+ [ $lasterr = 0 ] && return 0 || return 1
+}
+
+# 中止信号处理
+sigterm(){
+ _log "trap sigterm, exit" $(( 1 | 4 ))
+ xlnetacc_logout
+ rm -f "$down_state_file" "$up_state_file"
+ exit 0
+}
+
+# 初始化
+xlnetacc_init(){
+ [ "$1" != "--start" ] && return 1
+
+ # 防止重复启动
+ [ -f /var/lock/xlnetacc.lock ] && return 1
+ touch /var/lock/xlnetacc.lock
+
+ # 读取设置
+ readonly NAME=xlnetacc
+ readonly LOGFILE=/var/log/${NAME}.log
+ readonly down_state_file=/var/state/${NAME}_down_state
+ readonly up_state_file=/var/state/${NAME}_up_state
+ down_acc=$(uci_get_by_bool "general" "down_acc" 0)
+ up_acc=$(uci_get_by_bool "general" "up_acc" 0)
+ readonly logging=$(uci_get_by_bool "general" "logging" 1)
+ readonly verbose=$(uci_get_by_bool "general" "verbose" 0)
+ network=$(uci_get_by_name "general" "network" "wan")
+ keepalive=$(uci_get_by_name "general" "keepalive" 10)
+ relogin=$(uci_get_by_name "general" "relogin" 0)
+ readonly username=$(uci_get_by_name "general" "account")
+ readonly password=$(uci_get_by_name "general" "password")
+ local enabled=$(uci_get_by_bool "general" "enabled" 0)
+ ([ $enabled = 0 ] || [ $down_acc = 0 -a $up_acc = 0 ] || [ -z "$username" -o -z "$password" -o -z "$network" ]) && return 2
+ ([ -z "$keepalive" -o -n "${keepalive//[0-9]/}" ] || [ $keepalive -lt 5 -o $keepalive -gt 60 ]) && keepalive=10
+ readonly keepalive=$(( $keepalive ))
+ ([ -z "$relogin" -o -n "${relogin//[0-9]/}" ] || [ $relogin -gt 48 ]) && relogin=0
+ readonly relogin=$(( $relogin * 60 * 60 ))
+
+ [ $logging = 1 ] && [ ! -d /var/log ] && mkdir -p /var/log
+ [ -f "$LOGFILE" ] && _log "------------------------------"
+ _log "迅雷快鸟正在启动..."
+
+ # 检查外部调用工具
+ command -v curl >/dev/null || { _log "cURL 未安装";return 3;}
+ local opensslchk=$(echo -n 'openssl' | openssl dgst -sha1 | awk '{print $2}')
+ [ "$opensslchk" != 'c898fa1e7226427010e329971e82c669f8d8abb4' ] && { _log "openssl-util 未安装或计算错误";return 3;}
+
+ # 捕获中止信号
+ trap 'sigterm' INT # Ctrl-C
+ trap 'sigterm' QUIT # Ctrl-\
+ trap 'sigterm' TERM # kill
+
+ # 生成设备标识
+ gen_device_sign
+ [ ${#_peerid} -ne 16 -o ${#_devicesign} -ne 71 ] && return 4
+
+ clean_log
+ [ -d /var/state ] || mkdir -p /var/state
+ rm -f "$down_state_file" "$up_state_file"
+ return 0
+}
+
+# 程序主体
+xlnetacc_main(){
+ while :;do
+ # 获取外网IP地址
+ xlnetacc_retry 'get_bind_ip'
+ _http_cmd="curl -Lfs -m 5 --interface $_bind_ip"
+
+ # 注销快鸟帐号
+ xlnetacc_logout 3 && sleep 3s
+
+ # 登录快鸟帐号
+ while :;do
+ lasterr=
+ swjsq_login
+ case $lasterr in
+ 0)break;;# 登录成功
+ -1)sleep 5s;;# 服务器未响应
+ -2)return 7;;# cURL 参数解析错误
+ -3)sleep 3s;;# cURL 网络通信失败
+ 6)sleep 130m;;# 需要输入验证码
+ 8)sleep 3m;;# 服务器系统维护
+ 15)sleep 1s;;# 身份信息已失效
+ *)return 5;;# 登录失败
+ esac
+ done
+
+ # 获取用户信息
+ xlnetacc_retry 'swjsq_getuserinfo'
+ [ $down_acc = 0 -a $up_acc = 0 ] && break
+ # 登录时间更新
+ xlnetacc_retry 'swjsq_renewal'
+ # 获取提速入口
+ xlnetacc_retry 'swjsq_portal' 1 1
+ xlnetacc_retry 'swjsq_portal' 2 1
+ # 获取带宽信息
+ xlnetacc_retry 'isp_bandwidth' 1 1 10 || { sleep 3m;continue;}
+ xlnetacc_retry 'isp_bandwidth' 2 1 10 || { sleep 3m;continue;}
+ [ $down_acc = 0 -a $up_acc = 0 ] && break
+ # 带宽提速
+ xlnetacc_retry 'isp_upgrade' 1 1 10 || { sleep 3m;continue;}
+ xlnetacc_retry 'isp_upgrade' 2 1 10 || { sleep 3m;continue;}
+
+ # 心跳保持
+ local timer=$(date +%s)
+ while :;do
+ clean_log # 清理日志
+ sleep ${keepalive}m
+ [ $relogin -ne 0 -a $(( $(date +%s) - $timer )) -ge $relogin ] && break # 登录超时
+ xlnetacc_retry 'isp_keepalive' 1 2 5 || break
+ xlnetacc_retry 'isp_keepalive' 2 2 5 || break
+ done
+ done
+ xlnetacc_logout
+ _log "无法提速,迅雷快鸟已停止。"
+ return 6
+}
+
+# 程序入口
+xlnetacc_init "$@" && xlnetacc_main
+exit $?
diff --git a/luci-app-xlnetacc/root/usr/share/rpcd/acl.d/luci-app-xlnetacc.json b/luci-app-xlnetacc/root/usr/share/rpcd/acl.d/luci-app-xlnetacc.json
new file mode 100644
index 000000000..755eab6d9
--- /dev/null
+++ b/luci-app-xlnetacc/root/usr/share/rpcd/acl.d/luci-app-xlnetacc.json
@@ -0,0 +1,11 @@
+{
+ "luci-app-xlnetacc": {
+ "description": "Grant UCI access for luci-app-xlnetacc",
+ "read": {
+ "uci": [ "xlnetacc" ]
+ },
+ "write": {
+ "uci": [ "xlnetacc" ]
+ }
+ }
+}
diff --git a/luci-app-zerotier/Makefile b/luci-app-zerotier/Makefile
new file mode 100644
index 000000000..80fa08c2a
--- /dev/null
+++ b/luci-app-zerotier/Makefile
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI for Zerotier
+LUCI_DEPENDS:=+zerotier
+LUCI_PKGARCH:=all
+PKG_VERSION:=1.0
+PKG_RELEASE:=20
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-zerotier/luasrc/controller/zerotier.lua b/luci-app-zerotier/luasrc/controller/zerotier.lua
new file mode 100644
index 000000000..9c69c063e
--- /dev/null
+++ b/luci-app-zerotier/luasrc/controller/zerotier.lua
@@ -0,0 +1,23 @@
+module("luci.controller.zerotier",package.seeall)
+
+function index()
+ if not nixio.fs.access("/etc/config/zerotier") then
+ return
+ end
+
+ entry({"admin","vpn"}, firstchild(), "VPN", 45).dependent = false
+
+ entry({"admin", "vpn", "zerotier"},firstchild(), _("ZeroTier")).dependent = false
+
+ entry({"admin", "vpn", "zerotier", "general"}, cbi("zerotier/settings"), _("Base Setting"), 1)
+ entry({"admin", "vpn", "zerotier", "log"}, form("zerotier/info"), _("Interface Info"), 2)
+
+ entry({"admin", "vpn", "zerotier", "status"}, call("act_status"))
+end
+
+function act_status()
+ local e = {}
+ e.running = luci.sys.call("pgrep /usr/bin/zerotier-one >/dev/null") == 0
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(e)
+end
diff --git a/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua b/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua
new file mode 100644
index 000000000..199c468dd
--- /dev/null
+++ b/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua
@@ -0,0 +1,15 @@
+local fs = require "nixio.fs"
+local conffile = "/tmp/zero.info"
+
+f = SimpleForm("logview")
+
+t = f:field(TextValue, "conf")
+t.rmempty = true
+t.rows = 19
+function t.cfgvalue()
+ luci.sys.exec("for i in $(ifconfig | grep 'zt' | awk '{print $1}'); do ifconfig $i; done > /tmp/zero.info")
+ return fs.readfile(conffile) or ""
+end
+t.readonly="readonly"
+
+return f
diff --git a/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua b/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua
new file mode 100644
index 000000000..32ef1bbfc
--- /dev/null
+++ b/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua
@@ -0,0 +1,27 @@
+a = Map("zerotier")
+a.title = translate("ZeroTier")
+a.description = translate("Zerotier is an open source, cross-platform and easy to use virtual LAN")
+
+a:section(SimpleSection).template = "zerotier/zerotier_status"
+
+t = a:section(NamedSection, "sample_config", "zerotier")
+t.anonymous = true
+t.addremove = false
+
+e = t:option(Flag, "enabled", translate("Enable"))
+e.default = 0
+e.rmempty=false
+
+e = t:option(DynamicList, "join", translate('ZeroTier Network ID'))
+e.password = true
+e.rmempty = false
+
+e = t:option(Flag, "nat", translate("Auto NAT Clients"))
+e.description = translate("Allow zerotier clients access your LAN network")
+e.default = 0
+e.rmempty = false
+
+e = t:option(DummyValue, "opennewwindow", translate(""))
+e.description = translate("Create or manage your zerotier network, and auth clients who could access")
+
+return a
diff --git a/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm b/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm
new file mode 100644
index 000000000..9d216c5d9
--- /dev/null
+++ b/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm
@@ -0,0 +1,22 @@
+
+
+
+
+ <%:Collecting data...%>
+
+
diff --git a/luci-app-zerotier/po/zh-cn/zerotier.po b/luci-app-zerotier/po/zh-cn/zerotier.po
new file mode 100644
index 000000000..9ed1270b3
--- /dev/null
+++ b/luci-app-zerotier/po/zh-cn/zerotier.po
@@ -0,0 +1,25 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Gtranslator 2.91.7\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgid "Zerotier is an open source, cross-platform and easy to use virtual LAN"
+msgstr "Zerotier 是一个开源,跨平台,而且适合内网穿透互联的傻瓜配置虚拟 VPN LAN"
+
+msgid "Auto NAT Clients"
+msgstr "自动允许客户端 NAT"
+
+msgid "Allow zerotier clients access your LAN network"
+msgstr "允许 Zerotier 的拨入客户端访问路由器 LAN 资源(需要在 Zerotier 管理页面设定到 LAN 网段的路由表)"
+
+msgid "Create or manage your zerotier network, and auth clients who could access"
+msgstr "点击跳转到 Zerotier 官网管理平台,新建或者管理网络,并允许客户端接入访问你私人网路(新接入的节点默认不允许访问)"
+
+msgid "Base Setting"
+msgstr "基本设置"
+
+msgid "Interface Info"
+msgstr "接口信息"
diff --git a/luci-app-zerotier/root/etc/init.d/zerotier b/luci-app-zerotier/root/etc/init.d/zerotier
new file mode 100755
index 000000000..0403e5d17
--- /dev/null
+++ b/luci-app-zerotier/root/etc/init.d/zerotier
@@ -0,0 +1,113 @@
+#!/bin/sh /etc/rc.common
+
+START=99
+
+USE_PROCD=1
+
+PROG=/usr/bin/zerotier-one
+CONFIG_PATH=/var/lib/zerotier-one
+
+service_triggers() {
+ procd_add_reload_trigger "zerotier"
+ procd_add_interface_trigger "interface.*.up" wan /etc/init.d/zerotier restart
+}
+
+section_enabled() {
+ config_get_bool enabled "$1" 'enabled' 0
+ [ $enabled -gt 0 ]
+}
+
+start_instance() {
+ local cfg="$1"
+ local port secret config_path
+ local ARGS=""
+
+ if ! section_enabled "$cfg"; then
+ echo "disabled in config"
+ return 1
+ fi
+
+ [ -d /etc/config/zero ] || mkdir -p /etc/config/zero
+ config_path=/etc/config/zero
+
+ config_get_bool port $cfg 'port'
+ config_get secret $cfg 'secret'
+
+ # Remove existing link or folder
+ rm -rf $CONFIG_PATH
+
+ # Create link from CONFIG_PATH to config_path
+ if [ -n "$config_path" -a "$config_path" != $CONFIG_PATH ]; then
+ if [ ! -d "$config_path" ]; then
+ echo "ZeroTier config_path does not exist: $config_path"
+ return
+ fi
+
+ ln -s $config_path $CONFIG_PATH
+ fi
+
+ mkdir -p $CONFIG_PATH/networks.d
+
+ if [ -n "$port" ]; then
+ ARGS="$ARGS -p$port"
+ fi
+
+ if [ "$secret" = "generate" ]; then
+ echo "Generate secret - please wait..."
+ local sf="/tmp/zt.$cfg.secret"
+
+ zerotier-idtool generate "$sf" > /dev/null
+ [ $? -ne 0 ] && return 1
+
+ secret="$(cat $sf)"
+ rm "$sf"
+
+ uci set zerotier.$cfg.secret="$secret"
+ uci commit zerotier
+ fi
+
+ if [ -n "$secret" ]; then
+ echo "$secret" > $CONFIG_PATH/identity.secret
+ # make sure there is not previous identity.public
+ rm -f $CONFIG_PATH/identity.public
+ fi
+
+ add_join() {
+ # an (empty) config file will cause ZT to join a network
+ touch $CONFIG_PATH/networks.d/$1.conf
+ }
+
+ config_list_foreach $cfg 'join' add_join
+
+ procd_open_instance
+ procd_set_param command $PROG $ARGS $CONFIG_PATH
+ procd_set_param stderr 1
+ procd_close_instance
+}
+
+start_service() {
+ config_load 'zerotier'
+ config_foreach start_instance 'zerotier'
+ touch /tmp/zero.log && /etc/zerotier.start > /tmp/zero.log 2>&1 &
+}
+
+stop_instance() {
+ rm -f /tmp/zero.log
+ local cfg="$1"
+
+ /etc/zerotier.stop > /tmp/zero.log 2>&1 &
+
+ # Remove existing link or folder
+ rm -f $CONFIG_PATH/networks.d/*.conf
+ rm -rf $CONFIG_PATH
+}
+
+stop_service() {
+ config_load 'zerotier'
+ config_foreach stop_instance 'zerotier'
+}
+
+reload_service() {
+ stop
+ start
+}
\ No newline at end of file
diff --git a/luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier b/luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier
new file mode 100755
index 000000000..95f0ccfc9
--- /dev/null
+++ b/luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+ delete ucitrack.@zerotier[-1]
+ add ucitrack zerotier
+ set ucitrack.@zerotier[-1].init=zerotier
+ commit ucitrack
+ delete firewall.zerotier
+ set firewall.zerotier=include
+ set firewall.zerotier.type=script
+ set firewall.zerotier.path=/etc/zerotier.start
+ set firewall.zerotier.reload=1
+ commit firewall
+EOF
+
+rm -f /tmp/luci-indexcache
+exit 0
diff --git a/luci-app-zerotier/root/etc/zerotier.start b/luci-app-zerotier/root/etc/zerotier.start
new file mode 100755
index 000000000..b43e5f974
--- /dev/null
+++ b/luci-app-zerotier/root/etc/zerotier.start
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+zero_enable="$(uci get zerotier.sample_config.enabled)"
+
+[ "${zero_enable}" -ne "1" ] && exit 0
+
+[ -f "/tmp/zero.log" ] && {
+ while [ "$(ifconfig | grep 'zt' | awk '{print $1}')" = "" ]
+ do
+ sleep 1
+ done
+}
+
+nat_enable="$(uci get zerotier.sample_config.nat)"
+zt0="$(ifconfig | grep 'zt' | awk '{print $1}')"
+echo "${zt0}" > "/tmp/zt.nif"
+
+[ "${nat_enable}" -eq "1" ] && {
+ for i in ${zt0}
+ do
+ ip_segment=""
+ iptables -I FORWARD -i "$i" -j ACCEPT
+ iptables -I FORWARD -o "$i" -j ACCEPT
+ iptables -t nat -I POSTROUTING -o "$i" -j MASQUERADE
+ ip_segment="$(ip route | grep "dev $i proto kernel" | awk '{print $1}')"
+ iptables -t nat -I POSTROUTING -s "${ip_segment}" -j MASQUERADE
+ done
+}
diff --git a/luci-app-zerotier/root/etc/zerotier.stop b/luci-app-zerotier/root/etc/zerotier.stop
new file mode 100755
index 000000000..07450183e
--- /dev/null
+++ b/luci-app-zerotier/root/etc/zerotier.stop
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+zt0="$(ifconfig | grep 'zt' | awk '{print $1}')"
+[ -z "${zt0}" ] && zt0="$(cat "/tmp/zt.nif")"
+
+for i in ${zt0}
+do
+ ip_segment=""
+ iptables -D FORWARD -i "$i" -j ACCEPT 2>/dev/null
+ iptables -D FORWARD -o "$i" -j ACCEPT 2>/dev/null
+ iptables -t nat -D POSTROUTING -o "$i" -j MASQUERADE 2>/dev/null
+ ip_segment="$(ip route | grep "dev $i proto" | awk '{print $1}')"
+ iptables -t nat -D POSTROUTING -s "${ip_segment}" -j MASQUERADE 2>/dev/null
+ echo "zt interface $i is stopped!"
+done
\ No newline at end of file
diff --git a/luci-app-zerotier/root/etc/zerotier/zerotier.log b/luci-app-zerotier/root/etc/zerotier/zerotier.log
new file mode 100644
index 000000000..e69de29bb
diff --git a/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json b/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json
new file mode 100644
index 000000000..2724e99bf
--- /dev/null
+++ b/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json
@@ -0,0 +1,11 @@
+{
+ "luci-app-zerotier": {
+ "description": "Grant UCI access for luci-app-zerotier",
+ "read": {
+ "uci": [ "zerotier" ]
+ },
+ "write": {
+ "uci": [ "zerotier" ]
+ }
+ }
+}
diff --git a/luci-lib-docker/Makefile b/luci-lib-docker/Makefile
new file mode 100644
index 000000000..5cf246ab3
--- /dev/null
+++ b/luci-lib-docker/Makefile
@@ -0,0 +1,19 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=luci-lib-docker
+PKG_VERSION:=v0.3.4
+PKG_MAINTAINER:=lisaac \
+ Florian Eckert
+PKG_LICENSE:=AGPL-3.0
+
+LUCI_TYPE:=col
+
+LUCI_TITLE:=LuCI library for docker
+LUCI_DESCRIPTION:=Docker Engine API for LuCI
+
+LUCI_DEPENDS:=@(aarch64||arm||x86_64) +luci-lib-jsonc
+LUCI_PKGARCH:=all
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-lib-docker/luasrc/docker.lua b/luci-lib-docker/luasrc/docker.lua
new file mode 100644
index 000000000..b99f6ce0e
--- /dev/null
+++ b/luci-lib-docker/luasrc/docker.lua
@@ -0,0 +1,453 @@
+--[[
+LuCI - Lua Configuration Interface
+Copyright 2019 lisaac
+]]--
+require "nixio.util"
+require "luci.util"
+local jsonc = require "luci.jsonc"
+local nixio = require "nixio"
+local ltn12 = require "luci.ltn12"
+local fs = require "nixio.fs"
+
+local urlencode = luci.util.urlencode or luci.http and luci.http.protocol and luci.http.protocol.urlencode
+local json_stringify = jsonc.stringify
+local json_parse = jsonc.parse
+
+local chunksource = function(sock, buffer)
+ buffer = buffer or ""
+ return function()
+ local output
+ local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
+ if not count then
+ local newblock, code = sock:recv(1024)
+ if not newblock then return nil, code end
+ buffer = buffer .. newblock
+ _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
+ end
+ count = tonumber(count, 16)
+ if not count then
+ return nil, -1, "invalid encoding"
+ elseif count == 0 then -- finial
+ return nil
+ elseif count <= #buffer - endp then
+ --data >= count
+ output = buffer:sub(endp + 1, endp + count)
+ if count == #buffer - endp then -- [data]
+ buffer = buffer:sub(endp + count + 1)
+ count, code = sock:recvall(2) --read \r\n
+ if not count then return nil, code end
+ elseif count + 1 == #buffer - endp then -- [data]\r
+ buffer = buffer:sub(endp + count + 2)
+ count, code = sock:recvall(1) --read \n
+ if not count then return nil, code end
+ else -- [data]\r\n[count]\r\n[data]...
+ buffer = buffer:sub(endp + count + 3) -- cut buffer
+ end
+ return output
+ else
+ -- data < count
+ output = buffer:sub(endp + 1, endp + count)
+ buffer = buffer:sub(endp + count + 1)
+ local remain, code = sock:recvall(count - #output) --need read remaining
+ if not remain then return nil, code end
+ output = output .. remain
+ count, code = sock:recvall(2) --read \r\n
+ if not count then return nil, code end
+ return output
+ end
+ end
+end
+
+local chunksink = function (sock)
+ return function(chunk, err)
+ if not chunk then
+ return sock:writeall("0\r\n\r\n")
+ else
+ return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, tostring(chunk)))
+ end
+ end
+end
+
+local docker_stream_filter = function(buffer)
+ buffer = buffer or ""
+ if #buffer < 8 then
+ return ""
+ end
+ local stream_type = ((string.byte(buffer, 1) == 1) and "stdout") or ((string.byte(buffer, 1) == 2) and "stderr") or ((string.byte(buffer, 1) == 0) and "stdin") or "stream_err"
+ local valid_length =
+ tonumber(string.byte(buffer, 5)) * 256 * 256 * 256 + tonumber(string.byte(buffer, 6)) * 256 * 256 + tonumber(string.byte(buffer, 7)) * 256 + tonumber(string.byte(buffer, 8))
+ if valid_length > #buffer + 8 then
+ return ""
+ end
+ return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8)
+ -- return string.sub(buffer, 9, valid_length + 8)
+end
+
+local open_socket = function(req_options)
+ local socket
+ if type(req_options) ~= "table" then return socket end
+ if req_options.socket_path then
+ socket = nixio.socket("unix", "stream")
+ if socket:connect(req_options.socket_path) ~= true then return nil end
+ elseif req_options.host and req_options.port then
+ socket = nixio.connect(req_options.host, req_options.port)
+ end
+ if socket then
+ return socket
+ else
+ return nil
+ end
+end
+
+local send_http_socket = function(options, docker_socket, req_header, req_body, callback)
+ if docker_socket:send(req_header) == 0 then
+ return {
+ headers={code=498,message="bad path", protocol="HTTP/1.1"},
+ body={message="can\'t send data to socket"}
+ }
+ end
+
+ if req_body and type(req_body) == "function" and req_header and req_header:match("chunked") then
+ -- chunked send
+ req_body(chunksink(docker_socket))
+ elseif req_body and type(req_body) == "function" then
+ -- normal send by req_body function
+ req_body(docker_socket)
+ elseif req_body and type(req_body) == "table" then
+ -- json
+ docker_socket:send(json_stringify(req_body))
+ if options.debug then io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path) end
+ elseif req_body then
+ docker_socket:send(req_body)
+ if options.debug then io.popen("echo '".. req_body .. "' >> " .. options.debug_path) end
+ end
+
+ local linesrc = docker_socket:linesource()
+ -- read socket using source http://w3.impa.br/~diego/software/luasocket/ltn12.html
+ --http://lua-users.org/wiki/FiltersSourcesAndSinks
+ -- handle response header
+ local line = linesrc()
+ if not line then
+ docker_socket:close()
+ return {
+ headers = {code=499, message="bad socket path", protocol="HTTP/1.1"},
+ body = {message="no data receive from socket"}
+ }
+ end
+ local response = {code = 0, headers = {}, body = {}}
+
+ local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
+ response.protocol = p
+ response.code = tonumber(code)
+ response.message = msg
+ line = linesrc()
+ while line and line ~= "" do
+ local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
+ if key and key ~= "Status" then
+ if type(response.headers[key]) == "string" then
+ response.headers[key] = {response.headers[key], val}
+ elseif type(response.headers[key]) == "table" then
+ response.headers[key][#response.headers[key] + 1] = val
+ else
+ response.headers[key] = val
+ end
+ end
+ line = linesrc()
+ end
+ -- handle response body
+ local body_buffer = linesrc(true)
+ response.body = {}
+ if type(callback) ~= "function" then
+ if response.headers["Transfer-Encoding"] == "chunked" then
+ local source = chunksource(docker_socket, body_buffer)
+ code = ltn12.pump.all(source, (ltn12.sink.table(response.body))) and response.code or 555
+ response.code = code
+ else
+ local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
+ code = ltn12.pump.all(body_source, (ltn12.sink.table(response.body))) and response.code or 555
+ response.code = code
+ end
+ else
+ if response.headers["Transfer-Encoding"] == "chunked" then
+ local source = chunksource(docker_socket, body_buffer)
+ callback(response, source)
+ else
+ local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
+ callback(response, body_source)
+ end
+ end
+ docker_socket:close()
+ return response
+end
+
+local gen_header = function(options, http_method, api_group, api_action, name_or_id, request)
+ local header, query, path
+ name_or_id = (name_or_id ~= "") and name_or_id or nil
+
+ if request and type(request.query) == "table" then
+ local k, v
+ for k, v in pairs(request.query) do
+ if type(v) == "table" then
+ query = (query and query .. "&" or "?") .. k .. "=" .. urlencode(json_stringify(v))
+ elseif type(v) == "boolean" then
+ query = (query and query .. "&" or "?") .. k .. "=" .. (v and "true" or "false")
+ elseif type(v) == "number" or type(v) == "string" then
+ query = (query and query .. "&" or "?") .. k .. "=" .. v
+ end
+ end
+ end
+ path = (api_group and ("/" .. api_group) or "") .. (name_or_id and ("/" .. name_or_id) or "") .. (api_action and ("/" .. api_action) or "") .. (query or "")
+ header = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
+ header = header .. "Host: " .. options.host .. "\r\n"
+ header = header .. "User-Agent: " .. options.user_agent .. "\r\n"
+ header = header .. "Connection: close\r\n"
+
+ if request and type(request.header) == "table" then
+ local k, v
+ for k, v in pairs(request.header) do
+ header = header .. k .. ": " .. v .. "\r\n"
+ end
+ end
+
+ -- when requst_body is function, we need to custom header using custom header
+ if request and request.body and type(request.body) == "function" then
+ if not header:match("Content-Length:") then
+ header = header .. "Transfer-Encoding: chunked\r\n"
+ end
+ elseif http_method == "POST" and request and request.body and type(request.body) == "table" then
+ local conetnt_json = json_stringify(request.body)
+ header = header .. "Content-Type: application/json\r\n"
+ header = header .. "Content-Length: " .. #conetnt_json .. "\r\n"
+ elseif request and request.body and type(request.body) == "string" then
+ header = header .. "Content-Length: " .. #request.body .. "\r\n"
+ end
+ header = header .. "\r\n"
+ if options.debug then io.popen("echo '".. header .. "' >> " .. options.debug_path) end
+ return header
+end
+
+local call_docker = function(options, http_method, api_group, api_action, name_or_id, request, callback)
+ local req_options = setmetatable({}, {__index = options})
+
+ local req_header = gen_header(req_options, http_method, api_group, api_action, name_or_id, request)
+ local req_body = request and request.body or nil
+ local docker_socket = open_socket(req_options)
+
+ if docker_socket then
+ return send_http_socket(options, docker_socket, req_header, req_body, callback)
+ else
+ return {
+ headers = {code=497, message="bad socket path or host", protocol="HTTP/1.1"},
+ body = {message="can\'t connect to socket"}
+ }
+ end
+end
+
+local gen_api = function(_table, http_method, api_group, api_action)
+ local _api_action
+ if api_action == "get_archive" or api_action == "put_archive" then
+ _api_action = "archive"
+ elseif api_action == "df" then
+ _api_action = "system/df"
+ elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
+ _api_action = api_action
+ elseif (api_group == "containers" or api_group == "images" or api_group == "exec") and (api_action == "list" or api_action == "inspect") then
+ _api_action = "json"
+ end
+
+ local fp = function(self, request, callback)
+ local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
+ if api_action == "list" then
+ if (name_or_id ~= "" and name_or_id ~= nil) then
+ if api_group == "images" then
+ name_or_id = nil
+ else
+ request.query = request and request.query or {}
+ request.query.filters = request.query.filters or {}
+ request.query.filters.name = request.query.filters.name or {}
+ request.query.filters.name[#request.query.filters.name + 1] = name_or_id
+ name_or_id = nil
+ end
+ end
+ elseif api_action == "create" then
+ if (name_or_id ~= "" and name_or_id ~= nil) then
+ request.query = request and request.query or {}
+ request.query.name = request.query.name or name_or_id
+ name_or_id = nil
+ end
+ elseif api_action == "logs" then
+ local body_buffer = ""
+ local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
+ if response.code >= 200 and response.code < 300 then
+ for i, v in ipairs(response.body) do
+ body_buffer = body_buffer .. docker_stream_filter(response.body[i])
+ end
+ response.body = body_buffer
+ end
+ return response
+ end
+ local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
+ if response.headers and response.headers["Content-Type"] == "application/json" then
+ if #response.body == 1 then
+ response.body = json_parse(response.body[1])
+ else
+ local tmp = {}
+ for _, v in ipairs(response.body) do
+ tmp[#tmp+1] = json_parse(v)
+ end
+ response.body = tmp
+ end
+ end
+ return response
+ end
+
+ if api_group then
+ _table[api_group][api_action] = fp
+ else
+ _table[api_action] = fp
+ end
+end
+
+local _docker = {containers = {}, exec = {}, images = {}, networks = {}, volumes = {}}
+
+gen_api(_docker, "GET", "containers", "list")
+gen_api(_docker, "POST", "containers", "create")
+gen_api(_docker, "GET", "containers", "inspect")
+gen_api(_docker, "GET", "containers", "top")
+gen_api(_docker, "GET", "containers", "logs")
+gen_api(_docker, "GET", "containers", "changes")
+gen_api(_docker, "GET", "containers", "stats")
+gen_api(_docker, "POST", "containers", "resize")
+gen_api(_docker, "POST", "containers", "start")
+gen_api(_docker, "POST", "containers", "stop")
+gen_api(_docker, "POST", "containers", "restart")
+gen_api(_docker, "POST", "containers", "kill")
+gen_api(_docker, "POST", "containers", "update")
+gen_api(_docker, "POST", "containers", "rename")
+gen_api(_docker, "POST", "containers", "pause")
+gen_api(_docker, "POST", "containers", "unpause")
+gen_api(_docker, "POST", "containers", "update")
+gen_api(_docker, "DELETE", "containers", "remove")
+gen_api(_docker, "POST", "containers", "prune")
+gen_api(_docker, "POST", "containers", "exec")
+gen_api(_docker, "POST", "exec", "start")
+gen_api(_docker, "POST", "exec", "resize")
+gen_api(_docker, "GET", "exec", "inspect")
+gen_api(_docker, "GET", "containers", "get_archive")
+gen_api(_docker, "PUT", "containers", "put_archive")
+gen_api(_docker, "GET", "containers", "export")
+-- TODO: attch
+
+gen_api(_docker, "GET", "images", "list")
+gen_api(_docker, "POST", "images", "create")
+gen_api(_docker, "GET", "images", "inspect")
+gen_api(_docker, "GET", "images", "history")
+gen_api(_docker, "POST", "images", "tag")
+gen_api(_docker, "DELETE", "images", "remove")
+gen_api(_docker, "GET", "images", "search")
+gen_api(_docker, "POST", "images", "prune")
+gen_api(_docker, "GET", "images", "get")
+gen_api(_docker, "POST", "images", "load")
+
+gen_api(_docker, "GET", "networks", "list")
+gen_api(_docker, "GET", "networks", "inspect")
+gen_api(_docker, "DELETE", "networks", "remove")
+gen_api(_docker, "POST", "networks", "create")
+gen_api(_docker, "POST", "networks", "connect")
+gen_api(_docker, "POST", "networks", "disconnect")
+gen_api(_docker, "POST", "networks", "prune")
+
+gen_api(_docker, "GET", "volumes", "list")
+gen_api(_docker, "GET", "volumes", "inspect")
+gen_api(_docker, "DELETE", "volumes", "remove")
+gen_api(_docker, "POST", "volumes", "create")
+
+gen_api(_docker, "GET", nil, "events")
+gen_api(_docker, "GET", nil, "version")
+gen_api(_docker, "GET", nil, "info")
+gen_api(_docker, "GET", nil, "_ping")
+gen_api(_docker, "GET", nil, "df")
+
+function _docker.new(options)
+ local docker = {}
+ local _options = options or {}
+ docker.options = {
+ socket_path = _options.socket_path or nil,
+ host = _options.socket_path and "localhost" or _options.host,
+ port = not _options.socket_path and _options.port or nil,
+ tls = _options.tls or nil,
+ tls_cacert = _options.tls and _options.tls_cacert or nil,
+ tls_cert = _options.tls and _options.tls_cert or nil,
+ tls_key = _options.tls and _options.tls_key or nil,
+ version = _options.version or "v1.40",
+ user_agent = _options.user_agent or "LuCI",
+ protocol = _options.protocol or "HTTP/1.1",
+ debug = _options.debug or false,
+ debug_path = _options.debug and _options.debug_path or nil
+ }
+ setmetatable(
+ docker,
+ {
+ __index = function(t, key)
+ if _docker[key] ~= nil then
+ return _docker[key]
+ else
+ return _docker.containers[key]
+ end
+ end
+ }
+ )
+ setmetatable(
+ docker.containers,
+ {
+ __index = function(t, key)
+ if key == "options" then
+ return docker.options
+ end
+ end
+ }
+ )
+ setmetatable(
+ docker.networks,
+ {
+ __index = function(t, key)
+ if key == "options" then
+ return docker.options
+ end
+ end
+ }
+ )
+ setmetatable(
+ docker.images,
+ {
+ __index = function(t, key)
+ if key == "options" then
+ return docker.options
+ end
+ end
+ }
+ )
+ setmetatable(
+ docker.volumes,
+ {
+ __index = function(t, key)
+ if key == "options" then
+ return docker.options
+ end
+ end
+ }
+ )
+ setmetatable(
+ docker.exec,
+ {
+ __index = function(t, key)
+ if key == "options" then
+ return docker.options
+ end
+ end
+ }
+ )
+ return docker
+end
+
+return _docker
diff --git a/luci-lib-fs/Makefile b/luci-lib-fs/Makefile
new file mode 100644
index 000000000..9d78a2971
--- /dev/null
+++ b/luci-lib-fs/Makefile
@@ -0,0 +1,49 @@
+#
+# Copyright (C) 2009 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=luci-lib-fs
+PKG_VERSION:=1.0
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+PKG_INSTALL:=1
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/luci-lib-fs
+ SUBMENU:=Lua
+ SECTION:=lang
+ CATEGORY:=Languages
+ TITLE:=luci-lib-fs
+ PKGARCH:=all
+ URL:=https://github.com/lbthomsen/openwrt-luci
+ DEPENDS:=+luci +luci-lib-nixio
+endef
+
+define Package/luci-lib-fs/description
+ luci-lib-fs
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Build/Install
+endef
+
+
+define Package/luci-lib-fs/install
+ $(INSTALL_DIR) $(1)/usr/lib/lua/luci
+ $(CP) ./files/*.lua $(1)/usr/lib/lua/luci
+
+endef
+
+$(eval $(call BuildPackage,luci-lib-fs))
diff --git a/luci-lib-fs/files/fs.lua b/luci-lib-fs/files/fs.lua
new file mode 100644
index 000000000..a81ff675d
--- /dev/null
+++ b/luci-lib-fs/files/fs.lua
@@ -0,0 +1,244 @@
+--[[
+LuCI - Filesystem tools
+
+Description:
+A module offering often needed filesystem manipulation functions
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+local io = require "io"
+local os = require "os"
+local ltn12 = require "luci.ltn12"
+local fs = require "nixio.fs"
+local nutil = require "nixio.util"
+
+local type = type
+
+--- LuCI filesystem library.
+module "luci.fs"
+
+--- Test for file access permission on given path.
+-- @class function
+-- @name access
+-- @param str String value containing the path
+-- @return Number containing the return code, 0 on sucess or nil on error
+-- @return String containing the error description (if any)
+-- @return Number containing the os specific errno (if any)
+access = fs.access
+
+--- Evaluate given shell glob pattern and return a table containing all matching
+-- file and directory entries.
+-- @class function
+-- @name glob
+-- @param filename String containing the path of the file to read
+-- @return Table containing file and directory entries or nil if no matches
+-- @return String containing the error description (if no matches)
+-- @return Number containing the os specific errno (if no matches)
+function glob(...)
+ local iter, code, msg = fs.glob(...)
+ if iter then
+ return nutil.consume(iter)
+ else
+ return nil, code, msg
+ end
+end
+
+--- Checks wheather the given path exists and points to a regular file.
+-- @param filename String containing the path of the file to test
+-- @return Boolean indicating wheather given path points to regular file
+function isfile(filename)
+ return fs.stat(filename, "type") == "reg"
+end
+
+--- Checks wheather the given path exists and points to a directory.
+-- @param dirname String containing the path of the directory to test
+-- @return Boolean indicating wheather given path points to directory
+function isdirectory(dirname)
+ return fs.stat(dirname, "type") == "dir"
+end
+
+--- Read the whole content of the given file into memory.
+-- @param filename String containing the path of the file to read
+-- @return String containing the file contents or nil on error
+-- @return String containing the error message on error
+readfile = fs.readfile
+
+--- Write the contents of given string to given file.
+-- @param filename String containing the path of the file to read
+-- @param data String containing the data to write
+-- @return Boolean containing true on success or nil on error
+-- @return String containing the error message on error
+writefile = fs.writefile
+
+--- Copies a file.
+-- @param source Source file
+-- @param dest Destination
+-- @return Boolean containing true on success or nil on error
+copy = fs.datacopy
+
+--- Renames a file.
+-- @param source Source file
+-- @param dest Destination
+-- @return Boolean containing true on success or nil on error
+rename = fs.move
+
+--- Get the last modification time of given file path in Unix epoch format.
+-- @param path String containing the path of the file or directory to read
+-- @return Number containing the epoch time or nil on error
+-- @return String containing the error description (if any)
+-- @return Number containing the os specific errno (if any)
+function mtime(path)
+ return fs.stat(path, "mtime")
+end
+
+--- Set the last modification time of given file path in Unix epoch format.
+-- @param path String containing the path of the file or directory to read
+-- @param mtime Last modification timestamp
+-- @param atime Last accessed timestamp
+-- @return 0 in case of success nil on error
+-- @return String containing the error description (if any)
+-- @return Number containing the os specific errno (if any)
+function utime(path, mtime, atime)
+ return fs.utimes(path, atime, mtime)
+end
+
+--- Return the last element - usually the filename - from the given path with
+-- the directory component stripped.
+-- @class function
+-- @name basename
+-- @param path String containing the path to strip
+-- @return String containing the base name of given path
+-- @see dirname
+basename = fs.basename
+
+--- Return the directory component of the given path with the last element
+-- stripped of.
+-- @class function
+-- @name dirname
+-- @param path String containing the path to strip
+-- @return String containing the directory component of given path
+-- @see basename
+dirname = fs.dirname
+
+--- Return a table containing all entries of the specified directory.
+-- @class function
+-- @name dir
+-- @param path String containing the path of the directory to scan
+-- @return Table containing file and directory entries or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+function dir(...)
+ local iter, code, msg = fs.dir(...)
+ if iter then
+ local t = nutil.consume(iter)
+ t[#t+1] = "."
+ t[#t+1] = ".."
+ return t
+ else
+ return nil, code, msg
+ end
+end
+
+--- Create a new directory, recursively on demand.
+-- @param path String with the name or path of the directory to create
+-- @param recursive Create multiple directory levels (optional, default is true)
+-- @return Number with the return code, 0 on sucess or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+function mkdir(path, recursive)
+ return recursive and fs.mkdirr(path) or fs.mkdir(path)
+end
+
+--- Remove the given empty directory.
+-- @class function
+-- @name rmdir
+-- @param path String containing the path of the directory to remove
+-- @return Number with the return code, 0 on sucess or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+rmdir = fs.rmdir
+
+local stat_tr = {
+ reg = "regular",
+ dir = "directory",
+ lnk = "link",
+ chr = "character device",
+ blk = "block device",
+ fifo = "fifo",
+ sock = "socket"
+}
+--- Get information about given file or directory.
+-- @class function
+-- @name stat
+-- @param path String containing the path of the directory to query
+-- @return Table containing file or directory properties or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+function stat(path, key)
+ local data, code, msg = fs.stat(path)
+ if data then
+ data.mode = data.modestr
+ data.type = stat_tr[data.type] or "?"
+ end
+ return key and data and data[key] or data, code, msg
+end
+
+--- Set permissions on given file or directory.
+-- @class function
+-- @name chmod
+-- @param path String containing the path of the directory
+-- @param perm String containing the permissions to set ([ugoa][+-][rwx])
+-- @return Number with the return code, 0 on sucess or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+chmod = fs.chmod
+
+--- Create a hard- or symlink from given file (or directory) to specified target
+-- file (or directory) path.
+-- @class function
+-- @name link
+-- @param path1 String containing the source path to link
+-- @param path2 String containing the destination path for the link
+-- @param symlink Boolean indicating wheather to create a symlink (optional)
+-- @return Number with the return code, 0 on sucess or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+function link(src, dest, sym)
+ return sym and fs.symlink(src, dest) or fs.link(src, dest)
+end
+
+--- Remove the given file.
+-- @class function
+-- @name unlink
+-- @param path String containing the path of the file to remove
+-- @return Number with the return code, 0 on sucess or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+unlink = fs.unlink
+
+--- Retrieve target of given symlink.
+-- @class function
+-- @name readlink
+-- @param path String containing the path of the symlink to read
+-- @return String containing the link target or nil on error
+-- @return String containing the error description on error
+-- @return Number containing the os specific errno on error
+readlink = fs.readlink
diff --git a/nps/Makefile b/nps/Makefile
new file mode 100644
index 000000000..addd53f65
--- /dev/null
+++ b/nps/Makefile
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: GPL-3.0-only
+#
+# Copyright (C) 2021 ImmortalWrt.org
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=nps
+PKG_VERSION:=0.26.10
+PKG_RELEASE:=$(AUTORELEASE)
+
+PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
+PKG_SOURCE_URL:=https://codeload.github.com/ehang-io/nps/tar.gz/v$(PKG_VERSION)?
+PKG_HASH:=1b2fe9d251f55105d65027a1cee464f65d2f6ab3bd4a20e4655e5135db68aee7
+
+PKG_LICENSE:=Apache-2.0
+PKG_LICENSE_FILE:=LICENSE
+PKG_MAINTAINTER:=Tianling Shen
+
+PKG_CONFIG_DEPENDS:= \
+ CONFIG_NPC_COMPRESS_UPX \
+ CONFIG_NPS_COMPRESS_UPX
+
+PKG_BUILD_DEPENDS:=golang/host
+PKG_BUILD_PARALLEL:=1
+PKG_USE_MIPS16:=0
+
+GO_PKG:=ehang.io/nps
+GO_PKG_BUILD_PKG:=ehang.io/nps/cmd/...
+GO_PKG_LDFLAGS:=-s -w
+
+include $(INCLUDE_DIR)/package.mk
+include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
+
+define Package/nps/template
+ SECTION:=net
+ CATEGORY:=Network
+ TITLE:=A intranet penetration proxy server ($(1))
+ DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
+ URL:=https://ehang-io.github.io/nps
+endef
+
+Package/npc = $(call Package/nps/template,client)
+Package/nps = $(call Package/nps/template,server)
+
+define Package/nps/description/template
+ NPS is a lightweight, high-performance, powerful intranet penetration proxy server,
+ with a powerful web management terminal.
+endef
+
+Package/npc/description = $(Package/nps/description/template)
+Package/nps/description = $(Package/nps/description/template)
+
+define Package/npc/config
+ config NPC_COMPRESS_UPX
+ bool "Compress executable files with UPX"
+ default y
+endef
+
+define Package/nps/config
+ config NPS_COMPRESS_UPX
+ bool "Compress executable files with UPX"
+ default y
+endef
+
+define Build/Compile
+ $(call GoPackage/Build/Compile)
+ifeq ($(CONFIG_NPC_COMPRESS_UPX),y)
+ $(STAGING_DIR_HOST)/bin/upx --lzma --best $(GO_PKG_BUILD_BIN_DIR)/npc
+endif
+ifeq ($(CONFIG_NPS_COMPRESS_UPX),y)
+ $(STAGING_DIR_HOST)/bin/upx --lzma --best $(GO_PKG_BUILD_BIN_DIR)/nps
+endif
+endef
+
+define Package/nps/install/template
+ $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
+ $(INSTALL_DIR) $(1)/usr/bin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/$(2) $(1)/usr/bin/$(2)
+endef
+
+Package/npc/install = $(call Package/nps/install/template,$(1),npc)
+Package/nps/install = $(call Package/nps/install/template,$(1),nps)
+
+$(eval $(call GoBinPackage,npc))
+$(eval $(call GoBinPackage,nps))
+$(eval $(call BuildPackage,npc))
+$(eval $(call BuildPackage,nps))
diff --git a/nps/patches/100-remove-useless-sdk-in-npc.patch b/nps/patches/100-remove-useless-sdk-in-npc.patch
new file mode 100644
index 000000000..b96542fc4
--- /dev/null
+++ b/nps/patches/100-remove-useless-sdk-in-npc.patch
@@ -0,0 +1,4 @@
+diff --git a/cmd/npc/sdk.go b/cmd/npc/sdk.go.bak
+similarity index 100%
+rename from cmd/npc/sdk.go
+rename to cmd/npc/sdk.go.bak
diff --git a/openwrt-fullconenat/Makefile b/openwrt-fullconenat/Makefile
new file mode 100644
index 000000000..b04afda75
--- /dev/null
+++ b/openwrt-fullconenat/Makefile
@@ -0,0 +1,66 @@
+#
+# Copyright (C) 2018 Chion Tang
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+include $(INCLUDE_DIR)/kernel.mk
+
+PKG_NAME:=fullconenat
+PKG_RELEASE:=5
+
+PKG_SOURCE_DATE:=2021-12-04
+PKG_SOURCE_PROTO:=git
+PKG_SOURCE_URL:=https://github.com/llccd/netfilter-full-cone-nat.git
+PKG_SOURCE_VERSION:=7fe02604e073945a3b081c18efbb4d3c799a31f6
+PKG_MIRROR_HASH:=857062e07606d00ef9ce1b6c5d457ffc1ccfb7667694c1958bc59f1d7c4f08a4
+
+PKG_LICENSE:=GPL-2.0
+PKG_LICENSE_FILES:=LICENSE
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/iptables-mod-fullconenat
+ SUBMENU:=Firewall
+ SECTION:=net
+ CATEGORY:=Network
+ TITLE:=FULLCONENAT iptables extension
+ DEPENDS:=+iptables +kmod-ipt-fullconenat
+ MAINTAINER:=Chion Tang
+endef
+
+define Package/iptables-mod-fullconenat/install
+ $(INSTALL_DIR) $(1)/usr/lib/iptables
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/libipt_FULLCONENAT.so $(1)/usr/lib/iptables
+endef
+
+define KernelPackage/ipt-fullconenat
+ SUBMENU:=Netfilter Extensions
+ TITLE:=FULLCONENAT netfilter module
+ DEPENDS:=+kmod-nf-ipt +kmod-nf-nat
+ MAINTAINER:=Chion Tang
+ KCONFIG:=CONFIG_NF_CONNTRACK_EVENTS=y CONFIG_NF_CONNTRACK_CHAIN_EVENTS=y
+ FILES:=$(PKG_BUILD_DIR)/xt_FULLCONENAT.ko
+endef
+
+include $(INCLUDE_DIR)/kernel-defaults.mk
+
+define Build/Prepare
+ $(call Build/Prepare/Default)
+ $(CP) ./files/Makefile $(PKG_BUILD_DIR)/
+endef
+
+define Build/Compile
+ +$(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \
+ CROSS_COMPILE="$(TARGET_CROSS)" \
+ ARCH="$(LINUX_KARCH)" \
+ M="$(PKG_BUILD_DIR)" \
+ EXTRA_CFLAGS="$(BUILDFLAGS)" \
+ modules
+ $(call Build/Compile/Default)
+endef
+
+$(eval $(call BuildPackage,iptables-mod-fullconenat))
+$(eval $(call KernelPackage,ipt-fullconenat))
diff --git a/openwrt-fullconenat/files/Makefile b/openwrt-fullconenat/files/Makefile
new file mode 100644
index 000000000..b2f88db33
--- /dev/null
+++ b/openwrt-fullconenat/files/Makefile
@@ -0,0 +1,6 @@
+libipt_FULLCONENAT.so: libipt_FULLCONENAT.o
+ $(CC) -shared -lxtables -o $@ $^;
+libipt_FULLCONENAT.o: libipt_FULLCONENAT.c
+ $(CC) ${CFLAGS} -fPIC -D_INIT=$*_init -c -o $@ $<;
+
+obj-m += xt_FULLCONENAT.o