CoolSites
Version
License
Sites
Categories
Status
GitHub Pages
<p align="center">
<a href="https://ko-fi.com/X8K126YVER">
<img height="42" src="https://storage.ko-fi.com/cdn/kofi2.png?v=3" alt="Ko-fi에서 커피 사주기" />
</a>
</p>
<p align="center">
<sub><em>CoolSites가 유용한 것을 찾는 데 도움이 되었다면, 커피 한 잔이 제가 이 디렉토리를 큐레이션하고 최신 상태로 유지하는 데 도움이 됩니다.</em></sub>
</p>
시스템 관리자(sysadmins), 개발자(devs), 크리에이터, 홈랩 사용자(homelabbers) 및 끝없이 호기심이 많은 사람들을 위해 구축된 30개 카테고리 전반에 걸친 586개의 무료 도구, 오픈 소스 프로젝트 및 숨겨진 보물들의 큐레이션된 디렉토리입니다.
디렉토리 둘러보기
카테고리를 선택하고, 검색하며, 저장하고 싶은 항목에 별표를 표시하면 브라우저가 사용자의 그룹을 기억합니다. 어떠한 정보도 전송되지 않습니다. 계정이나 백엔드(backend)는 없으며 어떤 종류의 추적(tracking)도 없습니다.
기능 (Features)
-
30개 카테고리에 걸친 586개의 큐레이션된 사이트: 모든 항목은 직접 선정되고 설명이 추가되었습니다.
-
브라우징 작업 공간 (A browse workspace): 북마크는 카테고리 및 메타데이터 필터(metadata filters) 옆의 고정된 영역에 유지되며, 화면 나머지 부분은 검색과 결과 표시를 위해 비워둡니다.
-
집중적인 컬렉션 (Focused collections): 레일에서 시작점을 선택하고 모든 멤버를 스캔한 다음, 페이지를 벗어나지 않고 다른 컬렉션으로 이동할 수 있습니다.
-
범위를 좁히는 검색 (Search that narrows): 입력하는 모든 단어가 일치해야 합니다. 이름은 오타(typos)를 허용하지만, 더 긴 필드는 실제 일치가 필요하며, 검색 결과는 하이라이트됩니다.
-
쌓이는 필터 (Filters that stack): 카테고리, 태그, 오픈 소스 전용, 계정 불필요, 스태프 추천 및 도구가 대체하는 제품 등 모든 것이 결합되며, 각 항목마다 실시간 개수가 표시됩니다. 모든 개수는 현재 목록을 기준으로 측정되므로, 12라고 표시된 칩은 정확히 12개를 의미하며, 아무것도 남기지 않을 용어는 제시되지 않습니다.
-
정렬 (Sorting): A부터 Z까지, Z부터 A까지, 최근 추가됨, 무작위(shuffle) 또는 북마크가 상단에 고정된 기본 순서로 정렬할 수 있습니다.
-
10가지 테마: 다크 모드 8개(OLED, Catppuccin, Dracula, Nord, Rosé Pine, GitHub Dark, Midnight, Solarized), 라이트 모드 1개, 그리고 OS를 따르는 시스템 옵션이 제공됩니다.
-
그룹별 북마크 기능(Bookmarks with groups). 사이트를 즐겨찾기(Star)하고 색상 코드가 지정된 그룹에 분류한 후 드래그하여 순서를 재배열할 수 있습니다. 북마크를 제거하거나 그룹을 삭제해도 되돌릴 수 있습니다.
-
내보내기 및 가져오기(Export and import). 북마크는 버전이 지정된 JSON 파일로 다운로드되며 모든 장치에서 복원할 수 있습니다. 가져오기는 항목별로 검증되며 건너뛴 항목을 보고합니다.
-
그리드 및 목록 보기(Grid and list views). 검색어, 카테고리, 정렬, 태그, 필터와 함께 URL에 기억되므로, 특정 보기를 링크로 공유할 수 있습니다.
-
키보드 우선 사용(Keyboard first). /는 검색을 포커스하고, 화살표 키는 카드 사이를 이동하며, Enter는 열고, ?는 모든 단축키 목록을 보여줍니다.
-
마크다운으로 복사(Copy as Markdown). 현재 필터링된 목록을 메모나 이슈에 붙여넣을 수 있습니다.
-
화면에 보이는 내용 중 무작위 선택(Random pick) 기능과 모든 카드에 Wayback Machine 링크가 제공됩니다.
-
최대 5개 사이트를 나란히 비교할 수 있습니다.
-
피드(Feeds). 빌드 시점에 생성되는 최신 항목의 Atom 및 JSON 피드가 제공됩니다.
-
첫 방문 후 오프라인에서도 작동하며, 캐시된 데이터를 보여줄 때는 그렇게 표시합니다.
-
타사 요청 없음(No third-party requests). 폰트와 파비콘은 사이트에 포함되어 전송됩니다. CDN에서 로드되는 것이 없고, 외부로 데이터를 호출하지 않습니다.
-
런타임 의존성 없음(No runtime dependencies). 정적 HTML, CSS, JavaScript 및 JSON입니다. 프레임워크나 빌드 단계가 필요하지 않아 볼 수 있습니다.
-
접근성(Accessible). 모든 테마에서 본문 텍스트는 가시적 포커스, 라이브 영역(live regions), 건너뛰기 링크(skip link), 그리고 움직임 감소 및 강제 색상 지원을 통해 WCAG AA를 충족합니다.
-
GitHub Pages, 모든 정적 웹 서버 또는 이 리포지토리의 로컬 서버에 자체 호스팅(Self-hostable)할 수 있습니다.
카테고리 (Categories)
| 카테고리 | 사이트 | 하이라이트 |
|---|
| 데스크톱 소프트웨어 (Desktop Software) | 50 | DevToys, PowerToys, Files, Everything, ShareX, VLC, Ventoy, HWiNFO |
| ... | | |
빠른 시작 (Quick Start)
CoolSites는 런타임 의존성이 없는 정적 사이트입니다. 보조 JSON 파일에서 데이터를 로드하므로 디스크에서 열기보다는 HTTP를 통해 제공되어야 합니다.
GitHub Pages (권장)
- 이 저장소를 포크(Fork)하세요.
- 설정 (Settings) > **페이지 (Pages)**로 이동합니다.
- 소스를
main 브랜치, 루트 폴더로 설정합니다.
- 사용자의 디렉토리는
https://yourusername.github.io/CoolSites/에서 활성화됩니다.
로컬 환경 (Local)
git clone https://github.com/SysAdminDoc/CoolSites.git
cd CoolSites
npm run serve
출력된 http://127.0.0.1:4173/ 주소를 엽니다. 브라우저는 file:// 페치를 차단하기 때문에, 디스크에서 index.html을 직접 여는 것은 작동하지 않습니다. 페이지 자체에서도 이를 명시하며 서버를 실행하라고 안내합니다.
다른 웹 서버 사용 시 (Any other web server)
저장소 내용을 문서 루트(document root)에 복사하세요. nginx, Apache, Caddy 또는 python3 -m http.server와 함께 작동합니다. 클라이언트 측 라우팅(client-side routing)이 없으므로, 모든 오타를 홈페이지처럼 보이게 하지만 실제로는 아닌 페이지로 변환하는 캐치올 리라이트(catch-all rewrite) 규칙을 index.html에 추가하지 마십시오.
가능하면 문서 루트에서 제외할 만한 두 가지 항목이 있습니다: .git 디렉토리와 점(.)으로 시작하는 모든 파일입니다. 번들된 npm run serve는 이미 이러한 항목들을 거부합니다.
콘텐츠 보안 정책(Content Security Policy)은 <meta> 태그를 통해 전달되므로, GitHub Pages를 포함하여 페이지를 호스팅하는 모든 곳에 적용됩니다.
메타 태그가 할 수 없는 세 가지 일이 있으므로 실제 서버가 필요합니다. GitHub Pages는 이를 전송하지 않는데, 이는 가정하기보다는 알아두는 것이 좋습니다: 호스팅된 복사본은 누구에게나 프레임(frame)될 수 있으며 브라우저는 콘텐츠 유형을 스니핑(sniff)할 자유를 가집니다.
Content-Security-Policy: <the policy from index.html>; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
...
정책(policy)은 상수(constant)가 아니므로 여기에 작성되어 있지 않습니다. script-src와 style-src는 각각 모든 인라인 블록에 대한 SHA-256 값을 나열하며, npm run generate는 마지막 단계에서 이를 재계산합니다. 따라서 이 문자열을 어디에 복사하면 스크립트나 스타일시트가 변경될 때마다 오래된(stale) 정보를 복사하게 됩니다. 대신 npm run serve가 하는 것처럼 index.html의 <meta> 태그에서 읽어와야 합니다. 추가할 가치가 있는 유일한 것은 frame-ancestors인데, 브라우저는 이 값을 메타 태그에서는 무시하기 때문에 GitHub Pages에 호스팅된 복사본에는 실제로 이 값이 없습니다.
npm run serve는 네 가지 모두를 전송하므로, 로컬 개발 환경이 적절하게 구성된 호스트와 일치합니다.
nginx의 경우, add_header가 자체 헤더를 설정하는 location 블록으로 상속되지 않는다는 점을 기억해야 합니다. 따라서 이 값들은 서버 레벨에서 한 번 선언하기보다는 각 location 블록에 반복되어야 합니다.
작동 방식 (How It Works)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ sites.json │────>│ Filter Engine │────>│ Card Renderer │
│ │ │ │ │ │
...
데이터와 생성된 피드는 index.html 옆에 위치합니다:
| 파일 (File) | 내용물 (What it holds) |
|---|
sites.json | 모든 항목: 이름, URL, 설명, 카테고리, 태그, 오픈 소스 및 계정 플래그 |
| ... | |
추적(tracking), 쿠키(cookies), 분석(analytics) 기능은 없으며 제3자 요청도 없습니다. 북마크는 localStorage에 저장되며 브라우저를 벗어나지 않습니다.
명령어 (Commands)
npm run serve
npm run lint
npm run generate
...
npm run lint은 리포지토리 내에서 카운트가 표류(drift)하는 것을 방지합니다. 아무것도 이 범위를 벗어나 커버하지 않았기 때문에 GitHub의 설명에는 '588개의 무료 도구'라고 표시되었지만, sites.json 파일에는 586개만 담겨 있었습니다. 그리고 대부분의 사람들이 이 프로젝트에 대해 읽는 첫 번째 장소는 바로 그 보장이 미치지 못하는 곳이었습니다. npm run sync:repo는 README 배지에 사용되는 것과 동일한 소스에서 이를 수정하며, npm run sync:repo -- --check를 실행하면 아무것도 변경하지 않으면서도 차이를 보고합니다. 이 명령어는 gh CLI가 필요합니다. 이는 의도적으로 npm run build에 포함되지 않았는데, 왜냐하면 github.com의 무언가를 수정하는 빌드는 예상치 못한 빌드일 것이기 때문입니다.
잠글 파일(lockfile)이 없는 이유는 잠글 것이 없기 때문입니다. 런타임 종속성도 없고, 개발 종속성도 없고, 프레임워크도 없고, 페이지를 보기 위한 빌드 단계도 없습니다. 이것은 간과한 부분이 아니라 의도적인 선택이며, npm run lint이 이를 강제합니다: 만약 package-lock.json 없이 package.json에 종속성이 나타나는 날에는 빌드가 실패합니다.
검색 용이성 (Discoverability)
npm run build는 sitemap.xml을 작성합니다. 여기에 기록된 lastmod 날짜는 빌드가 실행된 시점이 아니라 디렉토리 내 가장 최신 항목의 날짜입니다. 왜냐하면 피드(feed)를 재생성할 때마다 타임스탬프가 바뀌는 것은 크롤러에게 아무것도 알려주지 못하고, 그 필드를 믿지 않도록 가르치기 때문입니다. robots.txt는 사이트맵을 가리키며, 로컬 링크 검사 보고서가 위치하는 곳인 work/만 접근을 금지합니다.
social-card.png는 해당 사이트를 Slack, Discord 또는 Mastodon에 붙여넣었을 때 나타나는 이미지입니다. 이것이 없다면 모든 공유는 밋밋한 파란색 링크일 뿐이며, 공유를 통해 디렉토리가 발견됩니다. node scripts/generate-social-card.js 스크립트는 리포지토리 자체의 글꼴(font)을 인라인으로 포함한 HTML 카드에서 헤드리스 Chrome(headless Chrome)을 사용하여 이를 렌더링하므로 네트워크나 이미지 라이브러리가 필요 없습니다. 이 카드는 빌드를 통해 생성되는 것이 아니라 수동으로 생성되며, 의도적으로 사이트 카운트를 담고 있지 않습니다. 여기에 카운트를 넣는다는 것은 항목이 추가될 때마다 114KB의 바이너리를 재작성해야 함을 의미하며, git은 그 모든 버전을 영원히 보존할 것입니다.
디렉토리를 다른 곳으로 가져가기 (Taking the directory elsewhere)
npm run build는 전체 디렉토리를 카테고리별로 그룹화하여 feeds/directory.opml을 작성합니다. OPML은 웹링(webring)과 블로그롤(blogroll) 세계에서 읽는 형식이며, 이 목록을 단순히 여기서 보는 것 외에 다른 곳으로 가져갈 수 있게 해주는 유일한 형식입니다. Kagi의 small-web 목록과 XXIIVV webring 모두 이를 발행합니다.
어떤 개요(outline)도 xmlUrl을 가지고 있지 않습니다. OPML의 일반적인 역할은 피드 구독(feed subscriptions)을 담는 것이므로, 이를 보는 리더기는 해당 피드를 폴링(poll)하려고 시도할 것이고, 이 중 대부분은 아예 피드가 없는 사이트입니다. 존재하지 않는 곳에 피드가 있다고 주장하는 것은 당신을 신뢰하는 리더기를 망가뜨립니다.
파비콘(Favicon) 용량
favicons.json은 방문할 때마다 전체가 전송되므로, 리더기가 기다리는 가장 큰 단일 항목입니다. 생성기는 Google에 32px 아이콘을 요청하지만, Google은 긴 꼬리(long tail)의 도메인에 대한 아이콘이 없으며, 폴백(fallbacks)들은 해당 사이트가 가지고 있는 무엇이든 반환합니다: 16px부터 256px까지 모든 크기를 담고 있는 다중 해상도 .ico 파일, 또는 180px의 apple-touch-icon입니다. 이 중 네 개는 단독으로 파일의 21%를 차지했으며, 가장 큰 것은 58KB였습니다.
npm run update:favicons은 이제 테스트를 이미 실행하는 headless Chrome에서 2KB가 넘는 모든 것을 디코드하고, 이를 32px로 다시 그린 다음, WebP 또는 PNG 중 더 작은 쪽을 유지합니다. 실제로 크기가 줄어든 결과만 유지하기 때문에, 이미 적절한 크기였던 아이콘은 그대로 두고, 벡터를 래스터화(rasterising)하는 것은 보존할 가치가 있는 것을 포기하게 만들기 때문에 SVG는 완전히 건너뜁니다. 이를 통해 캐시 용량이 525개 도메인에 걸쳐 827KB에서 593KB로 줄었습니다. Chrome을 사용할 수 없고 단지 가져오기(fetch)만 필요할 때는 --no-reencode를 전달하여 건너뛸 수 있습니다.
두 가지 제한(ceiling)이 이것이 다시 늘어나는 것을 막습니다: 단일 래스터 아이콘에 대해 8KB, 그리고 파일 전체에 대해 700KB입니다. 둘 다 경고하는 대신 실패합니다(fail the lint).
favicons.json의 항목은 세 가지 중 하나입니다. 데이터 URI(data URI)는 아이콘이 발견되었음을 의미합니다. false는 네 개의 모든 소스를 시도했지만 찾을 것이 없다는 것을 의미합니다: putty.org는 <link> 태그를 전혀 제공하지 않으며, retroarch.com은 일곱 개를 가지고 있지만 그중 어느 것도 아이콘이 아닙니다. 'Absent'는 아직 아무도 확인하지 않았음을 의미합니다. 이 중간 상태가 없다면 모든 새로고침 시도는 동일한 열세 개의 도메인을 재시도하고, 각 요청마다 이전 실행이 이미 알고 있던 것을 배우는 데 네 번의 리소스를 소비했을 것입니다. --retry-missing은 그들 중 하나가 아이콘을 게시하기 시작할 때를 대비하여 다시 요청합니다.
npm run lint가 중요한 관문입니다. 이 명령어는 모든 데이터 파일을 스키마(schema)와 비교하여 검증하고, 중복되거나 충돌하는 URL을 거부하며, 생성된 피드가 데이터와 일치하는지 확인합니다. 또한 index.html, manifest.json, sw.js 또는 이 README에 있는 버전이나 카운트 문자열이 package.json 및 sites.json에서 벗어났을 때 실패합니다.
캐시된 데이터 (Cached data)
stars.json과 favicons.json은 다른 누군가의 데이터 사본이며, 날짜가 없는 사본은 새로 생성된 것과 구별할 수 없습니다. cache-manifest.json은 각각 다음을 기록합니다: 해당 파일을 작성한 명령어, 읽은 소스, 실행이 완료된 시점, 포함된 레코드 개수, 실패한 페치(fetch) 횟수, 그리고 파일의 SHA-256 해시값입니다. 여기서 중요한 부분은 해시값입니다. 날짜만 가지고 있는 매니페스트는 누군가 파일을 수동으로 편집해도 여전히 "오늘 가져옴"이라고 표시될 것이므로, npm run lint는 해시값과 레코드 개수를 비교하고 새로고침 없이 둘 중 하나라도 변경되면 실패합니다.