<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[LKS410의 기술지]]></title><description><![CDATA[LKS410의 기술지]]></description><link>https://dev.lks410.me</link><generator>RSS for Node</generator><lastBuildDate>Thu, 23 Apr 2026 11:52:35 GMT</lastBuildDate><atom:link href="https://dev.lks410.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[논리적 그룹 네트워크 내 파일교환 시스템에 관한 구상]]></title><description><![CDATA[개요
한 조직 내 여러 컴퓨터가 파일을 교환해야 할 경우, 상당히 많은 양의 코드를 새로 짜야 한다. 이를 간편하게 하기 위해 조직 내의 서버에 의존하여 파일 전송을 할 수 있도록 하는 라이브러리 혹은 프레임워크를 구상한다.
궁극적으로 달성하고자 하는 목표
최종적으로는 이 라이브러리가 코드의 단순화를 목적으로 하기에, 개발자가 다음과 같은 코드만으로 파일을]]></description><link>https://dev.lks410.me/groupnetwork-file-exchange-idea</link><guid isPermaLink="true">https://dev.lks410.me/groupnetwork-file-exchange-idea</guid><dc:creator><![CDATA[LKS410]]></dc:creator><pubDate>Sun, 15 Feb 2026 13:16:35 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/68025b9e4560db24641a7411/7ecb9a41-3b8c-44da-a092-61368c03bf18.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>개요</h2>
<p>한 조직 내 여러 컴퓨터가 파일을 교환해야 할 경우, 상당히 많은 양의 코드를 새로 짜야 한다. 이를 간편하게 하기 위해 조직 내의 서버에 의존하여 파일 전송을 할 수 있도록 하는 라이브러리 혹은 프레임워크를 구상한다.</p>
<h2>궁극적으로 달성하고자 하는 목표</h2>
<p>최종적으로는 이 라이브러리가 코드의 단순화를 목적으로 하기에, 개발자가 다음과 같은 코드만으로 파일을 쉽게 공유할 수 있도록 하고자 한다.</p>
<p>이상적인 예시:</p>
<pre><code class="language-python">from osext.libaqnetutil.EdgeMachine import EdgeMachine as network

def assert_network_registered() -&gt; bool:
    return network.is_enrolled() # Returns true if enrolled to central server and is authorized

def main():
    # Check if network is not enrolled.
    # If so, the code should not work.    
    if not assert_network_registered(): raise Exception("Not enrolled to network!")
    
    # Get machine ID of machine name for john@mycompany.com
    machine_id: str = network.get_machine_id_for_user("john@mycompany.com")

    # Tries to send file. The library performs all the heavylifts.
    is_successful: bool = network.send_to_machine(machine_id, "/home/user/Desktop/my-file.txt")

    # Or, send using user identifier
    is_successful: bool = network.send_to_user("john@mycompany.com", "/home/user/Desktop/my-file.txt")
</code></pre>
<h2>네트워크 모델 - 구성 요소</h2>
<ul>
<li><p>CentralServer</p>
<ul>
<li><p>Relay</p>
</li>
<li><p>Authorization</p>
</li>
<li><p>Hold</p>
</li>
</ul>
</li>
<li><p>EdgeMachine</p>
</li>
</ul>
<p>해당 네트워크는 크게 두가지 종류가 있다: CentralServer 와 EdgeMachine 이다. EdgeMachine 은 일반적으로 사용하는 컴퓨터이고, CentralServer 는 EdgeMachine 이 다른 EdgeMachine 과 통신하기 위해 반드시 한번 이상 통신하는 서버이다.</p>
<h3>CentralServer</h3>
<p>CentralServer 는 세가지 역할을 수행한다. Relay, Authorization, Hold 이며, 한 머신은 여러가지 역할을 동시에 수행할 수 있다.</p>
<ul>
<li><p>Relay</p>
<ul>
<li>EdgeMachine 의 주소 및 상태를 반환하거나, 파일 전달을 위한 용도이다.</li>
</ul>
</li>
<li><p>Authorization</p>
<ul>
<li><p>EdgeMachine 의 장치 정보를 저장한다.</p>
</li>
<li><p>전송하는 장치와 수신할 장치의 자격 검증을 한다.</p>
</li>
<li><p>KMS 의 역할을 수행한다.</p>
</li>
<li><p>장치의 최초 등록 및 등록 정보 저장 역할을 수행한다.</p>
<ul>
<li>장치 등록시, Relay 와 Hold 서버의 주소를 반환하거나, 무작위 생성 시스템에 관한 정보를 반환한다.</li>
</ul>
</li>
</ul>
</li>
<li><p>Hold</p>
<ul>
<li>만약 Relay 를 사용한 파일 전송 시도시 실패할 경우, 임시로 파일을 저장할 서버이다. 이 서버는 필수가 아니며, 그러할 경우 임시 파일 저장을 위한 기능은 사용할 수 없다.</li>
</ul>
</li>
</ul>
<h3>EdgeMachine</h3>
<p>최종 사용자가 사용하는 장치이며, 이는 CentralServer 에 연결하여 파일 송수신을 준비할 수 있다.</p>
<p>아래 설명에서 사용자는 각각 김철수, 김영희로 설정한다.</p>
<ul>
<li><p>철수를 <code>DomainA/Group1</code> 에, 영희를 <code>DomainA/Group2</code> 에 지정한다.</p>
</li>
<li><p>철수의 로그온 아이디는 <code>bob@mynetwork.com</code> 이며, 사용중인 장치의 아이디는 다음과 같다:</p>
<ul>
<li><p>업무용 랩탑: <code>DomainA/Group1/bob@mynetwork.com/My-Laptop</code></p>
</li>
<li><p>업무용 데스크탑: <code>DomainA/Group1/bob@mynetwork.com/My-Desktop</code></p>
</li>
</ul>
</li>
<li><p>영희의 로그온 아이디는 <code>alice@mynetwork.com</code> 이며, 사용중인 장치의 아이디는 다음과 같다:</p>
<ul>
<li><p>업무용 랩탑: <code>DomainA/Group2/alice@mynetwork.com/My-Laptop</code></p>
</li>
<li><p>업무용 데스크탑: <code>DomainA/Group2/alice@mynetwork.com/My-Desktop</code></p>
</li>
</ul>
</li>
</ul>
<h2>CentralServer 정책 모델 예시</h2>
<h3>Relay</h3>
<pre><code class="language-json">{
    "RelayWhitelist": {
    	"DomainA/Group1/SubGroup1": {
	    	"P2PConnection.GeneralAllow": false,
    		"P2PConnection.Whitelist": [
    			"DomainA/Group2/SubGroup1/*"
    		]
    	},
    	"DomainA/Group2": {
    		"P2PConnection.GeneralAllow": false,
    		"P2PConnection.Whitelist": [
    			"DomainA/Group1/*"
    		]
    	}
    }
}
</code></pre>
<h2>파일 전송 모델</h2>
<p>모델 A 는 직접 전송 모델이다. 파일 전송시에 서버를 거치지 않는다. 이는 보안이 중요한 파일을 전송할 때 사용할 수 있다.</p>
<p>모델 B 는 서버 중계 전송 모델이다. 파일 전송시 서버에 저장되며, 감사의 목적이 필요할 때 사용할 수 있다.</p>
<p>철수가 영희에게 파일을 전송하려 할 경우, 작동 메커니즘은 다음과 같다:</p>
<ol>
<li><p>철수의 컴퓨터가 Authorization 서버에 등록될 때 저장한 각 분담 서버의 주소를 불러온다.</p>
<ol>
<li>예시: <code>auth.mynetwork.com</code>, <code>relay.mynetwork.com</code></li>
</ol>
</li>
<li><p>철수가 Authorization 서버에 영희의 장치 정보를 요청한다. 이 때, 서버 측에서는 정책에 따라 전송 모델 A 혹은 모델 B 를 결정한다. 만약 모델 A로 연결이 된다면 영희의 공개 암호화 키를 반환하고, 모델 B로 연결 된다면 정책과 수신자 장치 온라인 여부에 따라 Relay 혹은 Hold 서버의 공개 암호화 키를 반환한다.</p>
<ol>
<li><p>요청 예시: <code>REQ:&lt;현재 장치 인증 JWT 문자열&gt;:pk:&lt;로그온 ID&gt;, &lt;로그온 ID&gt;, .... :&lt;파일 유형&gt;:&lt;확장자&gt;:&lt;파일 크기 in bytes&gt;:EOD</code></p>
<ol>
<li><code>REQ:2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892:pk:alice@mynetwork.com:plain-text:txt:32768:EOD</code></li>
</ol>
</li>
<li><p>반환 예시: <code>RES:{"machines":{"name":{"pk":"xxx", ... }, ... }}:EOD</code></p>
<ol>
<li><code>RES:{"machines":{"DomainA/Group2/alice@mynetwork.com/My-Laptop":{"pk":"421c76d77563afa1914846b010bd164f395bd34c2102e5e99e0cb9cf173c1d87"},"DomainA/Group2/alice@mynetwork.com/My-Desktop":{"pk":"92ff90719d49bc974b12c2dfa6bf319c28f1d59419878e9148c9c472d5d9f599"}}}:EOD</code></li>
</ol>
</li>
</ol>
</li>
<li><p>철수가 보내고자 하는 장치를 고른 후 (예: My-Desktop) 이것의 장치 이름을 Relay 서버에 전송하여 커넥션에 필요한 정보를 반환받는다. 이 때, 서버 측에서는 정책에 따라 전송 모델 A 혹은 모델 B 를 결정한다. 만약 모델 A 가 되었을 경우 NAT Traversal 및 STUN/TURN 을 활용하기 위한 정보를 담은 후 영희의 장치 주소를 전송하고, 모델 B 가 되었을 경우, 정책과 수신자 장치 온라인 여부에 따라 Relay 혹은 Hold 서버의 해당 정보를 반환한다. Relay 서버에는 Session ID 와 바인딩 된 체크섬 및 수신 발신인의 정보를 포함한다.</p>
<ol>
<li><p>요청 예시: <code>REQ:&lt;현재 장치 인증 JWT 문자열&gt;:&lt;장치 이름&gt;,&lt;장치 이름&gt;, ...:&lt;파일 유형&gt;:&lt;확장자&gt;:&lt;파일 크기 in bytes&gt;:&lt;SHA-256 체크섬&gt;:EOD</code></p>
<ol>
<li><code>REQ:2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892:DomainA/Group2/alice@mynetwork.com/My-Desktop:plain-text:txt:32768:0c0e36f8c9580a11bb72906e973b81be37c1d0ab0ef4812a990069bfac142df7:EOD</code></li>
</ol>
</li>
<li><p>반환 예시: <code>RES:{"장치 이름":{"session-id":"xxx","phys-addr":"","stun":{},"turn":{},"nat":{}}}:EOD</code></p>
<ol>
<li><code>RES:{"DomainA/Group2/alice@mynetwork.com/My-Desktop":{"session-id":"12341234","phys-addr":"14.250.xxx.xxx","stun":{},"turn":{},"nat":{}}}:EOD</code></li>
</ol>
</li>
</ol>
</li>
<li><p>철수는 이제 저 주소로 전송 요청을 시도한다. 이 때, 2번 단계에서 불러온 PK 로 비대칭 암호화를 하고, 3번 단계에서 받아온 연결 정보로 파일 전송을 시도한다.</p>
</li>
</ol>
<h2>파일 수신 모델</h2>
<p>파일 수신은 서버 정책 및 그룹 정책에 따라 Polling 과 Reverse (검증 필요) 가 있다. 이 때, Polling 모드로 파일을 수신할 경우, Relay 서버는 송신인에게 반드시 Hold 서버의 정보를 전송해야 한다.</p>
<ul>
<li><p>Polling: 영희의 컴퓨터는 백그라운드에서 매 n 초 마다 Hold 서버에 현재 장치로 수신 예약된 파일이 있는지 확인한다. 만약 파일이 있다면 해당 파일을 자동으로 다운로드 한 후, 성공시 Hold 서버에서 해당 파일을 삭제한다.</p>
</li>
<li><p>Reverse: 영희의 컴퓨터는 Relay 서버로 무기한 요청을 연다. Relay 서버에서 전달할 파일이 들어오면 해당 파일을 해당 무기한 요청의 응답으로 반환한다. 이는 Reverse shell 과 비슷한 원리이다.</p>
</li>
</ul>
<h2>저장 위치에 관하여</h2>
<p>파일이 백그라운드에서 들어온다면, 파일이 자동으로 저장될 위치를 판단해야 한다. 이는 장치 내에서 설정 가능하며, 위에서 요청한 파일 유형 혹은 파일 확장자를 기반으로 판단한다.</p>
<p>만약 매핑이 없을 경우, 사용자에게 저장할 위치를 묻거나 Downloads 에 저장할 의사를 팝업으로 묻고 응답을 받을 때 까지 대기 시키거나, /tmp 에 저장 후 사용자에게 알린다 (설정에 따라 다름)</p>
<p>예: image-userphoto 를 /home/?/Pictures/ 로 매핑 설정하면 수신 에이전트는 받은 파일을 해당 위치에 자동 저장한다.</p>
<h2>파일 전송 이어받기</h2>
<p>네트워크 단절이나 시스템 종료 등 모종의 이유로 파일 전송이 중단될 경우를 대비해 식별자 기반의 이어받기를 지원한다.</p>
<ol>
<li><p>최초 전송 요청 시, 송신자는 해당 파일 전송 세션에 대한 고유 식별자를 서버로부터 발급받아 헤더에 포함한다.</p>
</li>
<li><p>전송이 중간에 끊긴 후 다시 연결될 때, 송신자 혹은 수신자는 이 고유식별자와 함께 현재까지 성공적으로 송수신된 파일의 바이트 위치(Offset, 예: 2147483648)를 명시하여 요청한다.</p>
</li>
<li><p>연결이 재개되면 해당 Offset 지점부터 청크(Chunk) 단위로 파일 업로드 및 다운로드가 진행되며, 메모리 부하를 방지하기 위해 디스크에 스트리밍 방식으로 직접 기록한다.</p>
</li>
</ol>
<p>![](<a href="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/68025b9e4560db24641a7411/47c524d2-6b95-4b30-9030-f98b289e26e8.png">https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/68025b9e4560db24641a7411/47c524d2-6b95-4b30-9030-f98b289e26e8.png</a> align="middle")</p>
<h2>문서 업데이트 내역</h2>
<ol>
<li><p>[2026. 02. 20] Listen 모드 관련 내용 삭제</p>
</li>
<li><p>[2026. 02. 20] Master 서버 관련 내용 삭제</p>
</li>
<li><p>[2026. 02. 20] 파일 전송 이어받기 관련 내용 추가</p>
</li>
<li><p>[2026. 02. 20] 정책 파일 관련 내용 수정</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Object Oriented Interpreter 언어에 대한 구상]]></title><description><![CDATA[개요
본문은 Object Oriented Interpreter 와 그것을 기반으로 한 ObjectiveShell 의 이론적 설계에 관해 서술하며 초기 모델링을 기술한다.
존재의 목적
상당히 많은 기존 터미널 체계 (bash, cmd 등) 는 오브젝트를 지원하지 않는다. 구조체나 복잡한 명령을 하려면 상당히 많은 기호와 직관적이지 않은 명령을 사용해야 한다. 이를 해결하고자 Python 을 기반으로 한 ObjectiveShell 을 구상해 보았다...]]></description><link>https://dev.lks410.me/about-object-oriented-interpreter</link><guid isPermaLink="true">https://dev.lks410.me/about-object-oriented-interpreter</guid><dc:creator><![CDATA[LKS410]]></dc:creator><pubDate>Fri, 06 Feb 2026 18:45:41 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-6rcc7jqu">개요</h2>
<p>본문은 Object Oriented Interpreter 와 그것을 기반으로 한 <a target="_blank" href="https://github.com/410-dev/AquariusOS/blob/5768b5a58e6c7d7c8c476db998420d3af8ff6887/src/libraries/system/python/oscore/objectiveshell.py">ObjectiveShell</a> 의 이론적 설계에 관해 서술하며 초기 모델링을 기술한다.</p>
<h2 id="heading-7kg07j6s7j2yiouqqeyggq">존재의 목적</h2>
<p>상당히 많은 기존 터미널 체계 (bash, cmd 등) 는 오브젝트를 지원하지 않는다. 구조체나 복잡한 명령을 하려면 상당히 많은 기호와 직관적이지 않은 명령을 사용해야 한다. 이를 해결하고자 Python 을 기반으로 한 ObjectiveShell 을 구상해 보았다.</p>
<h2 id="heading-6riw67cyioq1royhsa">기반 구조</h2>
<p>ObjectiveShell 에는 세션 단위 (<code>ObjectiveShellSession</code>)가 존재하며, 한 세션에는 두 종류의 메모리가 존재한다.</p>
<p>첫번째는 환경 변수 (<code>environment</code>)이다. 이는 bash 와 cmd 에서 작동하는 것과 같이 PATH 와 같은 변수를 담는 용도로 사용되며 반드시 string 타입만 사용할 수 있다. 이는 기존 명령어 환경과 친숙하게 작동하기 위함이다.</p>
<p>두번째는 일반 변수 (<code>variables</code>) 이다. 이는 실제 Python 오브젝트들을 가지고 있으며 파라미터로 넘겨줄 수 있다.</p>
<h2 id="heading-66qf66c57ja0">명령어</h2>
<p>명령어는 Python 파일로 구성되어 있으며, 두가지 혹은 세가지의 메서드를 담고 있다. (<a target="_blank" href="https://github.com/410-dev/AquariusOS/blob/b237c6d9435cf2d3e053efafb617c3ca85fcc680/src/resources/ObjectiveShell/Instructions/foundation/foreach.py">예제 파일</a>)</p>
<ol>
<li><p>(필수) <code>help(session) -&gt; str</code></p>
<ol>
<li>이는 도움말 문자열을 반환한다.</li>
</ol>
</li>
<li><p>(필수) <code>main(session, arg1, arg2, arg3 … argN) -&gt; tuple(int, Any)</code></p>
<ol>
<li><p>이는 해당 명령어를 일반적으로 실행하며 정해진 길이의 매개변수를 받을 때 사용된다.</p>
</li>
<li><p>반환하는 튜플의 첫번째 <code>int</code> 항목은 종료 코드다. 기본적으로 종료코드 0이 정상 종료이다.</p>
</li>
<li><p>반환하는 튜플의 두번째 <code>Any</code> 항목은 반환 오브젝트이다. 이는 오브젝트를 다룰 때 사용한다.</p>
</li>
<li><p>반환하는 항목이 만약 <code>int</code> 하나만 반환한다면, 그것이 연산의 결과값이더라도 종료 코드로 인식한다.</p>
</li>
<li><p>반환하는 항목이 만약 <code>int</code> 가 아닌 다른 타입이며 그것 하나만 반환된다면, 그것은 결과값으로 인식하고 종료코드를 0으로 간주한다.</p>
</li>
</ol>
</li>
<li><p>(선택) <code>udef_main(session, arg1, arg2, arg3 … argN) -&gt; tuple(int, Any)</code></p>
<ol>
<li><p>이는 해당 명령어를 실행하며 정해지지 않은 길이의 매개변수를 받을 때 사용된다. 예를 들어, 만약 받는 매개변수가 5개 이상이고 (<code>arg1, arg2 … arg5</code>), 들어온 매개변수가 10개라면, 첫 4개의 매개변수는 각자의 자리에 들어가고 (<code>arg1 … arg4</code>) 나머지 6개는 하나의 리스트로 묶여 마지막 매개변수 자리에 들어간다 (<code>arg5</code>).</p>
</li>
<li><p>반환값은 <code>main</code> 과 동일하다.</p>
</li>
</ol>
</li>
</ol>
<h2 id="heading-7yym7iux">파싱</h2>
<p>명령어에서 변수를 불러오거나 인라인 인터프리팅이 필요한 경우 다음과 같이 처리한다.</p>
<p>인라인 명령 실행: <code>$(명령 라인)</code><br />인라인 변수 호출: <code>${var:&lt;변수 이름&gt;}</code><br />인라인 환경변수 호출: <code>${env:&lt;변수 이름&gt;}</code></p>
]]></content:encoded></item><item><title><![CDATA[dpkg/apt 패키지 잠그기]]></title><description><![CDATA[dpkg 는 상당히 안정적이고 잘 만들어진 패키지 관리자라고 생각하지만, 특정 조건에서의 제어 능력이 아쉬울 때가 있다. 예를 들어 특정 패키지 설치/업데이트/제거를 차단하고자 할때 (우발적 변경을 방지) 이를 제어할 수 없다.
dpkg 에는 패키지를 hold 하는 기능이 있지만 이것으로는 부족하다. 따라서 dpkg wrapper 스크립트를 만든 후 이를 dpkg-divert 로 dpkg 처럼 작동하게 만들었다.
/usr/sbin/dpkg-wr...]]></description><link>https://dev.lks410.me/dpkgapt</link><guid isPermaLink="true">https://dev.lks410.me/dpkgapt</guid><dc:creator><![CDATA[LKS410]]></dc:creator><pubDate>Fri, 06 Feb 2026 18:23:23 GMT</pubDate><content:encoded><![CDATA[<p>dpkg 는 상당히 안정적이고 잘 만들어진 패키지 관리자라고 생각하지만, 특정 조건에서의 제어 능력이 아쉬울 때가 있다. 예를 들어 특정 패키지 설치/업데이트/제거를 차단하고자 할때 (우발적 변경을 방지) 이를 제어할 수 없다.</p>
<p>dpkg 에는 패키지를 hold 하는 기능이 있지만 이것으로는 부족하다. 따라서 dpkg wrapper 스크립트를 만든 후 이를 dpkg-divert 로 dpkg 처럼 작동하게 만들었다.</p>
<p>/usr/sbin/dpkg-wrapper.sh:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-built_in">set</span> -e

<span class="hljs-comment"># Block based on pattern</span>
<span class="hljs-keyword">if</span> [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--remove)</span>"</span> ]] ||                 <span class="hljs-comment"># Removal</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\-r)</span>"</span> ]] ||                      <span class="hljs-comment"># Removal</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--purge)</span>"</span> ]] ||                 <span class="hljs-comment"># Purge</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--install)</span>"</span> ]] ||               <span class="hljs-comment"># Install</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\-i)</span>"</span> ]] ||                      <span class="hljs-comment"># Install</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--reinstall)</span>"</span> ]] ||             <span class="hljs-comment"># Reinstall</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--upgrade)</span>"</span> ]] ||               <span class="hljs-comment"># Upgrade</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\-U)</span>"</span> ]] ||                      <span class="hljs-comment"># Upgrade</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--unpack)</span>"</span> ]] ||                <span class="hljs-comment"># apt install</span>
    [[ ! -z <span class="hljs-string">"<span class="hljs-subst">$(echo <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span> | grep \\--auto-deconfigure)</span>"</span> ]]; <span class="hljs-keyword">then</span>   <span class="hljs-comment"># apt install</span>

    <span class="hljs-comment"># Trigger python script to handle the logic</span>
    python3 /usr/sbin/dpkgCmdParser.py <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span>

    <span class="hljs-comment"># Check exit code</span>
    <span class="hljs-keyword">if</span> [ $? -ne 0 ]; <span class="hljs-keyword">then</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"Operation not permitted by system policy."</span>
        <span class="hljs-built_in">exit</span> 1
    <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">exec</span> /usr/bin/dpkg.distrib <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span>
</code></pre>
<p>위 코드는 dpkg 가 받는 매개변수를 읽어들인 후 패키지 변조를 하는 매개변수가 들어왔을 때 dpkgCmdParser.py 로 모든 매개변수를 넘긴 후, 파서에서 반환 코드 0을 확인한다. 만약 파서가 0을 반환하지 않았을 경우 장치 정책에 따라 변조 불가능한 패키지가 안에 들어있음을 의미하기에 실제 dpkg (dpkg.distrib) 을 실행하지 않는다.</p>
<p>/usr/sbin/dpkgCmdParser.py</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Read registry</span>
<span class="hljs-comment"># Not-removables:    HKEY_LOCAL_MACHINE/SOFTWARE/Policies/ProtectedPackages/&lt;package&gt;.dword.rv = 1</span>
<span class="hljs-comment"># Install blacklist: HKEY_LOCAL_MACHINE/SOFTWARE/Policies/BlacklistedPackages/&lt;package&gt;.dword.rv = 1</span>

import sys
import subprocess
import os

from oscore import libreg as reg


def _chk_registry_protect_mode(package_name: str) -&gt; bool:
    key_path = f<span class="hljs-string">"SOFTWARE/Policies/ProtectedPackages/{package_name}"</span>
    protected = reg.read(key_path, 0)
    <span class="hljs-keyword">if</span> protected == 1:
        <span class="hljs-built_in">return</span> False  <span class="hljs-comment"># Cannot remove protected package</span>
    <span class="hljs-built_in">return</span> True


def _chk_registry_install_mode(package_name: str) -&gt; bool:
    key_path = f<span class="hljs-string">"SOFTWARE/Policies/BlacklistedPackages/{package_name}"</span>
    protected = reg.read(key_path, 0)
    <span class="hljs-keyword">if</span> protected == 1:
        <span class="hljs-built_in">return</span> False  <span class="hljs-comment"># Cannot install blacklisted package</span>
    <span class="hljs-built_in">return</span> _chk_registry_protect_mode(package_name)


def _local_id(raw_args: list[str], install_mode: bool) -&gt; int:
    <span class="hljs-comment"># Parse package names</span>
    <span class="hljs-comment"># This usually looks like this:</span>
    <span class="hljs-comment"># --status-fd 21 --no-triggers --unpack --auto-deconfigure libyyjson0 fastfetch</span>
    <span class="hljs-comment"># Parsing will directly read the package names from the arguments.</span>

    <span class="hljs-keyword">for</span> element <span class="hljs-keyword">in</span> raw_args:

        <span class="hljs-comment"># Package name</span>
        <span class="hljs-comment"># Note: This does not guarantee the element is a valid package name.</span>
        <span class="hljs-keyword">if</span> not element.startswith(<span class="hljs-string">"-"</span>):
            package_name = element

            <span class="hljs-comment"># Note: package name contains : architecture suffix</span>
            <span class="hljs-keyword">if</span> <span class="hljs-string">":"</span> <span class="hljs-keyword">in</span> package_name:
                package_name = package_name.split(<span class="hljs-string">":"</span>, 1)[0]

            <span class="hljs-keyword">if</span> install_mode:
                <span class="hljs-keyword">if</span> not _chk_registry_install_mode(package_name):
                    <span class="hljs-built_in">return</span> 1  <span class="hljs-comment"># Block installation</span>
            <span class="hljs-keyword">else</span>:
                <span class="hljs-keyword">if</span> not _chk_registry_protect_mode(package_name):
                    <span class="hljs-built_in">return</span> 1  <span class="hljs-comment"># Block removal</span>

        <span class="hljs-comment"># Not package name</span>
        <span class="hljs-keyword">else</span>:
            pass

    <span class="hljs-built_in">return</span> 0 <span class="hljs-comment"># Allow operation</span>


def _file_path(raw_args: list[str], install_mode: bool) -&gt; int:
    <span class="hljs-comment"># Parse package names</span>
    <span class="hljs-comment"># This usually looks like this:</span>
    <span class="hljs-comment"># --status-fd 21 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/libyyjson0_0.12.0+ds-1_amd64.deb /var/cache/apt/archives/fastfetch_2.57.1+dfsg-1_amd64.deb</span>
    <span class="hljs-comment"># or if alot, then looks like this:</span>
    <span class="hljs-comment"># --status-fd 21 --no-triggers --unpack --auto-deconfigure --recursive &lt;path&gt;</span>
    <span class="hljs-comment"># Parsing will execute 'dpkg-deb -f &lt;filename.deb&gt; Package' in subprocess to fetch the package name.</span>

    <span class="hljs-keyword">if</span> <span class="hljs-string">"--recursive"</span> <span class="hljs-keyword">in</span> raw_args:
        recurse_index = raw_args.index(<span class="hljs-string">"--recursive"</span>)
        <span class="hljs-keyword">if</span> recurse_index + 1 &lt; len(raw_args):
            dir_path = raw_args[recurse_index + 1]
            <span class="hljs-keyword">if</span> os.path.isdir(dir_path):
                <span class="hljs-keyword">for</span> root, _, files <span class="hljs-keyword">in</span> os.walk(dir_path):
                    <span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> files:
                        <span class="hljs-keyword">if</span> file.endswith(<span class="hljs-string">".deb"</span>):
                            deb_path = os.path.join(root, file)
                            raw_args.append(deb_path)

    <span class="hljs-keyword">for</span> element <span class="hljs-keyword">in</span> raw_args:

        <span class="hljs-comment"># Deb file</span>
        <span class="hljs-keyword">if</span> element.endswith(<span class="hljs-string">".deb"</span>) and os.path.isfile(element):
            try:
                result = subprocess.run(
                    [<span class="hljs-string">"/usr/bin/dpkg-deb"</span>, <span class="hljs-string">"-f"</span>, element, <span class="hljs-string">"Package"</span>],
                    capture_output=True,
                    text=True,
                    check=True
                )
                package_name = result.stdout.strip()
                <span class="hljs-keyword">if</span> package_name:
                    <span class="hljs-keyword">if</span> install_mode:
                        <span class="hljs-keyword">if</span> not _chk_registry_install_mode(package_name):
                            <span class="hljs-built_in">return</span> 1  <span class="hljs-comment"># Block installation</span>
                    <span class="hljs-keyword">else</span>:
                        <span class="hljs-keyword">if</span> not _chk_registry_protect_mode(package_name):
                            <span class="hljs-built_in">return</span> 1  <span class="hljs-comment"># Block removal</span>
            except subprocess.CalledProcessError as e:
                <span class="hljs-built_in">print</span>(f<span class="hljs-string">"Error fetching package name from {element}: {e}"</span>, file=sys.stderr)
                <span class="hljs-built_in">return</span> 1

        <span class="hljs-comment"># Not deb file</span>
        <span class="hljs-keyword">else</span>:
            pass

    <span class="hljs-built_in">return</span> 0 <span class="hljs-comment"># Allow operation</span>


def main() -&gt; int:
    args: list[str] = sys.argv[1:]
    using_local_id = any(flag <span class="hljs-keyword">in</span> args <span class="hljs-keyword">for</span> flag <span class="hljs-keyword">in</span> [
        <span class="hljs-string">"--remove"</span>,
        <span class="hljs-string">"-r"</span>,
        <span class="hljs-string">"--purge"</span>,
        <span class="hljs-string">"--uninstall"</span>,
        <span class="hljs-string">"--configure"</span>,
        <span class="hljs-string">"--upgrade"</span>,
        <span class="hljs-string">"-U"</span>])
    is_removal = any(flag <span class="hljs-keyword">in</span> args <span class="hljs-keyword">for</span> flag <span class="hljs-keyword">in</span> [<span class="hljs-string">"--remove"</span>, <span class="hljs-string">"--purge"</span>, <span class="hljs-string">"-r"</span>, <span class="hljs-string">"--uninstall"</span>])


    <span class="hljs-built_in">return</span> _local_id(args, not is_removal) <span class="hljs-keyword">if</span> using_local_id <span class="hljs-keyword">else</span> _file_path(args, not is_removal)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    import sys
    sys.exit(main())
</code></pre>
<p>위 코드는 필자의 AquariusOS 의 레지스트리 시스템에서 소프트웨어 정책을 읽어와 체크하는 파이썬 코드이다. 해당 코드는 dpkg 가 변조하려는 큐에서 Blacklist (삭제만 가능) 혹은 Protected (변조 불가능) 상태를 확인한 후 통과 여부를 반환하는 코드이다.</p>
<p>위 두 코드를 모두 <code>/usr/sbin/</code>에 넣고, 다음 명령어를 <code>sudo</code> 권한으로 실행하여 적용한다:</p>
<pre><code class="lang-bash">dpkg-divert --divert /usr/bin/dpkg.distrib --rename /usr/bin/dpkg
ln -sf /usr/sbin/dpkg-wrapper.sh /usr/bin/dpkg
chmod +x /usr/sbin/dpkg-wrapper.sh
chmod +x /usr/bin/dpkg
</code></pre>
<p><code>fastfetch</code> 명령어를 Protected 에 등록하고 삭제하려 시도했을 때:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770402111009/b0f8f777-a561-4a11-8aad-2f70420e101e.png" alt class="image--center mx-auto" /></p>
<p>이후 등록을 해제하고 다시 삭제하려 하였을 때:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770402153112/3002f77c-4996-4b26-b69b-c3c9f992dd89.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item></channel></rss>