Select Page

隔離即高效:探索 Python 虛擬環境的專業實踐

我們常常在網路上安裝依賴 Python 的程式碼,但總是會有一推的衝突存在,Python 的虛擬環境(virtual environment)則是一個非常有用的工具,它可以在不同的專案間建立隔離的 Python 執行環境,可以確保不同專案的依賴庫互不干擾,讓 Python 的開發更加簡單、安全。以下將詳細介紹 Python 虛擬環境的概念、創建和使用方法。

虛擬環境的重要性

在沒有虛擬環境的情況下,Python 的庫和模組都會安裝在全局環境中。這會導致幾個問題:

  • 版本衝突:不同專案可能需要同一個庫的不同版本,全局安裝可能導致版本衝突。
  • 依賴混亂:隨著安裝的庫越來越多,全局環境可能變得難以管理和維護。

使用虛擬環境,每個專案都有自己獨立的環境,可以:

  • 隔離依賴:每個專案的依賴庫都獨立於其他專案,避免了版本和依賴衝突。
  • 易於管理:管理每個專案的依賴更為方便,升級或修改不會影響到其他專案。

如何創建和使用虛擬環境

1. 安裝虛擬環境

Python 3.3 以上版本已經內置了 venv 模組來創建虛擬環境。安裝新的虛擬環境非常簡單:

python3 -m venv myenv

這個命令會在當前目錄下創建一個名為 myenv 的資料夾,其中包含了獨立的 Python 執行環境。

2. 啟用虛擬環境

創建虛擬環境後,需要啟用它以使用:

Windows

.\myenv\Scripts\activate

Linux

source myenv/bin/activate

啟用虛擬環境後,你會在命令行前看到虛擬環境的名稱,這表明你現在在該虛擬環境中運行 Python。

3. 安裝和管理套件

在虛擬環境中,可以使用 pip 安裝所需的庫,而這些安裝只會影響當前的虛擬環境:

pip install -r requirements.txt

這個命令會在虛擬環境中安裝 Flask,而不會影響到其他環境或全局 Python 環境。

4. 退出虛擬環境

完成工作後,可以通過以下命令退出虛擬環境:

deactivate

這會恢復到全局 Python 環境。

參考資料

https://docs.python.org/zh-tw/3/tutorial/venv.html

手把手教學安裝 anything-llm (不使用 docker)

手把手教學安裝 anything-llm (不使用 docker)

AnythingLLM是一款全功能的應用程序,支持使用商業或開源的大語言模型(LLM)和向量數據庫建構私有ChatGPT。用戶可以在本地或遠端運行該系統,並利用已有文檔進行智能對話。此應用將文檔分類至稱為工作區的容器中,確保不同工作區間的資料隔離,保持清晰的上下文管理。

特點:多用戶支持、權限管理、內置智能代理(可執行網頁瀏覽、代碼運行等功能)、可嵌入到網站的聊天窗口、多種文檔格式支持、向量數據庫的簡易管理界面、聊天和查詢兩種對話模式、引用文檔內容的展示,以及完善的API支持客戶端定制整合。此外,該系統支持100%雲端部署,Docker部署,且在處理超大文檔時效率高,成本低。

安裝 Anything llm

注意,以下要用 linux 平台安裝,windows 用戶可以用 WSL,推薦用 Ubuntu OS

在自己的 home 目錄下,到 GitHub 中下載原始碼

git clone https://github.com/Mintplex-Labs/anything-llm.git

利用 yarn 作設定資源

cd anything-llm
yarn setup

把環境變數建立起來,後端主機是 NodeJS express

cp server/.env.example server/.env
nano server/.env

密文需要最少12位的字元,檔案的存放路徑也記得改成自己的

JWT_SECRET="my-random-string-for-seeding"
STORAGE_DIR="/your/absolute/path/to/server/storage"

前端的環境變數,先把/api打開即可

# VITE_API_BASE='http://localhost:3001/api' # Use this URL when developing locally
# VITE_API_BASE="https://$CODESPACE_NAME-3001.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/api" # for Github Codespaces
VITE_API_BASE='/api' # Use this URL deploying on non-localhost address OR in docker.

如果你在設定的時候,遇到更新請求,可以跟我著我下面的方法作

把 prisma 更新好

yarn add --dev prisma@latest
yarn add @prisma/client@latest

前端的程式碼

先編譯前端程式碼,前端是由 viteJS + React

cd frontend && yarn build

將編譯好的資料放到 server 的目錄下

cp -R frontend/dist/* server/public/

選項,如果需要用到本地端的 LLM 模型,就把 llama-cpp 下載下來

cd server && npx --no node-llama-cpp download

把資料庫建立好

cd server && npx prisma generate --schema=./prisma/schema.prisma
cd server && npx prisma migrate deploy --schema=./prisma/schema.prisma

Server端是用來處理 api 以及進行向量資料庫的管理以及跟 LLM 交互

Collector 是一個 NodeJS express server,用來作UI處理和解析文檔

cd server && NODE_ENV=production node index.js &
cd collector && NODE_ENV=production node index.js &

更新的指令碼

現在 anything llm 更新速度超快,把這一段指令碼複製起來,方便未來作更新的動作

#!/bin/bash

cd $HOME/anything-llm &&\
git checkout . &&\
git pull origin master &&\
echo "HEAD pulled to commit $(git log -1 --pretty=format:"%h" | tail -n 1)"

echo "Freezing current ENVs"
curl -I "http://localhost:3001/api/env-dump" | head -n 1|cut -d$' ' -f2

echo "Rebuilding Frontend"
cd $HOME/anything-llm/frontend && yarn && yarn build && cd $HOME/anything-llm

echo "Copying to Sever Public"
rm -rf server/public
cp -r frontend/dist server/public

echo "Killing node processes"
pkill node

echo "Installing collector dependencies"
cd $HOME/anything-llm/collector && yarn

echo "Installing server dependencies & running migrations"
cd $HOME/anything-llm/server && yarn
cd $HOME/anything-llm/server && npx prisma migrate deploy --schema=./prisma/schema.prisma
cd $HOME/anything-llm/server && npx prisma generate

echo "Booting up services."
truncate -s 0 /logs/server.log # Or any other log file location.
truncate -s 0 /logs/collector.log

cd $HOME/anything-llm/server
(NODE_ENV=production node index.js) &> /logs/server.log &

cd $HOME/anything-llm/collector
(NODE_ENV=production node index.js) &> /logs/collector.log &

清除 Nginx cache 的服務 – 使用NodeJS

如果你有用 Nginx 的快取服務,無論是 fastcgi or proxy cache,都會遇到很難有一套真正好用的清除工具,這邊示範一套自己開發的清除工具,也支援多伺服器使用

安裝必要的套件

你先需要有nodejs,沒有的人先去官網安裝下,安裝完畢的人,先建立一個目錄,並且輸入以下指令,安裝必要的套件

npm init -y
npm install express

建立一個 Node.js App

可以命名為 app.js,內容如下,會建立一個小型的伺服器,用來處理 http 的請求,並且刪除指定的目錄

const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');

const app = express();
const PORT = 3000;

const logFile = 'application.log';

function log(message) {
    const timestamp = new Date().toISOString();
    fs.appendFileSync(logFile, `${timestamp} - ${message}\n`);
}

app.get('/delete-dir', (req, res) => {
    let dir = req.query.dir;

    if (!dir || /[^a-zA-Z0-9_\-\/]/.test(dir)) {
        log('提供的路徑不合法!');
        return res.status(400).send('提供的路徑不合法!');
    }

    const command = `sudo find /run/proxy_cache/${dir} -mindepth 1 -type d -exec rm -rf {} +`;

    exec(command, (error, stdout, stderr) => {
        if (error) {
            log(`执行错误: ${error}`);
            return res.status(500).send('删除子目錄失敗!');
        }
        log('子目錄删除成功!');
        res.send('子目錄删除成功!');
    });
});

app.listen(PORT, () => {
    log(`服务器正在运行于 http://localhost:${PORT}`);
});

其中我把能刪除的資料定義在只有這個目錄下的才行 /run/proxy_cache/ ,避免權限過大問題

配置 sudo 權限

打開 sudoers 文件

sudo visudo

加入指令,記得把 <username> 改成自己的名字,或是 www-data

<username> sudo find /run/proxy_cache/ -mindepth 1 -type d -exec rm -rf {} +

完成後的測試

可以利用 CURL 呼叫 API ,成功就會看到 “子目錄删除成功!” 的訊息

curl "http://localhost:3000/delete-dir?dir=cache_directory"

結論與注意事項

要記得別讓外部的人可以輕易地存取這項服務,防火牆要記得把port鎖好,執行權限要小心設定,這樣就可以搭配 Nginx Cache 使用,之後再來出對應的刪除功能。

開機時候自動執行

要讓 Node.js 應用在系統開機時由特定使用者(例如 www-data)啟動,需要配置一個 systemd 服務單元。在這個情況中,我們將創建一個服務單元文件來確保 Node.js 應用作為 www-data 使用者運行。

創建 systemd 服務文件

建立一個新的 systemd 服務文件。

sudo nano /etc/systemd/system/nodeapp.service

加入以下配置

[Unit] 
Description=Node.js Web Application 
After=network.target 

[Service] 
Type=simple 
User=www-data 
Group=www-data 
WorkingDirectory=/path/to/your/app 
ExecStart=/usr/bin/node /path/to/your/app/app.js 
Restart=on-failure 

[Install] 
WantedBy=multi-user.target

參數意思如下

Description: 服務的描述。

After: 這個單元應該在網絡服務可用之後啟動。

UserGroup: 指定運行此服務的使用者和組。

WorkingDirectory: 指定 Node.js 應用的工作目錄。

ExecStart: 指定啟動應用的命令。

Restart: 指定何時重新啟動服務,on-failure 表示只在程序異常退出時重啟。

啟用和啟動服務

重新加載 systemd 配置以讀取新的服務文件:

sudo systemctl daemon-reload

啟用剛創建的服務,使其在開機時自動啟動:

sudo systemctl enable nodeapp.service

啟動服務以檢查它是否運行正常:

sudo systemctl start nodeapp.service

檢查服務的狀態來確認一切正常:

sudo systemctl status nodeapp.service

讓 www-data 執行 SERVER 上的特定權限指令

sudoers 檔案控制了使用者及群組在使用 sudo 指令時的權限。為了安全編輯這個檔案,應該使用 visudo 指令,因為它會檢查語法錯誤,避免創建一個無法使用的 sudoers 檔案。

更新 sudoers 檔案的步驟:

開啟終端機

開啟你的伺服器的命令行介面。

使用 visudo 編輯 sudoers 檔案: 輸入以下指令以安全方式編輯 sudoers 檔案:

sudo visudo

新增規則

在編輯器中,新增一條規則,允許 www-data 使用者無需密碼執行特定指令。

www-data ALL=(ALL) NOPASSWD: /usr/bin/rm -rf /path/to/nginx/cache/*

指令的意義是:

www-data:指定的使用者。

ALL=(ALL):這表示 www-data 使用者可以從任何主機以任何使用者身份執行指令。

NOPASSWD:允許 www-data 使用者在執行指定的指令時不需要輸入密碼。

/usr/bin/rm -rf /path/to/nginx/cache/*:指定 www-data 使用者無密碼可以執行的指令。

儲存並退出編輯器

如果你使用的是 nano,按 Ctrl+X 來退出,然後按 Y 鍵儲存更改,最後按 Enter 鍵確認檔案名稱。

測試配置

為了確保設定正確無誤,可以用 www-data 份執行指令:

sudo -u www-data sudo /usr/bin/rm -rf /path/to/nginx/cache/*

如果不需要密碼且指令執行成功,代表 sudoers 檔案配置正確。

安全注意事項:

  • 最小權限原則:只授權必要的最小權限,避免給予 www-data 使用者太廣泛的權限。
  • 明確指令路徑:使用完整的指令路徑(如 /usr/bin/rm),避免安全風險。
  • 定期審計:定期檢查 sudoers 檔案,確保所有條目仍然符合組織的安全政策和操作需要。
如何變動在 linux 中的預設 php 執行檔?

如何變動在 linux 中的預設 php 執行檔?

在 Linux 中更改命令行的預設 PHP 執行檔,通常涉及更新系統的 PATH 環境變數或使用 update-alternatives 工具(如果你的系統支援)。以下是幾種常見的方法來變更預設 PHP 執行檔:

方法 1:使用 update-alternatives(適用於 Debian、Ubuntu 及其衍生系統)

如果你的系統安裝了多個 PHP 版本,可以使用 update-alternatives 工具來設定預設的 PHP 版本。

列出所有可用的 PHP 替代版本

sudo update-alternatives --display php

設定預設 PHP 版本

使用下面的命令設定預設版本。將 phpX.Y 替換成你想設定為預設的 PHP 版本號,例如 php8.2

sudo update-alternatives --set php /usr/bin/php8.2

如果你想要在可用選項之間手動選擇,可以使用:

sudo update-alternatives --config php

方法 2:直接修改 PATH 環境變數

你可以通過修改 PATH 環境變數來讓系統首先找到你想要的 PHP 版本。這可以通過在你的 ~/.bashrc~/.zshrc 或其他適用的 shell 配置檔案中添加一行來實現。

假設你想使用的 PHP 版本位於 /usr/local/phpX.Y/bin(將 X.Y 替換成具體版本號),可以添加以下行:

export PATH=/usr/local/phpX.Y/bin:$PATH

然後,執行 source ~/.bashrc(或對應的配置檔案)來使變更生效。

source ~/.bashrc

方法 3:使用符號連結

另一個變更預設 PHP 執行檔的方法是建立一個指向你想要的 PHP 版本的符號連結(symlink)。這通常涉及到刪除或重命名舊的 PHP 符號連結,然後創建一個新的指向你選擇的版本。

刪除或重命名當前的符號連結

sudo mv /usr/bin/php /usr/bin/php.old

建立新的符號連結

/usr/bin/php 指向你想要的 PHP 版本。將 /path/to/your/php 替換成你想要設定為預設 PHP 執行檔的完整路徑。

sudo ln -s /path/to/your/php /usr/bin/php

在進行這些變更後,你可以通過執行 php -v 來驗證新的預設 PHP 版本是否已正確設定。

php -v
開發人員必看的 Web Push 功能教學

開發人員必看的 Web Push 功能教學

Web Push 是一種標準的 Web 協議,允許網站向使用者發送推播通知。這項功能可用於各種目的,例如:

  • 提醒使用者有新的內容或更新
  • 提供即時通知,例如交易狀態或聊天訊息
  • 提高使用者參與度

準備工作

  • 前端訂閱推播服務的 js 檔案
  • 前端訂閱推播服務的網頁,需要包含訂閱服務的 js
  • 後端紀錄使用者訂閱資訊的服務
  • 後端推播訊息的服務
  • 後端註冊訊息伺服器的程式碼

建立前端網頁的訂閱表單

這個檔案將包含安裝、激活、攔截請求和推播事件的處理器。創建一個名為sw.js的檔案,並將其放在你網站的根目錄下

// 安裝Service Worker
self.addEventListener('install', function(event) {
    console.log('Service Worker 安裝成功');
});

// Service Worker 激活
self.addEventListener('activate', function(event) {
    console.log('Service Worker 激活成功');
});

// 監聽推播事件
self.addEventListener('push', function(event) {
    var title = '推播通知';
    var options = {
        body: '這是一條推播消息。',
        icon: 'icon.png',
        badge: 'badge.png'
    };

    event.waitUntil(self.registration.showNotification(title, options));
});

在你的網站上註冊一個Service Worker,這是實現Web推播的必要步驟。Service Worker將在背景執行,即使用戶沒有直接訪問你的網站也能接收通知。通常會把下面的 javascript 寫在首頁中,觸發訂閱的條件。

// 在主要的JavaScript檔案中
function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    console.log('Service Worker 注册成功:', registration);
  }).catch(function(error) {
    console.log('Service Worker 注册失败:', error);
  });
}
navigator.serviceWorker.ready.then(function(registration) {
  if (!registration.pushManager) {
    alert('此瀏覽器不支持推播通知');
    return false;
  }
const applicationServerKey = 'your publice key';
  
// 訂閱推播
  registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(applicationServerKey)
  }).then(function(subscription) {
    console.log('推播訂閱成功:', subscription);

    // 發送訂閱資訊到後端
    fetch('https://your_webpush_server/subscribe', {
      method: 'post',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify({
        subscription: subscription
      }),
    });
  }).catch(function(error) {
    console.log('推播訂閱失败:', error);
  });

提供紀錄訂閱訊息的服務

這一段的作法很多,通常用你原本伺服器中的解決方案,例如 php,asp.net,nodejs,python,GO等,我這邊為了方便,用nodejs示範下

const express = require('express');
const cors = require('cors');

const bodyParser = require('body-parser');
const app = express();
app.use(cors());
app.use(bodyParser.json());

const subscriptions = {}; // 在實際應用中,應使用資料庫儲存訂閱資訊

const webPush = require('web-push');
    // 設置你的VAPID鑰匙
    webPush.setVapidDetails(
        'mailto:[email protected]',
      	'your publiec key',
      	'your private key'
      };

app.post('/subscribe', (req, res) => {
    const subscription = req.body;
    const key = subscription.endpoint; // 使用endpoint作為唯一鑰匙
    subscriptions[key] = subscription;

    console.log('subscripted');
    console.log(subscription);
    
    // subscription是從前端發送到後端的訂閱對象
    webPush.sendNotification(subscription.subscription, '消息內容')
        .then(result => console.log('推播成功'))
        .catch(err => console.log('推播失敗', err));

    res.status(200).json({message: '訂閱成功'});
});

app.listen(8060, () => console.log('伺服器運行在8060端口'));

其中 sendNotification 平常應該是要放在 webpush service中的,這邊加入是用來測試使用

生成VAPID鑰匙 (自願應用伺服器身份驗證)

大部分現代瀏覽器(如Chrome、Firefox、Edge)都支持Web推播API,但是如果你不用市面上的解決方案如 OneSignal 而是要直接與這些瀏覽器的推播服務交互的話,需要使用VAPID(自願應用伺服器身份驗證)鑰匙進行身份驗證。

生成 VAPID Key 的方法如下

npx web-push generate-vapid-keys

記住保存生成的鑰匙。公鑰將在前端用於訂閱推播,私鑰將在後端用於發送推播。


如果你無法使用npx(它通常隨npm自動安裝,作為npm 5.2.0及更高版本的一部分),那麼你可以通過下載最新版本的 nodejs

https://nodejs.org/

或是升級Node.js,或使用版本管理器如nvm(Node Version Manager)來管理不同版本的Node.js。

參考資料

https://developer.mozilla.org/en-US/docs/Web/API/Push_API