반응형

    https://www.ppomppu.co.kr/zboard/view.php?id=nas&no=44870&ismobile

     

    미니PC(proxmox)를 활용한 구글포토 무제한 이용하기!

    안녕하세요~얼마전 미니PC를 사서 이거저거 활용중인데요..전부터 하려고 북마크 해놓은구글포

    www.ppomppu.co.kr

    위 링크는 안드로이드 x86 가상머신을 proxmox 미니PC에 올려 구글포토 무제한 사용하는 방법을 설명한글입니다. 이 글을 잘 따라해서 무제한구글포토를 즐기고 있었습니다.

    그런데 구글포토를 돌릴 android-x86 VM을 상시구동하고있으면 CPU, RAM사용량이 많아 전력소모량도 많아집니다. 실제 구동중인 N100 미니PC의 전력량을 스마트플러그를 이용해 측정 시 안드로이드vm이 켜졌을 때 대략 25W, 꺼졌을때 18W로 꽤나 전력소모가 많아지는것을 확인할 수 있습니다.

    그래서 전력 낭비를 줄이기 위해 안드로이드는 평상시에는 꺼진상태를 유지하기로 했습니다. 만약 nas에 사진이 업로드되면 그 때 안드로이드를 켜고 CPU사용률이 idle상태가 되면 (사진을 모두 업로드 했으면) 다시 안드로이드 전원을 끄는 자동화를 구상해봤습니다.

    기억에 의존해 쓰는 글이므로 그대로 따라해도 오류가 있을 수 있습니다..

    사전준비사항

    • nas에 사진업로드 폴더가 안드로이드 VM에 마운트 되어있으며 전원을 켜면 업로드가 시작됨 (맨위의 링크까지 완료된 상태)
    • Proxmox에서 api를 사용할 user생성, api토큰받아두고 권한설정하기 (VM pwmgmt랑 audit? 이것만 있으면 됨. api와 user(또는 그룹)에 둘다 권한 줘야함.)
    • 안드로이드 x86에 macrodroid설치후 웹훅(/shutdown)을 트리거로 종료하도록 매크로 짜두기.

     

    대략적인 순서

    • lxc를 생성합니다. (데비안으로 cpu1, ram 512Mb로 생성함.)
    • lxc에 필요한것들을 설치합니다. 파이썬.. 등
    • lxc에 사진이 업로드되는 폴더를 마운트합니다. (/etc/fstab에 설정.. 예: 192.168.***.***:/volume3/gshare /mnt/gshare nfs defaults 0 0 )
    • gshare_manager.py를 만들어 아래 코드를 넣기
    import logging
    import time
    from dataclasses import dataclass
    from typing import Optional
    import requests
    import subprocess
    from datetime import datetime
    import pytz
    
    @dataclass
    class Config:
        # Proxmox android 사용량 감시
        ## Proxmox API 호스트
        PROXMOX_HOST: str = "https://192.168.***.***:8006/api2/json"
        ## Proxmox 노드 이름
        NODE_NAME: str = "******"
        ## android VM ID
        VM_ID: str = "***"
        ## API 토큰 ID
        TOKEN_ID: str = "*****@pve!*****"
        ## API 토큰 시크릿
        SECRET: str = "*******-****-****-****-************"
        ## CPU 사용량 임계치(%)
        CPU_THRESHOLD: float = 3.0
        ## 체크 간격(초)
        CHECK_INTERVAL: int = 120
        ## 체크 횟수
        THRESHOLD_COUNT: int = 2
        
        # 폴더용량 감시
        ## 감시폴더 마운트 경로
        MOUNT_PATH: str = "/mnt/gshare"
        ## 체크 간격은 CHECK_INTERVAL을 공통으로 사용
        ## macrodroid 종료 웹훅 URL
        SHUTDOWN_WEBHOOK_URL: str = "http://192.168.***.***:8080/shutdown"
        
        # 로그 시간대
        TIMEZONE: str = "Asia/Seoul"
    
    class ProxmoxAPI:
        def __init__(self, config: Config):
            self.config = config
            self.session = requests.Session()
            self.session.verify = False
            self._set_token_auth()
    
        def _set_token_auth(self) -> None:
            # API 토큰을 사용하여 인증 헤더 설정
            self.session.headers.update({
                "Authorization": f"PVEAPIToken={self.config.TOKEN_ID}={self.config.SECRET}"
            })
            logging.info("Proxmox API 토큰 인증 설정 완료")
    
        def is_vm_running(self) -> bool:
            try:
                response = self.session.get(
                    f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/current"
                )
                response.raise_for_status()
                return response.json()["data"]["status"] == "running"
            except Exception as e:
                logging.error(f"VM 상태 확인 실패: {e}")
                return False
        def get_vm_uptime(self) -> Optional[float]:
            try:
                response = self.session.get(
                    f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/current"
                )
                response.raise_for_status()
                return response.json()["data"]["uptime"]
            except Exception as e:
                logging.error(f"VM 부팅 시간 확인 실패: {e}")
                return None
    
        def get_cpu_usage(self) -> Optional[float]:
            try:
                response = self.session.get(
                    f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/current"
                )
                response.raise_for_status()
                return response.json()["data"]["cpu"] * 100
            except Exception as e:
                logging.error(f"CPU 사용량 확인 실패: {e}")
                return None
    
        def start_vm(self) -> bool:
            try:
                response = self.session.post(
                    f"{self.config.PROXMOX_HOST}/nodes/{self.config.NODE_NAME}/qemu/{self.config.VM_ID}/status/start"
                )
                response.raise_for_status()
                logging.info("VM 시작 성공")
                return True
            except Exception as e:
                logging.error(f"VM 시작 실패: {e}")
                return False
    
    class FolderMonitor:
        def __init__(self, config: Config):
            self.config = config
            self.previous_size = self._get_folder_size()
    
        def _get_folder_size(self) -> int:
            try:
                result = subprocess.run(
                    ['du', '-sb', self.config.MOUNT_PATH],
                    capture_output=True,
                    text=True,
                    check=True
                )
                return int(result.stdout.split()[0])
            except Exception as e:
                logging.error(f"폴더 크기 확인 실패: {e}")
                return 0
    
        def has_size_changed(self) -> bool:
            current_size = self._get_folder_size()
            if current_size != self.previous_size:
                logging.info(f"폴더 크기 변경: {self.previous_size} -> {current_size}")
                self.previous_size = current_size
                return True
            return False
    
    class GShareManager:
        def __init__(self, config: Config, proxmox_api: ProxmoxAPI):
            self.config = config
            self.proxmox_api = proxmox_api
            self.low_cpu_count = 0
            self.folder_monitor = FolderMonitor(config)
    
        def _format_uptime(self, seconds: float) -> str:
            hours = int(seconds // 3600)
            minutes = int((seconds % 3600) // 60)
            secs = int(seconds % 60)
            
            if hours > 0:
                return f"{hours}시간 {minutes}분 {secs}초"
            elif minutes > 0:
                return f"{minutes}분 {secs}초"
            else:
                return f"{secs}초"
    
        def _send_shutdown_webhook(self) -> None:
            try:
                response = requests.post(self.config.SHUTDOWN_WEBHOOK_URL)
                response.raise_for_status()
    
                uptime = self.proxmox_api.get_vm_uptime()
                uptime_str = self._format_uptime(uptime) if uptime is not None else "알 수 없음"
                logging.info(f"종료 웹훅 전송 성공, 업타임: {uptime_str}")
            except Exception as e:
                logging.error(f"종료 웹훅 전송 실패: {e}")
    
        def monitor(self) -> None:
            while True:
                if self.folder_monitor.has_size_changed() and not self.proxmox_api.is_vm_running():
                    self.proxmox_api.start_vm()
    
                if self.proxmox_api.is_vm_running():
                    cpu_usage = self.proxmox_api.get_cpu_usage()
                    if cpu_usage is not None:
                        if cpu_usage < self.config.CPU_THRESHOLD:
                            self.low_cpu_count += 1
                            if self.low_cpu_count >= self.config.THRESHOLD_COUNT:
                                self._send_shutdown_webhook()
                                self.low_cpu_count = 0
                        else:
                            self.low_cpu_count = 0
                time.sleep(self.config.CHECK_INTERVAL)
    
    if __name__ == '__main__':
        config = Config()
        
        logging.Formatter.converter = lambda *args: datetime.now(tz=pytz.timezone(config.TIMEZONE)).timetuple()
        logging.basicConfig(
            filename='./gshare_manager.log',
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        
        try:
            proxmox_api = ProxmoxAPI(config)
            gshare_manager = GShareManager(config, proxmox_api)
            
            logging.info("GShare 관리 시작")
            gshare_manager.monitor()
        except KeyboardInterrupt:
            logging.info("프로그램 종료")
        except Exception as e:
            logging.error(f"예상치 못한 오류 발생: {e}")
    • config부분을 채워넣고 lxc 부팅시마다 자동실행되도록 서비스를 등록합니다.
    nano /etc/systemd/system/gshare_manager.service
    
        [Unit]
        Description=GShare Manager Service
        After=network.target
    
        [Service]
        ExecStart=/usr/bin/python3 /home/*****/gshare_manager.py
        WorkingDirectory=/home/*****/
        StandardOutput=inherit
        StandardError=inherit
        Restart=always
        User=root
    
        [Install]
        WantedBy=multi-user.target
    • 저는 그냥 user를 root로 했습니다. 이것만 하는 lxc이기 때문에..
    # 서비스 파일 권한 설정
    sudo chmod 644 /etc/systemd/system/gshare_manager.service
    
    # systemd 데몬 리로드
    sudo systemctl daemon-reload
    
    # 부팅 시 자동 시작 설정
    sudo systemctl enable gshare_manager
    
    # 서비스 시작
    sudo systemctl start gshare_manager
    
    # 서비스 상태 확인
    sudo systemctl status gshare_manager

     

    반응형
    Posted by 뭐하라