分类 运维/Linux 下的文章

网上大部分是修改源码来实现,但我不喜欢折腾源码编译,所以使用了一些神奇的方法。

Nginx(Debian:nginx-full)中有两个神奇的指令,add_before_bodyadd_after_body,分别用来在响应内容前后插入内容,所以只要注入到末尾,用Javascript处理就行了。

location / {
    # ......
    add_after_body .uindex.html;
}

在目录下创建一个隐藏文件.uindex.html:

<!-- uindex  -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha512-Dop/vW3iOtayerlYAqCgkVr2aTr2ErwwTYOvRFUpzl2VhCMJyjQF0Q9TjUXIo6JhuM/3i0vVEt2e/7QQmnHQqw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script>
let dirlist = $("pre").html().split("\n");
function getFileInfo(line) {
    line = line.replace('    ', '|');
    line = line.replace('<a href="', '');
    line = line.replace('">', '|');
    line = line.replace('</a>', '|');
    line = line.split('|');
    temp = [];
    for(i in line) {
        if(line[i] != "") temp.push(line[i].replace(/(^\s*)|(\s*$)/g, ""));
    }
    line = null;
    //if(temp.length < 3) return [];
    return temp;
}
function outputHtml(dirlist) {
    let list = [];
    for(i in dirlist) {
        let temp = getFileInfo(dirlist[i]);
        if(temp) list.push(temp);
    }
    let temp = '';
    list.forEach((i) => {
        if(i.length >= 2) {
            if(i.length > 3) {
                temp += `<tr><td><a href="${i[0]}">${i[1]}</a></td><td>${i[2].replace(' -','')}</td><td>`;
                temp += `${i[3]}</td></tr>`;
            } else if(i.length == 2) {
                temp += `<tr><td><a href="${i[0]}">${i[1]}</a></td><td></td><td></td></tr>`;
            } else {
                temp += `<tr><td><a href="${i[0]}">${i[1]}</a></td><td>${i[2].replace(' -','')}</td><td>`;
                temp += `</td></tr>`;
            }
        }
    });
    return `<div class="container"><div class="row"><div class="col-md-12"><table class="table table-striped table-hover">
  <caption class="h3">${$("h1").text()}</caption>
  <thead><tr><th>fileName</th><th>date</th><th>size</th></tr></thead><tbody>${temp}</tbody></table></div></div></div><p style="text-align:center;font-size:14px;color:#dddddd;text-align:center;font-size:14px;color:#dddddd;">&copy; 2021 uindex.</p>`;
}
$(() => {
    //$("body").css({"display": "none"})
    let html = outputHtml(dirlist);
    $("h1").remove();
    $("hr").remove();
    $("pre").remove();
    $("body").append(html);
    //$("body").css({"display": "inline"})
});
</script>

不会正则,所以先替换再分割了一遍,旨在能用就行。使用时会一闪一下,正常。

21081701.png

一般情况下Nginx自带两个有关于鉴权的模块,一个是auth_basic,另一个是auth_request,本文主要介绍auth_request

流程

21080701.png

Nginx的配置

(server段中,也可以做成一个配置文件,通过include引用)

#设置401错误跳转,用于跳转到登录页
location @error401 {
    #这里的url方便我们登录成功的跳转
    return 302 https://auth.example.com/?url=https://$http_host$request_uri;
}
#内部路由
location /auth {
    internal;
    #鉴权服务器地址
    proxy_pass http://127.0.0.1:8888/auth;
    #不传递body内容,当然请求头会被传递
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
}

(location段中)
location / {
    #指定内部路由与返回401的处理
    auth_request /auth;
    error_page 401 = @error401;
}

鉴权验证

401.jpg
(猫猫来源: https://http.cat/)

Nginx会通过所返回的状态码来判断是否放行请求,返回2XX放行,返回4XX则拦截请求,我们可以通过设置cookie来保证登录态的持续:

(Python3)
from sanic import Sanic, html, json, redirect
app = Sanic(__name__)

password = "19260817"

@app.get("/")
async def index(request):
    return html("<form action="" method="POST"><input name="pw"><button type="submit">check</button></form>")

@app.post("/")
async def vindex(request):
    pw = request.form.get("pw", None)
    url = request.args.get("url", None)
    if pw == password:
        res = redirect(url)
        res.cookies["login"] = True
        res.cookies["login"]["httponly"] = True
        #全域cookie
        res.cookies["login"]["domain"] = ".example.com"
        return res
    return redirect("/")

@app.get("/auth")
async def auth(request):
    login = request.cookies.get("login", False)
    if login:
        return json({"code": 200}, 200)
    return json({"code": 401}, 401)

if __name__ == "__main__":
    app.run(port=8888)

注意替换example.com与证书路径!
配置如下:

server {
    listen [::]:443;
    listen 443;
    server_name duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    sub_filter_types text/html text/css application/x-javascript text/xml;
    access_log off;

    location / {
        proxy_pass https://duckduckgo.com;
        proxy_set_header Host duckduckgo.com;
        proxy_set_header Accept-Encoding "";
        proxy_set_header Referer "";
        sub_filter "duckduckgo.com" "duckgo.example.com";
        sub_filter_once off;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name images.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://images.duckduckgo.com;
        proxy_set_header Host images.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name icons.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://icons.duckduckgo.com;
        proxy_set_header Host icons.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name ac.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://ac.duckduckgo.com;
        proxy_set_header Host ac.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name external-content.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://external-content.duckduckgo.com;
        proxy_set_header Host external-content.duckduckgo.com;
    }
}
server {
    listen [::]:443;
    listen 443;
    server_name improving.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://improving.duckduckgo.com;
        proxy_set_header Host improving.duckduckgo.com;
    }
}

安装

多数的文章都是从头编译安装的,但是自Nginx 1.9(具体不知道)后,插件可以热加载,不再需要一开始就要编译入Nginx中,使用我们可以使用包管理器来快速安装:

(Debian/Nginx) sudo apt-get install nginx-full libnginx-mod-rtmp -y

配置

打开/etc/nginx/nginx.conf的配置文件,在末尾加入:

rtmp {
    server {
        listen 1926;
        chunk_size 4096;
        application live {
            #开启直播
            live on;
            #开启HLS
            #hls on;
            #HLS视频流目录
            #hls_path /var/www/html/live;
            #每个视频片段长度
            #hls_fragment 5s;
            #播放列表长度
            #hls_playlist_length 15s;
            #连续模式
            #hls_continuous on;
            #删除多余流片段
            #hls_cleanup on;
            #嵌套模式
            #hls_nested on;
        }
    }
}

若需要HLS,则需要在HTTP配置文件中添加:

#root /var/www/html/;
#这里的路径应与上方视频流目录一致
location /live {
            types {
                    application/vnd.apple.mpegurl m3u8;
                    video/mp2t ts;
            }
            alias /var/www/html/live;
 }

推流

可以使用FFmpeg,但我这里使OBS

rtmp://<your_device_ip>:1926/live

HLS

将以下HTML保存到服务器,可通过游览器查看推流效果:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" name="viewport" content="width=device-width,initial-scale=1" />
    <title>HLS Live Test</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/hls.js/8.0.0-beta.3/hls.min.js"></script>
</head>
<body>
        <video id="video" controls width="100%"></video>
        <script>
            var video = document.getElementById('video');
            var hls = new Hls();
            hls.loadSource('http://<your_device_ip>/live/index.m3u8');
            hls.attachMedia(video);
            hls.on(Hls.Events.MANIFEST_PARSED,function() {
            video.play();
            });
        </script>
</body>
</html>

GNU Privacy Guard(GnuPG或GPG)是一种加密软件,它是PGP加密软件的满足GPL的替代物。GnuPG依照由IETF订定的OpenPGP技术标准设计。GnuPG用于加密、数字签名及产生非对称钥匙对的软件。

基本用法

安装GnuPG

一般情况下,Linux系统自带GnuPG,输入gpg可以看到相关的帮助与用法:

gpg (GnuPG) 2.2.12
libgcrypt 1.8.4
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
......

当然,所有的Linux源中都带有该软件包,使用默认的包管理器即可安装:

(Debian/Ubuntu) sudo apt-get install gpg -y

生成一个密钥对

在终端输入:

gpg --full-generate-key

按步骤操作:

Please select what kind of key you want:(选择一个秘钥种类,默认即可)
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.(输入秘钥长度,这里我选择2048)
What keysize do you want? (3072) 2048
Requested keysize is 2048 bits
Please specify how long the key should be valid.(密钥对过期时间)
         0 = key does not expire (永不过期)
      <n>  = key expires in n days (天)
      <n>w = key expires in n weeks (周)
      <n>m = key expires in n months (月)
      <n>y = key expires in n years (年)
    (比如2年2月:2y2m,这里我做演示,所以为2天)
Key is valid for? (0) 2
Key expires at Thu 16 Jul 2020 09:07:42 AM UTC
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.
Real name: tester(名称)
Email address: [email protected](邮箱)
Comment: test(注释)
You selected this USER-ID:
    "tester (test) <[email protected]>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
......(这里之后会要你输入保护秘钥的密码)
......(最后输出一些信息)
gpg: key 9BFDDC59D3138AA7(这是秘钥ID,可替换下文的tester来作为秘钥标识) marked as ultimately trusted
gpg: revocation certificate stored as '/root/.gnupg/openpgp-
revocs.d/4E9A37A6608115A542D30BF69BFDDC59D3138AA7.rev'
public and secret key created and signed.

pub   rsa2048 2020-07-14 [SC] [expires: 2020-07-16]
      4E9A37A6608115A542D30BF69BFDDC59D3138AA7
uid                      tester (test) <[email protected]>
sub   rsa2048 2020-07-14 [E] [expires: 2020-07-16]

分享公钥

将你的公钥分享给朋友们,以便验明正身和加密信息:

gpg -a --export tester(秘钥标识,可为邮箱,名称以及秘钥ID) > gpg_pub.key

这个文件格式为:

-----BEGIN PGP PUBLIC KEY BLOCK-----
......
-----END PGP PUBLIC KEY BLOCK-----

私钥导出

这会要你输入密码:

gpg -a --export-secret-keys tester > gpg_pri.key

生成吊销证书

生成一张吊销证书,以备不时之需让秘钥失效,这会要你输入密码:

gpg --gen-revoke test > gpg_revoke.key

按步骤操作:

Create a revocation certificate for this key? (y/N) yes(当然yes)
Please select the reason for the revocation:(选择一个理由)
  0 = No reason specified (未指定理由)
  1 = Key has been compromised (秘钥被盗用)
  2 = Key is superseded (秘钥被取代)
  3 = Key is no longer used (不再使用)
  Q = Cancel (取消)
(Probably you want to select 1 here)
Your decision? 0
Enter an optional description; end it with an empty line:(一些说明)
> test
Reason for revocation: No reason specified
(No description given)
Is this okay? (y/N) y

如果需要吊销,只需导入该证书即可。

查看&管理

查看已有公钥:

gpg --list-keys

查看已有私钥:

gpg --list-secret-keys

删除一个秘钥:

gpg --delete-key 用户标识

导入一个秘钥:

gpg --import 秘钥文件

输出秘钥指纹:

gpg --fingerprint tester
(比如:4E9A 37A6 6081 15A5 42D3  0BF6 9BFD DC59 D313 8AA7)

加密与解密文件

加密一个文件:

gpg --recipient tester --encrypt demo.txt
(这会在目录下生成一个名为demo.txt.gpg的加密过后的文件)

解密一个文件,这会要求输入密码:

gpg --recipient tester --decrypt demo.txt.gpg > output.text

给文件签名

其实这是我最主要的用法:

gpg --recipient tester --clearsign demo.txt
(这会在目录下生成一个demo.txt.asc文件,包含签名)

单独生成签名:

gpg --recipient tester --armor --detach-sign demo.txt
(这会在目录下生成一个demo.txt.asc文件,只有签名)

验证签名:

gpg --verify demo.txt.asc demo.txt
(若匹配秘钥列表中的一个,则输出如下)
gpg: Signature made Tue 14 Jul 2020 09:45:54 AM UTC
gpg:                using RSA key D7AF674BE9E2EA5C9C5EA029FB89B9B6AA70B6DD
gpg: Good signature from "tester <[email protected]>" [ultimate]

与Python

安装相关包

sudo pip3 install python-gnupg

加密&解密

#!/usr/bin/python3
import gnupg
#这指向你的用户目录下的.gnupg文件夹
gpg = gnupg.GPG(gnupghome="/root/.gnupg")
#加密字符串
text = "817"
enc_text = gpg.encrypt(text,"tester")
print("ok: ",enc_text.ok)
print("status: ",enc_text.status)
print("stderr: ",enc_text.stderr)
print(str(enc_text))
#解密字符串
dec_text = gpg.decrypt(str(enc_text),passphrase="password")
print("ok: ",dec_text.ok)
print("status: ",dec_text.status)
print("stderr: ",dec_text.stderr)
print(str(dec_text))
#加密文件&加密文件
with open("demo.txt","rb") as f:
    status = gpg.encrypt_file(f,recipients="tester",output="demo.txt.gpg")
print("ok: ",status.ok)
print("status: ",status.status)
print("stderr: ",status.stderr)
with open("demo.txt","rb") as f:
    status = gpg.decrypt_file(f,passphrase="password",output="demo.txt")
print("ok: ",status.ok)
print("status: ",status.status)
print("stderr: ",status.stderr)

文档

https://docs.red-dove.com/python-gnupg/

使用DNSmasq通过DNS屏蔽掉广告域名。

安装

你可以通过源码安装,但大部分Linux系统源中带软件包。

(Debian/Ubuntu) sudo apt-get install dnsmasq

配置

由于我们只需要使用DNS功能,所以我们的配置如下:

(PATH = /etc/dnsmasq.conf)
#上游DNS
resolv-file = /etc/dnsmasq/resolv.dnsmasq.conf
#严格按照 resolv-file 中的顺序解析直到成功
strict-order
#hosts文件地址
addn-hosts = /etc/dnsmasq/hosts
#全IP监听
listen-address = 0.0.0.0
#日志
log-queries
log-facility=/etc/dnsmasq/dnsmasq.log


(PATH = /etc/dnsmasq/resolv.dnsmasq.conf)
#百度DNS
nameserver 180.76.76.76
#114DNS
nameserver 114.114.114.114

寻找hosts

GitHub有不少,目前我在用的是下面这个:
https://github.com/neoFelhz/neohosts
这个没什么要求的话选Basic Compatible Hosts / Basic Hosts就行了,将下载的文件放置在上面配置中的路径即可。

  • 将电脑或手机的DNS服务器设置为部署服务的IP即可(也许要刷新缓存:ipconfig /flushdns)

后续

× 没有时间更新。:(
√ 水文章:|
主要是Pi-hole安装失败才来用DNSmasq的。

访问验证解决方案(

使用

Nginx自带,无需编译配置。

#适用于http,server,location段中
#默认off,当启用时一般填字符串,该字符串会显示在验证弹窗中
auth_basic 'Server Info';
#存储用户验证信息的文件(htpasswd)
auth_basic_user_file htpasswd;

添加一个用户

你设置的htpasswd文件位置是以Nginx的配置文件目录为根目录,比如只填写htpasswd,那么该文件应当在/etc/nginx目录下。
该文件内容如下:

username:password

password需要转换为不可逆的密文,使用openssl生成:

openssl passwd yourpassword

添加一个测试用户于文件中:

test:HZGvodhKflZRk

效果

93_0.PNG

crontab为Linux自带定时任务管理器,在终端输入crontab -h可以看见它的用法:

命令

crontab: invalid option -- 'h'
crontab: usage error: unrecognized option
usage:  crontab [-u user] file
        crontab [ -u user ] [ -i ] { -e | -l | -r }
                (default operation is replace, per 1003.2)
        -e      (编辑当前用户的定时任务)
        -l      (列出当前用户的定时任务)
        -r      (删除当前用户所有的定时任务)
        -i      (启用删除提示)
        -u      (指定用户)

编辑配置

在终端输入crontab -e来编辑当前用户的定时任务,第一次它会要求你选择一个编辑器,在输入完后,输入crontab -l来查看当前存在的定时任务。

配置实践

配置格式如下:

* * * * * command
分钟 小时 日期 月份 周 命令

一些规定符号:

* 任意时间执行
, 分割时段
- 一定时间范围
/n 每n时间单位执行

比方说我要每个月的5号凌晨1-5点执行updata.sh

* 1-5 5 * * /home/user/updata.sh

再比方说一个天气数据爬虫脚本,需要每30分钟更新数据:

*/30 * * * * /home/user/get_weather.py