feat: Adds pocket id authentication to caddy, adds server management article.
All checks were successful
CI / release (push) Successful in 6m31s
All checks were successful
CI / release (push) Successful in 6m31s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,7 +9,7 @@ public/*
|
|||||||
.hugo_build.lock
|
.hugo_build.lock
|
||||||
deploy
|
deploy
|
||||||
node_modules
|
node_modules
|
||||||
env
|
.env
|
||||||
Package.resolved
|
Package.resolved
|
||||||
|
|
||||||
# Local Netlify folder
|
# Local Netlify folder
|
||||||
|
|||||||
53
Caddyfile
Normal file
53
Caddyfile
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
# Port to listen on
|
||||||
|
http_port 80
|
||||||
|
|
||||||
|
# Configure caddy-security.
|
||||||
|
order authenticate before respond
|
||||||
|
|
||||||
|
security {
|
||||||
|
oauth identity provider generic {
|
||||||
|
delay_start 3
|
||||||
|
realm generic
|
||||||
|
driver generic
|
||||||
|
client_id {env.OAUTH_CLIENT_ID}
|
||||||
|
client_secret {env.OAUTH_CLIENT_SECRET}
|
||||||
|
scopes openid email profile
|
||||||
|
base_auth_url https://id.housh.dev
|
||||||
|
metadata_url https://id.housh.dev/.well-known/openid-configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
authentication portal myportal {
|
||||||
|
crypto default token lifetime 3600 # Seconds until you have to re-authenticate
|
||||||
|
enable identity provider generic
|
||||||
|
cookie insecure on # Set to "on" if you're not using HTTPS
|
||||||
|
|
||||||
|
transform user {
|
||||||
|
match realm generic
|
||||||
|
action add role user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authorization policy mypolicy {
|
||||||
|
set auth url /caddy-security/oauth2/generic
|
||||||
|
allow roles user
|
||||||
|
inject headers with claims
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
https://docs.housh.dev {
|
||||||
|
@auth {
|
||||||
|
path /caddy-security/*
|
||||||
|
}
|
||||||
|
|
||||||
|
route @auth {
|
||||||
|
authenticate with myportal
|
||||||
|
}
|
||||||
|
|
||||||
|
route /* {
|
||||||
|
authorize with mypolicy
|
||||||
|
root * /app
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,14 +38,17 @@ RUN npx -y pagefind --site deploy
|
|||||||
# ==================================================
|
# ==================================================
|
||||||
# Run Image
|
# Run Image
|
||||||
# ==================================================
|
# ==================================================
|
||||||
FROM caddy:2.9.1-alpine
|
FROM ghcr.io/authcrunch/authcrunch:latest
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=css /build/deploy .
|
COPY --from=css /build/deploy .
|
||||||
COPY --from=css /build/content/static/output.css ./static/output.css
|
COPY --from=css /build/content/static/output.css ./static/output.css
|
||||||
COPY --from=css /build/deploy/pagefind ./pagefind
|
COPY --from=css /build/deploy/pagefind ./pagefind
|
||||||
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|
||||||
|
RUN /usr/bin/caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["/usr/bin/caddy", "file-server", "--root", "/app", "--listen", ":80"]
|
CMD ["/usr/bin/caddy", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ func baseLayout(
|
|||||||
.documentType("html"),
|
.documentType("html"),
|
||||||
html(lang: "en-US") {
|
html(lang: "en-US") {
|
||||||
generateHead(pageTitle, extraHeader)
|
generateHead(pageTitle, extraHeader)
|
||||||
body(class: "text-white text-lg pb-5 font-avenir \(section.rawValue)") {
|
body(class: "text-white text-lg font-avenir \(section.rawValue)") {
|
||||||
siteHeader(section)
|
siteHeader(section)
|
||||||
|
|
||||||
div(class: "container mb-auto") {
|
div(class: "mb-auto") {
|
||||||
children()
|
children()
|
||||||
}
|
}
|
||||||
if section == .articles {
|
if section == .articles {
|
||||||
@@ -57,9 +57,7 @@ private func siteHeader(_ section: Section) -> Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if section == .home {
|
|
||||||
div(class: "font-avenir w-full pt-4", id: "search") {}
|
div(class: "font-avenir w-full pt-4", id: "search") {}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,25 +97,38 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
|
|||||||
title: context.item.title,
|
title: context.item.title,
|
||||||
extraHeader: generateHeader(.article(context.item))
|
extraHeader: generateHeader(.article(context.item))
|
||||||
) {
|
) {
|
||||||
article(class: "pt-8") {
|
article(class: "pt-8 mx-10") {
|
||||||
h1 { context.item.title }
|
div(class: "bg-slate-800 py-10") {
|
||||||
div {
|
div(class: "mx-10") {
|
||||||
renderArticleInfo(context.item)
|
h1 { context.item.title }
|
||||||
}
|
div {
|
||||||
// Only index the body of the articles for search.
|
renderArticleInfo(context.item)
|
||||||
div(customAttributes: ["data-pagefind-body": ""]) {
|
}
|
||||||
Node.raw(context.item.body)
|
// Only index the body of the articles for search.
|
||||||
|
div(customAttributes: ["data-pagefind-body": ""]) {
|
||||||
|
Node.raw(context.item.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div(class: "border-t border-light pt-8 mt-16", id: "recents") {
|
div(class: "border-t border-light p-10 mt-16", id: "recents") {
|
||||||
div(class: "grid lg:grid-cols-2") {
|
div(class: "grid lg:grid-cols-2") {
|
||||||
h4(class: "text-3xl text-amber-500 font-extrabold mb-8") { otherArticles.title }
|
h4(class: "text-3xl text-amber-500 font-extrabold mb-8") { otherArticles.title }
|
||||||
if let tag = otherArticles.tag {
|
if let tag = otherArticles.tag {
|
||||||
a(href: "/articles/tag/\(tag)") {
|
a(href: "/articles/tag/\(tag)") {
|
||||||
div(class: " [&:hover]:border-b border-orange px-5 flex flex-row gap-5") {
|
div(class: " [&:hover]:border-b border-green-500 px-5 flex flex-row gap-5") {
|
||||||
img(src: "/static/img/tag.svg", width: "40")
|
img(class: "-mt-2", src: "/static/img/tag.svg", width: "40")
|
||||||
span(class: "text-4xl font-extrabold text-orange") { tag }
|
div(class: "block") {
|
||||||
|
div(class: "block") {
|
||||||
|
span(class: "mt-2 text-4xl font-extrabold text-orange") { tag }
|
||||||
|
}
|
||||||
|
div(class: "block") {
|
||||||
|
span(class: "text-sm text-orange-400") {
|
||||||
|
"View related articles with this tag."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,16 +150,18 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderArticleForGrid(article: Item<ArticleMetadata>) -> Node {
|
func renderArticleForGrid(article: Item<ArticleMetadata>, border: Bool = true) -> Node {
|
||||||
section {
|
div(class: "bg-slate-800\(border ? " border border-slate-400 rounded-lg" : "")") {
|
||||||
h3(class: "post-title text-2xl font-bold mb-2") {
|
section(class: "m-4") {
|
||||||
a(class: "[&:hover]:border-b border-orange-400", href: article.url) { article.title }
|
h3(class: "post-title text-2xl font-bold mb-2") {
|
||||||
}
|
a(class: "[&:hover]:border-b border-green-500", href: article.url) { article.title }
|
||||||
renderArticleInfo(article)
|
}
|
||||||
p {
|
renderArticleInfo(article)
|
||||||
a(href: article.url) {
|
p {
|
||||||
div {
|
a(href: article.url) {
|
||||||
article.summary
|
div {
|
||||||
|
article.summary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ func renderArticles(context: ItemsRenderingContext<ArticleMetadata>) -> Node {
|
|||||||
return baseLayout(canocicalURL: "/articles/", section: .articles, title: "Articles", rssLink: "", extraHeader: "") {
|
return baseLayout(canocicalURL: "/articles/", section: .articles, title: "Articles", rssLink: "", extraHeader: "") {
|
||||||
// TODO: Add list of tags here that can be navigated to.
|
// TODO: Add list of tags here that can be navigated to.
|
||||||
sortedByYearDescending.map { year, articles in
|
sortedByYearDescending.map { year, articles in
|
||||||
div {
|
div(class: "mt-8 bg-slate-800") {
|
||||||
div(class: "border-b border-light flex flex-row gap-4 mb-12") {
|
div(class: "pt-8 mx-10") {
|
||||||
img(src: "/static/img/calendar.svg", width: "40")
|
div(class: "border-b border-light flex flex-row gap-4 mb-12") {
|
||||||
h1(class: "text-4xl font-extrabold pt-3") { year }
|
img(src: "/static/img/calendar.svg", width: "40")
|
||||||
}
|
h1(class: "text-4xl font-extrabold pt-3") { year }
|
||||||
|
}
|
||||||
|
|
||||||
div(class: "grid gap-10 mb-16") {
|
div(class: "grid gap-10 mb-16") {
|
||||||
articles.map { renderArticleForGrid(article: $0) }
|
articles.map { renderArticleForGrid(article: $0, border: false) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,60 +65,6 @@ func renderYear<T>(context: PartitionedRenderingContext<T, ArticleMetadata>) ->
|
|||||||
baseRenderArticles(context.items, canocicalURL: "/articles/\(context.key)/", title: "Articles in \(context.key)")
|
baseRenderArticles(context.items, canocicalURL: "/articles/\(context.key)/", title: "Articles in \(context.key)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SearchData: Encodable {
|
|
||||||
let url: String
|
|
||||||
let title: String
|
|
||||||
let body: String
|
|
||||||
|
|
||||||
init(article: Item<ArticleMetadata>) throws {
|
|
||||||
self.url = article.url
|
|
||||||
self.title = article.title
|
|
||||||
let rawContent: String = try article.absoluteSource.read()
|
|
||||||
self.body = Self.parse(rawContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Grabs the metadata (wrapped within `---`), the first title, and the body of the document.
|
|
||||||
static func parts(from content: String) -> (String?, String?, String) {
|
|
||||||
let scanner = Scanner(string: content)
|
|
||||||
|
|
||||||
var header: String? = nil
|
|
||||||
var title: String? = nil
|
|
||||||
|
|
||||||
if scanner.scanString("---") == "---" {
|
|
||||||
header = scanner.scanUpToString("---")
|
|
||||||
_ = scanner.scanString("---")
|
|
||||||
}
|
|
||||||
|
|
||||||
if scanner.scanString("# ") == "# " {
|
|
||||||
title = scanner.scanUpToString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = String(scanner.string[scanner.currentIndex...])
|
|
||||||
|
|
||||||
return (header, title, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func parse(_ content: String) -> String {
|
|
||||||
let (_, _, body) = parts(from: content)
|
|
||||||
return body
|
|
||||||
.replacingOccurrences(of: "\n", with: " ")
|
|
||||||
.replacingOccurrences(of: "#", with: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderJson(_ articles: ItemsRenderingContext<ArticleMetadata>) throws -> String {
|
|
||||||
print(articles.items.count)
|
|
||||||
print(articles.items)
|
|
||||||
let data = try jsonEncoder.encode(articles.items.map(SearchData.init(article:)))
|
|
||||||
return String(data: data, encoding: .utf8)!
|
|
||||||
}
|
|
||||||
|
|
||||||
private let jsonEncoder: JSONEncoder = {
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
||||||
return encoder
|
|
||||||
}()
|
|
||||||
|
|
||||||
private func baseRenderArticles(
|
private func baseRenderArticles(
|
||||||
_ articles: [Item<ArticleMetadata>],
|
_ articles: [Item<ArticleMetadata>],
|
||||||
canocicalURL: String,
|
canocicalURL: String,
|
||||||
|
|||||||
@@ -31,63 +31,65 @@ func renderHome(body: String) -> Node {
|
|||||||
Node.raw(body)
|
Node.raw(body)
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
h2 { "Quick Links" }
|
div(class: "bg-slate-800 p-10 rounded-lg border border-slate-400") {
|
||||||
div(class: "grid lg:grid-cols-2 gap-6") {
|
h2 { "Quick Links" }
|
||||||
HomeLink.internal(
|
div(class: "grid lg:grid-cols-2 gap-6") {
|
||||||
"Articles",
|
HomeLink.internal(
|
||||||
icon: "newspaper",
|
"Articles",
|
||||||
href: "/articles/",
|
icon: "newspaper",
|
||||||
description: "Click here to view all articles."
|
href: "/articles/",
|
||||||
)
|
description: "Click here to view all articles."
|
||||||
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"Purchase Orders",
|
"Purchase Orders",
|
||||||
icon: "calculator",
|
icon: "calculator",
|
||||||
href: "https://po.housh.dev",
|
href: "https://po.housh.dev",
|
||||||
description: "Purchase orders application."
|
description: "Purchase orders application."
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"Service Monitor",
|
"Service Monitor",
|
||||||
icon: "heart-pulse",
|
icon: "heart-pulse",
|
||||||
href: "https://uptime.housh.dev/status/housh-dev",
|
href: "https://uptime.housh.dev/status/housh-dev",
|
||||||
description: "Server and services uptime status page."
|
description: "Server and services uptime status page."
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"Unifi Console",
|
"Unifi Console",
|
||||||
icon: "earth",
|
icon: "earth",
|
||||||
href: "https://unifi.ui.com",
|
href: "https://unifi.ui.com",
|
||||||
description: "Network management."
|
description: "Network management."
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"Excalidraw",
|
"Excalidraw",
|
||||||
icon: "pen-tool",
|
icon: "pen-tool",
|
||||||
href: "https://draw.housh.dev",
|
href: "https://draw.housh.dev",
|
||||||
description: "A drawing utility that runs locally in your browser."
|
description: "A drawing utility that runs locally in your browser."
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"Gitea",
|
"Gitea",
|
||||||
icon: "git-branch",
|
icon: "git-branch",
|
||||||
href: "https://git.housh.dev/explore/repos",
|
href: "https://git.housh.dev/explore/repos",
|
||||||
description: "Explore source code."
|
description: "Explore source code."
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"Legacy Purchase Orders",
|
"Legacy Purchase Orders",
|
||||||
icon: "file-archive",
|
icon: "file-archive",
|
||||||
href: "https://legach-po.housh.dev",
|
href: "https://legach-po.housh.dev",
|
||||||
description: "Legacy purchase order application (pre-2025)."
|
description: "Legacy purchase order application (pre-2025)."
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeLink.external(
|
HomeLink.external(
|
||||||
"HVAC Toolbox",
|
"HVAC Toolbox",
|
||||||
icon: "hammer",
|
icon: "hammer",
|
||||||
href: "https://hvac-toolbox.com",
|
href: "https://hvac-toolbox.com",
|
||||||
description: "A collection of HVAC calculators."
|
description: "A collection of HVAC calculators."
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
script(src: "https://unpkg.com/lucide@latest")
|
script(src: "https://unpkg.com/lucide@latest")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ services:
|
|||||||
image: git.housh.dev/homelab/docs:latest
|
image: git.housh.dev/homelab/docs:latest
|
||||||
container_name: docs
|
container_name: docs
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
env_file: .env
|
||||||
ports:
|
ports:
|
||||||
- ${PORT:-8081}:80
|
- ${PORT:-8081}:80
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
119
content/articles/2025-04-09-ServerManagementConsole.md
Normal file
119
content/articles/2025-04-09-ServerManagementConsole.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
date: 2025-04-09
|
||||||
|
tags: infrastructure, servers, homelab
|
||||||
|
primaryTag: infrastructure
|
||||||
|
---
|
||||||
|
|
||||||
|
# Server Management Console
|
||||||
|
|
||||||
|
This article I'll describe some steps to manage and / or trouble shoot the
|
||||||
|
servers.
|
||||||
|
|
||||||
|
## Management Console
|
||||||
|
|
||||||
|
The servers have a management console that is accessible from the internal
|
||||||
|
network. You will need to get the login name and password from Michael.
|
||||||
|
|
||||||
|
| Server | Link |
|
||||||
|
| ------------ | ---------------------------------------------------------------------- |
|
||||||
|
| mighty-mini | [console.mightymini.housh.dev](https://console.mightymini.housh.dev) |
|
||||||
|
| franken-mini | [console.frankenmini.housh.dev](https://console.frankenmini.housh.dev) |
|
||||||
|
| rogue-mini | [console.roguemini.housh.dev](https://console.roguemini.housh.dev) |
|
||||||
|
|
||||||
|
The management console allows you to update the server, check logs, and access a
|
||||||
|
terminal on the machine. If you are updating the server via the management
|
||||||
|
console, it is often required to reboot the server. All of the services are
|
||||||
|
setup to restart upon a reboot of the server, so that should not cause problems,
|
||||||
|
but you will be disconnected from the management console when the server shuts
|
||||||
|
down. It does take a few minutes generally for the servers to go through the
|
||||||
|
full boot process.
|
||||||
|
|
||||||
|
> Note: If something is not running the easiest thing to do would be to just
|
||||||
|
> reboot the servers and the services should restart.
|
||||||
|
|
||||||
|
[You can view the server and services status here.](https://uptime.housh.dev/status/housh-dev)
|
||||||
|
|
||||||
|
## Reboot the server
|
||||||
|
|
||||||
|
You can reboot the server from the management console in the `Overview` section
|
||||||
|
or by typing the following command in the terminal.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo reboot --now
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful Tips
|
||||||
|
|
||||||
|
There are several commands that may help trouble shoot the services on the
|
||||||
|
server. For these you will need to make sure to turn on administrative access by
|
||||||
|
clicking the button, if needed.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
All of the following commands can be entered into the `Terminal` section of the
|
||||||
|
console.
|
||||||
|
|
||||||
|
### Check the services are running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker ps --all
|
||||||
|
```
|
||||||
|
|
||||||
|
If working on a small screen or the output is bunched up then you can use the
|
||||||
|
following command to only reveal a smaller portion of the output.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker ps --format 'table {{.Names}}\t{{.Status}}'
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Here you would look for services where the **_STATUS_** says `Exited` or if any
|
||||||
|
of the services say `unhealthy`.
|
||||||
|
|
||||||
|
### Service locations
|
||||||
|
|
||||||
|
The services are primary located in `/etc/komodo/stacks` or `~/containers`
|
||||||
|
directories. You can list the contents of those directories using the following
|
||||||
|
command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lah ~/containers
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lah /etc/komodo/stacks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Starting services from the terminal
|
||||||
|
|
||||||
|
If you would like to ensure a service is up and running from the terminal move
|
||||||
|
into the directory of the service.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/containers/purchase-orders
|
||||||
|
```
|
||||||
|
|
||||||
|
And issue the following command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check the logs of a running container
|
||||||
|
|
||||||
|
You can check the logs of a container in several different ways. The easiest is
|
||||||
|
if you know the containers name.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker logs -f purchase_orders
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you know the directory you can move into the directory using the `cd`
|
||||||
|
command and use the following.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
To stop viewing the logs hit `Ctrl-c`.
|
||||||
BIN
content/static/img/servermanagement.console.png
LFS
Normal file
BIN
content/static/img/servermanagement.console.png
LFS
Normal file
Binary file not shown.
BIN
content/static/img/servermanagement.dockerps.png
LFS
Normal file
BIN
content/static/img/servermanagement.dockerps.png
LFS
Normal file
Binary file not shown.
@@ -170,15 +170,15 @@ td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
@apply mb-8;
|
@apply py-8 mb-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
table td {
|
table td {
|
||||||
@apply px-6;
|
@apply px-6 py-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@apply px-10;
|
@apply py-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container img {
|
.container img {
|
||||||
@@ -193,3 +193,7 @@ blockquote {
|
|||||||
blockquote p {
|
blockquote p {
|
||||||
@apply px-6 pt-6 text-blue-600 font-semibold;
|
@apply px-6 pt-6 text-blue-600 font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
@apply mb-6;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1 +1,3 @@
|
|||||||
PORT=8081
|
PORT=8081
|
||||||
|
OAUTH_CLIENT_ID="<id>"
|
||||||
|
OAUTH_CLIENT_SECRET="<secret>"
|
||||||
|
|||||||
Reference in New Issue
Block a user