acme.sh 实现从 letsencrypt 生成免费的证书.

acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书.

主要步骤:

  1. 安装 acme.sh
  2. 生成证书
  3. copy 证书到 nginx/apache 或者其他服务
  4. 更新证书
  5. 更新 acme.sh
  6. 出错怎么办, 如何调试

下面详细介绍.

1. 安装 acme.sh

安装很简单, 一个命令:

curl  https://get.acme.sh | sh

普通用户和 root 用户都可以安装使用. 安装过程进行了以下几步:

  1. 把 acme.sh 安装到你的 home 目录下:
~/.acme.sh/

并创建 一个 bash 的 alias, 方便你的使用: alias acme.sh=~/.acme.sh/acme.sh

2). 自动为你创建 cronjob, 每天 0:00 点自动检测所有的证书, 如果快过期了, 需要更新, 则会自动更新证书.

更高级的安装选项请参考: https://github.com/Neilpang/acme.sh/wiki/How-to-install

安装过程不会污染已有的系统任何功能和文件, 所有的修改都限制在安装目录中: ~/.acme.sh/

2. 生成证书

acme.sh 实现了 acme 协议支持的所有验证协议. 一般有两种方式验证: http 和 dns 验证.

1. http 方式需要在你的网站根目录下放置一个文件, 来验证你的域名所有权,完成验证. 然后就可以生成证书了.

acme.sh  --issue  -d mydomain.com -d www.mydomain.com  --webroot  /home/wwwroot/mydomain.com/

只需要指定域名, 并指定域名所在的网站根目录. acme.sh 会全自动的生成验证文件, 并放到网站的根目录, 然后自动完成验证. 最后会聪明的删除验证文件. 整个过程没有任何副作用.

如果你用的 apache服务器, acme.sh 还可以智能的从 apache的配置中自动完成验证, 你不需要指定网站根目录:

acme.sh --issue  -d mydomain.com   --apache

如果你用的 nginx服务器, 或者反代, acme.sh 还可以智能的从 nginx的配置中自动完成验证, 你不需要指定网站根目录:

acme.sh --issue  -d mydomain.com   --nginx

注意, 无论是 apache 还是 nginx 模式, acme.sh在完成验证之后, 会恢复到之前的状态, 都不会私自更改你本身的配置. 好处是你不用担心配置被搞坏, 也有一个缺点, 你需要自己配置 ssl 的配置, 否则只能成功生成证书, 你的网站还是无法访问https. 但是为了安全, 你还是自己手动改配置吧.

如果你还没有运行任何 web 服务, 80 端口是空闲的, 那么 acme.sh 还能假装自己是一个webserver, 临时听在80 端口, 完成验证:

acme.sh  --issue -d mydomain.com   --standalone

更高级的用法请参考: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert

2. 手动 dns 方式, 手动在域名上添加一条 txt 解析记录, 验证域名所有权.

这种方式的好处是, 你不需要任何服务器, 不需要任何公网 ip, 只需要 dns 的解析记录即可完成验证. 坏处是,如果不同时配置 Automatic DNS API,使用这种方式 acme.sh 将无法自动更新证书,每次都需要手动再次重新解析验证域名所有权。

acme.sh  --issue  --dns   -d mydomain.com

然后, acme.sh 会生成相应的解析记录显示出来, 你只需要在你的域名管理面板中添加这条 txt 记录即可.

等待解析完成之后, 重新生成证书:

acme.sh  --renew   -d mydomain.com

注意第二次这里用的是 --renew

dns 方式的真正强大之处在于可以使用域名解析商提供的 api 自动添加 txt 记录完成验证.

acme.sh 目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh 等数十种解析商的自动集成.

以 dnspod 为例, 你需要先登录到 dnspod 账号, 生成你的 api id 和 api key, 都是免费的. 然后:

export DP_Id="1234"

export DP_Key="sADDsdasdgdsf"

acme.sh   --issue   --dns dns_dp   -d aa.com  -d www.aa.com

证书就会自动生成了. 这里给出的 api id 和 api key 会被自动记录下来, 将来你在使用 dnspod api 的时候, 就不需要再次指定了. 直接生成就好了:

acme.sh  --issue   -d  mydomain2.com   --dns  dns_dp

更详细的 api 用法: https://github.com/Neilpang/acme.sh/blob/master/dnsapi/README.md

3. copy/安装 证书

前面证书生成以后, 接下来需要把证书 copy 到真正需要用它的地方.

注意, 默认生成的证书都放在安装目录下: ~/.acme.sh/, 请不要直接使用此目录下的文件, 例如: 不要直接让 nginx/apache 的配置文件使用这下面的文件. 这里面的文件都是内部使用, 而且目录结构可能会变化.

正确的使用方法是使用 --installcert 命令,并指定目标位置, 然后证书文件会被copy到相应的位置, 例如:

acme.sh  --installcert  -d  <domain>.com   
        --key-file   /etc/nginx/ssl/<domain>.key 
        --fullchain-file /etc/nginx/ssl/fullchain.cer 
        --reloadcmd  "service nginx force-reload"

(一个小提醒, 这里用的是 service nginx force-reload, 不是 service nginx reload, 据测试, reload 并不会重新加载证书, 所以用的 force-reload)

Nginx 的配置 ssl_certificate 使用 /etc/nginx/ssl/fullchain.cer ,而非 /etc/nginx/ssl/<domain>.cer ,否则 SSL Labs 的测试会报 Chain issues Incomplete 错误。

--installcert命令可以携带很多参数, 来指定目标文件. 并且可以指定 reloadcmd, 当证书更新以后, reloadcmd会被自动调用,让服务器生效.

详细参数请参考: https://github.com/Neilpang/acme.sh#3-install-the-issued-cert-to-apachenginx-etc

值得注意的是, 这里指定的所有参数都会被自动记录下来, 并在将来证书自动更新以后, 被再次自动调用.

4. 更新证书

目前证书在 60 天以后会自动更新, 你无需任何操作. 今后有可能会缩短这个时间, 不过都是自动的, 你不用关心.

5. 更新 acme.sh

目前由于 acme 协议和 letsencrypt CA 都在频繁的更新, 因此 acme.sh 也经常更新以保持同步.

升级 acme.sh 到最新版 :

acme.sh --upgrade

如果你不想手动升级, 可以开启自动升级:

acme.sh  --upgrade  --auto-upgrade

之后, acme.sh 就会自动保持更新了.

你也可以随时关闭自动更新:

acme.sh --upgrade  --auto-upgrade  0

6. 出错怎么办:

如果出错, 请添加 debug log:

acme.sh  --issue  .....  --debug 

或者:

acme.sh  --issue  .....  --debug  2

请参考: https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh

最后, 本文并非完全的使用说明, 还有很多高级的功能, 更高级的用法请参看其他 wiki 页面.

https://github.com/Neilpang/acme.sh/wiki

 

本文来源:

https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E

宝塔安装Nextcloud配置事项

本文环境:centos7,nginx,php7.3

 

1.安装php扩展

fileinfo
opcache

redis

imagemagick

exif

 

2.修改php.ini文件

软件商店,php7.3设置  进入–>配置文件

大概1898行

换成如下

 

[Zend Opcache]
zend_extension=/www/server/php/73/lib/php/extensions/no-debug-non-zts-20180731/opcache.so
opcache.enable=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1

 

 

3.修改php-fpm.conf文件

/www/server/php/73/etc/php-fpm.conf

最下面添加

 


env[PATH] = /usr/local/bin:/usr/bin:/bin:/usr/local/php/bin

 

 

4.修改网站配置文件

点击–>网站–>设置–>配置文件

 

     rewrite /.well-known/carddav /remote.php/dav permanent;
     rewrite /.well-known/caldav /remote.php/dav permanent;
     add_header Strict-Transport-Security max-age=15768000;

 

 

5.修改config.php文件

  1. /www/wwwroot/你的网站目录/config/config.php

 

添加缓存配置


  'memcache.local' => 'OCMemcacheRedis',
  'memcache.locking' => 'OCMemcacheRedis',
  'redis' => array( 'host' => 'localhost', 'port' => 6379, ),

 

6.修改php和nginx的上传限制

内存限制

上传文件大小限制

超时等

 

7.htpps配置

可以宝塔后台绑定宝塔实名帐号,在网站配置面板申请.

或者阿里云申请,再进行配置.

 

开启http2

配置好https后,在网站配置中修改配置文件

server
{
listen 80;
listen 443 ssl http2;

 

 

 

8.文件缩略图显示

8.1视频缩略图

安装ffmpeg扩展

wget http://download.bt.cn/install/ext/ffmpeg.sh && sh ffmpeg.sh

脚本只适用Centos6/Centos7 64位系统

 

查看安装情况

ffmpeg -version

 

在Php禁用函数中取消掉exec、system等函数

 

错误解决方法

ffmpeg: error while loading shared libraries: libavdevice.so.56:
cannot open shared object file: No such file or directory

 

先 find / -name libavdevice.so.56 得到该文件的目录地址,

然后 vim /etc/ld.so.conf 将上述目录添加到最后一行并保存退出;

加入:/usr/local/ffmpeg/lib //请修改成自己的实际目录
执行
sudo ldconfig

 

修改配置文件

添加configconfig.php
'enabledPreviewProviders' => array(
'OCPreviewPNG',
'OCPreviewJPEG',
'OCPreviewGIF',
'OCPreviewHEIC',
'OCPreviewBMP',
'OCPreviewXBitmap',
'OCPreviewMP3',
'OCPreviewTXT',
'OCPreviewMarkDown',
'OCPreviewMovie'
),

 

 

9.所使用的数据库为MySQL但没有对4字节字符的支持。为正确处理文件名或评论中使用的4字节字符(比如emoji表情),建议开启MySQL的4字节字符支持。详细信息请阅读相关文档页面。
在文件/www/wwwroot/你的网站目录/config/config.php中添加
'mysql.utf8mb4' => true,

 

 

10. 一些文件没有通过完整性检查.

一些文件没有通过完整性检查。了解如何解决该问题请查看我们的 文档。(无效文件列表… / 重新扫描…)

请把网站根目录下所有不是安装压缩包内的文件删除.

例如:安装的压缩包,https证书的验证文件等

然后点击重新扫描即可

 

 

11.文件有暴露的危险,禁止浏览器列出目录即可

 

location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/(?:.|autotest|occ|issue|indie|db_|console) {
deny all;
}

 

 

 

 

 

 

 

 

 

关于遇到的问题总结

 

1.APP视频上传一直不成功

原因:开始未配置https,APP登录后走的不是安全协议.

配置好htpps后,APP再重新登录一次就可以上传视频了.

 

2.挂载外部存储

提示: “smbclient” 未安装。无法挂载 "SMB / CIFS", "SMB / CIFS 使用 OC 登录信息"

需要安装smbclient

 

yum install libsmbclient libsmbclient-devel -y

 

宝塔中php的popen函数默认被禁用,删除掉.

 

pecl install smbclient

 

遇到警告:

WARNING: channel "pecl.php.net" has updated its protocols, use "pecl channel-update pecl.php.net" to update

 

pecl channel-update pecl.php.net

 

pecl安装smbclient扩展完成后

 

php.ini中添加这一行

extension="smbclient.so"

 

最后重启php

 

 

宝塔面板安装CRMEB后打开出现404错误

另外需要说明的是,CRMEB打着开源的旗号,放上去的其实是有缺失的代码,然后客户的态度让人反感

 

然后就是官网上写得一些功能,实际上很多是不存在的.

 

吐嘈结束.

 

 

关于运行目录

 

CRMEB3.0帮助地址: https://help.crmeb.net/crmeb_30/1277254

 

有一行说明:   4、配置运行目录为public

 

 

宝塔面板安装CRMEB后打开出现404错误,请调整运行目录为public

 

关于伪静态

CRMEB3.0帮助地址:  https://help.crmeb.net/crmeb_30/1277256

 

附nginx伪静态设置

location / {
       if (!-e $request_filename) {
       rewrite ^(.*)$ /index.php?s=$1 last;
       break;
        }
 }

 

fputcsv 导出excel,解决内存、性能、乱码、科学计数法问题

来源:https://blog.51cto.com/12750968/2133076

记录作为后续使用

 

<?php 
ob_end_clean();

// 文件名
$filename = $type.'_order_'.date('YmdHis').'.csv';
$title = ['单号', '状态', '数量', '商品名', '发货人', '收货人'];

// 设置 header 头
header('Content-Type: application/vnd.ms-excel'); // 文件格式
header('Content-Type: charset=utf-8'); // 文件编码
header('Content-Disposition: attachment; filenaeme='. $filename); // 文件名
header('Content-Type: application/octet-stream'); // 二进制流
// header("Accept-Ranges:bytes");// 表明范围单位为字节,可不写
header("Pragma: no-cache"); // 禁止缓存
header("Expires: 0");// 有效期时间

$fp = fopen('php://output','w+');

fputcsv($fp, transCode($title));

// 处理数据 [伪代码]
// 如果从数据库获取数据并处理,可以分批进行查询,也可以使用多个文件进行导出
// $result

$count = 0; //计数器
$limit = 10000; 

while ($row = $result->fetchRow()) {
    $count++;
    if ($count == $limit) {
        ob_flush(); // 刷新 php 缓存
        flush(); // 刷新输出缓存
        $count = 0; // 重置计数器
    }

    // 逐行写入
    fputcsv($fp, transCode($row));
    unset($row);
}

fclose($fp);
// 编码转换
// 代码一般是 utf-8 格式, csv 导出默认也是 utf-8 格式,而 excel 直接打开默认不识别 utf-8 格式,因此,要导出数据都要进行 格式转换
// 每个字段后加上 "t" 可以防止长数字显示为科学计数法 
function transCode(array &$arr){
    foreach ($arr as &$v) {
        $v = "t".iconv('utf-8', 'gb2312//ignore', $v);
    }
}

可能出现问题:
乱码问题:转换编码为 gb2312 或 gbk
特殊字符问题:字段后 加 "t" 或 双引号
数据丢失问题:gb2312 可识别的字符比较少,可以换成 gbk
csv 时间格式筛选: 文本格式的时间无法进行分组筛选,可在 转码的时候进行过滤,如果是时间格式,不转成 文本格式,即 不加 "t"

 

 

 

 

 

内网穿透工具FRP使用及配置

win7中frpc客户端加入开机启动

下载winsw进行配置https://github.com/kohsuke/winsw/releases

winsw.xml

<service>
<id>frp</id>
<name>frp本地运行</name>
<description>FRP本地运行配置文件</description>
<executable>frpc</executable>
<arguments>-c frpc.ini</arguments>
<onfailure action="restart" delay="60 sec"/>
<onfailure action="restart" delay="120 sec"/>
<logmode>reset</logmode>
</service>

 

启动.bat

@echo off

cd c:/frpc
winsw install
winsw start

停用.bat

@echo off

cd c:/frpc
winsw stop
winsw uninstall

 

说明:

我的frp客户端是放在C盘下的frpc文件夹中

配置文件位置C:frpcfrpc.ini

启动.bat,停用.bat,winsw.xml和winsw.exe同样放在frpc文件夹中

配置好后,只需点击一次启动.bat即可实现开机自动启动frp客户端.

停用.bat是停用frp客户端时使用的,在修改配置重新启动时可以先停用再启用即可.

 

 

 

 

 

复制frp服务端systemd/frps.service文件到/etc/systemd/system/frps.service

frps.service文件参考内容

[Unit]
Description=Frp Server Service
After=network.target

[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/frps/frps -c /frps/frps.ini

[Install]
WantedBy=multi-user.target

 

说明:

我的frp服务端是放在服务器根下的frps文件夹中

服务端配置文件在/frps/frps.ini

上面的代码中的路径请参考对应设置成自己的.

 

 

 

 

centos7.6中服务端加入开机启动

systemctl enable frps.service

停止服务

systemctl stop frps.service

启动服务

systemctl start frps.service

查询状态

systemctl status frps.service

 

 

在nginx配置80端口共用

说明:

frps.ini中http端口为8080

vhost_http_port = 8080

 

http
{

resolver 1.1.1.1;#添加这一行,避免出现502错误

resolver_timeout 5s;

 

找到配置块

server
{

 

}

下面添加以下代码

 


server
{
listen 80;
server_name *.domain.com;
location / {
proxy_pass http://$host:8080;
proxy_set_header Host $host:80;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_hide_header X-Powered-By;
}
location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
{
proxy_pass http://$host:8080;#添加这一行
expires 30d;
}

location ~ .*.(js|css)?$
{
proxy_pass http://$host:8080;#添加这一行
expires 12h;
} 
location ~ /.
{
deny all;
}
}


 

 

 

 

swiper常用配置

 

 

<script>        
  var mySwiper = new Swiper ('.swiper-container', {
                    autoplay: {
                        delay:5000,//秒            
                        disableOnInteraction: false,//滑动不会失效
                        reverseDirection: false,//如果最后一个 反向播放
                    },
                    loop: true,//轮播
                    followFinger: false,//手指滑动完毕在动
    // 如果需要分页器
      pagination: {
        el: '.swiper-pagination',
        clickable: true,
        renderBullet: function (index, className) {
          return '<span class="' + className + '">' + (index + 1) + '</span>';
        },
      },
    
    // 如果需要前进后退按钮
    navigation: {
      nextEl: '.swiper-button-next',
      prevEl: '.swiper-button-prev',
    },
    
  });
    mySwiper.el.onmouseover = function(){ //鼠标放上暂停轮播
      mySwiper.autoplay.stop();
    }
    mySwiper.el.onmouseleave = function(){
      mySwiper.autoplay.start();
    }
  </script>

 

 

 

 

 

PHP版获取访客IP函数

PHP版获取访客IP函数

 



function ip() {
    //strcasecmp 比较两个字符,不区分大小写。返回0,>0,<0。
    if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
        $ip = getenv('HTTP_CLIENT_IP');
    } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
        $ip = getenv('HTTP_X_FORWARDED_FOR');
    } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
        $ip = getenv('REMOTE_ADDR');
    } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    $res =  preg_match ( '/[d.]{7,15}/', $ip, $matches ) ? $matches [0] : '';
    return $res;
    //dump(phpinfo());//所有PHP配置信息
}

 

 

 

 

Data URI scheme支持的类型

 

 

 

data:, 文本数据
data:text/plain, 文本数据
data:text/html, HTML代码
data:text/html;base64, base64编码的HTML代码
data:text/css, CSS代码
data:text/css;base64, base64编码的CSS代码
data:text/javascript, Javascript代码
data:text/javascript;base64, base64编码的Javascript代码
data:image/gif;base64, base64编码的gif图片数据
data:image/png;base64, base64编码的png图片数据
data:image/jpeg;base64, base64编码的jpeg图片数据
data:image/x-icon;base64, base64编码的icon图片数据

WDOYO模板中头部模板head.html中TDK的写法

为方便模板制作,全站统一使用一个头部模板head.html ,而头部的TDK,不同页面不一样,所以需要在头部模板head.html中进行处理.

 

{if($product['id']!='')}
<title>{$product['title']}_{$GLOBALS['S']['title']}</title>
<meta name="keywords" content="{$product['keywords']}" />
<meta name="description" content="{$product['description']}" />
<meta property="og:type" content="product"/>
<meta property="og:image" content="{$GLOBALS['S']['http']}{$product['litpic']}"/>
<meta property="og:title" content="{$product['title']}"/>
<meta property="og:description" content="{$product['description']}"/>
<meta property="og:product:brand" content="HEIMAN"/>
{else}
{if($article['id']!='')}
<title>{$article['title']}_{$GLOBALS['S']['title']}</title>
<meta name="keywords" content="{$article['keywords']}" />
<meta name="description" content="{$article['description']}" />
{else}
{if($type['tid']!='')}
<title>{$type['classname']}_{$GLOBALS['S']['title']}</title>
<meta name="keywords" content="{$type['keywords']}" />
<meta name="description" content="{$type['description']}" />
{else}
{if($type['word']!='')}
<title>{$type['word']}_{$GLOBALS['S']['title']}</title>
<meta name="keywords" content="{$type['word']}" />
<meta name="description" content="{$type['word']}" />
{else}
<title>清晨博客-{$GLOBALS['S']['title']}</title>
<meta name="keywords" content="{$GLOBALS['S']['keywords']} " />
<meta name="description" content="{$GLOBALS['S']['description']} " />
{/if}
{/if}
{/if}
{/if}

 

其中搜索页是通过

{if($type['word']!='')}

进行判断的,同时还需要修改模块的源码

比如产品频道

打开sourceproduct.php

找到function search(){函数

修改这一行

$this->type=array('title'=>'Search','keywords'=>$GLOBALS['S']['keywords'],'description'=>$GLOBALS['S']['description'],'word'=>$this->syArgs('word',1),'classname'=>'所有搜索结果',);//添加搜索词word输出

 

 
通过判断ID来进行处理,达到目的.
 

 

PHP版内容自动创建索引目录的代码

来源于网络

PHP版内容自动创建索引目录的代码,可以自动把内容中的<h2><h3>标签中的内容作为索引目录,创建内容页中的小导航菜单.

关于css的代码,可以自行处理.

 

function create_con_menu($content) {
    $matches = array();
    $ul_li = '';
    //匹配出h2、h3标题
    $rh = "/<h[23]>(.*?)</h[23]>/im";
    $h2_num = 0;
    $h3_num = 0;
    //判断是否是文章页
    if(is_single()){
         if(preg_match_all($rh, $content, $matches)) {
            // 找到匹配的结果
            foreach($matches[1] as $num => $title) {
                $hx = substr($matches[0][$num], 0, 3);      //前缀,判断是h2还是h3
                $start = stripos($content, $matches[0][$num]);  //匹配每个标题字符串的起始位置
                $end = strlen($matches[0][$num]);       //匹配每个标题字符串的结束位置
                if($hx == "<h2"){
                    $h2_num += 1; //记录h2的序列,此效果请查看百度百科中的序号,如1.1、1.2中的第一位数
                    $h3_num = 0;
                    // 文章标题添加id,便于目录导航的点击定位
                    $content = substr_replace($content, '<h2 id="h2-'.$num.'">'.$title.'</h2>',$start,$end);
                    $title = preg_replace('/<.+?>/', "", $title); //将h2里面的a链接或者其他标签去除,留下文字
                    $ul_li .= '<li class="h2_nav"><a href="#h2-'.$num.'" class="tooltip" title="'.$title.'">'.$title."</a><i class="post_nav_dot"></i></li>n";
                }else if($hx == "<h3"){
                    $h3_num += 1; //记录h3的序列,此熬过请查看百度百科中的序号,如1.1、1.2中的第二位数
                    $content = substr_replace($content, '<h3 id="h3-'.$num.'">'.$title.'</h3>',$start,$end);
                    $title = preg_replace('/<.+?>/', "", $title); //将h3里面的a链接或者其他标签去除,留下文字
                    $ul_li .= '<li class="h3_nav"><a href="#h3-'.$num.'" class="tooltip" title="'.$title.'">'.$title."</a><i class="post_nav_dot"></i></li>n";
                }  
            }
        }
        // 将目录拼接到文章
        $content =    "<div class="post_nav"><ul class="post_nav_content">n" . $ul_li . "</ul></div>n".$content;
        return $content;
    }
}