preapring for rls
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ public/a
|
||||
public/tag_cache
|
||||
.env
|
||||
config.json
|
||||
bundle.css
|
||||
@@ -7,6 +7,7 @@ RUN apk add --no-cache \
|
||||
yt-dlp \
|
||||
ffmpegthumbnailer \
|
||||
imagemagick \
|
||||
ghostscript \
|
||||
git \
|
||||
mailcap \
|
||||
file \
|
||||
|
||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
first things
|
||||
|
||||
`cp .env.example .env`
|
||||
|
||||
fill with for example: f0ckm
|
||||
|
||||
`cp config_example.json config.json`
|
||||
|
||||
Edit to needs, for sql you can do this:
|
||||
|
||||
host can either be localhost or the docker containers hostname, when running via docker this must be the dockers hostname
|
||||
|
||||
```
|
||||
"sql": {
|
||||
"host": "f0ckm-db",
|
||||
"port": 5432,
|
||||
"user": "f0ckm",
|
||||
"password": "f0ckm",
|
||||
"database": "f0ckm",
|
||||
"multipleStatements": true,
|
||||
"max": 50
|
||||
},
|
||||
```
|
||||
|
||||
`docker compose up -d`
|
||||
|
||||
`docker exec -i f0ckm-db psql -U f0ckm -d f0ckm < migrations/f0ckm_schema.sql`
|
||||
|
||||
`docker exec -t f0ckm node scripts/seed.mjs`
|
||||
|
||||
`docker exec -t f0ckm node scripts/create-admin.mjs admin [PASSWORD]`
|
||||
|
||||
now vist http://localhost:1337 in your browser
|
||||
@@ -20,24 +20,23 @@
|
||||
"development": true
|
||||
},
|
||||
"allowedModes": [ "sfw", "nsfw", "untagged", "all", "nsfl" ],
|
||||
"enable_pdf": true,
|
||||
"enable_nsfl": true,
|
||||
"nsfl_tag_id": 1234,
|
||||
"enable_pdf": false,
|
||||
"enable_nsfl": false,
|
||||
"nsfl_tag_id": 4,
|
||||
"allowedMimes": [
|
||||
"audio",
|
||||
"image",
|
||||
"video",
|
||||
"pdf"
|
||||
"video"
|
||||
],
|
||||
"nsfp": [
|
||||
1, 2, 3
|
||||
2,3,4
|
||||
],
|
||||
"websrv": {
|
||||
"port": "1337",
|
||||
"language": "en",
|
||||
"allow_language_change": true,
|
||||
"cache": false,
|
||||
"eps": 100,
|
||||
"eps": 155,
|
||||
"background": true,
|
||||
|
||||
"description": "Example Description",
|
||||
@@ -71,17 +70,17 @@
|
||||
|
||||
"embed_youtube_in_comments": true,
|
||||
"show_content_warning": true,
|
||||
"default_comment_display_mode": 0,
|
||||
"default_comment_display_mode": 1,
|
||||
"phrases": [
|
||||
"Hello World"
|
||||
],
|
||||
"ban_video": "/b/17fd9881.mp4",
|
||||
"enable_xd_score": true,
|
||||
"ban_video": "",
|
||||
"enable_xd_score": false,
|
||||
"enable_autoplay": false,
|
||||
"enable_swiping": true,
|
||||
"enable_profile_description": true,
|
||||
"use_ententeich": true,
|
||||
"enable_swf": true,
|
||||
"user_alternative_infobox": false,
|
||||
"enable_swf": false,
|
||||
"swf_thumb": "/s/img/swf.png",
|
||||
|
||||
"open_registration": true,
|
||||
|
||||
@@ -11,23 +11,27 @@ services:
|
||||
networks:
|
||||
- f0ckm-net
|
||||
volumes:
|
||||
- ./config.json:/opt/f0bm/config.json
|
||||
- ./f0ckm-data/a/:/opt/f0bm/public/a/
|
||||
- ./f0ckm-data/b/:/opt/f0bm/public/b/
|
||||
- ./f0ckm-data/t/:/opt/f0bm/public/t/
|
||||
- ./f0ckm-data/deleted/:/opt/f0bm/deleted/
|
||||
- ./f0ckm-data/pending/:/opt/f0bm/pending/
|
||||
- ./f0ckm-data/emojis/:/opt/f0bm/public/s/emojis/
|
||||
- ./f0ckm-data/memes/:/opt/f0bm/public/memes/
|
||||
- ./f0ckm-data/ca/:/opt/f0bm/public/ca/
|
||||
- ./f0ckm-data/tmp/:/opt/f0bm/tmp/
|
||||
- ./f0ckm-data/logs/:/opt/f0bm/logs/
|
||||
- ./f0ckm-data/tag_cache/:/opt/f0bm/public/tag_cache/
|
||||
- ./f0ckm-data/fonts/:/opt/f0bm/public/s/fonts/
|
||||
- ./f0ckm-data/hall_cache/:/opt/f0bm/public/hall_cache/
|
||||
- ./f0ckm-data/hall_custom/:/opt/f0bm/public/hall_custom/
|
||||
- ./f0ckm-data/manifest.json:/opt/f0bm/public/manifest.json
|
||||
- ./config.json:/opt/f0bm/config.json:Z
|
||||
- ./src/:/opt/f0bm/src/:Z
|
||||
- ./views/:/opt/f0bm/views/:Z
|
||||
- ./scripts/:/opt/f0bm/scripts/:Z
|
||||
- ./f0ckm-data/a/:/opt/f0bm/public/a/:Z
|
||||
- ./f0ckm-data/b/:/opt/f0bm/public/b/:Z
|
||||
- ./f0ckm-data/t/:/opt/f0bm/public/t/:Z
|
||||
- ./f0ckm-data/deleted/:/opt/f0bm/deleted/:Z
|
||||
- ./f0ckm-data/pending/:/opt/f0bm/pending/:Z
|
||||
- ./f0ckm-data/emojis/:/opt/f0bm/public/s/emojis/:Z
|
||||
- ./f0ckm-data/memes/:/opt/f0bm/public/memes/:Z
|
||||
- ./f0ckm-data/ca/:/opt/f0bm/public/ca/:Z
|
||||
- ./f0ckm-data/tmp/:/opt/f0bm/tmp/:Z
|
||||
- ./f0ckm-data/logs/:/opt/f0bm/logs/:Z
|
||||
- ./f0ckm-data/tag_cache/:/opt/f0bm/public/tag_cache/:Z
|
||||
- ./f0ckm-data/fonts/:/opt/f0bm/public/s/fonts/:Z
|
||||
- ./f0ckm-data/hall_cache/:/opt/f0bm/public/hall_cache/:Z
|
||||
- ./f0ckm-data/hall_custom/:/opt/f0bm/public/hall_custom/:Z
|
||||
- ./f0ckm-data/manifest.json:/opt/f0bm/public/manifest.json:Z
|
||||
|
||||
command: npm run dev
|
||||
environment:
|
||||
- GIT_HASH=${f0ckm_TAG:-unknown}
|
||||
ports:
|
||||
@@ -46,7 +50,7 @@ services:
|
||||
POSTGRES_PASSWORD: f0ckm
|
||||
PGDATA: /data/postgres
|
||||
volumes:
|
||||
- ./postgres:/data/postgres
|
||||
- ./postgres:/data/postgres:Z
|
||||
ports:
|
||||
- "5454:5432"
|
||||
networks:
|
||||
|
||||
11
package.json
11
package.json
@@ -1,18 +1,21 @@
|
||||
{
|
||||
"name": "f0ckv2",
|
||||
"version": "2.2.1",
|
||||
"description": "f0ck, kennste?",
|
||||
"name": "f0ckm",
|
||||
"version": "2.5.1",
|
||||
"description": "f0ck, kennste noch?",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node --trace-uncaught src/index.mjs",
|
||||
"dev": "node --trace-uncaught --watch src/index.mjs",
|
||||
"trigger": "node debug/trigger.mjs",
|
||||
"autotagger": "node debug/autotagger.mjs",
|
||||
"thumbnailer": "node debug/thumbnailer.mjs",
|
||||
"test": "node debug/test.mjs",
|
||||
"clean": "node debug/clean.mjs",
|
||||
"fix:deleted": "node debug/fix_deleted.mjs",
|
||||
"build": "node scripts/build-css.mjs"
|
||||
"build": "node scripts/build-css.mjs",
|
||||
"seed": "node scripts/seed.mjs",
|
||||
"create-admin": "node scripts/create-admin.mjs"
|
||||
},
|
||||
"author": "Flummi",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -43,7 +43,7 @@ html[theme='f0ck'] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #090909;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--footbar-color: #9f0;
|
||||
--loading-indicator-color: #9f0;
|
||||
@@ -108,7 +108,7 @@ html[theme='p1nk'] {
|
||||
--badge-tag: #090909;
|
||||
--metadata-bg: #2b2b2b;
|
||||
--posts-meta-bg: #000000b8;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--footbar-color: #ff00d0;
|
||||
--loading-indicator-color: #ff00d0;
|
||||
@@ -169,7 +169,7 @@ html[theme='orange'] {
|
||||
--badge-sfw: #68a728;
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--footbar-color: #ff6f00;
|
||||
--loading-indicator-color: #ff6f00;
|
||||
@@ -231,7 +231,7 @@ html[theme='amoled'] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #1a1a1a;
|
||||
--scrollbar-color: #1d1c1c;
|
||||
--scrollbar-color: #444;
|
||||
--scroller-bg: #424242;
|
||||
--footbar-color: #fff;
|
||||
--loading-indicator-color: #fff;
|
||||
@@ -512,7 +512,7 @@ html[theme="atmos"] {
|
||||
--badge-nsfw: #a72828;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #353535;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--footbar-color: #1fb2b0;
|
||||
--loading-indicator-color: #1fb2b0;
|
||||
--img-border-width: 0;
|
||||
@@ -572,7 +572,7 @@ html[theme="term"] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #131212;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--tooltip-bg: #131212;
|
||||
--footbar-color: #00DF00;
|
||||
@@ -649,7 +649,7 @@ html[theme="iced"] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #22083c;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--tooltip-bg: #0a3f53;
|
||||
--footbar-color: #0084ff;
|
||||
@@ -732,7 +732,7 @@ html[theme='f0ck95'] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #959393;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--footbar-color: #000;
|
||||
--loading-indicator-color: #000;
|
||||
@@ -855,7 +855,7 @@ html[theme="4d"] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #353535;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--footbar-color: #1fb2b0;
|
||||
--loading-indicator-color: #1fb2b0;
|
||||
--img-border-width: 0;
|
||||
@@ -902,7 +902,7 @@ html[theme="xd"] {
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-tag: #353535;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--footbar-color: #1fb2b0;
|
||||
--loading-indicator-color: #1fb2b0;
|
||||
--img-border-width: 0;
|
||||
@@ -2288,7 +2288,7 @@ body.layout-legacy .scroll-to-bottom svg {
|
||||
|
||||
.comments-list::-webkit-scrollbar-thumb {
|
||||
background: var(--gray);
|
||||
border-radius: 3px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.comment {
|
||||
@@ -2997,7 +2997,7 @@ html[theme='f0ck95d'] {
|
||||
--badge-sfw: teal;
|
||||
--badge-nsfw: #a72828;
|
||||
--badge-tag: #2f2f2f;
|
||||
--scrollbar-color: #2b2b2b;
|
||||
--scrollbar-color: #555;
|
||||
--scroller-bg: #424242;
|
||||
--footbar-color: #fff;
|
||||
--loading-indicator-color: #fff;
|
||||
@@ -3202,18 +3202,31 @@ html[res="fullscreen"] span#favs {
|
||||
}
|
||||
|
||||
|
||||
/* Global scrollbar rules */
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-color: var(--scrollbar-color) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
/* Linux Firefox "thin" is often too small and hard to see. Use auto there. */
|
||||
html.is-linux.is-firefox * {
|
||||
scrollbar-width: auto;
|
||||
}
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
@@ -3233,14 +3246,13 @@ html {
|
||||
background: var(--bg) !important;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background: transparent !important;
|
||||
color: var(--white);
|
||||
margin: 0;
|
||||
font-family: var(--font);
|
||||
line-height: 2;
|
||||
scrollbar-color: var(--scrollbar-color) transparent;
|
||||
scrollbar-width: thin;
|
||||
overflow-x: clip;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,15 @@ window.cancelAnimFrame = (function () {
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
// OS and Browser detection for CSS targeting
|
||||
const ua = navigator.userAgent;
|
||||
const htmlEl = document.documentElement;
|
||||
if (ua.includes('Linux')) htmlEl.classList.add('is-linux');
|
||||
if (ua.includes('Windows')) htmlEl.classList.add('is-windows');
|
||||
if (ua.includes('Firefox')) htmlEl.classList.add('is-firefox');
|
||||
if (ua.includes('Chrome')) htmlEl.classList.add('is-chrome');
|
||||
if (ua.includes('Safari') && !ua.includes('Chrome')) htmlEl.classList.add('is-safari');
|
||||
|
||||
window.updateVisitIndicators = () => {
|
||||
try {
|
||||
// View indicators and counters have been permanently removed as requested.
|
||||
|
||||
52
scripts/create-admin.mjs
Normal file
52
scripts/create-admin.mjs
Normal file
@@ -0,0 +1,52 @@
|
||||
import db from "../src/inc/sql.mjs";
|
||||
import lib from "../src/inc/lib.mjs";
|
||||
import cfg from "../src/inc/config.mjs";
|
||||
import { getDefaultLayout } from "../src/inc/settings.mjs";
|
||||
|
||||
const [username, password] = process.argv.slice(2);
|
||||
|
||||
if (!username || !password) {
|
||||
console.error("Usage: node scripts/create-admin.mjs <username> <password>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (password.length < 20) {
|
||||
console.error("Error: Password must be at least 20 characters long to meet system security requirements.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function createAdmin() {
|
||||
console.log(`--- Creating Admin User: ${username} ---`);
|
||||
|
||||
// Check if user exists
|
||||
const existing = await db`select id from "user" where "login" = ${username.toLowerCase()} or "user" = ${username}`;
|
||||
if (existing.length > 0) {
|
||||
console.error("Error: Username already taken.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hash = await lib.hash(password);
|
||||
const ts = ~~(Date.now() / 1e3);
|
||||
|
||||
try {
|
||||
const newUser = await db`
|
||||
insert into "user" ("login", "password", "user", "created_at", "admin", "is_moderator", "activated")
|
||||
values (${username.toLowerCase()}, ${hash}, ${username}, to_timestamp(${ts}), true, true, true)
|
||||
returning id
|
||||
`;
|
||||
const userId = newUser[0].id;
|
||||
|
||||
await db`
|
||||
insert into user_options (user_id, mode, theme, fullscreen, avatar, avatar_file, use_new_layout, disable_autoplay, disable_swiping)
|
||||
values (${userId}, 3, 'amoled', 0, null, 'default.png', ${getDefaultLayout() === 'modern'}, false, false)
|
||||
`;
|
||||
|
||||
console.log(`--- Admin User ${username} Created Successfully ---`);
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error("Error creating admin user:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
createAdmin();
|
||||
65
scripts/seed.mjs
Normal file
65
scripts/seed.mjs
Normal file
@@ -0,0 +1,65 @@
|
||||
import db from "../src/inc/sql.mjs";
|
||||
|
||||
const SETTINGS = [
|
||||
{ key: 'motd', value: 'Hello World!' },
|
||||
{ key: 'manual_approval', value: 'true' },
|
||||
{ key: 'min_tags', value: '3' },
|
||||
{ key: 'registration_open', value: 'true' },
|
||||
{ key: 'trusted_uploads', value: '0' },
|
||||
{ key: 'about_text', value: 'Check the README.md for more information.' },
|
||||
{ key: 'rules_text', value: '' },
|
||||
{ key: 'terms_text', value: '' }
|
||||
];
|
||||
|
||||
const TAGS = [
|
||||
{ id: 1, tag: 'sfw', normalized: 'sfw' },
|
||||
{ id: 2, tag: 'nsfw', normalized: 'nsfw' },
|
||||
{ id: 3, tag: 'nsfp', normalized: 'nsfp' },
|
||||
{ id: 4, tag: 'nsfl', normalized: 'nsfl' }
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
console.log('--- Starting Database Seed ---');
|
||||
|
||||
// Seed Site Settings
|
||||
console.log('Seeding site_settings...');
|
||||
for (const setting of SETTINGS) {
|
||||
await db`
|
||||
INSERT INTO site_settings (key, value)
|
||||
VALUES (${setting.key}, ${setting.value})
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
|
||||
`;
|
||||
console.log(` Set ${setting.key} = ${setting.value.substring(0, 30)}${setting.value.length > 30 ? '...' : ''}`);
|
||||
}
|
||||
|
||||
// Seed Tags
|
||||
console.log('Seeding tags...');
|
||||
for (const tag of TAGS) {
|
||||
if (tag.id) {
|
||||
// For protected tags with specific IDs, we use the ID
|
||||
await db`
|
||||
INSERT INTO tags (id, tag, normalized)
|
||||
VALUES (${tag.id}, ${tag.tag}, ${tag.normalized})
|
||||
ON CONFLICT (id) DO UPDATE SET tag = EXCLUDED.tag, normalized = EXCLUDED.normalized
|
||||
`;
|
||||
// Also ensure sequence is updated if we inserted specific IDs
|
||||
await db`SELECT setval('tags_id_seq', (SELECT MAX(id) FROM tags))`;
|
||||
} else {
|
||||
await db`
|
||||
INSERT INTO tags (tag, normalized)
|
||||
VALUES (${tag.tag}, ${tag.normalized})
|
||||
ON CONFLICT (tag) DO NOTHING
|
||||
`;
|
||||
}
|
||||
console.log(` Tag: ${tag.tag}`);
|
||||
}
|
||||
|
||||
console.log('--- Seed Completed Successfully ---');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
seed().catch(err => {
|
||||
console.error('--- Seed Failed ---');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -172,7 +172,19 @@ process.on('uncaughtException', err => {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle missing default avatar with a redirect to 404.gif
|
||||
app.use(async (req, res) => {
|
||||
if (req.url.pathname === '/a/default.png') {
|
||||
const defaultAvatar = path.join(cfg.paths.a, 'default.png');
|
||||
if (!fs.existsSync(defaultAvatar)) {
|
||||
res.writeHead(302, { 'Location': '/s/img/404.gif' }).end();
|
||||
req.url.pathname = '/default_avatar_redirect_bypass';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.use(async (req, res) => {
|
||||
// This can be used to annoy people on discord sending links to your site lmao, shouldnt be used though since it sucks ass
|
||||
// if (cfg.main.development && req.method === 'POST') console.error(`[BOOT] [DEBUG_POST] ${req.method} ${req.url.pathname}`);
|
||||
// const ua = (req.headers['user-agent'] || '').toLowerCase();
|
||||
// if (ua.includes('discordbot')) {
|
||||
|
||||
Reference in New Issue
Block a user