mirror of
https://github.com/selfhst/icons.git
synced 2026-04-30 13:26:18 -04:00
Initiate automated workflow
This commit is contained in:
98
.github/workflows/build-and-push.yml
vendored
Normal file
98
.github/workflows/build-and-push.yml
vendored
Normal file
@@ -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)"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.*
|
.*
|
||||||
staging/
|
staging/
|
||||||
!/.gitignore
|
!/.gitignore
|
||||||
|
!/.github
|
||||||
101
README.md
101
README.md
@@ -1,107 +1,18 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img width="400" src="https://cdn.jsdelivr.net/gh/selfhst/cdn/assets/site/logos/selfh-st-icons.svg" alt="selfh.st/icons Logo">
|
<img width="400" src="https://cdn.jsdelivr.net/gh/selfhst/cdn/assets/site/logos/selfh-st-icons.svg" alt="selfh.st/icons Logo">
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://selfh.st/support/#/portal/support">
|
|
||||||
<img src="https://img.shields.io/badge/Stripe-5469d4?style=for-the-badge&logo=stripe&logoColor=ffffff" alt="Stripe"/></a>
|
|
||||||
<a href="https://ko-fi.com/selfhst">
|
|
||||||
<img src="https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white" alt="Ko-fi"/></a>
|
|
||||||
<a href="https://patreon.com/selfhst">
|
|
||||||
<img src="https://img.shields.io/badge/Patreon-000000?style=for-the-badge&logo=patreon&logoColor=white" alt="Patreon"/></a>
|
|
||||||
<a href="https://buymeacoffee.com/selfhst">
|
|
||||||
<img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me a Coffee"/></a>
|
|
||||||
</p>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img width="800" style="border-radius: 8px;" src="https://cdn.jsdelivr.net/gh/selfhst/cdn/assets/site/screenshots/selfh-st-icons.png" alt="selfh.st/icons Screenshot">
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
[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.
|
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.
|
#### Requests
|
||||||
* **Dark**: A modified version of an icon displayed entirely in black (```#000000```).
|
|
||||||
* **Light**: A modified version of an icon displayed entirely in white (```#FFFFFF```).
|
|
||||||
|
|
||||||
(Toggles to view icons by color type are available in the [directory hosted on the selfh.st website](https://selfh.st/icons).)
|
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.
|
||||||
|
|
||||||
## 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 `<img>` 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
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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}`);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user