mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-14 11:06:13 +05:00
Guide first-run setup through the web UI
This commit is contained in:
parent
11c23281c1
commit
220e4322dc
24
README.md
24
README.md
@ -73,7 +73,7 @@ mkdir -p ~/archivebox/data && cd ~/archivebox
|
||||
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
|
||||
docker compose pull
|
||||
docker compose up -d --wait # initializes new collections automatically
|
||||
docker compose exec archivebox archivebox manage createsuperuser # create the first Web UI user
|
||||
# open http://admin.archivebox.localhost:8000 to finish setup
|
||||
# docker compose run --rm archivebox add 'https://example.com'
|
||||
# docker compose run --rm archivebox help
|
||||
<br/>
|
||||
@ -82,8 +82,8 @@ docker compose exec archivebox archivebox manage createsuperuser # crea
|
||||
mkdir -p ~/archivebox/data && cd ~/archivebox/data
|
||||
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev init
|
||||
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev install
|
||||
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev manage createsuperuser
|
||||
docker run -d --name archivebox -v "$PWD:/data" -p 8000:8000 archivebox/archivebox:dev
|
||||
# open http://admin.archivebox.localhost:8000 to finish setup
|
||||
# docker run -it -v $PWD:/data archivebox/archivebox:dev add 'https://example.com'
|
||||
# docker run -it -v $PWD:/data archivebox/archivebox:dev help
|
||||
<br/>
|
||||
@ -186,11 +186,10 @@ ArchiveBox is free for everyone to self-host, but we also provide support, secur
|
||||
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
|
||||
docker compose pull
|
||||
</code></pre></li>
|
||||
<li>Start the server, which initializes a new collection automatically, then create the first admin user.
|
||||
<li>Start the server, which initializes a new collection automatically.
|
||||
<pre lang="bash"><code style="white-space: pre-line">docker compose up -d --wait
|
||||
docker compose exec archivebox archivebox manage createsuperuser
|
||||
</code></pre></li>
|
||||
<li>Next steps: Log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
|
||||
<li>Open <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a> and follow the setup wizard to create the first admin and configure web access.
|
||||
<pre lang="bash"><code style="white-space: pre-line">
|
||||
# run CLI commands inside the server container started above
|
||||
docker compose exec archivebox archivebox add 'https://example.com'
|
||||
@ -271,9 +270,8 @@ archivebox init # initialize a new collection
|
||||
archivebox install # install all the runtime dependencies (e.g. chrome, single-file, yt-dlp, etc.)
|
||||
</code></pre>
|
||||
</li>
|
||||
<li>Create an admin account, then optionally start the server and log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
|
||||
<pre lang="bash"><code style="white-space: pre-line">archivebox manage createsuperuser
|
||||
archivebox server 0.0.0.0:8000
|
||||
<li>Optionally start the server, then open the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a> and follow the same setup wizard used by Docker installs.
|
||||
<pre lang="bash"><code style="white-space: pre-line">archivebox server 0.0.0.0:8000
|
||||
# completely optional, CLI can always be used without running a server
|
||||
# archivebox [subcommand] [--help]
|
||||
archivebox help
|
||||
@ -308,9 +306,8 @@ archivebox add 'https://example.com'
|
||||
</code></pre>
|
||||
<br/>
|
||||
</li>
|
||||
<li>Create an admin account, then optionally start the server and log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
|
||||
<pre lang="bash"><code style="white-space: pre-line">archivebox manage createsuperuser
|
||||
archivebox server 0.0.0.0:8000
|
||||
<li>Optionally start the server, then open the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a> and follow the same setup wizard used by Docker installs.
|
||||
<pre lang="bash"><code style="white-space: pre-line">archivebox server 0.0.0.0:8000
|
||||
# completely optional, CLI can always be used without running a server
|
||||
# archivebox [subcommand] [--help]
|
||||
archivebox help
|
||||
@ -342,9 +339,8 @@ archivebox init
|
||||
archivebox install
|
||||
</code></pre>
|
||||
</li>
|
||||
<li>Create an admin account, then optionally start the server and log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
|
||||
<pre lang="bash"><code style="white-space: pre-line">archivebox manage createsuperuser
|
||||
archivebox server 0.0.0.0:8000
|
||||
<li>Optionally start the server, then open the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a> and follow the same setup wizard used by Docker installs.
|
||||
<pre lang="bash"><code style="white-space: pre-line">archivebox server 0.0.0.0:8000
|
||||
# completely optional, CLI can always be used without running a server
|
||||
# archivebox [subcommand] [--help]
|
||||
archivebox help
|
||||
|
||||
@ -12,11 +12,13 @@ from admin_data_views.admin import (
|
||||
get_app_list as adv_get_app_list,
|
||||
)
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model, login as auth_login
|
||||
from django.contrib.auth.decorators import login_not_required
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.db import DatabaseError, connection
|
||||
from django.db import DatabaseError, connection, transaction
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import gettext as _
|
||||
@ -103,12 +105,15 @@ class ArchiveBoxAdmin(admin.AdminSite):
|
||||
|
||||
from django.contrib.admin.forms import AdminAuthenticationForm
|
||||
|
||||
User = get_user_model()
|
||||
first_admin_setup = not User.objects.filter(is_superuser=True).exclude(username="system").exists()
|
||||
context = {
|
||||
**self.each_context(request),
|
||||
"title": _("Log in"),
|
||||
"title": _("Set up ArchiveBox") if first_admin_setup else _("Log in"),
|
||||
"subtitle": None,
|
||||
"app_path": request.get_full_path(),
|
||||
"username": request.user.get_username(),
|
||||
"first_admin_setup": first_admin_setup,
|
||||
}
|
||||
if REDIRECT_FIELD_NAME not in request.GET and REDIRECT_FIELD_NAME not in request.POST:
|
||||
context[REDIRECT_FIELD_NAME] = reverse("admin:index", current_app=self.name)
|
||||
@ -116,6 +121,30 @@ class ArchiveBoxAdmin(admin.AdminSite):
|
||||
|
||||
index_path = reverse("admin:index", current_app=self.name)
|
||||
request.current_app = self.name
|
||||
if first_admin_setup:
|
||||
form = UserCreationForm(request.POST or None)
|
||||
if request.method == "POST" and form.is_valid():
|
||||
with transaction.atomic():
|
||||
# Fresh collections contain the system superuser, which also
|
||||
# provides a row lock while the first real admin is created.
|
||||
list(User.objects.select_for_update().filter(is_superuser=True).values_list("pk", flat=True))
|
||||
if User.objects.filter(is_superuser=True).exclude(username="system").exists():
|
||||
form.add_error(None, _("An admin account was already created. Log in instead."))
|
||||
else:
|
||||
user = form.save(commit=False)
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
auth_login(request, user, backend="django.contrib.auth.backends.ModelBackend")
|
||||
return HttpResponseRedirect(index_path)
|
||||
|
||||
context["form"] = form
|
||||
return TemplateResponse(
|
||||
request,
|
||||
self.login_template or "admin/login.html",
|
||||
context,
|
||||
)
|
||||
|
||||
return ArchiveBoxLoginView.as_view(
|
||||
extra_context=context,
|
||||
authentication_form=self.login_form or AdminAuthenticationForm,
|
||||
|
||||
@ -438,16 +438,6 @@ def url_replace(context, **kwargs):
|
||||
return dict_.urlencode()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def has_real_admin_users() -> bool:
|
||||
"""True if any non-``system`` superuser exists. Used by the login page to
|
||||
only show the bootstrap hint (createsuperuser / ADMIN_USERNAME env vars)
|
||||
when the collection still has no real admin."""
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
return User.objects.filter(is_superuser=True).exclude(username="system").exists()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def admin_base_url(context) -> str:
|
||||
return get_admin_base_url(request=context.get("request"), config=context.get("CONFIG"))
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n static core_tags %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/login.css" %}">
|
||||
{{ form.media }}
|
||||
@ -18,7 +18,11 @@
|
||||
|
||||
{% block content_title %}
|
||||
<center>
|
||||
{% if first_admin_setup %}
|
||||
Create your first admin account to continue setup.
|
||||
{% else %}
|
||||
Log in to add, edit, and remove links from your archive.
|
||||
{% endif %}
|
||||
</center><br/><br/>
|
||||
<img src="{% static 'archive.png' %}" style="width: 80px; display: block; margin: auto"/><br/>
|
||||
{% endblock %}
|
||||
@ -42,6 +46,28 @@
|
||||
|
||||
<div id="content-main">
|
||||
|
||||
{% if first_admin_setup %}
|
||||
<form action="{{ app_path }}" method="post" id="first-admin-form">{% csrf_token %}
|
||||
<input type="hidden" name="create_first_admin" value="1">
|
||||
<div class="form-row" style="gap: 0;">
|
||||
{{ form.username.errors }}
|
||||
{{ form.username.label_tag }} {{ form.username }}
|
||||
</div>
|
||||
<div class="form-row" style="gap: 0;">
|
||||
{{ form.password1.errors }}
|
||||
{{ form.password1.label_tag }} {{ form.password1 }}
|
||||
{% if form.password1.help_text %}<div class="help">{{ form.password1.help_text|safe }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="form-row" style="gap: 0;">
|
||||
{{ form.password2.errors }}
|
||||
{{ form.password2.label_tag }} {{ form.password2 }}
|
||||
{% if form.password2.help_text %}<div class="help">{{ form.password2.help_text|safe }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="submit-row" style="border: none;">
|
||||
<label> </label><input type="submit" value="{% trans 'Create admin and continue' %}">
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
{% if user.is_authenticated %}
|
||||
<p class="errornote">
|
||||
{% blocktrans trimmed %}
|
||||
@ -66,6 +92,7 @@
|
||||
<label> </label><input type="submit" value="{% trans 'Log in' %}">
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<center>
|
||||
<br/><br/>
|
||||
@ -85,10 +112,6 @@ archivebox manage changepassword <username>
|
||||
<pre>
|
||||
|
||||
</pre>
|
||||
{% has_real_admin_users as real_admins_exist %}
|
||||
{% if not real_admins_exist %}
|
||||
(or set env vars <code>ADMIN_USERNAME</code> + <code>ADMIN_PASSWORD</code>)
|
||||
{% endif %}
|
||||
<br/>
|
||||
</center>
|
||||
|
||||
|
||||
@ -164,8 +164,11 @@ async function main() {
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto(config.loginUrl, {waitUntil: "networkidle2", timeout: 15000});
|
||||
const firstAdminForm = await page.$("#first-admin-form");
|
||||
if (!firstAdminForm) throw new Error("First admin setup form was not shown");
|
||||
await page.type('input[name="username"]', config.username);
|
||||
await page.type('input[name="password"]', config.password);
|
||||
await page.type('input[name="password1"]', config.password);
|
||||
await page.type('input[name="password2"]', config.password);
|
||||
await Promise.all([
|
||||
page.waitForNavigation({waitUntil: "networkidle2", timeout: 15000}),
|
||||
page.click('button[type="submit"], input[type="submit"]'),
|
||||
@ -202,6 +205,10 @@ async function main() {
|
||||
page.waitForNavigation({waitUntil: "networkidle2", timeout: 15000}),
|
||||
page.$eval('button[name="_continue"][form="machine_form"]', (button) => button.click()),
|
||||
]);
|
||||
await page.reload({waitUntil: "networkidle2", timeout: 15000});
|
||||
if (await page.$("#archivebox-setup-wizard")) {
|
||||
throw new Error("Setup wizard remained visible after BASE_URL was saved");
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({finalUrl: page.url(), bodyText: await page.$eval("body", el => el.innerText.slice(0, 500))}));
|
||||
await browser.close();
|
||||
@ -664,8 +671,6 @@ def test_unconfigured_public_host_superuser_can_reach_setup_wizard(tmp_path: Pat
|
||||
env = cli_env(
|
||||
port=port,
|
||||
disable_extractors=True,
|
||||
ADMIN_USERNAME="testadmin",
|
||||
ADMIN_PASSWORD="testpassword",
|
||||
ALLOWED_HOSTS="*",
|
||||
BIND_ADDR=f"127.0.0.1:{port}",
|
||||
)
|
||||
@ -709,7 +714,7 @@ def test_unconfigured_public_host_superuser_can_reach_setup_wizard(tmp_path: Pat
|
||||
"hostname": public_hostname,
|
||||
"loginUrl": f"http://{public_host}/admin/login/?next=/admin/",
|
||||
"username": "testadmin",
|
||||
"password": "testpassword",
|
||||
"password": "ArchiveBox-test-9vK!",
|
||||
},
|
||||
),
|
||||
capture_output=True,
|
||||
|
||||
@ -63,13 +63,14 @@ mkdir -p ~/archivebox/data && cd ~/archivebox
|
||||
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
|
||||
# (shortcut for getting https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/docker-compose.yml)
|
||||
|
||||
# pull and start the current image, then create an admin user for the Web UI
|
||||
# pull and start the current image
|
||||
# (the server initializes a new collection automatically)
|
||||
docker compose pull
|
||||
docker compose up -d --wait
|
||||
docker compose exec archivebox archivebox manage createsuperuser
|
||||
```
|
||||
|
||||
Open <http://admin.archivebox.localhost:8000> and follow the setup wizard to create the first admin and configure web access. Existing `BASE_URL` and security settings are used as-is, so configured servers skip the web-access wizard.
|
||||
|
||||
ArchiveBox installs and enables both ripgrep and [Sonic](https://github.com/valeriansaliou/sonic). Sonic is selected by default in the UI, while ripgrep remains available as the fallback. To select ripgrep explicitly:
|
||||
```bash
|
||||
docker compose exec archivebox archivebox config --set SEARCH_BACKEND_ENGINE=ripgrep
|
||||
@ -198,10 +199,11 @@ docker pull archivebox/archivebox:dev
|
||||
|
||||
mkdir -p ~/archivebox/data && cd ~/archivebox/data
|
||||
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev init
|
||||
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev manage createsuperuser
|
||||
docker run -d --name archivebox -v "$PWD:/data" -p 8000:8000 archivebox/archivebox:dev
|
||||
```
|
||||
|
||||
Then open <http://admin.archivebox.localhost:8000> and follow the setup wizard.
|
||||
|
||||
*(You can create a collection in any directory you want, `~/archivebox/data` is just used as an example here)*
|
||||
|
||||
If you encounter permissions issues, make sure the mounted data directory is writable by its intended owner. Docker startup automatically uses the first non-root owner detected from the existing collection, or the default `archivebox` user when the data directory is root-owned.
|
||||
|
||||
@ -76,7 +76,7 @@ If you're on macOS or Ubuntu, there is an optional auto-setup script provided.
|
||||
curl -fsSL 'https://get.archivebox.io' | bash
|
||||
# shortcut to run https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/bin/setup.sh
|
||||
```
|
||||
The script uses Docker Compose when available, otherwise plain Docker when it can pull the released image. If Docker is unavailable, it shows the native `uv` install plan and pauses so you can cancel before continuing. It initializes the collection, installs ArchiveBox's runtime dependencies, and starts the server; create the first admin afterward with the command printed at the end.
|
||||
The script uses Docker Compose when available, otherwise plain Docker when it can pull the released image. If Docker is unavailable, it shows the native `uv` install plan and pauses so you can cancel before continuing. It initializes the collection, installs ArchiveBox's runtime dependencies, and starts the server. Open the printed Admin UI URL and follow the setup wizard to create the first admin and configure web access.
|
||||
|
||||
Run it as your normal user unless you want a system-owned deployment. When run as root, the script creates the `archivebox` service user and places the collection under that account's home directory; it prints the exact path and follow-up commands when finished.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user