2024년 11월 24일 일요일

파이썬 상대 경로 임포트

ImportError: attempted relative import with no known parent package
임포트를 상대 경로로 지정하면 편할 것 같아 써보니 이런 에러가 난다.
검색해보면 -m 옵션을 쓰면 된다는데, 정리할겸 테스트를 해봤다. 
다음과 같이 준비를 한다.
$ mkdir -p test/src/foo
$ cd test/src
$ touch foo/{__init__,bar}.py
$ cat << EOF > main.py
from foo import bar

def main():
    print('ok')

if __name__ == '__main__':
    main()
EOF

디렉토리 구조는 이렇게 된다. main.py 이외는 빈 파일이다.
test
└── src
    ├── foo
    │    ├── __init__.py
    │    └── bar.py
    └── main.py

1. 먼저 src/에서 실행해 본다.
$ python main.py     - ok
$ python -m main     - ok
잘 동작한다.
이 번에 하나 위에서 실행해 본다.
$ cd ..
$ python src/main.py # ok
$ python -m src.main # ModuleNotFoundError: No module named 'foo'
foo는 이 디렉토리엔 없다. 하나 아래인 src/에 있다.
앞의 3개는 main.py가 있는 곳에서 임포팅을 시도하는 것 같은데,
마지막 것은 현제 디렉토리에서 하고 있는 듯 하다.

2. 이 번엔 foo 앞에 .을 붙여 본다. (상대 경로 지정)
sed -i 's/foo/.&/' src/main.py

src/에서 실행해 보면,
$ cd src
$ python main.py     # ImportError: attempted relative import with no known parent package
$ python -m main     # ImportError: attempted relative import with no known parent package
문제의 임포트 에러가 나왔다.
하나 위에서 실행해 본다.
$ cd ..
$ python src/main.py # ImportError: attempted relative import with no known parent package
$ python -m src.main # ok
1에서 성공했던 앞의 세 명령은 실패했고, 실패했던 마지막 명령은 성공했다.

3. 하는 김에 src도 붙여 본다.
$ sed -i 's/\.foo/src&/' src/main.py
$ cd src
$ python main.py     # ModuleNotFoundError: No module named 'src'
$ python -m main     # ModuleNotFoundError: No module named 'src'
$ cd ..
$ python src/main.py # ModuleNotFoundError: No module named 'src'
$ python -m src.main # ok
앞의 두 명령은 src가 없으니 그렇다쳐도 3번째 명령도 src가 없다고 한다.

지금까지만 보면 python으로 실행하면 임포트 기준이 대상 파일이 있는 디렉토리고
python -m 으로 실행하면 명령을 실행하는 디렉토리인 것 같다.

4. 하나 더 올라가보면
$ cd ..
$ python -m test.src.main # ModuleNotFoundError: No module named 'src'
$ sed -i 's/src.foo/.foo/' test/src/main.py 
$ python -m test.src.main # ok
test/ 위로 올라오니 src가 없다고 에러가 났다.
src없이 상대 경로로 바꾸니 정상 동작을 한다.
상대 경로의 좋은점이다.

지금까지의 테스트를 보면
실행(?) 파일이 있는 디렉토리를 기준으로 임포트 경로를 잡으면 어디에서 실행시키든 문제가 없다.
상대 경로를 쓸거면 -m 옵션으로 실행하고 실행 디렉토리가 임포트의 기준(top-level)이 된다.

5. 하나를 더 보면
$ cd test
$ mkdir lib
$ touch lib/__init__.py
$ sed -i '1i from .. import lib' src/main.py
$ python -m src.main # ImportError: attempted relative import beyond top-level package
src와 같은 레벨에 있는 lib을 상대경로로 지정하면 top-level을 넘을 수 없다고 나온다.
$ cd ..
$ python -m test.src.main # ok
이 경우 src와 lib를 포함하도록 하나 더 위에서 실행하면 된다.

추가로,
1의 상태에서 다음과 같이 추가한다.
$ cat << EOF > src/foo/bar.py
from .baz import foobar
EOF
$ cat << EOF > src/foo/baz.py
foobar = 'foobar'
EOF
$ python src/main.py
bar.py는 상대 경로를 쓰고 있는데 아무런 문제가 없다.
오히려 .를 없애면 baz를 찾을 수 없다는 에러가 나온다. (foo.baz는 괜찮다)

애초부터 최초의 임포트에러는 top-level까지 올라왔을 때만 발생하는 듯 하다.

2024년 9월 17일 화요일

pi-hole 포트 충돌

osmc에 pihole을 사용 중인데,
apt dist-upgrade한 뒤 (rbp4-mediacenter-osmc 21.1.0-4),  pihole이 동작을 안한다.
확인 해보니 53포트가 충돌나서인데 처음 설치시 했던 설정이 먹히지 않는다.

$ sudo find /etc/systemd | grep connman
/etc/systemd/system/multi-user.target.wants/connman.service

$ cat /etc/systemd/system/multi-user.target.wants/connman.service
[Unit]
Description=Connman connection service
After=dbus.service network-pre.target wpa_supplicant.service
Wants=network.target remote-fs-pre.target
Before=remote-fs-pre.target network.target

[Service]
Type=dbus
BusName=net.connman
EnvironmentFile=-/etc/osmc/prefs.d/connman
Restart=always
RestartSec=5
ExecStart = /usr/bin/start-network
ExecStopPost = /bin/sleep 2
StandardOutput=null

[Install]
WantedBy=multi-user.target

업데이트해서인지 EnvironmentFile이 다르다.

새로운 EnvironmentFile에도 noproxy로 설정을 바꾼다.
 
$ sudo sed -i 's/dnsproxy=yes/dnsproxy=no/' /etc/osmc/prefs.d/connman
$ sudo systemctl restart connman
$ ps -ef | grep connman
root      3787     1  0 11:32 ?        00:00:00 /usr/sbin/connmand -n --nodnsproxy --config=/etc/connman.conf

이제 정상으로 돌아왔다.

2024년 4월 27일 토요일

obsidian + syncthing

obsidian (개인적이고 유연한 글쓰기 앱)을 알게되어 설치해 봤다.
기기 간의 동기화는 유료여서 동기화에는 syncthing을 사용한다.

1. 기기 및 공유 디렉토리
우분투 22.04
  ~/sync/obsidian
안드로이드 폰
  내부저장소/sync/obsidian
* 두 기기의 obsidian 디렉토리를 동기화 시킨다.
* wi-fi로 연결된 같은 네트워크

2. ubuntu 설정
  2.1. obsidian
    2.1.1. 설치
$ wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.5.12/obsidian_1.5.12_amd64.deb
$ sudo dpkg -i obsidian_1.5.12_amd64.deb
$ mkdir ~/sync/obsidian
$ obsidian

     2.1.2. 보관소 생성
English -> 한국어
새 보관소 생성 >
  위치: 탐색 > 홈 > sync > 열기
  보관소 이름: obsidian
생성

  2.2. syncthing
우분투 설치 가이드에 따라 apt로 설치하고 systemd 서비스로 등록해도 된다.
* 여기서는 도커 설치 가이드에 따라 도커로 설치. (추천에 따라 host 네트워크 사용)
  2.2.1 설치
$ cd ~/sync
$ mkdir -p ./syncthing/obsidian
$ cat docker-compose.yml
services:
  syncthing:
    image: syncthing/syncthing:1.27.6
    container_name: syncthing
    hostname: syncthing
    environment:
      - PUID=1000  # id -u 값 설정
      - PGID=1000  # id -g 값 설정 
volumes: - ./syncthing:/var/syncthing - ./obsidian:/var/syncthing/obsidian network_mode: host restart: unless-stopped $ docker compose up -d syncthing

  2.2.2 설정
브라우저로 http://localhost:8384/에 연결
GUI 인증 > 설정 > GUI > GUI 인증 사용자, GUI 인증 비밀번호 입력 > 저장

3. 안드로이드 폰 설정
  3.1. obsidian
    3.1.1. 설치
      Play 스토어 > 검색 > obsidian > 설치

     3.1.2. 보관소 생성
Create new vault >
  Vault location: Choose > 내부저장소 > sync(생성) > 선택
  Vault name: obsidian
Create 탐색기?(왼쪽 위) > 설정(톱니바쿼) > Options > General > Language > English -> 한국어 > Relaunch

  3.2. syncthing
    3.2.1. 설치
      Play 스토어 > 검색 > syncthing > 설치

4. 동기화 설정
1) 우분투 > http://localhost:8384/ > 동작 > 기기 식별자 보기 > 큐알코드 표시 상태
2) 폰 > syncthing > 기기 > + > 큐알코드 > 우분투의 큐알코드 촬영, 이름: 우분투 > 확인 (체크마크)
3) 우분투 > http://localhost:8384/ > 큐알코드 종료 > 새 기기 > +기기 추가 > 저장
4) 폰 > syncthing > 폴더 > + >
  폴더명: obsidian,
  디렉토리: 내부저장소/sync/obsidian > 선택
  우분투: 활성화
확인 (체크마크)
5) 우분투 > http://localhost:8384/ > 새 폴더 > v 추가 > 폴더명: obsidian, 폴더 경로: ~/obsidian > 저장

5. 동기화 확인
우분투나 폰의 obsidian 에서 파일을 만들거나 수정하면 15초 ~ 20초 정도 후에 상대 기기에서 반영 확인.
폰의 Wi-fi를 끊고 모바일 네트워크로도 동기화 되는 것 확인.

6. 감상
메모를 보는데 네트워크를 사용하지 않는다니 좋다.
폰에서는 syncthing를 꺼뒀다가(☰ > X 나가기) 필요할 때만 켜서 동기화하면 될 듯 하다

우분투에 설치한 obsidian에는 Vim 키 설정(옵션 > 편집기 > Vim 키 설정)이라는게 있다.
vimwiki는 잘 쓰고 있는데 vimwiki와 연동하거나 외부에서 메모하는데 써봐야겠다.

우분투의 syncthing를 docker로 설치하니 설정이 깔끔하지 않은 듯 하다.

syncthing으로 공유 폴더를 설정시 서로 설정해서 그런지 같은 설정이 2개가 되버렸다.
같은 것이니 하나를 지우면 되긴 하는데 설정 방법이 틀린걸까?

2023년 10월 11일 수요일

리모트 서버의 도커 앱 디버깅

로칼의 vscode 로 리모트 서버(ubuntu)에서 돌아가는 파이썬 도커 앱을 디버깅하는 설정


1. 리모트 서버에 도커 컨테이너가 떠 있음

(리모트에 연결 안 된 상태)

2. 로칼의 vscode 시작
왼쪽 아래의 [><] 클릭 (Open a remote window)
Connect to Host...
설정된 호스트 선택 [또는 새 SSH 호스트 추가... 선택 및 설정]

(리모트에 연결 된 상태)

3. [리모트에 Dev Containers 플러그인이 설치되지 않은 경우 설치]
명령 파레트 > Dev Containers: Attach to running container >
연결할 컨테이너 이름 선택 [처음 연결하는 경우 서버 설치로 시간 걸림]

(리모트의 도커에 연결 된 상태)

4. [컨테이너에 Python 플러그인이 설치되지 않은 경우 설치]
메뉴 > File > Open Folder... > 프로젝트 경로 선택
명령 파레트 > Python: Select Interpreter >
앱에서 사용하는 파이썬 선택


5. 디버깅 [luanch 또는 attach]

2023년 8월 26일 토요일

vite + react + typescript

셋업
# pre-commit 과 yarn 설치
pip install pre-commit
npm i -g yarn

# 프로젝트 생성
yarn create vite 프로젝트명 --template react-swc-ts
cd 프로젝트명
git init
git add .
git commit -m 'initial commit'

# pre-commit 및 패키지 설치
pre-commit install
yarn

# 실행
yarn dev

.pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
      - id: end-of-file-fixer
      - id: trailing-whitespace
        args: [--markdown-linebreak-ext=md]
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.0.2                                        
    hooks:
      - id: prettier
  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.47.0
    hooks:
      - id: eslint
        files: \.[jt]sx?$
        types: [file]

vite.config.ts
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react-swc';

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  //const env = {...loadEnv(mode, '..', ''), ...loadEnv(mode, '.', '')};
  const env = loadEnv(mode, '.', '');
  return {
    plugins: [react()],
    server: {
      port: parseInt(env.PORT),
      open: true, // 시작시 브라우저 띄움
    },
  };
});

type Props = {
  setCount: (value: number) => void; // 함수
};

마운트/언마운트
  useEffect(() => {
    console.log('마운트');
    return () => {
      console.log('언마운트');
    };
  }, []);

마운트/업데이트
  useEffect(() => {
    console.log('마운트/업데이트');
  }); // 또는 [변수 리스트]

업데이트만
  const didMountRef = useRef(false);
  useEffect(() => {
    if (!didMountRef.current) { 
      didMountRef.current = true;
      return;
    }

    console.log('업데이트');
  }); // 또는 [변수 리스트]

확장 프로그램 > React Developer Tools > Components > 톱니(View Settings) > General > [v] Highlight updates when components render.

2023년 7월 23일 일요일

docker registry proxy 테스트

registry kvm: 192.168.122.99
proxy kvm: 192.168.122.98
client kvm: 192.168.122.97

    1. registry 용 vm 생성
host $ sudo virt-install --name registry --os-variant ubuntu22.04 --vcpus 1 --ram 2048 \
--network network:default --cdrom /path/to/ubuntu-22.04.1-live-server-amd64.iso \
--disk size=10

    2. (공통) 도커 설치
host $ virsh start registry
host $ ssh registry
registry $ echo "$USER ALL=(ALL:ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers
registry $ sudo apt update 
registry $ sudo apt install vim iptables
registry $ wget https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/containerd.io_1.6.21-1_amd64.deb
registry $ wget https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/docker-buildx-plugin_0.11.1-1~ubuntu.22.04~jammy_amd64.deb
registry $ wget https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/docker-ce-cli_24.0.4-1~ubuntu.22.04~jammy_amd64.deb
registry $ wget https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/docker-ce_24.0.4-1~ubuntu.22.04~jammy_amd64.deb
registry $ wget https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/docker-compose-plugin_2.19.1-1~ubuntu.22.04~jammy_amd64.deb
registry $ sudo dpkg -i *.deb
registry $ rm -rf *
registry $ sudo usermod -aG docker $USER
registry $ sudo shutdown 0

    3. registry vm 을 proxy vm(과 client vm)으로 복사
host $ virt-clone --original registry --name proxy --file /var/lib/libvirt/images/proxy.qcow2
host $ virsh dumpxml proxy | grep '<mac'
      <mac address='52:54:00:ce:18:74'/>
host $ virsh net-edit default
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.100' end='192.168.122.254'/>
      <host mac='52:54:00:a9:5f:97' name='registry' ip='192.168.122.99'/>
      <host mac='52:54:00:ce:18:74' name='proxy' ip='192.168.122.98'/>
      <host mac='52:54:00:22:dd:40' name='client' ip='192.168.122.97'/>
</dhcp> </ip> </network$ virsh net-destroy default host $ virsh net-start default

    4. proxy vm(과 client vm)의 hostname 변경
host $ virsh start proxy
host $ ssh proxy
proxy $ sudo hostnamectl set-hostname proxy
proxy $ sudo sed -i 's/registry/proxy/' /etc/hosts
proxy $ sudo reboot

    5. registry 설정
    5.1. 사설 repository 사용 설정
host $ ssh registry
registry $ sudo vi /etc/docker/daemon.json
{
  "insecure-registries": ["192.168.122.99:5000"]
}
registry $ sudo systemctl restart docker

    5.2. registry 실행
registry $ mkdir -p workspace/registry
registry $ cd workspace
registry $ vi docker-compose.yml
version: "3"
services:
  registry:
    image: registry:2.8.2
    container_name: registry
    volumes:
      - ./registry:/var/lib/registry
    ports:
      - 5000:5000
    restart: always
registry $ docker compose up -d

    5.3. 테스트용 도커 이미지 생성
registry $ mkdir ~/test
registry $ cd ~/test
registry $ vi Dockerfile
FROM alpine
CMD echo 'hello registry 1.0'
registry $ docker build -t 192.168.122.99:5000/test/hello:1.0 .
registry $ docker push 192.168.122.99:5000/test/hello:1.0
registry $ curl http://192.168.122.99:5000/v2/_catalog
{"repositories":["test/hello"]}
registry $ curl http://192.168.122.99:5000/v2/test/hello/tags/list
{"name":"test/hello","tags":["1.0"]}

    6. proxy 설정
host $ ssh proxy
proxy $ mkdir -p workspace/registry
proxy $ cd workspace
proxy $ vi docker-compose.yml
version: "3"
services:
  proxy:
    image: registry:2.8.2
    container_name: proxy
    environment:
      - REGISTRY_PROXY_REMOTEURL="http://192.168.122.99:5000"
    volumes:
      - ./registry:/var/lib/registry
    ports:
      - 5000:5000
    restart: always
proxy $ docker compose up -d

    7. client 설정
host $ ssh client
client $ sudo vi /etc/docker/daemon.json
{
  "insecure-registries": ["192.168.122.98:5000"]
}
client $ sudo systemctl restart docker
client $ curl http://192.168.122.98:5000/v2/_catalog
{"repositories":[]}
client $ docker run --rm -it 192.168.122.98:5000/test/hello:1.0
hello registry 1.0
client $ curl http://192.168.122.98:5000/v2/_catalog
{"repositories":["test/hello"]}

참고:
https://waspro.tistory.com/532
https://bobcares.com/blog/docker-registry-mirroring/
https://docs.docker.com/registry/spec/api/

2023년 7월 1일 토요일

우분투 업데이트 후 크롬 크래시

우분투 업데이트 후 크롬이 시작하질 않는다.
$ /usr/bin/google-chrome-stable
[5836:5836:0702/120130.931454:FATAL:credentials.cc(127)] Check failed: . : 허가 거부 (13)
추적/중단점 함정 (코어 덤프됨)

찾아보니 새로운 nvidia 커널을 사용하면 발생한다는 것으로 보인다.
https://stackoverflow.com/a/76299196
https://bugs.launchpad.net/ubuntu/+source/linux-meta-nvidia-5.19/+bug/2017980

현재 커널은 nvidia 용인 것으로 보인다.
$ uname -sr
Linux 5.19.0-1010-nvidia-lowlatency

우선 확인을 위해 다른 커널을 사용해 보기로 한다.
https://askubuntu.com/a/16049

현재 커널은 3가지가 설치되어 있다.
$ cat /boot/grub/grub.cfg  | egrep 'menuentry |submenu ' | awk -F"'" '{print $1 $2}'
menuentry Ubuntu
submenu Advanced options for Ubuntu
	menuentry Ubuntu, with Linux 5.19.0-1010-nvidia-lowlatency
	menuentry Ubuntu, with Linux 5.19.0-1010-nvidia-lowlatency (recovery mode)
	menuentry Ubuntu, with Linux 5.19.0-46-generic
	menuentry Ubuntu, with Linux 5.19.0-46-generic (recovery mode)
	menuentry Ubuntu, with Linux 5.15.0-60-generic
	menuentry Ubuntu, with Linux 5.15.0-60-generic (recovery mode)
menuentry UEFI Firmware Settings

아마도 부팅시 grub 메뉴가 안보이도록 되어 있을텐데,
재부팅 > (bios가 끝나고) grub 로딩중일(이라고 생각될) 때 ESC 를 한 번만 누른다.

위에서 확인한 커널을 사용한다.
Advanced > 'Ubuntu, with Linux 5.19.0-46-generic'

부팅후 크롬을 실행하니 문제없이 뜬다.

다음과 같은 해결법이 있겠다.
  1. 크롬 실행시 --no-sandbox 옵션 추가
    동작은 잘 하는데 보안상 문제가 있다고하니 안쓰는게 나을 듯

  2. 문제없는 nvidia 용 커널 사용
    수정 버전이 나올 때까지 기다린다.

  3. generic 커널을 사용하도록 grub 메뉴를 수정
  4. $ sudo sed -i '/^GRUB_DEFAULT=/s/=.*/="1>2"/' /etc/default/grub
    $ sudo update-grub
    $ sudo reboot

  5. nvidia 용 커널 삭제
  6. $ dpkg -l | grep 'ii  linux-image'
    $ sudo apt remove linux-image-5.19.0-1010-nvidia-lowlatency커
우선 nvidia 커널쪽 문제인듯 하니 4번으로 해결했다.
크롬이 수정할 수도 있을텐데 그러면 다시 설치하면 되겠지.
 

참고
  • https://stackoverflow.com/a/76299196
  • https://bugs.launchpad.net/ubuntu/+source/linux-meta-nvidia-5.19/+bug/2017980
  • https://askubuntu.com/a/16049
  • https://linux.how2shout.com/how-to-change-default-kernel-in-ubuntu-22-04-20-04-lts/