Activity

ultraviolet.asdf

Feature: Golang SDK

I wrote a SDK for go (a wrapper around the generate gRPC client) with nice features like:

  • Automatically setting up a client for every service (admin, buckets, objects and volumes)
  • Automatically creating a context which passes authorisation to the master server
  • The super nice features
    • Put an object in 1-3 lines instead of the previous ~50 (due to having to create a policy and then setting up the request and handling errors)
    • Get an object in 1-3 lines instead of ~28 (same reason as above)

I also fixed a bug where you couldn’t start the master server without migrating the schema

Next up I’ll probably write some proper docs with hugo, or add chunking support (or not, its a lot of effort), or add a compact button to the volumes list

v0.20.0 Binaries
Commit a516de71b7
Commit 00633bf003

Godoc Here

Attachment
0
ultraviolet.asdf

For you: I made Warehouse easier to run

I did this by:

  1. Actually including binaries for the volume+web servers (oops)
  2. Stopped harcoding the server auth token (double oops)
  3. Add an option (--schema) to the server binary that allows for automatically applying database schema, and include the schema in releases
  4. Updated README.md with quick start instructions

Releases are available at Codeberg if you want to try it yourself. These include the README with install instructions.

Commit 464f0e739a

Attachment
0
ultraviolet.asdf

Feature: Volumes list

I added an action to list volumes of a server, which shows usage and object count of each volume.

I need to display the wasted space count, and add an option to compact the volume.

I also fixed a bug where terminating the volume server before it had synced the needle locations to disk would result in the data being inaccessible. I fixed this by handling interrupts and syncing before shutdowns.

You might notice that volume 1 has 29 bytes of usage, but 0 objects. This is because the object has been flagged as deleted, but is still in physical storage. The reason for this is because volumes are an append only file, meaning deleted and duplicate files are kept until compaction.

v0.18.0 Binaries

Changelog:

Attachment
0
ultraviolet.asdf

Feature: Object preview

I added a details action that shows a preview of the object, and displayed the last updated date.

Right now previews only show for text/* content types, but I will add support for more, like a specific renderer for CSV, JSON, images and videos, with a toggle between raw and formatted.

v0.17.0 Binaries
Commit c4ccca7a84

Attachment
Attachment
0
ultraviolet.asdf

Feature: View objects

You can now view a buckets objects in the web UI, including total size and count.

The object viewer is flat for now, meaning there are no virtual folders and everything appears at the top level. I will implement this later.

Next up is object actions.

v0.16.0 Binaries
Commit 433c485c2f

Attachment
Attachment
0
ultraviolet.asdf

Feature: Display volume server usage

I made the usage of volume servers available (through /usage) and displayed it in the Web UI, with a since meter.

This update took longer than expected, because styling meters is hell and I didn’t even end up using the built in ones. I was also having CORS issues :)

v0.13.0 Binaries
Commit ca24d5fee4

Attachment
Attachment
Attachment
0
ultraviolet.asdf

Fixes: Master server untracks volumes when the volume server disconnects, and volume server crashes when the master disconnects

This fixes the issue where server where still marked as offline even after disconnecting. This was fixed by switching from a unary (one time) request to bidirectional stream, where disconnects can be handled.

I also:

  • Upgraded to HTMX v4
  • Fixed an issue where (DEGRADED) was shown instead of (OFFLINE) when all volume servers are online
  • Made border colours and radiuses consistent between pages

v0.12.0 Binaries
Commit cfa141bed9
Commit 6f95c42217

Attachment
Attachment
0
ultraviolet.asdf

Feature: Volume Server

I added a page with a list of all volume servers, their status, volume count, and capacity.

Next up is:

  • Total used space
  • Total volume server count / volume count / capacity
  • Volume servers to be marked as offline when they disconnect

v0.11.0 Binaries
Commit cf265572c8

Attachment
0
ultraviolet.asdf

Feature: Basic Admin UI

I implemented the basics of the Admin UI, using Golang, Templ, and TailwindCSS.

The numbers you see are made up, there is currently no integration with the master API. The colour also adapts to the status, if all servers are offline the colour is red and amber if some are offline.

Commit 59978babed
Commit ca97fe6061

Attachment
Attachment
0
ultraviolet.asdf

Major Feature: Remote Volume Servers

This is a big feature that allows for scaling horizontally. Each volume server connects to the master to initialise and then starts a REST API which provides direct access to needle management.

One difference from normal S3 is that every request is now pre-signed, and you have to communicate directly with each volume server.

How horizontal scaling works

How a volume server connects to the master:

How a put (overwrite) works:

Problems

  • Multiple requests - this is still a performance improvement over proxying data, but it makes DX works. I need to write an SDK that makes uploading 1 simple function call.
  • Volume compaction - the admin RPC is unimplemented in this version, I need to add an endpoint to the volume servers
  • Object getting - this is implemented on the volume server, however you need to know the needle and volume id. This also does not require authentication right now. I will implement this next.
  • Content type/object size limits are not verified.
  • Configuration is hardcoded in the volume server
  • Code quality
  • Error handling

I would’ve fixed all these problems, but this devlog was getting long enough :)

Commit 3bf51c25ed
v0.7.0 Binaries

Attachment
0
ultraviolet.asdf

Feature: Volume Compaction

One problem with using a single, append only, volume file is that deleted files and duplicates are not removed. Over time, this can waste a lot of storage. To fix this, I wrote a compaction tool, which reads the volume file, scanning each needle. If a needle is flagged as deleted, it is ignored and any previous needle with the same id is removed. I also only keep a copy of the latest needle, to keep the latest version and remove duplicates. Then, for each needle, the data is copied to a new volume file, then the old file is replaced with the new clean data.

I added an admin RPC to manually trigger compaction, and a utility to retrieve what proportion of the volume file is wasted.

v0.6.0 Binaries
be0acaa5bf

Attachment
0
ultraviolet.asdf

Big Feature: Volume Files

One problem with my storage server is that each object was stored as a separate file on disk.This means each file retrieval is actually multiple disk operations, which can slow down retrieval

Heavily based on Facebook’s Haystack Paper, I wrote a storage system that uses one large file for many smaller objects, lowering the number of disk operations to read one object.

  • Each object (file) is stored as a needle
  • A write works by appending the needle to a data file
  • A needle contains a small amount of metadata, and the data itself:
    • The ID (8 bytes)
    • The flags (whether or not the file has been deleted) (1 byte)
    • The size of the data (4 bytes)
    • The data itself
    • The checksum of the data (using the CRC hashing algorithm) (4 bytes)
  • Only 17 bytes are used for metadata, compared to XFS inodes using 536 bytes
  • The size and offset of each needle is stored in a kv store, and persisted to disk
  • A read retrieves the size and offset of the needle from kv storage, reads the file at the offset, and decodes each field. If the flag is 1, the file is deleted and an error is returned. The checksum of the data is calculated again and compared to the stored checksum
  • A delete sets the flag of the needle to 1, and removes the metadata from the kv store

There are some (fixable) problems with this approach:

  • Deleted/Duplicate files take up storage. I need to write a compression system, creating a new data file and only writing non-deleted and the first duplicate to the new data file
  • I have not written code to recreate the metadata index from the data store. If the metadata index is lost or corrupted, the metadata would have to be recovered by hand.

This work will allow me write volume servers, which manage multiple volumes, to allow for horizontal scaling and redundancy.

v0.5.0 Binaries
00ae00ba93

Attachment
Attachment
1

Comments

ultraviolet.asdf
ultraviolet.asdf 21 days ago

PS: Read the haystack paper! I found it very interesting!
(I had to cut out so many characters from this devlog)

ultraviolet.asdf

Feature: Object Retrieval

You can now retrieve files using the gRPC API. I still need to implement streaming puts/gets.

Note that the shown data field is encoded using base64, the actual data has been stored correctly

970d311e99
v0.4.0 Binaries

Attachment
Attachment
0
ultraviolet.asdf

Feature: Object Creation

Here’s all the changes I made:

  • Creating a bucket creates a buckets and backups folder on disk
  • Buckets no longer have an ID, solely identifiable by name
  • Remove unnecessary stuff and don’t try to restore backups that don’t exit ac054d4c96

And the features I added:

  • Object creation (Unary/Single Request - Optimal for small files, but I need to add a streaming version for large files)
  • Free space check, don’t start writing if there’s not enough space. (annoying to do, because of windows support)
  • Backups - If a file already exists, create a backup and ensure all steps succeed or restore the backup

5dc05e56e4
v0.2.1 Binaries

Attachment
0
ultraviolet.asdf

Rewrite + Automatic releases

  • I moved from a REST API to a gRPC API, because of all the time gRPC saves. Switching to gRPC greatly reduced the lines of code.
  • I made the Buckets.Get endpoint take a name, instead of an ID.
  • I added goreleaser, to automatically build the server and distributes it on Codeberg
  • I required an API key to use RPCs
0
ultraviolet.asdf

New endpoint

I added an endpoint to get a buckets information. I also wrote a function to stringify data and handle errors, to remove duplicated code.

I’m planning on rewriting the API with gRPC, because honestly I cannot be bothered with manually stringifying and parsing data. Protobuf is also way more efficient than JSON. It also allows me to generate clients for many languages automatically

76b8b61dd9

Attachment
0
ultraviolet.asdf

Project Restructure + Bucket name validation

I moved the go files to cmd/server, and moved utility functions into separate files.
I also added environment variable configuration for setting the server port and database location.
For bucket name validation, I used regex to only allow characters a-z, 0-9, ‘.’ and ‘_’, with a max length of 32

a38e257c09

Attachment
0
ultraviolet.asdf

Warehouse

In this devlog, I set up dependencies for Warehouse, and write a REST API with a single POST /buckets endpoint, which creates an entry in the SQLite Database

Motivation

I used S3/SeaweedFS for my project Watchtower. But I discovered a few problems:

  • I needed a Message Broker, like RabbitMQ to handle uploads. Some problems were:
    • I had to notify each queue of the upload from the API manually.
    • Clean up is hard. You have to wait for each queue to finish, and then remove it from S3.
    • What if a queue should stop processing, like if NSFW content is detected? How would the queue detect this to avoid wasting time processing?
  • I needed to write a custom CDN, due to the lack of pre-signed prefix polices. E.g. an HLS video with multiple files could not be served directly from S3, because a pre-signed get policy only allows access to one file
  • Too many services. I have to run 1. S3, 2. RabbitMQ, 3. CDN, and 3 separate queues - What if this could all be one service?

Feature Goals

  • Basic Bucket CRUD
  • Basic Object CRUD
  • Pre-signed Policies
  • Pre-signed Prefix Policies
  • Web UI
  • Authentication
  • Graph based upload processing
  • FFmpeg integration
  • TensorFlow integration
  • Golang client
  • TypeScript client
Attachment
0
ultraviolet.asdf

NSFW Filter for thumbnails

Thumbnails are now automatically blocked if anything NSFW is detected.

I also moved the clean-up step of jobs to a defer, so errored jobs will still be cleaned up.

I know the logs don’t look the nicest and aren’t consistent, I’ll be rewriting logs to use structured logs soon.

53f859535b

Attachment
Attachment
0
ultraviolet.asdf

UI Update: Infinite Scroll + Loading indicator for suggested videos

I added infinite scroll for suggested videos. This was already implemented in the API, but I guess I forgot to implement it on the website. I also added a loading indicator.

Note: The loading is intentionally slowed down in the demo video, just so you can actually see the indicator

8924fb5425

0
ultraviolet.asdf

UI Update: New layout options

I added layout options to suggested videos - a ‘comfy’ and ‘compact’ layout.
Your selected layout is consistent between page refreshes and different videos.

0c6e2273a8

0
ultraviolet.asdf

UI Update

I updated to video list to readd the separator, use flex wrapping and lower the font size.

Unfortunately, when the flex element wraps, the separator still shows. I spent around an hour trying to fix this using container queries. Container queries are awful to use with heights. Never again.

2b7319b412

Attachment
Attachment
0
ultraviolet.asdf

Testing + Code quality

I wrote a test case for users.GetUserById. Since a lot of validation was the same from the test for users.Get, I moved that logic into a separate function to be used for both test cases.

a25a0f14fe

The users.Create RPC returned a uint64 ID instead of an int64, so I updated the code to use int64 instead, and removed any no longer necessary conversions to int64.

a683c56d0d

Attachment
0
ultraviolet.asdf

Testing + Code quality

I added a test case for the Users.Get RPC, which verifies that all returned fields are as expected.

When I was writing this, I realised that type for User contained an email, even though it was not sent on most emails, which could lead to confusion using any RPC that returns a User.

To fix this, I added a separate type that contained the users email (UserWithEmail) that is used by RPCs that need the users email, and I removed the email field from the User type.

bc8cc01aa0

Attachment
0
ultraviolet.asdf

Testing

I wrote a test case for the GetFollowing RPC.

This test case has a similar setup to the GetFollowers RPC (create two users and follow one), so I split the logic into a separate function to reduce duplicated code.

The test case ensures:

  • Only one following user is returned
  • The returned following user has the correct username and ID
  • The CreatedAt timestamp is valid

5d7ed806a2

Attachment
0
ultraviolet.asdf

Testing

I added a test case for the GetFollowers RPC - this is passing :)

Honestly, writing tests is boring as hell. I’ve only written 37% of the tests for the Users service… and there’s around three other services to test for.

d0cd784d54

Attachment
0
ultraviolet.asdf

Bug Fixes

In my previous devlog I discovered users were able to follow themselves.

In this devlog I:

  • Rewrote the follow/unfollow RPCs so more logic is done with go than SQL
  • Blocked users from following themselves (test case passes)
  • Added a nicer error message when users try to follow a nonexistent users

761211acb0

Attachment
Attachment
0
ultraviolet.asdf

Testing

I added test cases for:

  • Following yourself (should not be allowed)
  • Following someone (allowed)
  • Unfollowing someone (allowed)
  • Following a nonexistent person (should not be allowed)

As a result of this, I have found that:

  • You can follow yourself through the API
  • Error handling should be improved for follow RPCs
  • I should improve my testing solution, right now I have to manually log if it passes, and fails are just logged as an error. I should make this automatic

These should be fixed in the next update :)

c7b3ea8e3b

Attachment
0
ultraviolet.asdf

Code quality + Testing

I improved code quality by:

  • Check grammar/spelling. I fixed a lot of grammar and spelling errors using harper-ls and harper-cli
  • Stricter formatting. I ran gofumpt, which has a lot stricter formatting
  • Linting. This took by far the longest time - I ran golanglint-ci, and one by one, fixed every single lint issue. This included many unhandled errors (that wouldn’t of caused a problem, just weren’t being logged)

a93729e78c

I also updated testing by:

  • Cleaning up code
  • Writing test cases for different password lengths

a5f74ef1f5

Attachment
Attachment
0
ultraviolet.asdf

Feature: Umami tracking

I added Umami, a privacy focused analytics service to Watchtower. As of now it only tracks page views, but in the future I plant to improve integration by adding events for video likes/dislikes, follows, video creation etc.

I also considered Rybbit, because it has more features like error tracking and web vitals, but I could not get a self hosted set up working. Maybe in the future.

Attachment
0
ultraviolet.asdf

Feature: Comment deletion

I added the ability to delete comments. This can only be done by the user who created the comment, or any admin account

0
ultraviolet.asdf

UX Improvement

After updating the video upload script, an issue was created where errors weren’t displayed properly. I have updated the website to clearly show errors

Attachment
0
ultraviolet.asdf

Bug fixes

On the second test case I wrote I found a bug. Not sure if this is a good thing, but at least it means testing is worth it.

  • 686d0d4b70 Add test cases to make sure duplicate emails and usernames are blocked
  • e6562c8aae Fix bug discovered above where errors were not
    handled correctly
  • 2266cef6fc Allow videos down to 240 pixels to be uploaded, and handle videos that are smaller than 240px properly, by rejecting them and showing an error message instead of transcoding them badly
Attachment
0
ultraviolet.asdf

Testing!

Yes - I’ve finally gotten around to writing tests!

Before I was relying on manual testing, by visiting the website - I’ve probably missed quite a few bugs. But now I’ve started writing automated tests for the API.

I have set up the tests so an ephemeral API is set up, along with temporary PostgreSQL, S3, RabbitMQ, and Valkey (redis). These automatically start when you run the tests, and stop when the tests have finished. I did this using the Golang SDK for docker-compose, and waiting for the API to start. After, migrations are automatically applied to the testing database.

As of now, there is only one test case, but I will setup more and more. I plan to implement E2E testing for the website in the future.

Attachment
1

Comments

Neon
Neon about 1 month ago

rabitmq,, very noice

ultraviolet.asdf

Full deployment

The full version of Watchtower is deployed on my new server (my old pc) which means you can now upload videos! These will be transcoded to multiple different resolutions, and analyzed to block NSFW content. The NSFW detection may be a bit inaccurate so make sure to use the report and appeal features

Changes

  • 472ff840c5 Fix transcoder dockerfile
  • 1141fb2aec Add analytics dockerfile
  • 15c7b46db9 Video duration’s are hidden if they are zero (happens in demo environment)
  • Multiple Various dockerfile fixes
  • Multiple Switch to old golang version of video analyser
  • d58a78215d Sidebar links are given a semitransparent background
Attachment
0
ultraviolet.asdf

Shipped this project!

Hours: 64.27
Cookies: 🍪 675
Multiplier: 22.64 cookies/hr

I updated my video sharing app!

In this update I polished code, started work on an iOS app and added suggested videos in the view video page!

Please check my previous ship message and devlogs for more details :)

ultraviolet.asdf

Polishing

I refactored some ridiculous code that was inline, and made it a separate function.
This will reduce allot of bandwidth because now the JavaScript is in one place instead of in every single comment

View Commit

Attachment
0
ultraviolet.asdf

Polishing

Spent some time polishing things, and working on docker images, for a move to another server

  • 612a3cecad I removed the dependency on go-sqlite3 which was leftover from when I used sqlite as the database. The error messages for duplicate usernames are now prettier
  • fc2f524b21 I added docker images
  • e6394fea10 I gave the icons when changing/uploading thumbnails a background, so the contrast is still OK when the uploaded image is light or dark
  • 1427c28e33 I allowed the recommended videos to be clicked anywhere for better UX, and added a background on hover

If you spot anything that needs polishing - please let me know!

Attachment
Attachment
Attachment
0
ultraviolet.asdf

iOS App: Update

  1. I added sort options, you can now select between latest, popular, recommended and trending. This took a fair amount of work, I had to guess the type of the gRPC client so I could pass it between Views. This is really hard when you don’t have a functional LSP. I also had to rewrite the way the gRPC client was created so the connection doesn’t close.
  2. Moved thumbnails to a separate component for clarity
0
ultraviolet.asdf

Web Update

I added suggested videos right in the view video page.

I also simplified a lot of repeated code, moved an API endpoint from the Users service to the Videos service, and made it so if there is only one comment on a video it says 1 Comment instead of 1 Comments - Crazy right?!

View 3 commits

Attachment
1

Comments

ultraviolet.asdf
ultraviolet.asdf about 2 months ago

Not pushed to demo yet, intending to change servers and enable custom video uploads soon

ultraviolet.asdf

iOS App!

I started working on an iOS app using swift!

This is my first time ever using swift, so it took quite a while.

Initially I tried using NativeScript Svelte, then React Native. Its pretty hard to get gRPC working with them, needing a proxy or something for the gRPC server, so I switched to swift instead.

Xcode (Apples IDE) doesn’t run on linux, so I had to install xtool and vscode/zed. I have to rebuild everytime I make a change, and the builds are pretty slow, but Swift is a really nice language and I may use it in the future

Attachment
0
ultraviolet.asdf

Shipped this project!

Hours: 44.5
Cookies: 🍪 1070
Multiplier: 24.06 cookies/hr

I built Watchtower, a Youtube clone. It is entirely self hostable.

I worked on it for over 92 hours before I found out about Flavortown.
As of now, I have coded over 44 hours worth of updates!

Note: Live streams and uploading videos are disabled in the demo. I do not have a GPU server, so videos would take days to process and analyse. Live streams would probably blow up the laptop I’m hosting everything on. You can still live chat though

Features

  • Transcoding: uploaded videos get transcoded to multiple resolutions (360p, 480p, 720p, 1080,1440p and 2160p). You can upload vertical videos and it will keep the aspect ratio
  • Thumbnail generation: Creates a storyboard image that allows you to seek through the video with a preview
  • Video Analyser: Videos are split into frames, frames are deduplicated and each frame is analysed for NSFW content
  • Live streams: You can stream from OBS to Watchtower. This uses nginx-rtmp. Authentication is managed by a golang server, approving or rejecting streams
  • Live chat: There is a live stream chat built using websockets
  • CDN: I built a CDN that manages permissions for videos, and proxies to the RTMP server so stream keys remain hidden
  • Admin dashboard: I wrote an admin dashboard with user/video analytics, it allows you to manage users, reports and appeals
  • Reports: Users can report videos and an admin can block reported videos
  • Appeals: Blocked videos can have an appeal requested by the owner, approved or rejected by an admin

Tech stack

  • The API is built using go with gRPC/Protobuf
  • The frontend is written with go, templ and htmx
  • The CDN is built with go
  • S3 (SeaweedFS) is used for storage
  • Postgres is used as the database
  • Valkey(redis) is used for the live chat
  • Gorse is used as a recommendation system
  • RabbitMQ is used as the message broker
  • The transcoder was built with Go + FFMPEG
  • The thumbnail generator was also built with Go + FFMPEG
  • The video_analyser was first built with go, then rewritten with Typescript (Bun). The package nsfwjs was used.
  • The analytics is written with go, and is run as a cronjob once a day, counting the number of users and videos

Development

I built a tool called devman specifically for this. It allows you to run multiple services from a configuration file. Each services can wait for other services to be online, so programs do not crash if something not up yet.
You can check it out on Codeberg

ultraviolet.asdf

Video Reporting

I finished video reporting, now admins can flag a reported video in the admin dashboard

Attachment
Attachment
0
ultraviolet.asdf

Moderation Appeal

You can now request an appeal if your video gets blocked.

There is a new page in the admin dashboard to view appeals

Admins can either reject or remove appeals

Rejecting will not allow the user to create another appeal

View The Code

Attachment
Attachment
Attachment
Attachment
Attachment
0
ultraviolet.asdf

Rewrite

I rewrote the video analyzer (NSFW detection) from Go to TypeScript, because the go package used for NSFW broke.
It might be slower, but at least it works

Can’t really have many screenshots for this update…

I also reduced the amount of things you need to install by using go tools,
where instead of installing the binary I run go get -tool and call it using go tool
More work for me, less work for someone trying to set this up themselves.

I also reinstalled my OS 2 times, so thats why this update took a while

You can view the changes on Codeberg

Attachment
0
ultraviolet.asdf

Live chat

I added chats to live streams! (you can chat even when the stream is offline
This was implemented using websockets (this is like the 8th microservice)

I made it so scrolling pauses the chat, so you can read messages.
The colour of the usernames are based on the hash of your username, I should probably let you pick a colour though.

0
ultraviolet.asdf

Replaced dropzone.js with a custom upload script, so uploading matches the theme better
(4h of work because everything decided to break)
(ps don’t use RustFS, it is buggy and vibecoded)

View the code

Attachment
Attachment
0
ultraviolet.asdf

I added trending, and did a few other things, like improving security, working on a demo environment, improving code, fixing a bug and cleaning up processed thumbnails

View 5 changes

Attachment
0
ultraviolet.asdf

Admin Dashboard Improvements

I added a user management page that lists all users (with scrolling to load more) and allows flag management (Admin/Verified flags)

View the change

I should add search next

0
ultraviolet.asdf

Improved UX by redirecting back to profile page after deleting a video (with a refresh) so deleted videos don’t show after deletion
Also changed some endpoints to use proper http semantics

Attachment
Attachment
0
ultraviolet.asdf

I hid private/unprocessed videos in the recommended feed
Also had problems with atlas migrations which took hours to fix :)

Attachment
0
ultraviolet.asdf

I added a most popular sort option to the homepage of WatchTower.
I am planning to add a trending option as well.
I should probably remove some of the duplicated logic though and replace /videos/latest and /videos/popular with /videos/{sort_type}

Attachment
Attachment
0
ultraviolet.asdf

I added a “latest” sort option to my video sharing platform
I also fixed a bug where usernames wouldn’t show on recommended videos
Next up is sorting by popularity

Attachment
0