This commit is contained in:
+2
-1
@@ -11,7 +11,7 @@ LINKLOG_APP_NAME=LinkLog
|
||||
LINKLOG_VERSION=0.1.0
|
||||
LINKLOG_SECRET_KEY=replace-with-a-long-random-secret
|
||||
LINKLOG_TOKEN_EXPIRY_DAYS=30
|
||||
LINKLOG_PUBLIC_URL=http://localhost:8000
|
||||
LINKLOG_PUBLIC_URL=localhost
|
||||
LINKLOG_SMTP_HOST=
|
||||
LINKLOG_SMTP_PORT=587
|
||||
LINKLOG_SMTP_USERNAME=
|
||||
@@ -22,6 +22,7 @@ LINKLOG_EMAIL_VERIFICATION_EXPIRY_HOURS=24
|
||||
LINKLOG_PASSWORD_RESET_EXPIRY_HOURS=1
|
||||
LINKLOG_MASTODON_CLIENT_NAME=LinkLog
|
||||
LINKLOG_MASTODON_OAUTH_EXPIRY_MINUTES=10
|
||||
LINKLOG_LOG_LEVEL=INFO
|
||||
# Optional comma-separated override. Leave empty to use the built-in list.
|
||||
LINKLOG_TRACKING_PARAMS=
|
||||
# Keep the default path when using the named linklog_data volume.
|
||||
|
||||
@@ -175,7 +175,7 @@ The main configurable values are:
|
||||
| `LINKLOG_SECRET_KEY` | token signing/security secret | required in Docker |
|
||||
| `LINKLOG_DATABASE_PATH` | SQLite file path inside the container | `/app/backend/data/linklog.db` |
|
||||
| `LINKLOG_TOKEN_EXPIRY_DAYS` | access-token lifetime | `30` |
|
||||
| `LINKLOG_PUBLIC_URL` | Base URL used in email verification links | `http://localhost:8000` |
|
||||
| `LINKLOG_PUBLIC_URL` | Public hostname used by Traefik and expanded to a callback URL by the backend | `localhost` |
|
||||
| `LINKLOG_SMTP_HOST` | SMTP server hostname; empty disables delivery in local development | empty |
|
||||
| `LINKLOG_SMTP_PORT` | SMTP server port | `587` |
|
||||
| `LINKLOG_SMTP_USERNAME` | SMTP login username | empty |
|
||||
@@ -185,7 +185,6 @@ The main configurable values are:
|
||||
| `LINKLOG_EMAIL_VERIFICATION_EXPIRY_HOURS` | Verification-link lifetime | `24` |
|
||||
| `LINKLOG_PASSWORD_RESET_EXPIRY_HOURS` | Password-reset-link lifetime | `1` |
|
||||
| `LINKLOG_TRACKING_PARAMS` | comma-separated tracking parameters (stripped from logged URLs) | built-in list |
|
||||
| `TRAEFIK_HOST` | hostname routed by Traefik | `localhost` |
|
||||
| `APP_PORT` | direct host port for FastAPI | `8000` |
|
||||
|
||||
New users created by an administrator are email-unverified and cannot sign in until they follow the verification link sent to their address. The link is valid for `LINKLOG_EMAIL_VERIFICATION_EXPIRY_HOURS` hours and is handled by `/api/auth/verify-email`. Configure `LINKLOG_SMTP_HOST`, `LINKLOG_SMTP_FROM`, and the SMTP credentials for delivery; local development may leave the SMTP host empty, in which case accounts remain pending verification and no message is sent.
|
||||
@@ -231,7 +230,7 @@ After logging in, open <http://localhost:8000/profile>, enter the Mastodon insta
|
||||
- **Instance**: hostname or URL such as `mastodon.social` or `https://mastodon.social`
|
||||
- **Post prefix**: text placed immediately before the link; defaults to `From my #LinkLog: `
|
||||
|
||||
`LINKLOG_PUBLIC_URL` must be the URL users can reach for the OAuth callback. For local development it can remain `http://localhost:8000`; for a deployment it must be the public LinkLog URL. Existing manually entered access tokens remain compatible with the plugin configuration API.
|
||||
`LINKLOG_PUBLIC_URL` is the public hostname used by Traefik and the backend. Use `localhost` for local development or a hostname such as `linklog.kolkman.org` for a deployment. The backend adds `http://` for localhost and `https://` for other hostnames when constructing OAuth and email links. Existing manually entered access tokens remain compatible with the plugin configuration API.
|
||||
|
||||
LinkLog caches the OAuth application credentials per Mastodon server in the persistent SQLite `app_settings` table, so subsequent connections do not register a new application on every attempt. If the server rate-limits application registration, the profile page reports the upstream `429` response and the user can retry after the server's cooldown.
|
||||
|
||||
|
||||
@@ -511,3 +511,120 @@ Add the creation of an xpi bundle for the webextention to the Makefile
|
||||
|
||||
### Assistant outcome
|
||||
Added a portable `xpi` Makefile target that regenerates logo assets and packages the Firefox extension into `LinkLog.xpi`, excluding metadata and minified artifacts. Added README instructions, ignored the generated XPI, and verified the archive contents and integrity with `unzip -tq`.
|
||||
|
||||
### User
|
||||
Correct: A signed XPI will be generated manually and checked into the repository under XPI/signed
|
||||
|
||||
### Assistant outcome
|
||||
Defined the release workflow around a manually checked-in signed XPI and added version/update-manifest validation, Docker image publishing, and Gitea release asset handling.
|
||||
|
||||
### User
|
||||
The "data_collection_permissions" property is missing from the plugin
|
||||
|
||||
### Assistant outcome
|
||||
Added Firefox `data_collection_permissions` requiring `websiteActivity`, with no optional categories, and enforced the declaration in release validation.
|
||||
|
||||
### User
|
||||
Validate the XPI during the make process of the unsigned xpi
|
||||
|
||||
### Assistant outcome
|
||||
Added unsigned-XPI validation to the Makefile. The validator checks ZIP integrity, required files, macOS metadata, and exact manifest parity.
|
||||
|
||||
### User
|
||||
pressing remove still does not remove a user.
|
||||
|
||||
### Assistant outcome
|
||||
Fixed admin user removal behavior, added server-side self-protection, improved browser error reporting, and updated the admin cache-busting version.
|
||||
|
||||
### User
|
||||
Trying to remove user alice: Could not remove alice: Internal Server Error
|
||||
|
||||
### Assistant outcome
|
||||
Found the SQLite foreign-key failure caused by Alice-owned tokens, links, and plugin configuration. User deletion now removes dependent records transactionally before deleting the user. Full validation passed.
|
||||
|
||||
### User
|
||||
Create a Gitea Action for every update to main that creates a Docker image tagged development
|
||||
|
||||
### Assistant outcome
|
||||
Added `.gitea/workflows/development.yml`, publishing `git.kolkman.org/olaf/link-log:development` on every push to `main`, with manual dispatch support.
|
||||
|
||||
### User
|
||||
Use an access token to publish to registry, not username and password
|
||||
|
||||
### Assistant outcome
|
||||
Changed release and development workflows to use `REGISTRY_TOKEN` as the Docker registry credential while retaining `REGISTRY_USERNAME` as the login identity.
|
||||
|
||||
### User
|
||||
Add SMTP capabilities and email validation links
|
||||
|
||||
### Assistant outcome
|
||||
Added SMTP delivery, email verification tokens, verification-link handling, and environment configuration for SMTP.
|
||||
|
||||
### User
|
||||
Do not configure default users at bootstrap. Present a configuration page when no administrator exists.
|
||||
|
||||
### Assistant outcome
|
||||
Removed default-user bootstrap, added a first-run setup page, persisted SMTP settings, created the first administrator through setup, and added an SMTP test-mail step.
|
||||
|
||||
### User
|
||||
Separate saving configuration from sending test mail, allow five sends with 20-second intervals, then hold for two minutes with a visual timer.
|
||||
|
||||
### Assistant outcome
|
||||
Split setup into Save configuration, Send test mail, and Complete setup actions. Added persistent server-side throttling, reload-safe countdown state, a five-send limit, and a two-minute cooldown.
|
||||
|
||||
### User
|
||||
Use `.env` defaults during initial configuration.
|
||||
|
||||
### Assistant outcome
|
||||
The setup status endpoint now exposes effective environment SMTP defaults, and the setup form populates blank fields from them. Persisted setup values take precedence after saving.
|
||||
|
||||
### User
|
||||
Use OAuth to register with the Mastodon account and obtain access to post.
|
||||
|
||||
### Assistant outcome
|
||||
Added Mastodon OAuth app registration, authorization and callback endpoints, one-time OAuth state storage, and per-user access-token persistence.
|
||||
|
||||
### User
|
||||
Allow the user to enter the Mastodon server to authenticate to.
|
||||
|
||||
### Assistant outcome
|
||||
The profile now requires an explicit Mastodon server, saves that server before OAuth, and authenticates against the selected instance.
|
||||
|
||||
### User
|
||||
Clicking authenticate with this server for social.secret-wg.org generates a 502 error.
|
||||
|
||||
### Assistant outcome
|
||||
Confirmed the Mastodon instance was returning HTTP 429 during app registration. OAuth app credentials are now cached per instance, and upstream rate-limit responses are passed through with retry information instead of being reported as generic 502 errors.
|
||||
|
||||
### User
|
||||
When a password is mistyped, send a password reset link.
|
||||
|
||||
### Assistant outcome
|
||||
Added hashed, expiring, single-use password-reset tokens, SMTP reset mail, a reset-password page, generic login errors, and session revocation after reset.
|
||||
|
||||
### User
|
||||
Log the unlogged chat and prompt in the VIBE directory.
|
||||
|
||||
### Assistant outcome
|
||||
Appended the previously unlogged feature work and this request to `VIBE/PROMPTS.md` and `VIBE/CHAT_LOG.md`.
|
||||
|
||||
### User
|
||||
Continue to log prompts and chats in VIBE
|
||||
|
||||
### User
|
||||
Use the VIBE directory in the current workspace.
|
||||
|
||||
### Assistant outcome
|
||||
Confirmed the current workspace is `/home/olaf/LinkLog_Dev` and appended both requests and this outcome to the VIBE logs located at `VIBE/PROMPTS.md` and `VIBE/CHAT_LOG.md`.
|
||||
|
||||
### User
|
||||
Posting to Mastodon seems to fail, try to fix, add debug logging.
|
||||
|
||||
### Assistant outcome
|
||||
Changed Mastodon status publishing to form-encoded API requests, added safe configurable logging for skipped, successful, and failed posts, included upstream HTTP response details without logging access tokens, and wired `LINKLOG_LOG_LEVEL` through the environment and Compose configuration. Updated the posting regression test. The focused test and complete Dockerized backend suite passed with 22 tests.
|
||||
|
||||
### User
|
||||
`LINKLOG_PUBLIC_URL` and `TRAEFIK_HOST` are the same and can be merged. Use `LINKLOG_PUBLIC_URL` in Docker Compose.
|
||||
|
||||
### Assistant outcome
|
||||
Removed the duplicate `TRAEFIK_HOST` configuration, changed both Traefik router rules to use `LINKLOG_PUBLIC_URL`, standardized the value as a host name, and normalized scheme-less values to valid HTTP/HTTPS backend callback URLs. Compose validation, URL normalization, and the complete Dockerized backend suite passed with 22 tests.
|
||||
|
||||
@@ -88,6 +88,33 @@
|
||||
84. Add the version to the name of the resulting xpi
|
||||
85. Set the version number of the backend to 0.1.0
|
||||
84. Add the creation of an xpi bundle for the webextention to the Makefile
|
||||
85. Correct: A signed XPI will be generated manually and checked into the repository under XPI/signed
|
||||
86. The "data_collection_permissions" property is missing from the plugin
|
||||
87. Validate the XPI during the make process of the unsigned xpi
|
||||
88. pressing remove still does not remove a user.
|
||||
89. Trying to remove user alice: Could not remove alice: Internal Server Error
|
||||
90. create a gitea action that is ran every time main is updated and that creates a docker image tagged development
|
||||
91. Use an access token to publish to registry, not username and password
|
||||
92. Add SMTP capabilities to the backend. Use it to validate the email addresses using a validation link in mail
|
||||
93. Do not configure default users at bootstrap. Instead present a configuration page (only present if no administrator is configured). The configuration page asks for the admin users credetials and allows to configure the SMTP settings and sends a test mail after configuration
|
||||
94. After saving the configuration - add link to home page together with "LinkLog is configured and the SMTP test mail was sent."
|
||||
95. In the confiuration seperate the safe and send mail functionality. Allow the user to resend test mail 5 times with a 20 seconds interval and then hold back for 2 minutes - show a visual timer counting down.
|
||||
96. when running the initial config use the defaults from the .env file when available
|
||||
97. Use oauth to register with the mastodon account and obtain access to post
|
||||
98. Allow user to enter the mastodon server to authenticate to
|
||||
99. Clicking authenticate with this server (social.secret-wg.org) generates 502 error
|
||||
100. Log the unlogged chat and promt in the VIBE directory
|
||||
101. when password is mistyped send a password reset link
|
||||
102. when running the initial config use the defaults from the .env file when available
|
||||
103. After saving the configuration - add link to home page together with "LinkLog is configured and the SMTP test mail was sent."
|
||||
104. In the confiuration seperate the safe and send mail functionality. Allow the user to resend test mail 5 times with a 20 seconds interval and then hold back for 2 minutes - show a visual timer counting down.
|
||||
105. Use oauth to register with the mastodon account and obtain access to post
|
||||
106. Allow user to enter the mastodon server to authenticate to
|
||||
107. Log the unlogged chat and promt in the VIBE directory
|
||||
108. Continue to log prompts and chats in VIBE
|
||||
109. use VIVE that is in the current directory
|
||||
110. Posting to mastodon seems to fail, try to fix, add debug logging
|
||||
111. LINKLOG_PUBLIC_URL and TRAEFIK_HOST are the same and can be merged. (use LINKLOG_PUBLIC_URL), fix docker-compose to use said variable
|
||||
|
||||
## Future entries
|
||||
|
||||
|
||||
+682583
File diff suppressed because one or more lines are too long
@@ -2,6 +2,7 @@
|
||||
## SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from fastapi import APIRouter, Header, HTTPException, status
|
||||
import logging
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.app.services.link_service import create_link, list_public_links, list_tags, update_link
|
||||
@@ -9,6 +10,7 @@ from backend.app.services.plugin_manager import plugin_manager
|
||||
from backend.app.services.token_service import validate_token
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinkCreate(BaseModel):
|
||||
@@ -44,7 +46,9 @@ def create_link_endpoint(payload: LinkCreate, authorization: str | None = Header
|
||||
record = create_link(info['user_id'], payload.title, payload.url, payload.comment, payload.timestamp, payload.tags)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=422, detail=str(error)) from error
|
||||
plugin_manager.dispatch({'type': 'link_created', **record})
|
||||
plugin_results = plugin_manager.dispatch({'type': 'link_created', **record})
|
||||
if any(result.get('status') == 'failed' for result in plugin_results):
|
||||
logger.warning('One or more plugins failed for link_id=%s results=%s', record['id'], plugin_results)
|
||||
return record
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,14 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
DB_PATH = BASE_DIR / 'data' / 'linklog.db'
|
||||
|
||||
|
||||
def normalize_public_url(value: str) -> str:
|
||||
value = value.strip().rstrip('/')
|
||||
if '://' in value:
|
||||
return value
|
||||
scheme = 'http' if value.startswith(('localhost', '127.0.0.1')) else 'https'
|
||||
return f'{scheme}://{value}'
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
app_name: str = os.getenv('LINKLOG_APP_NAME', 'LinkLog')
|
||||
@@ -17,7 +25,7 @@ class Settings:
|
||||
database_url: str = os.getenv('LINKLOG_DATABASE_URL', f'sqlite:///{DB_PATH}')
|
||||
secret_key: str = os.getenv('LINKLOG_SECRET_KEY', 'dev-secret-key-change-me')
|
||||
token_expiry_days: int = int(os.getenv('LINKLOG_TOKEN_EXPIRY_DAYS', '30'))
|
||||
public_url: str = os.getenv('LINKLOG_PUBLIC_URL', 'http://localhost:8000').rstrip('/')
|
||||
public_url: str = normalize_public_url(os.getenv('LINKLOG_PUBLIC_URL', 'http://localhost:8000'))
|
||||
smtp_host: str = os.getenv('LINKLOG_SMTP_HOST', '')
|
||||
smtp_port: int = int(os.getenv('LINKLOG_SMTP_PORT', '587'))
|
||||
smtp_username: str = os.getenv('LINKLOG_SMTP_USERNAME', '')
|
||||
@@ -28,6 +36,7 @@ class Settings:
|
||||
password_reset_expiry_hours: int = int(os.getenv('LINKLOG_PASSWORD_RESET_EXPIRY_HOURS', '1'))
|
||||
mastodon_client_name: str = os.getenv('LINKLOG_MASTODON_CLIENT_NAME', 'LinkLog')
|
||||
mastodon_oauth_expiry_minutes: int = int(os.getenv('LINKLOG_MASTODON_OAUTH_EXPIRY_MINUTES', '10'))
|
||||
log_level: str = os.getenv('LINKLOG_LOG_LEVEL', 'INFO').upper()
|
||||
tracking_params: list[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from fastapi import FastAPI
|
||||
import logging
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -20,6 +21,8 @@ from backend.app.core.config import settings
|
||||
from backend.app.database import AVATARS_DIR
|
||||
from backend.app.services.link_service import list_public_links
|
||||
|
||||
logging.basicConfig(level=getattr(logging, settings.log_level, logging.INFO))
|
||||
|
||||
app = FastAPI(title='LinkLog API', version=settings.version)
|
||||
app.mount('/static', StaticFiles(directory='frontend/static'), name='static')
|
||||
app.mount('/media', StaticFiles(directory=AVATARS_DIR), name='media')
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
## SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
import logging
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from backend.app.plugins.base import BasePlugin
|
||||
|
||||
DEFAULT_POST_PREFIX = 'From my #LinkLog: '
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultFrontendPlugin(BasePlugin):
|
||||
@@ -50,6 +53,10 @@ class MastodonPlugin(BasePlugin):
|
||||
instance = f'https://{instance}'
|
||||
access_token = str(config.get('access_token', '')).strip()
|
||||
if not instance or not access_token:
|
||||
logger.debug(
|
||||
'Mastodon post skipped: instance_configured=%s token_configured=%s user_id=%s',
|
||||
bool(instance), bool(access_token), user_id,
|
||||
)
|
||||
return {'status': 'skipped', 'plugin': self.name, 'reason': 'not_configured'}
|
||||
|
||||
status_parts = [event.get('title') or event.get('url', '')]
|
||||
@@ -63,25 +70,51 @@ class MastodonPlugin(BasePlugin):
|
||||
if event.get('tags'):
|
||||
status = f'{status} {" ".join(event["tags"])}'
|
||||
status_parts.append(status)
|
||||
post_body = '\n'.join(status_parts)
|
||||
endpoint = f'{instance}/api/v1/statuses'
|
||||
logger.debug(
|
||||
'Posting link to Mastodon: endpoint=%s user_id=%s event_id=%s body_length=%d',
|
||||
endpoint, user_id, event.get('id'), len(post_body),
|
||||
)
|
||||
|
||||
try:
|
||||
request = Request(
|
||||
f'{instance}/api/v1/statuses',
|
||||
data=json.dumps({'status': '\n'.join(status_parts)}).encode('utf-8'),
|
||||
endpoint,
|
||||
data=urlencode({'status': post_body}).encode('utf-8'),
|
||||
headers={
|
||||
'Authorization': f'Bearer {access_token}',
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'LinkLog/1.0',
|
||||
},
|
||||
method='POST',
|
||||
)
|
||||
with urlopen(request, timeout=5) as response:
|
||||
response_data = json.loads(response.read().decode('utf-8'))
|
||||
response_body = response.read().decode('utf-8')
|
||||
logger.debug(
|
||||
'Mastodon post response: endpoint=%s status=%s body_length=%d',
|
||||
endpoint, response.status, len(response_body),
|
||||
)
|
||||
response_data = json.loads(response_body)
|
||||
logger.info('Mastodon post succeeded: instance=%s user_id=%s post_id=%s', instance, user_id, response_data.get('id'))
|
||||
return {
|
||||
'status': 'posted',
|
||||
'plugin': self.name,
|
||||
'post_id': response_data.get('id'),
|
||||
}
|
||||
except (HTTPError, URLError, TimeoutError, OSError, ValueError) as error:
|
||||
except HTTPError as error:
|
||||
response_body = error.read().decode('utf-8', errors='replace')
|
||||
logger.warning(
|
||||
'Mastodon post failed: endpoint=%s user_id=%s status=%s response=%s',
|
||||
endpoint, user_id, error.code, response_body[:500],
|
||||
)
|
||||
return {
|
||||
'status': 'failed',
|
||||
'plugin': self.name,
|
||||
'reason': f'HTTP {error.code}: {response_body[:500]}',
|
||||
}
|
||||
except (URLError, TimeoutError, OSError, ValueError) as error:
|
||||
logger.exception('Mastodon post failed: endpoint=%s user_id=%s error=%s', endpoint, user_id, error)
|
||||
return {
|
||||
'status': 'failed',
|
||||
'plugin': self.name,
|
||||
@@ -111,7 +144,9 @@ class PluginManager:
|
||||
results = []
|
||||
for plugin in self.plugins:
|
||||
if plugin.enabled:
|
||||
results.append(plugin.handle_event(event))
|
||||
result = plugin.handle_event(event)
|
||||
results.append(result)
|
||||
logger.debug('Plugin dispatch result: plugin=%s event_id=%s result=%s', plugin.name, event.get('id'), result)
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import json
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from urllib.parse import parse_qs
|
||||
from uuid import uuid4
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -410,7 +411,8 @@ def test_link_submission_posts_to_enabled_mastodon_plugin():
|
||||
def do_POST(self):
|
||||
received['path'] = self.path
|
||||
received['authorization'] = self.headers['Authorization']
|
||||
received['body'] = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
|
||||
received['content_type'] = self.headers['Content-Type']
|
||||
received['body'] = parse_qs(self.rfile.read(int(self.headers['Content-Length'])).decode())
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
@@ -442,9 +444,8 @@ def test_link_submission_posts_to_enabled_mastodon_plugin():
|
||||
assert response.status_code == 201
|
||||
assert received['path'] == '/api/v1/statuses'
|
||||
assert received['authorization'] == 'Bearer test-token'
|
||||
assert received['body'] == {
|
||||
'status': 'A useful page\nWorth sharing\nFrom my #LinkLog: https://example.com/useful #python #web',
|
||||
}
|
||||
assert received['content_type'] == 'application/x-www-form-urlencoded'
|
||||
assert received['body'] == {'status': ['A useful page\nWorth sharing\nFrom my #LinkLog: https://example.com/useful #python #web']}
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
LINKLOG_DATABASE_PATH: ${LINKLOG_DATABASE_PATH:-/app/backend/data/linklog.db}
|
||||
LINKLOG_SECRET_KEY: ${LINKLOG_SECRET_KEY:?Set LINKLOG_SECRET_KEY in .env}
|
||||
LINKLOG_TOKEN_EXPIRY_DAYS: ${LINKLOG_TOKEN_EXPIRY_DAYS:-30}
|
||||
LINKLOG_PUBLIC_URL: ${LINKLOG_PUBLIC_URL:-https://linklog.example.com}
|
||||
LINKLOG_PUBLIC_URL: ${LINKLOG_PUBLIC_URL:-linklog.example.com}
|
||||
LINKLOG_SMTP_HOST: ${LINKLOG_SMTP_HOST:-smtp.example.com}
|
||||
LINKLOG_SMTP_PORT: ${LINKLOG_SMTP_PORT:-587}
|
||||
LINKLOG_SMTP_USERNAME: ${LINKLOG_SMTP_USERNAME:?Set LINKLOG_SMTP_USERNAME in .env}
|
||||
@@ -25,6 +25,7 @@ services:
|
||||
LINKLOG_PASSWORD_RESET_EXPIRY_HOURS: ${LINKLOG_PASSWORD_RESET_EXPIRY_HOURS:-1}
|
||||
LINKLOG_MASTODON_CLIENT_NAME: ${LINKLOG_MASTODON_CLIENT_NAME:-LinkLog}
|
||||
LINKLOG_MASTODON_OAUTH_EXPIRY_MINUTES: ${LINKLOG_MASTODON_OAUTH_EXPIRY_MINUTES:-10}
|
||||
LINKLOG_LOG_LEVEL: ${LINKLOG_LOG_LEVEL:-INFO}
|
||||
LINKLOG_TRACKING_PARAMS: ${LINKLOG_TRACKING_PARAMS:-}
|
||||
restart: ${APP_RESTART_POLICY:-unless-stopped}
|
||||
healthcheck:
|
||||
@@ -41,10 +42,10 @@ services:
|
||||
|
||||
|
||||
traefik.http.routers.linklog.entrypoints: web
|
||||
traefik.http.routers.linklog.rule: Host(`linklog.example.com`) # Make sure to change
|
||||
traefik.http.routers.linklog.rule: Host(`${LINKLOG_PUBLIC_URL:-linklog.example.com}`) # Make sure to change
|
||||
traefik.http.routers.linklog.middlewares: web-https-redirect,servicests # these middlewares must exist
|
||||
traefik.http.routers.linklog-secure.entrypoints: websecure
|
||||
traefik.http.routers.linklog-secure.rule: Host(`linklog.example.com`) #Make sure to change
|
||||
traefik.http.routers.linklog-secure.rule: Host(`${LINKLOG_PUBLIC_URL:-linklog.example.com}`) #Make sure to change
|
||||
traefik.http.routers.linklog-secure.tls: true
|
||||
traefik.http.routers.linklog-secure.middlewares: servicests
|
||||
|
||||
|
||||
+32
-16
@@ -10,24 +10,15 @@ services:
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:8000"
|
||||
volumes:
|
||||
- linklog_data:/app/backend/data
|
||||
- ./linklog_data:/app/backend/data
|
||||
environment:
|
||||
APP_ENV: ${APP_ENV:-production}
|
||||
LINKLOG_APP_NAME: ${LINKLOG_APP_NAME:-LinkLog}
|
||||
LINKLOG_DATABASE_PATH: ${LINKLOG_DATABASE_PATH:-/app/backend/data/linklog.db}
|
||||
LINKLOG_SECRET_KEY: ${LINKLOG_SECRET_KEY:?Set LINKLOG_SECRET_KEY in .env}
|
||||
LINKLOG_PUBLIC_URL: ${LINKLOG_PUBLIC_URL:-localhost}
|
||||
LINKLOG_LOG_LEVEL: ${LINKLOG_LOG_LEVEL:-INFO}
|
||||
LINKLOG_TOKEN_EXPIRY_DAYS: ${LINKLOG_TOKEN_EXPIRY_DAYS:-30}
|
||||
LINKLOG_PUBLIC_URL: ${LINKLOG_PUBLIC_URL:-http://localhost:8000}
|
||||
LINKLOG_SMTP_HOST: ${LINKLOG_SMTP_HOST:-}
|
||||
LINKLOG_SMTP_PORT: ${LINKLOG_SMTP_PORT:-587}
|
||||
LINKLOG_SMTP_USERNAME: ${LINKLOG_SMTP_USERNAME:-}
|
||||
LINKLOG_SMTP_PASSWORD: ${LINKLOG_SMTP_PASSWORD:-}
|
||||
LINKLOG_SMTP_FROM: ${LINKLOG_SMTP_FROM:-LinkLog <no-reply@localhost>}
|
||||
LINKLOG_SMTP_USE_TLS: ${LINKLOG_SMTP_USE_TLS:-true}
|
||||
LINKLOG_EMAIL_VERIFICATION_EXPIRY_HOURS: ${LINKLOG_EMAIL_VERIFICATION_EXPIRY_HOURS:-24}
|
||||
LINKLOG_PASSWORD_RESET_EXPIRY_HOURS: ${LINKLOG_PASSWORD_RESET_EXPIRY_HOURS:-1}
|
||||
LINKLOG_MASTODON_CLIENT_NAME: ${LINKLOG_MASTODON_CLIENT_NAME:-LinkLog}
|
||||
LINKLOG_MASTODON_OAUTH_EXPIRY_MINUTES: ${LINKLOG_MASTODON_OAUTH_EXPIRY_MINUTES:-10}
|
||||
LINKLOG_TRACKING_PARAMS: ${LINKLOG_TRACKING_PARAMS:-}
|
||||
restart: ${APP_RESTART_POLICY:-unless-stopped}
|
||||
healthcheck:
|
||||
@@ -36,7 +27,32 @@ services:
|
||||
timeout: ${APP_HEALTHCHECK_TIMEOUT:-5s}
|
||||
start_period: ${APP_HEALTHCHECK_START_PERIOD:-10s}
|
||||
retries: ${APP_HEALTHCHECK_RETRIES:-3}
|
||||
|
||||
|
||||
volumes:
|
||||
linklog_data:
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.middlewares.web-https-redirect.redirectscheme.scheme: https
|
||||
traefik.http.services.linklog.loadbalancer.server.port: 8000
|
||||
traefik.docker.network: git_traefik
|
||||
|
||||
|
||||
traefik.http.routers.linklog.entrypoints: web
|
||||
traefik.http.routers.linklog.rule: Host(`${LINKLOG_PUBLIC_URL:-localhost}`)
|
||||
traefik.http.routers.linklog.middlewares: web-https-redirect,servicests
|
||||
traefik.http.routers.linklog-secure.entrypoints: websecure
|
||||
traefik.http.routers.linklog-secure.rule: Host(`${LINKLOG_PUBLIC_URL:-localhost}`)
|
||||
traefik.http.routers.linklog-secure.tls: true
|
||||
traefik.http.routers.linklog-secure.middlewares: servicests
|
||||
|
||||
|
||||
traefik.http.routers.linklog-secure.tls.certresolver: myresolver
|
||||
traefik.http.routers.linklog-secure.service: linklog
|
||||
|
||||
|
||||
|
||||
|
||||
networks:
|
||||
- linklog_traefik
|
||||
|
||||
networks:
|
||||
linklog_traefik:
|
||||
external: true
|
||||
name: linklog_traefik
|
||||
|
||||
Reference in New Issue
Block a user