Compare commits

...

19 commits

Author SHA1 Message Date
asonix 03e8b31f92 Strip release binaries
All checks were successful
/ check (aarch64-unknown-linux-musl) (push) Successful in 2m42s
/ clippy (push) Successful in 1m50s
/ tests (push) Successful in 2m17s
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 2m44s
/ check (x86_64-unknown-linux-musl) (push) Successful in 2m36s
2024-02-12 15:17:32 -06:00
asonix 0b96b0244c Remove prerelease marker
All checks were successful
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 2m40s
/ clippy (push) Successful in 1m29s
/ tests (push) Successful in 2m22s
/ check (aarch64-unknown-linux-musl) (push) Successful in 2m36s
/ check (x86_64-unknown-linux-musl) (push) Successful in 2m24s
2024-02-12 14:33:05 -06:00
asonix a36b98881c Remove old docker stuff
All checks were successful
/ clippy (push) Successful in 1m9s
/ tests (push) Successful in 1m39s
/ check (aarch64-unknown-linux-musl) (push) Successful in 2m2s
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 2m6s
/ check (x86_64-unknown-linux-musl) (push) Successful in 1m59s
2024-02-11 14:55:00 -06:00
asonix 24e034c742 Add forgejo dockerfile
Some checks failed
/ clippy (push) Successful in 1m13s
/ check (armv7-unknown-linux-musleabihf) (push) Waiting to run
/ check (x86_64-unknown-linux-musl) (push) Waiting to run
/ tests (push) Has been cancelled
/ check (aarch64-unknown-linux-musl) (push) Has been cancelled
2024-02-11 14:52:32 -06:00
asonix d8eb763893 Remove reference to io-uring
All checks were successful
/ clippy (push) Successful in 1m13s
/ tests (push) Successful in 1m39s
/ check (aarch64-unknown-linux-musl) (push) Successful in 3m2s
/ check (armv7-unknown-linux-musleabihf) (push) Successful in 3m1s
/ check (x86_64-unknown-linux-musl) (push) Successful in 3m2s
2024-02-11 14:39:02 -06:00
asonix 17befe1c17 Add actions, remove drone
Some checks failed
/ tests (push) Successful in 2m22s
/ check (x86_64-unknown-linux-musl) (push) Waiting to run
/ clippy (push) Failing after 1m48s
/ check (aarch64-unknown-linux-musl) (push) Has been cancelled
/ check (armv7-unknown-linux-musleabihf) (push) Has been cancelled
2024-02-11 14:36:15 -06:00
asonix ae910514d4 Update flake
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-01 22:13:34 -06:00
asonix b68ee94f38 Remove dependency on once_cell
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 17:22:19 -06:00
asonix 8a6bb08134 clippy
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 17:13:58 -06:00
asonix d1ab413bf3 Update version
Some checks are pending
continuous-integration/drone/push Build is running
2024-02-01 17:13:11 -06:00
asonix 26d4c3efe9 Enable serving over TLS
Some checks are pending
continuous-integration/drone/push Build is running
2024-02-01 17:12:14 -06:00
asonix e54432d020 Add rustls-channel-resolver 2024-02-01 16:45:54 -06:00
asonix 232a1a04a1 Update ructe
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-01 16:45:27 -06:00
asonix aebc6ba814 Update tracing-log 2024-02-01 16:43:16 -06:00
asonix f43c3948fe Update opentelemtry to 0.21 2024-02-01 16:42:42 -06:00
asonix d18a33940f Update console-subscriber 2024-02-01 16:38:36 -06:00
asonix f123f7d60a Update minify-html, dependencies (minor & point) 2024-02-01 16:35:19 -06:00
asonix 7a862bc8ff Enable connecting to pict-rs over TLS 2024-02-01 16:32:17 -06:00
asonix d467dfb2d1 Don't print terrible upstream errors
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-17 22:02:45 -05:00
19 changed files with 1885 additions and 1317 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-proxy
- cp target/$TARGET/release/pict-rs-proxy .
- cp pict-rs-proxy pict-rs-proxy-linux-amd64
- name: push
image: plugins/docker:20
settings:
username: asonix
password:
from_secret: dockerhub_token
repo: asonix/pictrs-proxy
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-proxy-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-proxy
- cp target/$TARGET/release/pict-rs-proxy .
- cp pict-rs-proxy pict-rs-proxy-linux-arm64v8
- name: push
image: plugins/docker:20
settings:
username: asonix
password:
from_secret: dockerhub_token
repo: asonix/pictrs-proxy
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-proxy-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-proxy
- cp target/$TARGET/release/pict-rs-proxy .
- cp pict-rs-proxy pict-rs-proxy-linux-arm32v7
- name: push
image: plugins/docker:20
settings:
username: asonix
password:
from_secret: dockerhub_token
repo: asonix/pictrs-proxy
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-proxy-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-proxy
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-proxy
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-proxy
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-proxy
jobs:
clippy:
runs-on: base-image
container:
image: docker.io/asonix/actions-base-image:0.1
steps:
-
name: Checkout pict-rs-proxy
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-proxy
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-proxy
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-proxy
run: cargo zigbuild --target ${{ matrix.info.target }} --release
-
name: Prepare artifacts
run: |
mkdir artifacts
cp target/${{ matrix.info.target }}/release/pict-rs-proxy artifacts/pict-rs-proxy-${{ 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-proxy docker/forgejo/pict-rs-proxy
-
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-proxy
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 }}

1890
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
[package]
name = "pict-rs-proxy"
description = "A simple web frontend for pict-rs"
version = "0.5.0-alpha.1"
version = "0.5.0"
authors = ["asonix <asonix@asonix.dog>"]
license = "AGPL-3.0"
readme = "README.md"
@ -10,6 +10,9 @@ edition = "2021"
build = "src/build.rs"
[profile.release]
strip = true
[features]
default = []
@ -17,43 +20,48 @@ default = []
[dependencies]
actix-rt = "2.7.0"
actix-web = { version = "4.0.0", default-features = false }
actix-web = { version = "4.0.0", default-features = false, features = ["rustls-0_21"] }
anyhow = "1.0"
awc = { version = "3.0.0", default-features = false }
awc = { version = "3.0.0", default-features = false, features = ["rustls-0_21"] }
clap = { version = "4.0.2", features = ["derive", "env"] }
console-subscriber = "0.1"
console-subscriber = "0.2"
dotenv = "0.15.0"
mime = "0.3"
minify-html = "0.11.1"
once_cell = "1.4"
opentelemetry = { version = "0.19", features = ["rt-tokio"] }
opentelemetry-otlp = "0.12"
minify-html = "0.15.0"
opentelemetry = "0.21"
opentelemetry_sdk = { version = "0.21", features = ["rt-tokio"] }
opentelemetry-otlp = "0.14"
rustls = "0.21"
rustls-pemfile = "2.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_qs = { version = "0.12", features = ["actix4"] }
thiserror = "1.0"
tokio = { version = "1", features = ["fs"] }
tracing = "0.1"
tracing-error = "0.2"
tracing-futures = "0.2"
tracing-log = "0.1"
tracing-opentelemetry = "0.19"
tracing-log = "0.2"
tracing-opentelemetry = "0.22"
tracing-subscriber = { version = "0.3", features = [
"ansi",
"env-filter",
"fmt",
] }
url = "2.1"
webpki-roots = "0.26"
rustls-channel-resolver = "0.1.0"
[dependencies.tracing-actix-web]
version = "0.7.5"
version = "0.7.9"
default-features = false
features = ["emit_event_on_error", "opentelemetry_0_19"]
features = ["emit_event_on_error", "opentelemetry_0_21"]
[dependencies.tracing-awc]
version = "0.1.7"
version = "0.1.9"
default-features = false
features = ["emit_event_on_error", "opentelemetry_0_19"]
features = ["emit_event_on_error", "opentelemetry_0_21"]
[build-dependencies]
anyhow = "1.0"
dotenv = "0.15.0"
ructe = { version = "0.16.1", features = ["sass", "mime03"] }
ructe = { version = "0.17.0", features = ["sass", "mime03"] }

View file

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

View file

@ -1,25 +0,0 @@
image: asonix/pictrs-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: asonix/pictrs-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: asonix/pictrs-proxy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64v8
platform:
architecture: arm64
os: linux
variant: v8
-
image: asonix/pictrs-proxy:{{#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-proxy /usr/local/bin/pict-rs-proxy
USER app
EXPOSE 6669
EXPOSE 8080
VOLUME /mnt
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/local/bin/pict-rs-proxy"]

View file

@ -1,41 +0,0 @@
ARG REPO_ARCH=amd64
# cross-build environment
FROM asonix/rust-builder:$REPO_ARCH-latest AS builder
ARG TAG=main
ARG BINARY=pict-rs-proxy
ARG PROJECT=pict-rs-proxy
ARG GIT_REPOSITORY=https://git.asonix.dog/asonix/$PROJECT
ENV \
BINARY=${BINARY}
ADD \
--chown=build:build \
$GIT_REPOSITORY/archive/$TAG.tar.gz \
/opt/build/repo.tar.gz
RUN \
tar zxf repo.tar.gz
WORKDIR /opt/build/$PROJECT
RUN \
build
# production environment
FROM asonix/rust-runner:$REPO_ARCH-latest
ARG BINARY=pict-rs-proxy
ENV \
BINARY=${BINARY}
COPY \
--from=builder \
/opt/build/binary \
/usr/bin/${BINARY}
ENTRYPOINT ["/sbin/tini", "--"]
CMD /usr/bin/${BINARY}

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
function require() {
if [ "$1" = "" ]; then
echo "input '$2' required"
print_help
exit 1
fi
}
function print_help() {
echo "deploy.sh"
echo ""
echo "Usage:"
echo " deploy.sh [repo] [tag] [arch]"
echo ""
echo "Args:"
echo " repo: The docker repository to publish the image"
echo " tag: The tag applied to the docker image"
echo " arch: The architecuture of the doker image"
}
REPO=$1
TAG=$2
ARCH=$3
require "$REPO" repo
require "$TAG" tag
require "$ARCH" arch
sudo docker build \
--pull \
--build-arg TAG=$TAG \
--build-arg REPO_ARCH=$ARCH \
-t $REPO:$ARCH-$TAG \
-f Dockerfile \
.

View file

@ -1,87 +0,0 @@
#!/usr/bin/env bash
function require() {
if [ "$1" = "" ]; then
echo "input '$2' required"
print_help
exit 1
fi
}
function print_help() {
echo "deploy.sh"
echo ""
echo "Usage:"
echo " deploy.sh [tag] [branch] [push]"
echo ""
echo "Args:"
echo " tag: The git tag to be applied to the repository and docker build"
echo " branch: The git branch to use for tagging and publishing"
echo " push: Whether or not to push the image"
echo ""
echo "Examples:"
echo " ./deploy.sh v0.3.0-alpha.13 main true"
echo " ./deploy.sh v0.3.0-alpha.13-shell-out asonix/shell-out false"
}
function build_image() {
tag=$1
arch=$2
push=$3
./build-image.sh asonix/pictrs-proxy $tag $arch
sudo docker tag asonix/pictrs-proxy:$arch-$tag asonix/pictrs-proxy:$arch-latest
if [ "$push" == "true" ]; then
sudo docker push asonix/pictrs-proxy:$arch-$tag
sudo docker push asonix/pictrs-proxy:$arch-latest
fi
}
# Creating the new tag
new_tag="$1"
branch="$2"
push=$3
require "$new_tag" "tag"
require "$branch" "branch"
require "$push" "push"
if ! sudo docker run --rm -it arm64v8/alpine:3.11 /bin/sh -c 'echo "docker is configured correctly"'
then
echo "docker is not configured to run on qemu-emulated architectures, fixing will require sudo"
sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
fi
set -xe
git checkout $branch
# Changing the docker-compose prod
sed -i "s/asonix\/pictrs-proxy:.*/asonix\/pictrs-proxy:$new_tag/" docker-compose.yml
git add ../prod/docker-compose.yml
# The commit
git commit -m"Version $new_tag"
git tag $new_tag
# Push
git push origin $new_tag
git push
# Build for arm64v8, arm32v7 and amd64
build_image $new_tag arm64v8 $push
build_image $new_tag arm32v7 $push
build_image $new_tag amd64 $push
# Build for other archs
# TODO
if [ "$push" == "true" ]; then
./manifest.sh pictrs-proxy $new_tag
./manifest.sh pictrs-proxy latest
pushd ../../
cargo publish
popd
fi

View file

@ -8,7 +8,7 @@ services:
- ./volumes/pictrs:/mnt
pictrs-proxy:
image: asonix/pictrs-proxy:v0.3.0-beta.1
image: asonix/pictrs-proxy:0.3
ports:
- "8081:8081"
restart: always

View file

@ -1,43 +0,0 @@
#!/usr/bin/env bash
function require() {
if [ "$1" = "" ]; then
echo "input '$2' required"
print_help
exit 1
fi
}
function print_help() {
echo "deploy.sh"
echo ""
echo "Usage:"
echo " manifest.sh [repo] [tag]"
echo ""
echo "Args:"
echo " repo: The docker repository to update"
echo " tag: The git tag to be applied to the image manifest"
}
REPO=$1
TAG=$2
require "$REPO" "repo"
require "$TAG" "tag"
set -xe
sudo docker manifest create asonix/$REPO:$TAG \
-a asonix/$REPO:arm64v8-$TAG \
-a asonix/$REPO:arm32v7-$TAG \
-a asonix/$REPO:amd64-$TAG
sudo docker manifest annotate asonix/$REPO:$TAG \
asonix/$REPO:arm64v8-$TAG --os linux --arch arm64 --variant v8
sudo docker manifest annotate asonix/$REPO:$TAG \
asonix/$REPO:arm32v7-$TAG --os linux --arch arm --variant v7
sudo docker manifest annotate asonix/$REPO:$TAG \
asonix/$REPO:amd64-$TAG --os linux --arch amd64
sudo docker manifest push asonix/$REPO:$TAG --purge

View file

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1689192006,
"narHash": "sha256-QM0f0d8oPphOTYJebsHioR9+FzJcy1QNIzREyubB91U=",
"lastModified": 1706550542,
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2de8efefb6ce7f5e4e75bdf57376a96555986841",
"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}";
};

View file

@ -1,18 +1,14 @@
{ lib
, nixosTests
, protobuf
, rustPlatform
}:
rustPlatform.buildRustPackage {
pname = "pict-rs-proxy";
version = "0.5.0-alpha.1";
version = "0.5.0";
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
PROTOC = "${protobuf}/bin/protoc";
PROTOC_INCLUDE = "${protobuf}/include";
nativeBuildInputs = [ ];
passthru.tests = { inherit (nixosTests) pict-rs-proxy; };

View file

@ -10,18 +10,22 @@ use actix_web::{
middleware::NormalizePath,
web, App, HttpRequest, HttpResponse, HttpResponseBuilder, HttpServer, ResponseError,
};
use awc::Client;
use anyhow::Context;
use awc::{Client, Connector};
use clap::Parser;
use console_subscriber::ConsoleLayer;
use once_cell::sync::Lazy;
use opentelemetry::{
sdk::{propagation::TraceContextPropagator, Resource},
KeyValue,
};
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{propagation::TraceContextPropagator, Resource};
use rustls::{
sign::CertifiedKey, Certificate, ClientConfig, OwnedTrustAnchor, PrivateKey, RootCertStore,
ServerConfig,
};
use std::{
io::Cursor,
net::SocketAddr,
path::PathBuf,
sync::{Arc, OnceLock},
time::{Duration, SystemTime},
};
use tracing_actix_web::TracingLogger;
@ -29,8 +33,7 @@ use tracing_awc::Tracing;
use tracing_error::{ErrorLayer, SpanTrace};
use tracing_log::LogTracer;
use tracing_subscriber::{
filter::Targets, fmt::format::FmtSpan, layer::SubscriberExt, registry::LookupSpan, Layer,
Registry,
filter::Targets, layer::SubscriberExt, registry::LookupSpan, Layer, Registry,
};
use url::Url;
@ -47,7 +50,7 @@ struct Config {
short,
long,
env = "PICTRS_PROXY_ADDR",
default_value = "0.0.0.0:8081",
default_value = "[::]:8081",
help = "The address and port the server binds to"
)]
addr: SocketAddr,
@ -73,10 +76,17 @@ struct Config {
#[arg(
long,
env = "PICTRS_PROXY_CONSOLE_BUFFER_SIZE",
help = "Number of events to buffer for the console subscriber. When unset, console will be disabled"
help = "Number of events to buffer for the console subscriber"
)]
console_event_buffer_size: Option<usize>,
#[arg(
long,
env = "PICTRS_PROXY_CONSOLE",
help = "Enable the tokio-console at the specified address"
)]
console_addr: Option<SocketAddr>,
#[arg(
short,
long,
@ -84,11 +94,33 @@ struct Config {
help = "URL of OpenTelemetry Collector"
)]
opentelemetry_url: Option<Url>,
#[arg(
short,
long,
env = "PICTRS_PROXY_CERTIFICATE",
help = "Path to the certificate file to connect to pict-rs over TLS"
)]
certificate: Option<PathBuf>,
#[arg(
long,
env = "PICTRS_PROXY_SERVER_CERTIFICATE",
help = "Path to the certificate file to serve pict-rs-proxy over TLS"
)]
server_certificate: Option<PathBuf>,
#[arg(
long,
env = "PICTRS_PROXY_SERVER_PRIVATE_KEY",
help = "Path to the private key file to serve pict-rs-proxy over TLS"
)]
server_private_key: Option<PathBuf>,
}
impl Config {
fn domain(&self) -> Option<&str> {
CONFIG.domain.domain()
config().domain.domain()
}
fn upstream_upload_url(&self) -> String {
@ -185,7 +217,11 @@ impl Config {
}
}
static CONFIG: Lazy<Config> = Lazy::new(Config::parse);
static CONFIG: OnceLock<Config> = OnceLock::new();
fn config() -> &'static Config {
CONFIG.get_or_init(Config::parse)
}
pub enum UploadResult<'a> {
Image(Image),
@ -311,27 +347,27 @@ impl Image {
}
fn link(&self) -> String {
CONFIG.image_url(&self.file)
config().image_url(&self.file)
}
fn thumbnails(&self) -> String {
CONFIG.thumbnails_url(&self.file)
config().thumbnails_url(&self.file)
}
fn view(&self, size: Option<u64>) -> String {
CONFIG.view_url(size, &self.file)
config().view_url(size, &self.file)
}
fn thumb(&self, size: u64, filetype: FileType) -> String {
CONFIG.thumbnail_url(size, &self.file, filetype)
config().thumbnail_url(size, &self.file, filetype)
}
fn delete(&self) -> String {
CONFIG.delete_url(&self.delete_token, &self.file)
config().delete_url(&self.delete_token, &self.file)
}
fn confirm_delete(&self) -> String {
CONFIG.confirm_delete_url(&self.delete_token, &self.file)
config().confirm_delete_url(&self.delete_token, &self.file)
}
}
@ -345,6 +381,15 @@ pub struct Error {
kind: ErrorKind,
}
impl Error {
pub(crate) fn upstream_error(&self) -> Option<&str> {
match self.kind {
ErrorKind::UploadFailed(ref msg) => Some(msg),
_ => None,
}
}
}
impl<T> From<T> for Error
where
ErrorKind: From<T>,
@ -431,7 +476,7 @@ async fn list_uploads(
let mut details_handles = Vec::new();
for upload_id in &query.uploads {
let claim_url = CONFIG.upstream_claim_url(upload_id.as_str());
let claim_url = config().upstream_claim_url(upload_id.as_str());
let client = client.clone();
upload_handles.push(actix_rt::spawn(async move {
@ -452,7 +497,7 @@ async fn list_uploads(
}
for (file, delete_token) in &query.files {
let details_url = CONFIG.upstream_details_url(file);
let details_url = config().upstream_details_url(file);
let file = file.clone();
let delete_token = delete_token.clone();
@ -544,7 +589,7 @@ async fn upload(
body: web::Payload,
client: web::Data<Client>,
) -> Result<HttpResponse, Error> {
let client_request = client.request_from(CONFIG.upstream_upload_url(), req.head());
let client_request = client.request_from(config().upstream_upload_url(), req.head());
let client_request = if let Some(addr) = req.head().peer_addr {
client_request.append_header(("X-Forwarded-For", addr.to_string()))
@ -579,7 +624,7 @@ async fn thumbs(
) -> Result<HttpResponse, Error> {
let file = query.into_inner().image;
let url = CONFIG.upstream_details_url(&file);
let url = config().upstream_details_url(&file);
let mut res = client.get(url).send().await?;
if res.status() == StatusCode::NOT_FOUND {
@ -634,7 +679,7 @@ async fn view_original(
) -> Result<HttpResponse, Error> {
let file = file.into_inner();
let url = CONFIG.upstream_details_url(&file);
let url = config().upstream_details_url(&file);
let mut res = client.get(url).send().await?;
if res.status() == StatusCode::NOT_FOUND {
@ -665,7 +710,7 @@ async fn view(
return Ok(to_404());
}
let url = CONFIG.upstream_details_url(&file);
let url = config().upstream_details_url(&file);
let mut res = client.get(url).send().await?;
if res.status() == StatusCode::NOT_FOUND {
@ -694,7 +739,7 @@ async fn thumbnail(
let (size, filetype, file) = parts.into_inner();
if valid_thumbnail_size(size) {
let url = CONFIG.upstream_thumbnail_url(size, &file, filetype);
let url = config().upstream_thumbnail_url(size, &file, filetype);
return image(url, req, client).await;
}
@ -712,7 +757,7 @@ async fn full_res(
req: HttpRequest,
client: web::Data<Client>,
) -> Result<HttpResponse, Error> {
let url = CONFIG.upstream_image_url(&filename.into_inner());
let url = config().upstream_image_url(&filename.into_inner());
image(url, req, client).await
}
@ -757,7 +802,7 @@ async fn delete(
confirm,
} = query.into_inner();
let url = CONFIG.upstream_details_url(&file);
let url = config().upstream_details_url(&file);
let mut res = client.get(url).send().await?;
if res.status() == StatusCode::NOT_FOUND {
@ -765,7 +810,7 @@ async fn delete(
}
if confirm {
let url = CONFIG.upstream_delete_url(&token, &file);
let url = config().upstream_delete_url(&token, &file);
client.delete(url).send().await?;
render(HttpResponse::Ok(), |cursor| {
@ -825,6 +870,7 @@ fn render(
fn init_tracing(
service_name: &'static str,
opentelemetry_url: Option<&Url>,
console_addr: Option<SocketAddr>,
console_event_buffer_size: Option<usize>,
) -> Result<(), anyhow::Error> {
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
@ -835,27 +881,32 @@ fn init_tracing(
.unwrap_or_else(|_| "info".into())
.parse()?;
let format_layer = tracing_subscriber::fmt::layer()
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.with_filter(targets.clone());
let format_layer = tracing_subscriber::fmt::layer().with_filter(targets.clone());
let subscriber = Registry::default()
.with(format_layer)
.with(ErrorLayer::default());
if let Some(buffer_size) = console_event_buffer_size {
let console_layer = ConsoleLayer::builder()
if let Some(console_addr) = console_addr {
let console_builder = ConsoleLayer::builder()
.with_default_env()
.server_addr(([0, 0, 0, 0], 6669))
.event_buffer_capacity(buffer_size)
.spawn();
.server_addr(console_addr);
let console_layer = if let Some(buffer_size) = console_event_buffer_size {
console_builder.event_buffer_capacity(buffer_size).spawn()
} else {
console_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!("Serving tokio-console endpoint on {console_addr}");
} else {
init_subscriber(subscriber, targets, opentelemetry_url, service_name)
init_subscriber(subscriber, targets, opentelemetry_url, service_name)?;
}
Ok(())
}
fn init_subscriber<S>(
@ -869,18 +920,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)
@ -896,22 +948,85 @@ where
Ok(())
}
async fn rustls_client_config() -> anyhow::Result<ClientConfig> {
let mut cert_store = RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|anchor| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
anchor.subject.to_vec(),
anchor.subject_public_key_info.to_vec(),
anchor.name_constraints.as_ref().map(|d| d.to_vec()),
)
})
.collect(),
};
if let Some(cert) = config().certificate.as_ref() {
let cert_bytes = tokio::fs::read(&cert).await?;
for res in rustls_pemfile::certs(&mut cert_bytes.as_slice()) {
cert_store.add(&Certificate(res?.to_vec()))?;
}
};
Ok(ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(cert_store)
.with_no_client_auth())
}
async fn rustls_server_key() -> anyhow::Result<Option<CertifiedKey>> {
let certificate_path = if let Some(c) = &config().server_certificate {
c
} else {
tracing::info!("No server certificate");
return Ok(None);
};
let private_key_path = if let Some(p) = &config().server_private_key {
p
} else {
tracing::info!("No server private_key");
return Ok(None);
};
let cert_bytes = tokio::fs::read(certificate_path).await?;
let certs = rustls_pemfile::certs(&mut cert_bytes.as_slice())
.map(|res| res.map(|c| Certificate(c.to_vec())))
.collect::<Result<Vec<_>, _>>()?;
let key_bytes = tokio::fs::read(private_key_path).await?;
let key =
rustls_pemfile::private_key(&mut key_bytes.as_slice())?.context("No key in keyfile")?;
let signing_key = rustls::sign::any_supported_type(&PrivateKey(Vec::from(key.secret_der())))?;
Ok(Some(CertifiedKey::new(certs, signing_key)))
}
#[actix_rt::main]
async fn main() -> Result<(), anyhow::Error> {
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
init_tracing(
"pict-rs-proxy",
CONFIG.opentelemetry_url.as_ref(),
CONFIG.console_event_buffer_size,
config().opentelemetry_url.as_ref(),
config().console_addr,
config().console_event_buffer_size,
)?;
HttpServer::new(move || {
let client_config = rustls_client_config().await?;
let server = HttpServer::new(move || {
let client = Client::builder()
.wrap(Tracing)
.add_default_header(("User-Agent", "pict-rs-frontend, v0.5.0-alpha.1"))
.add_default_header(("User-Agent", "pict-rs-frontend, v0.5.0"))
.timeout(Duration::from_secs(30))
.disable_redirects()
.connector(Connector::new().rustls_021(Arc::new(client_config.clone())))
.finish();
App::new()
@ -936,10 +1051,45 @@ async fn main() -> Result<(), anyhow::Error> {
.service(web::resource("/delete").route(web::get().to(delete)))
.service(web::resource("/404").route(web::get().to(not_found)))
.default_service(web::get().to(go_home))
})
.bind(CONFIG.addr)?
.run()
.await?;
});
if let Some(key) = rustls_server_key().await? {
tracing::info!("Serving pict-rs-proxy over TLS on {}", config().addr);
let (tx, rx) = rustls_channel_resolver::channel::<32>(key);
let handle = actix_rt::spawn(async move {
let mut interval = actix_rt::time::interval(Duration::from_secs(30));
interval.tick().await;
loop {
interval.tick().await;
match rustls_server_key().await {
Ok(Some(key)) => tx.update(key),
Ok(None) => tracing::warn!("Missing server certificates"),
Err(e) => tracing::error!("Failed to read server certificates {e}"),
}
}
});
let server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_cert_resolver(rx);
server
.bind_rustls_021(config().addr, server_config)?
.run()
.await?;
handle.abort();
let _ = handle.await;
} else {
tracing::info!("Serving pict-rs-proxy on {}", config().addr);
server.bind(config().addr)?.run().await?;
}
Ok(())
}

View file

@ -48,7 +48,11 @@
UploadResult::Error(error) => {
<p>
Error:<br />
@if let Some(msg) = error.upstream_error() {
@msg
} else {
@error.to_string()
}
</p>
}
UploadResult::UploadId(_) => {

View file

@ -1,5 +1,5 @@
@use super::statics::{layout_css, favicon_ico};
@use crate::CONFIG;
@use crate::config;
@(title: &str, description: Option<&str>, head: Content, body: Content)
@ -24,7 +24,7 @@
<meta property="og:description" content="Upload and share image files" />
<meta property="twitter:description" content="Upload and share image files" />
}
@if let Some(domain) = CONFIG.domain() {
@if let Some(domain) = config().domain() {
<meta property="twitter:domain" content="@domain" />
}
<link rel="stylesheet" href="@crate::statics(layout_css.name)" type="text/css" />