우분투한국커뮤니티

우리 커뮤니티는 Ubuntu와 오픈소스를 좋아하는 모두를 위한 곳으로, Ubuntu LoCo Council의 인증을 받은 공식 한국 커뮤니티 입니다. Ubuntu와 오픈소스를 공통점으로 다양한 사람들을 연결하고, 행사 개최와 Ubuntu 및 관련 오픈소스 기여 활동, 그리고 국내외 다양한 오픈소스 커뮤니티와 교류를 통해 Ubuntu와 오픈소스 저변 확대에 많은 노력을 기울이고 있습니다.


우분투(Ubuntu) 사용해보기

더 많은 사람들에게 자유 소프트웨어(Free software)를 제공한다는 사명으로 시작한 우분투(Ubuntu)는 캐노니컬(Canonical)과 전세계 사람들이 함께하는 우분투 커뮤니티가 함께 개발한 리눅스 기반 오픈소스 OS이자, 오픈소스 프로젝트 입니다. 사용하기 쉬운 리눅스 데스크톱으로 시작했지만, 오늘날 서버, 클라우드, WSL, 임베디드, 컨테이너 등 다양한 환경에서 접할 수 있고, 전세계에서 가장 많은 사람들이 사용하는 리눅스 배포판(Distribution)입니다.

여러분이 원하는 환경에서 오늘 우분투를 만나보세요!


포럼

우분투 위키: 미디어 위키 기반으로 개편 예정

아시는 분들도 있겠지만, 우분투 위키(wiki.ubuntu.com) 은 현재 MoinMoin 기반이고, 유지보수가 잘 안 된지 오래되어 오작동을 한 지 오래 되었습니다. 그동안 이를 대체하기 위한 많은 시도가 있었습니다. Ubuntu Discourse 기반 문서 작성: Ubuntu Discourse 에 특정 카테고리 만들고, 이를 별도 웹사이트에서 불러와서 정리하여 표시 Sphinx 기반 문서: Python 기반 문서 웹사이트 생성기인 Sphinx 에 우분투 테마를 적용하여, 우분투의 각 프로젝트별 문서 웹사이트 만들어 제공 그러다가, 우분투 위키 자체를 개편 할 예정이라는 소식입니다. 기존 MoinMoin 대체를 위해 미디어 위키를 도입하고, 여기에 전용 우분투 스타일 테마를 만들어 적용 예정이라고 하네요. 새로 구축중인 우분투 위키의 모습과 기능, 미디어위키 선택 배경 등이 궁금하다면 아래 글을 한번 읽어 보시면 좋을 것 같습니다. Ubuntu Community Hub – 13 Jan 26 A new Ubuntu wiki, Part 2: Features Community ubuntu-wiki blog This post is the second in a series about our project to create a new Ubuntu wiki. In A New Ubuntu Wiki Part 1: Announcement, I gave some background on the wiki project, the history of the Ubuntu wiki, and some issues with the current... Reading time: 3 mins 🕑 Likes: 21 ❤ 1개의 게시물 - 1명의 참여자 전체 주제 읽기


Thu, 15 Jan 2026 17:29:16 +0000

포럼

좌충우돌 재미나이와 씨름하기~~~

왜, 다시 해보려는지 이해가 가는 하루 였습니다. 일전에 phpipam 을 계획적으로 만들어 보려구 구성도를 작성하였는데요. 구성도를 보여 줘도, 오후 내내 이놈과 씨름하여 완성하였답니다. 더미 데이터 인줄도 모르고 몇번을 돌렸는지 모르겠네요. 모듈 형태로 하는게 좋은건지 아닌지 모르겠네요. 하나 바꾸면 다 바꿔줘야 하는게, 어렵더라구요. PHPIPAM + pfsense 연동: 네트워크 MAC 주소 및 호스트네임 자동 수집 자동화 0. 전체 프로세스 흐름도 전체 과정은 **Search(검색) → Collect(수집) → Sync(동기화)**의 3단계로 이루어집니다. Search: PHPIPAM API를 조회하여 정보(MAC, Hostname)가 누락된 IP 추출 Collect: pfsense에 SSH로 접속하여 /var/dhcpd/var/db/dhcpd.leases 파싱 Sync: 수집된 정보를 PHPIPAM API(PATCH)를 통해 최종 업데이트 1. 환경 설정 (config.yaml) 가장 먼저 각 서버의 접속 정보를 한곳에 관리합니다. YAML # PHPIPAM 설정 phpipam: url: "https://ipam.gnsinfo.mooo.com" app_id: "앱id" token: "xX8앱 코드uUwz" # 업데이트 대상 서브넷 지정 target_subnets: - "192.168.55.0/24" - "192.168.0.0/24" pfsense: host: "192.168.55.254" username: "admin" password: "패스워드" # 수집 대상 설정 targets: iptime: ip: "192.168.0.1" community: "smarthome" pfsense: ip: "192.168.55.254" community: "pfsense" ssh_ip: "192.168.55.254" hosts: - "192.168.55.9" - "192.168.55.204" 2. 1단계: 누락된 IP 검색 (search_phpipam.py) PHPIPAM에서 우리가 채워 넣어야 할 “숙제” 리스트를 가져오는 단계입니다. Python import requests import yaml import ipaddress import pandas as pd import urllib3 # SSL 경고 무시 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def is_ip_in_subnets(ip, subnets): try: ip_obj = ipaddress.ip_address(ip) for subnet in subnets: if ip_obj in ipaddress.ip_network(subnet): return True except: return False return False def run_search(): with open("config.yaml") as f: config = yaml.safe_load(f) api_url = f"{config['phpipam']['url']}/api/{config['phpipam']['app_id']}" token = config['phpipam']['token'] headers = {"token": token} target_subnets = config.get('target_subnets', []) print(f"--- PHPIPAM 검색 시작 ---") try: resp = requests.get(f"{api_url}/addresses/", headers=headers, verify=False) resp.raise_for_status() all_data = resp.json().get('data', []) print(f"서버에서 총 {len(all_data)}개의 IP를 발견했습니다.") targets = [] for addr in all_data: ip = addr.get('ip') # [수정] None 데이터 방어 로직: 값이 없으면 빈 문자열로 대체 후 strip() mac = (addr.get('mac') or '').strip() hostname = (addr.get('hostname') or '').strip() # 서브넷 필터링 if is_ip_in_subnets(ip, target_subnets): # MAC이 없거나, Hostname이 없거나, Hostname이 'unknown'인 경우 if not mac or not hostname or hostname.lower() == 'unknown': targets.append({ "id": addr.get('id'), "ip": ip, "current_mac": mac, "current_hostname": hostname }) if targets: df = pd.DataFrame(targets) df.to_csv("targets_to_fix.csv", index=False) print(f"==> 검색 완료: 업데이트가 필요한 IP {len(targets)}개를 찾았습니다.") else: print("==> 업데이트할 대상이 없습니다.") except Exception as e: print(f"오류 발생: {e}") if __name__ == "__main__": run_search() 3. 2단계: pfsense에서 정보 수집 (collect_data.py) 이제 진짜 정보를 찾으러 갈 차례입니다. pfsense의 DHCP 리스 파일에는 장비의 MAC과 이름이 들어있습니다. Python import pandas as pd import re import paramiko import yaml import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def get_pfsense_dhcp_leases(conf): """pfsense 서버에서 dhcpd.leases 파일을 읽어 파싱""" leases = {} client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: # config.yaml 설정값 사용 client.connect( hostname=conf['host'], username=conf['username'], password=conf['password'], timeout=10 ) stdin, stdout, stderr = client.exec_command("cat /var/dhcpd/var/db/dhcpd.leases") content = stdout.read().decode() # lease 블록 추출 (가장 최근 리스가 뒤에 오므로 dict로 덮어쓰면 최신 정보가 남음) lease_blocks = re.findall(r"lease ([\d\.]+) \{(.*?)\}", content, re.DOTALL) for ip, block in lease_blocks: mac_match = re.search(r"hardware ethernet ([:\w]+);", block) host_match = re.search(r'client-hostname "(.*?)";', block) mac = mac_match.group(1) if mac_match else None hostname = host_match.group(1) if host_match else None if mac: leases[ip] = {"mac": mac, "hostname": hostname} except Exception as e: print(f"pfsense 접속 실패: {e}") finally: client.close() return leases def run_collect(): # 1. 설정 로드 with open("config.yaml") as f: config = yaml.safe_load(f) # 2. PHPIPAM 검색 결과 읽기 try: targets_df = pd.read_csv("targets_to_fix.csv") except FileNotFoundError: print("targets_to_fix.csv가 없습니다. 검색(Step 1)을 먼저 실행하세요.") return # 3. pfsense에서 데이터 수집 print(f"pfsense({config['pfsense']['host']})에서 DHCP 리스 정보를 수집 중...") pfsense_data = get_pfsense_dhcp_leases(config['pfsense']) collected_results = [] for _, row in targets_df.iterrows(): ip = row['ip'] if ip in pfsense_data: info = pfsense_data[ip] real_mac = info['mac'] # 이름이 없으면 자동 생성하여 빈칸 채우기 real_host = info['hostname'] if info['hostname'] else f"host-{ip.replace('.', '-')}" print(f" [발견] {ip} -> MAC: {real_mac}, Hostname: {real_host}") collected_results.append({ "id": row['id'], "ip": ip, "mac": real_mac, "hostname": real_host }) else: print(f" [미발견] {ip} 정보가 리스 파일에 없습니다.") # 4. 최종 결과 저장 if collected_results: pd.DataFrame(collected_results).to_csv("update_targets.csv", index=False) print(f"--- 수집 완료: update_targets.csv 생성 ({len(collected_results)}건) ---") else: print("업데이트할 새로운 정보가 수집되지 않았습니다.") if __name__ == "__main__": run_collect() 4. 3단계: PHPIPAM 동기화 (sync_to_phpipam.py) 수집된 정보를 다시 PHPIPAM에 넣어줍니다. 이때 NaN 값을 빈 문자열로 처리하는 것이 포인트입니다. Python import pandas as pd import requests import yaml import urllib3 import json # SSL 경고 무시 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def run_sync(): try: with open("config.yaml") as f: config = yaml.safe_load(f) base_url = config['phpipam']['url'].rstrip('/') app_id = config['phpipam']['app_id'] token = config['phpipam']['token'] headers = {"token": token, "Content-Type": "application/json"} except Exception as e: print(f"설정 파일 로드 실패: {e}") return try: # [수정] fillna('')를 추가하여 비어있는 칸(NaN)을 빈 문자로 바꿉니다. df = pd.read_csv("update_targets.csv").fillna('') except FileNotFoundError: print("에러: update_targets.csv 파일이 없습니다.") return print(f"--- PHPIPAM 업데이트 전송 시작 (대상: {len(df)}건) ---") for _, row in df.iterrows(): address_id = row['id'] ip = row['ip'] mac = str(row['mac']).strip() # 문자열로 변환 및 공백 제거 hostname = str(row['hostname']).strip() # [추가] 만약 MAC과 Hostname이 둘 다 비어있다면 업데이트할 필요가 없으므로 건너뜁니다. if not mac and not hostname: print(f" [건너뜀] {ip} -> 업데이트할 정보(MAC/Hostname)가 없습니다.") continue url = f"{base_url}/api/{app_id}/addresses/{address_id}/" payload = {"mac": mac, "hostname": hostname} try: resp = requests.patch(url, headers=headers, json=payload, verify=False) if resp.status_code == 200: print(f" [성공] {ip} 업데이트 완료 (MAC: {mac})") else: print(f" [실패] {ip} (코드: {resp.status_code}, 사유: {resp.text})") except Exception as e: print(f" [에러] {ip} 전송 중 예외 발생: {e}") if __name__ == "__main__": run_sync() 5. 실행 및 결과 (main.sh) 이 모든 과정을 단 하나의 셸 스크립트로 실행합니다. Bash #!/bin/bash # 가상환경 활성화 (venv 이름이 다르면 수정하세요) source venv/bin/activate echo "==============================================" echo " PHPIPAM 정보 자동 동기화 프로세스 시작" echo "==============================================" # 1단계: PHPIPAM에서 업데이트가 필요한 IP 검색 echo "[Step 1/3] PHPIPAM 서버에서 대상 IP 검색 중..." python3 search_phpipam.py # search_phpipam.py가 생성한 targets_to_fix.csv 파일이 있는지 확인 if [ ! -f "targets_to_fix.csv" ]; then echo "업데이트할 대상이 없거나 오류가 발생했습니다. 종료합니다." exit 1 fi # 2단계: 검색된 IP를 대상으로 실제 MAC 주소 및 Hostname 수집 echo "" echo "[Step 2/3] 장비 접속 및 실제 정보(MAC/Hostname) 수집 중..." python3 collect_data.py # collect_data.py가 생성한 update_targets.csv 파일이 있는지 확인 if [ ! -f "update_targets.csv" ]; then echo "수집된 정보가 없습니다. 수집 단계를 확인하세요." exit 1 fi # 3단계: 수집된 정보를 PHPIPAM 서버에 최종 전송 echo "" echo "[Step 3/3] PHPIPAM 서버로 수집된 정보 전송 중..." python3 sync_to_phpipam.py echo "" echo "==============================================" echo " 모든 프로세스가 완료되었습니다." echo "==============================================" 1개의 게시물 - 1명의 참여자 전체 주제 읽기


Thu, 15 Jan 2026 13:44:20 +0000


현재 예정된 행사가 없습니다


커뮤니티 참여하기

우분투한국커뮤니티에 대해 더 자세히 알아보세요. 커뮤니티 소개 ›

후원사 & 파트너