portkey logo

portkey

⚡🚀🔗
Repository of the portkey application
A simple web portal that can act as startup page and shows a collection of links/urls. It also supports adding small custom pages.
Sonar quality gate GitHub stars Go version Docker image size

Website (GitHub) | Contributing

As magical as in Harry Potter. Built with ☕️ by CodeHat and contributors
## Table of Contents - [Features](#features) - [Screenshots](#screenshots) - [Installation](#installation) - [Usage](#usage) - [Configuration](#configuration) - [Metrics](#metrics) - [Docker](#docker) - [Development](#development) - [See Also](#see-also) - [License](#license) ## Features - 🔗 Shows a collection of links acting as startup page or similar. - 🔎 Includes a search box with configurable keyword support and fuzzy matching. - 📂 Portals can be organised into named groups/sections on the home page. - 📐 Optional multi-column grid layout for portals and groups (configurable column count, mobile-responsive). - 🌎 Automatically fetches and caches favicons for external portals (direct or proxied). - 🎨 Portal icons support emojis, image files, or URLs. - 📄 Can be configured easily by modifying only one file. - 🗒️ Also supports adding smaller custom pages. - 🌓 Dark and light mode available. - 🪰 Very lightweight application with Docker images available. ## Screenshots

You can also find a demo here

Click to view screenshots #### Link collection (no groups) ![LinkCollectionNoGroups](docs/images/screenshot_no_groups_full.png "Link collection (no groups)") #### Link collection (with groups) ![LinkCollectionWithGroups](docs/images/screenshot_with_groups_full.png "Link collection (with groups)") #### Search for a link ![SearchForLink](docs/images/screenshot_search.png "Search for a link") #### Custom page ![CustomPage](docs/images/screenshot_custom_page.png "Custom page")
## Installation Download the `portkey` file for your OS. Probably to a location that is in your `PATH`, so you can use it right away. ## Usage 1. Create a `config.yml` or use the [example configuration](https://github.com/kodehat/portkey/blob/main/config.yml) from this repository and configure it as you want. > You can find a detailed explanation of all configuration options [here](#configuration). 2. Start the application with `portkey --config-path=`. Providing the path to the configuration file is optional if it's in the working directory. 3. Open your browser at the defined host and port. Default is ## Configuration `portkey` is configured with a single configuration file called `config.yml`. You can pass its location using the `--config-path` argument. You can also overwrite configuration values from file by using environment variables in uppercase and prefixed with `PORTKEY_`. Nested keys use underscores: the configuration key `server.host` can be passed as environment variable `PORTKEY_SERVER_HOST`, `favicon.mode` as `PORTKEY_FAVICON_MODE`. The `config.yml` contains the following configuration options: ### Server ```yaml server: # Can be changed to reduce or increase logs. Values could be "ERROR", "WARN", "INFO" or "DEBUG". Default: "INFO". logLevel: INFO # If enabled logs are in JSON format. Default: false. logJson: false # Set the host where the application should bind to. Default: "localhost". host: localhost # Set the port where the application should bind to. Default: "3000". port: 3000 # Set the context path (aka base-url) portkey is hosted under. Must not be specified unless you're using a reverse proxy and are hosting portkey under a directory. If that's the case then you can set this value to e.g. /portkey or whatever the directory is called. Note that the forward slash (/) in the beginning is required! Default: "" (no context path). contextPath: "" # Enables development-mode additions (browser-reload WebSocket endpoint at /reload and dev-mode markup). Default: false. devMode: false ``` ### Metrics ```yaml metrics: # Enables the HTTP server that serves metrics that can be scraped by e.g. Prometheus. Default: false. enabled: false # Set the host where the metrics server should bind to. Default: "localhost". host: localhost # Set the port where the metrics server should bind to. Default: "3030". port: 3030 ``` ### UI ```yaml ui: # Title of the application shown in the browser tab and in the top bar. # If empty, the top-bar title is hidden and the browser tab only shows the page name. Default: "portkey". title: "portkey" # Whether the search bar is shown. Can be useful with a low amount of portals making the search unnecessary. Default: true. showSearchBar: true # Defines whether portkey's application icon should be shown at the top left of the front page. Default: true. showTopIcon: true # If true keywords of portals are shown as tooltip on hover. Default: false. showKeywordsAsTooltips: false # If true all links are sorted alphabetically when shown on the front page. Otherwise they are shown in the order they are defined. Default: false. sortAlphabetically: false # Number of columns for the grid layout (0 = disabled/vertical). # On mobile (<768px) the layout always falls back to vertical stacking. # When groups exist, each group occupies one grid cell. # When no groups, portals are distributed across N columns. Default: 0 (vertical). layoutColumns: 0 # Allows adding additional scripts/stylesheets etc. to the HTML header. Can be useful for analytics or smaller style modifications. Default: "" (nothing added). headerAddition: |- # Footer (HTML support) that is shown on every page. # Remember that Tailwind CSS classes used here do only work if already used somewhere else in the application because the bundler couldn't look here! Default: "Works like a portal.". footer: |-

This is a footer!

``` ### Search ```yaml search: # If true search query is also compared to portals and keywords using Levenshtein string metric. Default: false. stringSimilarity: false # Minimum required similarity for results when 'stringSimilarity' is 'true'. Must be between '0.0' (0%) and '1.0' (100%). Default: 0.75. minimumSimilarity: 0.5 ``` ### Favicons ```yaml favicon: # How favicons are loaded. "direct": portkey discovers icons itself via the # favifetch library (parses the target site's HTML tags, web app manifests and # common fallback paths, with the Vemetric favicon API as last-resort fallback). # "proxied": every request is relayed to a Vemetric-compatible favicon service # (serviceUrl), which performs all discovery; the result is cached like a # direct fetch. Empty or unrecognized values fall back to "direct". Default: "direct". mode: direct # Favicon service host or full https URL (e.g. https://favicon.vemetric.com) used as # last-resort fallback in "direct" mode and as relay target in "proxied" mode. # Empty uses the built-in default (favicon.vemetric.com). A full URL is reduced to # its host (both favifetch and proxied mode always use HTTPS). Default: "" (built-in default). serviceUrl: "" # On-disk favicon cache directory. Icons are stored by normalized hostname, TTL is # 7 days; stale entries are refreshed in the background, failed fetches are backed # off for 1 hour. Created at startup, so it must be writable by the process. # Mountable as a Docker volume for persistence across restarts. Default: "" — # required (non-empty) when cacheEnabled is true. cacheDir: ./favicon-cache # Whether fetched favicons are stored on disk under cacheDir. Disabled by default; # if enabled, cacheDir must be set (startup fails otherwise). Default: false. cacheEnabled: true # Directory for custom icon files (SVG, PNG). Files are served at /_/icons/. # The path is resolved on the machine the process runs on. In Docker it must be a path # valid INSIDE the container: use the container-side destination of the volume mount, # e.g. mount with `-v $(PWD)/icons:/opt/icons` and set `/opt/icons` here. Relative paths # resolve against the working directory (/opt in the official image). Requires creating # the directory and placing icon files. Default: "" (custom icons disabled). customIconsDir: ./icons ``` > **Note:** For external portals without an explicit `icon`, Portkey makes outbound > HTTP requests to fetch their favicons. Favicon caching is **disabled by default**; > to enable it, set `favicon.cacheEnabled: true` and a writable `favicon.cacheDir` > (created at startup — in Docker, mount a writable volume there, see the Docker > section). With the cache disabled, icons are still fetched, just never stored. ### Portals (Links) ```yaml # Defines a list of portals (links) that have additional attributes defining their appearance. portals: # Name of the link - title: example # (Optional) Icon shown in front of the title. Supports multiple formats: # - Emoji: icon: "🔗" # - Custom SVG/PNG: icon: /_/icons/github.svg (place file in customIconsDir) # - Absolute URL: icon: https://example.com/icon.png # - Data URI: icon: data:image/svg+xml,%3Csvg... # If empty, the global favicon is used for external links (cached automatically) # and a file icon is shown for internal pages. # Link where the portal will lead to (can be relative for custom pages or absolute otherwise) link: https://example.com/ # Additional keywords used by the search feature. keywords: - url - example # (Optional) Group name for organising portals into sections on the home page. # Portals sharing the same group value are rendered together under a labelled heading. # Portals without a group are shown ungrouped at the bottom. Groups appear in the # order the first portal of each group is defined. group: My Group ``` > **Tip:** When a search query is active, portals are still grouped by their group field. Groups with no matching portals are hidden. ### Custom pages ```yaml # Defines a list of custom pages that are made available at the defined paths. # Important: These are not automatically added to the list of portals and have to be added manually! pages: # Heading for the custom page. Shown in browser tab and as heading on the page. - heading: Custom # Optional subtitle for the page (accepted but not displayed by the 4.0.0 layout). subtitle: An optional subtitle # Path where the custom page will be available. path: /custom # Content of the custom page and it supports using HTML. # The same CSS rules apply as for the footer! content: |- This is a custom page
It also supports using HTML! ``` ## Metrics Metrics can be enabled with the `metrics.enabled` configuration key and are served on a dedicated HTTP server. By default they are served on `http://localhost:3030/metrics`. Use this address to configure your tool of choice (e.g. [Prometheus](https://prometheus.io/)) to scrape the exported metrics. Besides the default metrics provided by the [Prometheus instrumentation library for Go applications ](https://github.com/prometheus/client_golang), the following additional metrics are provided: ```plain # HELP portkey_portal_handler_requests_total Total number of HTTP requests by portal. # TYPE portkey_portal_handler_requests_total counter portkey_portal_handler_requests_total{portal=""} 0 # HELP portkey_page_handler_requests_total Total number of HTTP requests by page. # TYPE portkey_page_handler_requests_total counter portkey_page_handler_requests_total{path=""} 0 # HELP portkey_search_requests_with_results_total Total number of HTTP requests for search with at least one result. # TYPE portkey_search_requests_with_results_total counter portkey_search_requests_with_results_total 0 # HELP portkey_search_requests_no_results_total Total number of HTTP requests for search with no results. # TYPE portkey_search_requests_no_results_total counter portkey_search_requests_no_results_total 0 # HELP portkey_search_duration_seconds Search query duration in seconds. # TYPE portkey_search_duration_seconds histogram portkey_search_duration_seconds_bucket{le="0.001"} 0 portkey_search_duration_seconds_bucket{le="0.005"} 0 portkey_search_duration_seconds_bucket{le="0.01"} 0 portkey_search_duration_seconds_bucket{le="0.025"} 0 portkey_search_duration_seconds_bucket{le="0.05"} 0 portkey_search_duration_seconds_bucket{le="0.1"} 0 portkey_search_duration_seconds_bucket{le="+Inf"} 0 portkey_search_duration_seconds_sum 0 portkey_search_duration_seconds_count 0 # HELP portkey_http_request_duration_seconds HTTP request duration by handler pattern. # TYPE portkey_http_request_duration_seconds histogram portkey_http_request_duration_seconds_bucket{handler="/",le="0.005"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="0.01"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="0.025"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="0.05"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="0.1"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="0.25"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="0.5"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="1"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="2.5"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="5"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="10"} 0 portkey_http_request_duration_seconds_bucket{handler="/",le="+Inf"} 0 portkey_http_request_duration_seconds_sum{handler="/"} 0 portkey_http_request_duration_seconds_count{handler="/"} 0 # HELP portkey_favicon_cache_hits_total Total number of favicon cache hits. # TYPE portkey_favicon_cache_hits_total counter portkey_favicon_cache_hits_total 0 # HELP portkey_favicon_cache_misses_total Total number of favicon cache misses. # TYPE portkey_favicon_cache_misses_total counter portkey_favicon_cache_misses_total 0 # HELP portkey_favicon_fetch_failures_total Total number of failed favicon fetches. # TYPE portkey_favicon_fetch_failures_total counter portkey_favicon_fetch_failures_total 0 # HELP portkey_favicon_cache_size Current number of favicons in the on-disk cache. # TYPE portkey_favicon_cache_size gauge portkey_favicon_cache_size 0 # HELP portkey_portals_total Total number of configured portals. # TYPE portkey_portals_total gauge portkey_portals_total 0 # HELP portkey_groups_total Total number of portal groups. # TYPE portkey_groups_total gauge portkey_groups_total 0 # HELP portkey_version_info Version information about portkey. # TYPE portkey_version_info gauge portkey_version_info{buildTime="2024.10.09_17:29:19",commitHash="4fd1a0f",goVersion="1.23.1",version="dev"} 1 ``` ## Docker There are also Docker images available at Docker hub that you can use. You can start a container with the following command: ```sh # Assumes that there is a config.yml in the current directory. # It is probably better to use a specific version than 'latest'. docker run --rm -it \ -v $(PWD)/config.yml:/opt/config.yml \ -v $(PWD)/favicon-cache:/opt/favicon-cache \ -v $(PWD)/icons:/opt/icons \ -e PORTKEY_FAVICON_CACHEENABLED=true \ -e PORTKEY_FAVICON_CACHE_DIR=/opt/favicon-cache \ -e PORTKEY_FAVICON_CUSTOMICONSDIR=/opt/icons \ -p 3000:3000 \ codehat/portkey:latest ``` > **Paths in Docker:** the container runs with the working directory `/opt`, so relative > paths in `config.yml` (e.g. `favicon.cacheDir: ./favicon-cache`) resolve to `/opt/...`. > For volume-mounted directories such as `favicon.customIconsDir`, always configure the > **container-side destination** of the mount, not a host path: `-v $(PWD)/icons:/opt/icons` > requires `favicon.customIconsDir: /opt/icons` (or `PORTKEY_FAVICON_CUSTOMICONSDIR=/opt/icons`). A volume > mount does not rewrite config paths — the path in the config must equal the mount target > inside the container. ## Development ### Application Code **portkey** is a *Go* application. You can install its dependencies with `go mod download`. ### Frontend The frontend dependencies (e.g. TailwindCSS, AlpineJS) can be installed with `npm install --include dev`. They can be watched with `npm run watch` and built with `npm run build`. ### Templates A library called [templ](https://templ.guide) is used for the templates. To generate the `.go` files from the templates, it has to be installed. `templ` is installed using go tools and can be invoked with: ```sh go tool templ ``` Afterwards you can generate the compiled templates with `templ generate`. ### Live Reload Live reloading is possible by installing [air](https://github.com/cosmtrek/air) and calling `air`. `air` is installed using go tools and can be invoked with: ```sh go tool air ``` Set `server.devMode: true` in `config.yml` (or the environment variable `PORTKEY_SERVER_DEVMODE=true`) to enable development-mode additions: a browser-reload WebSocket endpoint at `/reload` and dev-mode markup in the page layout. Dev mode is disabled by default. ## License [AGPL-3.0](https://www.tldrlegal.com/license/gnu-affero-general-public-license-v3-agpl-3-0)