Compare commits

...

22 commits
v0.1.1 ... main

Author SHA1 Message Date
asonix 2be2635eb3 Strip release binaries
All checks were successful
/ clippy (push) Successful in 1m33s
/ tests (push) Successful in 2m28s
/ check (aarch64-unknown-linux-musl) (push) Successful in 2m51s
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 2m46s
/ check (x86_64-unknown-linux-musl) (push) Successful in 2m32s
2024-02-12 15:16:51 -06:00
asonix 7465e57265 Remove prerelease marker
All checks were successful
/ clippy (push) Successful in 1m20s
/ tests (push) Successful in 2m34s
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 2m59s
/ check (x86_64-unknown-linux-musl) (push) Successful in 2m31s
/ check (aarch64-unknown-linux-musl) (push) Successful in 3m11s
2024-02-12 14:33:19 -06:00
asonix 15b30b320d Remove bad argument
All checks were successful
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 2m7s
/ check (x86_64-unknown-linux-musl) (push) Successful in 2m2s
/ clippy (push) Successful in 1m8s
/ tests (push) Successful in 1m42s
/ check (aarch64-unknown-linux-musl) (push) Successful in 2m5s
2024-02-11 14:53:15 -06:00
asonix 670f37510a Add forgejo dockerfile
Some checks failed
/ clippy (push) Successful in 1m15s
/ check (armv7-unknown-linux-musleabihf) (push) Waiting to run
/ check (x86_64-unknown-linux-musl) (push) Waiting to run
/ check (aarch64-unknown-linux-musl) (push) Has been cancelled
/ tests (push) Has been cancelled
2024-02-11 14:51:20 -06:00
asonix 89f99df2fb Add forgejo dockerfile 2024-02-11 14:50:30 -06:00
asonix fdc3c00eb8 Add actions, remove drone
Some checks failed
/ tests (push) Successful in 2m26s
/ clippy (push) Successful in 1m57s
/ check (aarch64-unknown-linux-musl) (push) Successful in 2m48s
/ check (armv7-unknown-linux-musleabihf) (push) Has been cancelled
/ check (x86_64-unknown-linux-musl) (push) Has been cancelled
2024-02-11 14:41:38 -06:00
asonix 122c17424c Support TLS upstream & downstream
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-01 22:12:33 -06:00
asonix 976b9851d3 Add rustls-channel-resolver, enable rustls 0.21, rustls 0.22
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 18:49:26 -06:00
asonix bf9d2e8a6e Bump version 2024-02-01 18:47:32 -06:00
asonix d9d8b196a1 Update tracing & otel libraries 2024-02-01 18:46:48 -06:00
asonix 7c40c73fdd Update dependencies (minor & point) 2024-02-01 18:40:14 -06:00
asonix bcdf965d67 Update minify-html 2024-02-01 18:39:45 -06:00
asonix 94c3400bbf Update flake
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 18:24:59 -06:00
asonix 0c66f533d1 Add search by date
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-08-29 14:02:33 -05:00
asonix 03a513e2b2 Update dependencies (minor & point) 2023-08-29 13:31:20 -05:00
asonix 28b64d9839 Bump version 2023-08-29 13:31:05 -05:00
asonix b9216705d3 Fix default bind address 2023-08-29 13:30:39 -05:00
asonix 1e0c1585d9 Fix example link
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-28 21:52:20 -05:00
asonix e2f5f6fae9 Add screenshot to readme
Some checks are pending
continuous-integration/drone/push Build is running
2023-08-28 21:51:39 -05:00
asonix 8ae9530b17 Bump version
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-08-28 21:26:56 -05:00
asonix d23ef5f64d Loop videos, set width
Some checks are pending
continuous-integration/drone/push Build is running
2023-08-28 21:26:14 -05:00
asonix 6e0d60d854 Enable setting video content type
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-08-28 21:08:17 -05:00
17 changed files with 1763 additions and 975 deletions

View file

@ -1,421 +0,0 @@
kind: pipeline
type: docker
name: clippy
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: clippy
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- rustup component add clippy
- cargo clippy -- -D warnings
trigger:
event:
- push
- pull_request
- tag
---
kind: pipeline
type: docker
name: tests
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: tests
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo test
trigger:
event:
- push
- pull_request
- tag
---
kind: pipeline
type: docker
name: check-amd64
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: check
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo check --target=$TARGET
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: build-amd64
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo build --target=$TARGET --release
- $TOOL-strip target/$TARGET/release/pict-rs-admin
- cp target/$TARGET/release/pict-rs-admin .
- cp pict-rs-admin pict-rs-admin-linux-amd64
- name: push
image: plugins/docker:20
settings:
username: asonix
password:
from_secret: dockerhub_token
repo: asonix/pictrs-admin
dockerfile: docker/drone/Dockerfile
auto_tag: true
auto_tag_suffix: linux-amd64
build_args:
- REPO_ARCH=amd64
- name: publish
image: plugins/gitea-release:1
settings:
api_key:
from_secret: gitea_token
base_url: https://git.asonix.dog
files:
- pict-rs-admin-linux-amd64
depends_on:
- clippy
- tests
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: check-arm64v8
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: check
image: asonix/rust-builder:latest-linux-arm64v8
pull: always
commands:
- cargo check --target=$TARGET
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: build-arm64v8
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-arm64v8
pull: always
commands:
- cargo build --target=$TARGET --release
- $TOOL-strip target/$TARGET/release/pict-rs-admin
- cp target/$TARGET/release/pict-rs-admin .
- cp pict-rs-admin pict-rs-admin-linux-arm64v8
- name: push
image: plugins/docker:20
settings:
username: asonix
password:
from_secret: dockerhub_token
repo: asonix/pictrs-admin
dockerfile: docker/drone/Dockerfile
auto_tag: true
auto_tag_suffix: linux-arm64v8
build_args:
- REPO_ARCH=arm64v8
- name: publish
image: plugins/gitea-release:1
settings:
api_key:
from_secret: gitea_token
base_url: https://git.asonix.dog
files:
- pict-rs-admin-linux-arm64v8
depends_on:
- clippy
- tests
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: check-arm32v7
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: check
image: asonix/rust-builder:latest-linux-arm32v7
pull: always
commands:
- cargo check --target=$TARGET
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: build-arm32v7
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-arm32v7
pull: always
commands:
- cargo build --target=$TARGET --release
- $TOOL-strip target/$TARGET/release/pict-rs-admin
- cp target/$TARGET/release/pict-rs-admin .
- cp pict-rs-admin pict-rs-admin-linux-arm32v7
- name: push
image: plugins/docker:20
settings:
username: asonix
password:
from_secret: dockerhub_token
repo: asonix/pictrs-admin
dockerfile: docker/drone/Dockerfile
auto_tag: true
auto_tag_suffix: linux-arm32v7
build_args:
- REPO_ARCH=arm32v7
- name: publish
image: plugins/gitea-release:1
settings:
api_key:
from_secret: gitea_token
base_url: https://git.asonix.dog
files:
- pict-rs-admin-linux-arm32v7
depends_on:
- clippy
- tests
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: manifest
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: manifest
image: plugins/manifest:1
settings:
username: asonix
password:
from_secret: dockerhub_token
dump: true
auto_tag: true
ignore_missing: true
spec: docker/drone/manifest.tmpl
depends_on:
- build-amd64
- build-arm64v8
- build-arm32v7
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: publish-crate
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: publish
image: asonix/rust-builder:latest-linux-amd64
pull: always
environment:
CRATES_IO_TOKEN:
from_secret: crates_io_token
commands:
- cargo publish --token $CRATES_IO_TOKEN
depends_on:
- build-amd64
- build-arm64v8
- build-arm32v7
trigger:
event:
- tag

View file

@ -0,0 +1,61 @@
on:
push:
branches:
- '*'
pull_request:
branches:
- main
jobs:
clippy:
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Clippy
run: |
cargo clippy --no-default-features -- -D warnings
tests:
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Test
run: cargo test
check:
strategy:
fail-fast: false
matrix:
target:
- x86_64-unknown-linux-musl
- armv7-unknown-linux-musleabihf
- aarch64-unknown-linux-musl
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Debug builds
run: cargo zigbuild --target ${{ matrix.target }}

View file

@ -0,0 +1,225 @@
on:
push:
tags:
- 'v*.*.*'
env:
REGISTRY_IMAGE: asonix/pictrs-admin
jobs:
clippy:
runs-on: base-image
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Clippy
run: |
cargo clippy --no-default-features -- -D warnings
tests:
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Test
run: cargo test
build:
needs:
- clippy
- tests
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
strategy:
fail-fast: false
matrix:
info:
- target: x86_64-unknown-linux-musl
artifact: linux-amd64
platform: linux/amd64
- target: armv7-unknown-linux-musleabihf
artifact: linux-arm32v7
platform: linux/arm/v7
- target: aarch64-unknown-linux-musl
artifact: linux-arm64v8
platform: linux/arm64
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Prepare Platform
run: |
platform=${{ matrix.info.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
shell: bash
-
name: Docker meta
id: meta
uses: https://github.com/docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
flavor: |
latest=auto
suffix=-${{ matrix.info.artifact }}
tags: |
type=raw,value=latest,enable={{ is_default_branch }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
-
name: Set up QEMU
uses: https://github.com/docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
-
name: Docker login
uses: https://github.com/docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Compile pict-rs-admin
run: cargo zigbuild --target ${{ matrix.info.target }} --release
-
name: Prepare artifacts
run: |
mkdir artifacts
cp target/${{ matrix.info.target }}/release/pict-rs-admin artifacts/pict-rs-admin-${{ matrix.info.artifact }}
-
uses: https://github.com/actions/upload-artifact@v3
with:
name: binaries
path: artifacts/
-
name: Prepare binary
run: |
cp target/${{ matrix.info.target }}/release/pict-rs-admin docker/forgejo/pict-rs-admin
-
name: Build and push ${{ matrix.info.platform }} docker image
id: build
uses: docker/build-push-action@v5
with:
context: ./docker/forgejo
platforms: ${{ matrix.info.platform }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},name-canonical=true,push=true
-
name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
echo "Created /tmp/digests/${digest#sha256:}"
shell: bash
-
name: Upload ${{ matrix.info.platform }} digest
uses: https://github.com/actions/upload-artifact@v3
with:
name: digests
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
publish-docker:
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
needs: [build]
steps:
-
name: Download digests
uses: https://github.com/actions/download-artifact@v3
with:
name: digests
path: /tmp/digests
pattern: digests-*
merge-multiple: true
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Docker login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Docker meta
id: meta
uses: https://github.com/docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
flavor: |
latest=auto
tags: |
type=raw,value=latest,enable={{ is_default_branch }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
-
name: Create manifest list and push
working-directory: /tmp/digests
run: |
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "${DOCKER_METADATA_OUTPUT_JSON}")
images=$(printf "${{ env.REGISTRY_IMAGE }}@sha256:%s " *)
echo "Running 'docker buildx imagetools create ${tags[@]} ${images[@]}'"
docker buildx imagetools create ${tags[@]} ${images[@]}
shell: bash
-
name: Inspect Image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
publish-forgejo:
needs: [build]
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
- uses: https://github.com/actions/download-artifact@v3
with:
name: binaries
path: artifacts/
merge-multiple: true
- uses: actions/forgejo-release@v1
with:
direction: upload
token: ${{ secrets.GITHUB_TOKEN }}
release-dir: artifacts/
publish-crate:
needs: [build]
runs-on: docker
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-admin
uses: https://github.com/actions/checkout@v4
-
name: Cargo Cache
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
-
name: Publish Crate
run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}

1697
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
[package]
name = "pict-rs-admin"
description = "An example pict-rs admin tool"
version = "0.1.1"
version = "0.2.0"
authors = ["asonix <asonix@asonix.dog>"]
license = "AGPL-3.0"
readme = "README.md"
@ -12,25 +12,34 @@ build = "src/build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
strip = true
[build-dependencies]
ructe = "0.17.0"
[dependencies]
actix-web = { version = "4.3.1", default-features = false, features = ["macros"] }
actix-web = { version = "4.3.1", default-features = false, features = ["macros", "rustls-0_21"] }
clap = { version = "4.4.1", features = ["derive", "env"] }
color-eyre = "0.6.2"
console-subscriber = "0.1.10"
minify-html = "0.11.1"
opentelemetry = { version = "0.20.0", features = ["rt-tokio"] }
opentelemetry-otlp = "0.13.0"
console-subscriber = "0.2.0"
minify-html = "0.15.0"
opentelemetry = "0.21.0"
opentelemetry_sdk = { version = "0.21.0", features = ["rt-tokio"] }
opentelemetry-otlp = "0.14.0"
reqwest = { version = "0.11.20", default-features = false, features = ["json", "rustls-tls", "stream"] }
reqwest-middleware = "0.2.3"
reqwest-tracing = { version = "0.4.6", features = ["opentelemetry_0_20"] }
reqwest-tracing = { version = "0.4.6", features = ["opentelemetry_0_21"] }
rustls_021 = { package = "rustls", version = "0.21" }
rustls = "0.22"
rustls-channel-resolver = "0.1.0"
rustls-pemfile = "2.0.0"
serde = { version = "1.0.188", features = ["derive"] }
tokio = { version = "1", features = ["fs"] }
tracing = "0.1.37"
tracing-actix-web = { version = "0.7.6", features = ["opentelemetry_0_20", "emit_event_on_error"] }
tracing-actix-web = { version = "0.7.6", features = ["opentelemetry_0_21", "emit_event_on_error"] }
tracing-error = "0.2.0"
tracing-log = "0.1.3"
tracing-opentelemetry = "0.20.0"
tracing-log = "0.2.0"
tracing-opentelemetry = "0.22.0"
tracing-subscriber = { version = "0.3.17", features = ["ansi", "env-filter", "fmt"] }
url = "2.4.1"

View file

@ -1,6 +1,8 @@
# pict-rs-admin
_A demo application showing off pict-rs administration capibilities_
![Application](https://git.asonix.dog/asonix/pict-rs-admin/raw/branch/main/images/example.png)
### Note: pict-rs-admin is not intended to be exposed to the internet.
## Running

View file

@ -1,12 +0,0 @@
ARG REPO_ARCH
FROM asonix/rust-runner:latest-linux-$REPO_ARCH
COPY pict-rs-admin /usr/local/bin/pict-rs-admin
USER app
EXPOSE 8080
EXPOSE 6669
VOLUME /mnt
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/local/bin/pict-rs-admin"]

View file

@ -1,25 +0,0 @@
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64v8
platform:
architecture: arm64
os: linux
variant: v8
-
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm32v7
platform:
architecture: arm
os: linux
variant: v7

24
docker/forgejo/Dockerfile Normal file
View file

@ -0,0 +1,24 @@
FROM alpine:3.19
ARG UID=991
ARG GID=991
ENV \
UID=${UID} \
GID=${GID}
USER root
RUN \
addgroup -g "${GID}" app && \
adduser -D -G app -u "${UID}" -g "" -h /opt/app app && \
apk add tini && \
chown -R app:app /mnt
COPY pict-rs-admin /usr/local/bin/pict-rs-admin
USER app
EXPOSE 6669
EXPOSE 8080
VOLUME /mnt
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/local/bin/pict-rs-admin"]

View file

@ -8,7 +8,7 @@ services:
- ./volumes/pictrs:/mnt
pictrs-admin:
image: asonix/pictrs-admin:0.1.0
image: asonix/pictrs-admin:0.2
ports:
- "8084:8084"
restart: always

View file

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1692799911,
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1693158576,
"narHash": "sha256-aRTTXkYvhXosGx535iAFUaoFboUrZSYb1Ooih/auGp0=",
"lastModified": 1706550542,
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a999c1cc0c9eb2095729d5aa03e0d8f7ed256780",
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
"type": "github"
},
"original": {

View file

@ -26,7 +26,7 @@
};
devShell = with pkgs; mkShell {
nativeBuildInputs = [ cargo cargo-outdated cargo-zigbuild clippy gcc protobuf rust-analyzer rustc rustfmt ];
nativeBuildInputs = [ cargo cargo-outdated clippy gcc rust-analyzer rustc rustfmt ];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
};

BIN
images/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -1,18 +1,14 @@
{ lib
, nixosTests
, protobuf
, rustPlatform
}:
rustPlatform.buildRustPackage {
pname = "pict-rs-admin";
version = "0.1.1";
version = "0.2.0";
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
PROTOC = "${protobuf}/bin/protoc";
PROTOC_INCLUDE = "${protobuf}/include";
RUSTFLAGS = "--cfg tokio_unstable";
nativeBuildInputs = [ ];

View file

@ -1,18 +1,17 @@
use std::net::SocketAddr;
use std::{net::SocketAddr, path::PathBuf, time::Duration};
use actix_web::{
body::BodyStream, error::ErrorInternalServerError, web, App, HttpResponse, HttpServer,
};
use clap::Parser;
use console_subscriber::ConsoleLayer;
use opentelemetry::{
sdk::{propagation::TraceContextPropagator, Resource},
KeyValue,
};
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{propagation::TraceContextPropagator, Resource};
use reqwest::{redirect::Policy, Client};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_tracing::TracingMiddleware;
use rustls_021::ServerConfig;
use tracing_actix_web::TracingLogger;
use tracing_error::ErrorLayer;
use tracing_subscriber::{
@ -26,7 +25,7 @@ struct Args {
short,
long,
env = "PICTRS_ADMIN__BIND_ADDRESS",
default_value = "127.0.0.0:8084"
default_value = "[::]:8084"
)]
bind_address: SocketAddr,
#[clap(
@ -39,8 +38,16 @@ struct Args {
pict_rs_api_key: String,
#[clap(long, env = "PICTRS_ADMIN__OPENTELEMETRY_URL")]
opentelemetry_url: Option<Url>,
#[clap(long, env = "PICTRS_ADMIN__OPENTELEMETRY_EVENT_BUFFER_SIZE")]
opentelemetry_event_buffer_size: Option<usize>,
#[clap(long, env = "PICTRS_ADMIN__CONSOLE_ADDRESS")]
console_address: Option<SocketAddr>,
#[clap(long, env = "PICTRS_ADMIN__CONSOLE_EVENT_BUFFER_SIZE")]
console_event_buffer_size: Option<usize>,
#[clap(long, env = "PICTRS_ADMIN__CERTIFICATE")]
pict_rs_certificate: Option<PathBuf>,
#[clap(long, env = "PICTRS_ADMIN__SERVER_CERTIFICATE")]
server_certificate: Option<PathBuf>,
#[clap(long, env = "PICTRS_ADMIN__SERVER_PRIVATE_KEY")]
server_private_key: Option<PathBuf>,
}
#[derive(Clone)]
@ -105,11 +112,14 @@ impl PictrsHash {
self.aliases.first().map(|alias| format!("/image/{alias}"))
}
fn is_video(&self) -> bool {
self.details
.as_ref()
.map(|d| d.content_type.starts_with("video"))
.unwrap_or(false)
fn video_type(&self) -> Option<String> {
self.details.as_ref().and_then(|d| {
if d.content_type.starts_with("video") {
Some(d.content_type.clone())
} else {
None
}
})
}
}
@ -134,7 +144,7 @@ impl PictrsPage {
}
impl PictrsClient {
async fn page(&self, slug: Option<String>) -> Result<PageResponse, reqwest_middleware::Error> {
async fn page(&self, query: &PageQuery) -> Result<PageResponse, reqwest_middleware::Error> {
let mut url = self.pict_rs_endpoint.clone();
url.set_path("/internal/hashes");
@ -142,7 +152,7 @@ impl PictrsClient {
.client
.get(url.as_str())
.header("x-api-token", &self.pict_rs_api_key)
.query(&PageQuery { slug })
.query(query)
.send()
.await?;
@ -187,13 +197,17 @@ impl PictrsClient {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct PageQuery {
slug: Option<String>,
timestamp: Option<String>,
}
async fn index(
web::Query(PageQuery { slug }): web::Query<PageQuery>,
web::Query(query): web::Query<PageQuery>,
client: web::Data<PictrsClient>,
) -> Result<HttpResponse, actix_web::Error> {
let page = client.page(slug).await.map_err(ErrorInternalServerError)?;
let page = client
.page(&query)
.await
.map_err(ErrorInternalServerError)?;
let page = match page {
PageResponse::Ok { page, .. } => page,
@ -269,6 +283,7 @@ async fn serve_static(name: web::Path<String>) -> HttpResponse {
fn init_tracing(
service_name: &'static str,
opentelemetry_url: Option<&Url>,
console_address: Option<SocketAddr>,
console_event_buffer_size: Option<usize>,
) -> color_eyre::Result<()> {
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
@ -285,19 +300,23 @@ fn init_tracing(
.with(format_layer)
.with(ErrorLayer::default());
if let Some(buffer_size) = console_event_buffer_size {
let console_layer = ConsoleLayer::builder()
.with_default_env()
.server_addr(([0, 0, 0, 0], 6669))
.event_buffer_capacity(buffer_size)
.spawn();
if let Some(addr) = console_address {
let builder = ConsoleLayer::builder().with_default_env().server_addr(addr);
let console_layer = if let Some(buffer_size) = console_event_buffer_size {
builder.event_buffer_capacity(buffer_size).spawn()
} else {
builder.spawn()
};
let subscriber = subscriber.with(console_layer);
init_subscriber(subscriber, targets, opentelemetry_url, service_name)
init_subscriber(subscriber, targets, opentelemetry_url, service_name)?;
tracing::info!("Starting console on {addr}");
} else {
init_subscriber(subscriber, targets, opentelemetry_url, service_name)
init_subscriber(subscriber, targets, opentelemetry_url, service_name)?;
}
Ok(())
}
fn init_subscriber<S>(
@ -311,18 +330,19 @@ where
for<'a> S: LookupSpan<'a>,
{
if let Some(url) = opentelemetry_url {
let tracer =
opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(opentelemetry::sdk::trace::config().with_resource(
Resource::new(vec![KeyValue::new("service.name", service_name)]),
))
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint(url.as_str()),
)
.install_batch(opentelemetry::runtime::Tokio)?;
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(
opentelemetry_sdk::trace::config().with_resource(Resource::new(vec![
KeyValue::new("service.name", service_name),
])),
)
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint(url.as_str()),
)
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
let otel_layer = tracing_opentelemetry::layer()
.with_tracer(tracer)
@ -338,6 +358,42 @@ where
Ok(())
}
async fn add_root_certificates(
builder: reqwest::ClientBuilder,
path: &PathBuf,
) -> color_eyre::Result<reqwest::ClientBuilder> {
let bytes = tokio::fs::read(path).await?;
let res = rustls_pemfile::certs(&mut bytes.as_slice()).try_fold(builder, |builder, res| {
let cert = res?;
let cert = reqwest::Certificate::from_der(&cert)?;
Ok(builder.add_root_certificate(cert))
});
res
}
async fn open_keys(
certificate: &PathBuf,
private_key: &PathBuf,
) -> color_eyre::Result<rustls_021::sign::CertifiedKey> {
let cert_bytes = tokio::fs::read(certificate).await?;
let key_bytes = tokio::fs::read(private_key).await?;
let certs = rustls_pemfile::certs(&mut cert_bytes.as_slice())
.map(|res| res.map(|c| rustls_021::Certificate(c.to_vec())))
.collect::<Result<Vec<_>, _>>()?;
let key = rustls_pemfile::private_key(&mut key_bytes.as_slice())?
.ok_or_else(|| color_eyre::eyre::eyre!("No key in keyfile"))?;
let signing_key =
rustls_021::sign::any_supported_type(&rustls_021::PrivateKey(key.secret_der().to_vec()))?;
Ok(rustls_021::sign::CertifiedKey::new(certs, signing_key))
}
#[actix_web::main]
async fn main() -> color_eyre::Result<()> {
let Args {
@ -345,19 +401,29 @@ async fn main() -> color_eyre::Result<()> {
pict_rs_endpoint,
pict_rs_api_key,
opentelemetry_url,
opentelemetry_event_buffer_size,
console_address,
console_event_buffer_size,
pict_rs_certificate,
server_certificate,
server_private_key,
} = Args::parse();
init_tracing(
"pict-rs-admin",
opentelemetry_url.as_ref(),
opentelemetry_event_buffer_size,
console_address,
console_event_buffer_size,
)?;
let client = Client::builder()
.user_agent("pict-rs-admin v0.1.0")
.redirect(Policy::none())
.build()?;
let builder = Client::builder()
.user_agent("pict-rs-admin v0.2.0")
.redirect(Policy::none());
let client = if let Some(path) = pict_rs_certificate {
add_root_certificates(builder, &path).await?.build()?
} else {
builder.build()?
};
let client = ClientBuilder::new(client)
.with(TracingMiddleware::default())
@ -369,7 +435,7 @@ async fn main() -> color_eyre::Result<()> {
pict_rs_api_key,
};
HttpServer::new(move || {
let server = HttpServer::new(move || {
App::new()
.wrap(TracingLogger::default())
.app_data(web::Data::new(client.clone()))
@ -377,10 +443,44 @@ async fn main() -> color_eyre::Result<()> {
.route("/image/{path}", web::get().to(image))
.route("/purge/{path}", web::get().to(purge))
.route("/static/{path}", web::get().to(serve_static))
})
.bind(bind_address)?
.run()
.await?;
});
if let Some((cert, key)) = server_certificate.zip(server_private_key) {
let certified_key = open_keys(&cert, &key).await?;
let (tx, rx) = rustls_channel_resolver::channel::<32>(certified_key);
let handle = actix_web::rt::spawn(async move {
let mut interval = actix_web::rt::time::interval(Duration::from_secs(30));
interval.tick().await;
loop {
interval.tick().await;
match open_keys(&cert, &key).await {
Ok(certified_key) => tx.update(certified_key),
Err(e) => tracing::error!("Failed to read TLS keys {e}"),
}
}
});
let server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_cert_resolver(rx);
tracing::info!("Starting pict-rs-admin with TLS on {bind_address}");
server
.bind_rustls_021(bind_address, server_config)?
.run()
.await?;
handle.abort();
let _ = handle.await;
} else {
tracing::info!("Starting pict-rs-admin on {bind_address}");
server.bind(bind_address)?.run().await?;
}
Ok(())
}

View file

@ -22,6 +22,17 @@ nav a {
padding: 8px 16px;
}
nav form {
display: inline-block;
}
nav input {
padding: 8px;
}
nav input[type="submit"] {
padding: 8px 16px;
}
section {
display: flex;
flex-wrap: wrap;
@ -48,7 +59,8 @@ article {
max-width: 100%;
}
img {
img,
video {
border-radius: 3px 3px 0 0;
display: block;
width: 100%;

View file

@ -14,7 +14,6 @@
</head>
<body>
@if page.prev_link().is_some() || page.next_link().is_some() {
<nav>
@if let Some(prev) = page.prev_link() {
<a href="@prev">Previous Page</a>
@ -22,8 +21,11 @@
@if let Some(next) = page.next_link() {
<a href="@next">Next Page</a>
}
<form method="GET" action="/">
<input type="text" name="timestamp" placeholder="rfc3339 timestamp" />
<input type="submit" />
</form>
</nav>
}
<section>
@if page.hashes.is_empty() {
@ -40,8 +42,8 @@
<div class="border">
@if let Some(src) = hash.media_link() {
<div class="image-box">
@if hash.is_video() {
<video controls src="@src"></video>
@if let Some(video_type) = hash.video_type() {
<video controls loop src="@src" type="@video_type"></video>
} else {
<img src="@src" />
}