From 43a7ed5b97f7a3e425cc5dd6646cb77e6c08b42a Mon Sep 17 00:00:00 2001 From: selfhst-bot Date: Sat, 9 Aug 2025 14:44:04 -0400 Subject: [PATCH] Initiate automated workflow --- .github/workflows/build-and-push.yml | 98 ++++++++++++++++++++++++++ .gitignore | 3 +- README.md | 101 ++------------------------- build/dockerfile | 11 --- build/package.json | 13 ---- build/server.js | 89 ----------------------- 6 files changed, 106 insertions(+), 209 deletions(-) create mode 100644 .github/workflows/build-and-push.yml delete mode 100755 build/dockerfile delete mode 100755 build/package.json delete mode 100755 build/server.js diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml new file mode 100644 index 00000000..e2f14b99 --- /dev/null +++ b/.github/workflows/build-and-push.yml @@ -0,0 +1,98 @@ +name: Build and Push Docker Images + +on: + push: + branches: [ main ] + paths: + - 'build/**' + - '.github/workflows/build-and-push.yml' + + release: + types: [ published ] + + workflow_dispatch: + inputs: + version: + description: 'Version tag (e.g., 2.0.0)' + required: true + default: 'latest' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: selfhst/icons + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Read version from file + id: version + run: | + VERSION=$(cat build/VERSION | tr -d '\n') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version from file: $VERSION" + + # Extract semantic version parts + MAJOR=$(echo $VERSION | cut -d. -f1) + MINOR=$(echo $VERSION | cut -d. -f1-2) + + echo "major_version=$MAJOR" >> $GITHUB_OUTPUT + echo "minor_version=$MINOR" >> $GITHUB_OUTPUT + echo "Major: $MAJOR, Minor: $MINOR" + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + # For releases, use the release tag + type=ref,event=tag + # For main branch pushes, create semantic version tags + type=raw,value=${{ steps.version.outputs.version }},enable={{is_default_branch}} + type=raw,value=${{ steps.version.outputs.major_version }},enable={{is_default_branch}} + type=raw,value=${{ steps.version.outputs.minor_version }},enable={{is_default_branch}} + type=raw,value=latest,enable={{is_default_branch}} + labels: | + org.opencontainers.image.title=selfh.st/icons + org.opencontainers.image.description=Self-hosted icon server with custom color support + org.opencontainers.image.version=${{ steps.version.outputs.version }} + org.opencontainers.image.source=https://github.com/${{ github.repository }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./build + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Output image info + run: | + echo "🐳 Images built and pushed:" + echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' + echo "" + echo "🏗️ Supported architectures:" + echo " - linux/amd64 (Intel/AMD x86_64)" + echo " - linux/arm64 (ARM 64-bit, Apple M1, Raspberry Pi 4)" + echo " - linux/arm/v7 (ARM 32-bit, Raspberry Pi 2/3)" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2dc68ed6..515e8d71 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .* staging/ -!/.gitignore \ No newline at end of file +!/.gitignore +!/.github \ No newline at end of file diff --git a/README.md b/README.md index 06a59b06..fff01cb2 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,18 @@
selfh.st/icons Logo
-
-

- -Stripe - -Ko-fi - -Patreon - -Buy Me a Coffee -

-
-
- selfh.st/icons Screenshot -
-
-[selfh.st/icons](https://selfh.st/icons) is a collection of 4,500+ logos and icons for self-hosted (and non-self-hosted) software. +[selfh.st/icons](https://selfh.st/icons) is a collection of logos and icons for self-hosted (and non-self-hosted) software. The collection is available for browsing via the directory at [selfh.st/icons](https://selfh.st/icons) and served to users directly from this repo using the jsDelivr content delivery network. -To self-host the collection, users can clone, download, or sync the repository with a tool such as [git-sync](https://github.com/AkashRajpurohit/git-sync) and serve it via a web server of their choosing (Caddy, NGINX, etc.). +To self-host the collection, users should refer to the [repository's wiki](https://github.com/selfhst/icons/wiki). -## Color Options +#### Custom Colors -By default, most SVG icons are available in three color formats: +By default, the directory provides SVG icons (when available) in standard, dark, and light colors. Users looking to display the icons using colors of their choice can become a [paid member of selfh.st](https://selfh.st/perks) or [deploy the collection themselves](https://github.com/selfhst/icons/wiki). -* **Standard**: The standard colors of an icon without any modifications. -* **Dark**: A modified version of an icon displayed entirely in black (```#000000```). -* **Light**: A modified version of an icon displayed entirely in white (```#FFFFFF```). +#### Requests -(Toggles to view icons by color type are available in the [directory hosted on the selfh.st website](https://selfh.st/icons).) - -## Custom Colors - -Because the dark and light versions of each icon are monochromatic, CSS can theoretically be leveraged to apply custom colors to the icons. - -This only works, however, when the SVG code is embedded directly onto a webpage. Unfortunately, most [integrations](https://selfh.st/apps/?tag=selfh-st-icons) link to the icons via an `` tag, which prevents styling from being overridden via CSS. - -As a workaround, a lightweight self-hosted server has been published via Docker that utilizes a URL parameter for color conversion on the fly. Continue reading for further instructions. - - -### Deploying the Custom Color Container - -* [Introduction](https://github.com/selfhst/icons#introduction) -* [Deploying the container](https://github.com/selfhst/icons#deployment) -* [Configuring a reverse proxy (optional)](https://github.com/selfhst/icons#reverse-proxy) -* [Linking to a custom icon](https://github.com/selfhst/icons#linking) -* [Changelog](https://github.com/selfhst/icons#changelog) - -#### Introduction - -The Docker image below allows users to host a local server that acts as a proxy between requests and jsDelivr. When a color parameter is detected in the URL, the server will intercept the requests, fill the SVG file with that color, and serve it to the user. - -Once deployed, users can append ```?color=eeeeee``` to the end of a URL to specify a custom color (replacing ```eeeeee``` with any [hex color code](https://htmlcolorcodes.com/)). - -#### Deployment - -The container can be easily deployed via docker-compose with the following snippet: - -``` -selfhst-icons: - image: ghcr.io/selfhst/icons:latest - restart: unless-stopped - ports: - - 4050:4050 -``` - -No volume mounts or environment variables are currently required. - -#### Reverse Proxy - -While out of the scope of this guide, many applications will require users to leverage HTTPS when linking to icons served from the container. - -The process to proxy the container and icons is straightforward. A sample Caddyfile configuration has been provided for reference: - -``` -icons.selfh.st { - reverse_proxy selfhst-icons:4050 -} -``` - -#### Linking - -After the container has been deployed, users can easily link to any existing icon within the collection: - -* ```https://icons.selfh.st/bookstack.svg``` -* ```https://icons.selfh.st/bookstack.png``` -* ```https://icons.selfh.st/bookstack-dark.webp``` - -To customize the color, users **must** link to the *standard* version of an SVG icon that has available monochromatic (dark/light) versions. To do so, append a custom URL parameter referencing any [hex color code](https://htmlcolorcodes.com/): - -* ```https://icons.selfh.st/bookstack.svg?color=eeeeee``` -* ```https://icons.selfh.st/bookstack.svg?color=439b68``` - -**Note the following:** - -* Only the standard icons accept URL parameters (for example, ```bookstack-light.svg?color=fff000``` will not yield a different color. -* Only append the alpha-numeric portion of the hex color code to the URL. The server will append the ```#``` in the backend before passing it on for styling. - -##### Changelog - -* 2025-04-30: Initial release \ No newline at end of file +Requests for new or updated icons can be made via the repository's [discussions](https://github.com/selfhst/icons/discussions). To ensure quality and consistency, pull requests will not be accepted from unapproved members. \ No newline at end of file diff --git a/build/dockerfile b/build/dockerfile deleted file mode 100755 index 0e94eff5..00000000 --- a/build/dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM node:18-alpine -WORKDIR /app - -COPY package.json ./ -RUN npm install - -COPY server.js . - -EXPOSE 4050 - -CMD ["node", "server.js"] \ No newline at end of file diff --git a/build/package.json b/build/package.json deleted file mode 100755 index 6ddc0425..00000000 --- a/build/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "test-repo", - "version": "1.0.0", - "main": "server.js", - "scripts": { - "start": "node server.js" - }, - "type": "module", - "dependencies": { - "express": "^4.18.2", - "node-fetch": "^3.3.0" - } -} \ No newline at end of file diff --git a/build/server.js b/build/server.js deleted file mode 100755 index 5c812048..00000000 --- a/build/server.js +++ /dev/null @@ -1,89 +0,0 @@ -import express from 'express' -import fetch from 'node-fetch' -import path from 'path' - -const app = express() -const PORT = 4050 -const CDN_ROOT = 'https://cdn.jsdelivr.net/gh/selfhst/icons' -const CDN_PATH = 'svg' - -async function fileExists(url) { - try { - const resp = await fetch(url, { method: 'HEAD' }); - return resp.ok; - } catch { - return false; - } -} - -async function fetchAndPipe(url, res) { - const response = await fetch(url); - if (!response.ok) return res.status(404).send('File not found'); - res.type(path.extname(url).slice(1)); - response.body.pipe(res); -} - -app.get('/*', async (req, res) => { - const urlPath = req.path; - const extMatch = urlPath.match(/\.(\w+)$/); - if (!extMatch) - return res.status(404).send('File extension missing'); - - const ext = extMatch[1].toLowerCase(); - if (!['png', 'webp', 'svg'].includes(ext)) - return res.status(404).send('Format not supported'); - - const filename = urlPath.slice(1); - const lowerFilename = filename.toLowerCase(); - - const isSuffix = lowerFilename.endsWith('-light.svg') || lowerFilename.endsWith('-dark.svg'); - - if (isSuffix) { - return fetchAndPipe(`${CDN_ROOT}/${CDN_PATH}/${filename}`, res); - } - - let mainUrl; - if (ext === 'png') { - mainUrl = `${CDN_ROOT}/png/${filename}`; - } else if (ext === 'webp') { - mainUrl = `${CDN_ROOT}/webp/${filename}`; - } else if (ext === 'svg') { - mainUrl = `${CDN_ROOT}/svg/${filename}`; - } else { - mainUrl = null; - } - - const hasColor = !!req.query['color'] && req.query['color'].trim() !== ''; - - if (ext === 'svg') { - if (hasColor) { - const baseName = filename.replace(/\.(png|webp|svg)$/, ''); - const suffixUrl = `${CDN_ROOT}/${CDN_PATH}/${baseName}-light.svg`; - if (await fileExists(suffixUrl)) { - let svgContent = await fetch(suffixUrl).then(r => r.text()); - const color = req.query['color'].startsWith('#') ? req.query['color'] : `#${req.query['color']}`; - svgContent = svgContent - .replace(/style="[^"]*fill:\s*#fff[^"]*"/gi, (match) => { - console.log('Replacing style fill:', match); - return match.replace(/fill:\s*#fff/gi, `fill:${color}`); - }) - .replace(/fill="#fff"/gi, `fill="${color}"`); - return res.type('image/svg+xml').send(svgContent); - } else { - return fetchAndPipe(mainUrl, res); - } - } else { - return fetchAndPipe(mainUrl, res); - } - } else { - // PNG/WebP: serve directly - return fetchAndPipe(mainUrl, res); - } -}); - -app.get('/', (req, res) => { - res.send('Self-hosted icon server'); -}); -app.listen(PORT, () => { - console.log(`Listening on port ${PORT}`); -}); \ No newline at end of file